# How Box3D Uses SSE2/NEON SIMD to Optimize the Contact Solver Inner Loops

> Discover how Box3D optimizes its contact solver inner loops using SSE2/NEON SIMD. Achieve up to 75% fewer floating-point operations with parallel constraint solving for x86 and ARM.

- Repository: [Erin Catto/box3d](https://github.com/erincatto/box3d)
- Tags: performance
- Published: 2026-08-01

---

**Box3D accelerates its contact solver by packing four rigid bodies into SIMD registers—using `__m128` for SSE2 on x86 and `float32x4_t` for NEON on ARM—and solving constraints in parallel, reducing floating-point operations by up to 75% while keeping the solver code portable through abstraction types defined in [`src/simd.h`](https://github.com/erincatto/box3d/blob/main/src/simd.h).**

The `erincatto/box3d` physics engine achieves high-performance constraint resolution by leveraging SSE2/NEON SIMD to optimize the contact solver inner loops. Instead of processing bodies one at a time, the solver vectorizes operations across four bodies simultaneously, minimizing memory bandwidth and maximizing instruction throughput on modern CPUs without relying on hand-written assembly.

## SIMD Type Abstractions in src/simd.h

Box3D hides platform-specific intrinsics behind a set of **wide types** defined in [`src/simd.h`](https://github.com/erincatto/box3d/blob/main/src/simd.h) and selected at compile time in [`src/simd.c`](https://github.com/erincatto/box3d/blob/main/src/simd.c) via the `B3_SIMD_SSE2` and `B3_SIMD_NEON` macros:

- **`b3FloatW`**: Represents four floats packed into one SIMD register. Maps to `__m128` on SSE2/x86 and `float32x4_t` on NEON/ARM.
- **`b3Vec3W`**: Contains three `b3FloatW` lanes representing the **x**, **y**, and **z** components of a 3D vector for four bodies.
- **`b3QuatW`**: Stores quaternion components (**x**, **y**, **z**, **w**) as four `b3FloatW` values.

These abstractions allow the contact solver to perform **four velocity updates** or **four impulse calculations** with a single instruction.

## Gathering Body State with b3GatherBodies

Before entering the constraint resolution loop, the solver calls `b3GatherBodies` in [`src/contact_solver.c`](https://github.com/erincatto/box3d/blob/main/src/contact_solver.c) (around lines 1020–1050) to pack the state of four bodies into SIMD registers. This function loads linear velocity, angular velocity, position delta, and orientation data into `b3BodyStateW` structures:

```c
#if defined( B3_SIMD_SSE2 ) || defined( B3_SIMD_NEON )
static b3BodyStateW b3GatherBodies( const b3BodyState* states, int* indices )
{
    /* … load each body or a dummy placeholder … */
    b3BodyStateW s;
    s.v.X = b3SetW( b1.linearVelocity.x, b2.linearVelocity.x,
                   b3.linearVelocity.x, b4.linearVelocity.x );
    s.v.Y = b3SetW( b1.linearVelocity.y, b2.linearVelocity.y,
                   b3.linearVelocity.y, b4.linearVelocity.y );
    s.v.Z = b3SetW( b1.linearVelocity.z, b2.linearVelocity.z,
                   b3.linearVelocity.z, b4.linearVelocity.z );
    /* … same for angular velocity, position delta, and quaternion … */
    return s;
}

```

The `b3SetW` helper packs four scalar floats into a single SIMD lane. On SSE2, this uses `_mm_set_ps`, while NEON uses `vld1q_f32`.

### SSE2 Implementation Details

For x86 architectures, [`src/simd.h`](https://github.com/erincatto/box3d/blob/main/src/simd.h) implements `b3SetW` using Intel intrinsics that store values in reverse order to match register layout:

```c
static inline b3FloatW b3SetW( float a, float b, float c, float d )
{
    return _mm_set_ps( d, c, b, a );   // 4-wide SIMD lane
}

```

### NEON Implementation Details

For ARM architectures, the same function uses ARM NEON intrinsics to load from a temporary array:

```c
static inline b3FloatW b3SetW( float a, float b, float c, float d )
{
    float32_t array[4] = { a, b, c, d };
    return vld1q_f32( array );
}

```

## Vectorized Constraint Solving

Once body data is packed into `b3Vec3W` structures, the contact solver performs impulse resolution using **horizontal operations** that process four constraints simultaneously. The helper functions `b3AddW` and `b3MulW` map directly to hardware intrinsics:

- **SSE2**: `_mm_add_ps` and `_mm_mul_ps`
- **NEON**: `vaddq_f32` and `vmulq_f32`

Because the inner loop updates four bodies per iteration, the total number of scalar floating-point operations drops by a factor of four. Memory traffic is also reduced, as four velocity components load or store in a single instruction.

## Scattering Results with b3ScatterBodies

After completing the SIMD iterations, `b3ScatterBodies` writes the updated velocities back to the original body structures. This function extracts individual lanes from the SIMD registers by casting the `__m128` or `float32x4_t` to float arrays:

```c
static void b3ScatterBodies( b3BodyState* states, int* indices,
                             const b3BodyStateW* simdBody )
{
    const float* vx = (const float*)&simdBody->v.X;
    const float* vy = (const float*)&simdBody->v.Y;
    const float* vz = (const float*)&simdBody->v.Z;
    const float* wx = (const float*)&simdBody->w.X;
    const float* wy = (const float*)&simdBody->w.Y;
    const float* wz = (const float*)&simdBody->w.Z;

    if ( indices[0] != 0 && (states[indices[0]-1].flags & b3_dynamicFlag) )
    {
        b3BodyState* s = states + (indices[0]-1);
        b3Vec3 v = { vx[0], vy[0], vz[0] };
        b3Vec3 w = { wx[0], wy[0], wz[0] };
        /* … apply lock flags … */
        s->linearVelocity = v;
        s->angularVelocity = w;
    }
    /* … repeat for indices[1] … indices[3] … */
}

```

This scatter pattern ensures the compiler generates optimal `movss` instructions on x86 or `vld1.32` on ARM for extracting individual elements.

## Complete SIMD Workflow Example

The following example demonstrates the gather-compute-scatter pattern used by the contact solver. Compile with `-DB3_SIMD_SSE2 -msse2` for x86 or `-DB3_SIMD_NEON -mfpu=neon` for ARM:

```c
/* tiny_simd_demo.c – shows how to pack four floats, add a constant, and unpack */
#define B3_SIMD_SSE2          // comment this line and enable B3_SIMD_NEON for ARM

#include "src/simd.h"
#include <stdio.h>

int main(void)
{
    /* 4 body linear‑velocity X components */
    float a = 1.0f, b = 2.0f, c = 3.0f, d = 4.0f;

    /* Pack them into a SIMD lane */
    b3FloatW v = b3SetW( a, b, c, d );

    /* Add the same impulse to every body */
    b3FloatW impulse = b3SplatW( 0.5f );          // 0.5,0.5,0.5,0.5
    b3FloatW vNew = b3AddW( v, impulse );

    /* Unpack */
    const float *p = (const float*)&vNew;
    printf("Updated velocities: %.2f %.2f %.2f %.2f\n",
           p[0], p[1], p[2], p[3]);
    return 0;
}

```

## Summary

Box3D achieves high-performance contact solving through strategic SSE2/NEON SIMD optimization:

- **Type Abstraction**: `b3FloatW`, `b3Vec3W`, and `b3QuatW` in [`src/simd.h`](https://github.com/erincatto/box3d/blob/main/src/simd.h) provide a portable interface over `__m128` and `float32x4_t`.
- **Data Packing**: `b3GatherBodies` loads four bodies into SIMD registers using `b3SetW`, reducing memory latency.
- **Parallel Processing**: Constraint iterations use `b3AddW` and `b3MulW` to process four impulses simultaneously via `_mm_add_ps` or `vaddq_f32`.
- **Data Unpacking**: `b3ScatterBodies` extracts results efficiently, writing back to body states only when dynamic flags permit.
- **Zero Assembly**: The implementation relies entirely on compiler intrinsics selected at compile time via `B3_SIMD_SSE2` and `B3_SIMD_NEON` macros.

## Frequently Asked Questions

### What is the difference between b3FloatW and b3Vec3W in Box3D?

`b3FloatW` represents a single SIMD register containing four floating-point values (one per body), while `b3Vec3W` aggregates three `b3FloatW` instances to represent the x, y, and z components of a 3D vector for four bodies simultaneously. This allows the contact solver to perform vector math on four bodies at once using standard SIMD lanes.

### How does Box3D handle SIMD on platforms without SSE2 or NEON?

When neither `B3_SIMD_SSE2` nor `B3_SIMD_NEON` is defined, Box3D falls back to scalar implementations where `b3FloatW` is simply a struct of four floats and operations are performed element-wise in C. This ensures the physics engine runs correctly on all architectures while optimizing for SIMD where available.

### Why does Box3D process exactly four bodies at a time in the contact solver?

SSE2 and NEON registers are 128 bits wide, which accommodates exactly four 32-bit floats (`4 × 32 = 128`). Processing four bodies simultaneously maximizes register utilization and provides a theoretical 4× speedup in the inner loops, while keeping the code complexity manageable compared to wider SIMD variants like AVX-512.

### Does using SIMD intrinsics affect the determinism of the physics simulation?

No, Box3D maintains deterministic behavior across platforms because the SIMD abstraction layers—`b3AddW`, `b3MulW`, and `b3SetW`—produce identical mathematical results to their scalar counterparts, just computed in parallel. The constraint solver follows the same iteration count and clamping logic regardless of whether SSE2, NEON, or scalar code paths execute.