fix(shader): choose v_div_scale_f32/v_div_fmas_f32 NaNs on the bits and refuse s_denorm_mode
v_div_scale_f32 returns a NaN source unchanged instead of scaling it, and v_div_fmas_f32 quiets the first NaN input or returns the default NaN 0xffc00000, so results no longer depend on the driver's NaNs. The test compares NaN results bit for bit. s_denorm_mode throws: the recompiler does not pin a denormal mode, so no value matches what it models. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
11bc279945
commit
1a5507698b
|
|
@ -30,8 +30,8 @@ alignas(256) constexpr std::array<std::uint32_t, 28> HelperCode{
|
|||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 29> DivisionCode{
|
||||
0x34020082, 0xe0302000, 0x80000401, 0xe0302004, 0x80000501, 0xbf8c3f70, 0xd56d1408, 0x04120b05,
|
||||
0xd56d6a09, 0x04120b04, 0x7e145508, 0xbfa5000f, 0xd54b000b, 0x23ca1508, 0x5614150b, 0x10161509,
|
||||
0xd54b000c, 0x24261708, 0x5616150c, 0xd54b0008, 0x24261708, 0xbfa5000c, 0xd56f0008, 0x042e1508,
|
||||
0xd56d6a09, 0x04120b04, 0x7e145508, 0xbf800000, 0xd54b000b, 0x23ca1508, 0x5614150b, 0x10161509,
|
||||
0xd54b000c, 0x24261708, 0x5616150c, 0xd54b0008, 0x24261708, 0xbf800000, 0xd56f0008, 0x042e1508,
|
||||
0xd55f000a, 0x04120b08, 0xe0702000, 0x80010a01, 0xbf810000,
|
||||
};
|
||||
|
||||
|
|
@ -604,14 +604,6 @@ private:
|
|||
ShaderRecompiler::RecompileResult result;
|
||||
};
|
||||
|
||||
bool IsNan(std::uint32_t bits) {
|
||||
return (bits & 0x7fffffffu) > 0x7f800000u;
|
||||
}
|
||||
|
||||
bool Same(std::uint32_t actual, std::uint32_t expected) {
|
||||
return actual == expected || (IsNan(actual) && IsNan(expected));
|
||||
}
|
||||
|
||||
std::string Hex(std::uint32_t value) {
|
||||
char text[16];
|
||||
std::snprintf(text, sizeof(text), "0x%08x", value);
|
||||
|
|
@ -643,10 +635,10 @@ void CheckHelpers(AgcDriver::VulkanDevice& device, bool fused) {
|
|||
const auto* out = &Output[lane * Stride];
|
||||
const std::string where = "division helpers: vector " + std::to_string(first + lane) + " (" + Hex(vector.s0) + ", " + Hex(vector.s1) + ", " + Hex(vector.s2) + ")";
|
||||
if (vector.s0 == vector.s1 || vector.s0 == vector.s2) {
|
||||
Require(Same(out[0], vector.scale) && out[1] == vector.scaleVcc, where + ": v_div_scale_f32 is " + Hex(out[0]) + " vcc " + std::to_string(out[1]) + ", expected " + Hex(vector.scale) + " vcc " + std::to_string(vector.scaleVcc));
|
||||
Require(out[0] == vector.scale && out[1] == vector.scaleVcc, where + ": v_div_scale_f32 is " + Hex(out[0]) + " vcc " + std::to_string(out[1]) + ", expected " + Hex(vector.scale) + " vcc " + std::to_string(vector.scaleVcc));
|
||||
}
|
||||
Require(!fused || Same(out[2], vector.fmas), where + " vcc " + std::to_string(vector.vcc) + ": v_div_fmas_f32 is " + Hex(out[2]) + ", expected " + Hex(vector.fmas));
|
||||
Require(Same(out[3], vector.fixup), where + ": v_div_fixup_f32 is " + Hex(out[3]) + ", expected " + Hex(vector.fixup));
|
||||
Require(!fused || out[2] == vector.fmas, where + " vcc " + std::to_string(vector.vcc) + ": v_div_fmas_f32 is " + Hex(out[2]) + ", expected " + Hex(vector.fmas));
|
||||
Require(out[3] == vector.fixup, where + ": v_div_fixup_f32 is " + Hex(out[3]) + ", expected " + Hex(vector.fixup));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -664,7 +656,7 @@ void CheckDivision(AgcDriver::VulkanDevice& device) {
|
|||
for (std::uint32_t lane = 0; lane < Threads && first + lane < count; ++lane) {
|
||||
const auto& vector = DivisionVectors[first + lane];
|
||||
const auto actual = Output[lane * Stride];
|
||||
Require(Same(actual, vector.quotient), "division macro: " + Hex(vector.numerator) + " / " + Hex(vector.denominator) + " is " + Hex(actual) + ", expected " + Hex(vector.quotient));
|
||||
Require(actual == vector.quotient, "division macro: " + Hex(vector.numerator) + " / " + Hex(vector.denominator) + " is " + Hex(actual) + ", expected " + Hex(vector.quotient));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -287,7 +287,10 @@ bool TranslationContext::vDivScaleF32(const RdnaInstruction& inst) {
|
|||
ir.LogicalOr(ir.LogicalNot(nearMin.Value()), isDenominator.Value())));
|
||||
const IrF32 up(ir.Emit(IrOpcode::FPMul32, IrType::F32, {&value.Value(), &ir.ConstantF32(18446744073709551616.0f)}));
|
||||
const IrF32 down(ir.Emit(IrOpcode::FPMul32, IrType::F32, {&value.Value(), &ir.ConstantF32(5.42101086242752217e-20f)}));
|
||||
const IrU32 scaled(ir.Select(scaleUp.Value(), ir.BitCastU32(up.Value()), ir.Select(scaleDown.Value(), ir.BitCastU32(down.Value()), bits.Value())));
|
||||
const IrU1 valueNan(ir.UGreaterThan(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffffffu)), ir.Constant(0x7f800000u)));
|
||||
const IrU1 scaleUpNumber(ir.LogicalAnd(scaleUp.Value(), ir.LogicalNot(valueNan.Value())));
|
||||
const IrU1 scaleDownNumber(ir.LogicalAnd(scaleDown.Value(), ir.LogicalNot(valueNan.Value())));
|
||||
const IrU32 scaled(ir.Select(scaleUpNumber.Value(), ir.BitCastU32(up.Value()), ir.Select(scaleDownNumber.Value(), ir.BitCastU32(down.Value()), bits.Value())));
|
||||
const auto isZero = [&](IrU32 word) { return IrU1(ir.IEqual(ir.BitwiseAnd(word.Value(), ir.Constant(0x7fffffffu)), ir.Constant(0u))); };
|
||||
const IrU1 zero(ir.LogicalOr(isZero(denominator).Value(), isZero(numerator).Value()));
|
||||
const IrU32 result(ir.Select(zero.Value(), ir.Constant(0xffc00000u), scaled.Value()));
|
||||
|
|
@ -322,7 +325,14 @@ bool TranslationContext::vDivFmasF32(const RdnaInstruction& inst) {
|
|||
&ir.Emit(IrOpcode::FPMul32, IrType::F32, {addend, &power})});
|
||||
IrValue& afterwards = ir.Emit(IrOpcode::FPMul32, IrType::F32, {&plain, &power});
|
||||
IrValue& scaledResult = ir.Emit(IrOpcode::SelectF32, IrType::F32, {&rescale.Value(), &rescaled, &afterwards});
|
||||
writeOperand(inst.destination, &ir.Emit(IrOpcode::SelectF32, IrType::F32, {&scale.Value(), &scaledResult, &plain}));
|
||||
const auto isNan = [&](IrValue& value) { return IrU1(ir.UGreaterThan(ir.BitwiseAnd(ir.BitCastU32(value), ir.Constant(0x7fffffffu)), ir.Constant(0x7f800000u))); };
|
||||
const auto quiet = [&](IrValue& value) { return IrU32(ir.BitwiseOr(ir.BitCastU32(value), ir.Constant(0x400000u))); };
|
||||
IrValue& computed = ir.Emit(IrOpcode::SelectF32, IrType::F32, {&scale.Value(), &scaledResult, &plain});
|
||||
IrU32 result(ir.Select(isNan(computed).Value(), ir.Constant(0xffc00000u), ir.BitCastU32(computed)));
|
||||
result = IrU32(ir.Select(isNan(*addend).Value(), quiet(*addend).Value(), result.Value()));
|
||||
result = IrU32(ir.Select(isNan(*rhs).Value(), quiet(*rhs).Value(), result.Value()));
|
||||
result = IrU32(ir.Select(isNan(*lhs).Value(), quiet(*lhs).Value(), result.Value()));
|
||||
writeOperand(inst.destination, &ir.BitCastF32(result.Value()));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "Translation/ScalarInstructions.hpp"
|
||||
#include "Translation/TranslationContext.hpp"
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace ShaderRecompiler {
|
||||
|
||||
|
|
@ -377,9 +378,10 @@ bool TranslationContext::emitScalar(const RdnaInstruction& inst) {
|
|||
case RdnaOpcode::SIcacheInv:
|
||||
case RdnaOpcode::SIncperflevel:
|
||||
case RdnaOpcode::SDecperflevel:
|
||||
case RdnaOpcode::SDenormMode:
|
||||
emitControlNop();
|
||||
return true;
|
||||
case RdnaOpcode::SDenormMode:
|
||||
throw std::runtime_error("s_denorm_mode " + std::to_string(inst.source0.value) + " at pc " + std::to_string(inst.programCounter) + ": the recompiler does not model denormal modes");
|
||||
case RdnaOpcode::SWaitcntDepctr:
|
||||
case RdnaOpcode::SWaitIdle:
|
||||
emitWaitcnt();
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- The shader recompiler [skips baryctric coordinates](../../core/shader/recompiler/Recompiler.cpp) (is not even passed to SpirvTargetOptions at row 212).
|
||||
- [libSceAvPlayer](../../core/libs/prx/libSceAvPlayer/Export.cpp): `sceAvPlayerSetLogCallback` accepts a callback that is never called, as the player produces no log messages, and `sceAvPlayerSetAvailableBandwidth` has no effect, as it governs HLS sources, which `sceAvPlayerAddSource` does not implement.
|
||||
- [sceAvPlayerSetTrickSpeed](../../core/libs/prx/libSceAvPlayer/src/Source.cpp) (libSceAvPlayer) with a negative speed runs the clock backwards but delivers no frames; when a forward speed is set again, playback resumes from the rewound time.
|
||||
- The shader recompiler [ignores `s_denorm_mode`](../../core/shader/recompiler/Translation/src/ScalarInstructions.cpp): denormals follow the Vulkan driver's float controls, not the mode the shader sets (the f32 division macro switches it around its Newton-Raphson steps).
|
||||
|
||||
### Unknown function info
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue