diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp index 4292adbd5..cdfab42ae 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp @@ -388,6 +388,19 @@ void DefineInputs(EmitterState& state) { } } for (auto& input: state.inputs) { + if (state.program.stage == ShaderType::Pixel && + input.kind == IR::StageInputKind::Parameter) { + const auto location = PixelParameterLocation(state, input.location); + const auto alias = std::ranges::find_if(state.inputs, [&](const InputBinding& other) { + return other.kind == IR::StageInputKind::Parameter && other.variable_id != 0 && + PixelParameterLocation(state, other.location) == location; + }); + if (alias != state.inputs.end()) { + EXIT_IF(alias->per_vertex != input.per_vertex); + input.variable_id = alias->variable_id; + continue; + } + } uint32_t type = TypeU32(state); switch (input.kind) { case IR::StageInputKind::VertexIndex: diff --git a/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp b/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp index bcaa4b5e0..8b8a4ae6a 100644 --- a/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp @@ -226,6 +226,18 @@ void CollectPixelInputs(const Program& program, const ShaderPixelInputInfo* pixe } } } + // Aliases of a vertex output share one SPIR-V interface variable. If any + // alias reads raw vertices, interpolate the other aliases from those too. + for (uint32_t input = 0; input < pixel->input_num; input++) { + for (uint32_t alias = 0; alias < pixel->input_num; alias++) { + if (ShaderPixelParameterMappedLocation(*pixel, input) == + ShaderPixelParameterMappedLocation(*pixel, alias) && + ShaderPixelParameterIsFlat(*pixel, input) == + ShaderPixelParameterIsFlat(*pixel, alias)) { + per_vertex[input] = per_vertex[input] || per_vertex[alias]; + } + } + } for (uint32_t input = 0; input < pixel->input_num; input++) { AddInput(info, StageInputKind::Parameter, input, 4, fmt::format("in_param_{}", input), per_vertex[input]); diff --git a/src/graphics/shader/rectListShader.cpp b/src/graphics/shader/rectListShader.cpp index 0d8088493..c809e83d7 100644 --- a/src/graphics/shader/rectListShader.cpp +++ b/src/graphics/shader/rectListShader.cpp @@ -40,12 +40,15 @@ std::vector GetParameters(const ShaderVertexInputInfo& vertex_info, } std::vector parameters; + std::array output_locations {}; for (const auto input: active_inputs) { const auto input_location = ShaderPixelParameterMappedLocation(*pixel_info, input); - if ((vertex_info.stage.program->param_export_mask & (1u << input_location)) != 0) { - parameters.push_back({input_location, - ShaderPixelParameterLocation(*pixel_info, active_inputs, input), + const auto output_location = ShaderPixelParameterLocation(*pixel_info, active_inputs, input); + if ((vertex_info.stage.program->param_export_mask & (1u << input_location)) != 0 && + !output_locations[output_location]) { + parameters.push_back({input_location, output_location, ShaderPixelParameterIsFlat(*pixel_info, input)}); + output_locations[output_location] = true; } } return parameters; diff --git a/src/graphics/shader/shaderPixelParameter.cpp b/src/graphics/shader/shaderPixelParameter.cpp index 89cc629e9..8ba5a771b 100644 --- a/src/graphics/shader/shaderPixelParameter.cpp +++ b/src/graphics/shader/shaderPixelParameter.cpp @@ -19,19 +19,31 @@ uint32_t ShaderPixelParameterLocation(const ShaderPixelInputInfo& info, std::span active_inputs, uint32_t input) { std::array used_locations {}; for (const auto active_input: active_inputs) { - auto location = ShaderPixelParameterMappedLocation(info, active_input); - if (location < used_locations.size() && used_locations[location]) { - location = active_input; - while (location < used_locations.size() && used_locations[location]) { - location++; + used_locations[ShaderPixelParameterMappedLocation(info, active_input)] = true; + } + std::array group_locations; + group_locations.fill(UINT32_MAX); + for (const auto active_input: active_inputs) { + const auto mapped = ShaderPixelParameterMappedLocation(info, active_input); + const auto group = mapped * 2u + ShaderPixelParameterIsFlat(info, active_input); + auto& location = group_locations[group]; + if (location == UINT32_MAX) { + location = mapped; + // Smooth and custom interpolation read the same vertex output. Only + // differing flat/smooth rectangle outputs need separate locations. + if (group_locations[group ^ 1u] != UINT32_MAX) { + location = 0; + while (location < used_locations.size() && used_locations[location]) { + location++; + } + EXIT_NOT_IMPLEMENTED(location >= used_locations.size()); } - EXIT_NOT_IMPLEMENTED(location >= used_locations.size()); + used_locations[location] = true; } if (active_input == input) { return location; } - used_locations[location] = true; } return ShaderPixelParameterMappedLocation(info, input); } diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index dc2eb7fde..e6813a3a2 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -1276,6 +1276,43 @@ size_t CountText(const std::string &text, const std::string &needle) { return count; } +void CheckPixelParameterAliases() { + constexpr const char *name = "PixelParameterAliases"; + ShaderPixelInputInfo pixel{}; + pixel.input_num = 2; + const std::array pair = {0, 1}; + for (const auto flat : {0u, 0x400u}) { + pixel.interpolator_settings[0] = flat | 3u; + pixel.interpolator_settings[1] = flat | 3u; + Require(name, "same-mode nonidentity alias", + ShaderPixelParameterLocation(pixel, pair, 0) == 3u && + ShaderPixelParameterLocation(pixel, pair, 1) == 3u, + "aliases of one export with the same interpolation mode must " + "share that physical location"); + } + pixel.interpolator_settings[0] = 3u; + pixel.interpolator_settings[1] = 0x423u; + pixel.custom_interpolation_mask = 2u; + Require(name, "smooth/custom alias", + ShaderPixelParameterLocation(pixel, pair, 0) == 3u && + ShaderPixelParameterLocation(pixel, pair, 1) == 3u, + "custom interpolation must retain its shared vertex export"); + + pixel.input_num = 3; + pixel.custom_interpolation_mask = 0; + pixel.interpolator_settings[0] = 0x400u; + pixel.interpolator_settings[1] = 0u; + pixel.interpolator_settings[2] = 1u; + const std::array active = {0, 1, 2}; + Require(name, "reserved physical locations", + ShaderPixelParameterLocation(pixel, active, 0) == 0u && + ShaderPixelParameterLocation(pixel, active, 1) == 2u && + ShaderPixelParameterLocation(pixel, active, 2) == 1u, + "a synthesized opposite-mode alias must not occupy another " + "vertex export's physical location"); + std::printf("[host] %-32s ok\n", name); +} + void CheckRectListShaders() { constexpr const char *name = "RectListShaders"; @@ -1357,6 +1394,25 @@ void CheckRectListShaders() { "duplicate pixel mappings must share one vertex input and keep " "distinct patch outputs"); + for (const auto flat : {0u, 0x400u}) { + pixel.interpolator_settings[0] = flat; + pixel.interpolator_settings[1] = flat; + const auto shared = BuildRectListShaders(vertex, &pixel); + ValidateSpirv(name, shared.control); + ValidateSpirv(name, shared.evaluation); + Require(name, "same-mode alias disassembly", + tools.Disassemble(shared.control, &control_text) && + tools.Disassemble(shared.evaluation, &evaluation_text), + "failed to disassemble shared rectangle-list parameters"); + Require(name, "same-mode interface deduplication", + CountText(control_text, " Location 0") == 2 && + CountText(evaluation_text, " Location 0") == 2 && + control_text.find(" Location 1") == std::string::npos && + evaluation_text.find(" Location 1") == std::string::npos, + "same-mode aliases must produce one input and one output at " + "the shared location in each rectangle-list stage"); + } + const auto position_only = BuildRectListShaders(vertex, nullptr); ValidateSpirv(name, position_only.control); ValidateSpirv(name, position_only.evaluation); @@ -25588,6 +25644,60 @@ GraphicsCase GraphicsPackedHalfCentroid() { return test; } +GraphicsCase GraphicsPackedHalfInputAlias(bool second_weights) { + auto test = GraphicsPackedHalfCentroid(); + test.name = second_weights ? "GraphicsPackedHalfAliasSecondWeights" + : "GraphicsPackedHalfInputAlias"; + test.pixel_custom_interpolation_mask = 2u; + test.pixel_interpolator_settings = {0u, 0x420u}; + // The head shader reads one exported vec4 through two logical inputs: + // ordinary interpolation and raw per-vertex packed-half interpolation. + // Move the captured raw attr0.x loads to logical attr1.x, still mapped to 0. + for (const auto word : {0u, 1u, 6u}) { + test.fragment_code[word] |= 1u << 10u; + } + test.fragment_code.resize(test.fragment_code.size() - 3u); + test.fragment_code.push_back(EncodeVintrp(0x00, 16, 0, 1, 0)); + test.fragment_code.push_back(EncodeVintrp(0x01, 16, 0, 1, 1)); + AppendVMovLiteral(&test.fragment_code, 17, 0x3f800000u); + test.fragment_code.push_back(EncodeExp0(0x00, 0xf)); + test.fragment_code.push_back(EncodeExp1(14, 15, 16, 17)); + AppendEnd(&test.fragment_code); + // The smooth component (2,4,8) must vary independently of packed UVs. + test.vertices[3] = 0x40000000u; + test.vertices[9] = 0x40800000u; + test.vertices[15] = 0x41000000u; + if (second_weights) { + // Change the right vertex from x=7 to x=3: weights become (1/2,1/4,1/4). + test.vertices[6] = 0x40400000u; + test.expected_pixel = {0x3f800000u, 0x3fe00000u, 0x40800000u, 0x3f800000u}; + } else { + test.expected_pixel = {0x3f600000u, 0x3fd00000u, 0x40700000u, 0x3f800000u}; + } + test.opcodes.insert(test.opcodes.end(), + {ShaderOpcode::V_INTERP_P1_F32, ShaderOpcode::V_INTERP_P2_F32, + ShaderOpcode::V_MOV_B32}); + return test; +} + +GraphicsCase GraphicsSmoothRawInputAlias() { + auto test = GraphicsPackedHalfInputAlias(false); + test.name = "GraphicsSmoothRawInputAlias"; + test.pixel_perspective_centroid_vgpr = UINT32_MAX; + // No explicit barycentric builtin: alias promotion must discover that the + // ordinary input needs weights while the raw alias returns vertex zero. + test.fragment_code = {EncodeVintrp(0x00, 0, 0, 1, 0), + EncodeVintrp(0x01, 0, 0, 1, 1), + EncodeVintrp(0x02, 1, 1, 1, 2), + EncodeExp0(0x00, 0xf), EncodeExp1(0, 1, 0, 1)}; + AppendEnd(&test.fragment_code); + test.expected_pixel = {0x40700000u, 0x40000000u, 0x40700000u, 0x40000000u}; + test.opcodes = {ShaderOpcode::V_INTERP_P1_F32, ShaderOpcode::V_INTERP_P2_F32, + ShaderOpcode::V_INTERP_MOV_F32, ShaderOpcode::EXP, + ShaderOpcode::S_ENDPGM}; + return test; +} + GraphicsCase GraphicsAncillaryLayer(bool front_face) { GraphicsCase test; test.name = front_face ? "GraphicsAncillaryAfterFrontFace" : "GraphicsAncillaryLayer"; @@ -26232,6 +26342,9 @@ std::vector MakeGraphicsCases() { GraphicsInterpolationExport(), GraphicsPositionWExport(), GraphicsPackedHalfCentroid(), + GraphicsPackedHalfInputAlias(false), + GraphicsPackedHalfInputAlias(true), + GraphicsSmoothRawInputAlias(), GraphicsAncillaryLayer(false), GraphicsAncillaryLayer(true), GraphicsAncillarySampleId(), @@ -30806,6 +30919,16 @@ int main(int argc, char **argv) { RunGraphicsCase(&vulkan, GraphicsPackedHalfCentroid()); return 0; } + if (argc == 2 && std::strcmp(argv[1], "--pixel-alias-only") == 0) { + CheckPixelParameterAliases(); + CheckRectListShaders(); + VulkanHarness vulkan; + RunGraphicsCase(&vulkan, GraphicsPackedHalfCentroid()); + RunGraphicsCase(&vulkan, GraphicsPackedHalfInputAlias(false)); + RunGraphicsCase(&vulkan, GraphicsPackedHalfInputAlias(true)); + RunGraphicsCase(&vulkan, GraphicsSmoothRawInputAlias()); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--clip-control-only") == 0) { CheckClipControlDepthClipState(); return 0; @@ -31168,6 +31291,7 @@ int main(int argc, char **argv) { CheckEmbeddedFetchVertexOffset(); CheckEmbeddedFetchLaneSpill(); CheckTessellationPrograms(); + CheckPixelParameterAliases(); CheckRectListShaders(); CheckIndirectImageKeySwitch(); CheckPs5GameExampleImageClearRuntimeShape();