shader: return the first source when both float min/max inputs are NaN

This commit is contained in:
nmzik
2026-10-22 00:00:04 +02:00
parent 9c39062d3e
commit 1e4ac1371d
2 changed files with 13 additions and 10 deletions
@@ -95,8 +95,8 @@ uint32_t EmitClassMaskF16(EmitterState& state, uint32_t bits, uint32_t mask) {
static uint32_t EmitMinMaxF32(EmitterState& state, uint32_t lhs, uint32_t rhs, uint32_t lhs_bits, static uint32_t EmitMinMaxF32(EmitterState& state, uint32_t lhs, uint32_t rhs, uint32_t lhs_bits,
uint32_t rhs_bits, bool max_value) { uint32_t rhs_bits, bool max_value) {
// RDNA2 V_MIN/V_MAX_F32 return the other source for a NaN source (S1 when both are NaN) // PS5 V_MIN/V_MAX_F32 return the other source for a NaN source (S0 when both are NaN)
// and order -0 below +0. An unordered compare keeps S0 for a NaN S1 and, unlike NMin/NMax, // and order -0 below +0. An ordered compare takes S1 for a NaN S0 and, unlike NMin/NMax,
// always returns the exact bits of one source. // always returns the exact bits of one source.
const auto either = EmitOrU32(state, lhs_bits, rhs_bits); const auto either = EmitOrU32(state, lhs_bits, rhs_bits);
const auto both_zero = EmitCompareU32Constant(state, spv::OpIEqual, const auto both_zero = EmitCompareU32Constant(state, spv::OpIEqual,
@@ -104,11 +104,11 @@ static uint32_t EmitMinMaxF32(EmitterState& state, uint32_t lhs, uint32_t rhs, u
const auto zero = const auto zero =
EmitBitcastU32ToF32(state, max_value ? EmitAndU32(state, lhs_bits, rhs_bits) : either); EmitBitcastU32ToF32(state, max_value ? EmitAndU32(state, lhs_bits, rhs_bits) : either);
const auto keep_lhs = const auto keep_lhs =
Binary(state, max_value ? spv::OpFUnordGreaterThanEqual : spv::OpFUnordLessThan, Binary(state, max_value ? spv::OpFOrdGreaterThanEqual : spv::OpFOrdLessThan,
TypeBool(state), lhs, rhs); TypeBool(state), lhs, rhs);
const auto ordered = Select(state, TypeF32(state), keep_lhs, lhs, rhs); const auto ordered = Select(state, TypeF32(state), keep_lhs, lhs, rhs);
const auto numeric = Select(state, TypeF32(state), both_zero, zero, ordered); const auto numeric = Select(state, TypeF32(state), both_zero, zero, ordered);
return Select(state, TypeF32(state), Unary(state, spv::OpIsNan, TypeBool(state), lhs), rhs, return Select(state, TypeF32(state), Unary(state, spv::OpIsNan, TypeBool(state), rhs), lhs,
numeric); numeric);
} }
@@ -127,9 +127,10 @@ uint32_t EmitFPMedTri32(EmitterState& state, uint32_t a, uint32_t b, uint32_t c)
const auto a_bits = EmitBitcastF32ToU32(state, a); const auto a_bits = EmitBitcastF32ToU32(state, a);
const auto b_bits = EmitBitcastF32ToU32(state, b); const auto b_bits = EmitBitcastF32ToU32(state, b);
const auto c_bits = EmitBitcastF32ToU32(state, c); const auto c_bits = EmitBitcastF32ToU32(state, c);
const auto min_ab = EmitMinMaxF32(state, a, b, a_bits, b_bits, false); // The NaN path is RDNA2 MIN3, which keeps the later source when two are NaN.
const auto min_ba = EmitMinMaxF32(state, b, a, b_bits, a_bits, false);
const auto min3 = const auto min3 =
EmitMinMaxF32(state, min_ab, c, EmitBitcastF32ToU32(state, min_ab), c_bits, false); EmitMinMaxF32(state, c, min_ba, c_bits, EmitBitcastF32ToU32(state, min_ba), false);
const auto max_ab = EmitMinMaxF32(state, a, b, a_bits, b_bits, true); const auto max_ab = EmitMinMaxF32(state, a, b, a_bits, b_bits, true);
const auto max3 = const auto max3 =
EmitMinMaxF32(state, max_ab, c, EmitBitcastF32ToU32(state, max_ab), c_bits, true); EmitMinMaxF32(state, max_ab, c, EmitBitcastF32ToU32(state, max_ab), c_bits, true);
+6 -4
View File
@@ -27897,7 +27897,8 @@ TestCase VectorMinMaxF32NanAndSignedZeroEdges() {
} }
// MIN/MAX/MIN3/MAX3 on runtime inputs, so no compiler can fold them. The expected bits follow // MIN/MAX/MIN3/MAX3 on runtime inputs, so no compiler can fold them. The expected bits follow
// the emulated rules: a NaN operand yields the other operand and signed zeros are ordered. // the emulated rules: a NaN operand yields the other (S0 for two NaNs) and signed zeros are
// ordered.
// Denormal operands are left out; their expected results are not pinned down here. // Denormal operands are left out; their expected results are not pinned down here.
TestCase VectorMinMaxF32RuntimeEdges(u32 wave) { TestCase VectorMinMaxF32RuntimeEdges(u32 wave) {
using O = ShaderOpcode; using O = ShaderOpcode;
@@ -27905,14 +27906,14 @@ TestCase VectorMinMaxF32RuntimeEdges(u32 wave) {
const auto zero = [](u32 v) { return (v & 0x7fffffffu) == 0u; }; const auto zero = [](u32 v) { return (v & 0x7fffffffu) == 0u; };
const auto value = [](u32 v) { return std::bit_cast<float>(v); }; const auto value = [](u32 v) { return std::bit_cast<float>(v); };
const auto min2 = [&](u32 a, u32 b) { const auto min2 = [&](u32 a, u32 b) {
if (nan(a)) return b;
if (nan(b)) return a; if (nan(b)) return a;
if (nan(a)) return b;
if (zero(a) && zero(b)) return a | b; if (zero(a) && zero(b)) return a | b;
return value(a) < value(b) ? a : b; return value(a) < value(b) ? a : b;
}; };
const auto max2 = [&](u32 a, u32 b) { const auto max2 = [&](u32 a, u32 b) {
if (nan(a)) return b;
if (nan(b)) return a; if (nan(b)) return a;
if (nan(a)) return b;
if (zero(a) && zero(b)) return a & b; if (zero(a) && zero(b)) return a & b;
return value(a) >= value(b) ? a : b; return value(a) >= value(b) ? a : b;
}; };
@@ -27922,7 +27923,8 @@ TestCase VectorMinMaxF32RuntimeEdges(u32 wave) {
{0x40000000u, 0x7fa00001u, 0xc0000000u}, {0x80000000u, 0x00000000u, 0x80000000u}, {0x40000000u, 0x7fa00001u, 0xc0000000u}, {0x80000000u, 0x00000000u, 0x80000000u},
{0x00000000u, 0x80000000u, 0x00000000u}, {0x80000000u, 0x80000000u, 0x00000000u}, {0x00000000u, 0x80000000u, 0x00000000u}, {0x80000000u, 0x80000000u, 0x00000000u},
{0xff800000u, 0x7f800000u, 0x00000000u}, {0x7fc00000u, 0x00000000u, 0x80000000u}, {0xff800000u, 0x7f800000u, 0x00000000u}, {0x7fc00000u, 0x00000000u, 0x80000000u},
{0xffc05678u, 0x3f800000u, 0x7fa00001u}, {0xbf800000u, 0xc0000000u, 0x7fc01234u}}; {0xffc05678u, 0x3f800000u, 0x7fa00001u}, {0xbf800000u, 0xc0000000u, 0x7fc01234u},
{0x7fc01234u, 0xffc05678u, 0x7fa00001u}, {0xffc05678u, 0x7fc01234u, 0x3f800000u}};
TestCase test; TestCase test;
test.name = "VectorMinMaxF32RuntimeEdges"; test.name = "VectorMinMaxF32RuntimeEdges";
test.has_compute_info = true; test.has_compute_info = true;