From dca7ece2caa0335bc650cfcb7a4f559c1a1a0438 Mon Sep 17 00:00:00 2001 From: nmzik Date: Sat, 29 Aug 2026 13:32:20 +0400 Subject: [PATCH] shader: legalize 1D level-zero gathers --- .../backend/spirv/spirvEmitterImage.cpp | 59 +++++++++++++++++++ tests/ShaderRecompilerComputeTests.cpp | 39 ++++++++++++ tests/shaderCfgTests.cpp | 24 -------- 3 files changed, 98 insertions(+), 24 deletions(-) diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp index 0418f7d8..bc281777 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterImage.cpp @@ -439,6 +439,49 @@ uint32_t UnpackImageGather(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uin return result; } +uint32_t EmitOneDimensionalGatherLz(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t pc, + uint32_t coord, bool integer) { + auto& state = ctx.state; + state.builder.RequireCapability(CapabilityImageQuery); + const auto image = LoadSampledImageDescriptor(state, mem, pc, ImageViewKind::Dim1D); + const auto width = state.builder.AllocateId(); + state.builder.AddFunction( + {OpImageQuerySizeLod, TypeU32(state), width, image, ConstantU32(state, 0)}); + const auto width_f32 = state.builder.AllocateId(); + state.builder.AddFunction({OpConvertUToF, TypeF32(state), width_f32, width}); + const auto left = state.builder.AllocateId(); + state.builder.AddFunction( + {OpExtInst, TypeF32(state), left, GlslStd450(state), GlslFloor, + Binary(state, OpFSub, TypeF32(state), + Binary(state, OpFMul, TypeF32(state), coord, width_f32), + ConstantF32(state, 0x3f000000u))}); + + const auto sampled = MakeSampledImage(state, mem, pc, ImageViewKind::Dim1D); + const auto vector_type = integer ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4); + const auto scalar_type = integer ? TypeU32(state) : TypeF32(state); + const auto component = ImageConversionFormat(state, mem).format == Prospero::BufferFormat::kInvalid + ? ImageGatherComponent(mem.dmask) + : 0u; + uint32_t values[2] {}; + for (uint32_t index = 0; index < 2u; index++) { + const auto sample_coord = Binary( + state, OpFDiv, TypeF32(state), + Binary(state, OpFAdd, TypeF32(state), left, + ConstantF32(state, index == 0u ? 0x3f000000u : 0x3fc00000u)), + width_f32); + const auto texel = state.builder.AllocateId(); + state.builder.AddFunction({OpImageSampleExplicitLod, vector_type, texel, sampled, sample_coord, + ImageOperandsLodMask, ZeroF32(state)}); + values[index] = state.builder.AllocateId(); + state.builder.AddFunction( + {OpCompositeExtract, scalar_type, values[index], texel, component}); + } + const auto result = state.builder.AllocateId(); + state.builder.AddFunction({OpCompositeConstruct, vector_type, result, values[0], values[1], + values[1], values[0]}); + return result; +} + uint32_t PackImageTexel(ValueEmitContext& ctx, const IR::MemoryInfo& mem, uint32_t texel) { const auto info = ImageConversionFormat(ctx.state, mem); if (info.format == Prospero::BufferFormat::kInvalid) return texel; @@ -613,6 +656,22 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) { const auto coord = CoordF32(ctx, mem, *address, layout.coord, ImageViewCoordinateComponents(view)); if (op == IR::ValueOpcode::ImageGatherRaw) { + if (view == ImageViewKind::Dim1D) { + if (dref || !HasFlag(mem, Decoder::ImageSampleFlagLevelZero) || + HasFlag(mem, Decoder::ImageSampleFlagOffset) || + HasFlag(mem, Decoder::ImageSampleFlagGatherHorizontal)) { + ctx.Fail(inst, "has an unsupported 1D gather variant"); + return true; + } + const auto sample = EmitOneDimensionalGatherLz(ctx, mem, pc, coord, integer); + ctx.Define(inst, ResultVector(ctx, UnpackImageGather(ctx, mem, sample), integer, + false, mem, true)); + return true; + } + if (view == ImageViewKind::Dim1DArray) { + ctx.Fail(inst, "has an unsupported 1D-array gather"); + return true; + } const auto sampled = MakeSampledImage(state, mem, pc, view); const auto sample = state.builder.AllocateId(); std::vector words = diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index a08bf9c2..bdedc6d0 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -19040,6 +19040,44 @@ TestCase ImageLoad1DUsesScalarCoordinate() { return test; } +TestCase ImageGather2DInstructionWith1DDescriptor() { + using O = ShaderOpcode; + + std::vector code; + AppendVMovLiteral(&code, 20, 0x3f000000u); + AppendVMovLiteral(&code, 21, 0x3f000000u); + code.push_back(EncodeMimg0(0x47, 0x1)); + code.push_back(EncodeMimg1(0, 20)); + for (u32 i = 0; i < 4u; i++) { + AppendStoreVgpr(&code, i, i); + } + AppendEnd(&code); + + auto image = MakeRgbaImage(4, 1); + SetRgbaPixel(&image, 4, 1, 0, 0x40000000u, 0, 0, 0); + SetRgbaPixel(&image, 4, 2, 0, 0x40400000u, 0, 0, 0); + + TestCase test; + test.name = "ImageGather2DInstructionWith1DDescriptor"; + test.code = code; + test.expected = {0x40000000u, 0x40400000u, 0x40400000u, 0x40000000u}; + test.opcodes = {O::V_MOV_B32, O::IMAGE_GATHER4_LZ, O::BUFFER_STORE_DWORD, + O::S_ENDPGM}; + test.image_width = 4; + test.image_height = 1; + test.sampled_image_rgba = image; + test.sampled_image_type = vk::ImageType::e1D; + test.sampled_image_view_type = vk::ImageViewType::e1D; + test.user_data = MakeSampledTextureData(Prospero::BufferFormat::k32Float); + test.user_data[3] = static_cast(Prospero::ImageType::kColor1D) + << 28u; + test.has_user_data = true; + test.required_spirv = {"sampled_1d", "OpImageQuerySizeLod", + "OpImageSampleExplicitLod", "Floor"}; + test.forbidden_spirv = {"OpImageGather"}; + return test; +} + TestCase ImageLoad1DArrayUsesLayerCoordinate() { using O = ShaderOpcode; @@ -20643,6 +20681,7 @@ std::vector MakeCases() { AddCase(ImageSamplePackedUintConvertsSampleAndGather); AddCase(ImageLoadR128IgnoresAdjacentMaskSgprs); AddCase(ImageLoad1DUsesScalarCoordinate); + AddCase(ImageGather2DInstructionWith1DDescriptor); AddCase(ImageLoad1DArrayUsesLayerCoordinate); AddCase(ImageLoad1DArrayDescriptorUsesSelectedLayer); AddCase(ImageLoadMipUsesVaddr2Lod2D); diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 9d2f95e6..cbc14438 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -5109,30 +5109,6 @@ void TestNewShaderRecompilerNullImageUsesCanonical2DView() { "null image retained the decoded 1D descriptor binding"); } -void TestNewShaderRecompilerRejectsOneDimensionalGather() { - constexpr std::array cases{ - std::pair{0u, Prospero::ImageType::kColor1D}, - std::pair{4u, Prospero::ImageType::kColor1DArray}, - }; - for (const auto &[dimension, type] : cases) { - const uint32_t shader[] = { - EncodeMimg0(0x47, 0x1, false, dimension), - EncodeMimg1(0, 0, 1, 0), - 0xbf810000u, - }; - auto user_data = ImageTestUserData(type); - auto options = MakeCompileOptions(ShaderType::Compute); - options.user_data = user_data.data(); - - ShaderRecompiler::CompileResult result; - std::string error; - Check(!ShaderRecompiler::TryRecompile(shader, options, result, &error) && - Common::ContainsStr(error, - "1D image gather is not supported by SPIR-V"), - "1D image gather escaped as invalid SPIR-V"); - } -} - void TestNewShaderRecompilerImageGatherVariants() { const uint32_t shader[] = { EncodeMimg0(0x47, 0x1),