packages feed

Box2D-0.1.0.0: upstream/src/simd.h

// SPDX-FileCopyrightText: 2026 Erin Catto
// SPDX-License-Identifier: MIT

#pragma once

#include "core.h"
#include "dynamic_tree.h"

#include "box2d/collision.h"
#include "box2d/math_functions.h"

#if defined( B2_SIMD_NEON )

#include <arm_neon.h>

typedef float32x4_t b2AABBV;

B2_FORCE_INLINE b2AABBV b2LoadAABBV( const b2AABB* aabb )
{
	return vld1q_f32( &aabb->lowerBound.x );
}

B2_FORCE_INLINE bool b2OverlapNode( b2AABBV av, const b2TreeNode* node )
{
	// [lower.x lower.y upper.x upper.y]
	float32x4_t bv = vld1q_f32( &node->aabb.lowerBound.x );

	// [alx aly blx bly]
	float32x4_t t1 = vcombine_f32( vget_low_f32( av ), vget_low_f32( bv ) );

	// [bux buy aux auy]
	float32x4_t t2 = vcombine_f32( vget_high_f32( bv ), vget_high_f32( av ) );

	return vminvq_u32( vcleq_f32( t1, t2 ) ) != 0;
}

B2_FORCE_INLINE bool b2OverlapV( const b2AABB* a, const b2AABB* b )
{
	// [lower.x lower.y upper.x upper.y]
	float32x4_t av = vld1q_f32( &a->lowerBound.x );
	float32x4_t bv = vld1q_f32( &b->lowerBound.x );

	// [alx aly blx bly]
	float32x4_t t1 = vcombine_f32( vget_low_f32( av ), vget_low_f32( bv ) );

	// [bux buy aux auy]
	float32x4_t t2 = vcombine_f32( vget_high_f32( bv ), vget_high_f32( av ) );

	return vminvq_u32( vcleq_f32( t1, t2 ) ) != 0;
}

B2_FORCE_INLINE b2AABB b2UnionV( b2AABB a, b2AABB b )
{
	float32x4_t b1 = vld1q_f32( &a.lowerBound.x );
	float32x4_t b2 = vld1q_f32( &b.lowerBound.x );
	float32x4_t lower = vminq_f32( b1, b2 );
	float32x4_t upper = vmaxq_f32( b1, b2 );
	b2AABB result;
	vst1q_f32( &result.lowerBound.x, vcombine_f32( vget_low_f32( lower ), vget_high_f32( upper ) ) );
	return result;
}

B2_FORCE_INLINE b2AABBV b2UnionPairV( const b2TreeNode* pair )
{
	float32x4_t b1 = vld1q_f32( &pair[0].aabb.lowerBound.x );
	float32x4_t b2 = vld1q_f32( &pair[1].aabb.lowerBound.x );
	return vcombine_f32( vget_low_f32( vminq_f32( b1, b2 ) ), vget_high_f32( vmaxq_f32( b1, b2 ) ) );
}

B2_FORCE_INLINE void b2StoreAABBV( b2AABB* aabb, b2AABBV value, bool condition )
{
	uint32x4_t mask = vdupq_n_u32( condition ? 0xFFFFFFFFu : 0u );
	float32x4_t old = vld1q_f32( &aabb->lowerBound.x );
	vst1q_f32( &aabb->lowerBound.x, vbslq_f32( mask, value, old ) );
}

B2_FORCE_INLINE int b2LaneMaskV( uint32x4_t condition )
{
	static const uint32_t laneBits[4] = { 1, 2, 4, 8 };
	return (int)vaddvq_u32( vandq_u32( condition, vld1q_u32( laneBits ) ) );
}

B2_FORCE_INLINE int b2OverlapPairMask( const b2TreeNode* pairA, const b2TreeNode* pairB )
{
	float32x4_t a0 = vld1q_f32( &pairA[0].aabb.lowerBound.x );
	float32x4_t a1 = vld1q_f32( &pairA[1].aabb.lowerBound.x );
	float32x4_t b0 = vld1q_f32( &pairB[0].aabb.lowerBound.x );
	float32x4_t b1 = vld1q_f32( &pairB[1].aabb.lowerBound.x );

	float32x4_t bLower = vzip1q_f32( b0, b1 );
	float32x4_t bUpper = vzip2q_f32( b0, b1 );
	float32x4_t bLx = vcombine_f32( vget_low_f32( bLower ), vget_low_f32( bLower ) );
	float32x4_t bLy = vcombine_f32( vget_high_f32( bLower ), vget_high_f32( bLower ) );
	float32x4_t bUx = vcombine_f32( vget_low_f32( bUpper ), vget_low_f32( bUpper ) );
	float32x4_t bUy = vcombine_f32( vget_high_f32( bUpper ), vget_high_f32( bUpper ) );

	float32x4_t aLx = vcombine_f32( vdup_laneq_f32( a0, 0 ), vdup_laneq_f32( a1, 0 ) );
	float32x4_t aLy = vcombine_f32( vdup_laneq_f32( a0, 1 ), vdup_laneq_f32( a1, 1 ) );
	float32x4_t aUx = vcombine_f32( vdup_laneq_f32( a0, 2 ), vdup_laneq_f32( a1, 2 ) );
	float32x4_t aUy = vcombine_f32( vdup_laneq_f32( a0, 3 ), vdup_laneq_f32( a1, 3 ) );

	uint32x4_t x = vandq_u32( vcleq_f32( aLx, bUx ), vcleq_f32( bLx, aUx ) );
	uint32x4_t y = vandq_u32( vcleq_f32( aLy, bUy ), vcleq_f32( bLy, aUy ) );
	return b2LaneMaskV( vandq_u32( x, y ) );
}

