mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-14 15:00:30 +02:00
CrossSIMD: Add MulAdd and Vec4S32FromF32Round
MulAdd is fused on ARM64 and LoongArch, which always have it. Rounding to nearest is a single instruction on every ISA except ARM32. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
0b0a30217b
commit
84b674f4d8
2 files changed
+52
-1
No files matched your search
@@ -406,6 +406,10 @@ inline Vec4F32 Select(Vec4S32 mask, Vec4F32 ifTrue, Vec4F32 ifFalse) {
|
||||
const __m128 m = _mm_castsi128_ps(mask.v);
|
||||
return Vec4F32{ _mm_or_ps(_mm_and_ps(m, ifTrue.v), _mm_andnot_ps(m, ifFalse.v)) };
|
||||
}
|
||||
// a * b + c. Fused where the ISA always has it (ARM64, LoongArch), so the rounding differs between platforms.
|
||||
inline Vec4F32 MulAdd(Vec4F32 a, Vec4F32 b, Vec4F32 c) { return Vec4F32{ _mm_add_ps(_mm_mul_ps(a.v, b.v), c.v) }; }
|
||||
// Rounds to nearest, with ties to even except on ARM32 (away from zero).
|
||||
inline Vec4S32 Vec4S32FromF32Round(Vec4F32 f) { return Vec4S32{ _mm_cvtps_epi32(f.v) }; }
|
||||
|
||||
inline bool AnyZeroSignBit(Vec4F32 value) {
|
||||
return _mm_movemask_ps(value.v) != 0xF;
|
||||
@@ -887,6 +891,25 @@ inline Vec4F32 Vec4F32FromBits(Vec4S32 bits) { return Vec4F32{ vreinterpretq_f32
|
||||
inline Vec4F32 Select(Vec4S32 mask, Vec4F32 ifTrue, Vec4F32 ifFalse) {
|
||||
return Vec4F32{ vbslq_f32(vreinterpretq_u32_s32(mask.v), ifTrue.v, ifFalse.v) };
|
||||
}
|
||||
// a * b + c. Fused where the ISA always has it (ARM64, LoongArch), so the rounding differs between platforms.
|
||||
inline Vec4F32 MulAdd(Vec4F32 a, Vec4F32 b, Vec4F32 c) {
|
||||
#if PPSSPP_ARCH(ARM64_NEON)
|
||||
return Vec4F32{ vfmaq_f32(c.v, a.v, b.v) };
|
||||
#else
|
||||
return Vec4F32{ vmlaq_f32(c.v, a.v, b.v) };
|
||||
#endif
|
||||
}
|
||||
// Rounds to nearest, with ties to even except on ARM32 (away from zero).
|
||||
inline Vec4S32 Vec4S32FromF32Round(Vec4F32 f) {
|
||||
#if PPSSPP_ARCH(ARM64_NEON)
|
||||
return Vec4S32{ vcvtnq_s32_f32(f.v) };
|
||||
#else
|
||||
// Add 0.5 with the sign of the value, then truncate.
|
||||
const uint32x4_t sign = vandq_u32(vreinterpretq_u32_f32(f.v), vdupq_n_u32(0x80000000));
|
||||
const float32x4_t half = vreinterpretq_f32_u32(vorrq_u32(sign, vreinterpretq_u32_f32(vdupq_n_f32(0.5f))));
|
||||
return Vec4S32{ vcvtq_s32_f32(vaddq_f32(f.v, half)) };
|
||||
#endif
|
||||
}
|
||||
|
||||
// Make sure the W component of scale is 1.0f.
|
||||
inline void ScaleInplace(Mat4F32 &m, Vec4F32 scale) {
|
||||
@@ -1413,6 +1436,10 @@ inline Vec4F32 Vec4F32FromBits(Vec4S32 bits) { return Vec4F32{ (__m128)bits.v };
|
||||
inline Vec4F32 Select(Vec4S32 mask, Vec4F32 ifTrue, Vec4F32 ifFalse) {
|
||||
return Vec4F32{ (__m128)__lsx_vbitsel_v((__m128i)ifFalse.v, (__m128i)ifTrue.v, mask.v) };
|
||||
}
|
||||
// a * b + c. Fused where the ISA always has it (ARM64, LoongArch), so the rounding differs between platforms.
|
||||
inline Vec4F32 MulAdd(Vec4F32 a, Vec4F32 b, Vec4F32 c) { return Vec4F32{ (__m128)__lsx_vfmadd_s(a.v, b.v, c.v) }; }
|
||||
// Rounds to nearest, with ties to even except on ARM32 (away from zero).
|
||||
inline Vec4S32 Vec4S32FromF32Round(Vec4F32 f) { return Vec4S32{ __lsx_vftint_w_s(f.v) }; }
|
||||
|
||||
// Make sure the W component of scale is 1.0f.
|
||||
inline void ScaleInplace(Mat4F32 &m, Vec4F32 scale) {
|
||||
@@ -2088,6 +2115,16 @@ inline Vec4F32 Select(Vec4S32 mask, Vec4F32 ifTrue, Vec4F32 ifFalse) {
|
||||
return temp;
|
||||
}
|
||||
|
||||
// a * b + c. Fused where the ISA always has it (ARM64, LoongArch), so the rounding differs between platforms.
|
||||
inline Vec4F32 MulAdd(Vec4F32 a, Vec4F32 b, Vec4F32 c) {
|
||||
return Vec4F32{ { a.v[0] * b.v[0] + c.v[0], a.v[1] * b.v[1] + c.v[1], a.v[2] * b.v[2] + c.v[2], a.v[3] * b.v[3] + c.v[3] } };
|
||||
}
|
||||
|
||||
// Rounds to nearest, with ties to even except on ARM32 (away from zero).
|
||||
inline Vec4S32 Vec4S32FromF32Round(Vec4F32 f) {
|
||||
return Vec4S32{ { (int32_t)lrintf(f.v[0]), (int32_t)lrintf(f.v[1]), (int32_t)lrintf(f.v[2]), (int32_t)lrintf(f.v[3]) } };
|
||||
}
|
||||
|
||||
// Make sure the W component of scale is 1.0f.
|
||||
inline void ScaleInplace(Mat4F32 &m, Vec4F32 scale) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
|
||||
@@ -236,7 +236,21 @@ static bool TestVec4F32Arith() {
|
||||
for (int i = 0; i < 4; i++) expected[i] = mask_values[i] ? a_values[i] : b_values[i];
|
||||
if (!CompareFloats(result, expected, 4, __LINE__)) return false;
|
||||
|
||||
const int bits_values[4] = { 0x3F800000, (int)0xC0000000, 0, 0x40490FDB };
|
||||
// Exact in float, so fused and unfused agree.
|
||||
MulAdd(a, b, Vec4F32::Splat(0.5f)).Store(result);
|
||||
for (int i = 0; i < 4; i++) expected[i] = a_values[i] * b_values[i] + 0.5f;
|
||||
if (!CompareFloats(result, expected, 4, __LINE__)) return false;
|
||||
|
||||
// No ties, which round differently on ARM32.
|
||||
const float round_values[4] = { 1.4f, -1.6f, 2.6f, -0.4f };
|
||||
int rounded[4];
|
||||
Vec4S32FromF32Round(Vec4F32::Load(round_values)).Store(rounded);
|
||||
EXPECT_EQ_INT(rounded[0], 1);
|
||||
EXPECT_EQ_INT(rounded[1], -2);
|
||||
EXPECT_EQ_INT(rounded[2], 3);
|
||||
EXPECT_EQ_INT(rounded[3], 0);
|
||||
|
||||
const int bits_values[4] ={ 0x3F800000, (int)0xC0000000, 0, 0x40490FDB };
|
||||
Vec4F32FromBits(Vec4S32::Load(bits_values)).Store(result);
|
||||
static const float known_bits[4] = { 1.0f, -2.0f, 0.0f, 3.14159274f };
|
||||
if (!CompareFloats(result, known_bits, 4, __LINE__)) return false;
|
||||
|
||||
Reference in new issue
Block a user