diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp index e36e43b37..96b20cb89 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp @@ -52,9 +52,12 @@ uint32_t EmitBuiltinU32(EmitterState& state, IR::StageInputKind kind, uint32_t c const auto value = state.builder.AllocateId(); const auto bits = state.builder.AllocateId(); state.builder.AddFunction(spv::OpLoad, TypeBool(state), value, variable); - // PS5 initializes v_front_face with float +1.0/-1.0 bits. - state.builder.AddFunction(spv::OpSelect, TypeU32(state), bits, value, - ConstantU32(state, 0x3f800000u), ConstantU32(state, 0xbf800000u)); + // SPI_BARYC_CNTL.FRONT_FACE_ALL_BITS selects uint masks or float +/-1. + const bool all_bits = + state.input_info.pixel != nullptr && state.input_info.pixel->ps_front_face_all_bits; + const auto front = ConstantU32(state, all_bits ? 0xffffffffu : 0x3f800000u); + const auto back = ConstantU32(state, all_bits ? 0u : 0xbf800000u); + state.builder.AddFunction(spv::OpSelect, TypeU32(state), bits, value, front, back); return bits; } if (kind == IR::StageInputKind::VertexIndex || kind == IR::StageInputKind::InstanceIndex || diff --git a/src/graphics/shader/shader.cpp b/src/graphics/shader/shader.cpp index f10d25fa0..b78d6e742 100644 --- a/src/graphics/shader/shader.cpp +++ b/src/graphics/shader/shader.cpp @@ -344,6 +344,8 @@ static void ShaderGetStaticInputInfoPS( ps_info.ps_pos_z = (active_inputs & 0x00000400u) != 0; ps_info.ps_pos_w = (active_inputs & 0x00000800u) != 0; ps_info.ps_front_face = (active_inputs & 0x00001000u) != 0; + // SPI_BARYC_CNTL.FRONT_FACE_ALL_BITS selects integer masks instead of float +/-1. + ps_info.ps_front_face_all_bits = (sh.baryc_cntl & 0x01000000u) != 0; ps_info.ps_ancillary = (active_inputs & 0x00002000u) != 0; ps_info.ps_sample_shading = (active_inputs & 0x00000011u) != 0; ps_info.ps_no_perspective = (sh.ps_input_ena & sh.ps_input_addr & 0x00000020u) != 0; @@ -464,6 +466,7 @@ void BuildStageStaticKey(const ShaderPixelInputInfo& info, std::vector key.push_back(static_cast(info.ps_pos_z)); key.push_back(static_cast(info.ps_pos_w)); key.push_back(static_cast(info.ps_front_face)); + key.push_back(static_cast(info.ps_front_face_all_bits)); 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)); @@ -737,6 +740,7 @@ void ShaderDbgDumpInputInfo(const ShaderPixelInputInfo& info) { "\t ps_pos_z = %s\n" "\t ps_pos_w = %s\n" "\t ps_front_face = %s\n" + "\t ps_front_face_all_bits = %s\n" "\t ps_ancillary = %s\n" "\t ps_sample_shading = %s\n" "\t ps_no_perspective = %s\n" @@ -745,11 +749,10 @@ void ShaderDbgDumpInputInfo(const ShaderPixelInputInfo& info) { "\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_sample_vgpr, - info.ps_perspective_centroid_vgpr, - info.ps_pos_x ? "true" : "false", + 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", + info.ps_front_face_all_bits ? "true" : "false", info.ps_ancillary ? "true" : "false", info.ps_sample_shading ? "true" : "false", info.ps_no_perspective ? "true" : "false", info.ps_pixel_kill_enable ? "true" : "false", info.ps_early_z ? "true" : "false", info.ps_execute_on_noop ? "true" : "false"); diff --git a/src/graphics/shader/shader.h b/src/graphics/shader/shader.h index 9294421e9..17a00bfbe 100644 --- a/src/graphics/shader/shader.h +++ b/src/graphics/shader/shader.h @@ -196,6 +196,7 @@ struct ShaderPixelInputInfo { bool ps_pos_z = false; bool ps_pos_w = false; bool ps_front_face = false; + bool ps_front_face_all_bits = false; bool ps_ancillary = false; bool ps_no_perspective = false; ShaderPixelParameterMode parameter_mode = ShaderPixelParameterMode::FirstVertex; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index c60798bfe..ce37e2208 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -1383,6 +1383,7 @@ struct GraphicsCase { std::vector vertices; bool pixel_ancillary = false; bool pixel_front_face = false; + bool pixel_front_face_all_bits = false; u32 layers = 1; vk::SampleCountFlagBits samples = vk::SampleCountFlagBits::e1; bool pixel_position_w = false; @@ -1834,6 +1835,7 @@ CompiledShader CompileFragmentCase(const GraphicsCase &test) { : ShaderPixelParameterMode::FirstVertex; pixel_info.ps_ancillary = test.pixel_ancillary; pixel_info.ps_front_face = test.pixel_front_face; + pixel_info.ps_front_face_all_bits = test.pixel_front_face_all_bits; 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; @@ -41299,6 +41301,83 @@ GraphicsCase GraphicsAncillarySampleId() { return test; } +// SPI_BARYC_CNTL[24] selects all-bits mode for uint/bool S_FRONT_FACE; +// float S_FRONT_FACE and __getSpecialVgprFloat(__v_front_face) leave it clear. +GraphicsCase GraphicsFrontFaceBits(bool reversed, bool all_bits) { + GraphicsCase test; + test.name = all_bits ? (reversed ? "GraphicsFrontFaceUintFront" : "GraphicsFrontFaceUintBack") + : (reversed ? "GraphicsFrontFaceFloatFront" : "GraphicsFrontFaceFloatBack"); + test.pixel_front_face = true; + test.pixel_front_face_all_bits = all_bits; + // Preserve every raw bit as two finite, exactly representable integer-to-float + // results. Directly exporting the integer front value would export a NaN. + AppendVop3(&test.fragment_code, 0x148, 4, Vgpr(2), InlineU32(0), InlineU32(16)); + AppendVop3(&test.fragment_code, 0x148, 5, Vgpr(2), InlineU32(16), InlineU32(16)); + test.fragment_code.push_back(EncodeVop1(0x06, 0, Vgpr(4))); + test.fragment_code.push_back(EncodeVop1(0x06, 1, Vgpr(5))); + // Integer inputs test raw zero; float inputs test the sign. + test.fragment_code.push_back(EncodeVopc(all_bits ? 0xc2 : 0x04, InlineU32(0), 2)); + AppendVMovLiteral(&test.fragment_code, 3, 0x3f800000u); + test.fragment_code.push_back(EncodeVop2(0x01, 2, InlineU32(0), 3)); + test.fragment_code.push_back(EncodeExp0(0x00, 0xf)); + test.fragment_code.push_back(EncodeExp1(0, 1, 2, 3)); + AppendEnd(&test.fragment_code); + test.opcodes = {ShaderOpcode::V_BFE_U32, ShaderOpcode::V_CVT_F32_U32, + all_bits ? ShaderOpcode::V_CMP_EQ_U32 : ShaderOpcode::V_CMP_GT_F32, + ShaderOpcode::V_CNDMASK_B32, ShaderOpcode::V_MOV_B32, + ShaderOpcode::EXP, ShaderOpcode::S_ENDPGM}; + test.vertices = { + 0xbf800000u, 0xbf800000u, 0x3e800000u, 0x3f000000u, 0x3f400000u, 0x3f800000u, + 0x40400000u, 0xbf800000u, 0x3e800000u, 0x3f000000u, 0x3f400000u, 0x3f800000u, + 0xbf800000u, 0x40400000u, 0x3e800000u, 0x3f000000u, 0x3f400000u, 0x3f800000u}; + if (reversed) { + std::swap_ranges(test.vertices.begin() + 6, test.vertices.begin() + 12, + test.vertices.begin() + 12); + } + // With the harness's positive-height viewport, the default triangle faces back. + const u32 bits = all_bits ? (reversed ? UINT32_MAX : 0u) + : (reversed ? 0x3f800000u : 0xbf800000u); + test.expected_pixel = { + std::bit_cast(static_cast(bits & 0xffffu)), + std::bit_cast(static_cast(bits >> 16u)), + reversed ? 0u : 0x3f800000u, 0x3f800000u}; + return test; +} + +void CheckPixelFrontFaceModes() { + constexpr const char *name = "PixelFrontFaceModes"; + static const std::array code = {EncodeSopp(0x01)}; + HW::PixelShaderInfo regs{}; + regs.ps_regs.data_addr = reinterpret_cast(code.data()); + ShaderMapUserData(regs.ps_regs.data_addr, + {.type = Prospero::ShaderBinaryType::kPs, + .code_size_bytes = static_cast(sizeof(code))}); + HW::ShaderRegisters sh{}; + sh.ps_input_ena = sh.ps_input_addr = 0x1002u; + const std::array export_mapping{}; + std::vector float_key; + for (bool all_bits : {false, true}) { + for (u32 other_bits : {0u, 0x120u}) { + sh.baryc_cntl = (all_bits ? 0x01000000u : 0u) | other_bits; + ShaderPixelInputInfo pixel{}; + (void)PrepareProgram(regs, sh, export_mapping, pixel); + Require(name, "register format decoding", + pixel.ps_front_face && pixel.ps_front_face_all_bits == all_bits && + pixel.ps_system_input_base == 2u, + "SPI_BARYC_CNTL[24] did not select the front-face format"); + std::vector key; + BuildStageStaticKey(pixel, key); + if (!all_bits && other_bits == 0u) { + float_key = key; + } + Require(name, "format specialization", + (key != float_key) == all_bits, + "front-face formats must have distinct shader keys and ignore unrelated bits"); + } + } + std::printf("[host] %-32s ok\n", name); +} + GraphicsCase GraphicsFlatInterpolatorExport() { using O = ShaderOpcode; @@ -42179,6 +42258,10 @@ std::vector MakeGraphicsCases() { GraphicsAncillaryLayer(false), GraphicsAncillaryLayer(true), GraphicsAncillarySampleId(), + GraphicsFrontFaceBits(false, false), + GraphicsFrontFaceBits(true, false), + GraphicsFrontFaceBits(false, true), + GraphicsFrontFaceBits(true, true), GraphicsFlatInterpolatorExport(), GraphicsDsAddtidScratchExport(), GraphicsDirectSgprPushConstantExport(), @@ -47959,6 +48042,17 @@ int main(int argc, char **argv) { RunCase(&vulkan, SiblingSharedExitKeepsCapturedConditions()); return 0; } + if (argc == 2 && std::strcmp(argv[1], "--front-face-only") == 0) { + CheckPixelFrontFaceModes(); + VulkanHarness vulkan; + for (bool all_bits : {false, true}) { + for (bool reversed : {false, true}) { + RunGraphicsCase(&vulkan, GraphicsFrontFaceBits(reversed, all_bits)); + } + } + RunGraphicsCase(&vulkan, GraphicsAncillaryLayer(true)); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--position-w-only") == 0) { VulkanHarness vulkan; RunGraphicsCase(&vulkan, GraphicsPositionWExport()); @@ -48574,6 +48668,7 @@ int main(int argc, char **argv) { CheckEmbeddedFetchLaneSpill(); CheckTessellationPrograms(); CheckPixelParameterAliases(); + CheckPixelFrontFaceModes(); CheckRectListShaders(); CheckSigned16ImageStore(vulkan); CheckBoundedFormattedBufferCopy(vulkan);