Skip to content

Commit 86c0c9b

Browse files
committed
refactor: port the wide body gather transpose from the C SIMD path
1 parent 187bed9 commit 86c0c9b

2 files changed

Lines changed: 110 additions & 0 deletions

File tree

src/Box2D.NET/B2BodyState.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// SPDX-FileCopyrightText: 2025 Ikpil Choi(ikpil@naver.com)
33
// SPDX-License-Identifier: MIT
44

5+
using System.Runtime.InteropServices;
6+
57
namespace Box2D.NET
68
{
79
// Body State
@@ -31,6 +33,9 @@ namespace Box2D.NET
3133
// round shapes.
3234

3335
// 32 bytes
36+
// The layout is load bearing: the wide solver loads this as two Vector128<float> and
37+
// transposes, so the field order must stay [vx vy w flags][dpx dpy dqc dqs].
38+
[StructLayout(LayoutKind.Sequential)]
3439
public struct B2BodyState
3540
{
3641
public B2Vec2 linearVelocity; // 8

src/Box2D.NET/B2ContactSolvers.cs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
using System.Runtime.CompilerServices;
88
#if NET7_0_OR_GREATER
99
using System.Runtime.Intrinsics;
10+
using System.Runtime.Intrinsics.Arm;
11+
using System.Runtime.Intrinsics.X86;
1012
#endif
1113
using static Box2D.NET.B2Arrays;
1214
using static Box2D.NET.B2Cores;
@@ -1295,6 +1297,108 @@ static void b2ScatterBodies( b2BodyState* states, int* indices, const b2BodyStat
12951297

12961298
#else
12971299

1300+
#if NET7_0_OR_GREATER
1301+
// b2UnpackLoW: [a0 b0 a1 b1]
1302+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1303+
private static Vector128<float> UnpackLoW(Vector128<float> a, Vector128<float> b)
1304+
{
1305+
if (Sse.IsSupported)
1306+
{
1307+
return Sse.UnpackLow(a, b);
1308+
}
1309+
1310+
if (AdvSimd.Arm64.IsSupported)
1311+
{
1312+
return AdvSimd.Arm64.ZipLow(a, b);
1313+
}
1314+
1315+
return Vector128.Create(a.GetElement(0), b.GetElement(0), a.GetElement(1), b.GetElement(1));
1316+
}
1317+
1318+
// b2UnpackHiW: [a2 b2 a3 b3]
1319+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1320+
private static Vector128<float> UnpackHiW(Vector128<float> a, Vector128<float> b)
1321+
{
1322+
if (Sse.IsSupported)
1323+
{
1324+
return Sse.UnpackHigh(a, b);
1325+
}
1326+
1327+
if (AdvSimd.Arm64.IsSupported)
1328+
{
1329+
return AdvSimd.Arm64.ZipHigh(a, b);
1330+
}
1331+
1332+
return Vector128.Create(a.GetElement(2), b.GetElement(2), a.GetElement(3), b.GetElement(3));
1333+
}
1334+
1335+
// Reads the two 16 byte halves of a 32 byte body state.
1336+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1337+
private static Vector128<float> LoadHalf(ref B2BodyState state, int half)
1338+
{
1339+
return Unsafe.Add(ref Unsafe.As<B2BodyState, Vector128<float>>(ref state), half);
1340+
}
1341+
1342+
// This is a load and transpose
1343+
internal static B2BodyStateW b2GatherBodies(Span<B2BodyState> states, ReadOnlySpan<int> indices)
1344+
{
1345+
B2_VALIDATE(indices[0] >= 0 && indices[1] >= 0 && indices[2] >= 0 && indices[3] >= 0);
1346+
1347+
// b2_identityBodyState is exactly [0 0 0 0][0 0 1 0], the identityA/identityB pair
1348+
// C builds inline, so a null index can bind to it and skip the per-load branch.
1349+
B2BodyState identity = b2_identityBodyState;
1350+
1351+
// zero means null
1352+
int i1 = indices[0] - 1;
1353+
int i2 = indices[1] - 1;
1354+
int i3 = indices[2] - 1;
1355+
int i4 = indices[3] - 1;
1356+
1357+
ref B2BodyState s1 = ref (i1 == B2_NULL_INDEX ? ref identity : ref states[i1]);
1358+
ref B2BodyState s2 = ref (i2 == B2_NULL_INDEX ? ref identity : ref states[i2]);
1359+
ref B2BodyState s3 = ref (i3 == B2_NULL_INDEX ? ref identity : ref states[i3]);
1360+
ref B2BodyState s4 = ref (i4 == B2_NULL_INDEX ? ref identity : ref states[i4]);
1361+
1362+
Vector128<float> b1a = LoadHalf(ref s1, 0);
1363+
Vector128<float> b1b = LoadHalf(ref s1, 1);
1364+
Vector128<float> b2a = LoadHalf(ref s2, 0);
1365+
Vector128<float> b2b = LoadHalf(ref s2, 1);
1366+
Vector128<float> b3a = LoadHalf(ref s3, 0);
1367+
Vector128<float> b3b = LoadHalf(ref s3, 1);
1368+
Vector128<float> b4a = LoadHalf(ref s4, 0);
1369+
Vector128<float> b4b = LoadHalf(ref s4, 1);
1370+
1371+
// [vx1 vx3 vy1 vy3]
1372+
Vector128<float> t1a = UnpackLoW(b1a, b3a);
1373+
1374+
// [vx2 vx4 vy2 vy4]
1375+
Vector128<float> t2a = UnpackLoW(b2a, b4a);
1376+
1377+
// [w1 w3 f1 f3]
1378+
Vector128<float> t3a = UnpackHiW(b1a, b3a);
1379+
1380+
// [w2 w4 f2 f4]
1381+
Vector128<float> t4a = UnpackHiW(b2a, b4a);
1382+
1383+
B2BodyStateW simdBody = new B2BodyStateW();
1384+
simdBody.v.X = StoreW(UnpackLoW(t1a, t2a));
1385+
simdBody.v.Y = StoreW(UnpackHiW(t1a, t2a));
1386+
simdBody.w = StoreW(UnpackLoW(t3a, t4a));
1387+
simdBody.flags = StoreW(UnpackHiW(t3a, t4a));
1388+
1389+
Vector128<float> t1b = UnpackLoW(b1b, b3b);
1390+
Vector128<float> t2b = UnpackLoW(b2b, b4b);
1391+
Vector128<float> t3b = UnpackHiW(b1b, b3b);
1392+
Vector128<float> t4b = UnpackHiW(b2b, b4b);
1393+
1394+
simdBody.dp.X = StoreW(UnpackLoW(t1b, t2b));
1395+
simdBody.dp.Y = StoreW(UnpackHiW(t1b, t2b));
1396+
simdBody.dq.C = StoreW(UnpackLoW(t3b, t4b));
1397+
simdBody.dq.S = StoreW(UnpackHiW(t3b, t4b));
1398+
1399+
return simdBody;
1400+
}
1401+
#else
12981402
// This is a load and transpose
12991403
internal static B2BodyStateW b2GatherBodies(Span<B2BodyState> states, ReadOnlySpan<int> indices)
13001404
{
@@ -1326,6 +1430,7 @@ internal static B2BodyStateW b2GatherBodies(Span<B2BodyState> states, ReadOnlySp
13261430

13271431
return simdBody;
13281432
}
1433+
#endif
13291434

13301435
// This writes only the velocities back to the solver bodies
13311436
internal static void b2ScatterBodies(Span<B2BodyState> states, ReadOnlySpan<int> indices, ref B2BodyStateW simdBody)

0 commit comments

Comments
 (0)