From 81a5dd612f4264116ec527ab9c2bb16e1e052c8f Mon Sep 17 00:00:00 2001 From: nmzik Date: Sat, 5 Sep 2026 16:00:46 +0400 Subject: [PATCH] shader: fix embedded vertex fetch swizzles --- .../host_gpu/renderer/pipeline/shaders.cpp | 71 +---------------- src/graphics/shader/recompiler/BufferFormat.h | 22 ++++++ .../backend/spirv/spirvEmitterHelpers.cpp | 14 +--- .../backend/spirv/spirvEmitterInternal.h | 2 +- .../backend/spirv/spirvEmitterMemory.cpp | 23 ++---- .../backend/spirv/spirvEmitterModule.cpp | 13 +-- .../frontend/translate/Attribute.cpp | 24 +++++- .../frontend/translate/Translate.cpp | 7 +- .../frontend/translate/Translator.h | 2 +- tests/ShaderRecompilerComputeTests.cpp | 26 ++++++ tests/shaderCfgTests.cpp | 79 +++++++++++++++++++ 11 files changed, 163 insertions(+), 120 deletions(-) diff --git a/src/graphics/host_gpu/renderer/pipeline/shaders.cpp b/src/graphics/host_gpu/renderer/pipeline/shaders.cpp index 1ec7356a6..66c694431 100644 --- a/src/graphics/host_gpu/renderer/pipeline/shaders.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/shaders.cpp @@ -553,76 +553,7 @@ void CreatePipelineInternal( EXIT_NOT_IMPLEMENTED(vs_input_info.resources[index].AddTid()); EXIT_NOT_IMPLEMENTED(vs_input_info.resources[index].SwizzleEnabled()); - auto log_unsupported_vertex_swizzle = [index, attr_size, registers_num](uint32_t swizzle, - uint32_t expected) { - static bool logged = false; - if (!logged) { - LOGF("VertexInput: temporary: accepting unsupported dst swizzle at attr %" PRIu32 - " (attr_size=%" PRIu32 ", regs=%" PRIu32 ", swizzle=0x%03" PRIx32 - ", expected=0x%03" PRIx32 ")\n", - static_cast(index), attr_size, registers_num, swizzle, expected); - logged = true; - } - }; - - switch (registers_num) { - case 1: { - auto swizzle = vs_input_info.resources[index].DstSelX(); - if (swizzle != DstSel(4)) { - log_unsupported_vertex_swizzle(swizzle, DstSel(4)); - } - break; - } - case 2: { - auto swizzle = vs_input_info.resources[index].DstSelXY(); - auto expected = (attr_size == 1 ? DstSel(4, 0) : DstSel(4, 5)); - if (swizzle != expected) { - log_unsupported_vertex_swizzle(swizzle, expected); - } - break; - } - case 3: { - auto swizzle = vs_input_info.resources[index].DstSelXYZ(); - auto expected = DstSel(4, 5, 6); - switch (attr_size) { - case 1: expected = DstSel(4, 0, 0); break; - case 2: expected = DstSel(4, 5, 0); break; - default: break; - } - if (swizzle != expected) { - log_unsupported_vertex_swizzle(swizzle, expected); - } - break; - } - case 4: { - auto swizzle = vs_input_info.resources[index].DstSelXYZW(); - auto expected = DstSel(4, 5, 6, 7); - bool supported = false; - switch (attr_size) { - case 1: - expected = DstSel(4, 0, 0, 1); - supported = (swizzle == expected); - break; - case 2: - expected = DstSel(4, 5, 0, 1); - supported = (swizzle == expected); - break; - case 3: - expected = DstSel(4, 5, 6, 1); - supported = (swizzle == expected || swizzle == DstSel(4, 5, 6, 0)); - break; - default: - supported = (swizzle == expected || swizzle == DstSel(4, 5, 6, 1) || - swizzle == DstSel(4, 5, 6, 0)); - break; - } - if (!supported) { - log_unsupported_vertex_swizzle(swizzle, expected); - } - break; - } - default: EXIT("invalid registers_num"); - } + EXIT_IF(registers_num < 1 || registers_num > 4); } vk::PipelineVertexInputStateCreateInfo vertex_input_info {}; diff --git a/src/graphics/shader/recompiler/BufferFormat.h b/src/graphics/shader/recompiler/BufferFormat.h index 03942a7a1..b99a32367 100644 --- a/src/graphics/shader/recompiler/BufferFormat.h +++ b/src/graphics/shader/recompiler/BufferFormat.h @@ -26,6 +26,28 @@ struct BufferFormatInfo { bool packed_bitfield = false; }; +enum class FormattedSourceKind { Memory, Zero, One, Invalid }; + +struct FormattedSource { + FormattedSourceKind kind = FormattedSourceKind::Zero; + uint32_t component = 0; +}; + +constexpr FormattedSource ResolveFormattedSource(const BufferFormatInfo& info, uint32_t selector) { + if (selector == 0u) return {}; + if (selector == 1u) return {FormattedSourceKind::One, 0}; + if (selector < 4u || selector > 7u || info.component_count == 0u) { + return {FormattedSourceKind::Invalid, 0}; + } + // Formatted decoding expands source channels before applying the swizzle. + return {FormattedSourceKind::Memory, (selector - 4u) % info.component_count}; +} + +constexpr uint32_t FormattedConstantBits(const BufferFormatInfo& info, FormattedSourceKind kind) { + if (kind != FormattedSourceKind::One) return 0; + return info.type == ComponentType::Uint || info.type == ComponentType::Sint ? 1u : 0x3f800000u; +} + constexpr Prospero::BufferFormat DecodeTBufferFormat(uint32_t data_format, uint32_t number_format) { return static_cast(((number_format & 0x7u) << 4u) | (data_format & 0xfu)); diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterHelpers.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterHelpers.cpp index e763caef9..ab80dcfd6 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterHelpers.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterHelpers.cpp @@ -175,22 +175,10 @@ uint32_t EmitLocalInvocationIndex(EmitterState& state) { return value; } -uint32_t VertexInputDefaultComponentU32(EmitterState& state, VertexInputScalarKind kind, - uint32_t component) { - if ((component & 3u) != 3u) { - return ConstantU32(state, 0); - } - return ConstantU32(state, kind == VertexInputScalarKind::Float ? 0x3f800000u : 1u); -} - uint32_t EmitVertexParameterComponentU32(EmitterState& state, const InputBinding& input, uint32_t component) { - const auto count = VertexParameterComponentCount(state, input); + const auto count = VertexParameterComponentCount(input); const auto kind = VertexParameterScalarKind(state, input.location); - if (component >= count) { - return VertexInputDefaultComponentU32(state, kind, component); - } - const auto scalar_type = VertexParameterScalarType(state, kind); uint32_t raw = state.builder.AllocateId(); if (count == 1u) { diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h index 05a1937ef..a96920e72 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h @@ -501,7 +501,7 @@ bool PixelParameterIsCustom(const EmitterState& state, uint32_t attr); VertexInputScalarKind VertexParameterScalarKind(const EmitterState& state, uint32_t location); -uint32_t VertexParameterComponentCount(const EmitterState& state, const InputBinding& input); +uint32_t VertexParameterComponentCount(const InputBinding& input); uint32_t VertexParameterScalarType(EmitterState& state, VertexInputScalarKind kind); diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp index fa12ab19f..244902139 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMemory.cpp @@ -386,12 +386,8 @@ IR::MemoryInfo RebaseRawComponent(IR::MemoryInfo mem, uint32_t component) { return mem; } -enum class FormattedSourceKind { Memory, Zero, One }; - -struct FormattedSource { - FormattedSourceKind kind = FormattedSourceKind::Zero; - uint32_t component = 0; -}; +using Format::FormattedSource; +using Format::FormattedSourceKind; FormattedSource ResolveFormattedSource(ValueEmitContext& ctx, const IR::MemoryInfo& mem, const Format::BufferFormatInfo& info, @@ -403,24 +399,17 @@ FormattedSource ResolveFormattedSource(ValueEmitContext& ctx, const IR::MemoryIn } const auto selector = GetDstSel(ctx.state.program.info.buffers[mem.resource].descriptor_swizzle, output_component); - if (selector == 0u) return {}; - if (selector == 1u) return {FormattedSourceKind::One, 0}; - if (selector < 4u) { + const auto source = Format::ResolveFormattedSource(info, selector); + if (source.kind == FormattedSourceKind::Invalid) { ExitDescriptorBindingFailure(ctx.state, IR::DescriptorBindingKind::Buffers, mem.resource, "buffer descriptor has reserved dst_sel"); } - const auto component = selector - 4u; - return component < info.component_count - ? FormattedSource {FormattedSourceKind::Memory, component} - : FormattedSource {}; + return source; } uint32_t FormattedConstant(ValueEmitContext& ctx, const Format::BufferFormatInfo& info, FormattedSourceKind kind) { - if (kind != FormattedSourceKind::One) return ConstantU32(ctx.state, 0); - const auto integer = - info.type == Format::ComponentType::Uint || info.type == Format::ComponentType::Sint; - return ConstantU32(ctx.state, integer ? 1u : 0x3f800000u); + return ConstantU32(ctx.state, Format::FormattedConstantBits(info, kind)); } template diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp index b34c757d5..743ccfc32 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp @@ -336,15 +336,8 @@ VertexInputScalarKind VertexParameterScalarKind(const EmitterState& state, uint3 } } -uint32_t VertexParameterComponentCount(const EmitterState& state, const InputBinding& input) { - uint32_t count = input.component_count; - if (state.stage == ShaderType::Vertex && input.location < ShaderVertexInputInfo::RES_MAX && - input.location < static_cast(state.input_info.vertex->resources_num) && - state.input_info.vertex->resources_dst[input.location].registers_num > 0) { - count = static_cast( - state.input_info.vertex->resources_dst[input.location].registers_num); - } - return std::clamp(count, 1u, 4u); +uint32_t VertexParameterComponentCount(const InputBinding& input) { + return std::clamp(input.component_count, 1u, 4u); } uint32_t VertexParameterScalarType(EmitterState& state, VertexInputScalarKind kind) { @@ -830,7 +823,7 @@ void DefineModule(EmitterState& state) { case IR::StageInputKind::Parameter: if (state.stage == ShaderType::Vertex) { const auto kind = VertexParameterScalarKind(state, input.location); - const auto components = VertexParameterComponentCount(state, input); + const auto components = VertexParameterComponentCount(input); ptr_type = VertexParameterInputPointerType(state, kind, components); } else if (input.per_vertex) { const auto array_type = state.builder.Type( diff --git a/src/graphics/shader/recompiler/frontend/translate/Attribute.cpp b/src/graphics/shader/recompiler/frontend/translate/Attribute.cpp index ffb835c30..827b8fca0 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Attribute.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Attribute.cpp @@ -1,4 +1,5 @@ #include "common/assert.h" +#include "graphics/shader/recompiler/BufferFormat.h" #include "graphics/shader/recompiler/frontend/translate/Translator.h" #include @@ -47,10 +48,27 @@ IR::ExportFlags Translator::AddExportInfo(const Decoder::Instruction& inst) { } void Translator::TranslateEmbeddedFetch(const Decoder::Instruction& inst, uint32_t attribute, - uint32_t component_count) { + uint32_t component_count, + const ShaderBufferResource& resource) { + const auto format = Format::GetFormatInfo(resource.Format()); for (uint32_t component = 0; component < component_count; component++) { - const auto value = - ir.Emit(IR::ValueOpcode::GetAttribute, {IR::Value(attribute), IR::Value(component)}); + auto source = Format::FormattedSource {Format::FormattedSourceKind::Memory, component}; + if (inst.formatted && !inst.typed) { + source = Format::ResolveFormattedSource( + format, GetDstSel(resource.DstSelXYZW(), component)); + if (source.kind == Format::FormattedSourceKind::Invalid) { + EXIT("invalid formatted vertex input %u at pc 0x%08x", attribute, inst.pc); + } + } + IR::Value value; + if (source.kind == Format::FormattedSourceKind::Memory) { + value = ir.Emit(IR::ValueOpcode::GetAttribute, + {IR::Value(attribute), IR::Value(source.component)}); + auto& required = program.info.vertex_fetch_components[attribute]; + required = static_cast(std::max(required, source.component + 1u)); + } else { + value = IR::Value(Format::FormattedConstantBits(format, source.kind)); + } WriteOperand(OffsetOperand(inst.dst, component), value); } } diff --git a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp index 15b44dac4..80636ddfa 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp @@ -1204,12 +1204,9 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& EXIT("embedded vertex fetch at 0x%08x has no resource for attribute %d", instruction.pc, embedded->attrib_id); } - result.info.vertex_fetch_components[static_cast(resource)] = - static_cast(std::max( - result.info.vertex_fetch_components[static_cast(resource)], - embedded->components)); translator.TranslateEmbeddedFetch(instruction, static_cast(resource), - embedded->components); + embedded->components, + options.vertex->resources[resource]); continue; } translator.TranslateInstruction(instruction); diff --git a/src/graphics/shader/recompiler/frontend/translate/Translator.h b/src/graphics/shader/recompiler/frontend/translate/Translator.h index bdf0a9265..a60293f3f 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translator.h +++ b/src/graphics/shader/recompiler/frontend/translate/Translator.h @@ -15,7 +15,7 @@ public: void TranslateInstruction(const Decoder::Instruction& inst); void TranslateEmbeddedFetch(const Decoder::Instruction& inst, uint32_t attribute, - uint32_t component_count); + uint32_t component_count, const ShaderBufferResource& resource); void AddBranchCondition(const CFG::BasicBlock& source, IR::BlockInfo& info); private: diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 3a43c1696..6a309c148 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -18433,6 +18433,27 @@ TestCase BufferLoadFormatXyzwResource32_32FloatAppliesXy01() { return test; } +TestCase BufferLoadFormatXyzwResource323232FloatRepeatsSource() { + 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 + 4); + AppendEnd(&code); + TestCase test; + test.name = "BufferLoadFormatXyzwResource323232FloatRepeatsSource"; + test.code = std::move(code); + test.initial = {0x3e800000u, 0x3f000000u, 0x3f400000u, 0xdeadbeefu, 0, 0, 0, 0}; + test.expected = {0x3e800000u, 0x3f000000u, 0x3f400000u, 0xdeadbeefu, + 0x3e800000u, 0x3f000000u, 0x3f400000u, 0x3e800000u}; + test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_FORMAT_XYZW, + O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.user_data = MakeStructuredStorageBufferData( + 0, 32, false, BufferFormat(Prospero::BufferFormat::k32_32_32Float)); + test.has_user_data = true; + return test; +} + TestCase BufferLoadFormatXyzwResource8_8_8_8UintAppliesWzy1() { using O = ShaderOpcode; @@ -22716,6 +22737,7 @@ std::vector MakeCases() { AddCase(BufferFormatVariants); AddCase(BufferLoadFormatXyzwSnapshotsOverlappingAddress); AddCase(BufferLoadFormatXyzwResource32_32FloatAppliesXy01); + AddCase(BufferLoadFormatXyzwResource323232FloatRepeatsSource); AddCase(BufferLoadFormatXyzwResource8_8_8_8UintAppliesWzy1); AddCase(BufferLoadFormatXyzwInactiveExecPreservesOverlappingAddress); AddCase(BufferFormatStoreVariants); @@ -23087,6 +23109,8 @@ void CheckEmbeddedFetchVertexOffset() { vertex.fetch_buffer_reg = 0; vertex.fetch_attrib_reg = 2; vertex.resources_num = 1; + vertex.resources[0].fields[3] = + BufferFormat(Prospero::BufferFormat::k32_32_32_32Float) << 12u | DstSel(4, 5, 6, 7); vertex.resources_dst[0].attr_id = 0; vertex.resources_dst[0].registers_num = 4; @@ -25966,6 +25990,8 @@ void CheckEmbeddedFetchLaneSpill() { vertex.fetch_embedded = true; vertex.fetch_buffer_reg = 0; vertex.resources_num = 1; + vertex.resources[0].fields[3] = + BufferFormat(Prospero::BufferFormat::k32_32_32_32Float) << 12u | DstSel(4, 5, 6, 7); vertex.resources_dst[0].attr_id = 0; vertex.resources_dst[0].registers_num = 4; diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index fa4d2c941..74aeafeed 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -9203,6 +9203,84 @@ void TestMergedShaderUserDataSnapshot() { } } +void TestEmbeddedVertexFormatSwizzle() { + using namespace ShaderRecompiler; + using namespace ShaderRecompiler::IR; + struct Case { + Prospero::BufferFormat format; + uint32_t swizzle, opcode, components, source_width; + std::array expected; + }; + const Case cases[] = { + {Prospero::BufferFormat::k32_32_32Float, DstSel(4, 5, 6, 7), 3, 4, 3, + {0x3e800000, 0x3f000000, 0x3f400000, 0x3e800000}}, + {Prospero::BufferFormat::k32_32_32Float, DstSel(4, 5, 6, 1), 3, 4, 3, + {0x3e800000, 0x3f000000, 0x3f400000, 0x3f800000}}, + {Prospero::BufferFormat::k32_32_32_32UInt, DstSel(7, 6, 5, 0), 3, 4, 4, + {0x40000000, 0x3f400000, 0x3f000000, 0}}, + {Prospero::BufferFormat::k32_32_32_32UInt, DstSel(7, 0, 0, 0), 0, 1, 4, + {0x40000000, 0, 0, 0}}, + {Prospero::BufferFormat::k32UInt, DstSel(1, 0, 1, 0), 3, 4, 0, + {1, 0, 1, 0}}, + {Prospero::BufferFormat::k32Float, DstSel(1, 0, 1, 0), 3, 4, 0, + {0x3f800000, 0, 0x3f800000, 0}}, + {Prospero::BufferFormat::k32_32_32_32Float, DstSel(7, 6, 5, 4), 0x0e, 4, 4, + {0x3e800000, 0x3f000000, 0x3f400000, 0x40000000}}, + }; + for (const auto &test : cases) { + const uint32_t code[] = {EncodeMubuf0(test.opcode), EncodeMubuf1(9, 0, 0), + EncodeExp0(0x20, (1u << test.components) - 1u), + EncodeExp1(9, 10, 11, 12), EncodeSopp(0x01)}; + Decoder::Program decoded; + Decoder::DecodeProgram(code, decoded); + ShaderVertexInputInfo input{}; + input.resources_num = 1; + input.resources[0].fields[3] = (static_cast(test.format) << 12u) | test.swizzle; + input.resources_dst[0].attr_id = 0; + input.resources_dst[0].registers_num = static_cast(test.components); + Frontend::EmbeddedFetchPlan fetch; + fetch.loads.push_back({.pc = 0, .attrib_id = 0, .components = test.components}); + Frontend::TranslateOptions options{}; + options.stage = ShaderType::Vertex; + options.wave_size = 32; + options.user_data_count = 0; + options.vertex = &input; + options.embedded_fetch = &fetch; + auto program = Frontend::TranslateProgram(decoded, CFG::BuildGraph(decoded), options); + Check(program.info.vertex_fetch_components[0] == test.source_width, + "vertex fetch width follows destination VGPR count instead of selected source channels"); + const uint32_t source[] = {0x3e800000, 0x3f000000, 0x3f400000, 0x40000000}; + for (auto *block : program.blocks) { + for (auto &inst : *block) { + if (inst.GetOpcode() == ValueOpcode::GetAttribute) { + Check(inst.Arg(0).U32() == 0 && inst.Arg(1).U32() < 4, + "vertex fetch selected an invalid attribute channel"); + inst.ReplaceUsesWith(Value(source[inst.Arg(1).U32()])); + } + } + } + RewriteToSsa(program.blocks); + RemoveIdentities(program.blocks); + ConstantPropagationPass(program.blocks); + uint32_t exports = 0; + for (const auto *block : program.blocks) { + for (const auto &inst : *block) { + if (inst.GetOpcode() != ValueOpcode::SetAttribute) continue; + ++exports; + const auto *value = inst.Arg(0).ResolveInstruction(); + Check(value != nullptr && value->GetOpcode() == ValueOpcode::CompositeConstructU32x4, + "vertex fetch export did not retain its four components"); + for (uint32_t component = 0; component < 4; ++component) { + const auto actual = value->Arg(component).Resolve(); + Check(actual.IsImmediate() && actual.U32() == test.expected[component], + "formatted vertex fetch did not apply the source-channel swizzle"); + } + } + } + Check(exports == 1, "vertex fetch test did not reach its export"); + } +} + void TestMeshInputAssembly() { using namespace ShaderRecompiler; using namespace ShaderRecompiler::IR; @@ -12558,6 +12636,7 @@ int main() { TestMeshExportStorage(); TestMergedShaderUserDataSnapshot(); TestMeshInputAssembly(); + TestEmbeddedVertexFormatSwizzle(); TestNewShaderRecompilerSetpcJumpTable(); TestNewShaderRecompilerPrunesUnreachableSetpcMetadata(); TestNewShaderRecompilerSetpcDwordJumpTable();