mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-26 02:57:34 -06:00
Render: an adjustment layer's footprint key ignores its effects, so a time-dependent effect (Noise) no longer re-keys it and the GPU re-uploads it every frame (#687)
This commit is contained in:
@@ -857,3 +857,44 @@ fn readback_staging_stays_bounded_across_sizes() {
|
||||
let held = g.ctx.staging_bytes();
|
||||
assert!(held <= (256 << 20) + 2 * 1024 * 16 * 376, "staging buffers hold {held} bytes");
|
||||
}
|
||||
|
||||
/// A static adjustment layer with a time-dependent effect (Noise) keeps its footprint key
|
||||
/// across frames (the footprint is the source through the masks, not the effects), so the GPU
|
||||
/// uploads nothing after the first frame; matches the CPU.
|
||||
#[test]
|
||||
fn adjustment_footprint_uploads_once_under_time_dependent_effects() {
|
||||
let mut s = Scene::new(BitDepth::Bpc8);
|
||||
let bg = s.solid([0.3, 0.5, 0.7], 96, 64);
|
||||
s.push(bg);
|
||||
let mut adj = s.solid([1.0, 1.0, 1.0], 80, 50);
|
||||
adj.switches.adjustment = true;
|
||||
s.effect(&mut adj, "ec.noise.noise", &[("amount", n(30.0))]);
|
||||
s.push(adj);
|
||||
// The key itself (no GPU needed): stable across time.
|
||||
let comp = s.p.comp(s.cid).expect("the scene's comp");
|
||||
let adj = comp.layers.last().expect("the adjustment layer");
|
||||
let key = |k: f64| {
|
||||
effectcraft_render::cache::footprint_key(&effectcraft_render::EvalCtx::new(&s.p, s.cid, comp, Tick::from_seconds_f64(k)), adj, 1.0, false, false)
|
||||
};
|
||||
assert!(key(0.0).is_some());
|
||||
assert_eq!(key(0.0), key(0.5), "the footprint key changes with time");
|
||||
let Some(g) = gpu() else { return };
|
||||
let cache = effectcraft_render::cache::LayerCache::new(64 << 20);
|
||||
let mut r = Renderer::new(&s.p, &Pattern, RenderOpts { backend: Backend::Gpu, ..opts() });
|
||||
r.accel = Some(g);
|
||||
r.cache = Some(&cache);
|
||||
let t = |k: u64| Tick::from_seconds_f64(k as f64 / 30.0);
|
||||
let mut prev = None;
|
||||
for k in 0..4 {
|
||||
let before = g.ctx.transfer_stats().upload_bytes;
|
||||
let img = g.render(&r, s.cid, t(k)).expect("the GPU renders the frame");
|
||||
let up = g.ctx.transfer_stats().upload_bytes - before;
|
||||
if k >= 2 {
|
||||
assert_eq!(up, 0, "frame {k} uploaded {up} bytes: the footprint was re-keyed");
|
||||
}
|
||||
// Noise changes every frame: the frames differ.
|
||||
assert!(prev.as_ref() != Some(&img.data), "frame {k} repeats the previous one");
|
||||
prev = Some(img.data);
|
||||
check(&format!("noise adjustment frame {k}"), compare_at(&s, opts(), t(k)), 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,8 +423,10 @@ fn hash_debug(h: &mut KeyHasher, v: &impl std::fmt::Debug) {
|
||||
|
||||
/// Time-based layer content: does any part of the source/effects read the clock directly
|
||||
/// (rather than through property values)?
|
||||
fn time_dependent(layer: &Layer) -> bool {
|
||||
if layer.switches.effects
|
||||
/// `effects: false` ignores the effects (a key of the source and masks only).
|
||||
fn time_dependent(layer: &Layer, effects: bool) -> bool {
|
||||
if effects
|
||||
&& layer.switches.effects
|
||||
&& let Some(fx) = layer.effects()
|
||||
{
|
||||
for g in fx.groups() {
|
||||
@@ -522,7 +524,10 @@ fn has_expression(g: &PropGroup) -> bool {
|
||||
/// Cache key for an adjustment layer's footprint (its source through its masks, see
|
||||
/// `Renderer::adjustment_footprint`): the GPU compositor then uploads it once, not every frame.
|
||||
pub fn footprint_key(ctx: &EvalCtx, layer: &Layer, scale: f64, draft: bool, blur: bool) -> Option<u64> {
|
||||
Some(derive(key_any(ctx, layer, scale, draft, blur, false, None)?, 0xf007_9417))
|
||||
// The footprint is the source through the masks, without the effects: a time-dependent
|
||||
// effect (Noise on a finishing adjustment layer) must not re-key it every frame (on the GPU,
|
||||
// a full-frame re-upload per frame).
|
||||
Some(derive(key_any(ctx, layer, scale, draft, blur, false, Some(0))?, 0xf007_9417))
|
||||
}
|
||||
|
||||
fn key_with(ctx: &EvalCtx, layer: &Layer, scale: f64, draft: bool, blur: bool, footage: bool) -> Option<u64> {
|
||||
@@ -620,7 +625,7 @@ fn key_any(ctx: &EvalCtx, layer: &Layer, scale: f64, draft: bool, blur: bool, fo
|
||||
effectcraft_effects::paint::cache_key(g, lt).0.hash(&mut h);
|
||||
}
|
||||
}
|
||||
if time_dependent(layer) {
|
||||
if time_dependent(layer, effects != Some(0)) {
|
||||
layer.layer_time(ctx.time).0.hash(&mut h);
|
||||
// Effects also see the layer's time mapping.
|
||||
layer.start_time.0.hash(&mut h);
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ impl Dependencies<'_> {
|
||||
hash_debug(self.h, layer);
|
||||
hash_values(self.h, ctx, layer, &layer.props);
|
||||
hash_debug(self.h, &ctx.world_matrix(layer));
|
||||
if super::time_dependent(layer) {
|
||||
if super::time_dependent(layer, true) {
|
||||
layer.layer_time(ctx.time).0.hash(self.h);
|
||||
}
|
||||
match layer.source {
|
||||
|
||||
Reference in New Issue
Block a user