/** * WebGPU FFT — Stockham autosort, radix-2, f32. * * Why Stockham rather than the textbook Cooley-Tukey the CPU core uses: it is * **self-sorting**. Each pass reads one buffer and scatters into another, so the output * comes out in natural order with **no bit-reversal permutation pass** — and a bit-reversal * is a pure memory shuffle, which is the one thing a GPU is worst at. The ping-pong between * two buffers is also exactly the access pattern the hardware wants. * * ### Is it worth it? (see `FFT_GPU_MIN_ELEMENTS` for the full table) * * Chrome / NVIDIA Pascal, warm JIT, against `fftCoreFloat64` — the flat f64 core that * `parallelFFT` runs on this thread: * * | n | CPU f64 | GPU f32 | speedup | * | --------- | -------- | -------- | --------- | * | 65,536 | 16.8 ms | 14.4 ms | 1.17× | * | 262,144 | 53.4 ms | 24.0 ms | **2.23×** | * | 1,048,576 | 253.1 ms | 79.7 ms | **3.18×** | * | 2,097,152 | 399.9 ms | 116.3 ms | **3.44×** | * * (~2.2-3.4x, and the ratio is genuinely noisy run to run — regenerate it with * `gpu-fft-bench.browser.test.ts` rather than trusting a single hero number. f32 error is * ~4e-7 peak-relative at every size above.) * * Two things decided the design: * * - **The threshold is 262,144, NOT `GPU_MIN_ELEMENTS`.** At 65,536 the GPU wins by 1.17×, * which is inside the noise and nowhere near enough to justify dropping f64 for f32. An * FFT makes log2(n) passes over the data, so it amortises the upload more slowly than the * memory-bound element-wise chain does. Sharing one threshold would have been convenient * and wrong. * - **f32 accuracy holds.** Error growth across log2(n) stages was the real risk — it is why * an f32 FFT is a harder sell than an f32 element-wise chain. Measured ~4e-7 relative to * the spectrum's peak magnitude even at 20 stages, a few times f32 epsilon. Stockham is * numerically well-behaved; the error did not compound. * * An earlier revision of this comment claimed 5.0-8.5x. That came from a COLD-JIT CPU * baseline and from comparing against a CPU path `parallelFFT` did not actually take. Both * are fixed; the numbers above are warm, and measured against the path it really runs. * * Same never-throw contract as the rest of the GPU surface: returns `null` — never rejects — * whenever the GPU is unavailable, not opted into, the input is below threshold, not a power * of two, or a device limit is exceeded. The caller then uses the exact f64 CPU path. * * @packageDocumentation */ import { type GPUContextOptions } from '@danielsimonjr/mathts-gpu'; /** Drop cached shaders/buffers (device loss, or between tests). */ export declare function resetGpuFft(): void; /** Options for the GPU FFT. `gpu` overrides the process-global `enableGpu()` flag. */ export interface GpuFftOptions extends GPUContextOptions { /** Force the GPU on/off for this call, ignoring the global flag. */ gpu?: boolean; } /** Result of a GPU FFT: f32-precision values widened into the f64 container. */ export interface GpuFftResult { real: Float64Array; imag: Float64Array; } /** * Run an FFT on the GPU. * * `real`/`imag` must be the same length, a power of two, and at least `FFT_GPU_MIN_ELEMENTS` * (262,144) — below that the GPU's margin (1.17× at 65,536) does not justify dropping f64 for * f32, so this declines and the caller keeps the exact CPU path. * * Returns `null` rather than throwing whenever it cannot or should not run, so the caller * simply falls through to the exact f64 CPU path. * * Precision: f32 (~4e-7 relative to the spectrum's peak, measured through 20 stages). * `enableGpu()` is how a caller consents to that. */ export declare function fftGpuDispatch(real: Float64Array, imag: Float64Array, inverse?: boolean, options?: GpuFftOptions): Promise; //# sourceMappingURL=fft-gpu.d.ts.map