diff --git a/src/graphics/shader/recompiler/frontend/translate/Convert.cpp b/src/graphics/shader/recompiler/frontend/translate/Convert.cpp index bf0dc6f82..8a50db91f 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Convert.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Convert.cpp @@ -49,18 +49,11 @@ void Translator::V_CVT_F16_16(const Decoder::Instruction& inst, bool signed_valu } void Translator::V_CVT_16_F16(const Decoder::Instruction& inst, bool signed_value) { - const auto value = ReadF16AsF32(inst.src0); // The 32-bit conversion already saturates and maps NaN to zero. - if (signed_value) { - const auto converted = ir.Emit(IR::ValueOpcode::ConvertS32F32, {value}); - const auto low = ir.Emit(IR::ValueOpcode::SMax32, {converted, IR::Value(0xffff8000u)}); - Write16Bits(DestinationOperand(inst), - IR::U32(ir.Emit(IR::ValueOpcode::SMin32, {low, IR::Value(0x7fffu)}))); - return; - } - const auto converted = ir.Emit(IR::ValueOpcode::ConvertU32F32, {value}); - Write16Bits(DestinationOperand(inst), - IR::U32(ir.Emit(IR::ValueOpcode::UMin32, {converted, IR::Value(0xffffu)}))); + const auto converted = IR::U32( + ir.Emit(signed_value ? IR::ValueOpcode::ConvertS32F32 : IR::ValueOpcode::ConvertU32F32, + {ReadF16AsF32(inst.src0)})); + Write16Bits(DestinationOperand(inst), Saturate16(converted, signed_value)); } void Translator::V_CVT_RPI_I32_F32(const Decoder::Instruction& inst) { @@ -130,6 +123,12 @@ void Translator::V_CVT_PK_U8_F32(const Decoder::Instruction& inst) { {ReadU32(SourceAt(inst, 2)), byte, shift, IR::Value(8u)})); } +void Translator::V_CVT_PK_16_32(const Decoder::Instruction& inst, bool signed_value) { + WriteOperand(DestinationOperand(inst), + PackU16Lanes(Saturate16(ReadU32(inst.src0), signed_value), + Saturate16(ReadU32(inst.src1), signed_value))); +} + void Translator::V_PACK_B32_F16(const Decoder::Instruction& inst) { WriteOperand(DestinationOperand(inst), PackU16Lanes(Read16LaneBits(inst.src0, false), Read16LaneBits(inst.src1, false))); @@ -140,4 +139,12 @@ IR::U32 Translator::PackU16Lanes(IR::U32 low, IR::U32 high) { ir.Emit(IR::ValueOpcode::BitFieldInsert, {low, high, IR::Value(16u), IR::Value(16u)})); } +IR::U32 Translator::Saturate16(IR::U32 value, bool signed_value) { + if (!signed_value) { + return IR::U32(ir.Emit(IR::ValueOpcode::UMin32, {value, IR::Value(0xffffu)})); + } + const auto low = ir.Emit(IR::ValueOpcode::SMax32, {value, IR::Value(0xffff8000u)}); + return IR::U32(ir.Emit(IR::ValueOpcode::SMin32, {low, IR::Value(0x7fffu)})); +} + } // namespace Libs::Graphics::ShaderRecompiler::Frontend diff --git a/src/graphics/shader/recompiler/frontend/translate/Integer.cpp b/src/graphics/shader/recompiler/frontend/translate/Integer.cpp index 1c4ef675c..57762a903 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Integer.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Integer.cpp @@ -25,8 +25,7 @@ void Translator::V_MAD_I16(const Decoder::Instruction& inst) { const auto rhs = ReadU16AsU32(inst.src1, true); auto result = ir.IAdd(ir.IMul(lhs, rhs), ReadU16AsU32(inst.src2, true)); if (inst.dst.clamp) { - result = IR::U32(ir.Emit(IR::ValueOpcode::SMax32, {result, IR::Value(0xffff8000u)})); - result = IR::U32(ir.Emit(IR::ValueOpcode::SMin32, {result, IR::Value(0x7fffu)})); + result = Saturate16(result, true); } Write16Bits(DestinationOperand(inst), result); } diff --git a/src/graphics/shader/recompiler/frontend/translate/Translator.h b/src/graphics/shader/recompiler/frontend/translate/Translator.h index 04829894c..e82a1ead7 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translator.h +++ b/src/graphics/shader/recompiler/frontend/translate/Translator.h @@ -114,6 +114,7 @@ private: IR::F32 SelectF32(IR::U1 condition, IR::F32 true_value, IR::F32 false_value); IR::U32 PackU16Lanes(IR::U32 low, IR::U32 high); + IR::U32 Saturate16(IR::U32 value, bool signed_value); void EmitCompareResult(const Decoder::Instruction& inst, IR::U1 value, bool scalar, bool cmpx); void EmitCompareConstant(const Decoder::Instruction& inst, bool value, bool scalar, bool cmpx); void EmitIntegerCompare(const Decoder::Instruction& inst, IR::ValueOpcode opcode, IR::Type type, @@ -141,6 +142,7 @@ private: void V_CVT_PKRTZ_F16_F32(const Decoder::Instruction& inst); void V_CVT_PKNORM_F32(const Decoder::Instruction& inst, IR::ValueOpcode opcode); void V_CVT_PK_U8_F32(const Decoder::Instruction& inst); + void V_CVT_PK_16_32(const Decoder::Instruction& inst, bool signed_value); void V_PACK_B32_F16(const Decoder::Instruction& inst); void PackedFloat16(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool accumulator, bool quiet_snan); diff --git a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp index 455c9a8a2..f95b82a75 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp @@ -495,8 +495,8 @@ void Translator::EmitVector(const Decoder::Instruction& inst) { return; case O::V_CVT_PK_U8_F32: V_CVT_PK_U8_F32(inst); return; case O::V_PACK_B32_F16: V_PACK_B32_F16(inst); return; - case O::V_CVT_PK_U16_U32: - case O::V_CVT_PK_I16_I32: return PackB16(inst, false, false); + case O::V_CVT_PK_U16_U32: return V_CVT_PK_16_32(inst, false); + case O::V_CVT_PK_I16_I32: return V_CVT_PK_16_32(inst, true); case O::V_LSHLREV_B16: return Integer16Shift(inst, IR::ValueOpcode::ShiftLeftLogical32, false); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 659f5cae1..6f01ddfb5 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -25986,8 +25986,9 @@ TestCase Vop3CvtPkI16I32Captured() { TestCase test; test.name = "Vop3CvtPkI16I32Captured"; test.code = std::move(code); + // Each half saturates: 98305 -> 0x7fff, -98306 -> 0x8000. test.initial = {0x00018001u, 0xfffe7ffeu, 0}; - test.expected = {0x00018001u, 0xfffe7ffeu, 0x7ffe8001u}; + test.expected = {0x00018001u, 0xfffe7ffeu, 0x80007fffu}; test.opcodes = {O::BUFFER_LOAD_DWORD, O::V_MOV_B32, O::V_CVT_PK_I16_I32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; test.decoded_counts = {{"0x00000014: V_CVT_PK_I16_I32 v5, v7, v16\n", 1}}; @@ -25996,6 +25997,35 @@ TestCase Vop3CvtPkI16I32Captured() { return test; } +// V_CVT_PK_U16_U32 / V_CVT_PK_I16_I32 saturate each half: Min(src.u, 0xffff) and +// Clamp(src.s, -0x8000, 0x7fff). +TestCase Vop3CvtPk16Saturates() { + using O = ShaderOpcode; + + TestCase test; + test.name = "Vop3CvtPk16Saturates"; + test.initial = {0x00001234u, 0x0000ffffu, 0x00010000u, 0x80000000u, + 0x00007fffu, 0xffff8000u, 0x00008000u, 0xffff7fffu, + 0u, 0u, 0u, 0u}; + test.expected = {0x00001234u, 0x0000ffffu, 0x00010000u, 0x80000000u, + 0x00007fffu, 0xffff8000u, 0x00008000u, 0xffff7fffu, + 0xffff1234u, 0xffffffffu, 0x80007fffu, 0x80007fffu}; + auto &code = test.code; + for (u32 i = 0; i < 8u; i++) { + AppendVMovU32(&code, 30, i * 4u); + AppendBufferLoadDword(&code, i, 30); + } + AppendVop3(&code, 0x36a, 10, Vgpr(0), Vgpr(1)); // in range + AppendVop3(&code, 0x36a, 11, Vgpr(2), Vgpr(3)); // both clamp to 0xffff + AppendVop3(&code, 0x36b, 12, Vgpr(4), Vgpr(5)); // in range + AppendVop3(&code, 0x36b, 13, Vgpr(6), Vgpr(7)); // clamp to 0x7fff / 0x8000 + for (u32 i = 0; i < 4u; i++) AppendStoreVgpr(&code, 10u + i, 8u + i); + AppendEnd(&code); + test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_CVT_PK_U16_U32, + O::V_CVT_PK_I16_I32, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + return test; +} + TestCase Vop3MulLoU16CapturedAndSelectors() { using O = ShaderOpcode; @@ -27048,7 +27078,7 @@ TestCase VectorLaneAndPackedOps() { {}, {0x12345678u, 0x12345678u, 0x12345678u, 0x12345678u, 0x40003c00u, 0x46004400u, 0x48004200u, 0x40003c00u, 0x44004200u, 0x48804400u, - 0x40000000u, 0xef015678u}, + 0x40000000u, 0xffffffffu}, {O::V_MOV_B32, O::V_READFIRSTLANE_B32, O::V_READLANE_B32, O::V_WRITELANE_B32, O::V_PERMLANE16_B32, O::V_CVT_PKRTZ_F16_F32, O::V_PK_ADD_F16, O::V_PK_MUL_F16, O::V_PK_MIN_F16, O::V_PK_MAX_F16, @@ -44044,6 +44074,7 @@ std::vector MakeCases() { AddCase(Vop2SdwaLshrrevCapturedByte1Source); AddCase(Vop2SdwaSubNcPreservesByteAndWordDestinations); AddCase(Vop3CvtPkI16I32Captured); + AddCase(Vop3CvtPk16Saturates); AddCase(Vop3MulLoU16CapturedAndSelectors); AddCase(Vop3MadI16CapturedSelectorsAndSaturation); AddCase(Vop3F32ClampFlushesNaN); @@ -51063,6 +51094,8 @@ int main(int argc, char **argv) { if (argc == 2 && std::strcmp(argv[1], "--cvt-pk-i16-only") == 0) { VulkanHarness vulkan; RunCase(&vulkan, Vop3CvtPkI16I32Captured()); + RunCase(&vulkan, Vop3CvtPk16Saturates()); + RunCase(&vulkan, VectorLaneAndPackedOps()); return 0; } if (argc == 2 && std::strcmp(argv[1], "--med3-i16-only") == 0) {