shader: saturate V_CVT_PK_U16_U32 and V_CVT_PK_I16_I32

This commit is contained in:
TheCruZ
2026-10-25 00:04:35 +02:00
committed by nmzik
parent 66e21a6245
commit 9210264d47
5 changed files with 58 additions and 17 deletions
@@ -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
@@ -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);
}
@@ -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);
@@ -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);
+35 -2
View File
@@ -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<TestCase> 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) {