From e6185ba6bb837314660bcd01a401f9fff2d8d2bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 23 Jan 2026 10:40:54 +0100 Subject: [PATCH] Clean out NaNs and INFs from vertex coordinates This enables a behavior seen on the real PSP where 0 * NaN == 0 in the GPU (NOTE: This means the vertex transform pipeline). However, this doesn't touch INFs unfortunately, and we need to modify those too... This, together with #21715, finally fixes #20204 . --- Common/Arm64Emitter.cpp | 8 ++ Common/Arm64Emitter.h | 2 + Common/ArmEmitter.cpp | 32 +----- Common/Math/CrossSIMD.h | 41 +++++++ Common/Math/SIMDHeaders.h | 136 +++++++++++++++++++++++- Common/x64Emitter.h | 8 ++ GPU/Common/VertexDecoderArm.cpp | 31 +++++- GPU/Common/VertexDecoderArm64.cpp | 33 ++++-- GPU/Common/VertexDecoderCommon.cpp | 6 +- GPU/Common/VertexDecoderLoongArch64.cpp | 1 + GPU/Common/VertexDecoderX86.cpp | 30 +++--- unittest/TestArmEmitter.cpp | 17 ++- 12 files changed, 284 insertions(+), 61 deletions(-) diff --git a/Common/Arm64Emitter.cpp b/Common/Arm64Emitter.cpp index 4c67f813a2..afddd61045 100644 --- a/Common/Arm64Emitter.cpp +++ b/Common/Arm64Emitter.cpp @@ -3115,6 +3115,10 @@ void ARM64FloatEmitter::FMAX(u8 size, ARM64Reg Rd, ARM64Reg Rn, ARM64Reg Rm) { EmitThreeSame(0, size >> 6, 0x1E, Rd, Rn, Rm); } +void ARM64FloatEmitter::FMAXNM(u8 size, ARM64Reg Rd, ARM64Reg Rn, ARM64Reg Rm) +{ + EmitThreeSame(0, size >> 6, 0x18, Rd, Rn, Rm); +} void ARM64FloatEmitter::FMLA(u8 size, ARM64Reg Rd, ARM64Reg Rn, ARM64Reg Rm) { EmitThreeSame(0, size >> 6, 0x19, Rd, Rn, Rm); @@ -3123,6 +3127,10 @@ void ARM64FloatEmitter::FMIN(u8 size, ARM64Reg Rd, ARM64Reg Rn, ARM64Reg Rm) { EmitThreeSame(0, 2 | size >> 6, 0x1E, Rd, Rn, Rm); } +void ARM64FloatEmitter::FMINNM(u8 size, ARM64Reg Rd, ARM64Reg Rn, ARM64Reg Rm) +{ + EmitThreeSame(0, 2 | size >> 6, 0x18, Rd, Rn, Rm); +} void ARM64FloatEmitter::FCVTL(u8 size, ARM64Reg Rd, ARM64Reg Rn) { Emit2RegMisc(false, 0, size >> 6, 0x17, Rd, Rn); diff --git a/Common/Arm64Emitter.h b/Common/Arm64Emitter.h index 81a8325c4b..b1d325871f 100644 --- a/Common/Arm64Emitter.h +++ b/Common/Arm64Emitter.h @@ -858,9 +858,11 @@ public: void FADD(u8 size, ARM64Reg Rd, ARM64Reg Rn, ARM64Reg Rm); void FADDP(u8 size, ARM64Reg Rd, ARM64Reg Rn, ARM64Reg Rm); void FMAX(u8 size, ARM64Reg Rd, ARM64Reg Rn, ARM64Reg Rm); + void FMAXNM(u8 size, ARM64Reg Rd, ARM64Reg Rn, ARM64Reg Rm); void FMLA(u8 size, ARM64Reg Rd, ARM64Reg Rn, ARM64Reg Rm); void FMLS(u8 size, ARM64Reg Rd, ARM64Reg Rn, ARM64Reg Rm); void FMIN(u8 size, ARM64Reg Rd, ARM64Reg Rn, ARM64Reg Rm); + void FMINNM(u8 size, ARM64Reg Rd, ARM64Reg Rn, ARM64Reg Rm); void FCVTL(u8 size, ARM64Reg Rd, ARM64Reg Rn); void FCVTL2(u8 size, ARM64Reg Rd, ARM64Reg Rn); void FCVTN(u8 dest_size, ARM64Reg Rd, ARM64Reg Rn); diff --git a/Common/ArmEmitter.cpp b/Common/ArmEmitter.cpp index 40bdca9ab8..e974196f44 100644 --- a/Common/ArmEmitter.cpp +++ b/Common/ArmEmitter.cpp @@ -492,48 +492,21 @@ void ARMXEmitter::MOVI2R(ARMReg reg, u32 val, bool optimize) Operand2 op2; bool inverse; -#if PPSSPP_ARCH(ARMV7) // Unused - if (!optimize) - { + if (!optimize) { // For backpatching on ARMv7 MOVW(reg, val & 0xFFFF); MOVT(reg, val, true); return; } -#endif if (TryMakeOperand2_AllowInverse(val, op2, &inverse)) { inverse ? MVN(reg, op2) : MOV(reg, op2); } else { -#if PPSSPP_ARCH(ARMV7) // Use MOVW+MOVT for ARMv7+ MOVW(reg, val & 0xFFFF); - if(val & 0xFFFF0000) + if (val & 0xFFFF0000) MOVT(reg, val, true); -#else - if (!TrySetValue_TwoOp(reg,val)) { - bool first = true; - for (int i = 0; i < 32; i += 2) { - u8 bits = RotR(val, i) & 0xFF; - if ((bits & 3) != 0) { - u8 rotation = i == 0 ? 0 : 16 - i / 2; - if (first) { - MOV(reg, Operand2(bits, rotation)); - first = false; - } else { - ORR(reg, reg, Operand2(bits, rotation)); - } - // Well, we took care of these other bits while we were at it. - i += 8 - 2; - } - } - // Use literal pool for ARMv6. - // Disabled for now as it is crashfing since Vertex Decoder JIT -// AddNewLit(val); -// LDR(reg, R_PC); // To be backpatched later - } -#endif } } @@ -2153,6 +2126,7 @@ void ARMXEmitter::VDUP(u32 Size, ARMReg Vd, ARMReg Vm, u8 index) Write32((0xF3 << 24) | (0xB << 20) | (imm4 << 16) \ | EncodeVd(Vd) | (0xC << 8) | (register_quad << 6) | EncodeVm(Vm)); } + void ARMXEmitter::VDUP(u32 Size, ARMReg Vd, ARMReg Rt) { _dbg_assert_msg_(Vd >= D0, "Pass invalid register to %s", __FUNCTION__); diff --git a/Common/Math/CrossSIMD.h b/Common/Math/CrossSIMD.h index 00e9c1646b..b39733d338 100644 --- a/Common/Math/CrossSIMD.h +++ b/Common/Math/CrossSIMD.h @@ -315,6 +315,15 @@ struct Vec4F32 { return Vec4F32{_mm_or_ps(_mm_and_ps(maskVec, v), _mm_andnot_ps(maskVec, other.v))}; } + Vec4F32 ZeroNaNs() const { + return Vec4F32{ zero_nans_sse(v) }; + } + + // The output becomes something that when multiplied by zero, yields 0. + Vec4F32 CleanNaNInfs() const { + return Vec4F32{ clean_nan_inf_sse(v) }; + } + inline Vec4F32 AsVec3ByMatrix44(const Mat4F32 &m) { return Vec4F32{ _mm_add_ps( _mm_add_ps( @@ -709,6 +718,14 @@ struct Vec4F32 { return Vec4F32{vsetq_lane_f32(vgetq_lane_f32(other.v, 3), v, 3)}; } + Vec4F32 ZeroNaNs() const { + return Vec4F32{ zero_nans_neon(v)}; + } + + Vec4F32 CleanNaNInfs() const { + return Vec4F32{ clean_inf_and_nan_neon(v)}; + } + Vec4F32 ShuffleXXYY() const { float32x2_t low = vget_low_f32(v); // {X, Y} pair // Combine them into {X, X, Y, Y} @@ -1146,6 +1163,14 @@ struct Vec4F32 { }; } + Vec4F32 ZeroNaNs() const { + return Vec4F32{ zero_nans_lsx(v) }; + } + + Vec4F32 CleanNaNInfs() const { + return Vec4F32{ clean_nan_inf_lsx(v) }; + } + Vec4F32 WithLane3Zero() const { return Vec4F32{ (__m128)__lsx_vinsgr2vr_w((__m128i)v, 0, 3) }; } @@ -1544,6 +1569,22 @@ struct Vec4F32 { float operator[](size_t index) const { return v[index]; } + Vec4F32 ZeroNaNs() const { + Vec4F32 temp; + for (int i = 0; i < 4; i++) { + temp.v[i] = isnan(v[i]) ? 0.0f : v[i]; + } + return temp; + } + + Vec4F32 CleanNaNInfs() { + Vec4F32 temp; + for (int i = 0; i < 4; i++) { + temp.v[i] = (isnan(v[i]) || isinf(v[i])) ? 0.0f : v[i]; + } + return temp; + } + Vec4F32 operator +(Vec4F32 other) const { return Vec4F32{ { v[0] + other.v[0], v[1] + other.v[1], v[2] + other.v[2], v[3] + other.v[3], } }; } diff --git a/Common/Math/SIMDHeaders.h b/Common/Math/SIMDHeaders.h index 651992e9e9..8c740a328c 100644 --- a/Common/Math/SIMDHeaders.h +++ b/Common/Math/SIMDHeaders.h @@ -40,12 +40,82 @@ static inline __m128 __lsx_vreplfr2vr_s(float val) { memcpy(&bits, &val, sizeof(bits)); return (__m128)__lsx_vreplgr2vr_w(bits); } + +static inline __m128 zero_nans_lsx(__m128 val) { + // vfcmp.ceq.s compares each float lane. + // NaN == NaN is false, so NaNs result in 0x00000000. + // Numbers result in 0xFFFFFFFF. + __m128 mask = __lsx_vfcmp_ceq_s(val, val); + // Bitwise AND to keep non-NaNs and turn NaNs into 0.0f + return __lsx_vand_v(val, mask); +} + +static inline __m128 clean_nan_inf_lsx(__m128 val) { +#if 0 + // Strip sign bit + __m128i sign_mask = __lsx_vreplgr2vr_w(0x7FFFFFFF); + __m128i abs_x = __lsx_vand_v(x, sign_mask); + + // Infinity threshold + __m128i inf_val = __lsx_vreplgr2vr_w(0x7F800000); + + // Compare: Is |x| < Inf? (using signed comparison since 0x7F800000 fits) + __m128i finite_mask = __lsx_vslt_w(abs_x, inf_val); + + return __lsx_vand_v(x, finite_mask); +#else + // 1. Broadcast the floating-point limits + // (Compilers will typically optimize these to a single vector load) + __m128 max_finite = (__m128)__lsx_vreplgr2vr_w(0x7F7FFFFF); // FLT_MAX bits + __m128 min_finite = (__m128)__lsx_vreplgr2vr_w(0xFF7FFFFF); // -FLT_MAX bits + + // 2. Clear NaNs and clamp positive infinity + __m128 upper_clamped = __lsx_vfmin_s((__m128)x, max_finite); + + // 3. Clamp negative infinity + return (__m128i)__lsx_vfmax_s(upper_clamped, min_finite); +#endif +} + #endif #endif // Basic types -#if PPSSPP_ARCH(ARM64_NEON) +#if PPSSPP_ARCH(LOONGARCH64) && PPSSPP_ARCH(LOONGARCH64_LSX) + +static inline __m128i zero_nans_lsx(__m128i x) { + // vfcmp.ceq.s compares each float lane. + // NaN == NaN is false, so NaNs result in 0x00000000. + // Numbers result in 0xFFFFFFFF. + __m128i mask = (__m128i)__lsx_vfcmp_ceq_s((__m128)x, (__m128)x); + // Bitwise AND to keep non-NaNs and turn NaNs into 0.0f + return __lsx_vand_v(x, mask); +} + +__m128 zero_nan_inf_lsx(__m128 x) { + // 1. Take absolute value of the single-precision float vector + // Emits: vfabs.s vr, vr + __m128 x_abs = __lsx_vfabs_s(x); + + // 2. Load the Positive Infinity bit pattern (0x7F800000) + // Emits: vreplgr2vr.w to duplicate the GPR value into all SIMD lanes + __m128i inf_bytes = __lsx_vreplgr2vr_w(0x7F800000); + __m128 inf_vec = (__m128)inf_bytes; + + // 3. Compare x_abs < inf_vec + // clt variant: Compare Less Than, returns all ones for true, zero for false/NaN + // Emits: vfcmp.clt.s vr, vr, vr + __m128i valid_mask = __lsx_vfcmp_clt_s(x_abs, inf_vec); + + // 4. Bitwise AND to clear the invalid lanes + // Emits: vand.v vr, vr, vr + __m128i result_bytes = __lsx_vand_v((__m128i)x, valid_mask); + + return (__m128)result_bytes; +} + +#elif PPSSPP_ARCH(ARM64_NEON) // No special ones here. @@ -89,6 +159,29 @@ static inline uint32x4_t vcgezq_f32(float32x4_t v) { #endif +#if PPSSPP_ARCH(ARM_NEON) || PPSSPP_ARCH(ARM64_NEON) + +static inline float32x4_t zero_nans_neon(float32x4_t x) { + // vceqq_f32 returns 0 for NaNs (since NaN != NaN) + uint32x4_t mask = vceqq_f32(x, x); + // Bitwise AND the float bits with the mask + return vreinterpretq_f32_u32(vandq_u32(vreinterpretq_u32_f32(x), mask)); +} + +static inline float32x4_t clean_inf_and_nan_neon(float32x4_t x) { + // Alternate solution that zeroes always + uint32x4_t raw_bits = vreinterpretq_u32_f32(x); + // Shift left by 1 to remove the sign bit. + // Finite numbers will be less than (0x7F800000 << 1) which is 0xFF000000 + uint32x4_t abs_shifted = vshlq_n_u32(raw_bits, 1); + uint32x4_t inf_shifted = vdupq_n_u32(0xFF000000); + // Compare less-than (unsigned) + uint32x4_t finite_mask = vcltq_u32(abs_shifted, inf_shifted); + return vreinterpretq_f32_u32(vandq_u32(raw_bits, finite_mask)); +} + +#endif + #if PPSSPP_ARCH(SSE2) #if defined __SSE4_2__ @@ -194,4 +287,45 @@ inline __m128i _mm_cmplt_epu16(__m128i x, __m128i y) { return _mm_cmpgt_epu16(y, x); } +inline __m128 zero_nans_sse(__m128 x) { + // Returns 0xFFFFFFFF where x is NOT NaN, 0x00000000 where it IS NaN + __m128 mask = _mm_cmpord_ps(x, x); + // NaN & 0 = 0.0f. Finite & 0xFF... = Finite. + return _mm_and_ps(x, mask); +} + +inline __m128 clean_nan_inf_sse(__m128 x) { +#if 0 + // 1. Create a mask to strip the sign bit (0x7FFFFFFF) + __m128i sign_mask = _mm_set1_epi32(0x7FFFFFFF); + + // 2. Create a vector representing Positive Infinity (0x7F800000) + __m128i inf_bytes = _mm_set1_epi32(0x7F800000); + __m128 inf_vec = _mm_castsi128_ps(inf_bytes); + + // 3. Get absolute value: x_abs = x & 0x7FFFFFFF + __m128 x_abs = _mm_and_ps(x, _mm_castsi128_ps(sign_mask)); + + // 4. Compare x_abs < INF + // Finite numbers -> 0xFFFFFFFF + // INF and NaN -> 0x00000000 + __m128 valid_mask = _mm_cmplt_ps(x_abs, inf_vec); + + // 5. Zero out the invalid elements + return _mm_and_ps(x, valid_mask); +#else + // 1. Establish your maximum and minimum finite bounds + __m128 max_finite = _mm_set1_ps(3.40282347e+38f); // FLT_MAX + __m128 min_finite = _mm_set1_ps(-3.40282347e+38f); // -FLT_MAX + // 2. Clamp the upper bound. + // If x is +Inf, min(Inf, FLT_MAX) -> FLT_MAX. + // If x is NaN, it outputs the second operand -> max_finite (FLT_MAX). + __m128 upper_clamped = _mm_min_ps(x, max_finite); + // 3. Clamp the lower bound. + // If upper_clamped is -Inf, max(-Inf, -FLT_MAX) -> -FLT_MAX. + // If upper_clamped became FLT_MAX because it was a NaN, max(FLT_MAX, -FLT_MAX) -> FLT_MAX. + return _mm_max_ps(upper_clamped, min_finite); +#endif +} + #endif diff --git a/Common/x64Emitter.h b/Common/x64Emitter.h index f3ec30bd2e..871de99fa2 100644 --- a/Common/x64Emitter.h +++ b/Common/x64Emitter.h @@ -659,6 +659,14 @@ public: void RCPPS(X64Reg regOp, OpArg& arg); void RSQRTPS(X64Reg regOp, OpArg arg); + inline void CMPEQPS(X64Reg regOp, OpArg arg) { CMPPS(regOp, arg, CMP_EQ); } + inline void CMPLTPS(X64Reg regOp, OpArg arg) { CMPPS(regOp, arg, CMP_LT); } + inline void CMPLEPS(X64Reg regOp, OpArg arg) { CMPPS(regOp, arg, CMP_LE); } + inline void CMPUNORDPS(X64Reg regOp, OpArg arg) { CMPPS(regOp, arg, CMP_UNORD); } + inline void CMPNEQPS(X64Reg regOp, OpArg arg) { CMPPS(regOp, arg, CMP_NEQ); } + inline void CMPNLTPS(X64Reg regOp, OpArg arg) { CMPPS(regOp, arg, CMP_NLT); } + inline void CMPORDPS(X64Reg regOp, OpArg arg) { CMPPS(regOp, arg, CMP_ORD); } + // SSE/SSE2: Floating point packed bitwise (x4 for float, x2 for double) void ANDPS(X64Reg regOp, OpArg arg); void ANDPD(X64Reg regOp, OpArg arg); diff --git a/GPU/Common/VertexDecoderArm.cpp b/GPU/Common/VertexDecoderArm.cpp index 29c4368ad2..cd0f68f59c 100644 --- a/GPU/Common/VertexDecoderArm.cpp +++ b/GPU/Common/VertexDecoderArm.cpp @@ -24,8 +24,9 @@ #define ARM #endif -#include +#include +#include #include "Common/CPUDetect.h" #include "Core/Config.h" #include "GPU/GPUState.h" @@ -50,6 +51,7 @@ alignas(16) static float boneMask[4] = {1.0f, 1.0f, 1.0f, 0.0f}; // When morphing, we never skin. So we're free to use Q4+. // Q4 is for color shift values, and Q5 is a secondary multipler inside the morph. // TODO: Maybe load all morph weights to Q6+ to avoid memory access? +// Also in PosFloat we're free to use Q4. static const float by128 = 1.0f / 128.0f; static const float by16384 = 1.0f / 16384.0f; @@ -912,12 +914,33 @@ void VertexDecoderJitCache::Jit_PosS16() { VST1(F_32, srcNEON, scratchReg, 2); } -// Just copy 12 bytes. +// Clean NaNs and Infs from position floats void VertexDecoderJitCache::Jit_PosFloat() { ADD(scratchReg, srcReg, dec_->posoff); - LDMIA(scratchReg, false, 3, tempReg1, tempReg2, tempReg3); + // Load the 3 floats (12 bytes) into srcNEON (Q2) + VLD1(F_32, srcNEON, scratchReg, 2, ALIGN_NONE); + + // Efficient NaN/Inf detection: shift left by 1 to remove sign bit, then check if < 0xFF000000 + // Finite numbers: exponent < 0xFF, so (value << 1) < 0xFF000000 + // NaN or Inf: exponent == 0xFF, so (value << 1) >= 0xFF000000 + + // Shift left by 1 into neonScratchRegQ (Q1), treating srcNEON as integer bits + VSHL(I_32, neonScratchRegQ, srcNEON, 1); + + // Create the comparison value 0xFF000000 in Q4 (available, used when skinning but we're not skinning if we're in PosFloat) + MOVI2R(scratchReg, 0xFF000000); + VDUP(I_32, Q4, scratchReg); + + // Compare unsigned: finite_mask = (abs_shifted < inf_shifted) + // Result in neonScratchRegQ (Q1): all-1s where finite, all-0s where NaN/Inf + VCLT(I_32 | I_UNSIGNED, neonScratchRegQ, neonScratchRegQ, Q4); + + // AND with the mask to zero out NaN/Inf values, keep finite values + VAND(srcNEON, srcNEON, neonScratchRegQ); + + // Store the cleaned result ADD(scratchReg, dstReg, dec_->decFmt.posoff); - STMIA(scratchReg, false, 3, tempReg1, tempReg2, tempReg3); + VST1(F_32, srcNEON, scratchReg, 2, ALIGN_NONE); } void VertexDecoderJitCache::Jit_NormalS8Skin() { diff --git a/GPU/Common/VertexDecoderArm64.cpp b/GPU/Common/VertexDecoderArm64.cpp index 9c322247b8..0b58ee9843 100644 --- a/GPU/Common/VertexDecoderArm64.cpp +++ b/GPU/Common/VertexDecoderArm64.cpp @@ -18,6 +18,8 @@ #include "ppsspp_config.h" #if PPSSPP_ARCH(ARM64) +#include + #include "Common/CPUDetect.h" #include "Common/Log.h" #include "Core/Config.h" @@ -69,7 +71,6 @@ static const ARM64Reg src[2] = {S2, S3}; static const ARM64Reg srcD = D2; static const ARM64Reg srcQ = Q2; -static const ARM64Reg srcNEON = Q8; static const ARM64Reg accNEON = Q9; static const ARM64Reg neonWeightRegsQ[2] = { Q3, Q2 }; // reverse order to prevent clash with neonScratchReg in Jit_WeightsU*Skin. @@ -648,16 +649,24 @@ void VertexDecoderJitCache::Jit_PosS16() { } void VertexDecoderJitCache::Jit_PosFloat() { - // Only need to copy 12 bytes, but copying 16 should be okay (and is faster.) - if ((dec_->posoff & 7) == 0 && (dec_->decFmt.posoff & 7) == 0) { - LDP(INDEX_SIGNED, EncodeRegTo64(tempReg1), EncodeRegTo64(tempReg2), srcReg, dec_->posoff); - STP(INDEX_SIGNED, EncodeRegTo64(tempReg1), EncodeRegTo64(tempReg2), dstReg, dec_->decFmt.posoff); - } else { - LDP(INDEX_SIGNED, tempReg1, tempReg2, srcReg, dec_->posoff); - STP(INDEX_SIGNED, tempReg1, tempReg2, dstReg, dec_->decFmt.posoff); - LDR(INDEX_UNSIGNED, tempReg3, srcReg, dec_->posoff + 8); - STR(INDEX_UNSIGNED, tempReg3, dstReg, dec_->decFmt.posoff + 8); - } + // Load the 3 floats (12 bytes) into neonScratchRegQ (Q2) + fp.LDUR(128, neonScratchRegQ, srcReg, dec_->posoff); + // Check for NaN: compare each element with itself; NaN != NaN, so comparison produces 0 for NaNs + fp.FCMEQ(32, neonScratchReg2Q, neonScratchRegQ, neonScratchRegQ); + // Now neonScratchReg2Q contains all 1s for non-NaN values, all 0s for NaN values + // AND with the original to zero out NaNs + fp.AND(neonScratchRegQ, neonScratchRegQ, neonScratchReg2Q); + // Now handle infinities by clamping + // Load FLT_MAX into neonScratchReg2Q (Q3) + fp.MOVI2FDUP(neonScratchReg2Q, FLT_MAX, tempReg1); + // Clamp to FLT_MAX (handles +Inf and values > FLT_MAX) + fp.FMINNM(32, neonScratchRegQ, neonScratchRegQ, neonScratchReg2Q); + // Negate to get -FLT_MAX + fp.FNEG(32, neonScratchReg2Q, neonScratchReg2Q); + // Clamp to -FLT_MAX (handles -Inf and values < -FLT_MAX) + fp.FMAXNM(32, neonScratchRegQ, neonScratchRegQ, neonScratchReg2Q); + // Store the cleaned result + fp.STUR(128, neonScratchRegQ, dstReg, dec_->decFmt.posoff); } void VertexDecoderJitCache::Jit_PosS8Through() { @@ -680,6 +689,8 @@ void VertexDecoderJitCache::Jit_PosS16Through() { } void VertexDecoderJitCache::Jit_PosFloatThrough() { + // TODO: Should probably clean out infs just like in Jit_PosFloat... + // Instead of just copying 12 bytes, we copy 8 and clamp Z. if ((dec_->posoff & 7) == 0 && (dec_->decFmt.posoff & 7) == 0) { LDR(INDEX_UNSIGNED, EncodeRegTo64(tempReg1), srcReg, dec_->posoff); diff --git a/GPU/Common/VertexDecoderCommon.cpp b/GPU/Common/VertexDecoderCommon.cpp index ec4721c029..1817621890 100644 --- a/GPU/Common/VertexDecoderCommon.cpp +++ b/GPU/Common/VertexDecoderCommon.cpp @@ -866,9 +866,8 @@ void VertexDecoder::Step_PosS16(const VertexDecoder *dec, const u8 *ptr, u8 *dec } void VertexDecoder::Step_PosFloat(const VertexDecoder *dec, const u8 *ptr, u8 *decoded) { - u8 *v = (u8 *)(decoded + dec->decFmt.posoff); - const u8 *fv = (const u8*)(ptr + dec->posoff); - memcpy(v, fv, 12); + Vec4F32 v = Vec4F32::Load((const float *)(ptr + dec->posoff)); + v.CleanNaNInfs().Store((float *)(decoded + dec->decFmt.posoff)); } void VertexDecoder::Step_PosS8Skin(const VertexDecoder *dec, const u8 *ptr, u8 *decoded) { @@ -886,6 +885,7 @@ void VertexDecoder::Step_PosS16Skin(const VertexDecoder *dec, const u8 *ptr, u8 } void VertexDecoder::Step_PosFloatSkin(const VertexDecoder *dec, const u8 *ptr, u8 *decoded) { + // TODO: Clean for NaN/INF here. float *pos = (float *)(decoded + dec->decFmt.posoff); const float_le *fn = (const float_le *)(ptr + dec->posoff); Vec3ByMatrix43(pos, fn, skinMatrix); diff --git a/GPU/Common/VertexDecoderLoongArch64.cpp b/GPU/Common/VertexDecoderLoongArch64.cpp index a80780292d..ba5ba90433 100644 --- a/GPU/Common/VertexDecoderLoongArch64.cpp +++ b/GPU/Common/VertexDecoderLoongArch64.cpp @@ -1032,6 +1032,7 @@ void VertexDecoderJitCache::Jit_PosS16() { void VertexDecoderJitCache::Jit_PosFloat() { // Just copy 12 bytes, play with over read/write later. + // TODO: This should clean out inf and NaN values. LD_W(tempReg1, srcReg, dec_->posoff + 0); LD_W(tempReg2, srcReg, dec_->posoff + 4); LD_W(tempReg3, srcReg, dec_->posoff + 8); diff --git a/GPU/Common/VertexDecoderX86.cpp b/GPU/Common/VertexDecoderX86.cpp index 16935da924..765a5587da 100644 --- a/GPU/Common/VertexDecoderX86.cpp +++ b/GPU/Common/VertexDecoderX86.cpp @@ -19,6 +19,7 @@ #if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64) +#include #include "Common/CPUDetect.h" #include "Common/Data/Convert/ColorConv.h" #include "Common/Math/SIMDHeaders.h" @@ -1468,21 +1469,25 @@ void VertexDecoderJitCache::Jit_PosS16() { MOVUPS(MDisp(dstReg, dec_->decFmt.posoff), XMM3); } -// Just copy 12 bytes. void VertexDecoderJitCache::Jit_PosFloat() { - if (cpu_info.Mode64bit) { - MOV(64, R(tempReg1), MDisp(srcReg, dec_->posoff)); - MOV(32, R(tempReg3), MDisp(srcReg, dec_->posoff + 8)); - MOV(64, MDisp(dstReg, dec_->decFmt.posoff), R(tempReg1)); - MOV(32, MDisp(dstReg, dec_->decFmt.posoff + 8), R(tempReg3)); + // Load the constants we need. + alignas(16) static const float fltMax[4] = {FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX,}; + alignas(16) static const float fltMaxNeg[4] = {-FLT_MAX, -FLT_MAX, -FLT_MAX, -FLT_MAX,}; + if (RipAccessible(&fltMax)) { + MOVAPS(fpScratchReg2, M(&fltMax)); // rip accessible + MOVAPS(fpScratchReg3, M(&fltMaxNeg)); } else { - MOV(32, R(tempReg1), MDisp(srcReg, dec_->posoff)); - MOV(32, R(tempReg2), MDisp(srcReg, dec_->posoff + 4)); - MOV(32, R(tempReg3), MDisp(srcReg, dec_->posoff + 8)); - MOV(32, MDisp(dstReg, dec_->decFmt.posoff), R(tempReg1)); - MOV(32, MDisp(dstReg, dec_->decFmt.posoff + 4), R(tempReg2)); - MOV(32, MDisp(dstReg, dec_->decFmt.posoff + 8), R(tempReg3)); + MOV(PTRBITS, R(tempReg1), ImmPtr(&fltMax)); + MOVAPS(XMM4, MatR(tempReg1)); + MOV(PTRBITS, R(tempReg1), ImmPtr(&fltMaxNeg)); + MOVAPS(XMM5, MatR(tempReg1)); } + + MOVUPS(fpScratchReg, MDisp(srcReg, dec_->posoff)); + MINPS(fpScratchReg, R(fpScratchReg2)); + MAXPS(fpScratchReg, R(fpScratchReg3)); + // Just store 4 floats. We'll overwrite the last one with other components or the next vertex. + MOVUPS(MDisp(dstReg, dec_->decFmt.posoff), fpScratchReg); } void VertexDecoderJitCache::Jit_PosS8Skin() { @@ -1496,6 +1501,7 @@ void VertexDecoderJitCache::Jit_PosS16Skin() { } void VertexDecoderJitCache::Jit_PosFloatSkin() { + // TODO: Should clean inf/nan here. MOVUPS(XMM3, MDisp(srcReg, dec_->posoff)); Jit_WriteMatrixMul(dec_->decFmt.posoff, true); } diff --git a/unittest/TestArmEmitter.cpp b/unittest/TestArmEmitter.cpp index 2083e88a90..ee1f342dbc 100644 --- a/unittest/TestArmEmitter.cpp +++ b/unittest/TestArmEmitter.cpp @@ -193,11 +193,26 @@ bool TestArmEmitter() { RET(CheckLast(emitter, "f2042156 VAND q1, q2, q3")); emitter.VDUP(F_32, Q14, D30, 1); RET(CheckLast(emitter, "f3fccc6e VDUP.32 q14, d30[1]")); - + // TODO: This is broken. // emitter.VDUP(F_32, D14, D30, 1); // RET(CheckLast(emitter, "f3bcec2e VDUP.32 d14, d30[1]")); + // VDUP from GPR to NEON register + // TODO: Fix the disassembler + emitter.VDUP(I_32, Q0, R0); + RET(CheckLast(emitter, "eea00b10 [undefined instr]")); // VDUP.32 q0, r0")); + emitter.VDUP(I_32, Q5, R3); + RET(CheckLast(emitter, "eeaa3b10 [undefined instr]")); // VDUP.32 q5, r3")); + emitter.VDUP(I_16, Q2, R7); + RET(CheckLast(emitter, "eea47b30 [undefined instr]")); // VDUP.16 q2, r7")); + emitter.VDUP(I_8, Q1, R2); + RET(CheckLast(emitter, "eec22b90 [undefined instr]")); // VDUP.8 q1, r2")); + emitter.VDUP(I_32, D0, R0); + RET(CheckLast(emitter, "ee800b10 [undefined instr]")); // VDUP.32 d0, r0")); + emitter.VDUP(I_16, D3, R5); + RET(CheckLast(emitter, "ee835b30 [undefined instr]")); // VDUP.16 d3, r5")); + //emitter.VNEG(S1, S2); //RET(CheckLast(emitter, "eef10a60 VNEG.f32 s1, s1")); emitter.VNEG(F_32, Q1, Q2);