filtrate 0.2.1

GPU texture filter library: built-in filters (blur, brightness, color matrix, ...) and runtime, built on filtrate-core.
Documentation

filtrate

GPU texture filter library built on wgpu. Filters are declared as pure data, fused into as few GPU passes as possible, and executed by a runtime that handles parameter animation, HDR intermediates, and scratch-texture reuse. filtrate powers WaterUI's visual-effect modifiers but has no WaterUI dependency — it works on images, video frames, or any wgpu texture in any Rust application.

Crates

Crate Role
filtrate Built-in filter library, WGSL shaders, and the GPU runtime (FilterAdapter).
filtrate-core no_std abstraction layer: the pure-data Filter trait, Chain composition, and parameter/stage visitors. Stable surface with no wgpu dependency.
filtrate-derive #[derive(Filter)] for the regular single-pass filter shapes.

Quick start

use filtrate::{FilterAdapter, FilterExt};
use filtrate::filters::{Blur, Brightness, Grayscale};

// Chain filters; adjacent color-only filters fuse into one GPU pass.
let chain = Grayscale(1.0).then(Blur(5.0)).then(Brightness(0.2));
let effect = FilterAdapter::new(chain);
// Hand `effect` to your render loop: `Effect::setup`, then
// `Effect::render` once per frame.

Reactive frontends implement FilterParam for their signal types so filter parameters animate without rebuilding the pipeline; plain f32 works for static values.

Design notes

  • Fusion: consecutive COLOR_ONLY filters compile into a single fragment shader; spatial filters (blurs, convolutions, distortions) each get a compute pass with automatic scratch ping-pong.
  • Color contract: premultiplied alpha end to end; texel values are filtered as sampled, with no implicit sRGB conversion.
  • HDR: intermediates prefer Rgba16Float and degrade to LDR only where the policy allows (FilterAdapter::require_hdr / FilterAdapter::force_ldr).

WebGL (wasm)

The optional webgl feature turns on wgpu's WebGL2 backend. It only exists for wasm32-unknown-unknown; enabling it on a native target is a compile_error!.

WebGL2 has no compute shaders or storage textures, so spatial stages run through a fragment translation of the same WGSL body: every spatial shader writes only its own output texel, which the runtime rewrites into a fullscreen draw. Color stages are unchanged — they already run as fragment passes. Selection is automatic from device limits (SpatialExecution::Auto); ForceFragment exercises the WebGL2 path on native GPUs for tests and benchmarks.

Honest caveats:

  • Precision: compute can round the final stage through an Rgba16Float scratch + blit while the fragment path writes the output attachment directly, so outputs may differ by a ±1 u8 step per channel.
  • Performance: on an Apple-Silicon Metal adapter (cargo bench --bench gpu_runtime), the fragment path is parity-or-faster because it skips the final blit — e.g. a 2048² separable blur measures 2.54 ms fragment vs 2.57 ms compute+blit (medians). These numbers exercise the same shader path WebGL runs, but cannot model browser/WebGL context overhead.
  • A spatial body that breaks the one-store-per-own-texel contract fails loudly at specialization rather than silently mis-rendering.

License

MIT OR Apache-2.0, at your option.