Repository navigation
Emulate PS2 float semantics for EE FPU results and DIV.S/RSQRT.S - #272
drakolordx7 wants to merge 1 commit into
Conversation
The EE FPU and the VUs have no Inf/NaN. A value with exponent 255 acts like a huge normal number, an overflowing result saturates to +-max (0x7F7FFFFF) and x/0 gives +-max with the sign of (fs xor ft). The generated code followed IEEE instead, so Inf/NaN leaked into later maths (a normalisation of a zero-length vector, for example). - ps2_runtime_macros.h: add ps2_fclamp/ps2_fdiv/ps2_frsqrt/ps2_vclamp. FPU_ADD_S/SUB_S/MUL_S clamp their inputs and result, FPU_DIV_S returns +-max on a zero divisor, FPU_SQRT_S takes sqrt(|x|), and PS2_VADD/VSUB/ VMUL clamp every lane. FPU_RSQRT_S is new. - fpu_translator.cpp: DIV.S no longer emits copysignf(INFINITY, ...) and wraps the DZ flag update and the division in one block. RSQRT.S was emitted as 1/sqrt(fs); the EE computes fs / sqrt(ft). Found while recompiling Killzone (SCUS-97402): RSQRT.S appears in 382 functions there (vector normalisation), and with the wrong operands the normals and lighting came out wrong. Verified with the new unit tests (edge values for each macro, and the translated DIV.S/RSQRT.S code with distinct fd/fs/ft), and by running Killzone with the change. Generated code must be regenerated and rebuilt to pick it up. CVT.W.S and the VU0 macro VDIV/VSQRT/VRSQRT Q-register operations are left to ran-j#215 and ran-j#198, which cover them. Made by drakolord and assisted with Claude Code.
| #define PS2_PNOR(a, b) _mm_xor_si128(_mm_or_si128((__m128i)(a), (__m128i)(b)), _mm_set1_epi32(0xFFFFFFFF)) | ||
|
|
||
| // PS2 float semantics (EE FPU and VU). The PS2 has no Inf/NaN: a value with exponent 255 behaves like a huge | ||
| // normal number, an overflowing result saturates to +-max (0x7F7FFFFF) and x/0 gives +-max with the sign of |
There was a problem hiding this comment.
0x7F7FFFFF is a float IEEE of 32 bits and not FPU from PS2.
| } | ||
|
|
||
| // fs / sqrt(|ft|); the radicand's sign is ignored. | ||
| inline float ps2_frsqrt(float a, float b) |
There was a problem hiding this comment.
Cheking this bettter you use a zero-denominator branch that reuses the sign rule for ordinary division, but RSQRT.S does not have the same behavior when ft is negative zero.
| // normal number, an overflowing result saturates to +-max (0x7F7FFFFF) and x/0 gives +-max with the sign of | ||
| // (numerator xor denominator). Hosts that follow IEEE produce Inf/NaN here, which then poisons later maths | ||
| // (e.g. a normalisation of a zero-length vector). | ||
| inline float ps2_fclamp(float x) |
There was a problem hiding this comment.
As I said early the clamping policy here conflates the host IEEE float range with the guest PS2 float range.
There was a problem hiding this comment.
And thats cary to ps2_fmax_signed and ps2_vclamp
| #define PS2_VADD(a, b) _mm_add_ps((__m128)(a), (__m128)(b)) | ||
| #define PS2_VSUB(a, b) _mm_sub_ps((__m128)(a), (__m128)(b)) | ||
| #define PS2_VMUL(a, b) _mm_mul_ps((__m128)(a), (__m128)(b)) | ||
| #define PS2_VADD(a, b) ps2_vclamp(_mm_add_ps(ps2_vclamp((__m128)(a)), ps2_vclamp((__m128)(b)))) |
There was a problem hiding this comment.
Could we benchmark the vector-clamping changes separately from the DIV.S/RSQRT.S fixes?
Becasue each of these macros now applies the vector clamp to both inputs and the result.
There was a problem hiding this comment.
I am not claiming a measured performance regression but just to check dont need to push a bench code just do this test please.
The PS2 EE FPU and the VUs have no Inf/NaN. A value with exponent 255 behaves like a huge normal number, an overflowing result saturates to ±max (
0x7F7FFFFF), and x/0 gives ±max with the sign of (numerator xor denominator). The generated code followed IEEE, so Inf/NaN leaked into later maths (for example normalising a zero-length vector).Changes
ps2_runtime_macros.h: newps2_fclamp/ps2_fdiv/ps2_frsqrt/ps2_vclamphelpers.FPU_ADD_S/SUB_S/MUL_Sclamp inputs and result;FPU_DIV_Sreturns ±max on a zero divisor;FPU_SQRT_Stakes sqrt(|x|); newFPU_RSQRT_S;PS2_VADD/VSUB/VMULclamp every lane (bit-based, so it does not depend on NaN handling in_mm_min_pson the sse2neon path).fpu_translator.cpp: DIV.S no longer emitscopysignf(INFINITY, ...). RSQRT.S was emitted as1/sqrt(fs); the EE computesfs / sqrt(ft).ps2_float_semantics_tests.cpp, 8 tests): edge values for each macro, and the translated DIV.S/RSQRT.S code with distinct fd/fs/ft registers.Found while recompiling Killzone (SCUS-97402): RSQRT.S is used in 382 functions there (vector normalisation), and with the wrong operands normals and lighting came out wrong.
Not included, because open PRs cover them: CVT.W.S truncation/saturation (#215) and the VU0 macro VDIV/VSQRT/VRSQRT Q operations (#198). Expect small textual conflicts with those two in
ps2_runtime_macros.hand the test registration.Notes: generated code has to be regenerated and rebuilt. The clamps add a few integer ops per FPU add/sub/mul; this has not been benchmarked separately.
Tests:
ps2x_testsrun from the repository root: 444/444 (436/436 on main).Made by drakolord and assisted with Claude Code.