FastLED 3.9.15
Loading...
Searching...
No Matches

◆ update()

void fl::WaveSimulation2D_Real::update ( )

Definition at line 257 of file wave_simulation_real.cpp.hpp.

257 {
258 i16 *curr = (whichGrid == 0 ? grid1.data() : grid2.data());
259 i16 *next = (whichGrid == 0 ? grid2.data() : grid1.data());
260
261 // Update horizontal boundaries.
262 for (fl::size j = 0; j < height + 2; ++j) {
263 if (mXCylindrical) {
264 curr[j * stride + 0] = curr[j * stride + width];
265 curr[j * stride + (width + 1)] = curr[j * stride + 1];
266 } else {
267 curr[j * stride + 0] = curr[j * stride + 1];
268 curr[j * stride + (width + 1)] = curr[j * stride + width];
269 }
270 }
271
272 // Update vertical boundaries.
273 for (fl::size i = 0; i < width + 2; ++i) {
274 curr[0 * stride + i] = curr[1 * stride + i];
275 curr[(height + 1) * stride + i] = curr[height * stride + i];
276 }
277
278 i32 mCourantSq32 = static_cast<i32>(mCourantSq);
279 // Hoist the lower-saturation bound — see WaveSimulation1D_Real::update()
280 // for the rationale. Fold half-duplex into the per-cell clamp instead
281 // of running a second pass over the grid (especially expensive on
282 // PSRAM-backed grids).
283 const i32 q15_min = mHalfDuplex ? 0 : -32768;
284
285 // Update each inner cell. Per-row hoist: lift j*stride out of the inner
286 // loop, set up row pointers to curr/next/above/below, and mark them
287 // FL_RESTRICT_PARAM so the optimizer knows curr and next don't alias.
288 // This gives the compiler a clean dependency picture across iterations
289 // (it can vectorize / interleave loads without re-deriving the address).
290 //
291 // Outer-level branch on the stencil keeps the inner loop branchless;
292 // the two kernels are otherwise identical (Q15 multiply, damping,
293 // clamp). FivePoint is the backward-compatible default; the wrapper
294 // class WaveSimulation2D auto-selects NinePointIsotropic at high
295 // super-sample factors where the anisotropy of the 5-point stencil
296 // becomes visually obvious.
297 const bool nine_point = (mStencil == LaplacianStencil::NinePointIsotropic);
298 for (fl::size j = 1; j <= height; ++j) {
299 const fl::size row = j * stride;
300 const i16* FL_RESTRICT_PARAM row_curr = curr + row;
301 const i16* FL_RESTRICT_PARAM row_above = curr + (row - stride);
302 const i16* FL_RESTRICT_PARAM row_below = curr + (row + stride);
303 i16* FL_RESTRICT_PARAM row_next = next + row;
304 for (fl::size i = 1; i <= width; ++i) {
305 const i32 c = row_curr[i];
306 i32 laplacian;
307 if (nine_point) {
308 // 9-point isotropic Laplacian, scaled up by 6 to keep
309 // everything in integer arithmetic:
310 // 6 * lap = (NW+NE+SW+SE) + 4*(N+S+E+W) - 20*C
311 // The /6 is folded into the term computation below so we
312 // pay it once per cell rather than four times.
313 const i32 diag = (i32)row_above[i - 1] + row_above[i + 1] +
314 row_below[i - 1] + row_below[i + 1];
315 const i32 nbr = (i32)row_above[i] + row_below[i] +
316 row_curr[i - 1] + row_curr[i + 1];
317 laplacian = diag + (nbr << 2) - 20 * c;
318 } else {
319 // Standard 5-point Laplacian: N + S + E + W - 4*C.
320 laplacian = (i32)row_curr[i + 1] + row_curr[i - 1] +
321 row_above[i] + row_below[i] - (c << 2);
322 }
323 // Promote to i64 before the multiply. With the 2D CFL clamp at
324 // 0.5, the 5-point worst case product is ~4.3e9 and the
325 // 9-point (scaled by 6, so |lap| ~6x larger) is ~2.6e10 — both
326 // past i32 max (2.15e9). The i64 promote also acts as a safety
327 // net if the clamp is ever regressed.
328 i64 product = static_cast<i64>(mCourantSq32) * laplacian;
329 i32 term;
330 if (nine_point) {
331 // Undo the x6 scaling on the 9-point Laplacian here.
332 // Integer division by a compile-time-constant 6 is lowered
333 // to a reciprocal-multiply on Cortex-M4+ and to a small
334 // shift+add on AVR; either way it's well under the cost
335 // of computing the Laplacian itself.
336 term = static_cast<i32>((product >> 15) / 6);
337 } else {
338 term = static_cast<i32>(product >> 15);
339 }
340 // f = -next[index] + 2 * curr[index] + mCourantSq * laplacian.
341 i32 f = -(i32)row_next[i] + (c << 1) + term;
342
343 // Apply damping with the precomputed Q15 decay multiplier —
344 // see the 1D update for the rationale.
345 f = static_cast<i32>(
346 (static_cast<i64>(f) * mDampDecayQ15) >> 15);
347
348 // Clamp f into [q15_min, 32767] in a single step — subsumes
349 // both the Q15 saturation clamp and the half-duplex zero pass.
350 row_next[i] = static_cast<i16>(fl::clamp(f, q15_min, static_cast<i32>(32767)));
351 }
352 }
353
354 // Swap the roles of the grids.
355 whichGrid ^= 1;
356}
fl::vector_psram< i16 > grid1
fl::vector_psram< i16 > grid2
fl::i64 i64
Definition s16x16x4.h:222
constexpr enable_if< is_fixed_point< T >::value, T >::type clamp(T x, T lo, T hi) FL_NOEXCEPT
#define FL_RESTRICT_PARAM

References fl::clamp(), FL_RESTRICT_PARAM, grid1, grid2, height, mCourantSq, mDampDecayQ15, mHalfDuplex, mStencil, mXCylindrical, fl::NinePointIsotropic, stride, whichGrid, and width.

+ Here is the call graph for this function: