Skip transparent pixels and quantise while compositing

Much of a layer buffer is often transparent (a stroked ring, a glow-padded
text layer, a precomp canvas), yet every destination pixel sampled it
bilinearly, and 8/16 bpc comps then re-quantised the layer's whole rotated
bounding box.

- Bilinear warps skip 16x16 source tiles whose pixels (and a one-pixel
  apron) all have zero alpha: their samples are transparent and were never
  drawn.
- The fast affine warp reports the rectangle it changed and, given the
  comp's levels, quantises each pixel as it blends it, so draw_comp has
  nothing left to re-quantise for that layer. quantize_px moves to
  effectcraft-raster for this.
- quantize_rect skips runs of four all-zero (+0.0) pixels, which quantise
  to themselves.
- to_rgba8 / to_rgba8_over convert row by row instead of per 4-byte item.

Every pixel stays bit-identical: tests compare the warp against the
reference path, fused quantising against quantising after blending, and
the zero-run skip against per-pixel quantising.

With the previous commit, demo comp to H.264 (`cargo xtask bench --scenario
demo --format h264 --frames 300`, 11 interleaved runs on a 16-thread Ryzen):
wall 9.26 s -> 6.97 s median (-24.8%), 8.55 s -> 6.55 s min (-23.3%); CPU
time -23%. Output byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilver
2026-10-27 17:40:41 +01:00
co-authored by Claude Opus 5.5
parent d8bca3cdd9
commit db41fa7980
5 changed files with 430 additions and 111 deletions
+49 -7
View File
@@ -342,11 +342,26 @@ impl<'a> Renderer<'a> {
/// Draw a processed layer into `target` (transform, motion blur) with `mode`/`opacity`.
pub(crate) fn place(&self, ctx: &EvalCtx, layer: &Layer, buf: &Buf, target: &mut Image, mode: BlendMode, opacity: f32) {
self.place_changed(ctx, layer, buf, target, mode, opacity, None);
}
/// [`Self::place`], returning the target pixels it changed when the warp tracked them (then
/// also quantised to `quantize` levels; see [`composite_warp_changed`]).
#[allow(clippy::too_many_arguments)]
fn place_changed(
&self,
ctx: &EvalCtx,
layer: &Layer,
buf: &Buf,
target: &mut Image,
mode: BlendMode,
opacity: f32,
quantize: Option<f32>,
) -> Option<[u32; 4]> {
let pl = self.placement(ctx, layer, buf);
let opts = WarpOpts { sampling: pl.sampling, opacity, mode, seed: pl.seed, clip: None };
if let [m] = pl.matrices.as_slice() {
composite_warp(target, &buf.img, m, &opts);
return;
return composite_warp_changed(target, &buf.img, m, &opts, quantize);
}
// Sub-samples sum straight into one buffer (each warp is row-parallel).
let mut acc = Image::new(target.width, target.height);
@@ -355,6 +370,7 @@ impl<'a> Renderer<'a> {
effectcraft_raster::accumulate_warp(&mut acc, &buf.img, m, opts.sampling, k);
}
target.blend_from(&acc, mode, opacity, pl.seed);
None
}
/// Draw a 2D layer into `canvas`; returns the region it may have changed.
@@ -377,8 +393,14 @@ impl<'a> Renderer<'a> {
let st = self.styled_to_blend(st);
let t1 = web_time::Instant::now();
let changed = if st.plain {
self.composite_layer(ctx, layer, &st.body, canvas, opacity);
self.footprint(ctx, layer, &st.body, opacity, blank)
// Pixels the warp tracks are quantised as it blends them (nothing left to do); the
// footprint is still needed when it doesn't, or when no pixel needs quantising.
let footprint = self.footprint(ctx, layer, &st.body, opacity, blank);
let quantize = if footprint == Region::Empty { None } else { self.pipe.levels };
match self.composite_layer(ctx, layer, &st.body, canvas, opacity, quantize) {
Some(_) if quantize.is_some() => Region::Empty,
drawn => narrow(footprint, drawn),
}
} else {
self.composite_styled(ctx, layer, &st, canvas, opacity);
Region::Full
@@ -432,16 +454,18 @@ impl<'a> Renderer<'a> {
});
}
fn composite_layer(&self, ctx: &EvalCtx, layer: &Layer, buf: &Buf, canvas: &mut Image, opacity: f32) {
/// Composite a plain layer; returns the canvas pixels it changed when they are known (then
/// also quantised to `quantize` levels).
fn composite_layer(&self, ctx: &EvalCtx, layer: &Layer, buf: &Buf, canvas: &mut Image, opacity: f32, quantize: Option<f32>) -> Option<[u32; 4]> {
let matte = layer.track_matte.is_some_and(|tm| tm.layer != layer.id && ctx.comp.layer(tm.layer).is_some());
if !matte && !layer.preserve_transparency {
self.place(ctx, layer, buf, canvas, layer.blend_mode, opacity);
return;
return self.place_changed(ctx, layer, buf, canvas, layer.blend_mode, opacity, quantize);
}
// Render in isolation, then matte / preserve transparency, then blend.
let mut iso = Image::new(canvas.width, canvas.height);
self.place(ctx, layer, buf, &mut iso, BlendMode::Normal, 1.0);
self.composite_iso(ctx, layer, iso, canvas, opacity);
None
}
/// Apply the track matte / preserve transparency to an isolated layer render, then blend.
@@ -556,3 +580,21 @@ pub struct Placement {
/// Dissolve noise seed.
pub seed: u32,
}
/// The part of `footprint` a warp actually changed (`drawn`, see [`composite_warp_changed`]):
/// pixels it left alone are still quantised.
fn narrow(footprint: Region, drawn: Option<[u32; 4]>) -> Region {
let Some([x0, y0, x1, y1]) = drawn else { return footprint };
if x0 >= x1 || y0 >= y1 {
return Region::Empty;
}
let r = color::PxRect { x0, y0, x1, y1 };
match footprint {
Region::Empty => Region::Empty,
Region::Full => Region::Rect(r),
Region::Rect(f) => {
let q = color::PxRect { x0: r.x0.max(f.x0), y0: r.y0.max(f.y0), x1: r.x1.min(f.x1), y1: r.y1.min(f.y1) };
if q.x0 >= q.x1 || q.y0 >= q.y1 { Region::Empty } else { Region::Rect(q) }
}
}
}