FastLED 3.10.8
Loading...
Searching...
No Matches

◆ update()

void fl::WaveSimulation2D_Real::update ( )

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

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

References fl::clamp(), fl::FL_NO_EXCEPT, 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: