diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h index aa3b334de..09363948b 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h @@ -190,6 +190,7 @@ enum class Opcode { V_CEIL_F16, V_TRUNC_F16, V_RNDNE_F16, + V_FRACT_F16, V_SIN_F16, V_COS_F16, V_SIN_F32, @@ -395,6 +396,7 @@ enum class Opcode { V_CMP_EQ_U16, V_CMP_LE_U16, V_CMP_GT_U16, + V_CMPX_LT_U16, V_CMPX_GT_U16, V_CMP_NE_U16, V_CMP_GE_U16, diff --git a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp index 6dde3dab3..318605ce6 100644 --- a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp @@ -139,6 +139,7 @@ constexpr OpcodeMap VOP1_OPCODE_LIST[] = { {0x5cu, Opcode::V_CEIL_F16}, {0x5du, Opcode::V_TRUNC_F16}, {0x5eu, Opcode::V_RNDNE_F16}, + {0x5fu, Opcode::V_FRACT_F16}, {0x60u, Opcode::V_SIN_F16}, {0x61u, Opcode::V_COS_F16}, }; @@ -192,6 +193,7 @@ constexpr OpcodeMap VOP3_ENCODED_VOP1_OPCODE_LIST[] = { {0x5cu, Opcode::V_CEIL_F16}, {0x5du, Opcode::V_TRUNC_F16}, {0x5eu, Opcode::V_RNDNE_F16}, + {0x5fu, Opcode::V_FRACT_F16}, {0x60u, Opcode::V_SIN_F16}, {0x61u, Opcode::V_COS_F16}, }; @@ -233,6 +235,7 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = { {0xa9u, Opcode::V_CMP_LT_U16}, {0xaau, Opcode::V_CMP_EQ_U16}, {0xabu, Opcode::V_CMP_LE_U16}, {0xacu, Opcode::V_CMP_GT_U16}, {0xadu, Opcode::V_CMP_NE_U16}, {0xaeu, Opcode::V_CMP_GE_U16}, + {0xb9u, Opcode::V_CMPX_LT_U16, false}, {0xbcu, Opcode::V_CMPX_GT_U16}, {0xc0u, Opcode::V_CMP_F_U32}, {0xc1u, Opcode::V_CMP_LT_U32}, {0xc2u, Opcode::V_CMP_EQ_U32}, {0xc3u, Opcode::V_CMP_LE_U32}, {0xc4u, Opcode::V_CMP_GT_U32}, @@ -479,6 +482,7 @@ bool IsVop1FloatSourceOpcode(Opcode opcode) { case Opcode::V_CEIL_F16: case Opcode::V_TRUNC_F16: case Opcode::V_RNDNE_F16: + case Opcode::V_FRACT_F16: case Opcode::V_SIN_F16: case Opcode::V_COS_F16: case Opcode::V_SIN_F32: @@ -548,6 +552,8 @@ constexpr Vop1SdwaRule VOP1_SDWA_RULES[] = { SdwaSelWords() | SdwaSelFull(), true}, {Opcode::V_RNDNE_F16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords(), SdwaSelWords() | SdwaSelFull(), true}, + {Opcode::V_FRACT_F16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords(), + SdwaSelWords() | SdwaSelFull(), true}, {Opcode::V_SIN_F16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords(), SdwaSelWords() | SdwaSelFull(), true}, {Opcode::V_COS_F16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords(), @@ -773,6 +779,7 @@ bool IsVop1FloatResultOpcode(Opcode opcode) { case Opcode::V_CEIL_F16: case Opcode::V_TRUNC_F16: case Opcode::V_RNDNE_F16: + case Opcode::V_FRACT_F16: case Opcode::V_SIN_F16: case Opcode::V_COS_F16: case Opcode::V_SIN_F32: @@ -1434,6 +1441,7 @@ bool IsVopcCompareExec(Opcode opcode) { case Opcode::V_CMPX_GE_U32: case Opcode::V_CMPX_NE_I64: case Opcode::V_CMPX_NE_U64: + case Opcode::V_CMPX_LT_U16: case Opcode::V_CMPX_GT_U16: case Opcode::V_CMPX_LT_F16: case Opcode::V_CMPX_EQ_F16: diff --git a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp index b2dc8f386..0fe254314 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp @@ -138,6 +138,9 @@ bool Translator::EmitVector(const Decoder::Instruction& inst) { case O::V_CMP_LT_U16: EmitInteger16Compare(inst, IR::ValueOpcode::ULessThan32, false, false); return true; + case O::V_CMPX_LT_U16: + EmitInteger16Compare(inst, IR::ValueOpcode::ULessThan32, false, true); + return true; case O::V_CMP_LE_U16: EmitInteger16Compare(inst, IR::ValueOpcode::ULessThanEqual32, false, false); return true; @@ -370,6 +373,7 @@ bool Translator::EmitVector(const Decoder::Instruction& inst) { case O::V_CEIL_F16: return Float16Unary(inst, IR::ValueOpcode::FPCeil32, false); case O::V_TRUNC_F16: return Float16Unary(inst, IR::ValueOpcode::FPTrunc32, false); case O::V_RNDNE_F16: return Float16Unary(inst, IR::ValueOpcode::FPRoundEven32, false); + case O::V_FRACT_F16: return Float16Unary(inst, IR::ValueOpcode::FPFract32, false); case O::V_SIN_F16: return Float16Trig(inst, IR::ValueOpcode::FPSin); case O::V_COS_F16: return Float16Trig(inst, IR::ValueOpcode::FPCos); case O::V_MIN3_F16: return Float16Ternary(inst, IR::ValueOpcode::FPMinTri32, false, false); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index e16a33237..919170478 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -17835,6 +17835,91 @@ TestCase VectorSpecialF16Ops() { O::BUFFER_STORE_DWORD, O::S_ENDPGM}}; } +TestCase VectorFractF16CapturedAndEdges() { + using O = ShaderOpcode; + + // RDNA2 section 12.8: x - floor(x), rounded to half precision. + // Load at runtime to exercise the emitted operation, including half subnormals. + const std::array inputs{ + 0x3d00u, 0xbd00u, 0x0000u, 0x8000u, 0x0001u, 0x8001u, + 0x03ffu, 0x83ffu, 0x7bffu, 0xfbffu, 0x3800u, 0xb800u, + 0x8c00u, 0x8c01u, 0x7c00u, 0xfc00u, 0x7e55u, 0x7d01u}; + const std::array fractions{ + 0x3400u, 0x3a00u, 0x0000u, 0x0000u, 0x0001u, 0x3c00u, + 0x03ffu, 0x3c00u, 0x0000u, 0x0000u, 0x3800u, 0x3800u, + 0x3c00u, 0x3bffu, 0x7e00u, 0x7e00u, 0x7e00u, 0x7e00u}; + TestCase test; + test.name = "VectorFractF16CapturedAndEdges"; + for (u32 bits : inputs) { + test.initial.push_back(0x42000000u | bits); // Distinct high half must be ignored. + } + test.expected = test.initial; + auto &code = test.code; + for (u32 i = 0; i < inputs.size(); i++) { + AppendVMovU32(&code, 30, i * 4u); + AppendBufferLoadDword(&code, 7, 30); + AppendVMovLiteral(&code, 9, 0xa5a51234u); + code.push_back(0x7e12bf07u); // Captured v_fract_f16 v9, v7. + if (i >= 14u) { + // Check quiet NaN class without constraining its sign or payload. + code.push_back(EncodeVop2(0x1b, 9, 255u, 9)); + code.push_back(0xffff7e00u); + } + AppendStoreVgpr(&code, 9, static_cast(inputs.size()) + i); + test.expected.push_back(0xa5a50000u | fractions[i]); + } + AppendEnd(&code); + test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_FRACT_F16, + O::V_AND_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.decoded_counts = {{"V_FRACT_F16 v9, v7", inputs.size()}}; + test.ir_counts = {{" = FPFract32 ", inputs.size()}}; + test.required_spirv = {" Fract ", " PackHalf2x16 ", " UnpackHalf2x16 "}; + test.forbidden_spirv = {"OpCapability Float16"}; + return test; +} + +TestCase VectorFractF16Modifiers() { + using O = ShaderOpcode; + + std::vector code; + AppendVMovLiteral(&code, 7, 0xbd003d00u); // high=-1.25h, low=+1.25h + for (u32 dst = 10; dst <= 19; dst++) { + AppendVMovLiteral(&code, dst, 0xabcd1234u); + } + code.push_back(EncodeVop1(0x5f, 10, 249)); + code.push_back(EncodeVop1Sdwa(7, 5, 2, 5)); // high -> high, preserve low + code.push_back(EncodeVop1(0x5f, 11, 249)); + code.push_back(EncodeVop1Sdwa(7, 4, 0, 5, 0, 1, 1)); // -abs(-1.25), pad high + code.push_back(EncodeVop1(0x5f, 12, 249)); + code.push_back(EncodeVop1Sdwa(7, 4, 2, 5, 0, 0, 1)); // abs(-1.25) + code.push_back(EncodeVop1(0x5f, 13, 249)); + code.push_back(EncodeVop1Sdwa(7, 4, 2, 5, 0, 0, 0, 0, 1, 1)); // *2, clamp + code.push_back(EncodeVop1(0x5f, 14, 250)); + code.push_back(EncodeVop1Dpp(7, 0x0e4) | (1u << 20u)); // DPP identity, negate + AppendVop3(&code, 0x1df, 15, Vgpr(7), 0, 0, 1, 0, true, 1, 1); + code.push_back(EncodeVop1(0x5f, 16, 255)); + code.push_back(0x4200bd00u); // literal -1.25h + code.push_back(EncodeVop1(0x5f, 17, 240)); // inline +0.5, interpreted as FP16 + AppendSMovLiteral(&code, 20, 0x4200bd00u); + code.push_back(EncodeVop1(0x5f, 18, 20)); // SGPR source + AppendVop3(&code, 0x1df, 19, Vgpr(7), 0, 0, 0, 0, false, 3); // *0.5 + for (u32 i = 0; i < 10; i++) { + AppendStoreVgpr(&code, 10 + i, i); + } + AppendEnd(&code); + + TestCase test{"VectorFractF16Modifiers", code, {}, + {0x3a001234u, 0x00003a00u, 0xabcd3400u, 0xabcd3c00u, + 0xabcd3a00u, 0xabcd3c00u, 0xabcd3a00u, 0xabcd3800u, + 0xabcd3a00u, 0xabcd3000u}, + {O::V_MOV_B32, O::S_MOV_B32, O::V_FRACT_F16, + O::BUFFER_STORE_DWORD, O::S_ENDPGM}}; + test.decoded_counts = {{"V_FRACT_F16", 10}}; + test.ir_counts = {{" = FPFract32 ", 10}}; + test.required_spirv = {" Fract ", " PackHalf2x16 "}; + return test; +} + TestCase VectorCosF16CapturedSdwaAndEdges() { using O = ShaderOpcode; @@ -19593,6 +19678,69 @@ TestCase VectorVopcCmpxGtU16CapturedSdwaExecMask() { return test; } +TestCase VectorVopcCmpxLtU16CapturedSdwaExecMask() { + using O = ShaderOpcode; + struct CompareCase { + u32 lhs; + u32 rhs; + u32 incoming_exec; + u32 expected_exec; + u32 encoding = 0; // Captured SDWA, compact, VOP3, WORD_1 src0/src1. + }; + const std::array cases{{ + {0xffff0000u, 0x12340001u, 1, 1}, // Ignore high halves. + {0xabcd0001u, 0x12340001u, 1, 0}, // Strict comparison: equal is false. + {0x0000ffffu, 0x12340001u, 1, 0}, // Unsigned, not signed -1. + {0x00007fffu, 0x12348000u, 1, 1}, + {0x00008000u, 0x12347fffu, 1, 0}, + {0x12340000u, 0x56780000u, 1, 0}, + {0x0000fffeu, 0x1234ffffu, 1, 1}, + {0x00000000u, 0x12340001u, 0, 0}, // CMPX cannot reactivate an inactive lane. + {0x00008000u, 0x0000ffffu, 1, 1, 1}, + {0x0000ffffu, 0x00008000u, 1, 0, 2}, + {0x0001ffffu, 0x12340002u, 1, 1, 3}, + {0x00000001u, 0x00020000u, 1, 1, 4}, + }}; + constexpr u32 vcc_hi = 0x89abcdefu; + TestCase test; + test.name = "VectorVopcCmpxLtU16CapturedSdwaExecMask"; + for (const auto &entry : cases) { + test.initial.push_back(entry.lhs); + } + test.expected = test.initial; + auto &code = test.code; + for (u32 i = 0; i < cases.size(); ++i) { + const auto &entry = cases[i]; + AppendVMovU32(&code, 30, i * 4u); + AppendBufferLoadDword(&code, 0, 30); // Runtime input prevents constant folding. + AppendVMovU32(&code, 1, entry.rhs); + AppendSMovLiteral(&code, 106, entry.rhs); + AppendSMovLiteral(&code, 107, vcc_hi); + code.push_back(EncodeSMovB32(126, InlineU32(entry.incoming_exec))); + switch (entry.encoding) { + case 1: code.push_back(0x7d720300u); break; // compact v0, v1 + case 2: code.insert(code.end(), {0xd4b9007eu, 0x00020300u}); break; + case 3: code.insert(code.end(), {0x7d72d4f9u, 0x86050000u}); break; + case 4: code.insert(code.end(), {0x7d72d4f9u, 0x85060000u}); break; + default: code.insert(code.end(), {0x7d72d4f9u, 0x86060000u}); break; + } + code.push_back(EncodeSMovB32(20, 126)); // Snapshot EXEC before restoring it. + code.push_back(EncodeSMovB32(21, 127)); + code.push_back(EncodeSMovB32(126, InlineU32(1))); + const u32 out = static_cast(cases.size()) + i * 4u; + AppendStoreSgprPair(&code, 20, out); + AppendStoreSgprPair(&code, 106, out + 2u); // CMPX must not overwrite either VCC half. + test.expected.insert(test.expected.end(), + {entry.expected_exec, 0u, entry.rhs, vcc_hi}); + } + AppendEnd(&code); + test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::BUFFER_LOAD_DWORD, + O::V_CMPX_LT_U16, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.decoded_counts = {{"V_CMPX_LT_U16", cases.size()}}; + test.required_spirv = {"OpULessThan"}; + return test; +} + TestCase VectorVopcCmpxNgtF16CapturedSdwaExecMask() { using O = ShaderOpcode; @@ -24928,6 +25076,8 @@ std::vector MakeCases() { AddCase(NativeAndSdwa16BitDestinationWrites); AddCase(VectorMinMaxMed3F16Ops); AddCase(VectorSpecialF16Ops); + AddCase(VectorFractF16CapturedAndEdges); + AddCase(VectorFractF16Modifiers); AddCase(VectorCosF16CapturedSdwaAndEdges); AddCase(VectorSinF16SdwaAndEdges); AddCase(VectorWritelaneIgnoresExecMask); @@ -24980,6 +25130,7 @@ std::vector MakeCases() { AddCase(VectorVop3CmpxWritesExecMask); AddCase(VectorVopcSdwaCmpxWritesExecMask); AddCase(VectorVopcCmpxGtU16CapturedSdwaExecMask); + AddCase(VectorVopcCmpxLtU16CapturedSdwaExecMask); AddCase(VectorVopcCmpxNgtF16CapturedSdwaExecMask); AddCase(VectorCompareInvertedMaskSelect); AddCase(BranchSelect); @@ -29204,6 +29355,26 @@ int main(int argc, char **argv) { std::setvbuf(stdout, nullptr, _IONBF, 0); EnsureConfigInitialized(); CheckLeastRecentlyUsedCacheOrdering(); + if (argc == 2 && std::strcmp(argv[1], "--cmpx-lt-u16-only") == 0) { + VulkanHarness vulkan; + RunCase(&vulkan, VectorVopcCmpxLtU16CapturedSdwaExecMask()); + RunCase(&vulkan, VectorVopcCmpxGtU16CapturedSdwaExecMask()); + RunCase(&vulkan, VectorVopcSdwaCmpxWritesExecMask()); + RunCase(&vulkan, VectorVop3CmpxWritesExecMask()); + RunCase(&vulkan, VectorVopcSdwaCmpxClassF32CapturedExecMask()); + return 0; + } + if (argc == 2 && std::strcmp(argv[1], "--fract-f16-only") == 0) { + VulkanHarness vulkan; + RunCase(&vulkan, VectorFractF16CapturedAndEdges()); + RunCase(&vulkan, VectorFractF16Modifiers()); + RunCase(&vulkan, NativeAndSdwa16BitDestinationWrites()); + RunCase(&vulkan, VectorSpecialF16Ops()); + RunCase(&vulkan, VectorCosF16CapturedSdwaAndEdges()); + RunCase(&vulkan, VectorSinF16SdwaAndEdges()); + RunCase(&vulkan, VectorFloatConversionOps()); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--packed-texture-only") == 0) { VulkanHarness vulkan; vulkan.CheckPackedTextureComponents(); diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index c113dea9b..3aeaf6263 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -4170,6 +4170,24 @@ void TestNewShaderDecoderArchitecture() { !sin_f16.src0.absolute, "decoder rejected the VOP1 SDWA V_SIN_F16 instruction"); + std::vector fract_f16_code(0x9b0u / 4u, EncodeSopp(0x00)); + fract_f16_code.push_back(0x7e12bf07u); + fract_f16_code.push_back(EncodeSopp(0x01)); + Instruction fract_f16; + ShaderRecompiler::Decoder::DecodeInstruction(fract_f16_code, 0x9b0u / 4u, + fract_f16); + Check(fract_f16.pc == 0x9b0u && fract_f16.family == Family::VOP1 && + fract_f16.opcode == Opcode::V_FRACT_F16 && + fract_f16.opcode_id == 0x5fu && fract_f16.word_count == 1u && + fract_f16.dst.kind == OperandKind::Vgpr && fract_f16.dst.reg == 9u && + fract_f16.src_count == 1u && + fract_f16.src0.kind == OperandKind::Vgpr && fract_f16.src0.reg == 7u, + "decoder rejected captured V_FRACT_F16 at pc 0x9b0"); + ShaderRecompiler::Decoder::Program fract_program; + ShaderRecompiler::Decoder::DecodeProgram(fract_f16_code, fract_program); + Check(!ShaderRecompiler::CFG::BuildGraph(fract_program).unsupported, + "captured V_FRACT_F16 still fails CFG construction"); + const uint32_t literal_code[] = {EncodeVop1(0x01, 2, 255u), 0x12345678u}; Instruction literal; ShaderRecompiler::Decoder::DecodeInstruction(literal_code, 0u, literal); @@ -4416,6 +4434,62 @@ void TestNewShaderRecompilerCapturedVopcSdwaCmpxClass() { CheckSpirvBinaryValidates(result.spirv); } +void TestNewShaderRecompilerCapturedVopcSdwaCmpxLtU16() { + using namespace ShaderRecompiler; + + const uint32_t shader[] = { + 0x7d72d4f9u, 0x86060000u, // v_cmpx_lt_u16 v0, vcc_lo (SDWA) + EncodeSopp(0x01), + }; + Decoder::Instruction decoded; + Decoder::DecodeInstruction(shader, 0u, decoded); + Check(decoded.family == Decoder::Family::VOPC && + decoded.opcode == Decoder::Opcode::V_CMPX_LT_U16 && + decoded.opcode_id == 0xb9u && decoded.word_count == 2u && + decoded.raw[0] == shader[0] && decoded.raw[1] == shader[1] && + decoded.dst.kind == Decoder::OperandKind::ExecLo && + decoded.src_count == 2u && + decoded.src0.kind == Decoder::OperandKind::Vgpr && + decoded.src0.reg == 0u && decoded.src0.sdwa_sel == 6u && + decoded.src1.kind == Decoder::OperandKind::VccLo && + decoded.src1.sdwa_sel == 6u && + !decoded.src0.sdwa_sext && !decoded.src1.sdwa_sext && + !decoded.src0.negate && !decoded.src1.negate && + !decoded.src0.absolute && !decoded.src1.absolute, + "decoder rejected captured SDWA V_CMPX_LT_U16 fields"); + + Decoder::Program program; + Decoder::DecodeProgram(shader, program); + Check(!CFG::BuildGraph(program).unsupported, + "captured SDWA V_CMPX_LT_U16 still fails CFG construction"); + auto result = RecompileForTest(shader, MakeCompileOptions(ShaderType::Pixel)); + Check(Common::ContainsStr(result.decoded_dump, + "V_CMPX_LT_U16 exec_lo, v0, vcc_lo"), + "captured SDWA V_CMPX_LT_U16 is missing from decoded dump"); + CheckSpirvBinaryValidates(result.spirv); + + // Independently assembled gfx1030 compact and VOP3 encodings. + const uint32_t compact[] = {0x7d720300u}; + const uint32_t vop3[] = {0xd4b9007eu, 0x00020300u}; + for (auto words : {std::span(compact), + std::span(vop3)}) { + Decoder::DecodeInstruction(words, 0u, decoded); + Check(decoded.opcode == Decoder::Opcode::V_CMPX_LT_U16 && + decoded.dst.kind == Decoder::OperandKind::ExecLo && + decoded.src0.kind == Decoder::OperandKind::Vgpr && + decoded.src0.reg == 0u && + decoded.src1.kind == Decoder::OperandKind::Vgpr && + decoded.src1.reg == 1u, + "compact/VOP3 V_CMPX_LT_U16 does not decode to an EXEC compare"); + } + const uint32_t dpp[] = {EncodeVopc(0xb9u, 250u, 1u), EncodeVop2Dpp(0u)}; + Decoder::DecodeInstruction(dpp, 0u, decoded); + Check(decoded.opcode == Decoder::Opcode::UNSUPPORTED && + Common::ContainsStr(decoded.unsupported_reason, + "VOPC DPP modifier is not supported for opcode"), + "V_CMPX_LT_U16 accepted an unsupported DPP encoding"); +} + void TestNewShaderRecompilerIrLookupMissFailsExplicitly() { namespace Decoder = ShaderRecompiler::Decoder; namespace CFG = ShaderRecompiler::CFG; @@ -13156,6 +13230,7 @@ int main() { TestImageAddressOperands(); TestSopkCompareImmediateExtension(); TestNewShaderRecompilerCapturedVopcSdwaCmpxClass(); + TestNewShaderRecompilerCapturedVopcSdwaCmpxLtU16(); TestNewShaderRecompilerIrLookupMissFailsExplicitly(); TestNewShaderRecompilerRejectsDppOn64BitCompares(); TestPsInputCountRegisterDecode();