Sweep a 2D coordinate grid the same way an Animartrix render pass would (one Perlin lookup per output pixel). 16*16 = 256 pixels per outer iter, matching a 16x16 panel — small enough that the compiler won't unroll the world, large enough that any per-iteration fixed overhead is amortized.
| 42 | // won't unroll the world, large enough that any per-iteration fixed |
| 43 | // overhead is amortized. |
| 44 | inline PerlinBenchResult runPerlinBenchmark(int iters) { |
| 45 | PerlinBenchResult r; |
| 46 | r.iterations = iters; |
| 47 | |
| 48 | // Init the i16 implementation's fade lookup table once, off the |
| 49 | // benchmark clock. Function-local static so the linter's |
| 50 | // static-in-header rule stays happy and C++11's thread-safe init |
| 51 | // guarantees a single initialization across calls. The float path |
| 52 | // has no equivalent setup. |
| 53 | struct FadeLut { |
| 54 | int32_t table[257]; |
| 55 | FadeLut() { fl::perlin_i16_optimized::init_fade_lut(table); } |
| 56 | }; |
| 57 | static const FadeLut fade_lut_holder; // C++11 magic statics |
| 58 | const int32_t* fade_lut = fade_lut_holder.table; |
| 59 | |
| 60 | constexpr int GRID = 16; // 16x16 pixel pass per iteration |
| 61 | constexpr float STEP_F = 0.05f; // Animartrix-typical pixel step in noise space |
| 62 | // Mirror in fixed-point: 0.05 in Q16.16 = 0.05 * 65536 ≈ 3277 |
| 63 | constexpr int32_t STEP_I = static_cast<int32_t>(0.05f * 65536.0f); |
| 64 | |
| 65 | // ── Float pnoise ────────────────────────────────────────────── |
| 66 | { |
| 67 | float ax = 0.0f, ay = 0.0f; |
| 68 | int32_t sink = 0; |
| 69 | uint32_t t0 = micros(); |
| 70 | for (int it = 0; it < iters; it++) { |
| 71 | // Slowly drift the origin across the iter so the compiler |
| 72 | // can't pre-compute. Same pattern Animartrix uses (the |
| 73 | // origin advances with `time_speed * dt` each frame). |
| 74 | ax += 0.011f; |
| 75 | ay += 0.013f; |
| 76 | for (int row = 0; row < GRID; row++) { |
| 77 | float y = ay + row * STEP_F; |
| 78 | for (int col = 0; col < GRID; col++) { |
| 79 | float x = ax + col * STEP_F; |
| 80 | float n = fl::pnoise(x, y, 0.0f); |
| 81 | // Map [-1, 1] → int8 the way Animartrix's output |
| 82 | // stage does. Sink to defeat DCE. |
| 83 | sink += static_cast<int32_t>(n * 127.0f); |
| 84 | } |
| 85 | } |
| 86 | } |
| 87 | uint32_t t1 = micros(); |
| 88 | g_animartrix_bench_sink = sink; |
| 89 | r.pnoise_float_us = static_cast<int64_t>(t1 - t0); |
| 90 | } |
| 91 | |
| 92 | // ── i16 fixed-point pnoise2d ────────────────────────────────── |
| 93 | { |
| 94 | int32_t ax_i = 0, ay_i = 0; |
| 95 | // Q16.16 increment for the slow drift (matches 0.011, 0.013 in float) |
| 96 | constexpr int32_t DRIFT_X = static_cast<int32_t>(0.011f * 65536.0f); |
| 97 | constexpr int32_t DRIFT_Y = static_cast<int32_t>(0.013f * 65536.0f); |
| 98 | int32_t sink = 0; |
| 99 | uint32_t t0 = micros(); |
| 100 | for (int it = 0; it < iters; it++) { |
| 101 | ax_i += DRIFT_X; |
no test coverage detected