B2_FORCE_INLINE int b2OverlapNodePairMask( b2AABBV av, const b2TreeNode* pair )
{
	float32x4_t b0 = vld1q_f32( &pair[0].aabb.lowerBound.x );
	float32x4_t b1 = vld1q_f32( &pair[1].aabb.lowerBound.x );

	float32x4_t bLower = vzip1q_f32( b0, b1 );
	float32x4_t bUpper = vzip2q_f32( b0, b1 );
	float32x4_t aLower = vzip1q_f32( av, av );
	float32x4_t aUpper = vzip2q_f32( av, av );

	int mask = b2LaneMaskV( vandq_u32( vcleq_f32( aLower, bUpper ), vcleq_f32( bLower, aUpper ) ) );
	return mask & ( mask >> 2 ) & 3;
}

#elif defined( B2_SIMD_SSE2 ) || defined( B2_SIMD_AVX2 )

#include <emmintrin.h>

typedef __m128 b2AABBV;

B2_FORCE_INLINE b2AABBV b2LoadAABBV( const b2AABB* aabb )
{
	return _mm_loadu_ps( &aabb->lowerBound.x );
}

// Passing the tree node rather than the AABB avoids a stack copy.
// todo confirm assembly
B2_FORCE_INLINE bool b2OverlapNode( b2AABBV av, const b2TreeNode* node )
{
	// Unaligned load
	// [lower.x lower.y upper.x upper.y]
	__m128 bv = _mm_loadu_ps( &node->aabb.lowerBound.x );

	// [alx aly blx bly]
	__m128 t1 = _mm_movelh_ps( av, bv );

	// [bux buy aux auy]
	__m128 t2 = _mm_movehl_ps( av, bv );

	return _mm_movemask_ps( _mm_cmple_ps( t1, t2 ) ) == 0xF;
}

B2_FORCE_INLINE bool b2OverlapV( const b2AABB* a, const b2AABB* b )
{
	// Unaligned load
	// [lower.x lower.y upper.x upper.y]
	__m128 av = _mm_loadu_ps( &a->lowerBound.x );
	__m128 bv = _mm_loadu_ps( &b->lowerBound.x );

	// [alx aly blx bly]
	__m128 t1 = _mm_movelh_ps( av, bv );

	// [bux buy aux auy]
	__m128 t2 = _mm_movehl_ps( av, bv );

	return _mm_movemask_ps( _mm_cmple_ps( t1, t2 ) ) == 0xF;
}

B2_FORCE_INLINE b2AABB b2UnionV( b2AABB a, b2AABB b )
{
	// Unaligned load
	// [lower.x lower.y upper.x upper.y]
	__m128 b1 = _mm_loadu_ps( &a.lowerBound.x );
	__m128 b2 = _mm_loadu_ps( &b.lowerBound.x );
	__m128 lower = _mm_min_ps( b1, b2 );
	__m128 upper = _mm_max_ps( b1, b2 );
	__m128 c = _mm_shuffle_ps( lower, upper, _MM_SHUFFLE( 3, 2, 1, 0 ) );
	b2AABB result = { 0 };
	_mm_storeu_ps( &result.lowerBound.x, c );
	return result;
}

B2_FORCE_INLINE b2AABBV b2UnionPairV( const b2TreeNode* pair )
{
	__m128 b1 = _mm_loadu_ps( &pair[0].aabb.lowerBound.x );
	__m128 b2 = _mm_loadu_ps( &pair[1].aabb.lowerBound.x );
	__m128 lower = _mm_min_ps( b1, b2 );
	__m128 upper = _mm_max_ps( b1, b2 );
	return _mm_shuffle_ps( lower, upper, _MM_SHUFFLE( 3, 2, 1, 0 ) );
}

// Conditionally store the value. This is optimized for tree refitting.
B2_FORCE_INLINE void b2StoreAABBV( b2AABB* aabb, b2AABBV value, bool condition )
{
	__m128 mask = _mm_castsi128_ps( _mm_set1_epi32( condition ? -1 : 0 ) );
	__m128 old = _mm_loadu_ps( &aabb->lowerBound.x );

	// blend
	_mm_storeu_ps( &aabb->lowerBound.x, _mm_or_ps( _mm_and_ps( mask, value ), _mm_andnot_ps( mask, old ) ) );
}

