shader: clamp V_FRACT_F16/F32/F64 results below one

This commit is contained in:
TheCruZ
2026-10-25 00:36:03 +02:00
committed by nmzik
parent 40aaab482e
commit e864cb20bd
6 changed files with 73 additions and 8 deletions
@@ -56,6 +56,7 @@ EMIT_NATIVE(BitFieldSExtract, OpBitFieldSExtract, U32, uint32_t, uint32_t, uint3
EMIT_NATIVE(SelectU1, OpSelect, U1, uint32_t, uint32_t, uint32_t) EMIT_NATIVE(SelectU1, OpSelect, U1, uint32_t, uint32_t, uint32_t)
EMIT_NATIVE(SelectU32, OpSelect, U32, uint32_t, uint32_t, uint32_t) EMIT_NATIVE(SelectU32, OpSelect, U32, uint32_t, uint32_t, uint32_t)
EMIT_NATIVE(SelectF32, OpSelect, F32, uint32_t, uint32_t, uint32_t) EMIT_NATIVE(SelectF32, OpSelect, F32, uint32_t, uint32_t, uint32_t)
EMIT_NATIVE(SelectF64, OpSelect, F64, uint32_t, uint32_t, uint32_t)
EMIT_NATIVE(IAdd32, OpIAdd, U32, uint32_t, uint32_t) EMIT_NATIVE(IAdd32, OpIAdd, U32, uint32_t, uint32_t)
EMIT_NATIVE(ISub32, OpISub, U32, uint32_t, uint32_t) EMIT_NATIVE(ISub32, OpISub, U32, uint32_t, uint32_t)
EMIT_NATIVE(IMul32, OpIMul, U32, uint32_t, uint32_t) EMIT_NATIVE(IMul32, OpIMul, U32, uint32_t, uint32_t)
@@ -1,6 +1,7 @@
#include "graphics/shader/recompiler/frontend/translate/Translator.h" #include "graphics/shader/recompiler/frontend/translate/Translator.h"
#include <array> #include <array>
#include <bit>
#include <utility> #include <utility>
namespace Libs::Graphics::ShaderRecompiler::Frontend { namespace Libs::Graphics::ShaderRecompiler::Frontend {
@@ -118,6 +119,33 @@ void Translator::FloatUnary(const Decoder::Instruction& inst, IR::ValueOpcode op
WriteOperand(DestinationOperand(inst), ir.Emit(opcode, {ReadOperand(inst.src0, type)})); WriteOperand(DestinationOperand(inst), ir.Emit(opcode, {ReadOperand(inst.src0, type)}));
} }
void Translator::V_FRACT(const Decoder::Instruction& inst, IR::Type type) {
// x - floor(x) rounds to 1.0 for a tiny negative x; v_fract returns the largest value below
// one instead. The ordered compare lets NaN through.
const auto destination = DestinationOperand(inst);
if (type == IR::Type::F64) {
const auto result = ir.Emit(IR::ValueOpcode::FPFract64, {ReadOperand(inst.src0, type)});
const auto below_one = ir.Emit(IR::ValueOpcode::BitCastF64U64,
{IR::Value(uint64_t {0x3fefffffffffffff})});
const auto above = ir.Emit(IR::ValueOpcode::FPOrdGreaterThan64, {result, below_one});
WriteOperand(destination, ir.Emit(IR::ValueOpcode::SelectF64, {above, below_one, result}));
return;
}
const bool half = type == IR::Type::F16;
const auto source = half ? ReadF16AsF32(inst.src0) : IR::F32(ReadOperand(inst.src0, type));
const auto result = IR::F32(ir.Emit(IR::ValueOpcode::FPFract32, {source}));
// Clamp in the destination precision: 0x3f7fe000 is the half 0x3bff.
const auto below_one =
IR::F32(IR::Value::F32(std::bit_cast<float>(half ? 0x3f7fe000u : 0x3f7fffffu)));
const auto above = IR::U1(ir.Emit(IR::ValueOpcode::FPOrdGreaterThan32, {result, below_one}));
const auto clamped = SelectF32(above, below_one, result);
if (half) {
WriteF16(destination, clamped);
} else {
WriteOperand(destination, clamped);
}
}
void Translator::FloatBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, void Translator::FloatBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
bool reverse) { bool reverse) {
std::array<IR::Value, 2> args; std::array<IR::Value, 2> args;
@@ -153,6 +153,7 @@ private:
void Float16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool accumulator, void Float16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool accumulator,
bool mix); bool mix);
void FloatUnary(const Decoder::Instruction& inst, IR::ValueOpcode opcode); void FloatUnary(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
void V_FRACT(const Decoder::Instruction& inst, IR::Type type);
void FloatBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool reverse); void FloatBinary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool reverse);
void FloatTernary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool accumulator, void FloatTernary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool accumulator,
bool mix); bool mix);
@@ -563,7 +563,7 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
case O::V_CEIL_F16: return Float16Unary(inst, IR::ValueOpcode::FPCeil32, false); 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_TRUNC_F16: return Float16Unary(inst, IR::ValueOpcode::FPTrunc32, false);
case O::V_RNDNE_F16: return Float16Unary(inst, IR::ValueOpcode::FPRoundEven32, 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_FRACT_F16: return V_FRACT(inst, IR::Type::F16);
case O::V_SIN_F16: return Float16Trig(inst, IR::ValueOpcode::FPSin); 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_COS_F16: return Float16Trig(inst, IR::ValueOpcode::FPCos);
case O::V_MIN3_F16: return Float16Ternary(inst, IR::ValueOpcode::FPMinTri32, false, false); case O::V_MIN3_F16: return Float16Ternary(inst, IR::ValueOpcode::FPMinTri32, false, false);
@@ -574,8 +574,8 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
case O::V_RCP_F32: return FloatUnary(inst, IR::ValueOpcode::FPRecip32); case O::V_RCP_F32: return FloatUnary(inst, IR::ValueOpcode::FPRecip32);
case O::V_RCP_F64: return FloatUnary(inst, IR::ValueOpcode::FPRecip64); 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_RCP_IFLAG_F32: return FloatUnary(inst, IR::ValueOpcode::FPRecipIFlag32);
case O::V_FRACT_F32: return FloatUnary(inst, IR::ValueOpcode::FPFract32); case O::V_FRACT_F32: return V_FRACT(inst, IR::Type::F32);
case O::V_FRACT_F64: return FloatUnary(inst, IR::ValueOpcode::FPFract64); case O::V_FRACT_F64: return V_FRACT(inst, IR::Type::F64);
case O::V_TRUNC_F32: return FloatUnary(inst, IR::ValueOpcode::FPTrunc32); 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_TRUNC_F64: return FloatUnary(inst, IR::ValueOpcode::FPTrunc64);
case O::V_CEIL_F32: return FloatUnary(inst, IR::ValueOpcode::FPCeil32); case O::V_CEIL_F32: return FloatUnary(inst, IR::ValueOpcode::FPCeil32);
@@ -96,6 +96,7 @@ VALUE_OPCODE(WqmU64, U64, U64)
// Core value operations used to expand architectural side effects. // Core value operations used to expand architectural side effects.
VALUE_OPCODE(SelectU1, U1, U1, U1, U1) VALUE_OPCODE(SelectU1, U1, U1, U1, U1)
VALUE_OPCODE(SelectF32, F32, U1, F32, F32) VALUE_OPCODE(SelectF32, F32, U1, F32, F32)
VALUE_OPCODE(SelectF64, F64, U1, F64, F64)
VALUE_OPCODE(IAdd32, U32, U32, U32) VALUE_OPCODE(IAdd32, U32, U32, U32)
VALUE_OPCODE(IAdd64, U64, U64, U64) VALUE_OPCODE(IAdd64, U64, U64, U64)
VALUE_OPCODE(ISub32, U32, U32, U32) VALUE_OPCODE(ISub32, U32, U32, U32)
+39 -5
View File
@@ -27670,16 +27670,16 @@ TestCase VectorSpecialF16Ops() {
TestCase VectorFractF16CapturedAndEdges() { TestCase VectorFractF16CapturedAndEdges() {
using O = ShaderOpcode; using O = ShaderOpcode;
// RDNA2 section 12.8: x - floor(x), rounded to half precision. // x - floor(x) rounded to half precision, clamped to 0x3bff below one.
// Load at runtime to exercise the emitted operation, including half subnormals. // Load at runtime to exercise the emitted operation, including half subnormals.
const std::array<u32, 18> inputs{ const std::array<u32, 18> inputs{
0x3d00u, 0xbd00u, 0x0000u, 0x8000u, 0x0001u, 0x8001u, 0x3d00u, 0xbd00u, 0x0000u, 0x8000u, 0x0001u, 0x8001u,
0x03ffu, 0x83ffu, 0x7bffu, 0xfbffu, 0x3800u, 0xb800u, 0x03ffu, 0x83ffu, 0x7bffu, 0xfbffu, 0x3800u, 0xb800u,
0x8c00u, 0x8c01u, 0x7c00u, 0xfc00u, 0x7e55u, 0x7d01u}; 0x8c00u, 0x8c01u, 0x7c00u, 0xfc00u, 0x7e55u, 0x7d01u};
const std::array<u32, 18> fractions{ const std::array<u32, 18> fractions{
0x3400u, 0x3a00u, 0x0000u, 0x0000u, 0x0001u, 0x3c00u, 0x3400u, 0x3a00u, 0x0000u, 0x0000u, 0x0001u, 0x3bffu,
0x03ffu, 0x3c00u, 0x0000u, 0x0000u, 0x3800u, 0x3800u, 0x03ffu, 0x3bffu, 0x0000u, 0x0000u, 0x3800u, 0x3800u,
0x3c00u, 0x3bffu, 0x7e00u, 0x7e00u, 0x7e00u, 0x7e00u}; 0x3bffu, 0x3bffu, 0x7e00u, 0x7e00u, 0x7e00u, 0x7e00u};
TestCase test; TestCase test;
test.name = "VectorFractF16CapturedAndEdges"; test.name = "VectorFractF16CapturedAndEdges";
for (u32 bits : inputs) { for (u32 bits : inputs) {
@@ -27710,6 +27710,37 @@ TestCase VectorFractF16CapturedAndEdges() {
return test; return test;
} }
// V_FRACT_F32 clamps to 0x3f7fffff where x - floor(x) rounds to 1.0, before output modifiers.
TestCase VectorFractF32Edges() {
using O = ShaderOpcode;
constexpr std::array<std::array<u32, 3>, 4> cases{{
{0xb3800000u, 0, 0x3f7fffffu}, // -2^-24: exact
{0xb3000000u, 0, 0x3f7fffffu}, // -2^-25: the tie rounds to 1.0
{0xb3000000u, 1, 0x3fffffffu}, // omod *2 applies after the clamp
{0x7fc00001u, 0, 0x7fc00000u}}}; // Quiet NaN class
TestCase test;
test.name = "VectorFractF32Edges";
for (const auto &[source, omod, expected] : cases) test.initial.push_back(source);
test.expected = test.initial;
auto &code = test.code;
for (u32 i = 0; i < cases.size(); i++) {
AppendVMovU32(&code, 30, i * 4u);
AppendBufferLoadDword(&code, 7, 30);
AppendVop3(&code, 0x1a0, 9, Vgpr(7), 0, 0, 0, 0, false, cases[i][1]);
if (i + 1u == cases.size()) {
code.push_back(EncodeVop2(0x1b, 9, 255u, 9));
code.push_back(0x7fc00000u);
}
AppendStoreVgpr(&code, 9, static_cast<u32>(cases.size()) + i);
test.expected.push_back(cases[i][2]);
}
AppendEnd(&code);
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_FRACT_F32,
O::V_AND_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
return test;
}
TestCase VectorFractF16Modifiers() { TestCase VectorFractF16Modifiers() {
using O = ShaderOpcode; using O = ShaderOpcode;
@@ -29628,7 +29659,7 @@ TestCase VectorFractF64CapturedAndEdges() {
{0x4320000000000001ull, 0x3fe0000000000000ull}, // 2^51 + .5 {0x4320000000000001ull, 0x3fe0000000000000ull}, // 2^51 + .5
{0, 0}, {0x8000000000000000ull, 0}, {0, 0}, {0x8000000000000000ull, 0},
{0x401c000000000000ull, 0}, {0xc01c000000000000ull, 0}, {0x401c000000000000ull, 0}, {0xc01c000000000000ull, 0},
{1, 1}, {0x8000000000000001ull, 0x3ff0000000000000ull}, {1, 1}, {0x8000000000000001ull, 0x3fefffffffffffffull}, // Clamped below one.
{0x000fffffffffffffull, 0x000fffffffffffffull}, {0x000fffffffffffffull, 0x000fffffffffffffull},
{0x7fefffffffffffffull, 0}, {0x7fefffffffffffffull, 0},
{0x7ff0000000000000ull, 0x7ff8000000000000ull}, {0x7ff0000000000000ull, 0x7ff8000000000000ull},
@@ -44123,6 +44154,7 @@ std::vector<TestCase> MakeCases() {
AddCase(VectorMinMaxMed3F16Ops); AddCase(VectorMinMaxMed3F16Ops);
AddCase(VectorSpecialF16Ops); AddCase(VectorSpecialF16Ops);
AddCase(VectorFractF16CapturedAndEdges); AddCase(VectorFractF16CapturedAndEdges);
AddCase(VectorFractF32Edges);
AddCase(VectorFractF16Modifiers); AddCase(VectorFractF16Modifiers);
AddCase(VectorCosF16CapturedSdwaAndEdges); AddCase(VectorCosF16CapturedSdwaAndEdges);
AddCase(VectorSinF16SdwaAndEdges); AddCase(VectorSinF16SdwaAndEdges);
@@ -50749,6 +50781,8 @@ int main(int argc, char **argv) {
if (argc == 2 && std::strcmp(argv[1], "--fract-f16-only") == 0) { if (argc == 2 && std::strcmp(argv[1], "--fract-f16-only") == 0) {
VulkanHarness vulkan; VulkanHarness vulkan;
RunCase(&vulkan, VectorFractF16CapturedAndEdges()); RunCase(&vulkan, VectorFractF16CapturedAndEdges());
RunCase(&vulkan, VectorFractF32Edges());
RunCase(&vulkan, VectorFractF64CapturedAndEdges());
RunCase(&vulkan, VectorFractF16Modifiers()); RunCase(&vulkan, VectorFractF16Modifiers());
RunCase(&vulkan, NativeAndSdwa16BitDestinationWrites()); RunCase(&vulkan, NativeAndSdwa16BitDestinationWrites());
RunCase(&vulkan, VectorSpecialF16Ops()); RunCase(&vulkan, VectorSpecialF16Ops());