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

◆ setupChasingSpiralFrame()

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

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

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

References fl::fl::assume_aligned(), fl::fmodf(), fl::perlin_s16x16::init_fade_lut(), fl::Context::mEngine, 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: