From 5e27a28ab0dd7e83a4b17b58bdcc63399d7db67d Mon Sep 17 00:00:00 2001 From: nmzik Date: Sun, 16 Aug 2026 03:35:27 +0200 Subject: [PATCH] shader: fix missing homogeneous semantics --- .../backend/spirv/spirvEmitterValueMemory.cpp | 23 ++++++-- src/graphics/shader/recompiler/ir/ShaderIR.h | 23 ++++---- .../ir/passes/ResourceMaterialization.cpp | 8 +-- src/graphics/shader/shader.cpp | 6 ++- tests/ResourceTrackingTests.cpp | 37 +++++++++++++ tests/ShaderRecompilerComputeTests.cpp | 54 ++++++++++++++++++- 6 files changed, 130 insertions(+), 21 deletions(-) diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterValueMemory.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterValueMemory.cpp index 356b14a6c..24c5fef0f 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterValueMemory.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterValueMemory.cpp @@ -222,8 +222,8 @@ Prospero::BufferFormat BufferFormat(const ValueEmitContext& ctx, const IR::Inst& IR::MemoryInfo RebaseFormattedComponent(IR::MemoryInfo mem, Prospero::BufferFormat format, uint32_t component) { - if (!mem.typed && component != 0u) { - const auto component_slot = component * 4u; + if (!mem.typed) { + const auto component_slot = mem.component_index * 4u; mem.offset = mem.offset >= component_slot ? mem.offset - component_slot : 0u; mem.offset += Format::GetFormatComponentByteOffset(format, component); } @@ -234,12 +234,27 @@ uint32_t FormattedLoad(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Me const auto format = BufferFormat(ctx, inst, mem); if (!Format::IsKnownFormat(format)) return LoadWord(ctx, inst, mem); const auto info = Format::GetFormatInfo(format); - const auto component = mem.component_index; + auto component = mem.component_index; + if (!mem.typed) { + const auto selector = + GetDstSel(ctx.state.program.info.buffers[mem.resource].descriptor_swizzle, component); + if (selector == 0u) return ConstantU32(ctx.state, 0); + if (selector == 1u) { + const auto integer = info.type == Format::ComponentType::Uint || + info.type == Format::ComponentType::Sint; + return ConstantU32(ctx.state, integer ? 1u : 0x3f800000u); + } + if (selector < 4u) { + ExitDescriptorBindingFailure(ctx.state, IR::DescriptorBindingKind::Buffers, + mem.resource, "buffer descriptor has reserved dst_sel"); + } + component = selector - 4u; + } if (component >= info.component_count) return ConstantU32(ctx.state, 0); uint32_t raw = 0; const auto bits = info.component_bits[component]; if (info.packed_bitfield) { - raw = LoadWord(ctx, inst, mem); + raw = LoadWord(ctx, inst, RebaseFormattedComponent(mem, format, component)); const auto type = IsSignedFormatComponent(info.type) ? ctx.state.int_type : ctx.state.uint_type; const auto source = diff --git a/src/graphics/shader/recompiler/ir/ShaderIR.h b/src/graphics/shader/recompiler/ir/ShaderIR.h index c9f4bce5b..dc6591174 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIR.h +++ b/src/graphics/shader/recompiler/ir/ShaderIR.h @@ -168,17 +168,18 @@ struct DescriptorValue { struct BufferResource { static constexpr uint32_t NoImageAlias = UINT32_MAX; - uint32_t source = 0; - uint32_t first_use_pc = 0; - uint32_t max_byte_extent = 0; - uint32_t packed_stride = 0; - Prospero::BufferFormat descriptor_format = Prospero::BufferFormat::kInvalid; - uint32_t image_alias = NoImageAlias; - bool read = false; - bool written = false; - bool atomic = false; - bool formatted = false; - bool scalar = false; + uint32_t source = 0; + uint32_t first_use_pc = 0; + uint32_t max_byte_extent = 0; + uint32_t packed_stride = 0; + Prospero::BufferFormat descriptor_format = Prospero::BufferFormat::kInvalid; + uint32_t descriptor_swizzle = DstSel(4, 5, 6, 7); + uint32_t image_alias = NoImageAlias; + bool read = false; + bool written = false; + bool atomic = false; + bool formatted = false; + bool scalar = false; bool operator==(const BufferResource& other) const = default; }; diff --git a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp index b056e2864..58a3ec30e 100644 --- a/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ResourceMaterialization.cpp @@ -190,7 +190,8 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh return false; } if (buffer.packed_stride != descriptor.PackedStride() || - buffer.descriptor_format != descriptor.Format()) { + buffer.descriptor_format != descriptor.Format() || + buffer.descriptor_swizzle != descriptor.DstSelXYZW()) { if (error != nullptr) { *error = fmt::format("buffer descriptor {} no longer matches specialization", i); } @@ -392,8 +393,9 @@ bool SpecializeResources(Program& program, const ResourceSnapshot& snapshot, std } return false; } - next.buffers[i].packed_stride = descriptor.PackedStride(); - next.buffers[i].descriptor_format = descriptor.Format(); + next.buffers[i].packed_stride = descriptor.PackedStride(); + next.buffers[i].descriptor_format = descriptor.Format(); + next.buffers[i].descriptor_swizzle = descriptor.DstSelXYZW(); } for (uint32_t i = 0; i < next.addresses.size(); i++) { next.addresses[i].specialized_base = diff --git a/src/graphics/shader/shader.cpp b/src/graphics/shader/shader.cpp index bf5ef7be0..439f9a2a4 100644 --- a/src/graphics/shader/shader.cpp +++ b/src/graphics/shader/shader.cpp @@ -1016,8 +1016,9 @@ static std::string ShaderDescribeSpecialization(const ShaderRecompiler::IR::Prog program.values != nullptr ? program.values->srt_reads.size() : 0u); for (uint32_t i = 0; i < program.info.buffers.size(); i++) { const auto& buffer = program.info.buffers[i]; - ret += fmt::format(" b{}[stride={} format={}]", i, buffer.packed_stride, - static_cast(buffer.descriptor_format)); + ret += + fmt::format(" b{}[stride={} format={} swizzle=0x{:03x}]", i, buffer.packed_stride, + static_cast(buffer.descriptor_format), buffer.descriptor_swizzle); } for (uint32_t i = 0; i < program.info.images.size(); i++) { const auto& image = program.info.images[i]; @@ -1054,6 +1055,7 @@ static void ShaderAppendNativeSpecialization(std::vector& for (const auto& buffer: program.info.buffers) { ids.push_back(buffer.packed_stride); ids.push_back(static_cast(buffer.descriptor_format)); + ids.push_back(buffer.descriptor_swizzle); } ids.push_back(static_cast(program.info.images.size())); for (const auto& image: program.info.images) { diff --git a/tests/ResourceTrackingTests.cpp b/tests/ResourceTrackingTests.cpp index 2cef71bc8..3decc98df 100644 --- a/tests/ResourceTrackingTests.cpp +++ b/tests/ResourceTrackingTests.cpp @@ -575,6 +575,42 @@ void TestAddressMaterializationAndSpecialization() { "typed address specialization was not applied"); } +void TestBufferSwizzleSpecialization() { + Fixture fixture; + const auto handle = fixture.Buffer( + {fixture.UserData(0), fixture.UserData(1), fixture.UserData(2), + fixture.UserData(3)}, + 4); + MemoryInfo memory; + memory.kind = ResourceKind::Buffer; + memory.formatted = true; + fixture.Emit(ValueOpcode::LoadBufferU32, + {handle, Value(0u), Value(0u), Value(0u), Value(true)}, + fixture.AddMemory(memory, 4)); + fixture.PlanAndTrack(); + + constexpr auto swizzle = Libs::Graphics::DstSel(4, 5, 0, 1); + std::array user_data{ + 0, 16u << 16u, 1, + swizzle | + (static_cast( + Libs::Graphics::Prospero::BufferFormat::k32_32Float) + << 12u) | + (1u << 24u)}; + SrtRuntime runtime{.user_data = user_data}; + ResourceSnapshot snapshot; + std::string error; + Check(MaterializeResources(fixture.program, runtime, snapshot, &error) && + SpecializeResources(fixture.program, snapshot, &error) && + fixture.program.info.buffers[0].descriptor_swizzle == swizzle && + ValidateResourceSpecialization(fixture.program, snapshot, &error), + "buffer destination selectors were not specialized"); + + snapshot.buffers[0].dwords[3] ^= 1u << 9u; + Check(!ValidateResourceSpecialization(fixture.program, snapshot, &error), + "buffer swizzle change did not invalidate specialization"); +} + void TestShaderInfoAndBindingLayout() { Fixture fixture; const auto handle = fixture.Buffer( @@ -667,6 +703,7 @@ int main() { Run("runtime-rooted loop", TestLoopCycleEnteredThroughRuntimeValue); Run("invariant loop phi", TestInvariantLoopPhi); Run("address materialization", TestAddressMaterializationAndSpecialization); + Run("buffer swizzle specialization", TestBufferSwizzleSpecialization); Run("shader info and bindings", TestShaderInfoAndBindingLayout); Run("resource limit", TestResourceLimitIsTransactional); } catch (const std::exception &exception) { diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 67549fc25..2e8eda601 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -1144,7 +1144,7 @@ std::array MakeStructuredStorageBufferData(u32 stride_bytes, std::array data{}; data[1] = (stride_bytes & 0x3fffu) << 16u; data[2] = num_records; - data[3] = 1u << 24u; + data[3] = DstSel(4, 5, 6, 7) | (1u << 24u); if (add_tid) { data[3] |= 1u << 23u; } @@ -14278,6 +14278,56 @@ TestCase BufferLoadFormatXyzwSnapshotsOverlappingAddress() { return test; } +TestCase BufferLoadFormatXyzwResource32_32FloatAppliesXy01() { + using O = ShaderOpcode; + + std::vector code; + AppendVMovU32(&code, 20, 0); + AppendBufferLoadOpcode(&code, 0x03, 0, 20); + for (u32 i = 0; i < 4; i++) { + AppendStoreVgpr(&code, i, i); + } + AppendEnd(&code); + + TestCase test; + test.name = "BufferLoadFormatXyzwResource32_32FloatAppliesXy01"; + test.code = std::move(code); + test.initial = {0x3f800000u, 0x40000000u, 0, 0}; + test.expected = {0x3f800000u, 0x40000000u, 0, 0x3f800000u}; + test.opcodes = {O::VMovB32, O::BufferLoadFormatXyzw, + O::BufferStoreDword, O::SEndpgm}; + test.user_data = MakeStructuredStorageBufferData( + 16, 1, false, BufferFormat(Prospero::BufferFormat::k32_32Float)); + test.user_data[3] = (test.user_data[3] & ~0xfffu) | DstSel(4, 5, 0, 1); + test.has_user_data = true; + return test; +} + +TestCase BufferLoadFormatXyzwResource8_8_8_8UintAppliesWzy1() { + using O = ShaderOpcode; + + std::vector code; + AppendVMovU32(&code, 20, 0); + AppendBufferLoadOpcode(&code, 0x03, 0, 20); + for (u32 i = 0; i < 4; i++) { + AppendStoreVgpr(&code, i, i); + } + AppendEnd(&code); + + TestCase test; + test.name = "BufferLoadFormatXyzwResource8_8_8_8UintAppliesWzy1"; + test.code = std::move(code); + test.initial = {0x11223344u, 0x55667788u, 0x99aabbccu, 0xddeeff00u}; + test.expected = {0x11u, 0x22u, 0x33u, 1u}; + test.opcodes = {O::VMovB32, O::BufferLoadFormatXyzw, + O::BufferStoreDword, O::SEndpgm}; + test.user_data = MakeStructuredStorageBufferData( + 16, 1, false, BufferFormat(Prospero::BufferFormat::k8_8_8_8UInt)); + test.user_data[3] = (test.user_data[3] & ~0xfffu) | DstSel(7, 6, 5, 1); + test.has_user_data = true; + return test; +} + TestCase BufferLoadFormatXyzwInactiveExecPreservesOverlappingAddress() { using O = ShaderOpcode; @@ -17264,6 +17314,8 @@ std::vector MakeCases() { AddCase(BufferStoreVariants); AddCase(BufferFormatVariants); AddCase(BufferLoadFormatXyzwSnapshotsOverlappingAddress); + AddCase(BufferLoadFormatXyzwResource32_32FloatAppliesXy01); + AddCase(BufferLoadFormatXyzwResource8_8_8_8UintAppliesWzy1); AddCase(BufferLoadFormatXyzwInactiveExecPreservesOverlappingAddress); AddCase(BufferFormatStoreVariants); AddCase(BufferStoreFormatXResource16UintWritesHalfword);