diff --git a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp index 38bcc3aac..8a680c636 100644 --- a/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/pipelineCache.cpp @@ -617,6 +617,13 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms( ShaderParams pixel_params; if (pixel_active) { pixel_params = PrepareProgram(pixel_regs, sh, target_export_mapping, pixel_info); + if (!mesh_active && !tess_active && Prospero::IsRectList(user_config.GetPrimType())) { + pixel_info.parameter_mode = ShaderPixelParameterMode::Rectangle; + } else if (context.GetModeControl().provoking_vtx_last) { + pixel_info.parameter_mode = ShaderPixelParameterMode::LastVertex; + } else { + pixel_info.parameter_mode = ShaderPixelParameterMode::FirstVertex; + } const auto& blend = context.GetBlendControl(0); pixel_info.dual_source_blending = blend.enable && !context.GetRenderTarget(0).info.blend_bypass && diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp index cd69d2d37..26fb09d81 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp @@ -177,6 +177,13 @@ uint32_t EmitAttribute(EmitterState& state, uint32_t attr, uint32_t chan) { return value; }; if (input->per_vertex) { + if (PixelParameterIsFlat(state, attr)) { + const auto value = load_per_vertex( + state.input_info.pixel->parameter_mode == ShaderPixelParameterMode::LastVertex ? 2u : 0u); + const auto bits = state.builder.AllocateId(); + state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value); + return bits; + } const auto barycentric_kind = state.input_info.pixel->ps_no_perspective ? IR::StageInputKind::BaryCoordNoPerspective : IR::StageInputKind::BaryCoordSmooth; @@ -231,8 +238,12 @@ uint32_t EmitInterpolationParameter(ValueEmitContext& ctx, uint32_t attr, uint32 return value; }; - const auto selected_vertex = (mode + 1u) % 3u; - uint32_t value = load_vertex(selected_vertex); + uint32_t selected_vertex = (mode + 1u) % 3u; + if (mode == 2u && PixelParameterIsFlat(state, attr)) { + selected_vertex = + state.input_info.pixel->parameter_mode == ShaderPixelParameterMode::LastVertex ? 2u : 0u; + } + uint32_t value = load_vertex(selected_vertex); if (!PixelParameterIsCustom(state, attr) && mode < 2u) { const auto delta = state.builder.AllocateId(); state.builder.AddFunction(spv::OpFSub, TypeF32(state), delta, value, load_vertex(0)); diff --git a/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp b/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp index 3f7eb662c..9528c7753 100644 --- a/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp @@ -227,15 +227,16 @@ 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. + // Mixed interpolation modes share raw vertices at their guest export slot. + // Rectangle expansion alone supplies separate flat and smooth outputs. for (uint32_t input = 0; input < pixel->input_num; input++) { for (uint32_t alias = 0; alias < pixel->input_num; alias++) { + const bool same_mode = ShaderPixelParameterIsFlat(*pixel, input) == + ShaderPixelParameterIsFlat(*pixel, alias); if (ShaderPixelParameterMappedLocation(*pixel, input) == ShaderPixelParameterMappedLocation(*pixel, alias) && - ShaderPixelParameterIsFlat(*pixel, input) == - ShaderPixelParameterIsFlat(*pixel, alias)) { - per_vertex[input] = per_vertex[input] || per_vertex[alias]; + (pixel->parameter_mode != ShaderPixelParameterMode::Rectangle || same_mode)) { + per_vertex[input] = per_vertex[input] || per_vertex[alias] || !same_mode; } } } @@ -244,7 +245,8 @@ void CollectPixelInputs(const Program& program, const ShaderPixelInputInfo* pixe per_vertex[input]); } for (uint32_t input = 0; input < pixel->input_num; input++) { - if (interpolated[input] && per_vertex[input]) { + if (interpolated[input] && per_vertex[input] && + !ShaderPixelParameterIsFlat(*pixel, input)) { const auto kind = pixel->ps_no_perspective ? StageInputKind::BaryCoordNoPerspective : StageInputKind::BaryCoordSmooth; AddInput(info, kind, 0, 3, diff --git a/src/graphics/shader/shader.cpp b/src/graphics/shader/shader.cpp index cd3db6e09..fee4ba91c 100644 --- a/src/graphics/shader/shader.cpp +++ b/src/graphics/shader/shader.cpp @@ -663,6 +663,7 @@ void BuildStageStaticKey(const ShaderPixelInputInfo& info, std::vector key.push_back(static_cast(info.ps_front_face)); key.push_back(static_cast(info.ps_ancillary)); key.push_back(static_cast(info.ps_no_perspective)); + key.push_back(static_cast(info.parameter_mode)); key.push_back(static_cast(info.ps_pixel_kill_enable)); key.push_back(static_cast(info.ps_depth_export_enable)); key.push_back(static_cast(info.ps_sample_mask_export_enable)); diff --git a/src/graphics/shader/shader.h b/src/graphics/shader/shader.h index e20eff2a0..d1c0b24ef 100644 --- a/src/graphics/shader/shader.h +++ b/src/graphics/shader/shader.h @@ -159,6 +159,7 @@ struct ShaderComputeInputInfo: ShaderWorkgroupInputInfo { }; enum class ShaderAlphaBlendSource : uint8_t { None, SourceAlpha, SourceAlphaOne, SourceAlphaZero }; +enum class ShaderPixelParameterMode : uint8_t { FirstVertex, LastVertex, Rectangle }; struct ShaderPixelInputInfo { uint32_t interpolator_settings[32] = {0}; @@ -180,6 +181,7 @@ struct ShaderPixelInputInfo { bool ps_front_face = false; bool ps_ancillary = false; bool ps_no_perspective = false; + ShaderPixelParameterMode parameter_mode = ShaderPixelParameterMode::FirstVertex; bool ps_pixel_kill_enable = false; bool ps_depth_export_enable = false; bool ps_sample_mask_export_enable = false; diff --git a/src/graphics/shader/shaderPixelParameter.cpp b/src/graphics/shader/shaderPixelParameter.cpp index 290082d91..f86b8e4f2 100644 --- a/src/graphics/shader/shaderPixelParameter.cpp +++ b/src/graphics/shader/shaderPixelParameter.cpp @@ -30,6 +30,9 @@ uint32_t ShaderPixelParameterMappedLocation(const ShaderPixelInputInfo& info, ui uint32_t ShaderPixelParameterLocation(const ShaderPixelInputInfo& info, std::span active_inputs, uint32_t input) { + if (info.parameter_mode != ShaderPixelParameterMode::Rectangle) { + return ShaderPixelParameterMappedLocation(info, input); + } std::array used_locations {}; for (const auto active_input: active_inputs) { used_locations[ShaderPixelParameterMappedLocation(info, active_input)] = true; @@ -42,8 +45,7 @@ uint32_t ShaderPixelParameterLocation(const ShaderPixelInputInfo& info, 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. + // Rectangle expansion broadcasts flat aliases through separate outputs. if (group_locations[group ^ 1u] != UINT32_MAX) { location = 0; while (location < used_locations.size() && used_locations[location]) { diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 49b9a2731..45c64f61c 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -1353,6 +1353,7 @@ struct GraphicsCase { std::vector push_constants; std::vector pixel_interpolator_settings; bool pixel_no_perspective = false; + bool provoking_vtx_last = false; std::vector vertices; bool pixel_ancillary = false; bool pixel_front_face = false; @@ -1449,6 +1450,20 @@ void CheckPixelParameterAliases() { pixel.interpolator_settings[1] = 0u; pixel.interpolator_settings[2] = 1u; const std::array active = {0, 1, 2}; + Require(name, "ordinary mixed aliases", + ShaderPixelParameterLocation(pixel, active, 0) == 0u && + ShaderPixelParameterLocation(pixel, active, 1) == 0u && + ShaderPixelParameterLocation(pixel, active, 2) == 1u, + "flat/smooth aliases must retain the real vertex export location"); + std::vector ordinary_key, rectangle_key, provoking_key; + BuildStageStaticKey(pixel, ordinary_key); + pixel.parameter_mode = ShaderPixelParameterMode::LastVertex; + BuildStageStaticKey(pixel, provoking_key); + pixel.parameter_mode = ShaderPixelParameterMode::Rectangle; + BuildStageStaticKey(pixel, rectangle_key); + Require(name, "interpolation specialization", + ordinary_key != rectangle_key && ordinary_key != provoking_key, + "rectangle and provoking-vertex modes must participate in the shader key"); Require(name, "reserved physical locations", ShaderPixelParameterLocation(pixel, active, 0) == 0u && ShaderPixelParameterLocation(pixel, active, 1) == 2u && @@ -1473,6 +1488,7 @@ void CheckRectListShaders() { vertex.stage.program = &vertex_program; vertex.stage.resources = &empty_snapshot; ShaderPixelInputInfo pixel{}; + pixel.parameter_mode = ShaderPixelParameterMode::Rectangle; pixel.input_num = 2; pixel.interpolator_settings[0] = 0x400u; pixel.interpolator_settings[1] = 0; @@ -1774,6 +1790,8 @@ CompiledShader CompileFragmentCase(const GraphicsCase &test) { ? 1u : static_cast(test.pixel_interpolator_settings.size()); pixel_info.ps_no_perspective = test.pixel_no_perspective; + pixel_info.parameter_mode = test.provoking_vtx_last ? ShaderPixelParameterMode::LastVertex + : ShaderPixelParameterMode::FirstVertex; pixel_info.ps_ancillary = test.pixel_ancillary; pixel_info.ps_front_face = test.pixel_front_face; pixel_info.ps_pos_w = test.pixel_position_w; @@ -16928,6 +16946,12 @@ public: raster.frontFace = vk::FrontFace::eCounterClockwise; raster.lineWidth = 1.0f; + vk::PipelineRasterizationProvokingVertexStateCreateInfoEXT provoking_vertex{}; + if (test.provoking_vtx_last) { + provoking_vertex.provokingVertexMode = vk::ProvokingVertexModeEXT::eLastVertex; + raster.pNext = &provoking_vertex; + } + vk::PipelineMultisampleStateCreateInfo multisample{}; multisample.sType = vk::StructureType::ePipelineMultisampleStateCreateInfo; multisample.rasterizationSamples = test.samples; @@ -35871,6 +35895,38 @@ GraphicsCase GraphicsSmoothRawInputAlias() { return test; } +GraphicsCase GraphicsSmoothFlatInputAlias(bool flat_first, bool provoking_last = false) { + auto test = GraphicsSmoothRawInputAlias(); + test.name = provoking_last + ? (flat_first ? "GraphicsFlatSmoothAliasLast" : "GraphicsSmoothFlatAliasLast") + : (flat_first ? "GraphicsFlatSmoothInputAlias" : "GraphicsSmoothFlatInputAlias"); + test.pixel_custom_interpolation_mask = 0; + test.provoking_vtx_last = provoking_last; + test.pixel_interpolator_settings = flat_first ? std::vector{0x400u, 0u} + : std::vector{0u, 0x400u}; + const u32 smooth = flat_first ? 1u : 0u; + const u32 flat = smooth ^ 1u; + // One export carries smooth values (2,4,8) and packed light IDs (5,7,11). + // Flat P0 preserves the ID bits; flat P1/P2 uses the same provoking vertex. + test.vertices[4] = 0x3f800005u; + test.vertices[10] = 0x3f800007u; + test.vertices[16] = 0x3f80000bu; + test.fragment_code = {EncodeVintrp(0x00, 0, smooth, 1, 0), + EncodeVintrp(0x01, 0, smooth, 1, 1), + EncodeVintrp(0x02, 1, flat, 2, 2), + EncodeVop2(0x1b, 1, InlineU32(63), 1), + EncodeVop1(0x06, 1, Vgpr(1)), + EncodeVintrp(0x00, 2, flat, 1, 0), + EncodeVintrp(0x01, 2, flat, 1, 1), + EncodeExp0(0x00, 0xf), EncodeExp1(0, 1, 2, 0)}; + AppendEnd(&test.fragment_code); + test.expected_pixel = {0x40700000u, provoking_last ? 0x41300000u : 0x40a00000u, + provoking_last ? 0x41000000u : 0x40000000u, 0x40700000u}; + test.opcodes.insert(test.opcodes.end(), + {ShaderOpcode::V_AND_B32, ShaderOpcode::V_CVT_F32_U32}); + return test; +} + GraphicsCase GraphicsAncillaryLayer(bool front_face) { GraphicsCase test; test.name = front_face ? "GraphicsAncillaryAfterFrontFace" : "GraphicsAncillaryLayer"; @@ -36714,6 +36770,10 @@ std::vector MakeGraphicsCases() { GraphicsPackedHalfInputAlias(false), GraphicsPackedHalfInputAlias(true), GraphicsSmoothRawInputAlias(), + GraphicsSmoothFlatInputAlias(false), + GraphicsSmoothFlatInputAlias(true), + GraphicsSmoothFlatInputAlias(false, true), + GraphicsSmoothFlatInputAlias(true, true), GraphicsAncillaryLayer(false), GraphicsAncillaryLayer(true), GraphicsAncillarySampleId(), @@ -42196,6 +42256,11 @@ int main(int argc, char **argv) { RunGraphicsCase(&vulkan, GraphicsPackedHalfInputAlias(false)); RunGraphicsCase(&vulkan, GraphicsPackedHalfInputAlias(true)); RunGraphicsCase(&vulkan, GraphicsSmoothRawInputAlias()); + RunGraphicsCase(&vulkan, GraphicsSmoothFlatInputAlias(false)); + RunGraphicsCase(&vulkan, GraphicsSmoothFlatInputAlias(true)); + RunGraphicsCase(&vulkan, GraphicsSmoothFlatInputAlias(false, true)); + RunGraphicsCase(&vulkan, GraphicsSmoothFlatInputAlias(true, true)); + RunGraphicsCase(&vulkan, GraphicsFlatInterpolatorExport()); return 0; } if (argc == 2 && std::strcmp(argv[1], "--clip-control-only") == 0) {