pixfx

gpui

pixfx-gpui paints live effects into a gpui window: colour with normal blending, light with additive blending, enlarged on the GPU with nearest-neighbour sampling so the pixel art stays crisp.

Requirements

Additive sprites and nearest sampling need a gpui fork. Check it out next to this repository at ../zed-gpui, branch pixfx/additive-sprites. Without it, crates/pixfx-gpui and examples/gpui-web do not build; everything else does, because both are separate cargo workspaces.

cargo test --manifest-path crates/pixfx-gpui/Cargo.toml

Use

Keep one FxView per effect, tick it with real elapsed time, and paint it during gpui's paint phase:

use pixfx_core::{Layer, Options};
use pixfx_gpui::{FxView, paint_fx};

let campfire = pixfx_effects::effect("fire/campfire").ok_or("missing")?;
let mut view = FxView::new(campfire, Options::default());

// each frame
view.tick(dt_seconds);

// inside a canvas paint callback
paint_fx(&mut view, Layer::Both, origin, 4, window)?;

examples/gpui-web/main.rs is a complete app: a candle and a campfire on a ground, ticked from Instant::now() and repainted every animation frame.

API

FxView::new(effect, opts)

An instance on a canvas with glow_margin room on every side. opts.x and opts.y are replaced by the effect's pivot.

FxView::tick(dt)

Advance by dt seconds and mark the view changed.

paint_fx(view, layer, origin, scale, window)

Paint layer with the effect's origin at origin (logical pixels), each effect pixel scale×scale logical pixels (0 paints as 1). Colour is painted with SpriteBlend::Normal, then light with SpriteBlend::Additive, both with SpriteFilter::Nearest. Call it during the paint phase.

Images are rebuilt only when the view ticked. Each rebuild uploads only the occupied rectangle of each layer to gpui's sprite atlas, then drops the images it replaces, so the atlas holds one generation per effect and layer. Paint a view into one window only.

Web demo

cd examples/gpui-web
trunk serve --release

Open http://127.0.0.1:8090/ and click the page once: gpui throttles a page that does not have focus, so effects run slowly until it is focused. Add ?backend=webgl or ?backend=webgpu to force a renderer. The console logs the frame rate every two seconds. The first build takes about 5 minutes; later builds take seconds.