From 37f6fda9c901fab5ba718570a45050b5ed2cb314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 23 May 2026 14:37:11 +0200 Subject: [PATCH 1/4] Add a CrossSIMD loongarch64 LSX implementation. Note: This is untested! --- Common/Math/CrossSIMD.h | 402 ++++++++++++++++++++++++++++++++ GPU/Common/DrawEngineCommon.cpp | 2 +- 2 files changed, 403 insertions(+), 1 deletion(-) diff --git a/Common/Math/CrossSIMD.h b/Common/Math/CrossSIMD.h index 9376936fe1..641ab53916 100644 --- a/Common/Math/CrossSIMD.h +++ b/Common/Math/CrossSIMD.h @@ -792,6 +792,408 @@ struct Vec8U16 { void Store(uint16_t *mem) { vst1q_u16(mem, v); } }; + +#elif PPSSPP_ARCH(LOONGARCH64_LSX) + +struct Mat4F32 { + Mat4F32() {} + Mat4F32(const float *matrix) { + col0 = (__m128)__lsx_vld(matrix, 0); + col1 = (__m128)__lsx_vld(matrix + 4, 0); + col2 = (__m128)__lsx_vld(matrix + 8, 0); + col3 = (__m128)__lsx_vld(matrix + 12, 0); + } + void Store(float *m) { + __lsx_vst(col0, m, 0); + __lsx_vst(col1, m + 4, 0); + __lsx_vst(col2, m + 8, 0); + __lsx_vst(col3, m + 12, 0); + } + + // Unlike the old one, this one is careful about not loading out-of-range data. + // The last two loads overlap. + static Mat4F32 Load4x3(const float *m) { + Mat4F32 result; + __m128 mask1110 = (__m128)__lsx_vreplgr2vr_w(0xFFFFFFFF); + mask1110 = (__m128)__lsx_vinsgr2vr_w((__m128i)mask1110, 0, 3); + result.col0 = (__m128)__lsx_vand_v((__m128i)__lsx_vld(m, 0), (__m128i)mask1110); + result.col1 = (__m128)__lsx_vand_v((__m128i)__lsx_vld(m + 3, 0), (__m128i)mask1110); + result.col2 = (__m128)__lsx_vand_v((__m128i)__lsx_vld(m + 6, 0), (__m128i)mask1110); + __m128 lastCol = (__m128)__lsx_vld(m + 8, 0); + // Shuffle to get [m[9], m[10], m[11], x] then mask and add 1.0f to lane 3 + lastCol = (__m128)__lsx_vshuf4i_w((__m128i)lastCol, 0b11100100); // [1, 2, 3, 3] + result.col3 = (__m128)__lsx_vand_v((__m128i)lastCol, (__m128i)mask1110); + result.col3 = (__m128)__lsx_vfadd_s(result.col3, (__m128)__lsx_vinsgr2vr_w(__lsx_vldi(0), *(int *)&(float){1.0f}, 3)); + return result; + } + + __m128 col0; + __m128 col1; + __m128 col2; + __m128 col3; +}; + +// The columns are spread out between the data*. This is just intermediate storage for multiplication. +struct Mat4x3F32 { + Mat4x3F32(const float *matrix) { + data0 = (__m128)__lsx_vld(matrix, 0); + data1 = (__m128)__lsx_vld(matrix + 4, 0); + data2 = (__m128)__lsx_vld(matrix + 8, 0); + } + + __m128 data0; + __m128 data1; + __m128 data2; +}; + +inline Mat4F32 Mul4x4By4x4(Mat4F32 a, Mat4F32 b) { + Mat4F32 result; + + __m128 r_col = (__m128)__lsx_vfmul_s(b.col0, (__m128)__lsx_vshuf4i_w((__m128i)a.col0, 0b00000000)); + r_col = (__m128)__lsx_vfmadd_s(b.col1, (__m128)__lsx_vshuf4i_w((__m128i)a.col0, 0b01010101), r_col); + r_col = (__m128)__lsx_vfmadd_s(b.col2, (__m128)__lsx_vshuf4i_w((__m128i)a.col0, 0b10101010), r_col); + result.col0 = (__m128)__lsx_vfmadd_s(b.col3, (__m128)__lsx_vshuf4i_w((__m128i)a.col0, 0b11111111), r_col); + + r_col = (__m128)__lsx_vfmul_s(b.col0, (__m128)__lsx_vshuf4i_w((__m128i)a.col1, 0b00000000)); + r_col = (__m128)__lsx_vfmadd_s(b.col1, (__m128)__lsx_vshuf4i_w((__m128i)a.col1, 0b01010101), r_col); + r_col = (__m128)__lsx_vfmadd_s(b.col2, (__m128)__lsx_vshuf4i_w((__m128i)a.col1, 0b10101010), r_col); + result.col1 = (__m128)__lsx_vfmadd_s(b.col3, (__m128)__lsx_vshuf4i_w((__m128i)a.col1, 0b11111111), r_col); + + r_col = (__m128)__lsx_vfmul_s(b.col0, (__m128)__lsx_vshuf4i_w((__m128i)a.col2, 0b00000000)); + r_col = (__m128)__lsx_vfmadd_s(b.col1, (__m128)__lsx_vshuf4i_w((__m128i)a.col2, 0b01010101), r_col); + r_col = (__m128)__lsx_vfmadd_s(b.col2, (__m128)__lsx_vshuf4i_w((__m128i)a.col2, 0b10101010), r_col); + result.col2 = (__m128)__lsx_vfmadd_s(b.col3, (__m128)__lsx_vshuf4i_w((__m128i)a.col2, 0b11111111), r_col); + + r_col = (__m128)__lsx_vfmul_s(b.col0, (__m128)__lsx_vshuf4i_w((__m128i)a.col3, 0b00000000)); + r_col = (__m128)__lsx_vfmadd_s(b.col1, (__m128)__lsx_vshuf4i_w((__m128i)a.col3, 0b01010101), r_col); + r_col = (__m128)__lsx_vfmadd_s(b.col2, (__m128)__lsx_vshuf4i_w((__m128i)a.col3, 0b10101010), r_col); + result.col3 = (__m128)__lsx_vfmadd_s(b.col3, (__m128)__lsx_vshuf4i_w((__m128i)a.col3, 0b11111111), r_col); + + return result; +} + +inline Mat4F32 Mul4x3By4x4(Mat4x3F32 a, Mat4F32 b) { + Mat4F32 result; + + __m128 r_col = (__m128)__lsx_vfmul_s(b.col0, (__m128)__lsx_vshuf4i_w((__m128i)a.data0, 0b00000000)); + r_col = (__m128)__lsx_vfmadd_s(b.col1, (__m128)__lsx_vshuf4i_w((__m128i)a.data0, 0b01010101), r_col); + result.col0 = (__m128)__lsx_vfmadd_s(b.col2, (__m128)__lsx_vshuf4i_w((__m128i)a.data0, 0b10101010), r_col); + + r_col = (__m128)__lsx_vfmul_s(b.col0, (__m128)__lsx_vshuf4i_w((__m128i)a.data0, 0b11111111)); + r_col = (__m128)__lsx_vfmadd_s(b.col1, (__m128)__lsx_vshuf4i_w((__m128i)a.data1, 0b00000000), r_col); + result.col1 = (__m128)__lsx_vfmadd_s(b.col2, (__m128)__lsx_vshuf4i_w((__m128i)a.data1, 0b01010101), r_col); + + r_col = (__m128)__lsx_vfmul_s(b.col0, (__m128)__lsx_vshuf4i_w((__m128i)a.data1, 0b10101010)); + r_col = (__m128)__lsx_vfmadd_s(b.col1, (__m128)__lsx_vshuf4i_w((__m128i)a.data1, 0b11111111), r_col); + result.col2 = (__m128)__lsx_vfmadd_s(b.col2, (__m128)__lsx_vshuf4i_w((__m128i)a.data2, 0b00000000), r_col); + + r_col = (__m128)__lsx_vfmul_s(b.col0, (__m128)__lsx_vshuf4i_w((__m128i)a.data2, 0b01010101)); + r_col = (__m128)__lsx_vfmadd_s(b.col1, (__m128)__lsx_vshuf4i_w((__m128i)a.data2, 0b10101010), r_col); + r_col = (__m128)__lsx_vfmadd_s(b.col2, (__m128)__lsx_vshuf4i_w((__m128i)a.data2, 0b11111111), r_col); + + // The last entry has an implied 1.0f. + result.col3 = (__m128)__lsx_vfadd_s(r_col, b.col3); + return result; +} + +struct Vec4S32 { + __m128i v; + + static Vec4S32 Zero() { return Vec4S32{ __lsx_vldi(0) }; } + static Vec4S32 Splat(int lane) { return Vec4S32{ __lsx_vreplgr2vr_w(lane) }; } + + static Vec4S32 Load(const int *src) { return Vec4S32{ __lsx_vld(src, 0) }; } + static Vec4S32 LoadAligned(const int *src) { return Vec4S32{ __lsx_vld(src, 0) }; } + void Store(int *dst) { __lsx_vst(v, dst, 0); } + void Store2(int *dst) { __lsx_vstelm_d(v, dst, 0, 0); } + void StoreAligned(int *dst) { __lsx_vst(v, dst, 0); } + + // Warning: Unlike on x86, this is a full 32-bit multiplication. + Vec4S32 Mul16(Vec4S32 other) const { return Vec4S32{ __lsx_vmul_w(v, other.v) }; } + + Vec4S32 SignExtend16() const { return Vec4S32{ __lsx_vsrai_w(__lsx_vslli_w(v, 16), 16) }; } + // NOTE: These can be done in sequence, but when done, you must FixupAfterMinMax to get valid output (on SSE2 at least). + Vec4S32 Min16(Vec4S32 other) const { return Vec4S32{ __lsx_vmin_w(v, other.v) }; } + Vec4S32 Max16(Vec4S32 other) const { return Vec4S32{ __lsx_vmax_w(v, other.v) }; } + Vec4S32 FixupAfterMinMax() const { return Vec4S32{ v }; } + + // NOTE: May be slow. + int operator[](size_t index) const { return ((int *)&v)[index]; } + + Vec4S32 operator +(Vec4S32 other) const { return Vec4S32{ __lsx_vadd_w(v, other.v) }; } + Vec4S32 operator -(Vec4S32 other) const { return Vec4S32{ __lsx_vsub_w(v, other.v) }; } + Vec4S32 operator *(Vec4S32 other) const { return Vec4S32{ __lsx_vmul_w(v, other.v) }; } + Vec4S32 operator |(Vec4S32 other) const { return Vec4S32{ __lsx_vor_v(v, other.v) }; } + Vec4S32 operator &(Vec4S32 other) const { return Vec4S32{ __lsx_vand_v(v, other.v) }; } + Vec4S32 operator ^(Vec4S32 other) const { return Vec4S32{ __lsx_vxor_v(v, other.v) }; } + Vec4S32 AndNot(Vec4S32 inverted) const { return Vec4S32{ __lsx_vand_v(v, __lsx_vnor_v(inverted.v, inverted.v)) }; } + Vec4S32 Mul(Vec4S32 other) const { return Vec4S32{ __lsx_vmul_w(v, other.v) }; } + void operator &=(Vec4S32 other) { v = __lsx_vand_v(v, other.v); } + + template + Vec4S32 Shl() const { return Vec4S32{ __lsx_vslli_w(v, imm) }; } + + void operator +=(Vec4S32 other) { v = __lsx_vadd_w(v, other.v); } + void operator -=(Vec4S32 other) { v = __lsx_vsub_w(v, other.v); } + + Vec4S32 CompareEq(Vec4S32 other) const { return Vec4S32{ __lsx_vseq_w(v, other.v) }; } + Vec4S32 CompareLt(Vec4S32 other) const { return Vec4S32{ __lsx_vslt_w(v, other.v) }; } + Vec4S32 CompareGt(Vec4S32 other) const { return Vec4S32{ __lsx_vslt_w(other.v, v) }; } + Vec4S32 CompareGtZero() const { return Vec4S32{ __lsx_vslt_w(__lsx_vldi(0), v) }; } +}; + +struct Vec4F32 { + __m128 v; + + static Vec4F32 Zero() { return Vec4F32{ (__m128)__lsx_vldi(0) }; } + static Vec4F32 Splat(float lane) { return Vec4F32{ __lsx_vreplfr2vr_s(lane) }; } + + static Vec4F32 Load(const float *src) { return Vec4F32{ (__m128)__lsx_vld(src, 0) }; } + static Vec4F32 LoadS8Norm(const int8_t *src) { + int32_t temp; + memcpy(&temp, src, sizeof(temp)); + __m128i value = __lsx_vinsgr2vr_w(__lsx_vldi(0), temp, 0); + // Sign extend 8->16->32 + value = __lsx_vilvl_b(value, value); + value = __lsx_vsrai_h(value, 8); + value = __lsx_vilvl_h(value, value); + value = __lsx_vsrai_w(value, 16); + return Vec4F32 { (__m128)__lsx_vffint_s_w(__lsx_vsrai_w(value, 7)) }; + } + static Vec4F32 LoadS16Norm(const int16_t *src) { // Divides by 32768.0f + __m128i value = __lsx_vldrepl_d(src, 0); + // Sign extend 16-bit to 32-bit + value = __lsx_vilvl_h(value, value); + value = __lsx_vsrai_w(value, 16); + return Vec4F32 { (__m128)__lsx_vffint_s_w(__lsx_vsrai_w(value, 15)) }; + } + static Vec4F32 LoadAligned(const float *src) { return Vec4F32{ (__m128)__lsx_vld(src, 0) }; } + + static Vec4F32 LoadConvertS16(const int16_t *src) { + __m128i value = __lsx_vldrepl_d(src, 0); + // Sign extend 16-bit to 32-bit + value = __lsx_vilvl_h(value, value); + value = __lsx_vsrai_w(value, 16); + return Vec4F32{ (__m128)__lsx_vffint_s_w(value) }; + } + + static Vec4F32 LoadConvertS8(const int8_t *src) { // Note: will load 8 bytes, not 4. Only the first 4 bytes will be used. + __m128i value = __lsx_vldrepl_d(src, 0); + // Sign extend 8->16->32 + value = __lsx_vilvl_b(value, value); + value = __lsx_vsrai_h(value, 8); + value = __lsx_vilvl_h(value, value); + value = __lsx_vsrai_w(value, 16); + return Vec4F32{ (__m128)__lsx_vffint_s_w(value) }; + } + + static Vec4F32 LoadConvertU8(const uint8_t *src) { // Note: will load 8 bytes, not 4. Only the first 4 bytes will be used. + __m128i value = __lsx_vldrepl_d(src, 0); + __m128i zero = __lsx_vldi(0); + // Zero extend 8->16->32 + value = __lsx_vilvl_b(zero, value); + value = __lsx_vilvl_h(zero, value); + return Vec4F32{ (__m128)__lsx_vffint_s_w(value) }; + } + + static Vec4F32 LoadF24x3_One(const uint32_t *src) { + __m128i value = __lsx_vslli_w(__lsx_vld(src, 0), 8); + // Set lane 3 to 1.0f + value = __lsx_vinsgr2vr_w(value, *(int *)&(float){1.0f}, 3); + return Vec4F32{ (__m128)value }; + } + + static Vec4F32 FromVec4S32(Vec4S32 other) { + return Vec4F32{ (__m128)__lsx_vffint_s_w(other.v) }; + } + + void Store(float *dst) { __lsx_vst(v, dst, 0); } + void Store2(float *dst) { __lsx_vstelm_d((__m128i)v, dst, 0, 0); } + void StoreAligned(float *dst) { __lsx_vst(v, dst, 0); } + void Store3(float *dst) { + __lsx_vstelm_d((__m128i)v, dst, 0, 0); + __lsx_vstelm_w((__m128i)v, dst, 8, 2); + } + void StoreConvertToU8(uint8_t *dest) { + __m128i ivalue32 = __lsx_vftint_w_s(v); + __m128i ivalue16 = __lsx_vssrlrni_hu_w(ivalue32, ivalue32, 0); + __m128i ivalue8 = __lsx_vssrlrni_bu_h(ivalue16, ivalue16, 0); + uint32_t value = __lsx_vpickve2gr_wu(ivalue8, 0); + memcpy(dest, &value, sizeof(uint32_t)); + } + + // NOTE: May be slow. + float operator[](size_t index) const { return ((float *)&v)[index]; } + + Vec4F32 operator +(Vec4F32 other) const { return Vec4F32{ (__m128)__lsx_vfadd_s(v, other.v) }; } + Vec4F32 operator -(Vec4F32 other) const { return Vec4F32{ (__m128)__lsx_vfsub_s(v, other.v) }; } + Vec4F32 operator *(Vec4F32 other) const { return Vec4F32{ (__m128)__lsx_vfmul_s(v, other.v) }; } + Vec4F32 Min(Vec4F32 other) const { return Vec4F32{ (__m128)__lsx_vfmin_s(v, other.v) }; } + Vec4F32 Max(Vec4F32 other) const { return Vec4F32{ (__m128)__lsx_vfmax_s(v, other.v) }; } + void operator +=(Vec4F32 other) { v = (__m128)__lsx_vfadd_s(v, other.v); } + void operator -=(Vec4F32 other) { v = (__m128)__lsx_vfsub_s(v, other.v); } + void operator *=(Vec4F32 other) { v = (__m128)__lsx_vfmul_s(v, other.v); } + void operator /=(Vec4F32 other) { v = (__m128)__lsx_vfdiv_s(v, other.v); } + void operator &=(Vec4S32 other) { v = (__m128)__lsx_vand_v((__m128i)v, other.v); } + Vec4F32 operator *(float f) const { return Vec4F32{ (__m128)__lsx_vfmul_s(v, __lsx_vreplfr2vr_s(f)) }; } + void operator *=(float f) { v = (__m128)__lsx_vfmul_s(v, __lsx_vreplfr2vr_s(f)); } + + Vec4F32 Mul(float f) const { return Vec4F32{ (__m128)__lsx_vfmul_s(v, __lsx_vreplfr2vr_s(f)) }; } + + Vec4F32 Recip() const { + __m128 recip = (__m128)__lsx_vfrecip_s(v); + // LSX vfrecip is fairly accurate, but we can refine if needed + // recip = recip * (2.0 - v * recip) + __m128 two = __lsx_vreplfr2vr_s(2.0f); + recip = (__m128)__lsx_vfmul_s(recip, (__m128)__lsx_vfsub_s(two, (__m128)__lsx_vfmul_s(v, recip))); + return Vec4F32{ recip }; + } + + Vec4F32 RecipApprox() const { + return Vec4F32{ (__m128)__lsx_vfrecip_s(v) }; + } + + Vec4F32 Clamp(float lower, float higher) const { + return Vec4F32{ + (__m128)__lsx_vfmin_s((__m128)__lsx_vfmax_s(v, __lsx_vreplfr2vr_s(lower)), __lsx_vreplfr2vr_s(higher)) + }; + } + + Vec4F32 WithLane3Zero() const { + return Vec4F32{ (__m128)__lsx_vinsgr2vr_w((__m128i)v, 0, 3) }; + } + + Vec4F32 WithLane3One() const { + return Vec4F32{ (__m128)__lsx_vinsgr2vr_w((__m128i)v, *(int *)&(float){1.0f}, 3) }; + } + + Vec4S32 CompareEq(Vec4F32 other) const { return Vec4S32{ (__m128i)__lsx_vfcmp_ceq_s(v, other.v) }; } + Vec4S32 CompareLt(Vec4F32 other) const { return Vec4S32{ (__m128i)__lsx_vfcmp_clt_s(v, other.v) }; } + Vec4S32 CompareGt(Vec4F32 other) const { return Vec4S32{ (__m128i)__lsx_vfcmp_clt_s(other.v, v) }; } + Vec4S32 CompareLe(Vec4F32 other) const { return Vec4S32{ (__m128i)__lsx_vfcmp_cle_s(v, other.v) }; } + Vec4S32 CompareGe(Vec4F32 other) const { return Vec4S32{ (__m128i)__lsx_vfcmp_cle_s(other.v, v) }; } + + static void Transpose(Vec4F32 &col0, Vec4F32 &col1, Vec4F32 &col2, Vec4F32 &col3) { + // Transpose 4x4 matrix using interleave operations + __m128 tmp0 = (__m128)__lsx_vilvl_w((__m128i)col1.v, (__m128i)col0.v); // [a0, b0, a1, b1] + __m128 tmp1 = (__m128)__lsx_vilvh_w((__m128i)col1.v, (__m128i)col0.v); // [a2, b2, a3, b3] + __m128 tmp2 = (__m128)__lsx_vilvl_w((__m128i)col3.v, (__m128i)col2.v); // [c0, d0, c1, d1] + __m128 tmp3 = (__m128)__lsx_vilvh_w((__m128i)col3.v, (__m128i)col2.v); // [c2, d2, c3, d3] + col0.v = (__m128)__lsx_vilvl_d((__m128i)tmp2, (__m128i)tmp0); // [a0, b0, c0, d0] + col1.v = (__m128)__lsx_vilvh_d((__m128i)tmp2, (__m128i)tmp0); // [a1, b1, c1, d1] + col2.v = (__m128)__lsx_vilvl_d((__m128i)tmp3, (__m128i)tmp1); // [a2, b2, c2, d2] + col3.v = (__m128)__lsx_vilvh_d((__m128i)tmp3, (__m128i)tmp1); // [a3, b3, c3, d3] + } + + static void LoadTranspose(const float *src, Vec4F32 &col0, Vec4F32 &col1, Vec4F32 &col2, Vec4F32 &col3) { + col0.v = (__m128)__lsx_vld(src, 0); + col1.v = (__m128)__lsx_vld(src + 4, 0); + col2.v = (__m128)__lsx_vld(src + 8, 0); + col3.v = (__m128)__lsx_vld(src + 12, 0); + Transpose(col0, col1, col2, col3); + } + + inline Vec4F32 AsVec3ByMatrix44(const Mat4F32 &m) { + __m128 sum = (__m128)__lsx_vfadd_s( + (__m128)__lsx_vfadd_s( + (__m128)__lsx_vfmul_s(m.col0, (__m128)__lsx_vshuf4i_w((__m128i)v, 0b00000000)), + (__m128)__lsx_vfmul_s(m.col1, (__m128)__lsx_vshuf4i_w((__m128i)v, 0b01010101))), + (__m128)__lsx_vfadd_s( + (__m128)__lsx_vfmul_s(m.col2, (__m128)__lsx_vshuf4i_w((__m128i)v, 0b10101010)), + m.col3)); + return Vec4F32{ sum }; + } + + template float GetLane() const { + return __lsx_vpickve2gr_w((__m128i)v, i); + } +}; + +inline Vec4S32 Vec4S32FromF32(Vec4F32 f) { return Vec4S32{ __lsx_vftint_w_s(f.v) }; } +inline Vec4F32 Vec4F32FromS32(Vec4S32 s) { return Vec4F32{ (__m128)__lsx_vffint_s_w(s.v) }; } + +// Make sure the W component of scale is 1.0f. +inline void ScaleInplace(Mat4F32 &m, Vec4F32 scale) { + m.col0 = (__m128)__lsx_vfmul_s(m.col0, scale.v); + m.col1 = (__m128)__lsx_vfmul_s(m.col1, scale.v); + m.col2 = (__m128)__lsx_vfmul_s(m.col2, scale.v); + m.col3 = (__m128)__lsx_vfmul_s(m.col3, scale.v); +} + +// Make sure the W component of scale is 1.0f, and the W component of translate should be 0. +inline void TranslateAndScaleInplace(Mat4F32 &m, Vec4F32 scale, Vec4F32 translate) { + m.col0 = (__m128)__lsx_vfadd_s((__m128)__lsx_vfmul_s(m.col0, scale.v), (__m128)__lsx_vfmul_s(translate.v, (__m128)__lsx_vshuf4i_w((__m128i)m.col0, 0b11111111))); + m.col1 = (__m128)__lsx_vfadd_s((__m128)__lsx_vfmul_s(m.col1, scale.v), (__m128)__lsx_vfmul_s(translate.v, (__m128)__lsx_vshuf4i_w((__m128i)m.col1, 0b11111111))); + m.col2 = (__m128)__lsx_vfadd_s((__m128)__lsx_vfmul_s(m.col2, scale.v), (__m128)__lsx_vfmul_s(translate.v, (__m128)__lsx_vshuf4i_w((__m128i)m.col2, 0b11111111))); + m.col3 = (__m128)__lsx_vfadd_s((__m128)__lsx_vfmul_s(m.col3, scale.v), (__m128)__lsx_vfmul_s(translate.v, (__m128)__lsx_vshuf4i_w((__m128i)m.col3, 0b11111111))); +} + +inline bool AnyZeroSignBit(Vec4S32 value) { + // Check if any lane has sign bit not set (i.e., >= 0) + __m128i sign_mask = __lsx_vsrai_w(value.v, 31); + // All lanes should have sign bit set for false, otherwise true + int mask = __lsx_bz_v(sign_mask); + return mask == 0; // If all zeros, no sign bits set +} + +inline bool AnyZeroSignBit(Vec4F32 value) { + __m128i ival = (__m128i)value.v; + __m128i sign_mask = __lsx_vsrai_w(ival, 31); + int mask = __lsx_bz_v(sign_mask); + return mask == 0; +} + +struct Vec4U16 { + __m128i v; // we only use the lower 64 bits. + + static Vec4U16 Zero() { return Vec4U16{ __lsx_vldi(0) }; } + static Vec4U16 Splat(uint16_t value) { return Vec4U16{ __lsx_vreplgr2vr_h(value) }; } + + static Vec4U16 Load(const uint16_t *mem) { return Vec4U16{ __lsx_vldrepl_d(mem, 0) }; } + void Store(uint16_t *mem) { __lsx_vstelm_d(v, mem, 0, 0); } + + static Vec4U16 FromVec4S32(Vec4S32 v) { + // Saturate and pack 32-bit to 16-bit + __m128i packed = __lsx_vssrlrni_hu_w(v.v, v.v, 0); + return Vec4U16{ packed }; + } + static Vec4U16 FromVec4F32(Vec4F32 v) { + __m128i i32 = __lsx_vftint_w_s(v.v); + __m128i packed = __lsx_vssrlrni_hu_w(i32, i32, 0); + return Vec4U16{ packed }; + } + + Vec4U16 operator |(Vec4U16 other) const { return Vec4U16{ __lsx_vor_v(v, other.v) }; } + Vec4U16 operator &(Vec4U16 other) const { return Vec4U16{ __lsx_vand_v(v, other.v) }; } + Vec4U16 operator ^(Vec4U16 other) const { return Vec4U16{ __lsx_vxor_v(v, other.v) }; } + + Vec4U16 Max(Vec4U16 other) const { return Vec4U16{ __lsx_vmax_hu(v, other.v) }; } + Vec4U16 Min(Vec4U16 other) const { return Vec4U16{ __lsx_vmin_hu(v, other.v) }; } + Vec4U16 CompareLT(Vec4U16 other) { return Vec4U16{ __lsx_vslt_hu(v, other.v) }; } + + Vec4U16 AndNot(Vec4U16 inverted) { + return Vec4U16{ + __lsx_vand_v(v, __lsx_vnor_v(inverted.v, inverted.v)) + }; + } +}; + +inline Vec4U16 SignBits32ToMaskU16(Vec4S32 v) { + __m128i sign_mask = __lsx_vsrai_w(v.v, 31); + __m128i result = __lsx_vssrlrni_h_w(sign_mask, sign_mask, 0); + return Vec4U16{ result }; +} + +struct Vec8U16 { + __m128i v; + + static Vec8U16 Zero() { return Vec8U16{ __lsx_vldi(0) }; } + static Vec8U16 Splat(uint16_t value) { return Vec8U16{ __lsx_vreplgr2vr_h(value) }; } + + static Vec8U16 Load(const uint16_t *mem) { return Vec8U16{ __lsx_vld(mem, 0) }; } + void Store(uint16_t *mem) { __lsx_vst(v, mem, 0); } +}; + #else #define CROSSSIMD_SLOW 1 diff --git a/GPU/Common/DrawEngineCommon.cpp b/GPU/Common/DrawEngineCommon.cpp index 182e82d74a..3a49b4c704 100644 --- a/GPU/Common/DrawEngineCommon.cpp +++ b/GPU/Common/DrawEngineCommon.cpp @@ -499,7 +499,7 @@ static bool TestBoundingBoxFast(const float *worldViewProj, const void *vdata, i } uint64_t insideBits = vget_lane_u64(vreinterpret_u64_u16(vmovn_u32(inside)), 0); return ~insideBits == 0; -#elif 0 && PPSSPP_ARCH(LOONGARCH64_LSX) +#elif PPSSPP_ARCH(LOONGARCH64_LSX) // NOTE: Untested alignas(16) static const float planesXY[4] = { 1, -1, 1, -1 }; const __m128 worldX = (__m128)__lsx_vld(worldViewProj, 0); From c05e1cfd3c6c7513fe68ef14c4f5219a3204c010 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 24 May 2026 11:36:15 +0000 Subject: [PATCH 2/4] Address LSX CrossSIMD review feedback Agent-Logs-Url: https://github.com/hrydgard/ppsspp/sessions/6187ce4f-6c64-4b50-b82c-49799ea8cfd2 Co-authored-by: hrydgard <130929+hrydgard@users.noreply.github.com> --- Common/Math/CrossSIMD.h | 65 +++++++++++----------------------- Common/Math/SIMDHeaders.h | 6 ++++ Common/Math/fast/fast_matrix.h | 9 ----- 3 files changed, 26 insertions(+), 54 deletions(-) diff --git a/Common/Math/CrossSIMD.h b/Common/Math/CrossSIMD.h index 641ab53916..66783aec88 100644 --- a/Common/Math/CrossSIMD.h +++ b/Common/Math/CrossSIMD.h @@ -814,14 +814,13 @@ struct Mat4F32 { // The last two loads overlap. static Mat4F32 Load4x3(const float *m) { Mat4F32 result; - __m128 mask1110 = (__m128)__lsx_vreplgr2vr_w(0xFFFFFFFF); - mask1110 = (__m128)__lsx_vinsgr2vr_w((__m128i)mask1110, 0, 3); + __m128 mask1110 = (__m128)__lsx_vbsrl_v(__lsx_vldi(-1), 4); result.col0 = (__m128)__lsx_vand_v((__m128i)__lsx_vld(m, 0), (__m128i)mask1110); result.col1 = (__m128)__lsx_vand_v((__m128i)__lsx_vld(m + 3, 0), (__m128i)mask1110); result.col2 = (__m128)__lsx_vand_v((__m128i)__lsx_vld(m + 6, 0), (__m128i)mask1110); __m128 lastCol = (__m128)__lsx_vld(m + 8, 0); // Shuffle to get [m[9], m[10], m[11], x] then mask and add 1.0f to lane 3 - lastCol = (__m128)__lsx_vshuf4i_w((__m128i)lastCol, 0b11100100); // [1, 2, 3, 3] + lastCol = (__m128)__lsx_vshuf4i_w((__m128i)lastCol, 0b11111001); // [1, 2, 3, 3] result.col3 = (__m128)__lsx_vand_v((__m128i)lastCol, (__m128i)mask1110); result.col3 = (__m128)__lsx_vfadd_s(result.col3, (__m128)__lsx_vinsgr2vr_w(__lsx_vldi(0), *(int *)&(float){1.0f}, 3)); return result; @@ -926,7 +925,7 @@ struct Vec4S32 { Vec4S32 operator |(Vec4S32 other) const { return Vec4S32{ __lsx_vor_v(v, other.v) }; } Vec4S32 operator &(Vec4S32 other) const { return Vec4S32{ __lsx_vand_v(v, other.v) }; } Vec4S32 operator ^(Vec4S32 other) const { return Vec4S32{ __lsx_vxor_v(v, other.v) }; } - Vec4S32 AndNot(Vec4S32 inverted) const { return Vec4S32{ __lsx_vand_v(v, __lsx_vnor_v(inverted.v, inverted.v)) }; } + Vec4S32 AndNot(Vec4S32 inverted) const { return Vec4S32{ __lsx_vandn_v(inverted.v, v) }; } Vec4S32 Mul(Vec4S32 other) const { return Vec4S32{ __lsx_vmul_w(v, other.v) }; } void operator &=(Vec4S32 other) { v = __lsx_vand_v(v, other.v); } @@ -950,49 +949,30 @@ struct Vec4F32 { static Vec4F32 Load(const float *src) { return Vec4F32{ (__m128)__lsx_vld(src, 0) }; } static Vec4F32 LoadS8Norm(const int8_t *src) { - int32_t temp; - memcpy(&temp, src, sizeof(temp)); - __m128i value = __lsx_vinsgr2vr_w(__lsx_vldi(0), temp, 0); - // Sign extend 8->16->32 - value = __lsx_vilvl_b(value, value); - value = __lsx_vsrai_h(value, 8); - value = __lsx_vilvl_h(value, value); - value = __lsx_vsrai_w(value, 16); - return Vec4F32 { (__m128)__lsx_vffint_s_w(__lsx_vsrai_w(value, 7)) }; + return LoadConvertS8(src) * (1.0f / 128.0f); } static Vec4F32 LoadS16Norm(const int16_t *src) { // Divides by 32768.0f - __m128i value = __lsx_vldrepl_d(src, 0); - // Sign extend 16-bit to 32-bit - value = __lsx_vilvl_h(value, value); - value = __lsx_vsrai_w(value, 16); - return Vec4F32 { (__m128)__lsx_vffint_s_w(__lsx_vsrai_w(value, 15)) }; + return LoadConvertS16(src) * (1.0f / 32768.0f); } static Vec4F32 LoadAligned(const float *src) { return Vec4F32{ (__m128)__lsx_vld(src, 0) }; } static Vec4F32 LoadConvertS16(const int16_t *src) { __m128i value = __lsx_vldrepl_d(src, 0); - // Sign extend 16-bit to 32-bit - value = __lsx_vilvl_h(value, value); - value = __lsx_vsrai_w(value, 16); + value = __lsx_vsllwil_w_h(value, 0); return Vec4F32{ (__m128)__lsx_vffint_s_w(value) }; } static Vec4F32 LoadConvertS8(const int8_t *src) { // Note: will load 8 bytes, not 4. Only the first 4 bytes will be used. - __m128i value = __lsx_vldrepl_d(src, 0); - // Sign extend 8->16->32 - value = __lsx_vilvl_b(value, value); - value = __lsx_vsrai_h(value, 8); - value = __lsx_vilvl_h(value, value); - value = __lsx_vsrai_w(value, 16); + __m128i value = __lsx_vldrepl_w(src, 0); + value = __lsx_vsllwil_h_b(value, 0); + value = __lsx_vsllwil_w_h(value, 0); return Vec4F32{ (__m128)__lsx_vffint_s_w(value) }; } static Vec4F32 LoadConvertU8(const uint8_t *src) { // Note: will load 8 bytes, not 4. Only the first 4 bytes will be used. - __m128i value = __lsx_vldrepl_d(src, 0); - __m128i zero = __lsx_vldi(0); - // Zero extend 8->16->32 - value = __lsx_vilvl_b(zero, value); - value = __lsx_vilvl_h(zero, value); + __m128i value = __lsx_vldrepl_w(src, 0); + value = __lsx_vsllwil_hu_bu(value, 0); + value = __lsx_vsllwil_wu_hu(value, 0); return Vec4F32{ (__m128)__lsx_vffint_s_w(value) }; } @@ -1015,7 +995,7 @@ struct Vec4F32 { __lsx_vstelm_w((__m128i)v, dst, 8, 2); } void StoreConvertToU8(uint8_t *dest) { - __m128i ivalue32 = __lsx_vftint_w_s(v); + __m128i ivalue32 = __lsx_vftintrz_w_s(v); __m128i ivalue16 = __lsx_vssrlrni_hu_w(ivalue32, ivalue32, 0); __m128i ivalue8 = __lsx_vssrlrni_bu_h(ivalue16, ivalue16, 0); uint32_t value = __lsx_vpickve2gr_wu(ivalue8, 0); @@ -1109,7 +1089,7 @@ struct Vec4F32 { } }; -inline Vec4S32 Vec4S32FromF32(Vec4F32 f) { return Vec4S32{ __lsx_vftint_w_s(f.v) }; } +inline Vec4S32 Vec4S32FromF32(Vec4F32 f) { return Vec4S32{ __lsx_vftintrz_w_s(f.v) }; } inline Vec4F32 Vec4F32FromS32(Vec4S32 s) { return Vec4F32{ (__m128)__lsx_vffint_s_w(s.v) }; } // Make sure the W component of scale is 1.0f. @@ -1129,18 +1109,13 @@ inline void TranslateAndScaleInplace(Mat4F32 &m, Vec4F32 scale, Vec4F32 translat } inline bool AnyZeroSignBit(Vec4S32 value) { - // Check if any lane has sign bit not set (i.e., >= 0) - __m128i sign_mask = __lsx_vsrai_w(value.v, 31); - // All lanes should have sign bit set for false, otherwise true - int mask = __lsx_bz_v(sign_mask); - return mask == 0; // If all zeros, no sign bits set + int mask = __lsx_vmskltz_w(value.v); + return mask != 0xF; } inline bool AnyZeroSignBit(Vec4F32 value) { - __m128i ival = (__m128i)value.v; - __m128i sign_mask = __lsx_vsrai_w(ival, 31); - int mask = __lsx_bz_v(sign_mask); - return mask == 0; + int mask = __lsx_vmskltz_w((__m128i)value.v); + return mask != 0xF; } struct Vec4U16 { @@ -1173,14 +1148,14 @@ struct Vec4U16 { Vec4U16 AndNot(Vec4U16 inverted) { return Vec4U16{ - __lsx_vand_v(v, __lsx_vnor_v(inverted.v, inverted.v)) + __lsx_vandn_v(inverted.v, v) }; } }; inline Vec4U16 SignBits32ToMaskU16(Vec4S32 v) { __m128i sign_mask = __lsx_vsrai_w(v.v, 31); - __m128i result = __lsx_vssrlrni_h_w(sign_mask, sign_mask, 0); + __m128i result = __lsx_vpickev_h(sign_mask, sign_mask); return Vec4U16{ result }; } diff --git a/Common/Math/SIMDHeaders.h b/Common/Math/SIMDHeaders.h index e25623573e..651992e9e9 100644 --- a/Common/Math/SIMDHeaders.h +++ b/Common/Math/SIMDHeaders.h @@ -34,6 +34,12 @@ #if PPSSPP_ARCH(LOONGARCH64) #if PPSSPP_ARCH(LOONGARCH64_LSX) #include + +static inline __m128 __lsx_vreplfr2vr_s(float val) { + int32_t bits; + memcpy(&bits, &val, sizeof(bits)); + return (__m128)__lsx_vreplgr2vr_w(bits); +} #endif #endif diff --git a/Common/Math/fast/fast_matrix.h b/Common/Math/fast/fast_matrix.h index 051c78199a..7e642a2bf3 100644 --- a/Common/Math/fast/fast_matrix.h +++ b/Common/Math/fast/fast_matrix.h @@ -25,15 +25,6 @@ inline void fast_matrix_mul_4x4(float *dest, const float *a, const float *b) { #elif PPSSPP_ARCH(LOONGARCH64_LSX) -inline __m128 __lsx_vreplfr2vr_s(float val) { - typedef union { - int32_t i; - float f; - } FloatInt; - FloatInt tmpval = {.f = val}; - return (__m128)__lsx_vreplgr2vr_w(tmpval.i); -} - inline void fast_matrix_mul_4x4(float *dest, const float *a, const float *b) { __m128 a_col_1 = (__m128)__lsx_vld(a, 0); __m128 a_col_2 = (__m128)__lsx_vld(a + 4, 0); From 8b6951c803f50718428f20a9c3e41929696fc1e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 25 May 2026 13:48:37 +0200 Subject: [PATCH 3/4] Apply vldi fix --- Common/Math/CrossSIMD.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common/Math/CrossSIMD.h b/Common/Math/CrossSIMD.h index 66783aec88..c4402cca95 100644 --- a/Common/Math/CrossSIMD.h +++ b/Common/Math/CrossSIMD.h @@ -814,7 +814,7 @@ struct Mat4F32 { // The last two loads overlap. static Mat4F32 Load4x3(const float *m) { Mat4F32 result; - __m128 mask1110 = (__m128)__lsx_vbsrl_v(__lsx_vldi(-1), 4); + __m128 mask1110 = (__m128)__lsx_vbsrl_v(__lsx_vldi(0b11111111), 4); result.col0 = (__m128)__lsx_vand_v((__m128i)__lsx_vld(m, 0), (__m128i)mask1110); result.col1 = (__m128)__lsx_vand_v((__m128i)__lsx_vld(m + 3, 0), (__m128i)mask1110); result.col2 = (__m128)__lsx_vand_v((__m128i)__lsx_vld(m + 6, 0), (__m128i)mask1110); From 250abe0d56c8451ceba0be19050b9b848c990dfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 25 May 2026 15:39:30 +0200 Subject: [PATCH 4/4] Loongarch64 build fixes --- Common/Math/CrossSIMD.h | 13 ++++++++----- GPU/Common/DrawEngineCommon.cpp | 4 ++-- GPU/Software/Rasterizer.cpp | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Common/Math/CrossSIMD.h b/Common/Math/CrossSIMD.h index c4402cca95..c2f5a58371 100644 --- a/Common/Math/CrossSIMD.h +++ b/Common/Math/CrossSIMD.h @@ -814,6 +814,7 @@ struct Mat4F32 { // The last two loads overlap. static Mat4F32 Load4x3(const float *m) { Mat4F32 result; + constexpr int kOneF32Bits = 0x3F800000; __m128 mask1110 = (__m128)__lsx_vbsrl_v(__lsx_vldi(0b11111111), 4); result.col0 = (__m128)__lsx_vand_v((__m128i)__lsx_vld(m, 0), (__m128i)mask1110); result.col1 = (__m128)__lsx_vand_v((__m128i)__lsx_vld(m + 3, 0), (__m128i)mask1110); @@ -822,7 +823,7 @@ struct Mat4F32 { // Shuffle to get [m[9], m[10], m[11], x] then mask and add 1.0f to lane 3 lastCol = (__m128)__lsx_vshuf4i_w((__m128i)lastCol, 0b11111001); // [1, 2, 3, 3] result.col3 = (__m128)__lsx_vand_v((__m128i)lastCol, (__m128i)mask1110); - result.col3 = (__m128)__lsx_vfadd_s(result.col3, (__m128)__lsx_vinsgr2vr_w(__lsx_vldi(0), *(int *)&(float){1.0f}, 3)); + result.col3 = (__m128)__lsx_vfadd_s(result.col3, (__m128)__lsx_vinsgr2vr_w(__lsx_vldi(0), kOneF32Bits, 3)); return result; } @@ -977,9 +978,10 @@ struct Vec4F32 { } static Vec4F32 LoadF24x3_One(const uint32_t *src) { + constexpr int kOneF32Bits = 0x3F800000; __m128i value = __lsx_vslli_w(__lsx_vld(src, 0), 8); // Set lane 3 to 1.0f - value = __lsx_vinsgr2vr_w(value, *(int *)&(float){1.0f}, 3); + value = __lsx_vinsgr2vr_w(value, kOneF32Bits, 3); return Vec4F32{ (__m128)value }; } @@ -1044,7 +1046,8 @@ struct Vec4F32 { } Vec4F32 WithLane3One() const { - return Vec4F32{ (__m128)__lsx_vinsgr2vr_w((__m128i)v, *(int *)&(float){1.0f}, 3) }; + constexpr int kOneF32Bits = 0x3F800000; + return Vec4F32{ (__m128)__lsx_vinsgr2vr_w((__m128i)v, kOneF32Bits, 3) }; } Vec4S32 CompareEq(Vec4F32 other) const { return Vec4S32{ (__m128i)__lsx_vfcmp_ceq_s(v, other.v) }; } @@ -1109,12 +1112,12 @@ inline void TranslateAndScaleInplace(Mat4F32 &m, Vec4F32 scale, Vec4F32 translat } inline bool AnyZeroSignBit(Vec4S32 value) { - int mask = __lsx_vmskltz_w(value.v); + int mask = __lsx_vpickve2gr_w(__lsx_vmskltz_w(value.v), 0); return mask != 0xF; } inline bool AnyZeroSignBit(Vec4F32 value) { - int mask = __lsx_vmskltz_w((__m128i)value.v); + int mask = __lsx_vpickve2gr_w(__lsx_vmskltz_w((__m128i)value.v), 0); return mask != 0xF; } diff --git a/GPU/Common/DrawEngineCommon.cpp b/GPU/Common/DrawEngineCommon.cpp index 3a49b4c704..59f669d44b 100644 --- a/GPU/Common/DrawEngineCommon.cpp +++ b/GPU/Common/DrawEngineCommon.cpp @@ -527,7 +527,7 @@ static bool TestBoundingBoxFast(const float *worldViewProj, const void *vdata, i __m128 posXY = (__m128)__lsx_vshuf4i_w(clippos, 0b01010000); // [x, x, y, y] __m128 posW = (__m128)__lsx_vshuf4i_w(clippos, 0b11111111); // [w, w, w, w] __m128 planeDist = (__m128)__lsx_vfmadd_s(planesMul, posXY, posW); - inside = (__m128)__lsx_vor_v(inside, (__m128)__lsx_vfcmp_cle_s((__m128)__lsx_vreplfr2vr_s(0.0f), planeDist)); + inside = (__m128)__lsx_vor_v((__m128i)inside, (__m128i)__lsx_vfcmp_cle_s((__m128)__lsx_vreplfr2vr_s(0.0f), planeDist)); } // Check if all 4 lanes are set __m128i mask = (__m128i)__lsx_vseqi_w((__m128i)inside, 0); @@ -578,7 +578,7 @@ bool DrawEngineCommon::TestBoundingBoxFast(const float *worldViewProj, const voi // Check for weird scaling that can make graphics extend beyond the viewport. // NOTE: These checks are not bullet proof. float mtx[16]; - if (vpXCenter != 2048.0f || vpYCenter != 2048.0f || vpXScale < ((scissorX2 + 1) >> 1) && vpYScale < ((scissorY2 + 1) >> 1)) { + if (vpXCenter != 2048.0f || vpYCenter != 2048.0f || vpXScale < ((scissorX2 + 1) >> 1) || vpYScale < ((scissorY2 + 1) >> 1)) { // Note that the PSP does not clip against the viewport. const Vec2f baseOffset = Vec2f(gstate.getOffsetX(), gstate.getOffsetY()); // Region1 (rate) is used as an X1/Y1 here, matching PSP behavior. diff --git a/GPU/Software/Rasterizer.cpp b/GPU/Software/Rasterizer.cpp index b1a422a083..8b14f40249 100644 --- a/GPU/Software/Rasterizer.cpp +++ b/GPU/Software/Rasterizer.cpp @@ -1152,7 +1152,7 @@ void DrawTriangleSlice( int32x4_t sec = vsetq_lane_s32(0, sec_color[i].ivec, 3); prim_color[i].ivec = vaddq_s32(prim_color[i].ivec, sec); #else - prim_color[i] += Vec4(sec_color[i], 0); + prim_color[i] = Vec4(sec_color[i], 0); #endif } }