From bc2664cefa341f7c534a55107ed1feb92525b78b Mon Sep 17 00:00:00 2001 From: nmzik Date: Wed, 14 Oct 2026 21:40:12 +0200 Subject: [PATCH] shader: decode comparison gathers with explicit LOD Co-authored-by: fxpw --- .../recompiler/frontend/decode/ImageOps.cpp | 1 + .../frontend/decode/ShaderDecoder.cpp | 2 + .../frontend/decode/ShaderDecoder.h | 1 + .../recompiler/frontend/translate/Memory.cpp | 1 + tests/ShaderRecompilerComputeTests.cpp | 101 +++++++++++++++++- tests/shaderCfgTests.cpp | 27 +++++ 6 files changed, 132 insertions(+), 1 deletion(-) diff --git a/src/graphics/shader/recompiler/frontend/decode/ImageOps.cpp b/src/graphics/shader/recompiler/frontend/decode/ImageOps.cpp index 53d8b4f70..36e16892f 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ImageOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/ImageOps.cpp @@ -185,6 +185,7 @@ constexpr MimgGatherInfo MIMG_GATHER_OPCODE_LIST[] = { {0x44u, Opcode::IMAGE_GATHER4_L, ImageSampleFlagLod}, {0x47u, Opcode::IMAGE_GATHER4_LZ, ImageSampleFlagLevelZero}, {0x48u, Opcode::IMAGE_GATHER4_C, ImageSampleFlagCompare}, + {0x4cu, Opcode::IMAGE_GATHER4_C_L, ImageSampleFlagCompare | ImageSampleFlagLod}, {0x4fu, Opcode::IMAGE_GATHER4_C_LZ, ImageSampleFlagCompare | ImageSampleFlagLevelZero}, {0x57u, Opcode::IMAGE_GATHER4_LZ_O, diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp index fd0ec7d1f..f59460eaa 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp @@ -143,6 +143,7 @@ std::string FormatMimg(const Instruction& inst) { case Opcode::IMAGE_GATHER4_L: case Opcode::IMAGE_GATHER4_LZ: case Opcode::IMAGE_GATHER4_C: + case Opcode::IMAGE_GATHER4_C_L: case Opcode::IMAGE_GATHER4_C_LZ: case Opcode::IMAGE_GATHER4_LZ_O: case Opcode::IMAGE_GATHER4_C_O: @@ -611,6 +612,7 @@ std::string InstructionToString(const Instruction& inst) { case Opcode::IMAGE_GATHER4_L: case Opcode::IMAGE_GATHER4_LZ: case Opcode::IMAGE_GATHER4_C: + case Opcode::IMAGE_GATHER4_C_L: case Opcode::IMAGE_GATHER4_C_LZ: case Opcode::IMAGE_GATHER4_LZ_O: case Opcode::IMAGE_GATHER4_C_O: diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h index 1630376f6..69e86fdab 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h @@ -699,6 +699,7 @@ enum class Opcode { IMAGE_GATHER4_L, IMAGE_GATHER4_LZ, IMAGE_GATHER4_C, + IMAGE_GATHER4_C_L, IMAGE_GATHER4_C_LZ, IMAGE_GATHER4_LZ_O, IMAGE_GATHER4_C_O, diff --git a/src/graphics/shader/recompiler/frontend/translate/Memory.cpp b/src/graphics/shader/recompiler/frontend/translate/Memory.cpp index 8eb843f02..76220cc18 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Memory.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Memory.cpp @@ -1162,6 +1162,7 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) { case Decoder::Opcode::IMAGE_GATHER4_L: case Decoder::Opcode::IMAGE_GATHER4_LZ: case Decoder::Opcode::IMAGE_GATHER4_C: + case Decoder::Opcode::IMAGE_GATHER4_C_L: case Decoder::Opcode::IMAGE_GATHER4_C_LZ: case Decoder::Opcode::IMAGE_GATHER4_LZ_O: case Decoder::Opcode::IMAGE_GATHER4_C_O: diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 3c6f521aa..c94f0f242 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -12569,6 +12569,103 @@ public: check_layout(false, DstSel(4, 4, 4, 4)); check_layout(false, DstSel(4, 4, 4, 1)); check_layout(true, DstSel(4, 0, 0, 1)); + for (const u32 wave : {32u, 64u}) { + TestCase test; + test.name = wave == 32u ? "ImageGatherCompareLod32" : "ImageGatherCompareLod64"; + test.has_user_data = test.has_compute_info = true; + test.compute_info.wave_size = wave; + test.compute_info.threads_num[0] = wave; + test.compute_info.threads_num[1] = test.compute_info.threads_num[2] = 1u; + test.compute_info.thread_ids_num = 1u; + test.compute_info.lds_size_dwords = 0; + ShaderTextureResource descriptor{{static_cast(base >> 8u), + (u32(Prospero::BufferFormat::k32Float) << 20u) | (3u << 30u), 3u << 14u, + (u32(Prospero::ImageType::kColor2D) << 28u) | + (u32(Prospero::TileMode::kStandard4KB) << 20u) | (1u << 16u) | DstSel(4, 4, 4, 4), + 0, 0x00700000u | (1u << 4u), 0, 0}}; + ShaderSamplerResource sampler_descriptor{{0x92u | (1u << 12u), 0xfff000u, 1u << 26u, 0}}; + std::copy_n(descriptor.fields, 8u, test.user_data.begin() + 12u); + std::copy_n(sampler_descriptor.fields, 4u, test.user_data.begin() + 20u); + test.code.push_back(EncodeVop1(0x01, 40, Vgpr(0))); + AppendVMovLiteral(&test.code, 25, std::bit_cast(0.5f)); + AppendVMovLiteral(&test.code, 26, std::bit_cast(0.5f)); + constexpr std::array mip_depths{0.1f, 0.9f, 0.7f, 0.3f}; + constexpr std::array gather_order{2u, 3u, 1u, 0u}; + for (u32 mip = 0; mip < 2u; ++mip) { + AppendVMovLiteral(&test.code, 22, std::bit_cast(float(mip))); + for (const float reference : {0.2f, 0.5f, 0.8f}) { + AppendVMovLiteral(&test.code, 19, std::bit_cast(reference)); + // Exact captured NSA order: reference v19, coordinates v25/v26, LOD v22. + test.code.insert(test.code.end(), {0xf130010au, 0x00a30013u, 0x00161a19u}); + for (u32 component = 0; component < 4u; ++component) { + AppendStoreVgprAtLaneDwordOffset(&test.code, component, 40, test.expected.size()); + const float depth = mip == 0u ? 0.6f : mip_depths[gather_order[component]]; + test.expected.insert(test.expected.end(), wave, reference < depth ? 0x3f800000u : 0u); + } + } + } + AppendEnd(&test.code); + test.user_data[50] = test.expected.size() * sizeof(u32); + test.user_data[51] = 3u << 28u; + test.initial.assign(test.expected.size(), 0xdeadbeefu); + test.expected_mip_descriptors = 2u; + test.required_spirv = {"OpImageDrefGather", "OpSwitch"}; + test.decoded_counts = {{"IMAGE_GATHER4_C_L", 6u}}; + const auto compiled = CompileCase(test, SubgroupSize()); + Require(test.name, "comparison mip resource", compiled.program.info.images.size() == 1u && + compiled.program.info.images[0].depth_compare, + "explicit comparison gather lost its depth resource"); + std::memset(mapped, 0, allocation_size); + RenderContext context(m_runtime_context); + auto &scheduler = context.GetCommandScheduler(); + HW::Context registers{}; + HW::UserConfig user_config{}; + HW::Shader shaders{}; + scheduler.Begin(registers, user_config, shaders); + context.MapMemory(base, allocation_size); + auto &executor = context.GetRenderExecutor(); + auto binding = RenderExecutorTestAccess::ResolveTexture( + executor, compiled.program.info.images[0], compiled.resources.images[0]); + auto &cache = context.GetTextureCache(); + Image sampled; + sampled.view = cache.FindTexture(binding.image_id, binding.desc); + auto &image = cache.GetImage(binding.image_id); + Require(test.name, "native depth mip backing", + image.info.pixel_format == vk::Format::eD32Sfloat && image.backing.mip_levels == 2u, + "comparison gather did not allocate both depth mips"); + std::vector pixels(16u, std::bit_cast(0.6f)); + for (const auto depth : mip_depths) pixels.push_back(std::bit_cast(depth)); + auto staging = CreateHostBuffer(test.name, pixels.size() * sizeof(u32), + vk::BufferUsageFlagBits::eTransferSrc, pixels); + image.Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, + {}, scheduler.Current().Handle()); + std::array copies{}; + for (u32 mip = 0; mip < 2u; ++mip) { + copies[mip].bufferOffset = mip == 0u ? 0u : 16u * sizeof(u32); + copies[mip].imageSubresource = {vk::ImageAspectFlagBits::eDepth, mip, 0, 1}; + copies[mip].imageExtent = {4u >> mip, 4u >> mip, 1u}; + } + scheduler.Current().Handle().copyBufferToImage(staging.buffer, image.backing.image, + vk::ImageLayout::eTransferDstOptimal, copies.size(), copies.data()); + image.Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, + {}, scheduler.Current().Handle()); + scheduler.Finish(); + DestroyBuffer(&staging); + sampled.image = image.backing.image; + sampled.format = image.info.pixel_format; + sampled.mip_levels = 2u; + sampled.layout = image.backing.state.layout; + const auto sampler = context.GetSamplerCache().GetSampler(sampler_descriptor, false); + auto output = CreateStorageBuffer(test.name, test.initial, test.initial.size()); + Dispatch(test, compiled, output, nullptr, &sampled, nullptr, nullptr, sampler); + Require(test.name, "explicit LOD, comparison reference and gather component order", + ReadBuffer(test.name, output, test.expected.size()) == test.expected, + "comparison gather selected the wrong mip, reference or component order"); + DestroyBuffer(&output); + RenderExecutorTestAccess::ResetBindings(executor); + context.UnmapMemory(base, allocation_size); + scheduler.Finish(); + } Require(name, "unmap", Libs::LibKernel::Memory::KernelMunmap(base, allocation_size) == 0, "comparison texture unmap failed"); Require(name, "release", Libs::LibKernel::Memory::KernelReleaseDirectMemory( @@ -16612,7 +16709,7 @@ public: view.viewType = test.sampled_image_view_type; view.format = source_image->format; view.subresourceRange = { - vk::ImageAspectFlagBits::eColor, mip, 1, + image.depth_compare ? vk::ImageAspectFlagBits::eDepth : vk::ImageAspectFlagBits::eColor, mip, 1, test.sampled_image_view_base_layer, test.sampled_image_view_layers != 0 ? test.sampled_image_view_layers @@ -20589,6 +20686,7 @@ CoverageClass ClassifyOpcode(ShaderOpcode opcode, case Opcode::IMAGE_SAMPLE: case Opcode::IMAGE_GATHER4_LZ: case Opcode::IMAGE_GATHER4_C: + case Opcode::IMAGE_GATHER4_C_L: case Opcode::IMAGE_GATHER4_C_LZ: case Opcode::IMAGE_GATHER4_LZ_O: case Opcode::IMAGE_GATHER4_C_O: @@ -46858,6 +46956,7 @@ int main(int argc, char **argv) { } if (argc == 2 && std::strcmp(argv[1], "--image-gather-lod-only") == 0) { VulkanHarness vulkan; + vulkan.CheckComparisonDepthTexture(); RunCase(&vulkan, ImageGatherExplicitLod()); RunCase(&vulkan, ImageGatherLodPerLane<0>()); RunCase(&vulkan, ImageGatherLodPerLane<1>()); diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 0a034dc7c..183d99cc8 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -5567,6 +5567,26 @@ void TestNewShaderRecompilerCubeSampleCoordinates() { void TestImageAddressOperands() { using namespace ShaderRecompiler; + // Captured at PC 0x10cc in compute shader 0041b6a03db7472d. + const uint32_t captured[] = {0xf130010au, 0x00a30013u, 0x00161a19u}; + Decoder::Instruction gather; + Decoder::DecodeInstruction(captured, 0, gather); + Check(gather.opcode == Decoder::Opcode::IMAGE_GATHER4_C_L && + gather.opcode_id == 0x4cu && gather.word_count == 3u && + gather.image_dimension == Decoder::ImageDimension::Dim2D && + gather.dmask == 1u && gather.data_components == 4u && + gather.data_dwords == 4u && gather.data_bits == 32u && + gather.image_sample_flags == (Decoder::ImageSampleFlagCompare | + Decoder::ImageSampleFlagLod) && + gather.image_address_components == 4u && gather.image_nsa_dwords == 1u && + gather.dst.reg == 0u && gather.src0.reg == 19u && + gather.src1.reg == 12u && gather.src2.reg == 20u && + gather.image_nsa_addr[0] == 25u && gather.image_nsa_addr[1] == 26u && + gather.image_nsa_addr[2] == 22u && !gather.glc && !gather.slc && + !gather.image_r128, + "captured IMAGE_GATHER4_C_L lost compare/LOD, NSA, or result metadata"); + Check(Decoder::InstructionToString(gather).find("IMAGE_GATHER4_C_L") != std::string::npos, + "captured IMAGE_GATHER4_C_L is missing from the decoded dump"); struct Case { uint32_t opcode; uint32_t dimension; @@ -5581,6 +5601,7 @@ void TestImageAddressOperands() { Case{0x20, 1, true, false, 2, 1}, // packed xy Case{0x3d, 1, true, false, 5, 4}, // offset, half bias, full dref, packed xy Case{0x20, 2, true, true, 3, 2}, // packed xy/z in nonconsecutive VGPRs + Case{0x4c, 1, false, true, 4, 4}, // full dref, xy, and LOD in NSA VGPRs Case{0x0e, 1, false, false, 1, 1}, // resource query mip }; for (const auto &test : cases) { @@ -5602,6 +5623,12 @@ void TestImageAddressOperands() { options.stage = ShaderType::Compute; options.input_info.compute = &compute; auto program = Frontend::TranslateProgram(decoded, CFG::BuildGraph(decoded), options); + if (test.opcode == 0x4cu) { + Check(program.memory_info.size() == 1u && + program.memory_info[0].image_sample_flags == gather.image_sample_flags && + program.memory_info[0].data_dwords == 4u, + "IMAGE_GATHER4_C_L lost its shared compare/LOD metadata during translation"); + } IR::RewriteToSsa(program.blocks); IR::ConstantPropagationPass(program.blocks); uint32_t addresses = 0;