diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp index 5194717a3..3c088f05c 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp @@ -42,8 +42,9 @@ uint32_t EmitBuiltinU32(EmitterState& state, IR::StageInputKind kind, uint32_t c EmitBinaryU32(state, spv::OpIMul, group, ConstantU32(state, size))); } const bool centroid = kind == IR::StageInputKind::BaryCoordSmoothCentroid; + const bool sample = kind == IR::StageInputKind::BaryCoordSmoothSample; const auto variable = InputVariableForKind( - state, centroid ? IR::StageInputKind::BaryCoordSmooth : kind); + state, centroid || sample ? IR::StageInputKind::BaryCoordSmooth : kind); if (variable == 0) { return ConstantU32(state, 0); } @@ -76,15 +77,24 @@ uint32_t EmitBuiltinU32(EmitterState& state, IR::StageInputKind kind, uint32_t c state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value); return bits; } - if (centroid || kind == IR::StageInputKind::BaryCoordSmooth || + if (centroid || sample || kind == IR::StageInputKind::BaryCoordSmooth || kind == IR::StageInputKind::BaryCoordNoPerspective) { const auto value = state.builder.AllocateId(); const auto bits = state.builder.AllocateId(); - if (centroid) { + if (centroid || sample) { const auto coordinates = state.builder.AllocateId(); state.builder.RequireCapability(spv::CapabilityInterpolationFunction); - state.builder.AddFunction(spv::OpExtInst, TypeF32Vector(state, 3), coordinates, - GlslStd450(state), GLSLstd450InterpolateAtCentroid, variable); + if (sample) { + const auto sample_id = state.builder.AllocateId(); + state.builder.AddFunction(spv::OpLoad, TypeI32(state), sample_id, + InputVariableForKind(state, IR::StageInputKind::SampleId)); + state.builder.AddFunction(spv::OpExtInst, TypeF32Vector(state, 3), coordinates, + GlslStd450(state), GLSLstd450InterpolateAtSample, + variable, sample_id); + } else { + state.builder.AddFunction(spv::OpExtInst, TypeF32Vector(state, 3), coordinates, + GlslStd450(state), GLSLstd450InterpolateAtCentroid, variable); + } state.builder.AddFunction(spv::OpCompositeExtract, TypeF32(state), value, coordinates, component + 1u); } else { diff --git a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp index e4ab93128..e2840d064 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp @@ -1281,6 +1281,8 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& } }; barycentric_pair(ps->ps_perspective_center_vgpr, IR::StageInputKind::BaryCoordSmooth); + barycentric_pair(ps->ps_perspective_sample_vgpr, + IR::StageInputKind::BaryCoordSmoothSample); barycentric_pair(ps->ps_perspective_centroid_vgpr, IR::StageInputKind::BaryCoordSmoothCentroid); uint32_t reg = ps->ps_system_input_base; diff --git a/src/graphics/shader/recompiler/ir/ShaderIR.h b/src/graphics/shader/recompiler/ir/ShaderIR.h index 3629d52a4..1c6c2828e 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIR.h +++ b/src/graphics/shader/recompiler/ir/ShaderIR.h @@ -196,6 +196,7 @@ enum class StageInputKind { Layer, SampleId, BaryCoordSmooth, + BaryCoordSmoothSample, BaryCoordSmoothCentroid, BaryCoordNoPerspective, WorkgroupId, diff --git a/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp b/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp index 30151b6ba..ead67fa76 100644 --- a/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp @@ -143,6 +143,7 @@ void ValidateValueReferences(const Program& program, ShaderStageInputInfo input_ } break; case StageInputKind::BaryCoordSmooth: + case StageInputKind::BaryCoordSmoothSample: case StageInputKind::BaryCoordSmoothCentroid: case StageInputKind::BaryCoordNoPerspective: if (component >= 2u) { @@ -299,6 +300,9 @@ void CollectBuiltinInputs(const Program& program, ShaderInfo& info) { break; case StageInputKind::Layer: AddInput(info, kind, 0, 1, "gl_Layer"); break; case StageInputKind::SampleId: AddInput(info, kind, 0, 1, "gl_SampleID"); break; + case StageInputKind::BaryCoordSmoothSample: + AddInput(info, StageInputKind::SampleId, 0, 1, "gl_SampleID"); + [[fallthrough]]; case StageInputKind::BaryCoordSmooth: case StageInputKind::BaryCoordSmoothCentroid: AddInput(info, StageInputKind::BaryCoordSmooth, 0, 3, "gl_BaryCoordKHR"); diff --git a/src/graphics/shader/shader.cpp b/src/graphics/shader/shader.cpp index 766c06c66..810c174db 100644 --- a/src/graphics/shader/shader.cpp +++ b/src/graphics/shader/shader.cpp @@ -525,6 +525,9 @@ static void ShaderGetStaticInputInfoPS( EXIT_NOT_IMPLEMENTED(ps_info.input_num > std::size(ps_info.interpolator_settings)); ps_info.ps_system_input_base = ShaderCalcPsSystemInputBase(sh); const uint32_t active_inputs = sh.ps_input_ena & sh.ps_input_addr; + if ((active_inputs & 0x00000001u) != 0) { + ps_info.ps_perspective_sample_vgpr = 0; + } if ((active_inputs & 0x00000002u) != 0) { ps_info.ps_perspective_center_vgpr = (active_inputs & 0x00000001u) != 0 ? 2u : 0u; } @@ -649,6 +652,7 @@ void BuildStageStaticKey(const ShaderPixelInputInfo& info, std::vector key.push_back(info.ps_system_input_base); key.push_back(info.custom_interpolation_mask); key.push_back(info.ps_perspective_center_vgpr); + key.push_back(info.ps_perspective_sample_vgpr); key.push_back(info.ps_perspective_centroid_vgpr); key.push_back(static_cast(info.ps_pos_x)); key.push_back(static_cast(info.ps_pos_y)); @@ -910,6 +914,7 @@ void ShaderDbgDumpInputInfo(const ShaderPixelInputInfo& info) { "\t ps_system_input_base = %u\n" "\t custom_interpolation_mask = 0x%08" PRIx32 "\n" "\t ps_perspective_center_vgpr = %" PRIu32 "\n" + "\t ps_perspective_sample_vgpr = %" PRIu32 "\n" "\t ps_perspective_centroid_vgpr = %" PRIu32 "\n" "\t ps_pos_x = %s\n" "\t ps_pos_y = %s\n" @@ -923,7 +928,8 @@ void ShaderDbgDumpInputInfo(const ShaderPixelInputInfo& info) { "\t ps_early_z = %s\n" "\t ps_execute_on_noop = %s\n", info.input_num, info.ps_system_input_base, info.custom_interpolation_mask, - info.ps_perspective_center_vgpr, info.ps_perspective_centroid_vgpr, + info.ps_perspective_center_vgpr, info.ps_perspective_sample_vgpr, + info.ps_perspective_centroid_vgpr, info.ps_pos_x ? "true" : "false", info.ps_pos_y ? "true" : "false", info.ps_pos_z ? "true" : "false", info.ps_pos_w ? "true" : "false", info.ps_front_face ? "true" : "false", diff --git a/src/graphics/shader/shader.h b/src/graphics/shader/shader.h index bc39b9a84..e666b01d2 100644 --- a/src/graphics/shader/shader.h +++ b/src/graphics/shader/shader.h @@ -167,6 +167,7 @@ struct ShaderPixelInputInfo { uint32_t ps_system_input_base = 0; uint32_t custom_interpolation_mask = 0; uint32_t ps_perspective_center_vgpr = UINT32_MAX; + uint32_t ps_perspective_sample_vgpr = UINT32_MAX; uint32_t ps_perspective_centroid_vgpr = UINT32_MAX; uint8_t target_output_mode[8] = {}; std::array target_export_mapping = {}; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index e5ddaddd7..b74d6c0f8 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -1344,6 +1344,7 @@ struct GraphicsCase { bool pixel_position_w = false; float vertex_clip_w = 1.0f; bool pixel_depth_export = false; + u32 pixel_perspective_sample_vgpr = UINT32_MAX; u32 pixel_perspective_centroid_vgpr = UINT32_MAX; u32 pixel_custom_interpolation_mask = 0; }; @@ -1761,6 +1762,7 @@ CompiledShader CompileFragmentCase(const GraphicsCase &test) { pixel_info.ps_pos_w = test.pixel_position_w; pixel_info.ps_depth_export_enable = test.pixel_depth_export; pixel_info.ps_system_input_base = 2; + pixel_info.ps_perspective_sample_vgpr = test.pixel_perspective_sample_vgpr; pixel_info.ps_perspective_centroid_vgpr = test.pixel_perspective_centroid_vgpr; pixel_info.custom_interpolation_mask = test.pixel_custom_interpolation_mask; for (u32 i = 0; i < std::size(pixel_info.interpolator_settings); i++) { @@ -35107,6 +35109,26 @@ GraphicsCase GraphicsPackedHalfCentroid() { return test; } +GraphicsCase GraphicsPerspectiveSample(bool multisample) { + GraphicsCase test; + test.name = multisample ? "GraphicsPerspectiveSample4x" + : "GraphicsPerspectiveSample1x"; + test.pixel_perspective_sample_vgpr = 0; + test.samples = multisample ? vk::SampleCountFlagBits::e4 + : vk::SampleCountFlagBits::e1; + test.fragment_code = { + EncodeVop2(0x08, 2, Vgpr(0), 0), // I squared + EncodeVop2(0x08, 3, Vgpr(1), 1), // J squared + EncodeExp0(0x00, 0xf), EncodeExp1(2, 3, 0, 1)}; + AppendEnd(&test.fragment_code); + // The triangle gives I=x/2, J=y/2. Squaring before the 4x resolve + // distinguishes sample interpolation (21/256) from pixel-center (1/16). + const auto square = std::bit_cast(multisample ? 21.0f / 256.0f : 1.0f / 16.0f); + test.expected_pixel = {square, square, 0x3e800000u, 0x3e800000u}; + test.opcodes = {ShaderOpcode::V_MUL_F32, ShaderOpcode::EXP, ShaderOpcode::S_ENDPGM}; + return test; +} + GraphicsCase GraphicsPackedHalfInputAlias(bool second_weights) { auto test = GraphicsPackedHalfCentroid(); test.name = second_weights ? "GraphicsPackedHalfAliasSecondWeights" @@ -35982,6 +36004,8 @@ std::vector MakeGraphicsCases() { GraphicsInterpolationExport(), GraphicsPositionWExport(), GraphicsPackedHalfCentroid(), + GraphicsPerspectiveSample(false), + GraphicsPerspectiveSample(true), GraphicsPackedHalfInputAlias(false), GraphicsPackedHalfInputAlias(true), GraphicsSmoothRawInputAlias(), @@ -41299,6 +41323,8 @@ int main(int argc, char **argv) { if (argc == 2 && std::strcmp(argv[1], "--centroid-only") == 0) { VulkanHarness vulkan; RunGraphicsCase(&vulkan, GraphicsPackedHalfCentroid()); + RunGraphicsCase(&vulkan, GraphicsPerspectiveSample(false)); + RunGraphicsCase(&vulkan, GraphicsPerspectiveSample(true)); return 0; } if (argc == 2 && std::strcmp(argv[1], "--pixel-alias-only") == 0) { diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 0eb793cc3..23a0112d4 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -6539,6 +6539,7 @@ void TestPerspectiveCentroidInputs() { ShaderMapUserData(regs.ps_regs.data_addr, mapped); HW::ShaderRegisters sh{}; sh.ps_input_ena = sh.ps_input_addr = inputs; + sh.m_cbShaderMask = 0xf; const std::array mappings{}; ShaderPixelInputInfo pixel{}; (void)PrepareProgram(regs, sh, mappings, pixel); @@ -6570,6 +6571,47 @@ void TestPerspectiveCentroidInputs() { } } +void TestPerspectiveSampleInputs() { + for (const uint32_t inputs : {1u, 3u, 5u, 7u}) { + const std::vector shader = { + EncodeExp0(0x00, 0xf), + EncodeExp1(0, 1, inputs == 1 ? 0 : 2, inputs == 1 ? 1 : 3), + EncodeSopp(0x01)}; + HW::PixelShaderInfo regs{}; + regs.ps_regs.data_addr = reinterpret_cast(shader.data()); + ShaderMappedData mapped{}; + mapped.code_size_bytes = shader.size() * sizeof(uint32_t); + ShaderMapUserData(regs.ps_regs.data_addr, mapped); + HW::ShaderRegisters sh{}; + sh.ps_input_ena = sh.ps_input_addr = inputs; + sh.m_cbShaderMask = 0xf; + const std::array mappings{}; + ShaderPixelInputInfo pixel{}; + (void)PrepareProgram(regs, sh, mappings, pixel); + Check(pixel.ps_perspective_sample_vgpr == 0 && + pixel.ps_system_input_base == 2u * std::popcount(inputs), + "perspective sample pair did not occupy the first enabled VGPRs"); + const auto key = MakeStageStaticKey(pixel); + pixel.ps_perspective_sample_vgpr = UINT32_MAX; + Check(key != MakeStageStaticKey(pixel), "sample input is missing from shader key"); + pixel.ps_perspective_sample_vgpr = 0; + auto options = MakeCompileOptions(ShaderType::Pixel); + options.input_info.pixel = &pixel; + const auto result = RecompileForTest(shader, options); + const auto source = DisassembleSpirvBinary(result.spirv); + Check(SpirvContainsCapability(result.spirv, 52u) && + SpirvHasDecorationValue(result.spirv, 11u, 18u) && + source.find("InterpolateAtSample %gl_BaryCoordKHR") != std::string::npos && + SpirvSourceHasInstructionUsing(source, "OpLoad", "%int %gl_SampleID") && + SpirvSourceHasInstructionUsing(source, "OpCompositeExtract", " 1") && + SpirvSourceHasInstructionUsing(source, "OpCompositeExtract", " 2"), + "sample I/J did not evaluate BaryCoordKHR Y/Z at SampleId"); + Check(!SpirvHasDecorationValueWithDecoration(result.spirv, 11u, 5286u, 17u), + "sample interpolation changed the shared barycentric input location"); + CheckSpirvBinaryValidates(result.spirv); + } +} + void TestPsInputCountRegisterDecode() { HW::Context context; // NUM_INTERP is 3 while bit 14 is an independent control flag that must be @@ -15054,6 +15096,7 @@ int main() { TestNewShaderRecompilerStageInputInfo(); TestCustomVintrpMovTranslation(); TestPerspectiveCentroidInputs(); + TestPerspectiveSampleInputs(); TestGraphicsCreateInterpolantMapping(); TestNewShaderRecompilerPixelPipelineEntry(); TestComputeLdsAllocationIdentity();