mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-21 11:26:56 +02:00
shader: emit F32 min/max as exact compare-selects, MED3 with one max and saturate as NClamp
This commit is contained in:
@@ -115,7 +115,7 @@ uint32_t EmitF32ToU32(EmitterState& state, uint32_t src, bool signed_value) {
|
||||
} else {
|
||||
state.builder.AddFunction(spv::OpConvertFToU, TypeU32(state), converted_raw, trunc);
|
||||
}
|
||||
const auto nan = EmitClassifyF32(state, src).nan;
|
||||
const auto nan = Unary(state, spv::OpIsNan, TypeBool(state), src);
|
||||
if (signed_value) {
|
||||
const auto below = Binary(state, spv::OpFOrdLessThanEqual, TypeBool(state), src,
|
||||
ConstantF32(state, 0xcf000000u));
|
||||
@@ -242,11 +242,9 @@ uint32_t EmitPackFloat2x16Rtz(EmitterState& state, uint32_t arg0, uint32_t arg1)
|
||||
}
|
||||
|
||||
uint32_t EmitFPSaturate32(EmitterState& state, uint32_t arg0) {
|
||||
// The clamp modifier runs in DX10 clamp mode: NaN becomes 0. FClamp leaves NaN undefined.
|
||||
const auto clamped = EmitExt(state, TypeF32(state), GLSLstd450FClamp,
|
||||
{arg0, ConstantF32(state, 0), ConstantF32(state, 0x3f800000u)});
|
||||
return Select(state, TypeF32(state), EmitClassifyF32(state, arg0).nan, ConstantF32(state, 0),
|
||||
clamped);
|
||||
// The clamp modifier runs in DX10 clamp mode: NaN becomes 0, as NClamp's NMax step does.
|
||||
return EmitExt(state, TypeF32(state), GLSLstd450NClamp,
|
||||
{arg0, ConstantF32(state, 0), ConstantF32(state, 0x3f800000u)});
|
||||
}
|
||||
|
||||
uint32_t EmitSMulHi(EmitterState& state, uint32_t arg0, uint32_t arg1) {
|
||||
|
||||
@@ -25,10 +25,6 @@ F32Class EmitClassifyF32Bits(EmitterState& state, uint32_t bits) {
|
||||
return cls;
|
||||
}
|
||||
|
||||
F32Class EmitClassifyF32(EmitterState& state, uint32_t value) {
|
||||
return EmitClassifyF32Bits(state, EmitBitcastF32ToU32(state, value));
|
||||
}
|
||||
|
||||
uint32_t EmitClassMaskBitMatch(EmitterState& state, uint32_t mask, uint32_t bit,
|
||||
uint32_t class_match) {
|
||||
const auto selected =
|
||||
@@ -100,51 +96,53 @@ uint32_t EmitClassMaskF16(EmitterState& state, uint32_t bits, uint32_t mask) {
|
||||
return EmitClassMaskBits(state, bits, mask, true);
|
||||
}
|
||||
|
||||
static uint32_t EmitMinMaxF32(EmitterState& state, uint32_t lhs, uint32_t rhs,
|
||||
const F32Class& lhs_class, const F32Class& rhs_class,
|
||||
bool max_value) {
|
||||
const auto numeric_cond = state.builder.AllocateId();
|
||||
state.builder.AddFunction(max_value ? spv::OpFOrdGreaterThanEqual : spv::OpFOrdLessThan,
|
||||
TypeBool(state), numeric_cond, lhs, rhs);
|
||||
const auto ordered_bits =
|
||||
EmitSelectValueU32(state, numeric_cond, lhs_class.bits, rhs_class.bits);
|
||||
|
||||
const auto both_zero = EmitLogicalAndBool(state, lhs_class.zero, rhs_class.zero);
|
||||
const auto zero_bits = max_value ? EmitAndU32(state, lhs_class.bits, rhs_class.bits)
|
||||
: EmitOrU32(state, lhs_class.bits, rhs_class.bits);
|
||||
const auto numeric_bits = EmitSelectValueU32(state, both_zero, zero_bits, ordered_bits);
|
||||
|
||||
const auto rhs_nan_bits =
|
||||
EmitSelectValueU32(state, rhs_class.nan, lhs_class.bits, numeric_bits);
|
||||
const auto result_bits = EmitSelectValueU32(state, lhs_class.nan, rhs_class.bits, rhs_nan_bits);
|
||||
return EmitBitcastU32ToF32(state, result_bits);
|
||||
static uint32_t EmitMinMaxF32(EmitterState& state, uint32_t lhs, uint32_t rhs, uint32_t lhs_bits,
|
||||
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)
|
||||
// and order -0 below +0. An unordered compare keeps S0 for a NaN S1 and, unlike NMin/NMax,
|
||||
// always returns the exact bits of one source.
|
||||
const auto either = EmitOrU32(state, lhs_bits, rhs_bits);
|
||||
const auto both_zero = EmitCompareU32Constant(state, spv::OpIEqual,
|
||||
EmitAndConstant(state, either, 0x7fffffffu), 0);
|
||||
const auto zero =
|
||||
EmitBitcastU32ToF32(state, max_value ? EmitAndU32(state, lhs_bits, rhs_bits) : either);
|
||||
const auto keep_lhs =
|
||||
Binary(state, max_value ? spv::OpFUnordGreaterThanEqual : spv::OpFUnordLessThan,
|
||||
TypeBool(state), lhs, rhs);
|
||||
const auto ordered = Select(state, TypeF32(state), keep_lhs, lhs, rhs);
|
||||
const auto numeric = Select(state, TypeF32(state), both_zero, zero, ordered);
|
||||
return Select(state, TypeF32(state), Unary(state, spv::OpIsNan, TypeBool(state), lhs), rhs,
|
||||
numeric);
|
||||
}
|
||||
|
||||
uint32_t EmitMinMaxF32Value(EmitterState& state, uint32_t lhs, uint32_t rhs, bool max_value) {
|
||||
const auto lhs_class = EmitClassifyF32(state, lhs);
|
||||
const auto rhs_class = EmitClassifyF32(state, rhs);
|
||||
return EmitMinMaxF32(state, lhs, rhs, lhs_class, rhs_class, max_value);
|
||||
return EmitMinMaxF32(state, lhs, rhs, EmitBitcastF32ToU32(state, lhs),
|
||||
EmitBitcastF32ToU32(state, rhs), max_value);
|
||||
}
|
||||
|
||||
uint32_t EmitFPMedTri32(EmitterState& state, uint32_t a, uint32_t b, uint32_t c) {
|
||||
const auto a_class = EmitClassifyF32(state, a);
|
||||
const auto b_class = EmitClassifyF32(state, b);
|
||||
const auto c_class = EmitClassifyF32(state, c);
|
||||
const auto min_ab = EmitMinMaxF32(state, a, b, a_class, b_class, false);
|
||||
const auto min_ab_class = EmitClassifyF32(state, min_ab);
|
||||
const auto min3 = EmitMinMaxF32(state, min_ab, c, min_ab_class, c_class, false);
|
||||
const auto max_ab = EmitMinMaxF32(state, a, b, a_class, b_class, true);
|
||||
const auto max_ab_class = EmitClassifyF32(state, max_ab);
|
||||
const auto max3 = EmitMinMaxF32(state, max_ab, c, max_ab_class, c_class, true);
|
||||
const auto max_bc = EmitMinMaxF32(state, b, c, b_class, c_class, true);
|
||||
const auto max_ac = EmitMinMaxF32(state, a, c, a_class, c_class, true);
|
||||
// RDNA2 compares numerically against MAX3, including ties between signed zeros.
|
||||
const auto a_bits = EmitBitcastF32ToU32(state, a);
|
||||
const auto b_bits = EmitBitcastF32ToU32(state, b);
|
||||
const auto c_bits = EmitBitcastF32ToU32(state, c);
|
||||
const auto min_ab = EmitMinMaxF32(state, a, b, a_bits, b_bits, false);
|
||||
const auto min3 =
|
||||
EmitMinMaxF32(state, min_ab, c, EmitBitcastF32ToU32(state, min_ab), c_bits, false);
|
||||
const auto max_ab = EmitMinMaxF32(state, a, b, a_bits, b_bits, true);
|
||||
const auto max3 =
|
||||
EmitMinMaxF32(state, max_ab, c, EmitBitcastF32ToU32(state, max_ab), c_bits, true);
|
||||
// RDNA2 compares numerically against MAX3, including ties between signed zeros, then takes
|
||||
// V_MAX_F32 of the other two sources in their original order.
|
||||
const auto a_is_max = Binary(state, spv::OpFOrdEqual, TypeBool(state), max3, a);
|
||||
const auto b_is_max = Binary(state, spv::OpFOrdEqual, TypeBool(state), max3, b);
|
||||
const auto median = Select(state, TypeF32(state), a_is_max, max_bc,
|
||||
Select(state, TypeF32(state), b_is_max, max_ac, max_ab));
|
||||
const auto nan_ab = EmitLogicalOrBool(state, a_class.nan, b_class.nan);
|
||||
const auto any_nan = EmitLogicalOrBool(state, nan_ab, c_class.nan);
|
||||
const auto x = Select(state, TypeF32(state), a_is_max, b, a);
|
||||
const auto y =
|
||||
Select(state, TypeF32(state), EmitLogicalOrBool(state, a_is_max, b_is_max), c, b);
|
||||
const auto median = EmitMinMaxF32Value(state, x, y, true);
|
||||
const auto is_nan = [&](uint32_t value) {
|
||||
return Unary(state, spv::OpIsNan, TypeBool(state), value);
|
||||
};
|
||||
const auto any_nan =
|
||||
EmitLogicalOrBool(state, EmitLogicalOrBool(state, is_nan(a), is_nan(b)), is_nan(c));
|
||||
return Select(state, TypeF32(state), any_nan, min3, median);
|
||||
}
|
||||
|
||||
|
||||
@@ -510,8 +510,6 @@ inline constexpr auto EmitLogicalNotBool = EmitNative<spv::OpLogicalNot, IR::Typ
|
||||
|
||||
F32Class EmitClassifyF32Bits(EmitterState& state, uint32_t bits);
|
||||
|
||||
F32Class EmitClassifyF32(EmitterState& state, uint32_t value);
|
||||
|
||||
uint32_t EmitClassMaskBitMatch(EmitterState& state, uint32_t mask, uint32_t bit,
|
||||
uint32_t class_match);
|
||||
|
||||
|
||||
@@ -27914,8 +27914,8 @@ TestCase VectorMed3F32NanUsesMin3Path(u32 wave) {
|
||||
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_MED3_F32,
|
||||
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
test.ir_counts = {{"FPMedTri32", std::size(cases)}};
|
||||
// Measured 17,593/36,607 words; keep classification growth below a single MED3.
|
||||
test.max_spirv_words = wave == 32u ? 17600u : 36620u;
|
||||
// Measured 16,140/33,705 words; keep min/max expansion growth below a single MED3.
|
||||
test.max_spirv_words = wave == 32u ? 16150u : 33720u;
|
||||
return test;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user