From 0a5774be7b725759576b90fdb4a5075214fd3100 Mon Sep 17 00:00:00 2001 From: gohansharma79-svg Date: Mon, 28 Sep 2026 19:09:31 +0530 Subject: [PATCH] shader: support FP64 conversions used by FighterZ (#884) shader: support float and unsigned widening to FP64 Co-authored-by: nmzik --- .../backend/spirv/spirvEmitterAlu.cpp | 5 ++ .../backend/spirv/spirvEmitterInstructions.h | 2 + .../backend/spirv/spirvEmitterModule.cpp | 4 +- .../frontend/decode/ShaderDecoder.h | 2 + .../frontend/decode/VectorAluOps.cpp | 6 ++ .../frontend/translate/Translate.cpp | 4 +- .../recompiler/frontend/translate/Vector.cpp | 2 + .../recompiler/ir/opcodes/ValueOpcodes.inc | 2 + tests/ShaderRecompilerComputeTests.cpp | 80 +++++++++++++++++++ 9 files changed, 104 insertions(+), 3 deletions(-) diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAlu.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAlu.cpp index fe9c62a0e..9480911bb 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAlu.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAlu.cpp @@ -307,6 +307,11 @@ uint32_t EmitConvertF32F64(EmitterState& state, uint32_t arg0) { Unary(state, spv::OpBitcast, TypeF32(state), clamped), converted)); } +uint32_t EmitConvertF64F32(EmitterState& state, uint32_t arg0) { + return EmitNative(state, + EmitFlushF32DenormToSignedZero(state, arg0)); +} + uint32_t EmitCompositeExtractU64(EmitterState& state, uint32_t arg0, IR::Value arg1) { return EmitNative(state, arg0, arg1.U32()); } diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h index 26d1f9bdc..d12020db2 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h @@ -31,7 +31,9 @@ uint32_t EmitConvertSigned32ToFloat(EmitterState& state, uint32_t arg0) { inline constexpr auto EmitConvertF32S32 = EmitConvertSigned32ToFloat; inline constexpr auto EmitConvertF64S32 = EmitConvertSigned32ToFloat; uint32_t EmitConvertF32F64(EmitterState& state, uint32_t arg0); +uint32_t EmitConvertF64F32(EmitterState& state, uint32_t arg0); EMIT_NATIVE(ConvertF32U32, OpConvertUToF, F32, uint32_t) +EMIT_NATIVE(ConvertF64U32, OpConvertUToF, F64, uint32_t) EMIT_NATIVE(CompositeConstructU64, OpCompositeConstruct, U64, uint32_t, uint32_t) EMIT_NATIVE(CompositeConstructU32x2, OpCompositeConstruct, U32x2, uint32_t, uint32_t) EMIT_NATIVE(CompositeConstructU32x3, OpCompositeConstruct, U32x3, uint32_t, uint32_t, uint32_t) diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp index 93d72247a..90d332613 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp @@ -684,8 +684,8 @@ void DefineModule(EmitterState& state) { state.builder.AddExecutionMode(state.main_func, spv::ExecutionModeSignedZeroInfNanPreserve, 32u); if (state.requirements.float64) { - EXIT_NOT_IMPLEMENTED(state.program.stage != ShaderType::Compute); - EXIT_NOT_IMPLEMENTED(state.input_info.compute->float_mode != 0xc0); + EXIT_NOT_IMPLEMENTED(state.program.stage == ShaderType::Compute && + state.input_info.compute->float_mode != 0xc0); // MODE=0xc0 uses round-to-nearest-even and preserves FP64 input/output denormals. state.builder.RequireCapability(spv::CapabilityFloat64); state.builder.RequireCapability(spv::CapabilityRoundingModeRTE); diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h index e0785ebc3..7b98e4693 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h @@ -156,6 +156,8 @@ enum class Opcode { V_DOT2C_F32_F16, V_CVT_F64_I32, V_CVT_F32_F64, + V_CVT_F64_F32, + V_CVT_F64_U32, V_CVT_F32_I32, V_CVT_F32_U32, V_CVT_U32_F32, diff --git a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp index 2213f29df..7881bdccf 100644 --- a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp @@ -89,6 +89,8 @@ constexpr OpcodeMap VOP1_OPCODE_LIST[] = { {0x02u, Opcode::V_READFIRSTLANE_B32}, {0x04u, Opcode::V_CVT_F64_I32}, {0x0fu, Opcode::V_CVT_F32_F64}, + {0x10u, Opcode::V_CVT_F64_F32}, + {0x16u, Opcode::V_CVT_F64_U32}, {0x2fu, Opcode::V_RCP_F64}, {0x05u, Opcode::V_CVT_F32_I32}, {0x06u, Opcode::V_CVT_F32_U32}, @@ -151,6 +153,8 @@ constexpr OpcodeMap VOP3_ENCODED_VOP1_OPCODE_LIST[] = { {0x02u, Opcode::V_READFIRSTLANE_B32}, {0x04u, Opcode::V_CVT_F64_I32}, {0x0fu, Opcode::V_CVT_F32_F64}, + {0x10u, Opcode::V_CVT_F64_F32}, + {0x16u, Opcode::V_CVT_F64_U32}, {0x2fu, Opcode::V_RCP_F64}, {0x05u, Opcode::V_CVT_F32_I32}, {0x06u, Opcode::V_CVT_F32_U32}, @@ -461,6 +465,7 @@ bool IsVopcCompareExec(Opcode opcode); bool IsVop1FloatSourceOpcode(Opcode opcode) { switch (opcode) { case Opcode::V_CVT_F32_F64: + case Opcode::V_CVT_F64_F32: case Opcode::V_RCP_F64: case Opcode::V_MOV_B32: case Opcode::V_CVT_F32_F16: @@ -721,6 +726,7 @@ void ApplyDppModifier(Operand& operand, uint32_t modifier, uint32_t encoding) { void DecodeVop1Dpp(uint32_t pc, std::span code, uint32_t word_index, uint32_t opcode, uint32_t vdst, Instruction& inst) { if (inst.opcode == Opcode::V_CVT_F64_I32 || inst.opcode == Opcode::V_CVT_F32_F64 || + inst.opcode == Opcode::V_CVT_F64_F32 || inst.opcode == Opcode::V_CVT_F64_U32 || inst.opcode == Opcode::V_RCP_F64) { SetUnsupported(inst, Family::VOP1, opcode, "FP64 instructions do not support DPP"); return; diff --git a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp index cfdbbfca6..1b52ab89f 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp @@ -876,7 +876,9 @@ void IncludeInstructionVectorRegisters(const Decoder::Instruction& inst, uint32_ include_vector(inst.src2); include_vector(inst.src3); switch (inst.opcode) { - case Decoder::Opcode::V_CVT_F64_I32: include_vector(inst.dst, 2u); break; + case Decoder::Opcode::V_CVT_F64_I32: + case Decoder::Opcode::V_CVT_F64_F32: + case Decoder::Opcode::V_CVT_F64_U32: include_vector(inst.dst, 2u); break; case Decoder::Opcode::V_FMA_F64: include_vector(inst.src2, 2u); [[fallthrough]]; case Decoder::Opcode::V_MUL_F64: include_vector(inst.src1, 2u); [[fallthrough]]; case Decoder::Opcode::V_RCP_F64: include_vector(inst.dst, 2u); [[fallthrough]]; diff --git a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp index a2b7a8db3..68855e5d9 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp @@ -299,6 +299,8 @@ void Translator::EmitVector(const Decoder::Instruction& inst) { case O::V_CVT_F32_I32: V_CVT_F32_I32(inst); return; case O::V_CVT_F64_I32: return FloatUnary(inst, IR::ValueOpcode::ConvertF64S32); case O::V_CVT_F32_F64: return FloatUnary(inst, IR::ValueOpcode::ConvertF32F64); + case O::V_CVT_F64_F32: return FloatUnary(inst, IR::ValueOpcode::ConvertF64F32); + case O::V_CVT_F64_U32: return FloatUnary(inst, IR::ValueOpcode::ConvertF64U32); case O::V_CVT_U32_F32: V_CVT_U32_F32(inst); return; case O::V_CVT_I32_F32: V_CVT_I32_F32(inst); return; case O::V_CVT_F16_F32: V_CVT_F16_F32(inst); return; diff --git a/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc b/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc index 768679402..1941e812c 100644 --- a/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc +++ b/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc @@ -66,7 +66,9 @@ VALUE_OPCODE(ConvertU32F32, U32, F32) VALUE_OPCODE(ConvertF32S32, F32, U32) VALUE_OPCODE(ConvertF64S32, F64, U32) VALUE_OPCODE(ConvertF32F64, F32, F64) +VALUE_OPCODE(ConvertF64F32, F64, F32) VALUE_OPCODE(ConvertF32U32, F32, U32) +VALUE_OPCODE(ConvertF64U32, F64, U32) VALUE_OPCODE(CompositeConstructU64, U64, U32, U32) VALUE_OPCODE(CompositeConstructU32x2, U32x2, U32, U32) VALUE_OPCODE(CompositeConstructU32x3, U32x3, U32, U32, U32) diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index b2f64353a..bd6a4fa0b 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -16709,6 +16709,8 @@ CoverageClass ClassifyOpcode(ShaderOpcode opcode, case Opcode::V_DOT2C_F32_F16: case Opcode::V_CVT_F64_I32: case Opcode::V_CVT_F32_F64: + case Opcode::V_CVT_F64_F32: + case Opcode::V_CVT_F64_U32: case Opcode::V_RCP_F64: case Opcode::V_MUL_F64: case Opcode::V_FMA_F64: @@ -22122,6 +22124,83 @@ TestCase VectorF64CapturedScreenSpaceShadows() { return test; } +TestCase VectorF64WideningConversions() { + using O = ShaderOpcode; + TestCase test; + test.name = "VectorF64WideningConversions"; + const auto store_pair = [&](u32 reg, uint64_t bits) { + AppendStoreVgpr(&test.code, reg, static_cast(test.expected.size())); + AppendStoreVgpr(&test.code, reg + 1, + static_cast(test.expected.size() + 1)); + test.expected.insert(test.expected.end(), {static_cast(bits), + static_cast(bits >> 32)}); + }; + for (const u32 bits : + {0u, 0x80000000u, 0x3f800001u, 0xbf800001u, 0x00800000u, 0x7f7fffffu, + 0xff7fffffu, 0x7f800000u, 0xff800000u, 1u, 0x80000001u}) { + AppendSMovLiteral(&test.code, 8, bits); + test.code.push_back(0x7e002008u); + const auto source = (bits & 0x7f800000u) == 0 ? bits & 0x80000000u : bits; + store_pair(0, std::bit_cast( + static_cast(std::bit_cast(source)))); + } + for (const u32 bits : {0x7fc12345u, 0x7f812345u}) { + AppendSMovLiteral(&test.code, 8, bits); + test.code.push_back(0x7e002008u); + AppendVMovLiteral(&test.code, 3, 0x7ff80000u); + test.code.push_back(EncodeVop2(0x1b, 1, Vgpr(3), 1)); + AppendStoreVgpr(&test.code, 1, static_cast(test.expected.size())); + test.expected.push_back(0x7ff80000u); + } + AppendVMovLiteral(&test.code, 1, std::bit_cast(-3.5f)); + AppendVop3(&test.code, 0x190, 1, Vgpr(1), 0, 0, 1, 0, false, 0, 1); + store_pair(1, std::bit_cast(-3.5)); + for (const u32 value : + {0u, 1u, 16777217u, 0x7fffffffu, 0x80000000u, 0xffffffffu}) { + AppendVMovLiteral(&test.code, 1, value); + test.code.push_back(EncodeVop1(0x16, 1, Vgpr(1))); + store_pair(1, std::bit_cast(static_cast(value))); + } + AppendSMovLiteral(&test.code, 8, 0xffffffffu); + AppendVop3(&test.code, 0x196, 1, 8u, 0); + store_pair(1, std::bit_cast(4294967295.0)); + test.code.push_back(EncodeSop1(0x04, 12, 126)); + test.code.push_back(EncodeSop1(0x04, 126, InlineU32(0))); + test.code.push_back(EncodeVop1(0x10, 1, InlineU32(0))); + test.code.push_back(EncodeVop1(0x16, 1, InlineU32(0))); + test.code.push_back(EncodeSop1(0x04, 126, 12)); + store_pair(1, std::bit_cast(4294967295.0)); + AppendEnd(&test.code); + test.opcodes = {O::S_MOV_B32, O::S_MOV_B64, O::V_MOV_B32, + O::V_AND_B32, O::V_CVT_F64_F32, O::V_CVT_F64_U32, + O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.required_spirv = {"OpCapability Float64", "OpTypeFloat 64", "OpFConvert", + "OpConvertUToF"}; + + std::vector vertex_code; + AppendSMovLiteral(&vertex_code, 8, std::bit_cast(2.0f)); + vertex_code.insert(vertex_code.end(), + {0x7e002008u, 0x7e005f00u, 0x7e401f00u}); + vertex_code.push_back(EncodeExp0(0x0c, 0xf)); + vertex_code.push_back(EncodeExp1(32, 32, 32, 32)); + AppendEnd(&vertex_code); + ShaderVertexInputInfo vertex{}; + ShaderRecompiler::CompileOptions options; + options.stage = ShaderType::Vertex; + options.input_info.vertex = &vertex; + auto translated = ShaderRecompiler::TranslateProgram(vertex_code, options); + auto result = + ShaderRecompiler::CompileProgram(std::move(translated), options, {}); + ValidateSpirv(test.name, result.spirv); + Require(test.name, "captured vertex conversion chain", + result.ir_dump.find("ConvertF64F32") != std::string::npos && + result.ir_dump.find("FPRecip64") != std::string::npos && + result.ir_dump.find("ConvertF32F64") != std::string::npos, + "vertex compilation lost the captured widening/reciprocal/narrowing " + "sequence"); + return test; +} + TestCase VectorF64ModesModifiersAndExec() { using O = ShaderOpcode; TestCase test; @@ -29876,6 +29955,7 @@ std::vector MakeCases() { AddCase(VectorRcpIflagF32IntegerReciprocal); AddCase(VectorF64CapturedScreenSpaceShadows); AddCase(VectorF64ModesModifiersAndExec); + AddCase(VectorF64WideningConversions); AddCase(VectorSinCosMaxFiniteSpecialCases); AddCase(VectorCompareF32ExposureGuard); for (const auto mode : {0xc0u, 0xe0u}) {