import { TgpuRoot, TgpuTexture, TgpuTextureView, TgpuBindGroupLayout, TgpuFn } from 'typegpu'; import { ComputeStep } from '../compute'; import { Expr, GpuComputeNode, GpuFragmentParams, GpuMapInfo, GpuMapWindow } from '../contract'; import * as d from 'typegpu/data'; /** Default compute resolution. ~655k pixels — comparable to 0.5× of a 1080p canvas. * Memory per RGBA32F texture: ~10 MB (~5 MB at rgba16float). Heavy presets (Glow with 4 * storage textures) total ~30 MB regardless of canvas size. Callers can override for * memory-tight scenarios or higher quality. */ export declare const DEFAULT_COMPUTE_WIDTH = 1024; export declare const DEFAULT_COMPUTE_HEIGHT = 640; /** Storage format of the variable-blur radius map (caller-written). */ export declare const BLUR_MAP_FORMAT: "rgba32float"; /** * Bindable source-texture forms accepted by the blur (the RTT being blurred). A `TgpuTexture` * must carry `sampled` usage; callers may also pass a KitTexture's underlying view. */ export type BlurInputTexture = TgpuTextureView | TgpuTexture | GPUTextureView; /** * Full symmetric Gaussian weights over the whole kernel (no truncation), normalized to 1. * Used by the variable-radius blur, whose per-pixel tap spread is scaled at sample time so the * weights describe SHAPE only. */ export declare function buildFixedWeights(halfKernel: number): number[]; /** * Truncated + renormalized Gaussian for one pass. Taps beyond ~3σ contribute nothing visible, * so the active half-kernel is `clamp(ceil(3σ), 1, halfKernel)`; weights outside it are zero and * the remaining support is renormalized to sum to 1. Returns the full-length weight array (zeros * padded outside the active window) plus the active half so the kernel loop walks only that far. * Pulled out of the per-frame `updateWeights` closure so it can be golden-tested on the CPU. */ export declare function buildTruncatedWeights(halfKernel: number, sigma: number): { weights: number[]; activeHalf: number; }; export interface GaussianBlurComputeResult { /** * Ordered compute steps: [horizontal pass, vertical pass]. Dispatch via the compute * dispatcher. The horizontal pass reads the input texture; when the input is bound late * (via `setInputTexture` — see blur-run note below) this array's H entry is swapped in place, * so a caller that holds this array reference always dispatches the currently-bound H pass. */ computeSteps: ComputeStep[]; /** rgba16float, storage + sampled. The buffer consumers bilinear-sample at canvas resolution. */ outputTexture: TgpuTexture; updateRadius: (pixelRadius: number) => void; /** Fixed compute resolution — exposed so callers know the buffer's pixel size. */ computeWidth: number; computeHeight: number; /** Update input-canvas dimensions on resize. Doesn't trigger any rebuild. */ setInputDimensions: (width: number, height: number) => void; /** * blur-run: (re)bind the source texture the H pass reads. Needed when the input is an RTT * boundary the composition renders — the pass manager allocates that texture AFTER * composition, so the consumer defers the input and binds it here once it exists (and rebinds * on recompose). Rebuilds only the H bind group; the V pass is input-independent. */ setInputTexture: (input: BlurInputTexture) => void; } export interface VariableBlurComputeResult { computeSteps: ComputeStep[]; outputTexture: TgpuTexture; /** * rgba32float, storage. Stores the desired blur radius (input/canvas pixels) per compute * pixel in its .r channel. The CALLER fills it in their own compute pass by binding it to a * `{ storageTexture: d.textureStorage2d(BLUR_MAP_FORMAT, 'write-only') }` layout entry. */ blurMapTexture: TgpuTexture; computeWidth: number; computeHeight: number; setInputDimensions: (width: number, height: number) => void; /** blur-run: (re)bind the source texture the H pass reads (see GaussianBlurComputeResult). */ setInputTexture: (input: BlurInputTexture) => void; } /** * GPU-free construction of the fixed-blur node graph: bind-group layouts + kernel fns. No * device is touched, so the kernels can be `tgpu.resolve`d for WGSL snapshots. `createGaussian * BlurCompute` calls this and then allocates the resources + pipelines. */ export declare function buildFixedBlurGraph(halfKernel: number, computeWidth: number, computeHeight: number): { hLayout: TgpuBindGroupLayout<{ input: { texture: d.WgslTexture2d; }; intermediate: { storageTexture: d.WgslStorageTexture2d<"rgba16float", string>; }; weights: { storage: d.WgslArray; access: "readonly"; }; params: { uniform: d.WgslStruct<{ activeHalf: d.I32; inputWidth: d.F32; inputHeight: d.F32; }>; }; }>; vLayout: TgpuBindGroupLayout<{ src: { storageTexture: d.WgslStorageTexture2d<"rgba16float", string>; }; output: { storageTexture: d.WgslStorageTexture2d<"rgba16float", string>; }; weights: { storage: d.WgslArray; access: "readonly"; }; params: { uniform: d.WgslStruct<{ activeHalf: d.I32; }>; }; }>; kernelH: TgpuFn<(cx: d.U32, cy: d.U32) => d.Void>; kernelV: TgpuFn<(cx: d.U32, cy: d.U32) => d.Void>; HParams: d.WgslStruct<{ activeHalf: d.I32; inputWidth: d.F32; inputHeight: d.F32; }>; VParams: d.WgslStruct<{ activeHalf: d.I32; }>; KERNEL_SIZE: number; }; /** * 2-pass separable Gaussian blur backed by compute shaders, fixed-resolution output. The * fragment shader samples the result at canvas-resolution screenUV (bilinear handles up/downscale). * * @param root - the TypeGPU root (device). * @param inputTexture - source texture view to blur (typically a `convertToTexture` RTT). * @param inputWidth/inputHeight - source dimensions (canvas size). * @param onCleanup - cleanup callback registration. * @param halfKernel - half-kernel width (default 24 → 49 taps per pass). * @param computeWidth/computeHeight - fixed internal compute resolution (never reallocated). */ export declare function createGaussianBlurCompute(root: TgpuRoot, inputTexture: BlurInputTexture | null, inputWidth: number, inputHeight: number, onCleanup: (cb: () => void) => void, halfKernel?: number, computeWidth?: number, computeHeight?: number): GaussianBlurComputeResult; /** * GPU-free construction of the variable-blur node graph. `jitterTaps` is a build-time constant * baked into the kernel bodies (comptime), so enabling it changes the emitted WGSL. */ export declare function buildVariableBlurGraph(halfKernel: number, computeWidth: number, computeHeight: number, jitterTaps: boolean): { hLayout: TgpuBindGroupLayout<{ blurMap: { storageTexture: d.WgslStorageTexture2d<"rgba32float", string>; }; input: { texture: d.WgslTexture2d; }; intermediate: { storageTexture: d.WgslStorageTexture2d<"rgba16float", string>; }; weights: { storage: d.WgslArray; access: "readonly"; }; params: { uniform: d.WgslStruct<{ inputWidth: d.F32; inputHeight: d.F32; }>; }; }>; vLayout: TgpuBindGroupLayout<{ blurMap: { storageTexture: d.WgslStorageTexture2d<"rgba32float", string>; }; src: { storageTexture: d.WgslStorageTexture2d<"rgba16float", string>; }; output: { storageTexture: d.WgslStorageTexture2d<"rgba16float", string>; }; weights: { storage: d.WgslArray; access: "readonly"; }; params: { uniform: d.WgslStruct<{ scaleY: d.F32; }>; }; }>; kernelH: TgpuFn<(cx: d.U32, cy: d.U32) => d.Void>; kernelV: TgpuFn<(cx: d.U32, cy: d.U32) => d.Void>; HParams: d.WgslStruct<{ inputWidth: d.F32; inputHeight: d.F32; }>; VParams: d.WgslStruct<{ scaleY: d.F32; }>; KERNEL_SIZE: number; }; /** * Variable-radius (per-pixel) Gaussian blur with fixed-resolution output. The blur map texture is * at compute resolution and stores the desired blur radius (input/canvas pixels) per compute * pixel. The CALLER fills the map in their own compute pass by binding `blurMapTexture` to a * write-only storage-texture entry (the `blurMapWriteNode` replacement). * * @param options.jitterTaps - opt-in interleaved-gradient-noise tap jitter (decorrelates the * fixed tap comb's moiré against periodic content). Baked into the WGSL at build time. */ export declare function createVariableGaussianBlurCompute(root: TgpuRoot, inputTexture: BlurInputTexture | null, inputWidth: number, inputHeight: number, onCleanup: (cb: () => void) => void, halfKernel?: number, computeWidth?: number, computeHeight?: number, options?: { jitterTaps?: boolean; }): VariableBlurComputeResult; /** * Normalized Gaussian weights for `count` taps spread over ±1 of a Gaussian with std-dev `sigma`, * i.e. tap `i` sits at `t = (i/(count-1) - 0.5) * 2`. Computed on the CPU at build time so the * emitted WGSL carries literal weights instead of `exp()` per tap per pixel. * * σ² is rounded to 15 significant digits before the divide. `0.8 * 0.8` is `0.6400000000000001` in * float64, and these weights land in the WGSL as FULL-PRECISION literals — the un-rounded product * would move every consumer's snapshot for a one-ULP difference. 15 digits is lossless for any * decimal σ an author writes, and reproduces the hand-written `/ 0.64` tables bit-for-bit. */ export declare function gaussianTapWeights(count: number, sigma: number): number[]; export interface UnrolledTapGatherConfig { /** One weight per tap; its length IS the tap count. Typically {@link gaussianTapWeights}. */ weights: number[]; /** Sample coordinate for tap `i` (0-based). */ tapCoord: (tapIndex: number) => Expr; /** Sample the source at a coordinate — usually `(uv) => texture.sample(uv)`. */ sample: (coord: Expr) => Expr; /** * Reduce each sample to one channel before weighting (`'a'`, `'r'`, …). Omit to gather the * whole vec4. Used by the alpha-silhouette blurs (DropShadow) that only carry one channel. */ component?: string; } /** * Build the weighted sum of N unrolled taps: `Σ sample(tapCoord(i)) * weights[i]`. * * WHY UNROLLED. `textureSample` is illegal under non-uniform control flow, so a runtime loop over * taps is not available in a fragment shader. The builder emits a flat expression tree instead — * one `textureSample` per tap, no control flow at all. The tree is left-folded in tap order * (`((t0 + t1) + t2) …`) because float addition is not associative: any other grouping is a * different result. * * The caller owns the alpha convention. When the source is a premultiplied RTT (the normal case * for a filter) the result is premultiplied — finish with `blend.unpremultiplyAlpha`, or let * `defineRttFilter` append that tail. */ export declare function unrolledTapGather(config: UnrolledTapGatherConfig): Expr; /** * The four map-source channels a blur radius can be driven by — the same set the fragment path's * `resolveMapForProp` supports. A BUILD-TIME constant at every consumer: the channel selects one * of the memoized fns below, so each channel emits its own specialised WGSL. */ export type BlurMapChannel = 'luminance' | 'luminanceInverted' | 'alpha' | 'alphaInverted'; /** * Struct fields for the map-source canvas dimensions. Spread into a fill kernel's params struct so * the kernel can map its compute pixel onto the source's canvas pixel. Spread ORDER is * load-bearing (it is the uniform struct's member order) — these go before the remap window, which * is the order all six kernels already used. */ export declare const MAP_SOURCE_DIM_FIELDS: { readonly inputWidth: d.F32; readonly inputHeight: d.F32; }; /** Struct fields for the remap window — the CPU side of these is {@link GpuMapWindow}. */ export declare const REMAP_WINDOW_FIELDS: { readonly inputMin: d.F32; readonly inputMax: d.F32; readonly outputMin: d.F32; readonly outputMax: d.F32; readonly curve: d.F32; }; /** * The driving scalar a map source contributes at one texel, for `channel`. Memoized and `$name`d * per channel (C3): a shader picks one at graph-build time and calls it from its kernel body. * * Deliberately `dot(rgb, vec3(0.2126, 0.7152, 0.0722))` rather than `tone.luma709`, which spells * the same weights as a multiply-add chain. Same standard, different instruction sequence — and * these kernels are gated byte-identical. */ export declare function mapSourceScalar(channel: BlurMapChannel): TgpuFn<(sample: d.Vec4f) => d.F32>; /** * The map remap window, identical to the fragment path's: normalise `raw` into the input window, * apply the curve as an exponent (`2^(-curve·2)`, so 0 is linear and the sign flips the bend), and * mix across the output window. The input range is guarded against a zero-width window. * * Six scalar args rather than a struct (C1's threshold is "about five") because every consumer * reads these straight off its own uniform struct — a struct param here would either force a * per-kernel uniform-layout change or an inline struct construction, and these kernels are gated * byte-identical below the call. */ export declare const applyRemapWindow: TgpuFn<(raw: d.F32, inputMin: d.F32, inputMax: d.F32, outputMin: d.F32, outputMax: d.F32, curve: d.F32) => d.F32>; /** Spread a live {@link GpuMapWindow} into a fill kernel's params write. */ export declare function remapWindowValues(window: GpuMapWindow): GpuMapWindow; /** * Bright-pixel extraction for a bloom, at one compute pixel. * * Two things are happening. First, the canvas-pixel FOOTPRINT of this compute pixel is * area-averaged over a 4×4 stratified grid rather than point-sampled: that is energy-preserving, so * a hairline or a dither dot contributes in proportion to its coverage instead of shimmering on and * off as the grids slide past each other. Second, the threshold has a quadratic soft knee (the * Unity / Call-of-Duty bloom curve) so brightness ramps INTO the bloom around the threshold instead * of popping, and the mask is luminance-normalised so the extracted colour keeps its hue. * * Returns the extracted (premultiplied) colour to store in the bright buffer. The texture arrives * as an fn argument per C6. */ export declare const bloomExtractSoftKnee: TgpuFn<(childTexture: d.WgslTexture2d, cx: d.U32, cy: d.U32, inputSize: d.Vec2f, computeSize: d.Vec2f, threshold: d.F32) => d.Vec4f>; /** * A fill graph: the bind-group layout, the kernel that writes the radius map (and, for a bloom, the * bright buffer), and the params struct. Built GPU-free so the kernel `tgpu.resolve`s for snapshots. */ export interface BlurFillGraph { layout: TgpuBindGroupLayout; kernel: (cx: number, cy: number) => void; Params: d.AnyWgslStruct; } /** * The bloom pre-pass graph with a UNIFORM blur radius: bright-extract the canvas-res child RTT into * `brightMap` and write `params.size` into every texel of `blurMap`. * * `kernelName` is the emitted WGSL fn name — pass the consumer's existing name so its snapshot * doesn't move. */ export declare function buildBloomExtractGraph(computeWidth: number, computeHeight: number, kernelName: string): { layout: TgpuBindGroupLayout<{ childTexture: { texture: d.WgslTexture2d; }; brightMap: { storageTexture: d.WgslStorageTexture2d<"rgba16float", string>; }; blurMap: { storageTexture: d.WgslStorageTexture2d<"rgba32float", string>; }; params: { uniform: d.WgslStruct<{ inputWidth: d.F32; inputHeight: d.F32; threshold: d.F32; size: d.F32; }>; }; }>; kernel: TgpuFn<(cx: d.U32, cy: d.U32) => d.Void>; Params: d.WgslStruct<{ inputWidth: d.F32; inputHeight: d.F32; threshold: d.F32; size: d.F32; }>; }; /** * The bloom pre-pass graph with a MAP-DRIVEN blur radius. Bright extraction is identical to * {@link buildBloomExtractGraph}; the radius comes from a nearest tap on the map SOURCE (a * canvas-resolution RTT, bound late) pushed through the shared remap window. No `× 0.36` scaling — * a bloom's size prop is already in pixels. */ export declare function buildBloomExtractMapGraph(computeWidth: number, computeHeight: number, kernelName: string, channel: BlurMapChannel): { layout: TgpuBindGroupLayout<{ childTexture: { texture: d.WgslTexture2d; }; source: { texture: d.WgslTexture2d; }; brightMap: { storageTexture: d.WgslStorageTexture2d<"rgba16float", string>; }; blurMap: { storageTexture: d.WgslStorageTexture2d<"rgba32float", string>; }; params: { uniform: d.WgslStruct<{ inputMin: d.F32; inputMax: d.F32; outputMin: d.F32; outputMax: d.F32; curve: d.F32; inputWidth: d.F32; inputHeight: d.F32; threshold: d.F32; }>; }; }>; kernel: TgpuFn<(cx: d.U32, cy: d.U32) => d.Void>; Params: d.WgslStruct<{ inputMin: d.F32; inputMax: d.F32; outputMin: d.F32; outputMax: d.F32; curve: d.F32; inputWidth: d.F32; inputHeight: d.F32; threshold: d.F32; }>; }; /** * Compute resolution for a bloom: cap the LONGER edge at the default long edge and derive the other * from the canvas aspect. A fixed 1024×640 grid stretches the glow kernel on any other aspect — * the blur works in compute pixels, so non-square compute pixels smear it directionally. */ export declare function aspectAwareComputeRes(canvasWidth: number, canvasHeight: number): { width: number; height: number; }; /** What a `compute` hook returns. */ type BlurComputeNode = NonNullable>; export interface FixedBlurComputeConfig { /** Per-frame Gaussian radius in INPUT (canvas) pixels. Read live so mouse/auto drivers pull through. */ radius: () => number; halfKernel?: number; outputKey?: string; } /** * A uniform-radius separable Gaussian over the composed child. Blur's static path and ChannelBlur. */ export declare function withFixedBlurCompute(params: GpuFragmentParams, config: FixedBlurComputeConfig): BlurComputeNode | null; export interface VariableBlurComputeConfig { /** * Build the radius-map fill graph at the blur's compute resolution. Its layout MUST bind * `blurMap` (write-only, {@link BLUR_MAP_FORMAT}) and `params`; with `source` set it must also * bind a `source` sampled texture. */ buildFill: (computeWidth: number, computeHeight: number) => BlurFillGraph; /** * Per-frame values for the fill params. `dims` is the live canvas size in device pixels; * `window` is the live remap window, present only on the map-driven path. */ fillValues: (dims: { width: number; height: number; }, window?: GpuMapWindow) => Record; /** Map-driven radius: pass `getMapInfo(prop)`. Its source RTT is bound late, like the child. */ source?: GpuMapInfo; halfKernel?: number; outputKey?: string; } /** * A per-pixel-radius Gaussian whose radius map is filled by a caller-supplied kernel each frame. * ProgressiveBlur, TiltShift, and Blur's map-driven path. */ export declare function withVariableBlurCompute(params: GpuFragmentParams, config: VariableBlurComputeConfig): BlurComputeNode | null; export interface BloomComputeConfig { /** * Build the extract graph at the bloom's compute resolution — normally a one-line delegate to * {@link buildBloomExtractGraph} carrying the shader's own kernel name (so its snapshot is * stable and its GPU-free resolve test has something to import). */ buildExtract: (computeWidth: number, computeHeight: number) => BlurFillGraph; /** The map-driven twin, via {@link buildBloomExtractMapGraph}. Required when `mapInfo` is given. */ buildExtractMap?: (computeWidth: number, computeHeight: number, channel: BlurMapChannel) => BlurFillGraph; /** Map-driven radius: pass `getMapInfo(sizeProp)`, or null for the uniform path. */ mapInfo?: GpuMapInfo | null; /** Per-frame soft-knee threshold in [0, 1]. */ threshold: () => number; /** Per-frame uniform bloom radius in canvas pixels (uniform path only). */ radius: () => number; /** Key the blurred bright buffer is published under (Glow: default; FilmStock: `halationTexture`). */ outputKey?: string; } /** * A bloom: bright-extract the child at aspect-aware compute resolution, then blur the extract. * * The blur's input dimensions ARE its compute dimensions here (scaleY 1), so the H pass maps 1:1 and * the blur never needs `setInputDimensions` — only the extract kernel's canvas dims change on * resize. Tap jitter is on: the bloom radius is large and a fixed tap comb beats against periodic * content as halo moiré. */ export declare function withBloomCompute(params: GpuFragmentParams, config: BloomComputeConfig): BlurComputeNode | null; /** * The fragment tail every compute-backed blur shares: composite the canvas-resolution SHARP child * against the compute-resolution BLURRED buffer, and unpremultiply on the way back into the * straight-alpha blend pipeline (both textures hold premultiplied data — the child RTT always does). * * `compose` receives `(blurred, sharp)` samples at `ctx.uv` and returns the premultiplied result: * `vec4(blurred.rgb, sharp.a)` for a plain blur, a mix by the shader's own blur-amount field for a * selective one, a bloom add for a glow. * * When compute did not run — no device, or a shader's own bypass returned `null` from its `compute` * hook — this falls back to a sharp passthrough of the child RTT. That fallback is what makes the * GPU-free resolve gates work, so every consumer needs it and none should write it again. */ export declare function composeBlurredOverSharp(params: GpuFragmentParams, compose: (blurred: Expr, sharp: Expr) => Expr, options?: { blurredKey?: string; sharpKey?: string; }): Expr; /** * Blur intensity (UI 0–200) → Gaussian pixel radius. The separable Gaussian derives its per-pass * sigma from this radius (sigmaH = radius × 0.5). The factor is `intensity × 0.36` — shared by * Blur, ProgressiveBlur, and TiltShift (their intensity props are all 0–100/0–200 UI scales over * the same pixel-radius mapping). */ export declare const INTENSITY_TO_RADIUS = 0.36; export declare const intensityToRadius: (intensity: number) => number; /** * ChannelBlur's per-channel UI intensity (0–100) → Gaussian pixel radius via the `× 0.1` factor. A * single fixed Gaussian runs at the MAX per-channel radius; the fragment then mixes each channel * between the sharp source and that one blurred buffer by `channelRadius / maxRadius` (an * approximation that avoids 6 dispatches / 6 storage textures). */ export declare const CHANNEL_INTENSITY_TO_RADIUS = 0.1; export declare const channelIntensityToRadius: (intensity: number) => number; /** * Blur's map-driven fill graph: per compute pixel, sample the map-source RTT at the corresponding * canvas pixel, extract the driving scalar from a channel, run the SAME remap `resolveMapForProp` * uses in the fragment path, scale to a pixel radius (× 0.36), and write it into the variable * blur's radius map. GPU-free (resolvable without a device). `channel` is a build-time constant — * the channel branch is comptime-folded, so each channel emits its own specialised WGSL. */ export declare function buildFillBlurMapGraph(computeWidth: number, computeHeight: number, channel: BlurMapChannel): { layout: TgpuBindGroupLayout<{ source: { texture: d.WgslTexture2d; }; blurMap: { storageTexture: d.WgslStorageTexture2d<"rgba32float", string>; }; params: { uniform: d.WgslStruct<{ inputMin: d.F32; inputMax: d.F32; outputMin: d.F32; outputMax: d.F32; curve: d.F32; inputWidth: d.F32; inputHeight: d.F32; }>; }; }>; kernel: TgpuFn<(cx: d.U32, cy: d.U32) => d.Void>; Params: d.WgslStruct<{ inputMin: d.F32; inputMax: d.F32; outputMin: d.F32; outputMax: d.F32; curve: d.F32; inputWidth: d.F32; inputHeight: d.F32; }>; }; /** * ChannelBlur's fragment composite: per-channel `mix(sharp, blurred, channelRadius / maxRadius)`. * The blur ran at `maxRadius = max(r, g, b, 0.01)`, so the max-intensity channel takes the full * blur, a zero channel stays exactly sharp, and intermediate channels linearly interpolate. Alpha * comes from the sharp child. */ export declare const channelBlurCompose: TgpuFn<(sharp: d.Vec4f, blurred: d.Vec4f, redIntensity: d.F32, greenIntensity: d.F32, blueIntensity: d.F32) => d.Vec4f>; /** * ProgressiveBlur's directional blur amount at a UV: project the vector from the center onto the * (aspect-corrected) blur direction, clamp to the forward half-plane, and ramp it over `falloff`. * `center` is the TRANSFORMED position (transformPosition stores `(x, 1 - y)`), so `1.0 - centerY` * recovers the authored y. Pure float; used by the fill kernel to build the per-pixel radius map. */ export declare const progressiveBlurAmount: TgpuFn<(angleDeg: d.F32, centerX: d.F32, centerY: d.F32, falloff: d.F32, uv: d.Vec2f, aspect: d.F32) => d.F32>; /** * ProgressiveBlur's fill graph: the kernel writes `blurAmount × maxRadius` (input pixels) into the * variable blur's radius map. GPU-free. */ export declare function buildProgressiveBlurFillGraph(computeWidth: number, computeHeight: number): { layout: TgpuBindGroupLayout<{ blurMap: { storageTexture: d.WgslStorageTexture2d<"rgba32float", string>; }; params: { uniform: d.WgslStruct<{ angle: d.F32; centerX: d.F32; centerY: d.F32; falloff: d.F32; aspect: d.F32; maxRadius: d.F32; }>; }; }>; kernel: TgpuFn<(cx: d.U32, cy: d.U32) => d.Void>; Params: d.WgslStruct<{ angle: d.F32; centerX: d.F32; centerY: d.F32; falloff: d.F32; aspect: d.F32; maxRadius: d.F32; }>; }; /** * ProgressiveBlur's MAP-DRIVEN fill graph. The per-pixel max radius is sampled from the map SOURCE * (canvas-resolution RTT), remapped through the same window `resolveMapForProp` uses in the * fragment path, scaled `× 0.36`, then multiplied by the directional blur amount. Bind-group adds * the `source` texture (bound late — its RTT is allocated after composition). */ export declare function buildProgressiveBlurFillMapGraph(computeWidth: number, computeHeight: number, channel: BlurMapChannel): { layout: TgpuBindGroupLayout<{ source: { texture: d.WgslTexture2d; }; blurMap: { storageTexture: d.WgslStorageTexture2d<"rgba32float", string>; }; params: { uniform: d.WgslStruct<{ inputMin: d.F32; inputMax: d.F32; outputMin: d.F32; outputMax: d.F32; curve: d.F32; inputWidth: d.F32; inputHeight: d.F32; angle: d.F32; centerX: d.F32; centerY: d.F32; falloff: d.F32; aspect: d.F32; }>; }; }>; kernel: TgpuFn<(cx: d.U32, cy: d.U32) => d.Void>; Params: d.WgslStruct<{ inputMin: d.F32; inputMax: d.F32; outputMin: d.F32; outputMax: d.F32; curve: d.F32; inputWidth: d.F32; inputHeight: d.F32; angle: d.F32; centerX: d.F32; centerY: d.F32; falloff: d.F32; aspect: d.F32; }>; }; /** * TiltShift's blur amount at a UV: perpendicular distance from the (aspect-corrected) focus line, * ramped from the sharp band (`focusWidth = width × 0.5`) out over `falloff`. `center` is the * TRANSFORMED position (transformPosition stores `(x, 1 - y)`), so `1.0 - center.y` recovers the * authored y. Pure float. Shared by BOTH the fill kernel (to build the per-pixel radius map) AND * the fragment (to keep in-focus pixels crisp), so the two can never drift. */ export declare const tiltShiftBlurAmount: TgpuFn<(angleDeg: d.F32, center: d.Vec2f, width: d.F32, falloff: d.F32, uv: d.Vec2f, aspect: d.F32) => d.F32>; /** * TiltShift's fill graph: the kernel writes `blurAmount × maxRadius` (input pixels) into the * variable blur's radius map. GPU-free. */ export declare function buildTiltShiftFillGraph(computeWidth: number, computeHeight: number): { layout: TgpuBindGroupLayout<{ blurMap: { storageTexture: d.WgslStorageTexture2d<"rgba32float", string>; }; params: { uniform: d.WgslStruct<{ angle: d.F32; centerX: d.F32; centerY: d.F32; width: d.F32; falloff: d.F32; aspect: d.F32; maxRadius: d.F32; }>; }; }>; kernel: TgpuFn<(cx: d.U32, cy: d.U32) => d.Void>; Params: d.WgslStruct<{ angle: d.F32; centerX: d.F32; centerY: d.F32; width: d.F32; falloff: d.F32; aspect: d.F32; maxRadius: d.F32; }>; }; /** * TiltShift's MAP-DRIVEN fill graph. The per-pixel max radius is sampled from the map SOURCE, * remapped through the fragment path's window, scaled `× 0.36`, then multiplied by the tilt-shift * blur amount. Bind-group adds the `source` texture (bound late). */ export declare function buildTiltShiftFillMapGraph(computeWidth: number, computeHeight: number, channel: BlurMapChannel): { layout: TgpuBindGroupLayout<{ source: { texture: d.WgslTexture2d; }; blurMap: { storageTexture: d.WgslStorageTexture2d<"rgba32float", string>; }; params: { uniform: d.WgslStruct<{ inputMin: d.F32; inputMax: d.F32; outputMin: d.F32; outputMax: d.F32; curve: d.F32; inputWidth: d.F32; inputHeight: d.F32; angle: d.F32; centerX: d.F32; centerY: d.F32; width: d.F32; falloff: d.F32; aspect: d.F32; }>; }; }>; kernel: TgpuFn<(cx: d.U32, cy: d.U32) => d.Void>; Params: d.WgslStruct<{ inputMin: d.F32; inputMax: d.F32; outputMin: d.F32; outputMax: d.F32; curve: d.F32; inputWidth: d.F32; inputHeight: d.F32; angle: d.F32; centerX: d.F32; centerY: d.F32; width: d.F32; falloff: d.F32; aspect: d.F32; }>; }; /** * Glow's bloom composite: original colour plus the intensity-scaled blurred bloom, with the glow * aura extending into transparent areas (composite the blurred coverage OVER the child's alpha so * the halo isn't clipped to `original.a`). Pure float. */ export declare const glowCompose: TgpuFn<(original: d.Vec4f, bloom: d.Vec4f, intensity: d.F32) => d.Vec4f>; /** Glow's combined bright-extract + blur-map fill pre-pass (uniform static size). GPU-free. */ export declare const buildGlowPrepassGraph: (computeWidth: number, computeHeight: number) => { layout: TgpuBindGroupLayout<{ childTexture: { texture: d.WgslTexture2d; }; brightMap: { storageTexture: d.WgslStorageTexture2d<"rgba16float", string>; }; blurMap: { storageTexture: d.WgslStorageTexture2d<"rgba32float", string>; }; params: { uniform: d.WgslStruct<{ inputWidth: d.F32; inputHeight: d.F32; threshold: d.F32; size: d.F32; }>; }; }>; kernel: TgpuFn<(cx: d.U32, cy: d.U32) => d.Void>; Params: d.WgslStruct<{ inputWidth: d.F32; inputHeight: d.F32; threshold: d.F32; size: d.F32; }>; }; /** Glow's MAP-DRIVEN pre-pass: bright extraction is identical; the blur radius per pixel is sampled * from the size-map SOURCE and remapped through the same window the fragment path uses. No * `× 0.36` — Glow's size is already in pixels. */ export declare const buildGlowPrepassMapGraph: (computeWidth: number, computeHeight: number, channel: BlurMapChannel) => { layout: TgpuBindGroupLayout<{ childTexture: { texture: d.WgslTexture2d; }; source: { texture: d.WgslTexture2d; }; brightMap: { storageTexture: d.WgslStorageTexture2d<"rgba16float", string>; }; blurMap: { storageTexture: d.WgslStorageTexture2d<"rgba32float", string>; }; params: { uniform: d.WgslStruct<{ inputMin: d.F32; inputMax: d.F32; outputMin: d.F32; outputMax: d.F32; curve: d.F32; inputWidth: d.F32; inputHeight: d.F32; threshold: d.F32; }>; }; }>; kernel: TgpuFn<(cx: d.U32, cy: d.U32) => d.Void>; Params: d.WgslStruct<{ inputMin: d.F32; inputMax: d.F32; outputMin: d.F32; outputMax: d.F32; curve: d.F32; inputWidth: d.F32; inputHeight: d.F32; threshold: d.F32; }>; }; /** Radius UI (0–100) → gather radius in input/canvas pixels. Max ~80px disc — creamy but bounded so * the fixed tap comb stays dense enough to keep discs clean. */ export declare const BOKEH_RADIUS_SCALE = 0.8; export declare const bokehRadiusToPixels: (radius: number) => number; /** Fixed gather tap count. Bokeh isn't separable (a shaped disc can't split into two 1D passes), * so taps cost linearly — but the gather runs at the kit blurs' capped compute resolution and the * taps carry NO per-tap trig (positions are CPU-precomputed), so 100 taps lands in the same cost * ballpark as the kit's fixed 2×49-tap separable Gaussian. One tier, no quality knob. */ export declare const BOKEH_TAP_COUNT = 100; export interface BokehTap { x: number; y: number; rim: number; } export declare const BOKEH_SHAPES: readonly ["blades", "circle", "star", "heart", "flower", "cross", "ring"]; export type BokehShape = (typeof BOKEH_SHAPES)[number]; /** Polygon aperture radius along direction `theta` for a regular `sides`-gon inscribed in the unit * circle (vertices at r=1); `sides < 3` → circle. CPU mirror of the old GPU helper. */ export declare const bokehPolygonRadius: (theta: number, sides: number) => number; /** * Precompute `tapCount` gather offsets inside the chosen aperture shape (unit-radius space, * unrotated) plus each tap's rim-brightening weight. Circle/blades use the exact Vogel-spiral + * polygon-radius math the kernel used to run per tap (pixel-identical look); the other shapes * rejection-sample a Halton sequence against the CPU SDF mirrors. Offsets are NEGATED so the * VISIBLE disc (a bright point spreads to pixels at `point − offset`) matches the shape's screen * orientation — hearts upright, star tips up. Deterministic: same inputs → same table. */ export declare function generateBokehTaps(shape: string, bladeCount: number, tapCount: number): BokehTap[]; /** Map-driven channel (the driving scalar the radius-map source contributes per pixel). Same four * channels the fragment map path supports; a build-time constant so the branch is comptime-folded * (Blur's `BlurMapChannel` precedent). */ export type BokehMapChannel = BlurMapChannel; /** * Highlight-weighting stage (the bokeh trick): weight bright taps up so they dominate the gather * and form discs instead of washing out. Soft-knee smoothstep above `threshold`; `rim` carries the * tap's precomputed rim-brightening lift. Separable from the gather (pure per-tap math), shared by * the uniform-radius and map-driven kernels. */ export declare const bokehHighlightWeight: TgpuFn<(base: d.Vec4f, threshold: d.F32, gain: d.F32, rim: d.F32) => d.F32>; /** * GPU-free construction of the single-pass bokeh gather node graph (bind-group layout + kernel fn). * Bokeh is NOT separable, so unlike the kit's 2-pass Gaussian this scatters-as-gather over the * precomputed aperture tap table in ONE pass: for each output pixel it walks `tapCount` taps (unit * aperture offsets, rotated by the uniform cos/sin and scaled by the gather radius), boosts bright * taps via the highlight-weighting stage, brightens the rim via each tap's precomputed weight, and * optionally fringes the R/B channels for a lens look. Mirrors kit/blur's `buildFixedBlurGraph` * style (GPU-free, 2D dispatch — no manual bounds guard, SAMPLED `textureLoad` with a mip level * for the input, STORAGE `textureStore` for the output). * * The tap loop itself is an ATOMIC gather core — irreducible: one gather integral whose taps * accumulate into shared colour/alpha/weight sums, with the chromatic-fringe taps bound to this * graph's layout (conditional textureLoads can't leave the kernel) — so the loop does not * decompose past the weighting stage above. */ export declare function buildBokehGraph(computeWidth: number, computeHeight: number, tapCount: number): { layout: TgpuBindGroupLayout<{ input: { texture: d.WgslTexture2d; }; output: { storageTexture: d.WgslStorageTexture2d<"rgba16float", string>; }; params: { uniform: d.WgslStruct<{ radius: d.F32; highlightGain: d.F32; highlightThreshold: d.F32; rotCos: d.F32; rotSin: d.F32; chromaticFringe: d.F32; inputWidth: d.F32; inputHeight: d.F32; }>; }; taps: { uniform: d.WgslArray; }; }>; kernel: TgpuFn<(cx: d.U32, cy: d.U32) => d.Void>; Params: d.WgslStruct<{ radius: d.F32; highlightGain: d.F32; highlightThreshold: d.F32; rotCos: d.F32; rotSin: d.F32; chromaticFringe: d.F32; inputWidth: d.F32; inputHeight: d.F32; }>; TapArray: d.WgslArray; }; /** * GPU-free construction of the MAP-DRIVEN gather graph. Identical gather to `buildBokehGraph`, but the * per-pixel radius is sampled from the radius-map SOURCE (canvas-resolution RTT), remapped through the * same window `resolveMapForProp` uses in the fragment path, then scaled × RADIUS_SCALE. The `source` * texture is bound late (its RTT is allocated after composition). Mirrors Glow's map-prepass remap math. */ export declare function buildBokehMapGraph(computeWidth: number, computeHeight: number, tapCount: number, channel: BokehMapChannel): { layout: TgpuBindGroupLayout<{ input: { texture: d.WgslTexture2d; }; source: { texture: d.WgslTexture2d; }; output: { storageTexture: d.WgslStorageTexture2d<"rgba16float", string>; }; params: { uniform: d.WgslStruct<{ inputMin: d.F32; inputMax: d.F32; outputMin: d.F32; outputMax: d.F32; curve: d.F32; inputWidth: d.F32; inputHeight: d.F32; highlightGain: d.F32; highlightThreshold: d.F32; rotCos: d.F32; rotSin: d.F32; chromaticFringe: d.F32; }>; }; taps: { uniform: d.WgslArray; }; }>; kernel: TgpuFn<(cx: d.U32, cy: d.U32) => d.Void>; Params: d.WgslStruct<{ inputMin: d.F32; inputMax: d.F32; outputMin: d.F32; outputMax: d.F32; curve: d.F32; inputWidth: d.F32; inputHeight: d.F32; highlightGain: d.F32; highlightThreshold: d.F32; rotCos: d.F32; rotSin: d.F32; chromaticFringe: d.F32; }>; TapArray: d.WgslArray; }; export {}; //# sourceMappingURL=blur.d.ts.map