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

◆ setupChasingSpiralFrame()

FrameSetup fl::anonymous_namespace{chasing_spirals.cpp.hpp}::setupChasingSpiralFrame ( Context & ctx,
ChasingSpiralState & state )

Definition at line 151 of file chasing_spirals.cpp.hpp.

151 {
152 auto *e = ctx.mEngine.get();
153 e->get_ready();
154
155 // Timing (once per frame, float is fine here)
156 e->timings.master_speed = 0.01;
157 e->timings.ratio[0] = 0.1;
158 e->timings.ratio[1] = 0.13;
159 e->timings.ratio[2] = 0.16;
160 e->timings.offset[1] = 10;
161 e->timings.offset[2] = 20;
162 e->timings.offset[3] = 30;
163 e->calculate_oscillators(e->timings);
164
165 const int num_x = e->num_x;
166 const int num_y = e->num_y;
167 const int total_pixels = num_x * num_y;
168
169 // Per-frame constants (float->FP boundary conversions)
170 constexpr FP scale(0.1f);
171 const FP radius_fp(e->radial_filter_radius);
172 const FP center_x_scaled = FP(e->animation.center_x * 0.1f);
173 const FP center_y_scaled = FP(e->animation.center_y * 0.1f);
174
175 const FP radial0(e->move.radial[0]);
176 const FP radial1(e->move.radial[1]);
177 const FP radial2(e->move.radial[2]);
178
179 // Reduce linear offsets modulo the Perlin noise period before converting
180 // to s16x16. Two reasons:
181 // 1. Prevents s16x16 overflow (range ±32767 in integer part).
182 // 2. Float32 precision fix: matches the same reduction applied in
183 // Chasing_Spirals_Float (animartrix v1 and v2 float paths) so both
184 // paths compute identical Perlin coordinates at all time values.
185 // Without this reduction, float32 loses per-pixel coordinate precision
186 // when move.linear grows large (ULP at 200,000 ≈ 0.024 > pixel step 0.1).
187 // Perlin noise is exactly periodic with period 256 at integer coordinates,
188 // so with scale_x=0.1 the effective period for offset_x is 256/0.1 = 2560.
189 // See: tests/fl/fx/2d/animartrix2.cpp "period reduction" test cases.
190 constexpr float perlin_period = 2560.0f; // 256.0f / scale_x(0.1f)
191 constexpr float scale_f = 0.1f;
192 const FP linear0_scaled = FP(fmodf(e->move.linear[0], perlin_period) * scale_f);
193 const FP linear1_scaled = FP(fmodf(e->move.linear[1], perlin_period) * scale_f);
194 const FP linear2_scaled = FP(fmodf(e->move.linear[2], perlin_period) * scale_f);
195
196 constexpr FP three_fp(3.0f);
197 constexpr FP one(1.0f);
198
199 // Build per-pixel SoA geometry (once when grid size changes)
200 if (state.count != total_pixels) {
201 const int padded = (total_pixels + 3) & ~3; // multiple of 4 for SIMD safety
202 state.base_angle.resize(padded, 0);
203 state.dist_scaled.resize(padded, 0);
204 state.rf3.resize(padded, 0);
205 state.rf_half.resize(padded, 0);
206 state.rf_quarter.resize(padded, 0);
207 state.pixel_idx.resize(padded, 0);
208
209 const FP inv_radius = one / radius_fp;
210 const FP one_third = one / three_fp;
211 int idx = 0;
212 for (int x = 0; x < num_x; x++) {
213 for (int y = 0; y < num_y; y++) {
214 const FP theta(e->polar_theta[x][y]);
215 const FP dist(e->distance[x][y]);
216 const FP rf = (radius_fp - dist) * inv_radius;
217 state.base_angle[idx] = (three_fp * theta - dist * one_third).raw();
218 state.dist_scaled[idx] = (dist * scale).raw();
219 state.rf3[idx] = (three_fp * rf).raw();
220 state.rf_half[idx] = (rf >> 1).raw();
221 state.rf_quarter[idx] = (rf >> 2).raw();
222 state.pixel_idx[idx] = e->mCtx->xyMapFn(x, y, e->mCtx->xyMapUserData);
223 idx++;
224 }
225 }
226 state.count = total_pixels;
227 }
228
229 // Initialize Perlin fade LUT once per state lifetime
230 if (!state.fade_lut_initialized) {
232 state.fade_lut_initialized = true;
233 }
234
235 const i32 cx_raw = center_x_scaled.raw();
236 const i32 cy_raw = center_y_scaled.raw();
237 const i32 lin0_raw = linear0_scaled.raw();
238 const i32 lin1_raw = linear1_scaled.raw();
239 const i32 lin2_raw = linear2_scaled.raw();
240 const i32 rad0_raw = radial0.raw();
241 const i32 rad1_raw = radial1.raw();
242 const i32 rad2_raw = radial2.raw();
243
244 // Stamp alignment on SoA pointers at the source so every downstream
245 // consumer (Q31 scalar loop, SIMD 4-wide loop, loadAligned helper)
246 // inherits the hint without needing per-site annotations.
247 //
248 // Why this matters for performance:
249 // 1. The SIMD path calls loadAligned() which feeds load_u32_4_aligned().
250 // With the alignment hint the compiler emits movdqa/movaps (aligned
251 // 128-bit loads) instead of movdqu/movups (unaligned). On older x86
252 // (pre-Nehalem) unaligned loads are significantly slower; on modern
253 // cores they still cost an extra micro-op when the address crosses a
254 // cache-line boundary.
255 // 2. The Q31 scalar path benefits too: the compiler can widen scalar
256 // i32 loads into SIMD gathers or auto-vectorize more aggressively
257 // when it knows the base pointer is 16-byte aligned.
258 // 3. fade_lut (256-entry i32 Perlin fade table) is accessed in every
259 // Perlin noise evaluation; the alignment hint lets the compiler
260 // assume cache-line-friendly access patterns.
261 //
262 // The underlying SoA arrays are allocated with FL_ALIGNAS(16) in
263 // ChasingSpiralState, so this is a promise (not a request).
264 // pixel_idx is u16 (2 bytes) and not SIMD-loaded, so no hint needed.
265 return FrameSetup{
266 total_pixels,
267 fl::assume_aligned<16>(state.base_angle.data()),
268 fl::assume_aligned<16>(state.dist_scaled.data()),
269 fl::assume_aligned<16>(state.rf3.data()),
270 fl::assume_aligned<16>(state.rf_half.data()),
271 fl::assume_aligned<16>(state.rf_quarter.data()),
272 state.pixel_idx.data(),
274 PERLIN_NOISE,
275 cx_raw,
276 cy_raw,
277 lin0_raw,
278 lin1_raw,
279 lin2_raw,
280 rad0_raw,
281 rad1_raw,
282 rad2_raw,
283 e->mCtx->leds
284 };
285}
TestState state
fl::UISlider scale("Scale", 4,.1, 4,.1)
constexpr i32 raw() const FL_NO_EXCEPT
Definition s16x16.h:62
T * assume_aligned(T *ptr) FL_NO_EXCEPT
Definition s16x16x4.h:126
float fmodf(float x, float y) FL_NO_EXCEPT
Definition math.h:344
fl::unique_ptr< Engine > mEngine
Definition context.h:38
static void init_fade_lut(fl::i32 *table) FL_NO_EXCEPT

References fl::fl::assume_aligned(), fl::FL_NO_EXCEPT, fl::fmodf(), fl::perlin_s16x16::init_fade_lut(), fl::s16x16::raw(), scale, setupChasingSpiralFrame(), state, fl::x, and fl::y.

Referenced by setupChasingSpiralFrame().

+ Here is the call graph for this function:
+ Here is the caller graph for this function: