Files
o3de/Code/CryEngine/Cry3DEngine/VMath_NEON.hpp
T
2021-03-25 13:57:57 -07:00

553 lines
15 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
// Description : unified vector math lib
#ifndef __D_VMATH_NEON__
#define __D_VMATH_NEON__
#include <arm_neon.h>
typedef float32x4_t vec4;
#include "VMath_Prototypes.hpp"
#define AVOID_COMPILER_BUG
#ifdef AVOID_COMPILER_BUG
#define SWIZZLEMASK5(X, Y, Z, W) ((X) | (Y << 2) | (Z << 4) | (W << 6))
#define SWIZZLEMASK4(N, X, Y, Z) N##x = SWIZZLEMASK5(X, Y, Z, 0), \
N##y = SWIZZLEMASK5(X, Y, Z, 1), \
N##z = SWIZZLEMASK5(X, Y, Z, 2), \
N##w = SWIZZLEMASK5(X, Y, Z, 3),
#define SWIZZLEMASK3(N, X, Y) SWIZZLEMASK4(N##x, X, Y, 0) \
SWIZZLEMASK4(N##y, X, Y, 1) \
SWIZZLEMASK4(N##z, X, Y, 2) \
SWIZZLEMASK4(N##w, X, Y, 3)
#define SWIZZLEMASK2(N, X) SWIZZLEMASK3(N##x, X, 0) \
SWIZZLEMASK3(N##y, X, 1) \
SWIZZLEMASK3(N##z, X, 2) \
SWIZZLEMASK3(N##w, X, 3)
#define SWIZZLEMASK1 SWIZZLEMASK2(x, 0) \
SWIZZLEMASK2(y, 1) \
SWIZZLEMASK2(z, 2) \
SWIZZLEMASK2(w, 3)
enum ESwizzleMask
{
SWIZZLEMASK1
};
#else
#define SWIZZLEMASK5(X, Y, Z, W) {(X + 0), (X + 1), (X + 2), (X + 3), (Y + 0), (Y + 1), (Y + 2), (Y + 3), (Z + 16), (Z + 17), (Z + 18), (Z + 19), (W + 16), (W + 17), (W + 18), (W + 19) }
#define SWIZZLEMASK4(N, X, Y, Z) SWIZZLEMASK5(X, Y, Z, 0), \
SWIZZLEMASK5(X, Y, Z, 4), \
SWIZZLEMASK5(X, Y, Z, 8), \
SWIZZLEMASK5(X, Y, Z, 12),
#define SWIZZLEMASK3(N, X, Y) SWIZZLEMASK4(N##x, X, Y, 0) \
SWIZZLEMASK4(N##y, X, Y, 4) \
SWIZZLEMASK4(N##z, X, Y, 8) \
SWIZZLEMASK4(N##w, X, Y, 12)
#define SWIZZLEMASK2(N, X) SWIZZLEMASK3(N##x, X, 0) \
SWIZZLEMASK3(N##y, X, 4) \
SWIZZLEMASK3(N##z, X, 8) \
SWIZZLEMASK3(N##w, X, 12)
#define SWIZZLEMASK1 SWIZZLEMASK2(x, 0) \
SWIZZLEMASK2(y, 4) \
SWIZZLEMASK2(z, 8) \
SWIZZLEMASK2(w, 12)
#define SWIZZLEINDEX5(X, Y, Z, W) (X + Y + Z + W)
#define SWIZZLEINDEX4(N, X, Y, Z) N##x = SWIZZLEINDEX5(X, Y, Z, 0), \
N##y = SWIZZLEINDEX5(X, Y, Z, 1), \
N##z = SWIZZLEINDEX5(X, Y, Z, 2), \
N##w = SWIZZLEINDEX5(X, Y, Z, 3),
#define SWIZZLEINDEX3(N, X, Y) SWIZZLEINDEX4(N##x, X, Y, 0) \
SWIZZLEINDEX4(N##y, X, Y, 1) \
SWIZZLEINDEX4(N##z, X, Y, 2) \
SWIZZLEINDEX4(N##w, X, Y, 3)
#define SWIZZLEINDEX2(N, X) SWIZZLEINDEX3(N##x, X, 0) \
SWIZZLEINDEX3(N##y, X, 1) \
SWIZZLEINDEX3(N##z, X, 2) \
SWIZZLEINDEX3(N##w, X, 3)
#define SWIZZLEINDEX1 SWIZZLEINDEX2(x, 0) \
SWIZZLEINDEX2(y, 1) \
SWIZZLEINDEX2(z, 2) \
SWIZZLEINDEX2(w, 3)
enum ESwizzleMask
{
SWIZZLEINDEX1
};
namespace cryengine_vmath_neon_internal
{
// Contains pre-calculated lookup indices for Shuffle(). Indexed by ESwizzleMask
static const uint8x16_t g_SwizzleMasks[] = {
SWIZZLEMASK1
};
} // namespace cryengine_vmath_neon_internal
#endif // AVOID_COMPILER_BUG
enum ECacheLvl
{
ECL_LVL1,
ECL_LVL2,
ECL_LVL3,
};
/**
* Bit masks for use with result from SignMask()
*/
static const uint32 BitX = 0x00000080;
static const uint32 BitY = 0x00008000;
static const uint32 BitZ = 0x00800000;
static const uint32 BitW = 0x80000000;
ILINE vec4 Vec4(float x, float y, float z, float w)
{
const vec4 Ret = {x, y, z, w};
return Ret;
}
ILINE vec4 Vec4(uint32 x, uint32 y, uint32 z, uint32 w)
{
const uint32x4_t Ret = {x, y, z, w};
return (float32x4_t)Ret;
}
ILINE vec4 Vec4(float x)
{
return Vec4(x, x, x, x);
}
template<int Idx>
ILINE int32 Vec4int32(vec4 V)
{
return vgetq_lane_s32((int32x4_t)V, Idx);
}
template <int Idx>
ILINE float Vec4float(vec4 V)
{
return vgetq_lane_f32(V, Idx);
}
ILINE int32 Vec4int32(vec4 V, uint32 Idx)
{
switch (Idx)
{
case 0:
return vgetq_lane_s32((int32x4_t)V, 0);
case 1:
return vgetq_lane_s32((int32x4_t)V, 1);
case 2:
return vgetq_lane_s32((int32x4_t)V, 2);
case 3:
return vgetq_lane_s32((int32x4_t)V, 3);
default:
assert(0);
return 0;
}
//return V.m_s[Idx];
}
ILINE float Vec4float(vec4 V, uint32 Idx)
{
switch (Idx)
{
case 0:
return vgetq_lane_f32(V, 0);
case 1:
return vgetq_lane_f32(V, 1);
case 2:
return vgetq_lane_f32(V, 2);
case 3:
return vgetq_lane_f32(V, 3);
default:
assert(0);
return 0;
}
}
ILINE vec4 Vec4Zero()
{
static const float32x4_t V = {0.f, 0.f, 0.f, 0.f};
return V;
}
ILINE vec4 Vec4One()
{
static const float32x4_t V = {1.f, 1.f, 1.f, 1.f};
return V;
}
ILINE vec4 Vec4Four()
{
static const float32x4_t V = {4.f, 4.f, 4.f, 4.f};
return V;
}
ILINE vec4 Vec4ZeroOneTwoThree()
{
static const float32x4_t V = {0.f, 1.f, 2.f, 3.f};
return V;
}
ILINE vec4 Vec4FFFFFFFF()
{
return Vec4(0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff);
}
ILINE vec4 Vec4Epsilon()
{
static const float32x4_t V = {FLT_EPSILON, FLT_EPSILON, FLT_EPSILON, FLT_EPSILON};
return V;
}
/**
* Creates vector where each word element is set to value of specified element.
* INDEX 0 is x, 1 is y, etc.
* @returns {V[INDEX], V[INDEX], V[INDEX], V[INDEX]}
*/
template<int INDEX>
ILINE vec4 Splat(vec4 V)
{
switch (INDEX)
{
case 0:
return vdupq_lane_f32(vget_low_f32(V), 0);
case 1:
return vdupq_lane_f32(vget_low_f32(V), 1);
case 2:
// expanded to fix a clang compiler error with the preprocessor
return vdupq_lane_f32(vget_high_f32(V), 0);
case 3:
// expanded to fix a clang compiler error with the preprocessor
return vdupq_lane_f32(vget_high_f32(V), 1);
default:
assert(0);
return Vec4FFFFFFFF();
}
}
template<ECacheLvl L>
ILINE void Prefetch(const void* pData)
{
__builtin_prefetch(pData);
}
/**
* Return vector containing words from V0 and V1 based on mask M.
* First two elements of M specify word index in V0 and last two elements
* specify word index in V1.
*
* e.g.
* Shuffle( M={0,1,2,3} ) => {V0[0], V0[1], V1[2], V1[3]}
*/
template<ESwizzleMask M>
ILINE vec4 Shuffle(vec4 V0, vec4 V1)
{
#ifdef AVOID_COMPILER_BUG
return Vec4(Vec4float(V0, M & 3), Vec4float(V0, (M >> 2) & 3), Vec4float(V1, (M >> 4) & 3), Vec4float(V1, (M >> 6) & 3));
#else
//return Vec4(V0.m_u[M&3],V0.m_u[(M>>2)&3],V1.m_u[(M>>4)&3],V1.m_u[(M>>6)&3]);
assert(M >= 0 && M < sizeof(cryengine_vmath_neon_internal::g_SwizzleMasks) / sizeof(cryengine_vmath_neon_internal::g_SwizzleMasks[0]));
// Get precomputed vector containing lookup indices for swizzle
const uint8x16_t mask = cryengine_vmath_neon_internal::g_SwizzleMasks[ M ];
// Split the lookup indexes into xy and zw pair
const uint8x8_t low_index = vget_low_u8(mask);
const uint8x8_t high_index = vget_high_u8(mask);
// Combine inputs into table; VO=xyzw, V1=XYZW => xyzwXYZW
const uint8x8x4_t tbl = {
{
(uint8x8_t)vget_low_f32(V0),
(uint8x8_t)vget_high_f32(V0),
(uint8x8_t)vget_low_f32(V1),
(uint8x8_t)vget_high_f32(V1),
}
};
// Look up the xy values, then zw values, then combine them
const uint8x8_t low = vtbl4_u8(tbl, low_index);
const uint8x8_t high = vtbl4_u8(tbl, high_index);
const uint8x16_t ret = vcombine_u8(low, high);
return (float32x4_t)ret;
#endif // AVOID_COMPILER_BUG
}
template<ESwizzleMask M>
ILINE vec4 Swizzle(vec4 V)
{
return Shuffle<M>(V, V);
}
/**
* @returns V0 + V1
*/
ILINE vec4 Add(vec4 V0, vec4 V1)
{
return vaddq_f32(V0, V1);
}
/**
* @returns V0 - V1
*/
ILINE vec4 Sub(vec4 V0, vec4 V1)
{
return vsubq_f32(V0, V1);
}
/**
* @returns V0 * V1
*/
ILINE vec4 Mul(vec4 V0, vec4 V1)
{
return vmulq_f32(V0, V1);
}
/**
* @returns V0 / V1
*/
ILINE vec4 Div(vec4 V0, vec4 V1)
{
return Mul(V0, Rcp(V1));
}
/**
* @returns 1 / V
*/
ILINE vec4 RcpFAST(vec4 V)
{
return vrecpeq_f32(V);
}
/**
* @returns V0 / V1
*/
ILINE vec4 DivFAST(vec4 V0, vec4 V1)
{
return Mul(V0, RcpFAST(V1));
}
/**
* @returns 1 / V
*/
ILINE vec4 Rcp(vec4 V)
{
// Improve estimate precision with a single Newton-Raphson iteration
vec4 vEstimate = RcpFAST(V);
return vmulq_f32(vrecpsq_f32(V, vEstimate), vEstimate);
}
/**
* @returns V0 * V1 + V2
*/
ILINE vec4 Madd(vec4 V0, vec4 V1, vec4 V2)
{
// Madd return V0*V1+V2 but vmla(a,b,c) does b*c+a
return vmlaq_f32(V2, V0, V1);
}
/**
* @returns V0 * V1 - V2
*/
ILINE vec4 Msub(vec4 V0, vec4 V1, vec4 V2)
{
// Msub returns V0*V1-V2 but vmls(a,b,c) does a-b*c
return Sub(Mul(V0, V1), V2);
}
ILINE vec4 Min(vec4 V0, vec4 V1)
{
return vminq_f32(V0, V1);
}
ILINE vec4 Max(vec4 V0, vec4 V1)
{
return vmaxq_f32(V0, V1);
}
ILINE vec4 floatToint32(vec4 V)
{
return (float32x4_t)vcvtq_s32_f32(V);
}
ILINE vec4 int32Tofloat(vec4 V)
{
return vcvtq_f32_s32((int32x4_t)V);
}
ILINE vec4 CmpLE(vec4 V0, vec4 V1)
{
return (float32x4_t)vcleq_f32(V0, V1);
}
/**
* Return sign bit of each word as a single word. Each is accessible via
* Bit[X|Y|Z|W] mask.
*/
ILINE uint32 SignMask(vec4 V)
{
// Xcode 6.3 removed support for the non-standard 'vtbl1q_p8' in favour
// of the standard 'vtbl2_u8' which is not available in Xcode 6.2
#if defined(__AARCH64_SIMD__) && defined(__apple_build_version__) && (__apple_build_version__ < 6020037)
const uint8x16_t tbl = vcombine_u8((uint8x8_t)vget_low_f32(V), (uint8x8_t)vget_high_f32(V));
const uint8x8_t idx_sign = {3, 7, 11, 15}; // MSB -> LSB = W -> X (0xWWZZYYXX)
const uint8x8_t sign_bytes = vtbl1q_p8(tbl, idx_sign);
#elif 1
// Create table from which be select bytes with msb (sign) and combine into
// single word
const uint8x8x2_t tbl = {
{
(uint8x8_t)vget_low_f32(V),
(uint8x8_t)vget_high_f32(V),
}
};
const uint8x8_t idx_sign = {3, 7, 11, 15}; // MSB -> LSB = W -> X (0xWWZZYYXX)
const uint8x8_t sign_bytes = vtbl2_u8(tbl, idx_sign);
#else
const int32x4_t rev = vshrq_n_s32((int32x4_t)V, 24);
const int16x4_t shorts = vmovn_s32(rev);
const uint8x8_t tbl = (uint8x8_t)shorts;
const uint8x8_t index = {1, 3, 5, 7};
const uint8x8_t sign_bytes = vtbl1_u8(tbl, index);
#endif
return vget_lane_u32((uint32x2_t)sign_bytes, 0);
//return (V.m_Xu>>31)|((V.m_Yu>>31)<<1)|((V.m_Zu>>31)<<2)|((V.m_Wu>>31)<<3);
}
/**
* @returns V0 & V1
*/
ILINE vec4 And(vec4 V0, vec4 V1)
{
return (float32x4_t)vandq_u32((uint32x4_t)V0, (uint32x4_t)V1);
}
/**
* @returns ~V0 & V1
*/
ILINE vec4 AndNot(vec4 V0, vec4 V1)
{
return (float32x4_t)vandq_u32(vmvnq_u32((uint32x4_t)V0), (uint32x4_t)V1);
}
/**
* @returns V0 | V1
*/
ILINE vec4 Or(vec4 V0, vec4 V1)
{
return (float32x4_t)vorrq_u32((uint32x4_t)V0, (uint32x4_t)V1);
}
/**
* @returns V0 ^ V1
*/
ILINE vec4 Xor(vec4 V0, vec4 V1)
{
return (float32x4_t)veorq_u32((uint32x4_t)V0, (uint32x4_t)V1);
}
ILINE vec4 Select(vec4 V0, vec4 V1, vec4 M)
{
return SelectSign(V0, V1, M);
}
/**
* Returns new vector where each word element comes from V0 if the sign of
* the corresponding word in M is 0 (+), otherwise the word element comes from
* V1.
*
* e.g.
* SelectSign( {0.f,1.f,2.f,3.f}, {a.f,b.f,c.f,d.f}, {0.f,0.f,-1.f,-1.f} )
* => {0.f, 1.f, c.f, d.f}
*/
ILINE vec4 SelectSign(vec4 V0, vec4 V1, vec4 M)
{
const int32x4_t mask = vshrq_n_s32((int32x4_t)M, 31);
return vbslq_f32((uint32x4_t)mask, V1, V0);
//M = ShiftAR(M,31);
//return Or(AndNot(M,V0),And(M,V1));
}
/**
* Expands each byte of VIn as a float.
*
* e.g.
* ExtractByteToFloat( {0x00010203, 0x04...} ) => {3.f, 2.f, 1.f, 0.f} ...
*/
static ILINE void ExtractByteToFloat(vec4& rVout0, vec4& rVout1, vec4& rVout2, vec4& rVout3, vec4 VIn)
{
// 16 bytes => 2x 8 shorts
const int8x8_t XY8 = vget_low_s8((int8x16_t)VIn);
const int8x8_t ZW8 = vget_high_s8((int8x16_t)VIn);
const int16x8_t XY16 = vmovl_s8(XY8);
const int16x8_t ZW16 = vmovl_s8(ZW8);
// 2x 8 shorts => 4x 4 int
const int16x4_t X16 = vget_low_s16(XY16);
const int16x4_t Y16 = vget_high_s16(XY16);
const int16x4_t Z16 = vget_low_s16(ZW16);
const int16x4_t W16 = vget_high_s16(ZW16);
const int32x4_t X32 = vmovl_s16(X16);
const int32x4_t Y32 = vmovl_s16(Y16);
const int32x4_t Z32 = vmovl_s16(Z16);
const int32x4_t W32 = vmovl_s16(W16);
// 4x 4 int => 4x 4 float
rVout0 = vcvtq_f32_s32(X32);
rVout1 = vcvtq_f32_s32(Y32);
rVout2 = vcvtq_f32_s32(Z32);
rVout3 = vcvtq_f32_s32(W32);
}
/**
* Returns vector where each bit is from V0 if the corresponding bit in
* M is 0, otherwise the bit is from V1 (i.e. vsel instruction from PowerPC, or selb from SPU).
*
* e.g.
* SelectBits(b0001, b1110, b0011) => b0010
*/
ILINE vec4 SelectBits(vec4 V0, vec4 V1, vec4 M)
{
// Order of args to vbsl is exact opposite of SelectBits()
return vbslq_f32((uint32x4_t)M, V1, V0);
//return Or(AndNot(M,V0),And(M,V1));
}
/**
* Returns vector where, for each word, all bits are set to 1 if the
* corresponding words in V0 and V1 are equal.
*
* e.g.
* CmpEq( {0.f...}, {0.f, 1.f, 2.f...} ) => {0xffffffff, 0x0, 0x0, 0x0}
*/
ILINE vec4 CmpEq(vec4 V0, vec4 V1)
{
return (float32x4_t)vceqq_f32(V0, V1);
}
template <int M>
ILINE vec4 SelectStatic(vec4 V0, vec4 V1)
{
const vec4 mask = Vec4(M & 0x1 ? ~0x0u : 0x0u, M & 0x2 ? ~0x0u : 0x0u, M & 0x4 ? ~0x0u : 0x0u, M & 0x8 ? ~0x0u : 0x0u);
return Select(V0, V1, mask);
}
#endif // __D_VMATH_NEON__
#undef AVOID_COMPILER_BUG