From 8274e517b2e94be41615e0aea08a44e04b9644d0 Mon Sep 17 00:00:00 2001 From: nmzik Date: Sun, 4 Oct 2026 23:08:09 +0400 Subject: [PATCH] shader: add FP64 min, max and rounding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Çağatay Güçlü <33038942+defektu@users.noreply.github.com> --- .../backend/spirv/spirvEmitterAlu.cpp | 30 ++++ .../backend/spirv/spirvEmitterInstructions.h | 5 + .../frontend/decode/ShaderDecoder.h | 5 + .../frontend/decode/VectorAluOps.cpp | 19 ++- .../frontend/translate/Translate.cpp | 5 + .../recompiler/frontend/translate/Vector.cpp | 5 + .../recompiler/ir/opcodes/ValueOpcodes.inc | 5 + tests/ShaderRecompilerComputeTests.cpp | 145 ++++++++++++++++++ tests/shaderCfgTests.cpp | 48 ++++++ 9 files changed, 266 insertions(+), 1 deletion(-) diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAlu.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAlu.cpp index db5773b4c..fbae465a8 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAlu.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterAlu.cpp @@ -30,6 +30,28 @@ uint32_t CompareEqual64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_va return Unary(state, not_equal ? spv::OpAny : spv::OpAll, TypeBool(state), compare); } +uint32_t EmitMinMaxF64(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max_value) { + const auto bits_type = TypeU64(state); + const auto lhs_bits = Unary(state, spv::OpBitcast, bits_type, lhs); + const auto rhs_bits = Unary(state, spv::OpBitcast, bits_type, rhs); + const auto ordered = Binary(state, max_value ? spv::OpFOrdGreaterThan : spv::OpFOrdLessThan, + TypeBool(state), lhs, rhs); + auto result = Select(state, TypeF64(state), ordered, lhs, rhs); + + // Equal values have identical bits except signed zero: min chooses -0, max chooses +0. + const auto equal = Binary(state, spv::OpFOrdEqual, TypeBool(state), lhs, rhs); + const auto equal_bits = Binary(state, max_value ? spv::OpBitwiseAnd : spv::OpBitwiseOr, + bits_type, lhs_bits, rhs_bits); + result = Select(state, TypeF64(state), equal, + Unary(state, spv::OpBitcast, TypeF64(state), equal_bits), result); + + // Non-IEEE mode selects the other operand for NaN, including rhs when both are NaN. + result = Select(state, TypeF64(state), Unary(state, spv::OpIsNan, TypeBool(state), rhs), + lhs, result); + return Select(state, TypeF64(state), Unary(state, spv::OpIsNan, TypeBool(state), lhs), + rhs, result); +} + uint32_t CompareOrdered64(EmitterState& state, uint32_t lhs_value, uint32_t rhs_value, spv::Op high_compare, spv::Op low_compare) { const auto lhs = ExtractPair(state, lhs_value); @@ -468,6 +490,14 @@ uint32_t EmitFPMax32(EmitterState& state, uint32_t arg0, uint32_t arg1) { return EmitMinMaxF32Value(state, arg0, arg1, true); } +uint32_t EmitFPMin64(EmitterState& state, uint32_t arg0, uint32_t arg1) { + return EmitMinMaxF64(state, arg0, arg1, false); +} + +uint32_t EmitFPMax64(EmitterState& state, uint32_t arg0, uint32_t arg1) { + return EmitMinMaxF64(state, arg0, arg1, true); +} + uint32_t EmitFPMinTri32(EmitterState& state, uint32_t arg0, uint32_t arg1, uint32_t arg2) { return EmitFMinMax3(state, arg0, arg1, arg2, false); } diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h index 7457930e3..32cca8209 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInstructions.h @@ -164,6 +164,8 @@ inline constexpr auto EmitFPFma32 = EmitGlsl; uint32_t EmitFPMin32(EmitterState& state, uint32_t arg0, uint32_t arg1); uint32_t EmitFPMax32(EmitterState& state, uint32_t arg0, uint32_t arg1); +uint32_t EmitFPMin64(EmitterState& state, uint32_t arg0, uint32_t arg1); +uint32_t EmitFPMax64(EmitterState& state, uint32_t arg0, uint32_t arg1); uint32_t EmitFPMinTri32(EmitterState& state, uint32_t arg0, uint32_t arg1, uint32_t arg2); uint32_t EmitFPMaxTri32(EmitterState& state, uint32_t arg0, uint32_t arg1, uint32_t arg2); uint32_t EmitFPMedTri32(EmitterState& state, uint32_t arg0, uint32_t arg1, uint32_t arg2); @@ -178,6 +180,9 @@ inline constexpr auto EmitFPRoundEven32 = EmitGlsl; inline constexpr auto EmitFPCeil32 = EmitGlsl; inline constexpr auto EmitFPTrunc32 = EmitGlsl; +inline constexpr auto EmitFPFloor64 = EmitGlsl; +inline constexpr auto EmitFPCeil64 = EmitGlsl; +inline constexpr auto EmitFPTrunc64 = EmitGlsl; inline constexpr auto EmitFPFract32 = EmitGlsl; inline constexpr auto EmitFPFract64 = EmitGlsl; uint32_t EmitFPSin(EmitterState& state, uint32_t arg0); diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h index fd990a2b2..48c926923 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h @@ -188,9 +188,12 @@ enum class Opcode { V_FRACT_F32, V_FRACT_F64, V_TRUNC_F32, + V_TRUNC_F64, V_CEIL_F32, + V_CEIL_F64, V_RNDNE_F32, V_FLOOR_F32, + V_FLOOR_F64, V_EXP_F32, V_LOG_F32, V_RSQ_F32, @@ -248,6 +251,8 @@ enum class Opcode { V_FMA_F64, V_ADD_F64, V_MUL_F64, + V_MIN_F64, + V_MAX_F64, V_FMA_F32, V_FMA_F16, V_PACK_B32_F16, diff --git a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp index bc6e91b46..b625e57f5 100644 --- a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp @@ -91,6 +91,9 @@ constexpr OpcodeMap VOP1_OPCODE_LIST[] = { {0x0fu, Opcode::V_CVT_F32_F64}, {0x10u, Opcode::V_CVT_F64_F32}, {0x16u, Opcode::V_CVT_F64_U32}, + {0x17u, Opcode::V_TRUNC_F64}, + {0x18u, Opcode::V_CEIL_F64}, + {0x1au, Opcode::V_FLOOR_F64}, {0x2fu, Opcode::V_RCP_F64}, {0x05u, Opcode::V_CVT_F32_I32}, {0x06u, Opcode::V_CVT_F32_U32}, @@ -156,6 +159,9 @@ constexpr OpcodeMap VOP3_ENCODED_VOP1_OPCODE_LIST[] = { {0x0fu, Opcode::V_CVT_F32_F64}, {0x10u, Opcode::V_CVT_F64_F32}, {0x16u, Opcode::V_CVT_F64_U32}, + {0x17u, Opcode::V_TRUNC_F64}, + {0x18u, Opcode::V_CEIL_F64}, + {0x1au, Opcode::V_FLOOR_F64}, {0x2fu, Opcode::V_RCP_F64}, {0x05u, Opcode::V_CVT_F32_I32}, {0x06u, Opcode::V_CVT_F32_U32}, @@ -292,6 +298,8 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = { {0x14cu, Opcode::V_FMA_F64}, {0x164u, Opcode::V_ADD_F64}, {0x165u, Opcode::V_MUL_F64}, + {0x166u, Opcode::V_MIN_F64}, + {0x167u, Opcode::V_MAX_F64}, {0x148u, Opcode::V_BFE_U32}, {0x149u, Opcode::V_BFE_I32}, {0x14au, Opcode::V_BFI_B32}, @@ -478,7 +486,9 @@ 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; + opcode == Opcode::V_RCP_F64 || opcode == Opcode::V_FRACT_F64 || + opcode == Opcode::V_TRUNC_F64 || opcode == Opcode::V_CEIL_F64 || + opcode == Opcode::V_FLOOR_F64; } bool IsVop1FloatSourceOpcode(Opcode opcode) { @@ -487,6 +497,9 @@ bool IsVop1FloatSourceOpcode(Opcode opcode) { case Opcode::V_CVT_F64_F32: case Opcode::V_RCP_F64: case Opcode::V_FRACT_F64: + case Opcode::V_TRUNC_F64: + case Opcode::V_CEIL_F64: + case Opcode::V_FLOOR_F64: case Opcode::V_MOV_B32: case Opcode::V_CVT_F32_F16: case Opcode::V_CVT_U32_F32: @@ -1235,6 +1248,8 @@ uint32_t NativeVop3SourceCount(Opcode opcode) { switch (opcode) { case Opcode::V_ADD_F64: case Opcode::V_MUL_F64: + case Opcode::V_MIN_F64: + case Opcode::V_MAX_F64: case Opcode::V_MUL_LO_U32: case Opcode::V_MUL_HI_U32: case Opcode::V_MUL_LO_I32: @@ -1387,6 +1402,8 @@ bool SupportsNativeVop3SourceModifiers(Opcode opcode) { case Opcode::V_MAD_F32: case Opcode::V_ADD_F64: case Opcode::V_MUL_F64: + case Opcode::V_MIN_F64: + case Opcode::V_MAX_F64: case Opcode::V_FMA_F64: case Opcode::V_FMA_F32: case Opcode::V_PACK_B32_F16: diff --git a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp index cad642821..14e748e98 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp @@ -895,8 +895,13 @@ void IncludeInstructionVectorRegisters(const Decoder::Instruction& inst, uint32_ 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_ADD_F64: + case Decoder::Opcode::V_MIN_F64: + case Decoder::Opcode::V_MAX_F64: case Decoder::Opcode::V_MUL_F64: include_vector(inst.src1, 2u); [[fallthrough]]; case Decoder::Opcode::V_RCP_F64: + case Decoder::Opcode::V_TRUNC_F64: + case Decoder::Opcode::V_CEIL_F64: + case Decoder::Opcode::V_FLOOR_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: diff --git a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp index eb0f65b24..87acd4d7a 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp @@ -456,9 +456,12 @@ void Translator::EmitVector(const Decoder::Instruction& inst) { 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_TRUNC_F64: return FloatUnary(inst, IR::ValueOpcode::FPTrunc64); case O::V_CEIL_F32: return FloatUnary(inst, IR::ValueOpcode::FPCeil32); + case O::V_CEIL_F64: return FloatUnary(inst, IR::ValueOpcode::FPCeil64); case O::V_RNDNE_F32: return FloatUnary(inst, IR::ValueOpcode::FPRoundEven32); case O::V_FLOOR_F32: return FloatUnary(inst, IR::ValueOpcode::FPFloor32); + case O::V_FLOOR_F64: return FloatUnary(inst, IR::ValueOpcode::FPFloor64); case O::V_EXP_F32: return FloatUnary(inst, IR::ValueOpcode::FPExp2); case O::V_LOG_F32: return FloatUnary(inst, IR::ValueOpcode::FPLog2); case O::V_RSQ_F32: return FloatUnary(inst, IR::ValueOpcode::FPRecipSqrt32); @@ -471,6 +474,8 @@ void Translator::EmitVector(const Decoder::Instruction& inst) { case O::V_MUL_F32: return FloatBinary(inst, IR::ValueOpcode::FPMul32, false); case O::V_ADD_F64: return FloatBinary(inst, IR::ValueOpcode::FPAdd64, false); case O::V_MUL_F64: return FloatBinary(inst, IR::ValueOpcode::FPMul64, false); + case O::V_MIN_F64: return FloatBinary(inst, IR::ValueOpcode::FPMin64, false); + case O::V_MAX_F64: return FloatBinary(inst, IR::ValueOpcode::FPMax64, false); case O::V_MIN_F32: return FloatBinary(inst, IR::ValueOpcode::FPMin32, false); case O::V_MAX_F32: return FloatBinary(inst, IR::ValueOpcode::FPMax32, false); case O::V_LDEXP_F32: return FloatBinary(inst, IR::ValueOpcode::FPLdexp, false); diff --git a/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc b/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc index cc4c38323..ef3cb5c00 100644 --- a/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc +++ b/src/graphics/shader/recompiler/ir/opcodes/ValueOpcodes.inc @@ -186,6 +186,8 @@ VALUE_OPCODE(FPFma64, F64, F64, F64, F64) VALUE_OPCODE(FPRecip64, F64, F64) VALUE_OPCODE(FPMin32, F32, F32, F32) VALUE_OPCODE(FPMax32, F32, F32, F32) +VALUE_OPCODE(FPMin64, F64, F64, F64) +VALUE_OPCODE(FPMax64, F64, F64, F64) VALUE_OPCODE(FPMinTri32, F32, F32, F32, F32) VALUE_OPCODE(FPMaxTri32, F32, F32, F32, F32) VALUE_OPCODE(FPMedTri32, F32, F32, F32, F32) @@ -202,6 +204,9 @@ VALUE_OPCODE(FPRoundEven32, F32, F32) VALUE_OPCODE(FPFloor32, F32, F32) VALUE_OPCODE(FPCeil32, F32, F32) VALUE_OPCODE(FPTrunc32, F32, F32) +VALUE_OPCODE(FPFloor64, F64, F64) +VALUE_OPCODE(FPCeil64, F64, F64) +VALUE_OPCODE(FPTrunc64, F64, F64) VALUE_OPCODE(FPFract32, F32, F32) VALUE_OPCODE(FPFract64, F64, F64) diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 8e51b7c11..2c535c5bb 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -24486,6 +24486,147 @@ TestCase VectorFractF64CapturedAndEdges() { return test; } +TestCase VectorMinMaxF64CapturedAndEdges() { + using O = ShaderOpcode; + // MODE.IEEE=0: select the numeric operand, or the second NaN unchanged. + constexpr uint64_t positive_zero = 0, negative_zero = 0x8000000000000000ull; + constexpr uint64_t one = 0x3ff0000000000000ull, minus_two = 0xc000000000000000ull; + constexpr uint64_t qnan = 0x7ff8123456789abcull, snan = 0xfff0000000000042ull; + constexpr uint64_t infinity = 0x7ff0000000000000ull; + constexpr std::array, 20> cases{{ + {one, minus_two, minus_two, one}, {minus_two, one, minus_two, one}, + {one, one, one, one}, + {positive_zero, negative_zero, negative_zero, positive_zero}, + {negative_zero, positive_zero, negative_zero, positive_zero}, + {negative_zero, negative_zero, negative_zero, negative_zero}, + {positive_zero, positive_zero, positive_zero, positive_zero}, + {qnan, one, one, one}, {one, qnan, one, one}, + {snan, minus_two, minus_two, minus_two}, {minus_two, snan, minus_two, minus_two}, + {qnan, snan, snan, snan}, {snan, qnan, qnan, qnan}, + {infinity, one, one, infinity}, + {infinity | negative_zero, one, infinity | negative_zero, one}, + {0x3fe0000000000000ull, one, 0x3fe0000000000000ull, one}, + {1, negative_zero | 1, negative_zero | 1, 1}, {1, 0, 0, 1}, + {1, 2, 1, 2}, {2, 1, 1, 2}}}; + TestCase test; + test.name = "VectorMinMaxF64CapturedAndEdges"; + for (const auto &item : cases) { + for (u32 source = 0; source < 2; source++) { + test.initial.insert(test.initial.end(), {u32(item[source]), u32(item[source] >> 32)}); + } + } + test.expected = test.initial; + 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(), {u32(bits), u32(bits >> 32)}); + }; + for (u32 i = 0; i < cases.size(); i++) { + for (u32 word = 0; word < 4; word++) { + AppendVMovU32(&test.code, 30, (i * 4 + word) * 4); + AppendBufferLoadDword(&test.code, 2 + word, 30); + } + AppendVop3(&test.code, 0x166, 7, Vgpr(2), Vgpr(4)); + store_pair(7, cases[i][2]); + AppendVop3(&test.code, 0x167, 9, Vgpr(2), Vgpr(4)); + store_pair(9, cases[i][3]); + // CS daced64c72398e80 PCs 0x10c/0x11c clamp the pair to [0, 1]. + test.code.insert(test.code.end(), {0xd5670006u, 0x00020480u, + 0xd5660006u, 0x00020cf2u}); + const auto source = std::bit_cast(cases[i][0]); + uint64_t clamped = 0; + if (source > 0) clamped = source < 1 ? cases[i][0] : one; + store_pair(6, clamped); + } + AppendSMovLiteral(&test.code, 8, 0); + AppendSMovLiteral(&test.code, 9, u32(minus_two >> 32)); + // Scalar source and odd destination pairs; ABS must precede NEG. + AppendVop3(&test.code, 0x166, 1, 8, 243, 0, 1, 0, false, 0, 1); + store_pair(1, minus_two); + AppendVop3(&test.code, 0x167, 1, 8, 243, 0, 1, 0, false, 0, 1); + store_pair(1, 0xbff0000000000000ull); + test.code.push_back(EncodeSop1(0x04, 12, 126)); + test.code.push_back(EncodeSop1(0x04, 126, InlineU32(0))); + AppendVop3(&test.code, 0x166, 1, 242, 242); + AppendVop3(&test.code, 0x167, 1, 242, 242); + test.code.push_back(EncodeSop1(0x04, 126, 12)); + store_pair(1, 0xbff0000000000000ull); + AppendEnd(&test.code); + test.initial.resize(test.expected.size()); + test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::S_MOV_B64, O::BUFFER_LOAD_DWORD, + O::V_MIN_F64, O::V_MAX_F64, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.required_spirv = {"OpCapability Float64", "OpTypeFloat 64", + "SignedZeroInfNanPreserve 64"}; + return test; +} + +TestCase VectorRoundF64EdgesAndModifiers() { + using O = ShaderOpcode; + constexpr std::array sources{ + 0x400a000000000000ull, 0xbff4000000000000ull, + 0x3fd0000000000000ull, 0xbfd0000000000000ull, + 0, 0x8000000000000000ull, 0x4330000000000001ull, 0xc330000000000001ull, + 0x7ff0000000000000ull, 0xfff0000000000000ull, + 0xfff8123456789abcull, 0x7ff0000000000001ull, 1, 0x8000000000000001ull}; + TestCase test; + test.name = "VectorRoundF64EdgesAndModifiers"; + for (const auto bits : sources) { + test.initial.insert(test.initial.end(), {u32(bits), u32(bits >> 32)}); + } + test.expected = test.initial; + const auto rounded = [](u32 opcode, double value) { + switch (opcode) { + case 0x17: return std::trunc(value); + case 0x18: return std::ceil(value); + case 0x1a: return std::floor(value); + default: std::abort(); + } + }; + 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(), {u32(bits), u32(bits >> 32)}); + }; + for (const u32 opcode : {0x17u, 0x18u, 0x1au}) { + for (u32 i = 0; i < sources.size(); i++) { + for (u32 word = 0; word < 2; word++) { + AppendVMovU32(&test.code, 30, (i * 2 + word) * 4); + AppendBufferLoadDword(&test.code, 2 + word, 30); + } + test.code.push_back(EncodeVop1(opcode, 5, Vgpr(2))); + AppendVop3(&test.code, 0x180 + opcode, 7, Vgpr(2), 0); + const auto value = std::bit_cast(sources[i]); + for (const u32 reg : {5u, 7u}) { + if (std::isnan(value)) { + // IEEE=false does not promise a particular NaN payload or quiet bit. + test.code.push_back(EncodeVopc(0x22, Vgpr(reg), reg)); + AppendStoreSgprPair(&test.code, 106, static_cast(test.expected.size())); + test.expected.insert(test.expected.end(), {0, 0}); + } else { + store_pair(reg, std::bit_cast(rounded(opcode, value))); + } + } + } + AppendSMovLiteral(&test.code, 8, 0); + AppendSMovLiteral(&test.code, 9, 0xbff40000u); + AppendVop3(&test.code, 0x180 + opcode, 1, 8, 0, 0, 1, 0, false, 0, 1); + const auto expected = std::bit_cast(rounded(opcode, -1.25)); + store_pair(1, expected); + test.code.push_back(EncodeSop1(0x04, 12, 126)); + test.code.push_back(EncodeSop1(0x04, 126, InlineU32(0))); + test.code.push_back(EncodeVop1(opcode, 1, 242)); + test.code.push_back(EncodeSop1(0x04, 126, 12)); + store_pair(1, expected); + } + AppendEnd(&test.code); + test.initial.resize(test.expected.size()); + test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::S_MOV_B64, O::BUFFER_LOAD_DWORD, + O::V_TRUNC_F64, O::V_CEIL_F64, O::V_FLOOR_F64, O::V_CMP_EQ_F64, + O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.required_spirv = {"OpCapability Float64", "OpTypeFloat 64", "Trunc", "Ceil", "Floor"}; + return test; +} + TestCase VectorFractF64CapturedChainRuntimeExec() { using O = ShaderOpcode; TestCase test; @@ -34023,6 +34164,8 @@ std::vector MakeCases() { cases.push_back(VectorCompare64WaveMasks(64, true)); AddCase(VectorF64CapturedScreenSpaceShadows); AddCase(VectorFractF64CapturedAndEdges); + AddCase(VectorMinMaxF64CapturedAndEdges); + AddCase(VectorRoundF64EdgesAndModifiers); AddCase(VectorFractF64CapturedChainRuntimeExec); AddCase(VectorF64ModesModifiersAndExec); AddCase(VectorF64WideningConversions); @@ -39371,6 +39514,8 @@ int main(int argc, char **argv) { RunCase(&vulkan, VectorCompare64WaveMasks(64)); RunCase(&vulkan, VectorF64CapturedScreenSpaceShadows()); RunCase(&vulkan, VectorFractF64CapturedAndEdges()); + RunCase(&vulkan, VectorMinMaxF64CapturedAndEdges()); + RunCase(&vulkan, VectorRoundF64EdgesAndModifiers()); RunCase(&vulkan, VectorFractF64CapturedChainRuntimeExec()); RunCase(&vulkan, VectorF64ModesModifiersAndExec()); return 0; diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 0002dc3be..733a0fcef 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -3600,6 +3600,53 @@ void TestNewShaderRecompilerCapturedFractF64() { } } +void TestNewShaderRecompilerF64AluEncodings() { + using namespace ShaderRecompiler::Decoder; + for (const bool maximum : {false, true}) { + const std::array captured = maximum + ? std::array{0xd5670006u, 0x00020480u} + : std::array{0xd5660006u, 0x00020cf2u}; + Instruction decoded; + DecodeInstruction(captured, 0, decoded); + Check(decoded.family == Family::VOP3 && + decoded.opcode == (maximum ? Opcode::V_MAX_F64 : Opcode::V_MIN_F64) && + decoded.opcode_id == (maximum ? 0x167u : 0x166u) && decoded.word_count == 2 && + decoded.dst.kind == OperandKind::Vgpr && decoded.dst.reg == 6 && + decoded.src0.kind == (maximum ? OperandKind::IntegerInlineConstant + : OperandKind::FloatInlineConstant) && + decoded.src0.value == (maximum ? 0u : 0x3f800000u) && + decoded.src1.kind == OperandKind::Vgpr && decoded.src1.reg == (maximum ? 2u : 6u) && + !decoded.src0.absolute && !decoded.src0.negate && + !decoded.src1.absolute && !decoded.src1.negate && + !decoded.dst.clamp && decoded.dst.omod == 0, + "captured V_MIN/MAX_F64 lost its inline source, register pair, or modifiers"); + } + for (const auto [opcode, expected] : { + std::pair{0x17u, Opcode::V_TRUNC_F64}, std::pair{0x18u, Opcode::V_CEIL_F64}, + std::pair{0x1au, Opcode::V_FLOOR_F64}}) { + for (const bool vop3 : {false, true}) { + const std::array words = vop3 + ? std::array{EncodeVop3Word0(0x180 + opcode, 1, 0, 1), + EncodeVop3Word1(8, 0, 0) | (1u << 29)} + : std::array{EncodeVop1(opcode, 1, 258), 0}; + Instruction decoded; + DecodeInstruction(words, 0, decoded); + Check(decoded.opcode == expected && decoded.word_count == (vop3 ? 2u : 1u) && + decoded.dst.reg == 1 && decoded.src0.reg == (vop3 ? 8u : 2u) && + decoded.src0.absolute == vop3 && decoded.src0.negate == vop3, + "FP64 rounding lost its VOP1/VOP3 encoding, odd pair, or source modifiers"); + } + for (const uint32_t source : {249u, 250u}) { + const uint32_t invalid[] = {EncodeVop1(opcode, 1, source), + source == 249u ? 0x00060604u : EncodeVop1Dpp(4, 0xe4)}; + Instruction decoded; + DecodeInstruction(invalid, 0, decoded); + Check(decoded.opcode == Opcode::UNSUPPORTED, + "FP64 rounding accepted unsupported SDWA or DPP encoding"); + } + } +} + void TestNewShaderRecompilerVop1SdwaBfrev() { using namespace ShaderRecompiler::Decoder; const uint32_t captured[] = {0x7e0070f9u, 0x00040600u}; @@ -14742,6 +14789,7 @@ int main() { TestNewShaderRecompilerDsWideAndAtomicTranslation(); TestNewShaderRecompilerCapturedVop1SdwaByteConvert(); TestNewShaderRecompilerCapturedFractF64(); + TestNewShaderRecompilerF64AluEncodings(); TestNewShaderRecompilerVop1SdwaBfrev(); TestNewShaderRecompilerVop1SdwaNotDestination(); TestNewShaderRecompilerScalarMemoryBindingDomains();