diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp index 99a09b6d5..986f211e1 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp @@ -41,7 +41,9 @@ uint32_t EmitBuiltinU32(EmitterState& state, IR::StageInputKind kind, uint32_t c return EmitAddU32(state, local, EmitBinaryU32(state, spv::OpIMul, group, ConstantU32(state, size))); } - const auto variable = InputVariableForKind(state, kind); + const bool centroid = kind == IR::StageInputKind::BaryCoordSmoothCentroid; + const auto variable = InputVariableForKind( + state, centroid ? IR::StageInputKind::BaryCoordSmooth : kind); if (variable == 0) { return ConstantU32(state, 0); } @@ -74,15 +76,24 @@ uint32_t EmitBuiltinU32(EmitterState& state, IR::StageInputKind kind, uint32_t c state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value); return bits; } - if (kind == IR::StageInputKind::BaryCoordSmooth || + if (centroid || kind == IR::StageInputKind::BaryCoordSmooth || kind == IR::StageInputKind::BaryCoordNoPerspective) { - const auto pointer = state.builder.AllocateId(); const auto value = state.builder.AllocateId(); const auto bits = state.builder.AllocateId(); - state.builder.AddFunction(spv::OpAccessChain, - TypePointer(state, spv::StorageClassInput, TypeF32(state)), - pointer, variable, ConstantU32(state, component + 1u)); - state.builder.AddFunction(spv::OpLoad, TypeF32(state), value, pointer); + if (centroid) { + const auto coordinates = state.builder.AllocateId(); + state.builder.RequireCapability(spv::CapabilityInterpolationFunction); + 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 { + const auto pointer = state.builder.AllocateId(); + state.builder.AddFunction(spv::OpAccessChain, + TypePointer(state, spv::StorageClassInput, TypeF32(state)), + pointer, variable, ConstantU32(state, component + 1u)); + state.builder.AddFunction(spv::OpLoad, TypeF32(state), value, pointer); + } state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value); return bits; } diff --git a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp index 8ab24afc0..338a593e1 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp @@ -1184,13 +1184,15 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& builtin(IR::StageInputKind::PrimitiveId)); } else if (options.stage == ShaderType::Pixel) { const auto* ps = options.input_info.pixel; - if (ps->ps_perspective_center_vgpr != UINT32_MAX) { - entry_ir.SetVectorReg(static_cast(ps->ps_perspective_center_vgpr), - builtin(IR::StageInputKind::BaryCoordSmooth, 0)); - entry_ir.SetVectorReg( - static_cast(ps->ps_perspective_center_vgpr + 1u), - builtin(IR::StageInputKind::BaryCoordSmooth, 1)); - } + const auto barycentric_pair = [&](uint32_t reg, IR::StageInputKind kind) { + if (reg != UINT32_MAX) { + entry_ir.SetVectorReg(static_cast(reg), builtin(kind, 0)); + entry_ir.SetVectorReg(static_cast(reg + 1u), builtin(kind, 1)); + } + }; + barycentric_pair(ps->ps_perspective_center_vgpr, IR::StageInputKind::BaryCoordSmooth); + barycentric_pair(ps->ps_perspective_centroid_vgpr, + IR::StageInputKind::BaryCoordSmoothCentroid); uint32_t reg = ps->ps_system_input_base; if (ps->ps_pos_x) { entry_ir.SetVectorReg(static_cast(reg++), diff --git a/src/graphics/shader/recompiler/ir/ShaderIR.h b/src/graphics/shader/recompiler/ir/ShaderIR.h index a8be5e5ef..8b6be623c 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIR.h +++ b/src/graphics/shader/recompiler/ir/ShaderIR.h @@ -171,6 +171,7 @@ enum class StageInputKind { Layer, SampleId, BaryCoordSmooth, + BaryCoordSmoothCentroid, BaryCoordNoPerspective, WorkgroupId, LocalInvocationId, diff --git a/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp b/src/graphics/shader/recompiler/ir/passes/ShaderInfoCollection.cpp index e63c43d9f..bcaa4b5e0 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::BaryCoordSmoothCentroid: case StageInputKind::BaryCoordNoPerspective: if (component >= 2u) { return Fail("typed barycentric component is out of range"); @@ -287,7 +288,8 @@ void CollectBuiltinInputs(const Program& program, ShaderInfo& info) { case StageInputKind::Layer: AddInput(info, kind, 0, 1, "gl_Layer"); break; case StageInputKind::SampleId: AddInput(info, kind, 0, 1, "gl_SampleID"); break; case StageInputKind::BaryCoordSmooth: - AddInput(info, kind, 0, 3, "gl_BaryCoordKHR"); + case StageInputKind::BaryCoordSmoothCentroid: + AddInput(info, StageInputKind::BaryCoordSmooth, 0, 3, "gl_BaryCoordKHR"); break; case StageInputKind::BaryCoordNoPerspective: AddInput(info, kind, 0, 3, "gl_BaryCoordNoPerspKHR"); diff --git a/src/graphics/shader/shader.cpp b/src/graphics/shader/shader.cpp index 712522baf..79a04b3d0 100644 --- a/src/graphics/shader/shader.cpp +++ b/src/graphics/shader/shader.cpp @@ -584,6 +584,9 @@ static void ShaderGetStaticInputInfoPS( if ((active_inputs & 0x00000002u) != 0) { ps_info.ps_perspective_center_vgpr = (active_inputs & 0x00000001u) != 0 ? 2u : 0u; } + if ((active_inputs & 0x00000004u) != 0) { + ps_info.ps_perspective_centroid_vgpr = 2u * std::popcount(active_inputs & 0x3u); + } for (uint32_t i = 0; i < data.num_input_semantics && i < ps_info.input_num && i < 32u; i++) { const auto& semantic = data.input_semantics[i]; if (semantic.is_custom != 0 && semantic.is_f16 == 0) { @@ -707,6 +710,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_centroid_vgpr); key.push_back(static_cast(info.ps_pos_x)); key.push_back(static_cast(info.ps_pos_y)); key.push_back(static_cast(info.ps_pos_z)); @@ -958,6 +962,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_centroid_vgpr = %" PRIu32 "\n" "\t ps_pos_x = %s\n" "\t ps_pos_y = %s\n" "\t ps_pos_z = %s\n" @@ -970,7 +975,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_pos_x ? "true" : "false", + info.ps_perspective_center_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", info.ps_ancillary ? "true" : "false", diff --git a/src/graphics/shader/shader.h b/src/graphics/shader/shader.h index ace2dfaff..880b2707e 100644 --- a/src/graphics/shader/shader.h +++ b/src/graphics/shader/shader.h @@ -157,6 +157,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_centroid_vgpr = UINT32_MAX; uint8_t target_output_mode[8] = {}; std::array target_export_mapping = {}; uint32_t scratch_size_dwords = 0; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index f538ba24c..83fddebe9 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -1219,6 +1219,8 @@ struct GraphicsCase { bool pixel_position_w = false; float vertex_clip_w = 1.0f; bool pixel_depth_export = false; + u32 pixel_perspective_centroid_vgpr = UINT32_MAX; + u32 pixel_custom_interpolation_mask = 0; }; struct CompiledShader { @@ -1565,6 +1567,8 @@ 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_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++) { pixel_info.interpolator_settings[i] = i; } @@ -25553,6 +25557,37 @@ GraphicsCase GraphicsPositionWExport() { return test; } +GraphicsCase GraphicsPackedHalfCentroid() { + GraphicsCase test; + test.name = "GraphicsPackedHalfCentroid"; + test.pixel_perspective_centroid_vgpr = 0; + test.pixel_custom_interpolation_mask = 1; + test.pixel_interpolator_settings = {0x420u}; + // Captured ab810715011baef3 interpolation, with attr3.x remapped to attr0.x. + test.fragment_code = { + 0xc8120002u, 0xc80e0000u, // raw vertex0/vertex1 packed halves + 0xcc204007u, 0x9c1206f2u, // low(vertex1) - low(vertex0) + 0xcc207005u, 0x9c1206f2u, // high(vertex1) - high(vertex0) + 0xc80a0001u, // raw vertex2 + 0xcc204006u, 0x9c1204f2u, + 0xcc207003u, 0x9c1204f2u, + 0xcc20400eu, 0x04120f00u, // low = I * delta10 + vertex0 + 0xcc20600fu, 0x04120b00u, // high = I * delta10 + vertex0 + 0x3e1c0306u, 0x3e1e0303u, // += J * delta20 + EncodeExp0(0x00, 0xf), EncodeExp1(14, 15, 14, 15)}; + AppendEnd(&test.fragment_code); + // At the pixel center the vertex weights are (5/8, 1/8, 1/4). + // Packed values are (0,1), (1,2), (3,3): expected result (0.875, 1.625). + test.vertices = { + 0xbf800000u, 0xbf800000u, 0x3c000000u, 0, 0, 0, + 0x40e00000u, 0xbf800000u, 0x40003c00u, 0, 0, 0, + 0xbf800000u, 0x40400000u, 0x42004200u, 0, 0, 0}; + test.expected_pixel = {0x3f600000u, 0x3fd00000u, 0x3f600000u, 0x3fd00000u}; + test.opcodes = {ShaderOpcode::V_INTERP_MOV_F32, ShaderOpcode::V_FMA_F32, ShaderOpcode::V_MAC_F32, + ShaderOpcode::EXP, ShaderOpcode::S_ENDPGM}; + return test; +} + GraphicsCase GraphicsAncillaryLayer(bool front_face) { GraphicsCase test; test.name = front_face ? "GraphicsAncillaryAfterFrontFace" : "GraphicsAncillaryLayer"; @@ -26196,6 +26231,7 @@ std::vector MakeGraphicsCases() { return { GraphicsInterpolationExport(), GraphicsPositionWExport(), + GraphicsPackedHalfCentroid(), GraphicsAncillaryLayer(false), GraphicsAncillaryLayer(true), GraphicsAncillarySampleId(), @@ -30665,6 +30701,11 @@ int main(int argc, char **argv) { RunGraphicsCase(&vulkan, GraphicsPositionWExport()); return 0; } + if (argc == 2 && std::strcmp(argv[1], "--centroid-only") == 0) { + VulkanHarness vulkan; + RunGraphicsCase(&vulkan, GraphicsPackedHalfCentroid()); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--clip-control-only") == 0) { CheckClipControlDepthClipState(); return 0; diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 929198468..096362e07 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -6103,6 +6103,53 @@ void TestCustomVintrpMovTranslation() { CheckSpirvBinaryValidates(mixed_linear_result.spirv); } +void TestPerspectiveCentroidInputs() { + constexpr std::array cases{std::array{4u, UINT32_MAX, 0u}, + std::array{5u, UINT32_MAX, 2u}, + std::array{6u, 0u, 2u}, std::array{7u, 2u, 4u}}; + for (const auto &[inputs, center, centroid] : cases) { + const uint32_t other = center == UINT32_MAX ? centroid : center; + const std::vector shader = { + EncodeExp0(0x00, 0xf), EncodeExp1(centroid, centroid + 1, other, other + 1), + 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; + const std::array mappings{}; + ShaderPixelInputInfo pixel{}; + (void)PrepareProgram(regs, sh, mappings, pixel); + Check(pixel.ps_perspective_centroid_vgpr == centroid && + pixel.ps_perspective_center_vgpr == center && + pixel.ps_system_input_base == centroid + 2, + "perspective-centroid pair did not follow enabled sample/center inputs"); + const auto key = MakeStageStaticKey(pixel); + pixel.ps_perspective_centroid_vgpr = UINT32_MAX; + Check(key != MakeStageStaticKey(pixel), "centroid input is missing from shader key"); + pixel.ps_perspective_centroid_vgpr = centroid; + 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, 5286u) && + Common::ContainsStr(source, "InterpolateAtCentroid %gl_BaryCoordKHR") && + SpirvSourceHasInstructionUsing(source, "OpCompositeExtract", " 1") && + SpirvSourceHasInstructionUsing(source, "OpCompositeExtract", " 2"), + "centroid I/J did not evaluate BaryCoordKHR Y/Z at the centroid"); + Check(!SpirvHasDecorationValueWithDecoration(result.spirv, 11u, 5286u, 16u), + "centroid input changed the shared center builtin's interpolation location"); + if (center != UINT32_MAX) { + Check(SpirvSourceHasInstructionUsing(source, "OpLoad", "%float "), + "center pair lost its ordinary barycentric loads when centroid was enabled"); + } + CheckSpirvBinaryValidates(result.spirv); + } +} + void TestPsInputCountRegisterDecode() { HW::Context context; // NUM_INTERP is 3 while bit 14 is an independent control flag that must be @@ -13444,6 +13491,7 @@ int main() { TestNewShaderRecompilerNativeBindingPlan(); TestNewShaderRecompilerStageInputInfo(); TestCustomVintrpMovTranslation(); + TestPerspectiveCentroidInputs(); TestGraphicsCreateInterpolantMapping(); TestNewShaderRecompilerPixelPipelineEntry(); TestComputeLdsAllocationIdentity();