Skip to main content

box2d_rust/contact_solver/
wide.rs

1// Wide (SIMD) primitives for the contact solver, ported from the b2FloatW
2// abstraction in contact_solver.c.
3//
4// Box2D's default x64 build uses B2_SIMD_SSE2 with B2_SIMD_WIDTH == 4, so the
5// wide contact solver processes four graph-color contacts per block. This port
6// stays in safe Rust: `FloatW` is a newtype over `[f32; 4]` and every operation
7// is a fixed-size lane loop, which the fat-LTO release profile autovectorizes.
8//
9// Bit-exactness requirement: the lane arithmetic is composed op-for-op exactly
10// as the C intrinsics compose it. In particular b2MulAddW on SSE2 is
11// `_mm_add_ps(a, _mm_mul_ps(b, c))` — a real multiply followed by a real add,
12// NOT a fused multiply-add — so `mul_add(a, b, c)` here is `a + b * c` computed
13// as two separate rounded operations. min/max/sym_clamp mirror the SSE2 `>`/`<`
14// comparison semantics, which coincide with the scalar `max_float`/`min_float`/
15// `clamp_float` used by the overflow path. This is why the wide path produces
16// results bit-identical to the scalar path for disjoint bodies within a color.
17//
18// b2FloatW is four `float` lanes regardless of BOX2D_DOUBLE_PRECISION: in
19// Box2D `b2Vec2` and `b2BodyState` stay f32 in large-world mode (only b2Pos /
20// transform translation becomes f64), so the wide solver is identical in both
21// precision configurations and `FloatW` is always `[f32; 4]`.
22//
23// SPDX-FileCopyrightText: 2023 Erin Catto
24// SPDX-License-Identifier: MIT
25//
26// bring-up: called by the wide contact kernels.
27//
28// The fixed-size lane loops index several `[f32; 4]` arrays in lockstep, which
29// is the point of the SIMD layout; enumerate() would obscure the C-mirroring
30// intent. The add/sub/mul methods deliberately mirror b2AddW/b2SubW/b2MulW
31// rather than the std::ops traits so the kernels read like the C source.
32#![allow(clippy::needless_range_loop)]
33#![allow(clippy::should_implement_trait)]
34
35use crate::body::{body_flags, BodyState, IDENTITY_BODY_STATE};
36use crate::core::NULL_INDEX;
37use crate::math_functions::Vec2;
38
39/// Number of SIMD lanes. Matches the C B2_SIMD_WIDTH for the SSE2/NEON x64/ARM
40/// default (BOX2D_AVX2 with width 8 is not the default and is not ported).
41pub const SIMD_WIDTH: usize = 4;
42
43/// Wide float holding four lanes. (b2FloatW)
44#[derive(Debug, Clone, Copy, PartialEq, Default)]
45pub struct FloatW(pub [f32; SIMD_WIDTH]);
46
47/// Comparison mask, one boolean per lane. The C code represents masks as
48/// all-ones / all-zero float bit patterns and blends with bitwise and/or; a
49/// per-lane boolean with a `select` blend is behaviorally identical.
50#[derive(Debug, Clone, Copy, PartialEq, Default)]
51pub struct MaskW(pub [bool; SIMD_WIDTH]);
52
53impl MaskW {
54    /// (b2OrW on masks)
55    #[inline]
56    pub fn or(self, other: MaskW) -> MaskW {
57        let mut r = [false; SIMD_WIDTH];
58        for i in 0..SIMD_WIDTH {
59            r[i] = self.0[i] || other.0[i];
60        }
61        MaskW(r)
62    }
63}
64
65impl FloatW {
66    /// (b2ZeroW)
67    #[inline]
68    pub fn zero() -> FloatW {
69        FloatW([0.0; SIMD_WIDTH])
70    }
71
72    /// (b2SplatW)
73    #[inline]
74    pub fn splat(scalar: f32) -> FloatW {
75        FloatW([scalar; SIMD_WIDTH])
76    }
77
78    /// (b2AddW)
79    #[inline]
80    pub fn add(self, b: FloatW) -> FloatW {
81        let mut r = [0.0; SIMD_WIDTH];
82        for i in 0..SIMD_WIDTH {
83            r[i] = self.0[i] + b.0[i];
84        }
85        FloatW(r)
86    }
87
88    /// (b2SubW)
89    #[inline]
90    pub fn sub(self, b: FloatW) -> FloatW {
91        let mut r = [0.0; SIMD_WIDTH];
92        for i in 0..SIMD_WIDTH {
93            r[i] = self.0[i] - b.0[i];
94        }
95        FloatW(r)
96    }
97
98    /// (b2MulW)
99    #[inline]
100    pub fn mul(self, b: FloatW) -> FloatW {
101        let mut r = [0.0; SIMD_WIDTH];
102        for i in 0..SIMD_WIDTH {
103            r[i] = self.0[i] * b.0[i];
104        }
105        FloatW(r)
106    }
107
108    /// (b2MulAddW) self + b * c, two separate rounded ops as on SSE2.
109    #[inline]
110    pub fn mul_add(self, b: FloatW, c: FloatW) -> FloatW {
111        let mut r = [0.0; SIMD_WIDTH];
112        for i in 0..SIMD_WIDTH {
113            r[i] = self.0[i] + b.0[i] * c.0[i];
114        }
115        FloatW(r)
116    }
117
118    /// (b2MulSubW) self - b * c, two separate rounded ops as on SSE2.
119    #[inline]
120    pub fn mul_sub(self, b: FloatW, c: FloatW) -> FloatW {
121        let mut r = [0.0; SIMD_WIDTH];
122        for i in 0..SIMD_WIDTH {
123            r[i] = self.0[i] - b.0[i] * c.0[i];
124        }
125        FloatW(r)
126    }
127
128    /// (b2MinW) matches SSE2 `_mm_min_ps` (a < b ? a : b) and `min_float`.
129    #[inline]
130    pub fn min(self, b: FloatW) -> FloatW {
131        let mut r = [0.0; SIMD_WIDTH];
132        for i in 0..SIMD_WIDTH {
133            r[i] = if self.0[i] < b.0[i] {
134                self.0[i]
135            } else {
136                b.0[i]
137            };
138        }
139        FloatW(r)
140    }
141
142    /// (b2MaxW) matches SSE2 `_mm_max_ps` (a > b ? a : b) and `max_float`.
143    #[inline]
144    pub fn max(self, b: FloatW) -> FloatW {
145        let mut r = [0.0; SIMD_WIDTH];
146        for i in 0..SIMD_WIDTH {
147            r[i] = if self.0[i] > b.0[i] {
148                self.0[i]
149            } else {
150                b.0[i]
151            };
152        }
153        FloatW(r)
154    }
155
156    /// (b2SymClampW) clamp(self, -b, b) as max(-b, min(self, b)).
157    #[inline]
158    pub fn sym_clamp(self, b: FloatW) -> FloatW {
159        let mut r = [0.0; SIMD_WIDTH];
160        for i in 0..SIMD_WIDTH {
161            let nb = -b.0[i];
162            let m = if self.0[i] < b.0[i] {
163                self.0[i]
164            } else {
165                b.0[i]
166            };
167            r[i] = if nb > m { nb } else { m };
168        }
169        FloatW(r)
170    }
171
172    /// (b2GreaterThanW) per-lane self > b.
173    #[inline]
174    pub fn greater_than(self, b: FloatW) -> MaskW {
175        let mut r = [false; SIMD_WIDTH];
176        for i in 0..SIMD_WIDTH {
177            r[i] = self.0[i] > b.0[i];
178        }
179        MaskW(r)
180    }
181
182    /// (b2EqualsW) per-lane self == b.
183    #[inline]
184    pub fn equals(self, b: FloatW) -> MaskW {
185        let mut r = [false; SIMD_WIDTH];
186        for i in 0..SIMD_WIDTH {
187            r[i] = self.0[i] == b.0[i];
188        }
189        MaskW(r)
190    }
191
192    /// (b2AllZeroW) true if every lane is exactly zero.
193    #[inline]
194    pub fn all_zero(self) -> bool {
195        self.0[0] == 0.0 && self.0[1] == 0.0 && self.0[2] == 0.0 && self.0[3] == 0.0
196    }
197
198    /// (b2BlendW) component-wise returns mask ? b : a.
199    #[inline]
200    pub fn blend(a: FloatW, b: FloatW, mask: MaskW) -> FloatW {
201        let mut r = [0.0; SIMD_WIDTH];
202        for i in 0..SIMD_WIDTH {
203            r[i] = if mask.0[i] { b.0[i] } else { a.0[i] };
204        }
205        FloatW(r)
206    }
207}
208
209/// Wide vec2. (b2Vec2W)
210#[derive(Debug, Clone, Copy, PartialEq, Default)]
211pub struct Vec2W {
212    pub x: FloatW,
213    pub y: FloatW,
214}
215
216/// Wide rotation. (b2RotW)
217#[derive(Debug, Clone, Copy, PartialEq, Default)]
218pub struct RotW {
219    pub c: FloatW,
220    pub s: FloatW,
221}
222
223/// (b2DotW)
224#[inline]
225pub fn dot_w(a: Vec2W, b: Vec2W) -> FloatW {
226    a.x.mul(b.x).add(a.y.mul(b.y))
227}
228
229/// (b2CrossW)
230#[inline]
231pub fn cross_w(a: Vec2W, b: Vec2W) -> FloatW {
232    a.x.mul(b.y).sub(a.y.mul(b.x))
233}
234
235/// (b2RotateVectorW)
236#[inline]
237pub fn rotate_vector_w(q: RotW, v: Vec2W) -> Vec2W {
238    Vec2W {
239        x: q.c.mul(v.x).sub(q.s.mul(v.y)),
240        y: q.s.mul(v.x).add(q.c.mul(v.y)),
241    }
242}
243
244/// Wide version of b2BodyState. (b2BodyStateW)
245#[derive(Debug, Clone, Copy, PartialEq, Default)]
246pub struct BodyStateW {
247    pub v: Vec2W,
248    pub w: FloatW,
249    pub flags: FloatW,
250    pub dp: Vec2W,
251    pub dq: RotW,
252}
253
254/// Load four body states through lane indices. A stored index of 0 means null
255/// (the C encodes body sim index + 1), which yields the identity body state.
256/// This mirrors the scalar-fallback `b2GatherBodies`; the SSE2/NEON transpose
257/// variants compute the same logical result. (b2GatherBodies)
258#[inline]
259pub fn gather_bodies(states: &[BodyState], indices: &[i32; SIMD_WIDTH]) -> BodyStateW {
260    let mut out = BodyStateW::default();
261    for lane in 0..SIMD_WIDTH {
262        // zero means null
263        let i = indices[lane] - 1;
264        let s = if i == NULL_INDEX {
265            IDENTITY_BODY_STATE
266        } else {
267            states[i as usize]
268        };
269        out.v.x.0[lane] = s.linear_velocity.x;
270        out.v.y.0[lane] = s.linear_velocity.y;
271        out.w.0[lane] = s.angular_velocity;
272        out.flags.0[lane] = s.flags as f32;
273        out.dp.x.0[lane] = s.delta_position.x;
274        out.dp.y.0[lane] = s.delta_position.y;
275        out.dq.c.0[lane] = s.delta_rotation.c;
276        out.dq.s.0[lane] = s.delta_rotation.s;
277    }
278    out
279}
280
281/// Write the four lanes' velocities back to the solver bodies. Only dynamic
282/// bodies are written (checked against the live state flags, not the gathered
283/// lane), matching the C which avoids sharing a dummy body across workers.
284/// (b2ScatterBodies)
285#[inline]
286pub fn scatter_bodies(states: &mut [BodyState], indices: &[i32; SIMD_WIDTH], body: &BodyStateW) {
287    for lane in 0..SIMD_WIDTH {
288        // zero means null
289        let i = indices[lane] - 1;
290        if i == NULL_INDEX {
291            continue;
292        }
293        let state = &mut states[i as usize];
294        if state.flags & body_flags::DYNAMIC_FLAG != 0 {
295            state.linear_velocity = Vec2 {
296                x: body.v.x.0[lane],
297                y: body.v.y.0[lane],
298            };
299            state.angular_velocity = body.w.0[lane];
300        }
301    }
302}