B2_FORCE_INLINE int b2OverlapPairMask( const b2TreeNode* pairA, const b2TreeNode* pairB )
{
	__m128 a0 = _mm_loadu_ps( &pairA[0].aabb.lowerBound.x );
	__m128 a1 = _mm_loadu_ps( &pairA[1].aabb.lowerBound.x );
	__m128 b0 = _mm_loadu_ps( &pairB[0].aabb.lowerBound.x );
	__m128 b1 = _mm_loadu_ps( &pairB[1].aabb.lowerBound.x );

	__m128 bLower = _mm_unpacklo_ps( b0, b1 );
	__m128 bUpper = _mm_unpackhi_ps( b0, b1 );
	__m128 bLx = _mm_movelh_ps( bLower, bLower );
	__m128 bLy = _mm_movehl_ps( bLower, bLower );
	__m128 bUx = _mm_movelh_ps( bUpper, bUpper );
	__m128 bUy = _mm_movehl_ps( bUpper, bUpper );

	__m128 aLx = _mm_shuffle_ps( a0, a1, _MM_SHUFFLE( 0, 0, 0, 0 ) );
	__m128 aLy = _mm_shuffle_ps( a0, a1, _MM_SHUFFLE( 1, 1, 1, 1 ) );
	__m128 aUx = _mm_shuffle_ps( a0, a1, _MM_SHUFFLE( 2, 2, 2, 2 ) );
	__m128 aUy = _mm_shuffle_ps( a0, a1, _MM_SHUFFLE( 3, 3, 3, 3 ) );

	__m128 x = _mm_and_ps( _mm_cmple_ps( aLx, bUx ), _mm_cmple_ps( bLx, aUx ) );
	__m128 y = _mm_and_ps( _mm_cmple_ps( aLy, bUy ), _mm_cmple_ps( bLy, aUy ) );
	return _mm_movemask_ps( _mm_and_ps( x, y ) );
}

B2_FORCE_INLINE int b2OverlapNodePairMask( b2AABBV av, const b2TreeNode* pair )
{
	__m128 b0 = _mm_loadu_ps( &pair[0].aabb.lowerBound.x );
	__m128 b1 = _mm_loadu_ps( &pair[1].aabb.lowerBound.x );

	__m128 bLower = _mm_unpacklo_ps( b0, b1 );
	__m128 bUpper = _mm_unpackhi_ps( b0, b1 );
	__m128 aLower = _mm_unpacklo_ps( av, av );
	__m128 aUpper = _mm_unpackhi_ps( av, av );

	int mask = _mm_movemask_ps( _mm_and_ps( _mm_cmple_ps( aLower, bUpper ), _mm_cmple_ps( bLower, aUpper ) ) );
	return mask & ( mask >> 2 ) & 3;
}

#else

typedef b2AABB b2AABBV;

B2_FORCE_INLINE b2AABBV b2LoadAABBV( const b2AABB* aabb )
{
	return *aabb;
}

B2_FORCE_INLINE bool b2OverlapNode( b2AABBV av, const b2TreeNode* node )
{
	const b2AABB* bv = &node->aabb;
	return av.lowerBound.x <= bv->upperBound.x && av.lowerBound.y <= bv->upperBound.y && bv->lowerBound.x <= av.upperBound.x &&
		   bv->lowerBound.y <= av.upperBound.y;
}

B2_FORCE_INLINE bool b2OverlapV( const b2AABB* a, const b2AABB* b )
{
	return a->lowerBound.x <= b->upperBound.x && a->lowerBound.y <= b->upperBound.y && b->lowerBound.x <= a->upperBound.x &&
		   b->lowerBound.y <= a->upperBound.y;
}

B2_FORCE_INLINE b2AABB b2UnionV( b2AABB a, b2AABB b )
{
	return b2AABB_Union( a, b );
}

B2_FORCE_INLINE b2AABBV b2UnionPairV( const b2TreeNode* pair )
{
	return b2AABB_Union( pair[0].aabb, pair[1].aabb );
}

B2_FORCE_INLINE void b2StoreAABBV( b2AABB* aabb, b2AABBV value, bool condition )
{
	if ( condition )
	{
		*aabb = value;
	}
}

B2_FORCE_INLINE int b2OverlapPairMask( const b2TreeNode* pairA, const b2TreeNode* pairB )
{
	int mask = 0;
	mask |= b2OverlapV( &pairA[0].aabb, &pairB[0].aabb ) ? 1 : 0;
	mask |= b2OverlapV( &pairA[0].aabb, &pairB[1].aabb ) ? 2 : 0;
	mask |= b2OverlapV( &pairA[1].aabb, &pairB[0].aabb ) ? 4 : 0;
	mask |= b2OverlapV( &pairA[1].aabb, &pairB[1].aabb ) ? 8 : 0;
	return mask;
}

B2_FORCE_INLINE int b2OverlapNodePairMask( b2AABBV av, const b2TreeNode* pair )
{
	int mask = 0;
	mask |= b2OverlapNode( av, pair + 0 ) ? 1 : 0;
	mask |= b2OverlapNode( av, pair + 1 ) ? 2 : 0;
	return mask;
}

#endif

int b2GetSIMDWidth( void );
void b2SetSIMDWidth( int width );