mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-14 06:50:29 +02:00
SSE2 has no 32-bit lane multiply, so Vec4S32::Mul is emulated with two pmuludq and shuffles, and the bilinear sampler spent a tenth of its time there. The texel row offsets, the texture functions, fog and blending only multiply values that fit 16 bits, which Mul16 does in one pmaddwd. The bilinear lerp of two channels packed as 16-bit halves uses the new MulHalves (pmullw), which multiplies both at once. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
622 lines
21 KiB
C++
622 lines
21 KiB
C++
// Copyright (c) 2012- PPSSPP Project.
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
// Tests for Common/Math/CrossSIMD.h.
|
|
//
|
|
// CrossSIMD has four independent implementations - SSE2, NEON, LoongArch LSX and a plain scalar
|
|
// fallback - and only one of them is compiled on any given machine. So the point of these tests is
|
|
// to check whatever got compiled against results worked out by hand, rather than against each other;
|
|
// that way every architecture we build for verifies its own copy. Running the unit tests under
|
|
// qemu covers the ones we have no hardware for.
|
|
//
|
|
// Only functions that all four implementations provide are tested here, otherwise this wouldn't
|
|
// build everywhere.
|
|
|
|
#include "ppsspp_config.h"
|
|
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
|
|
#include "Common/Common.h"
|
|
#include "Common/CommonTypes.h"
|
|
#include "Common/Math/CrossSIMD.h"
|
|
#include "unittest/UnitTest.h"
|
|
|
|
static bool CompareFloats(const float *values, const float *known_good, int count, int line) {
|
|
int wrongCount = 0;
|
|
for (int i = 0; i < count; i++) {
|
|
if (values[i] != known_good[i]) {
|
|
wrongCount++;
|
|
}
|
|
}
|
|
if (wrongCount > 0) {
|
|
for (int i = 0; i < count; i++) {
|
|
bool wrong = values[i] != known_good[i];
|
|
printf("%d: %0.3f vs %0.3f %s\n", i + 1, values[i], known_good[i], wrong ? "!! MISMATCH" : "");
|
|
}
|
|
printf("At TestCrossSIMD.cpp:%d: %d / %d were wrong\n", line, wrongCount, count);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// For the approximate operations (reciprocals), which are allowed to differ between architectures.
|
|
static bool CompareFloatsApprox(const float *values, const float *known_good, int count, float tolerance, int line) {
|
|
for (int i = 0; i < count; i++) {
|
|
const float diff = fabsf(values[i] - known_good[i]);
|
|
const float scale = fabsf(known_good[i]) > 1.0f ? fabsf(known_good[i]) : 1.0f;
|
|
if (diff / scale > tolerance) {
|
|
printf("At TestCrossSIMD.cpp:%d: lane %d: %0.6f vs %0.6f\n", line, i, values[i], known_good[i]);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool TestVec4S32() {
|
|
const int a_values[4] = { 3, -7, 0x4000, -1 };
|
|
const int b_values[4] = { 5, 11, -2, 0x7FFF };
|
|
|
|
Vec4S32 a = Vec4S32::Load(a_values);
|
|
Vec4S32 b = Vec4S32::Load(b_values);
|
|
|
|
int result[4];
|
|
|
|
(a + b).Store(result);
|
|
for (int i = 0; i < 4; i++) {
|
|
EXPECT_EQ_INT(result[i], a_values[i] + b_values[i]);
|
|
}
|
|
|
|
(a - b).Store(result);
|
|
for (int i = 0; i < 4; i++) {
|
|
EXPECT_EQ_INT(result[i], a_values[i] - b_values[i]);
|
|
}
|
|
|
|
a.Mul(b).Store(result);
|
|
for (int i = 0; i < 4; i++) {
|
|
EXPECT_EQ_INT(result[i], a_values[i] * b_values[i]);
|
|
}
|
|
|
|
// Mul16 only promises correct results when both sides fit in 16 bits.
|
|
const int small_a[4] = { 3, -7, 300, -1 };
|
|
const int small_b[4] = { 5, 11, -2, 1000 };
|
|
Vec4S32 sa = Vec4S32::Load(small_a);
|
|
Vec4S32 sb = Vec4S32::Load(small_b);
|
|
sa.Mul16(sb).Store(result);
|
|
for (int i = 0; i < 4; i++) {
|
|
EXPECT_EQ_INT(result[i], small_a[i] * small_b[i]);
|
|
}
|
|
|
|
Vec4S32::Splat(-5).Store(result);
|
|
for (int i = 0; i < 4; i++) {
|
|
EXPECT_EQ_INT(result[i], -5);
|
|
}
|
|
|
|
Vec4S32::Zero().Store(result);
|
|
for (int i = 0; i < 4; i++) {
|
|
EXPECT_EQ_INT(result[i], 0);
|
|
}
|
|
|
|
a.Shl<2>().Store(result);
|
|
for (int i = 0; i < 4; i++) {
|
|
EXPECT_EQ_INT(result[i], a_values[i] << 2);
|
|
}
|
|
|
|
// operator[] should agree with Store.
|
|
a.Store(result);
|
|
for (int i = 0; i < 4; i++) {
|
|
EXPECT_EQ_INT(a[i], result[i]);
|
|
}
|
|
|
|
// Min16/Max16 likewise operate on 16-bit lanes.
|
|
sa.Min16(sb).Store(result);
|
|
for (int i = 0; i < 4; i++) {
|
|
EXPECT_EQ_INT(result[i], small_a[i] < small_b[i] ? small_a[i] : small_b[i]);
|
|
}
|
|
sa.Max16(sb).Store(result);
|
|
for (int i = 0; i < 4; i++) {
|
|
EXPECT_EQ_INT(result[i], small_a[i] > small_b[i] ? small_a[i] : small_b[i]);
|
|
}
|
|
|
|
const int sext_values[4] = { 0x0000FFFF, 0x00007FFF, 0x00008000, 0x00000001 };
|
|
Vec4S32::Load(sext_values).SignExtend16().Store(result);
|
|
EXPECT_EQ_INT(result[0], -1);
|
|
EXPECT_EQ_INT(result[1], 32767);
|
|
EXPECT_EQ_INT(result[2], -32768);
|
|
EXPECT_EQ_INT(result[3], 1);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool TestVec4S32Compares() {
|
|
const int a_values[4] = { 1, 2, 3, 4 };
|
|
const int b_values[4] = { 1, 5, 0, 4 };
|
|
Vec4S32 a = Vec4S32::Load(a_values);
|
|
Vec4S32 b = Vec4S32::Load(b_values);
|
|
|
|
int result[4];
|
|
|
|
// Compares produce all-ones or all-zeros per lane.
|
|
a.CompareEq(b).Store(result);
|
|
EXPECT_EQ_HEX((u32)result[0], 0xFFFFFFFFu);
|
|
EXPECT_EQ_HEX((u32)result[1], 0u);
|
|
EXPECT_EQ_HEX((u32)result[2], 0u);
|
|
EXPECT_EQ_HEX((u32)result[3], 0xFFFFFFFFu);
|
|
|
|
a.CompareLt(b).Store(result);
|
|
EXPECT_EQ_HEX((u32)result[0], 0u);
|
|
EXPECT_EQ_HEX((u32)result[1], 0xFFFFFFFFu);
|
|
EXPECT_EQ_HEX((u32)result[2], 0u);
|
|
EXPECT_EQ_HEX((u32)result[3], 0u);
|
|
|
|
a.CompareGt(b).Store(result);
|
|
EXPECT_EQ_HEX((u32)result[0], 0u);
|
|
EXPECT_EQ_HEX((u32)result[1], 0u);
|
|
EXPECT_EQ_HEX((u32)result[2], 0xFFFFFFFFu);
|
|
EXPECT_EQ_HEX((u32)result[3], 0u);
|
|
|
|
// AllCompareBitsSet / AnyCompareBitsSet over those masks.
|
|
EXPECT_FALSE(AllCompareBitsSet(a.CompareEq(b)));
|
|
EXPECT_TRUE(AnyCompareBitsSet(a.CompareEq(b)));
|
|
EXPECT_TRUE(AllCompareBitsSet(a.CompareEq(a)));
|
|
EXPECT_FALSE(AnyCompareBitsSet(a.CompareLt(a)));
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool TestVec4F32Arith() {
|
|
const float a_values[4] = { 1.0f, -2.0f, 3.5f, 0.25f };
|
|
const float b_values[4] = { 4.0f, 8.0f, -0.5f, 2.0f };
|
|
|
|
Vec4F32 a = Vec4F32::Load(a_values);
|
|
Vec4F32 b = Vec4F32::Load(b_values);
|
|
|
|
float result[4];
|
|
float expected[4];
|
|
|
|
(a + b).Store(result);
|
|
for (int i = 0; i < 4; i++) expected[i] = a_values[i] + b_values[i];
|
|
if (!CompareFloats(result, expected, 4, __LINE__)) return false;
|
|
|
|
(a - b).Store(result);
|
|
for (int i = 0; i < 4; i++) expected[i] = a_values[i] - b_values[i];
|
|
if (!CompareFloats(result, expected, 4, __LINE__)) return false;
|
|
|
|
(a * b).Store(result);
|
|
for (int i = 0; i < 4; i++) expected[i] = a_values[i] * b_values[i];
|
|
if (!CompareFloats(result, expected, 4, __LINE__)) return false;
|
|
|
|
a.Mul(2.0f).Store(result);
|
|
for (int i = 0; i < 4; i++) expected[i] = a_values[i] * 2.0f;
|
|
if (!CompareFloats(result, expected, 4, __LINE__)) return false;
|
|
|
|
a.Min(b).Store(result);
|
|
for (int i = 0; i < 4; i++) expected[i] = a_values[i] < b_values[i] ? a_values[i] : b_values[i];
|
|
if (!CompareFloats(result, expected, 4, __LINE__)) return false;
|
|
|
|
a.Max(b).Store(result);
|
|
for (int i = 0; i < 4; i++) expected[i] = a_values[i] > b_values[i] ? a_values[i] : b_values[i];
|
|
if (!CompareFloats(result, expected, 4, __LINE__)) return false;
|
|
|
|
a.Clamp(0.0f, 3.0f).Store(result);
|
|
static const float known_clamp[4] = { 1.0f, 0.0f, 3.0f, 0.25f };
|
|
if (!CompareFloats(result, known_clamp, 4, __LINE__)) return false;
|
|
|
|
// Recip is allowed to be approximate on some architectures, RecipApprox definitely is.
|
|
b.Recip().Store(result);
|
|
for (int i = 0; i < 4; i++) expected[i] = 1.0f / b_values[i];
|
|
if (!CompareFloatsApprox(result, expected, 4, 0.001f, __LINE__)) return false;
|
|
|
|
b.RecipApprox().Store(result);
|
|
if (!CompareFloatsApprox(result, expected, 4, 0.01f, __LINE__)) return false;
|
|
|
|
const float sq_values[4] = { 4.0f, 0.25f, 2.0f, 100.0f };
|
|
Vec4F32::Load(sq_values).RecipSqrt().Store(result);
|
|
for (int i = 0; i < 4; i++) expected[i] = 1.0f / sqrtf(sq_values[i]);
|
|
if (!CompareFloatsApprox(result, expected, 4, 1e-6f, __LINE__)) return false;
|
|
|
|
// Select picks per lane by the mask, and FromBits keeps the bits as they are.
|
|
const int mask_values[4] = { -1, 0, 0, -1 };
|
|
Select(Vec4S32::Load(mask_values), a, b).Store(result);
|
|
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;
|
|
|
|
// 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;
|
|
|
|
Vec4F32::Splat(1.5f).Store(result);
|
|
static const float known_splat[4] = { 1.5f, 1.5f, 1.5f, 1.5f };
|
|
if (!CompareFloats(result, known_splat, 4, __LINE__)) return false;
|
|
|
|
Vec4F32::Zero().Store(result);
|
|
static const float known_zero[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
|
|
if (!CompareFloats(result, known_zero, 4, __LINE__)) return false;
|
|
|
|
// Dot products. Dot3 ignores lane 3.
|
|
EXPECT_EQ_FLOAT(a.Dot3(b), a_values[0] * b_values[0] + a_values[1] * b_values[1] + a_values[2] * b_values[2]);
|
|
EXPECT_EQ_FLOAT(a.Dot4(b), a_values[0] * b_values[0] + a_values[1] * b_values[1] + a_values[2] * b_values[2] + a_values[3] * b_values[3]);
|
|
|
|
// operator[] and GetLane<> should agree with Store.
|
|
a.Store(result);
|
|
for (int i = 0; i < 4; i++) {
|
|
EXPECT_EQ_FLOAT(a[i], result[i]);
|
|
}
|
|
EXPECT_EQ_FLOAT(a.GetLane<0>(), a_values[0]);
|
|
EXPECT_EQ_FLOAT(a.GetLane<3>(), a_values[3]);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool TestVec4F32Compares() {
|
|
const float a_values[4] = { 1.0f, 2.0f, 3.0f, 4.0f };
|
|
const float b_values[4] = { 1.0f, 5.0f, 0.0f, 4.0f };
|
|
Vec4F32 a = Vec4F32::Load(a_values);
|
|
Vec4F32 b = Vec4F32::Load(b_values);
|
|
|
|
int result[4];
|
|
|
|
a.CompareEq(b).Store(result);
|
|
EXPECT_EQ_HEX((u32)result[0], 0xFFFFFFFFu);
|
|
EXPECT_EQ_HEX((u32)result[1], 0u);
|
|
|
|
a.CompareLt(b).Store(result);
|
|
EXPECT_EQ_HEX((u32)result[1], 0xFFFFFFFFu);
|
|
EXPECT_EQ_HEX((u32)result[2], 0u);
|
|
|
|
a.CompareGt(b).Store(result);
|
|
EXPECT_EQ_HEX((u32)result[2], 0xFFFFFFFFu);
|
|
EXPECT_EQ_HEX((u32)result[1], 0u);
|
|
|
|
a.CompareLe(b).Store(result);
|
|
EXPECT_EQ_HEX((u32)result[0], 0xFFFFFFFFu);
|
|
EXPECT_EQ_HEX((u32)result[2], 0u);
|
|
|
|
a.CompareGe(b).Store(result);
|
|
EXPECT_EQ_HEX((u32)result[0], 0xFFFFFFFFu);
|
|
EXPECT_EQ_HEX((u32)result[1], 0u);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool TestVec4F32Lanes() {
|
|
const float values[4] = { 1.0f, 2.0f, 3.0f, 4.0f };
|
|
const float other_values[4] = { 9.0f, 9.0f, 9.0f, 42.0f };
|
|
Vec4F32 v = Vec4F32::Load(values);
|
|
Vec4F32 other = Vec4F32::Load(other_values);
|
|
|
|
float result[4];
|
|
|
|
v.WithLane3Zero().Store(result);
|
|
static const float known_zero3[4] = { 1.0f, 2.0f, 3.0f, 0.0f };
|
|
if (!CompareFloats(result, known_zero3, 4, __LINE__)) return false;
|
|
|
|
v.WithLane3One().Store(result);
|
|
static const float known_one3[4] = { 1.0f, 2.0f, 3.0f, 1.0f };
|
|
if (!CompareFloats(result, known_one3, 4, __LINE__)) return false;
|
|
|
|
v.WithLane3From(other).Store(result);
|
|
static const float known_from3[4] = { 1.0f, 2.0f, 3.0f, 42.0f };
|
|
if (!CompareFloats(result, known_from3, 4, __LINE__)) return false;
|
|
|
|
v.ShuffleXXXX().Store(result);
|
|
static const float known_xxxx[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
|
|
if (!CompareFloats(result, known_xxxx, 4, __LINE__)) return false;
|
|
|
|
v.ShuffleYYYY().Store(result);
|
|
static const float known_yyyy[4] = { 2.0f, 2.0f, 2.0f, 2.0f };
|
|
if (!CompareFloats(result, known_yyyy, 4, __LINE__)) return false;
|
|
|
|
v.ShuffleZZZZ().Store(result);
|
|
static const float known_zzzz[4] = { 3.0f, 3.0f, 3.0f, 3.0f };
|
|
if (!CompareFloats(result, known_zzzz, 4, __LINE__)) return false;
|
|
|
|
v.ShuffleWWWW().Store(result);
|
|
static const float known_wwww[4] = { 4.0f, 4.0f, 4.0f, 4.0f };
|
|
if (!CompareFloats(result, known_wwww, 4, __LINE__)) return false;
|
|
|
|
v.ShuffleXXYY().Store(result);
|
|
static const float known_xxyy[4] = { 1.0f, 1.0f, 2.0f, 2.0f };
|
|
if (!CompareFloats(result, known_xxyy, 4, __LINE__)) return false;
|
|
|
|
v.ShuffleZZWW().Store(result);
|
|
static const float known_zzww[4] = { 3.0f, 3.0f, 4.0f, 4.0f };
|
|
if (!CompareFloats(result, known_zzww, 4, __LINE__)) return false;
|
|
|
|
// Store2/Store3 must leave the rest of the destination alone.
|
|
float partial[4] = { -1.0f, -1.0f, -1.0f, -1.0f };
|
|
v.Store2(partial);
|
|
static const float known_store2[4] = { 1.0f, 2.0f, -1.0f, -1.0f };
|
|
if (!CompareFloats(partial, known_store2, 4, __LINE__)) return false;
|
|
|
|
float partial3[4] = { -1.0f, -1.0f, -1.0f, -1.0f };
|
|
v.Store3(partial3);
|
|
static const float known_store3[4] = { 1.0f, 2.0f, 3.0f, -1.0f };
|
|
if (!CompareFloats(partial3, known_store3, 4, __LINE__)) return false;
|
|
|
|
// Transpose of four columns.
|
|
static const float col_values[4][4] = {
|
|
{ 0.0f, 1.0f, 2.0f, 3.0f },
|
|
{ 4.0f, 5.0f, 6.0f, 7.0f },
|
|
{ 8.0f, 9.0f, 10.0f, 11.0f },
|
|
{ 12.0f, 13.0f, 14.0f, 15.0f },
|
|
};
|
|
Vec4F32 c0 = Vec4F32::Load(col_values[0]);
|
|
Vec4F32 c1 = Vec4F32::Load(col_values[1]);
|
|
Vec4F32 c2 = Vec4F32::Load(col_values[2]);
|
|
Vec4F32 c3 = Vec4F32::Load(col_values[3]);
|
|
Vec4F32::Transpose(c0, c1, c2, c3);
|
|
float transposed[16];
|
|
c0.Store(transposed);
|
|
c1.Store(transposed + 4);
|
|
c2.Store(transposed + 8);
|
|
c3.Store(transposed + 12);
|
|
for (int row = 0; row < 4; row++) {
|
|
for (int col = 0; col < 4; col++) {
|
|
EXPECT_EQ_FLOAT(transposed[row * 4 + col], col_values[col][row]);
|
|
}
|
|
}
|
|
|
|
// LoadTranspose should match a plain Load of each row followed by Transpose.
|
|
static const float flat[16] = {
|
|
0.0f, 1.0f, 2.0f, 3.0f,
|
|
4.0f, 5.0f, 6.0f, 7.0f,
|
|
8.0f, 9.0f, 10.0f, 11.0f,
|
|
12.0f, 13.0f, 14.0f, 15.0f,
|
|
};
|
|
Vec4F32 t0, t1, t2, t3;
|
|
Vec4F32::LoadTranspose(flat, t0, t1, t2, t3);
|
|
float loadTransposed[16];
|
|
t0.Store(loadTransposed);
|
|
t1.Store(loadTransposed + 4);
|
|
t2.Store(loadTransposed + 8);
|
|
t3.Store(loadTransposed + 12);
|
|
if (!CompareFloats(loadTransposed, transposed, 16, __LINE__)) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool TestVec4F32NaNInf() {
|
|
const float inf = INFINITY;
|
|
const float nan = NAN;
|
|
const float values[4] = { 1.0f, nan, inf, -inf };
|
|
Vec4F32 v = Vec4F32::Load(values);
|
|
|
|
float result[4];
|
|
|
|
v.ZeroNaNs().Store(result);
|
|
EXPECT_EQ_FLOAT(result[0], 1.0f);
|
|
EXPECT_EQ_FLOAT(result[1], 0.0f);
|
|
// ZeroNaNs leaves infinities alone.
|
|
EXPECT_TRUE(std::isinf(result[2]));
|
|
EXPECT_TRUE(std::isinf(result[3]));
|
|
|
|
// The contract is just "a value that yields zero when multiplied by zero" - whatever is
|
|
// cheapest to get there. So the implementations legitimately differ on what a bad lane
|
|
// becomes: SSE2 clamps to +-FLT_MAX, NEON, LSX and the scalar fallback zero it. Check the
|
|
// property rather than the value, and that good lanes are left alone.
|
|
v.CleanNaNInfs().Store(result);
|
|
EXPECT_EQ_FLOAT(result[0], 1.0f);
|
|
for (int i = 0; i < 4; i++) {
|
|
EXPECT_EQ_FLOAT(result[i] * 0.0f, 0.0f);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool TestVec4F32Loads() {
|
|
float result[4];
|
|
|
|
// Load3One: three floats, lane 3 1.0f.
|
|
const float xyz[3] = { -1.5f, 2.0f, 1e20f };
|
|
Vec4F32::Load3One(xyz).Store(result);
|
|
EXPECT_EQ_FLOAT(result[0], -1.5f);
|
|
EXPECT_EQ_FLOAT(result[1], 2.0f);
|
|
EXPECT_EQ_FLOAT(result[2], 1e20f);
|
|
EXPECT_EQ_FLOAT(result[3], 1.0f);
|
|
|
|
// MulHalves: two 16-bit products per lane, each kept to 16 bits.
|
|
{
|
|
const int halvesA[4] = { 0x00FF00FF, 0x01230045, (int)0xFFFF0002, 0x7FFF8000 };
|
|
const int halvesB[4] = { 0x00100010, 0x00030007, 0x00020003, 0x00020002 };
|
|
int halvesOut[4];
|
|
Vec4S32::Load(halvesA).MulHalves(Vec4S32::Load(halvesB)).Store(halvesOut);
|
|
for (int i = 0; i < 4; i++) {
|
|
const uint32_t lo = ((uint32_t)halvesA[i] * (uint32_t)halvesB[i]) & 0xFFFF;
|
|
const uint32_t hi = (((uint32_t)halvesA[i] >> 16) * ((uint32_t)halvesB[i] >> 16)) & 0xFFFF;
|
|
EXPECT_EQ_HEX((uint32_t)halvesOut[i], (hi << 16) | lo);
|
|
}
|
|
}
|
|
|
|
// LoadS64Low: the low halves.
|
|
int iresult[4];
|
|
const int64_t wide[4] = { 0x123456789LL, -2, (int64_t)0x7FFFFFFF << 1, -0x100000000LL + 7 };
|
|
Vec4S32::LoadS64Low(wide).Store(iresult);
|
|
for (int i = 0; i < 4; i++)
|
|
EXPECT_EQ_INT(iresult[i], (int32_t)(uint32_t)(uint64_t)wide[i]);
|
|
|
|
// LoadPairs: firsts and seconds.
|
|
const int32_t pairs[8] = { 1, -1, 2, -2, 3, -3, 4, -4 };
|
|
Vec4S32 firsts, seconds;
|
|
Vec4S32::LoadPairs(pairs + 6, pairs, pairs + 4, pairs + 2, firsts, seconds);
|
|
const int32_t expectedFirsts[4] = { 4, 1, 3, 2 };
|
|
firsts.Store(iresult);
|
|
for (int i = 0; i < 4; i++)
|
|
EXPECT_EQ_INT(iresult[i], expectedFirsts[i]);
|
|
seconds.Store(iresult);
|
|
for (int i = 0; i < 4; i++)
|
|
EXPECT_EQ_INT(iresult[i], -expectedFirsts[i]);
|
|
|
|
// LoadF24x3_One: three 24-bit values shifted up into floats, lane 3 forced to 1.0f.
|
|
const uint32_t f24_values[4] = { 0x3F8000 >> 0, 0x400000, 0x3F0000, 0x123456 };
|
|
Vec4F32::LoadF24x3_One(f24_values).Store(result);
|
|
for (int i = 0; i < 3; i++) {
|
|
float expected;
|
|
uint32_t bits = f24_values[i] << 8;
|
|
memcpy(&expected, &bits, 4);
|
|
EXPECT_EQ_FLOAT(result[i], expected);
|
|
}
|
|
EXPECT_EQ_FLOAT(result[3], 1.0f);
|
|
|
|
// LoadF24x4: same, but all four lanes come from the source.
|
|
Vec4F32::LoadF24x4(f24_values).Store(result);
|
|
for (int i = 0; i < 4; i++) {
|
|
float expected;
|
|
uint32_t bits = f24_values[i] << 8;
|
|
memcpy(&expected, &bits, 4);
|
|
EXPECT_EQ_FLOAT(result[i], expected);
|
|
}
|
|
|
|
// The normalizing loads. Some of these read 8 bytes, so give them room.
|
|
const int8_t s8_values[8] = { -1, -128, 127, 45, 0, 0, 0, 0 };
|
|
Vec4F32::LoadS8Norm(s8_values).Store(result);
|
|
for (int i = 0; i < 4; i++) {
|
|
EXPECT_EQ_FLOAT(result[i], (float)s8_values[i] / 128.0f);
|
|
}
|
|
|
|
const int16_t s16_values[8] = { -1, -32768, 32767, 1234, 0, 0, 0, 0 };
|
|
Vec4F32::LoadS16Norm(s16_values).Store(result);
|
|
for (int i = 0; i < 4; i++) {
|
|
EXPECT_EQ_FLOAT(result[i], (float)s16_values[i] / 32768.0f);
|
|
}
|
|
|
|
const uint8_t u8_values[8] = { 0, 255, 128, 7, 0, 0, 0, 0 };
|
|
Vec4F32::LoadU8Norm(u8_values).Store(result);
|
|
for (int i = 0; i < 4; i++) {
|
|
EXPECT_APPROX_EQ_FLOAT(result[i], (float)u8_values[i] / 255.0f);
|
|
}
|
|
|
|
// The converting (non-normalizing) loads.
|
|
Vec4F32::LoadConvertS16(s16_values).Store(result);
|
|
for (int i = 0; i < 4; i++) {
|
|
EXPECT_EQ_FLOAT(result[i], (float)s16_values[i]);
|
|
}
|
|
|
|
Vec4F32::LoadConvertS8(s8_values).Store(result);
|
|
for (int i = 0; i < 4; i++) {
|
|
EXPECT_EQ_FLOAT(result[i], (float)s8_values[i]);
|
|
}
|
|
|
|
Vec4F32::LoadConvertU8(u8_values).Store(result);
|
|
for (int i = 0; i < 4; i++) {
|
|
EXPECT_EQ_FLOAT(result[i], (float)u8_values[i]);
|
|
}
|
|
|
|
// StoreConvertToU8 truncates towards zero and saturates to 0-255.
|
|
const float to_u8_values[4] = { 0.0f, 255.0f, 12.75f, 200.0f };
|
|
uint8_t u8_out[4];
|
|
Vec4F32::Load(to_u8_values).StoreConvertToU8(u8_out);
|
|
EXPECT_EQ_INT(u8_out[0], 0);
|
|
EXPECT_EQ_INT(u8_out[1], 255);
|
|
EXPECT_EQ_INT(u8_out[2], 12);
|
|
EXPECT_EQ_INT(u8_out[3], 200);
|
|
|
|
const float saturate_values[4] = { -1.0f, 300.0f, -0.5f, 1000.0f };
|
|
Vec4F32::Load(saturate_values).StoreConvertToU8(u8_out);
|
|
EXPECT_EQ_INT(u8_out[0], 0);
|
|
EXPECT_EQ_INT(u8_out[1], 255);
|
|
EXPECT_EQ_INT(u8_out[2], 0);
|
|
EXPECT_EQ_INT(u8_out[3], 255);
|
|
|
|
// Int to float.
|
|
const int int_values[4] = { 0, -7, 1000, -32768 };
|
|
Vec4F32::FromVec4S32(Vec4S32::Load(int_values)).Store(result);
|
|
for (int i = 0; i < 4; i++) {
|
|
EXPECT_EQ_FLOAT(result[i], (float)int_values[i]);
|
|
}
|
|
|
|
// Load2 only fills the first two lanes.
|
|
const float two_values[2] = { 3.0f, 4.0f };
|
|
Vec4F32::Load2(two_values).Store(result);
|
|
EXPECT_EQ_FLOAT(result[0], 3.0f);
|
|
EXPECT_EQ_FLOAT(result[1], 4.0f);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool TestMatrices() {
|
|
static const float a_values[16] = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f };
|
|
static const float b_values[16] = { -12.0f, 3.0f, -2.5f, 5.0f, 31.0f, 0.5f, 4.0f, 6.0f, 7.0f, 13.0f, 12.0f, 51.0f, 81.0f, 32.0f };
|
|
static const float known_result[16] = { 395.0f, 171.0f, 41.5f, 170.0f, 942.0f, 410.5f, 111.5f, 475.0f, 1358.0f, 607.5f, 163.0f, 728.0f, 297.0f, 49.5f, 25.0f, 160.0f, };
|
|
float result[16];
|
|
Mat4F32 a(a_values);
|
|
Mat4F32 b(b_values);
|
|
|
|
Mul4x4By4x4(a, b).Store(result);
|
|
if (!CompareFloats(result, known_result, 16, __LINE__)) {
|
|
return false;
|
|
}
|
|
|
|
Mat4x3F32 d = Mat4x3F32(b_values + 2);
|
|
Mul4x3By4x4(d, a).Store(result);
|
|
|
|
static const float known_4x3_result[16] = { 332.5f, 371.0f, 404.5f, 438.0f, 80.5f, 95.0f, 105.5f, 116.0f, 192.0f, 237.0f, 269.0f, 301.0f, 790.0f, 1036.0f, 1185.0f, 1349.0f, };
|
|
if (!CompareFloats(result, known_4x3_result, 16, __LINE__)) {
|
|
return false;
|
|
}
|
|
|
|
static const float vec_values[4] = { 3.0f, 5.0f, 7.0f, 10000000.0f };
|
|
Vec4F32 v = Vec4F32::Load(vec_values);
|
|
|
|
v.AsVec3ByMatrix44(b).Store3(result);
|
|
|
|
static const float known_vec_result[3] = { 249.0f, 134.5f, 96.5f, };
|
|
if (!CompareFloats(result, known_vec_result, ARRAY_SIZE(known_vec_result), __LINE__)) {
|
|
return false;
|
|
}
|
|
Vec4F32 scale = Vec4F32::Load(a_values);
|
|
Vec4F32 translate = Vec4F32::Load(b_values);
|
|
|
|
TranslateAndScaleInplace(a, scale, translate);
|
|
a.Store(result);
|
|
|
|
static const float known_scale_result[16] = { -47.0f, 16.0f, -1.0f, 36.0f, -103.0f, 41.0f, 1.5f, 81.0f, -146.0f, 61.0f, 3.5f, 117.0f, 14.0f, 30.0f, 0.0f, 0.0f,};
|
|
if (!CompareFloats(result, known_scale_result, ARRAY_SIZE(known_scale_result), __LINE__)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool TestCrossSIMD() {
|
|
if (!TestVec4S32()) return false;
|
|
if (!TestVec4S32Compares()) return false;
|
|
if (!TestVec4F32Arith()) return false;
|
|
if (!TestVec4F32Compares()) return false;
|
|
if (!TestVec4F32Lanes()) return false;
|
|
if (!TestVec4F32NaNInf()) return false;
|
|
if (!TestVec4F32Loads()) return false;
|
|
if (!TestMatrices()) return false;
|
|
return true;
|
|
}
|