From 0abe03af94c0a47dadb2bd02b9dee3baf3106721 Mon Sep 17 00:00:00 2001 From: nmzik Date: Fri, 17 Jul 2026 12:54:28 +0200 Subject: [PATCH] Support reverse RGBA16F render targets --- src/graphics/guest_gpu/gpu_defs.h | 26 +++++ .../host_gpu/objects/textureCommon.cpp | 3 + src/graphics/host_gpu/objects/textureCommon.h | 6 +- .../host_gpu/renderer/colorRenderTarget.cpp | 4 +- .../host_gpu/renderer/pipelineCache.cpp | 8 +- src/graphics/host_gpu/renderer/renderDraw.cpp | 9 +- src/graphics/host_gpu/renderer/renderState.h | 2 + .../spirvEmitter/spirvEmitterExport.cpp | 19 +++- .../spirvEmitter/spirvEmitterInternal.h | 1 + src/graphics/shader/shader.cpp | 23 +++- src/graphics/shader/shader.h | 4 + tests/ShaderRecompilerComputeTests.cpp | 53 +++++++++ tests/shaderCfgTests.cpp | 103 +++++++++++++++++- 13 files changed, 245 insertions(+), 16 deletions(-) diff --git a/src/graphics/guest_gpu/gpu_defs.h b/src/graphics/guest_gpu/gpu_defs.h index 0c02d1f..06d1e79 100644 --- a/src/graphics/guest_gpu/gpu_defs.h +++ b/src/graphics/guest_gpu/gpu_defs.h @@ -104,6 +104,32 @@ enum class ChannelOrder : uint32_t { kAltReversed = 3, }; +// Maps a shader's logical RGBA export to the host color-attachment components. Two bits per +// component keep this cheap to carry in shader and pipeline keys. +struct ColorComponentMapping { + uint8_t packed = 0xe4u; // RGBA: 0, 1, 2, 3 + + [[nodiscard]] constexpr uint32_t Map(uint32_t component) const { + return component < 4u ? (packed >> (component * 2u)) & 0x3u : component; + } + + [[nodiscard]] constexpr uint32_t ApplyMask(uint32_t mask) const { + uint32_t mapped = 0; + for (uint32_t component = 0; component < 4u; component++) { + mapped |= ((mask >> component) & 1u) << Map(component); + } + return mapped; + } + + [[nodiscard]] constexpr bool IsIdentity() const { return packed == 0xe4u; } + + bool operator==(const ColorComponentMapping&) const = default; +}; + +static_assert(sizeof(ColorComponentMapping) == sizeof(uint8_t)); + +inline constexpr ColorComponentMapping ColorMappingAbgr {0x1bu}; // 3, 2, 1, 0 + enum class DepthFormat : uint32_t { kInvalid = 0, kZ16 = 1, diff --git a/src/graphics/host_gpu/objects/textureCommon.cpp b/src/graphics/host_gpu/objects/textureCommon.cpp index 8cc4a3a..38a2f4f 100644 --- a/src/graphics/host_gpu/objects/textureCommon.cpp +++ b/src/graphics/host_gpu/objects/textureCommon.cpp @@ -92,6 +92,9 @@ RenderTargetFormatInfo TextureGetRenderTargetFormat(uint32_t raw_layout, uint32_ if (is(Prospero::ChannelLayout::k16_16_16_16, Prospero::ChannelType::kFloat, Prospero::ChannelOrder::kStandard)) { return {VK_FORMAT_R16G16B16A16_SFLOAT, 8}; } + if (is(Prospero::ChannelLayout::k16_16_16_16, Prospero::ChannelType::kFloat, Prospero::ChannelOrder::kReversed)) { + return {VK_FORMAT_R16G16B16A16_SFLOAT, 8, Prospero::ColorMappingAbgr}; + } if (is(Prospero::ChannelLayout::k32, Prospero::ChannelType::kFloat, Prospero::ChannelOrder::kStandard)) { return {VK_FORMAT_R32_SFLOAT, 4}; } diff --git a/src/graphics/host_gpu/objects/textureCommon.h b/src/graphics/host_gpu/objects/textureCommon.h index 0e7d713..42e3b95 100644 --- a/src/graphics/host_gpu/objects/textureCommon.h +++ b/src/graphics/host_gpu/objects/textureCommon.h @@ -3,6 +3,7 @@ #include "common/abi.h" #include "common/common.h" +#include "graphics/guest_gpu/gpu_defs.h" #include "graphics/guest_gpu/tile.h" #include "graphics/host_gpu/utils.h" @@ -47,8 +48,9 @@ enum class TextureUploadDestination { MipLevels, MipAtlas }; enum class TextureUploadSliceLayout { MipChainPerSlice, MipLevelPerSlice }; struct RenderTargetFormatInfo { - VkFormat format = VK_FORMAT_UNDEFINED; - uint32_t bytes_per_element = 0; + VkFormat format = VK_FORMAT_UNDEFINED; + uint32_t bytes_per_element = 0; + Prospero::ColorComponentMapping export_mapping; }; struct TextureUploadLayout { diff --git a/src/graphics/host_gpu/renderer/colorRenderTarget.cpp b/src/graphics/host_gpu/renderer/colorRenderTarget.cpp index 15ff9bd..d11b473 100644 --- a/src/graphics/host_gpu/renderer/colorRenderTarget.cpp +++ b/src/graphics/host_gpu/renderer/colorRenderTarget.cpp @@ -285,7 +285,8 @@ void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const H mask = 0x0f; } - r->target_slot = rt_slot; + r->target_slot = rt_slot; + r->export_mapping = {}; if (rt.base.addr == 0 || mask == 0) { if (graphics_debug_dump_enabled()) { @@ -479,6 +480,7 @@ void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const H r->extent = view_extent; r->base_mip_level = rt.view.current_mip_level; r->buffer_size = backing_size; + r->export_mapping = rt_format.export_mapping; } else { const auto layout = static_cast(rt.info.format); const auto type = static_cast(rt.info.channel_type); diff --git a/src/graphics/host_gpu/renderer/pipelineCache.cpp b/src/graphics/host_gpu/renderer/pipelineCache.cpp index ac4c30b..887c45e 100644 --- a/src/graphics/host_gpu/renderer/pipelineCache.cpp +++ b/src/graphics/host_gpu/renderer/pipelineCache.cpp @@ -82,10 +82,10 @@ PipelineCache::GraphicsPipeline* PipelineCache::CreateGraphicsPipeline( const HW::BlendColor& bclr = ctx->GetBlendColor(); uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {}; for (uint32_t i = 0; i < color_count; i++) { - color_mask[i] = - (colors[i].vulkan_buffer != nullptr - ? render_target_mask_slot(ctx->GetRenderTargetMask(), colors[i].target_slot) - : 0); + color_mask[i] = (colors[i].vulkan_buffer != nullptr + ? colors[i].export_mapping.ApplyMask(render_target_mask_slot( + ctx->GetRenderTargetMask(), colors[i].target_slot)) + : 0); } const HW::ModeControl& mc = ctx->GetModeControl(); diff --git a/src/graphics/host_gpu/renderer/renderDraw.cpp b/src/graphics/host_gpu/renderer/renderDraw.cpp index f69b16a..aa29bcb 100644 --- a/src/graphics/host_gpu/renderer/renderDraw.cpp +++ b/src/graphics/host_gpu/renderer/renderDraw.cpp @@ -748,6 +748,12 @@ static void RefreshShaders(HW::Context* ctx, HW::Shader* sh_ctx, const DrawCallI state->vs_shader = {}; state->ps_shader = {}; state->ps_input_info = {}; + std::array + target_export_mapping {}; + for (uint32_t i = 0; i < state->color_count; i++) { + target_export_mapping[state->color_info[i].target_slot] = + state->color_info[i].export_mapping; + } EXIT_IF(g_render_ctx == nullptr || g_render_ctx->GetGraphicCtx() == nullptr); const auto lane_mask_mode = SelectGraphicsLaneMaskMode(*g_render_ctx->GetGraphicCtx(), 64u); @@ -766,7 +772,8 @@ static void RefreshShaders(HW::Context* ctx, HW::Shader* sh_ctx, const DrawCallI LogDrawPhase(draw.name, "ShaderCompileInfoPS"); } if (!ShaderCompileInfoPS(&pixel_shader_info, &shader_regs, lane_mask_mode, - &state->vs_input_info, &state->ps_input_info, &state->ps_shader)) { + &state->vs_input_info, target_export_mapping, &state->ps_input_info, + &state->ps_shader)) { EXIT("ShaderCompileInfoPS failed for draw %s\n", draw.name); } } diff --git a/src/graphics/host_gpu/renderer/renderState.h b/src/graphics/host_gpu/renderer/renderState.h index 81279f0..623cc01 100644 --- a/src/graphics/host_gpu/renderer/renderState.h +++ b/src/graphics/host_gpu/renderer/renderState.h @@ -4,6 +4,7 @@ #include "common/abi.h" #include "common/assert.h" #include "common/common.h" +#include "graphics/guest_gpu/gpu_defs.h" #include #include @@ -241,6 +242,7 @@ struct RenderColorInfo { uint64_t base_addr = 0; uint64_t buffer_size = 0; uint32_t target_slot = 0; + Prospero::ColorComponentMapping export_mapping; bool color_clear_enable = false; VkClearColorValue color_clear_value {}; }; diff --git a/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterExport.cpp b/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterExport.cpp index 2c26cca..d3a2a2e 100644 --- a/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterExport.cpp +++ b/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterExport.cpp @@ -50,6 +50,23 @@ uint32_t EmitExportVec4F32(EmitterState* state, const IR::Instruction& inst) { return vec; } +uint32_t ApplyMrtExportMapping(EmitterState* state, const IR::Instruction& inst, uint32_t value) { + if (inst.export_info.kind != IR::ExportTargetKind::Mrt || state->pixel_input_info == nullptr || + inst.export_info.index >= state->pixel_input_info->target_export_mapping.size()) { + return value; + } + + const auto mapping = state->pixel_input_info->target_export_mapping[inst.export_info.index]; + if (mapping.IsIdentity()) { + return value; + } + + const auto mapped = state->builder.AllocateId(); + state->builder.AddFunction({OpVectorShuffle, state->vec4_float_type, mapped, value, value, + mapping.Map(0), mapping.Map(1), mapping.Map(2), mapping.Map(3)}); + return mapped; +} + bool ExportWritesData(const IR::Instruction& inst) { switch (inst.export_info.kind) { case IR::ExportTargetKind::Null: @@ -97,7 +114,7 @@ void EmitExport(EmitterState* state, const IR::Instruction& inst) { return; } - const auto value = EmitExportVec4F32(state, inst); + const auto value = ApplyMrtExportMapping(state, inst, EmitExportVec4F32(state, inst)); if (inst.export_info.kind == IR::ExportTargetKind::Position) { const auto pointer = state->builder.AllocateId(); state->builder.AddFunction({OpAccessChain, state->ptr_output_vec4_float, pointer, variable, diff --git a/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterInternal.h b/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterInternal.h index 678d3ed..b2ee662 100644 --- a/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterInternal.h +++ b/src/graphics/shader/recompiler/spirvEmitter/spirvEmitterInternal.h @@ -135,6 +135,7 @@ enum : uint32_t { OpArrayLength = 68, OpDecorate = 71, OpMemberDecorate = 72, + OpVectorShuffle = 79, OpCompositeConstruct = 80, OpCompositeExtract = 81, OpSampledImage = 86, diff --git a/src/graphics/shader/shader.cpp b/src/graphics/shader/shader.cpp index 09ab3a1..6ccb5d7 100644 --- a/src/graphics/shader/shader.cpp +++ b/src/graphics/shader/shader.cpp @@ -791,10 +791,11 @@ static bool ShaderGetStaticInputInfoVS(const HW::VertexShaderInfo* regs, return true; } -static void ShaderGetStaticInputInfoPS(const HW::PixelShaderInfo* regs, - const HW::ShaderRegisters* sh, - const ShaderVertexInputInfo* vs_info, - ShaderPixelInputInfo* ps_info) { +static void ShaderGetStaticInputInfoPS( + const HW::PixelShaderInfo* regs, const HW::ShaderRegisters* sh, + const ShaderVertexInputInfo* vs_info, + std::span target_export_mapping, + ShaderPixelInputInfo* ps_info) { KYTY_PROFILER_FUNCTION(); EXIT_IF(vs_info == nullptr); @@ -834,6 +835,9 @@ static void ShaderGetStaticInputInfoPS(const HW::PixelShaderInfo* regs, for (int i = 0; i < 8; i++) { ps_info->target_output_mode[i] = sh->target_output_mode[i]; + ps_info->target_export_mapping[i] = + sh->target_output_mode[i] != 0 ? target_export_mapping[i] + : Prospero::ColorComponentMapping {}; } ps_info->mrt_output_mask = 0; } @@ -1105,11 +1109,12 @@ bool ShaderCompileInfoVS(const HW::VertexShaderInfo* regs, const HW::ShaderRegis bool ShaderCompileInfoPS(const HW::PixelShaderInfo* regs, const HW::ShaderRegisters* sh, ShaderLaneMaskMode lane_mask_mode, const ShaderVertexInputInfo* vs_info, + std::span target_export_mapping, ShaderPixelInputInfo* ps_info, std::span* spirv) { EXIT_IF(spirv == nullptr); *spirv = {}; - ShaderGetStaticInputInfoPS(regs, sh, vs_info, ps_info); + ShaderGetStaticInputInfoPS(regs, sh, vs_info, target_export_mapping, ps_info); const auto shader_hash = regs->ps_regs.chksum != 0 ? regs->ps_regs.chksum : regs->ps_regs.data_addr; const auto program_id = ShaderGetIdPS(regs, ps_info, false); @@ -1632,6 +1637,14 @@ ShaderId ShaderGetIdPS(const HW::PixelShaderInfo* regs, const ShaderPixelInputIn for (auto mode: input_info->target_output_mode) { ret.ids.push_back(mode); } + for (uint32_t base = 0; base < input_info->target_export_mapping.size(); base += 4u) { + uint32_t packed = 0; + for (uint32_t i = 0; i < 4u; i++) { + packed |= static_cast(input_info->target_export_mapping[base + i].packed) + << (i * 8u); + } + ret.ids.push_back(packed); + } ret.ids.push_back(input_info->mrt_output_mask); for (uint32_t i = 0; i < input_info->input_num; i++) { diff --git a/src/graphics/shader/shader.h b/src/graphics/shader/shader.h index 8186fb4..b0f9322 100644 --- a/src/graphics/shader/shader.h +++ b/src/graphics/shader/shader.h @@ -3,8 +3,10 @@ #include "common/abi.h" #include "common/common.h" +#include "graphics/guest_gpu/gpu_defs.h" #include "graphics/shader/shaderBindings.h" +#include #include #include #include @@ -98,6 +100,7 @@ struct ShaderPixelInputInfo { uint32_t input_num = 0; uint32_t ps_system_input_base = 0; uint8_t target_output_mode[8] = {}; + std::array target_export_mapping = {}; uint32_t mrt_output_mask = 0; uint32_t descriptor_set = 0; bool ps_pos_x = false; @@ -227,6 +230,7 @@ bool ShaderCompileInfoVS(const HW::VertexShaderInfo* regs, const HW::ShaderRegis std::span* spirv); bool ShaderCompileInfoPS(const HW::PixelShaderInfo* regs, const HW::ShaderRegisters* sh, ShaderLaneMaskMode lane_mask_mode, const ShaderVertexInputInfo* vs_info, + std::span target_export_mapping, ShaderPixelInputInfo* input_info, std::span* spirv); bool ShaderCompileInfoCS(const HW::ComputeShaderInfo* regs, const HW::ShaderRegisters* sh, ShaderComputeInputInfo* input_info, std::span* spirv); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 79a9809..a12ad51 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -8676,6 +8676,51 @@ bool CacheFault(void *opaque, PageFaultAccess access, uint64_t vaddr, context->texture->InvalidateMemory(access, vaddr, size, phase); } +[[noreturn]] void RunReverseRenderTargetDeathCase() { + (void)TextureGetRenderTargetFormat(12u, 7u, 3u); + std::_Exit(0x7f); +} + +void CheckReverseRenderTargetFormatContract() { + const auto format = TextureGetRenderTargetFormat(12u, 7u, 2u); + Require("ReverseRenderTarget", "exact format", + format.format == VK_FORMAT_R16G16B16A16_SFLOAT && + format.bytes_per_element == 8u && + format.export_mapping == Prospero::ColorMappingAbgr, + "exact reverse RGBA16F render-target tuple was rejected"); + Require("ReverseRenderTarget", "write masks", + format.export_mapping.ApplyMask(0x1u) == 0x8u && + format.export_mapping.ApplyMask(0x2u) == 0x4u && + format.export_mapping.ApplyMask(0x4u) == 0x2u && + format.export_mapping.ApplyMask(0x8u) == 0x1u && + format.export_mapping.ApplyMask(0xfu) == 0xfu, + "reverse RGBA16F component mask was not mapped exactly once"); + + char path[MAX_PATH]{}; + Require("ReverseRenderTarget", "host", + GetModuleFileNameA(nullptr, path, MAX_PATH) != 0, + "GetModuleFileName failed"); + std::string command = std::string("\"") + path + "\" --reverse-rt-death"; + std::vector mutable_command(command.begin(), command.end()); + mutable_command.push_back('\0'); + STARTUPINFOA startup{sizeof(startup)}; + PROCESS_INFORMATION process{}; + Require("ReverseRenderTarget", "host", + CreateProcessA(nullptr, mutable_command.data(), nullptr, nullptr, FALSE, + CREATE_NO_WINDOW, nullptr, nullptr, &startup, &process) != 0, + "CreateProcess failed"); + Require("ReverseRenderTarget", "host", + WaitForSingleObject(process.hProcess, 10000) == WAIT_OBJECT_0, + "unsupported adjacent render-target tuple timed out"); + DWORD exit_code = 0; + const bool exited = GetExitCodeProcess(process.hProcess, &exit_code) != 0; + CloseHandle(process.hThread); + CloseHandle(process.hProcess); + Require("ReverseRenderTarget", "hard failure", exited && exit_code == 321, + "adjacent unproven render-target tuple did not retain the fatal guard"); + std::printf("[host] %-32s ok\n", "ReverseRenderTargetFormat"); +} + [[noreturn]] void RunImageViewDeathCase(const char *kind) { if (std::strcmp(kind, "sampled") == 0) { (void)SelectSampledColorView(VK_FORMAT_R8G8B8A8_UNORM, @@ -11729,6 +11774,13 @@ int main(int argc, char **argv) { return 0; } #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS + if (argc == 2 && std::strcmp(argv[1], "--reverse-rt-death") == 0) { + RunReverseRenderTargetDeathCase(); + } + if (argc == 2 && std::strcmp(argv[1], "--reverse-rt-only") == 0) { + CheckReverseRenderTargetFormatContract(); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--image-overlap-only") == 0) { CheckImageOverlapResolution(); return 0; @@ -11802,6 +11854,7 @@ int main(int argc, char **argv) { if (argc == 3 && std::strcmp(argv[1], "--metadata-descriptor-death") == 0) { RunMetadataDescriptorDeathCase(argv[2]); } + CheckReverseRenderTargetFormatContract(); CheckSampledColorViews(); CheckSampledDepthResource(); CheckSampledDepthDescriptor(); diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 3ad83bd..3ac27dd 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -4,6 +4,7 @@ #include "common/threads.h" #include "graphics/guest_gpu/hardwareContext.h" #include "graphics/guest_gpu/pm4.h" +#include "graphics/host_gpu/objects/textureCommon.h" #include "graphics/host_gpu/renderer/shaderResourceBarrier.h" #include "graphics/host_gpu/renderer/shaderSubgroup.h" #include "graphics/shader/recompiler/ExecMask.h" @@ -135,6 +136,24 @@ uint32_t SpirvInstructionOpcodeCount(const std::vector& binary, uint32 return count; } +bool SpirvContainsVectorShuffle(const std::vector& binary, + const std::array& selectors) { + for (size_t i = 5; i < binary.size();) { + const uint32_t word = binary[i]; + const uint32_t op = word & 0xffffu; + const uint32_t word_count = word >> 16u; + if (word_count == 0 || i + word_count > binary.size()) { + return false; + } + if (op == 79u && word_count == 9u && + std::equal(selectors.begin(), selectors.end(), binary.begin() + i + 5u)) { + return true; + } + i += word_count; + } + return false; +} + bool SpirvContainsCapability(const std::vector& binary, uint32_t capability) { for (size_t i = 5; i < binary.size();) { const uint32_t word = binary[i]; @@ -5696,6 +5715,82 @@ void TestNewShaderRecompilerExpPixelOutputs() { CheckSpirvBinaryValidates(result.spirv); } +void TestRenderTargetReverseFloat16ExportMapping() { + const auto format = + TextureGetRenderTargetFormat(Prospero::GpuEnumValue(Prospero::ChannelLayout::k16_16_16_16), + Prospero::GpuEnumValue(Prospero::ChannelType::kFloat), + Prospero::GpuEnumValue(Prospero::ChannelOrder::kReversed)); + Check(format.format == VK_FORMAT_R16G16B16A16_SFLOAT && format.bytes_per_element == 8u, + "reverse RGBA16F render target did not retain its native host format " + "and size"); + Check(format.export_mapping == Prospero::ColorMappingAbgr && + format.export_mapping.ApplyMask(0x1u) == 0x8u && + format.export_mapping.ApplyMask(0x2u) == 0x4u && + format.export_mapping.ApplyMask(0x4u) == 0x2u && + format.export_mapping.ApplyMask(0x8u) == 0x1u && + format.export_mapping.ApplyMask(0xfu) == 0xfu, + "reverse RGBA16F render-target export or write-mask mapping is " + "incorrect"); + const auto legacy_alt = TextureGetRenderTargetFormat( + Prospero::GpuEnumValue(Prospero::ChannelLayout::k8_8_8_8), + Prospero::GpuEnumValue(Prospero::ChannelType::kUNorm), + Prospero::GpuEnumValue(Prospero::ChannelOrder::kAlt)); + Check(legacy_alt.format == VK_FORMAT_B8G8R8A8_UNORM && legacy_alt.export_mapping.IsIdentity(), + "legacy BGRA render target acquired a duplicate shader export mapping"); + + const uint32_t shader[] = { + EncodeExp0(0x00, 0xf), + EncodeExp1(0, 1, 2, 3), // MRT0 + 0xbf810000u, + }; + ShaderPixelInputInfo identity_info; + ShaderPixelInputInfo reversed_info; + reversed_info.target_export_mapping[0] = format.export_mapping; + + ShaderRecompiler::CompileOptions options; + options.stage = ShaderType::Pixel; + options.pixel_input_info = &identity_info; + ShaderRecompiler::CompileResult identity_result; + std::string error; + Check(ShaderRecompiler::TryRecompile(shader, options, &identity_result, &error), error.c_str()); + Check(SpirvInstructionOpcodeCount(identity_result.spirv, 79u) == 0u, + "identity MRT export unexpectedly added a component shuffle"); + + options.pixel_input_info = &reversed_info; + ShaderRecompiler::CompileResult reversed_result; + Check(ShaderRecompiler::TryRecompile(shader, options, &reversed_result, &error), error.c_str()); + Check(SpirvContainsVectorShuffle(reversed_result.spirv, {3u, 2u, 1u, 0u}), + "reverse MRT export did not emit a WZYX component shuffle"); + CheckSpirvBinaryValidates(reversed_result.spirv); + + HW::PixelShaderInfo regs {}; + Check(ShaderGetIdPS(®s, &identity_info, false) != + ShaderGetIdPS(®s, &reversed_info, false), + "pixel shader cache identity omitted the render-target export mapping"); + + regs.ps_regs.data_addr = reinterpret_cast(shader); + regs.ps_regs.chksum = 0xf16ab6f000000001ull; + ShaderMappedData mapped {}; + mapped.code_size_bytes = sizeof(shader); + ShaderMapUserData(regs.ps_regs.data_addr, mapped); + HW::ShaderRegisters sh {}; + ShaderVertexInputInfo vs_info {}; + vs_info.stage.program = std::make_shared(); + std::array mappings {}; + mappings[0] = format.export_mapping; + ShaderPixelInputInfo compiled_info {}; + std::span compiled_spirv; + Check(ShaderCompileInfoPS(®s, &sh, ShaderLaneMaskMode::NativeWave, &vs_info, mappings, + &compiled_info, &compiled_spirv) && + compiled_info.target_export_mapping[0].IsIdentity(), + "inactive reverse MRT mapping was not normalized out of the shader cache key"); + sh.target_output_mode[0] = 4; + Check(ShaderCompileInfoPS(®s, &sh, ShaderLaneMaskMode::NativeWave, &vs_info, mappings, + &compiled_info, &compiled_spirv) && + compiled_info.target_export_mapping[0] == format.export_mapping, + "active reverse MRT mapping was lost before shader specialization"); +} + void TestNewShaderRecompilerEarlyZDisabledWhenPixelKillEnabled() { constexpr uint32_t ExecutionModeEarlyFragmentTests = 9; @@ -6510,10 +6605,11 @@ void TestPixelProgramCacheDescriptorSetIdentity() { } ShaderVertexInputInfo vs_info {}; vs_info.stage.program = std::move(vs_program); + const std::array identity_mappings {}; ShaderPixelInputInfo ps_info {}; std::span spirv; Check(ShaderCompileInfoPS(®s, &sh, ShaderLaneMaskMode::NativeWave, &vs_info, - &ps_info, &spirv), + identity_mappings, &ps_info, &spirv), "pixel program-cache transition failed to compile"); const auto expected_set = has_vs_descriptors ? 1u : 0u; Check(ps_info.descriptor_set == expected_set && ps_info.stage.program != nullptr && @@ -6537,9 +6633,11 @@ void TestPixelProgramCacheDescriptorSetIdentity() { auto compile_mask_mode = [&](ShaderLaneMaskMode mode) { ShaderVertexInputInfo vs_info {}; vs_info.stage.program = std::make_shared(); + const std::array identity_mappings {}; ShaderPixelInputInfo ps_info {}; std::span spirv; - Check(ShaderCompileInfoPS(&mask_regs, &sh, mode, &vs_info, &ps_info, &spirv), + Check(ShaderCompileInfoPS(&mask_regs, &sh, mode, &vs_info, identity_mappings, &ps_info, + &spirv), "pixel lane-mask cache transition failed to compile"); Check(ps_info.stage.program != nullptr && ps_info.stage.program->lane_mask_mode == mode, "pixel program cache reused a different lane-mask lowering"); @@ -6715,6 +6813,7 @@ int main() { TestNewShaderRecompilerVertexExportUsesLaneExecMask(); TestNewShaderRecompilerPerInvocationMasks(); TestNewShaderRecompilerExpPixelOutputs(); + TestRenderTargetReverseFloat16ExportMapping(); TestNewShaderRecompilerEarlyZDisabledWhenPixelKillEnabled(); TestScalarProvenanceRealWideMoveLowering(); TestScalarProvenanceRealCarryAndScalarLoads();