mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-02 06:27:11 -06:00
Render: drop the blanket layer cover from effect stacks; effects pay only what they need (#1205)
Since #659 every text or shape layer with an enabled video effect grew its effect buffer to the comp-sized layer rectangle before the stack ran, so glow and blur — which only need their own margins — allocated a 1920x1080 RGBA32F texture per layer per frame in flight (~5 GB peak VRAM in parallel export, measured on an RTX 3090). Effects that actually place pixels outside the painted content already grow the buffer themselves by exactly what they need (CC Tiler, CC Smear, CC Mosaic sharp via layer_cover_pad / Buf::pad), on CPU and GPU. The blanket target.cover(layer_size) in Renderer::run_effects_on, the FxTarget::cover trait method with its CPU default, and the GPU GpuTarget::cover texture allocation are removed; Buf::cover/cover_plan stay as the effects-crate helpers they were designed as. The #659 regression tests (tiling across the layer region, centred-shape edge pinning) and the render/effects/gpu suites pass unchanged.
This commit is contained in:
@@ -145,21 +145,6 @@ impl effectcraft_render::FxTarget for GpuFx<'_, '_> {
|
||||
let out = f(Buf { img, offset: b.offset, scale: b.scale });
|
||||
self.b = self.e.g.upload_image(&out.img).map(|img| GBuf { img, offset: out.offset, scale: out.scale });
|
||||
}
|
||||
|
||||
/// GBuf::cover, like `pad`: a bigger texture with the old one copied into place, no
|
||||
/// readback. A region the device can't allocate leaves the buffer as it is.
|
||||
fn cover(&mut self, size: [f64; 2]) {
|
||||
let Some(b) = self.b.take() else { return };
|
||||
let out = match effectcraft_effects::cover_plan(b.offset, b.scale, size, b.img.width, b.img.height) {
|
||||
Some((dx, dy, w, h)) if self.e.g.fits(w, h) => {
|
||||
let img = self.e.image(w, h);
|
||||
self.e.copy_into(&b.img, &img, dx, dy);
|
||||
GBuf { img, offset: [b.offset[0] + dx as f64, b.offset[1] + dy as f64], scale: b.scale }
|
||||
}
|
||||
_ => b,
|
||||
};
|
||||
self.b = Some(out);
|
||||
}
|
||||
}
|
||||
|
||||
fn apply(e: &mut Enc, id: &str, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
|
||||
|
||||
@@ -490,16 +490,6 @@ pub trait FxTarget {
|
||||
fn gpu(&mut self, steps: &[FxStep]) -> bool;
|
||||
/// Run one CPU effect: `f` maps the buffer.
|
||||
fn cpu(&mut self, f: &mut dyn FnMut(Buf) -> Buf);
|
||||
/// Grow the buffer to cover its whole layer rectangle (`size` layer pixels from its own
|
||||
/// corner, the `layer_size` handed to effects), as [`effectcraft_effects::Buf::cover`].
|
||||
/// Authored buffers (text, shapes) only span their painted content; this gives the stack
|
||||
/// the room a sized source's buffer already has (#659).
|
||||
fn cover(&mut self, size: [f64; 2]) {
|
||||
self.cpu(&mut |mut buf| {
|
||||
buf.cover(size);
|
||||
buf
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// The CPU effect target (GPU chains go through [`Accelerator::effects`]: upload, readback).
|
||||
@@ -1132,15 +1122,11 @@ impl<'a> Renderer<'a> {
|
||||
let Some(fx) = layer.effects() else { return };
|
||||
let lt = layer.layer_time(ctx.time);
|
||||
let layer_size = ctx.effect_bounds(layer).0;
|
||||
// #659: an authored layer's buffer spans only its painted content while the stack works
|
||||
// on the layer's whole region (`layer_size`): give the buffer the room a sized source
|
||||
// already has, so tiling or displacement can place pixels outside the content instead
|
||||
// of losing them at its edge. Only when something will actually run, and not for an
|
||||
// adjustment layer, whose buffer is the comp below and already spans it.
|
||||
if !adjustment
|
||||
&& fx.groups().any(|g| g.enabled && matches!(&g.kind, GroupKind::Effect { effect } if effectcraft_effects::find(effect).is_some_and(|s| !effectcraft_effects::audio_fx::is_audio_effect(s.id)))) {
|
||||
target.cover(layer_size);
|
||||
}
|
||||
// #659 / #1205: an authored layer's buffer spans only its painted content. Effects that
|
||||
// place pixels outside it grow the buffer themselves, by exactly what they need
|
||||
// (`layer_cover_pad`: CC Tiler, CC Smear, CC Mosaic sharp) instead of a blanket cover of
|
||||
// the whole layer rectangle on every stack — which paid a comp-sized texture per layer,
|
||||
// per frame in flight, for glow and blur that never use the extra room.
|
||||
let mut mask_shapes = masks::shapes(ctx, layer);
|
||||
if origin != [0.0; 2] {
|
||||
for q in mask_shapes.iter_mut().flat_map(|m| m.points.iter_mut()) {
|
||||
|
||||
Reference in New Issue
Block a user