diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h index 4a2618df7..bbbd2846a 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h @@ -179,6 +179,7 @@ inline constexpr auto EmitFPFloor32 = EmitGlsl; inline constexpr auto EmitFPTrunc32 = EmitGlsl; inline constexpr auto EmitFPFract32 = EmitGlsl; +inline constexpr auto EmitFPFract64 = EmitGlsl; uint32_t EmitFPSin(EmitterState& state, uint32_t arg0); uint32_t EmitFPCos(EmitterState& state, uint32_t arg0); #undef EMIT_NATIVE diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h index cade969e8..eec07627f 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h @@ -186,6 +186,7 @@ enum class Opcode { V_RCP_F32, V_RCP_IFLAG_F32, V_FRACT_F32, + V_FRACT_F64, V_TRUNC_F32, V_CEIL_F32, V_RNDNE_F32, diff --git a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp index fbc4ac981..039f3982d 100644 --- a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp @@ -123,6 +123,7 @@ constexpr OpcodeMap VOP1_OPCODE_LIST[] = { {0x39u, Opcode::V_FFBH_U32}, {0x3au, Opcode::V_FFBL_B32}, {0x3bu, Opcode::V_FFBH_I32}, + {0x3eu, Opcode::V_FRACT_F64}, {0x3fu, Opcode::V_FREXP_EXP_I32_F32}, {0x40u, Opcode::V_FREXP_MANT_F32}, {0x42u, Opcode::V_MOVRELD_B32}, @@ -182,6 +183,7 @@ constexpr OpcodeMap VOP3_ENCODED_VOP1_OPCODE_LIST[] = { {0x39u, Opcode::V_FFBH_U32}, {0x3au, Opcode::V_FFBL_B32}, {0x3bu, Opcode::V_FFBH_I32}, + {0x3eu, Opcode::V_FRACT_F64}, {0x3fu, Opcode::V_FREXP_EXP_I32_F32}, {0x40u, Opcode::V_FREXP_MANT_F32}, {0x42u, Opcode::V_MOVRELD_B32}, @@ -472,11 +474,18 @@ bool IsNativeVop3B16BinaryOpcode(Opcode opcode) { bool IsVop1FloatResultOpcode(Opcode opcode); bool IsVopcCompareExec(Opcode opcode); +bool IsVop1Float64Opcode(Opcode opcode) { + return opcode == Opcode::V_CVT_F64_I32 || opcode == Opcode::V_CVT_F32_F64 || + opcode == Opcode::V_CVT_F64_F32 || opcode == Opcode::V_CVT_F64_U32 || + opcode == Opcode::V_RCP_F64 || opcode == Opcode::V_FRACT_F64; +} + 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_FRACT_F64: case Opcode::V_MOV_B32: case Opcode::V_CVT_F32_F16: case Opcode::V_CVT_U32_F32: @@ -648,6 +657,10 @@ bool SupportsVop1Clamp(Opcode opcode) { } bool ValidateVop1Sdwa(Instruction& inst, uint32_t opcode, uint32_t modifier) { + if (IsVop1Float64Opcode(inst.opcode)) { + SetUnsupported(inst, Family::VOP1, opcode, "FP64 instructions do not support SDWA"); + return false; + } const auto dst_sel = (modifier >> 8u) & 0x7u; const auto dst_u = (modifier >> 11u) & 0x3u; const auto clamp = (modifier >> 13u) & 0x1u; @@ -736,9 +749,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) { + if (IsVop1Float64Opcode(inst.opcode)) { 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 ea14959e0..93d45ed38 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp @@ -896,7 +896,8 @@ void IncludeInstructionVectorRegisters(const Decoder::Instruction& inst, uint32_ case Decoder::Opcode::V_FMA_F64: include_vector(inst.src2, 2u); [[fallthrough]]; case Decoder::Opcode::V_ADD_F64: case Decoder::Opcode::V_MUL_F64: include_vector(inst.src1, 2u); [[fallthrough]]; - case Decoder::Opcode::V_RCP_F64: include_vector(inst.dst, 2u); [[fallthrough]]; + case Decoder::Opcode::V_RCP_F64: + case Decoder::Opcode::V_FRACT_F64: include_vector(inst.dst, 2u); [[fallthrough]]; case Decoder::Opcode::V_CVT_F32_F64: include_vector(inst.src0, 2u); break; case Decoder::Opcode::V_CMP_EQ_F64: case Decoder::Opcode::V_CMP_LE_F64: diff --git a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp index 344861bf3..5669663fc 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp @@ -453,6 +453,7 @@ void Translator::EmitVector(const Decoder::Instruction& inst) { case O::V_RCP_F64: return FloatUnary(inst, IR::ValueOpcode::FPRecip64); case O::V_RCP_IFLAG_F32: return FloatUnary(inst, IR::ValueOpcode::FPRecipIFlag32); case O::V_FRACT_F32: return FloatUnary(inst, IR::ValueOpcode::FPFract32); + case O::V_FRACT_F64: return FloatUnary(inst, IR::ValueOpcode::FPFract64); case O::V_TRUNC_F32: return FloatUnary(inst, IR::ValueOpcode::FPTrunc32); case O::V_CEIL_F32: return FloatUnary(inst, IR::ValueOpcode::FPCeil32); case O::V_RNDNE_F32: return FloatUnary(inst, IR::ValueOpcode::FPRoundEven32); diff --git a/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc b/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc index 0dc701989..7a840876c 100644 --- a/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc +++ b/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc @@ -203,6 +203,7 @@ VALUE_OPCODE(FPFloor32, F32, F32) VALUE_OPCODE(FPCeil32, F32, F32) VALUE_OPCODE(FPTrunc32, F32, F32) VALUE_OPCODE(FPFract32, F32, F32) +VALUE_OPCODE(FPFract64, F64, F64) VALUE_OPCODE(LaneId, U32) VALUE_OPCODE(Ballot, U32x4, U1) diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index a3ecf6616..25016a751 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -17774,6 +17774,7 @@ CoverageClass ClassifyOpcode(ShaderOpcode opcode, case Opcode::V_CVT_F64_F32: case Opcode::V_CVT_F64_U32: case Opcode::V_RCP_F64: + case Opcode::V_FRACT_F64: case Opcode::V_MUL_F64: case Opcode::V_FMA_F64: case Opcode::V_CVT_F32_I32: @@ -23693,6 +23694,67 @@ TestCase VectorF64CapturedScreenSpaceShadows() { return test; } +TestCase VectorFractF64CapturedAndEdges() { + using O = ShaderOpcode; + constexpr std::array, 17> cases{{ + {0x400a000000000000ull, 0x3fd0000000000000ull}, // 3.25 -> .25 + {0xbff4000000000000ull, 0x3fe8000000000000ull}, // -1.25 -> .75 + {0x3ff0000000000001ull, 0x3cb0000000000000ull}, // 1 + 2^-52 + {0xbff0000000000001ull, 0x3feffffffffffffeull}, + {0x4320000000000001ull, 0x3fe0000000000000ull}, // 2^51 + .5 + {0, 0}, {0x8000000000000000ull, 0}, + {0x401c000000000000ull, 0}, {0xc01c000000000000ull, 0}, + {1, 1}, {0x8000000000000001ull, 0x3ff0000000000000ull}, + {0x000fffffffffffffull, 0x000fffffffffffffull}, + {0x7fefffffffffffffull, 0}, + {0x7ff0000000000000ull, 0x7ff8000000000000ull}, + {0xfff0000000000000ull, 0x7ff8000000000000ull}, + {0x7ff8123456789abcull, 0x7ff8000000000000ull}, + {0x7ff0000000000001ull, 0x7ff8000000000000ull}}}; + TestCase test; + test.name = "VectorFractF64CapturedAndEdges"; + const auto store_pair = [&](u32 reg, uint64_t expected) { + const auto index = static_cast(test.expected.size()); + AppendStoreVgpr(&test.code, reg, index); + AppendStoreVgpr(&test.code, reg + 1, index + 1); + test.expected.insert(test.expected.end(), {u32(expected), u32(expected >> 32)}); + }; + for (u32 i = 0; i < cases.size(); i++) { + const auto [source, expected] = cases[i]; + test.initial.insert(test.initial.end(), {u32(source), u32(source >> 32)}); + for (u32 word = 0; word < 2; word++) { + AppendVMovU32(&test.code, 30, (i * 2 + word) * 4); + AppendBufferLoadDword(&test.code, 4 + word, 30); + } + test.code.push_back(0x7e087d04u); // Captured v_fract_f64 v[4:5], v[4:5]. + if ((source & 0x7ff0000000000000ull) == 0x7ff0000000000000ull) { + // Classify quiet NaNs without requiring a particular payload or sign. + AppendVMovU32(&test.code, 4, 0); + AppendVMovLiteral(&test.code, 6, 0x7ff80000u); + test.code.push_back(EncodeVop2(0x1b, 5, Vgpr(6), 5)); + } + store_pair(4, expected); + } + AppendVMovU32(&test.code, 1, 0); + AppendVMovLiteral(&test.code, 2, 0xbff40000u); + AppendVop3(&test.code, 0x1be, 1, Vgpr(1), 0, 0, 1, 0, false, 0, 1); + store_pair(1, 0x3fe8000000000000ull); // Odd pair; ABS precedes NEG. + AppendVMovU32(&test.code, 4, 1); + AppendVMovLiteral(&test.code, 5, 0x3ff00000u); + test.code.push_back(EncodeSop1(0x04, 12, 126)); + test.code.push_back(EncodeSop1(0x04, 126, InlineU32(0))); + test.code.push_back(0x7e087d04u); + test.code.push_back(EncodeSop1(0x04, 126, 12)); + store_pair(4, 0x3ff0000000000001ull); // Both words survive inactive EXEC. + AppendEnd(&test.code); + test.initial.resize(test.expected.size()); + test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_FRACT_F64, + O::V_AND_B32, O::S_MOV_B64, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.decoded_counts = {{"V_FRACT_F64 v4, v4", cases.size() + 1}}; + test.required_spirv = {"OpCapability Float64", "OpTypeFloat 64", "Fract"}; + return test; +} + TestCase VectorF64WideningConversions() { using O = ShaderOpcode; TestCase test; @@ -32493,6 +32555,7 @@ std::vector MakeCases() { cases.push_back(VectorCompareF64WaveMasks(32)); cases.push_back(VectorCompareF64WaveMasks(64)); AddCase(VectorF64CapturedScreenSpaceShadows); + AddCase(VectorFractF64CapturedAndEdges); AddCase(VectorF64ModesModifiersAndExec); AddCase(VectorF64WideningConversions); AddCase(VectorSinCosMaxFiniteSpecialCases); @@ -37596,6 +37659,7 @@ int main(int argc, char **argv) { RunCase(&vulkan, VectorCompareF64WaveMasks(32)); RunCase(&vulkan, VectorCompareF64WaveMasks(64)); RunCase(&vulkan, VectorF64CapturedScreenSpaceShadows()); + RunCase(&vulkan, VectorFractF64CapturedAndEdges()); RunCase(&vulkan, VectorF64ModesModifiersAndExec()); return 0; } diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 2d0bf0031..d80967a54 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -3579,6 +3579,27 @@ void TestNewShaderRecompilerCapturedVop1SdwaByteConvert() { "V_CVT_F16_U16 accepted unimplemented SDWA byte sign extension"); } +void TestNewShaderRecompilerCapturedFractF64() { + using namespace ShaderRecompiler::Decoder; + const uint32_t captured[] = {0x7e087d04u}; + Instruction decoded; + DecodeInstruction(captured, 0, decoded); + Check(decoded.family == Family::VOP1 && decoded.opcode == Opcode::V_FRACT_F64 && + decoded.opcode_id == 0x3e && decoded.word_count == 1 && + decoded.dst.kind == OperandKind::Vgpr && decoded.dst.reg == 4 && + decoded.src0.kind == OperandKind::Vgpr && decoded.src0.reg == 4, + "captured V_FRACT_F64 did not decode its in-place register pair"); + for (const uint32_t opcode : {0x3eu, 0x2fu}) { + for (const uint32_t source : {249u, 250u}) { + const uint32_t invalid[] = {EncodeVop1(opcode, 4, source), + source == 249u ? 0x00060604u : EncodeVop1Dpp(4, 0xe4)}; + DecodeInstruction(invalid, 0, decoded); + Check(decoded.opcode == Opcode::UNSUPPORTED, + "FP64 VOP1 accepted an unsupported SDWA or DPP encoding"); + } + } +} + void TestNewShaderRecompilerVop1SdwaBfrev() { using namespace ShaderRecompiler::Decoder; const uint32_t captured[] = {0x7e0070f9u, 0x00040600u}; @@ -14567,6 +14588,7 @@ int main() { TestNewShaderRecompilerDsReadWrite2Translation(); TestNewShaderRecompilerDsWideAndAtomicTranslation(); TestNewShaderRecompilerCapturedVop1SdwaByteConvert(); + TestNewShaderRecompilerCapturedFractF64(); TestNewShaderRecompilerVop1SdwaBfrev(); TestNewShaderRecompilerVop1SdwaNotDestination(); TestNewShaderRecompilerScalarMemoryBindingDomains();