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 .
This commit is contained in:
Henrik Rydgård
2026-01-23 10:40:54 +01:00
parent 21e4677305
commit e6185ba6bb
12 changed files with 284 additions and 61 deletions
+8
View File
@@ -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);
+2
View File
@@ -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);
+3 -29
View File
@@ -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__);
+41
View File
@@ -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], } };
}
+135 -1
View File
@@ -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
+8
View File
@@ -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);
+27 -4
View File
@@ -24,8 +24,9 @@
#define ARM
#endif
#include <stddef.h>
#include <cfloat>
#include <stddef.h>
#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() {
+22 -11
View File
@@ -18,6 +18,8 @@
#include "ppsspp_config.h"
#if PPSSPP_ARCH(ARM64)
#include <cfloat>
#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);
+3 -3
View File
@@ -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);
+1
View File
@@ -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);
+18 -12
View File
@@ -19,6 +19,7 @@
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
#include <cfloat>
#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);
}
+16 -1
View File
@@ -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);