Skip to content

Support linear filter chains in vello_common, vello_cpu and vello_gpu - #7

Open
nicoburns wants to merge 4 commits into
mainfrom
devin/1790704340-filter-chains
Open

nicoburns wants to merge 4 commits into
mainfrom
devin/1790704340-filter-chains

Conversation

@nicoburns

Copy link
Copy Markdown
Member

Summary

Adds ordered (linear) filter chains so a CSS filter: f1() f2() ... list can be rendered by both backends. Previously PreparedFilter::new panicked on graphs with more than one primitive and FilterGraph::add unioned per-primitive expansions, which clips chained spatial filters. Explicit SVG graph inputs, multithreaded vello_cpu and ComponentTransfer remain out of scope.

Built on top of linebender#1765 (ColorMatrix, included as the first commits here) because chain encoding on the GPU relies on its variable-stride filter data.

vello_common

  • Filter::from_primitives(iter) / Filter::from_functions(&[FilterFunction]); from_primitive delegates. Primitives are treated as a chain in insertion order (each takes the previous output).
  • Expansion accumulates as a directional sum rather than a union: blur(5) → drop-shadow(10,10,5) now expands by 15 + (10+15) px per side instead of max(15, 25).
  • PreparedFilter::chain(&Filter, &Affine) -> PreparedFilterChain (a SmallVec<[PreparedFilter; 1]>); unsupported primitives are skipped instead of panicking.
  • FilterFunction::to_primitive lowers brightness/contrast/grayscale/hue-rotate/invert/opacity/saturate/sepia to ColorMatrix (removes the unimplemented!()s in Filter::from_function).

vello_cpu — filter_lowp/filter_highp run the prepared chain in order against the same layer Pixmap + scratch buffer.

vello_gpu

  • FilterContext encodes every primitive of a layer back-to-back; FilterOp now references a PreparedGpuFilterChain (contiguous range) instead of a single header/offset.
  • FilterPassBuilder::emit_filter emits each primitive's passes consecutively (blur DecimationSizer reset per blur); the result is returned to the original region only once at the end.
  • Pass steps are now FilterStep { copies, filter }: the copy-to-scratch for a drop-shadow happens at its step (after ensuring the previous stage's output is in the original region), so a mid-chain drop-shadow composites against its real input rather than the unfiltered layer. wgpu and WebGL executors updated to run copies before each step's filter passes.

Tests — unit tests for the CSS matrices and summed expansion, GPU pass-plan tests (chain sequencing, drop-shadow input preservation, empty chain), and vello_tests snapshots filter_chain_* (order-sensitive brightness→contrast vs reversed, grayscale/invert/opacity, sepia/hue-rotate/saturate, identity functions, blur↔drop-shadow, offset→drop-shadow, drop-shadow→grayscale) run on CPU u8/f32 (all SIMD levels) and GPU.

Follow-up (separate repo): anyrender_vello_cpu / anyrender_vello_hybrid still truncate to nodes()[0].

Link to Devin session: https://dioxus.staging.devinenterprise.com/sessions/5cb5c4089ef348c49c6038b9047cb2d3
Open in Devin Desktop: https://dioxus.staging.devinenterprise.com/desktop/session/5cb5c4089ef348c49c6038b9047cb2d3?variant=devin-insiders
Requested by: @nicoburns

AdrianEddy and others added 3 commits September 26, 2026 21:20
Add a 4x5 color matrix filter (`feColorMatrix` style): each output
channel is a linear combination of the straight-alpha input channels plus
a constant offset.

vello_cpu applies matrices that only mix the color channels and preserve
alpha directly to the premultiplied channels, skipping the
unpremultiply/premultiply round trip.

vello_gpu now packs filter parameters back to back with a per-filter
texel stride, like encoded paints, instead of a fixed three-texel slot,
so the six-texel color matrix doesn't widen every other filter: offset
and flood take one texel, gaussian blur two, drop shadow three.

Builds on the original color matrix work in linebender#1623.

Co-authored-by: Bruce Mitchener <bruce.mitchener@gmail.com>
A filter layer can now apply several filter primitives in sequence, each
operating on the output of the previous one. This is what CSS filter lists
need.

- vello_common: `FilterGraph` is treated as an ordered chain; `add`
  accumulates the layer expansion as a sum instead of a union so that e.g.
  `blur -> drop-shadow` is not clipped. `PreparedFilter::chain` replaces
  `PreparedFilter::new` and skips unsupported primitives instead of
  panicking. `Filter::from_primitives`/`from_functions` build chains, and
  all CSS `FilterFunction`s lower to `ColorMatrix` (new `matrices::*`
  constructors) instead of hitting `unimplemented!`.
- vello_cpu: run the prepared chain in place on the layer pixmap.
- vello_gpu: `FilterContext::push` encodes all primitives of a layer and
  returns a `PreparedGpuFilterChain`; `FilterPassPlan` emits the passes of
  each primitive back-to-back. A drop shadow inside a chain copies its actual
  input (the previous primitive's output) to the scratch texture right before
  its own passes, instead of a single up-front copy of the layer. Both the
  wgpu and WebGL executors handle the per-step copy passes.
- vello_tests: snapshot tests for order-sensitive color chains, blur/drop
  shadow/offset combinations and neutral functions.
@staging-devin-ai-integration

Copy link
Copy Markdown

I'll fix CI failures and address comments from users with write access that start with 'Devin'.

  • Disable automatic comment, CI, and merge conflict monitoring

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants