mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-30 13:08:07 -06:00
Render: CC Particle World layers with expressions aren't layer cached (#1373)
The simulation checks its checkpoints against the Birth Rate history, but a cached layer is returned before the effect runs. The layer key sees the layer's keyframes and its current values, not what an expression returned at earlier times: a Birth Rate expression reading another layer could change the history while the current rate, 0, stayed the same. effects::reads_param_history names CC Particle World. The renderer already declines to cache layers whose time effects (ec.time.*) meet expressions. It now treats Particle World the same way, in the layer key, the input key (only for the effects that input runs) and the dependency walk.
This commit is contained in:
@@ -1248,6 +1248,13 @@ pub fn is_time_dependent(id: &str) -> bool {
|
||||
id.starts_with("ec.sim.") || TIME_DEPENDENT.contains(&id)
|
||||
}
|
||||
|
||||
/// Does effect `id` read its own parameters at earlier times (CC Particle World samples its
|
||||
/// Birth Rate at every step, #1373)? Caches keyed on the current values can't see what an
|
||||
/// expression returned then.
|
||||
pub fn reads_param_history(id: &str) -> bool {
|
||||
id == "ec.sim.ccparticleworld"
|
||||
}
|
||||
|
||||
/// Run an effect on a buffer.
|
||||
pub fn apply(spec: &EffectSpec, ctx: &EffectCtx, buf: Buf) -> Buf {
|
||||
(spec.render)(ctx, buf)
|
||||
|
||||
@@ -484,10 +484,11 @@ fn hash_values(h: &mut KeyHasher, ctx: &EvalCtx, layer: &Layer, g: &PropGroup) {
|
||||
/// time, or `None` when the layer must not be cached (footage, precomps, cameras, adjustment
|
||||
/// layers…).
|
||||
pub fn layer_key(ctx: &EvalCtx, layer: &Layer, scale: f64, draft: bool, blur: bool) -> Option<u64> {
|
||||
// Time effects see property values at other times. Keyframes are part of the hashed
|
||||
// structure (and the layer time is folded in), but expressions may read other layers at
|
||||
// those times, which the key cannot see: such layers are not cached.
|
||||
if reads_other_times(layer) && layer.props.children.iter().any(|c| matches!(c, Node::Group(g) if g.match_id != "transform" && has_expression(g))) {
|
||||
// Time effects (and CC Particle World's Birth Rate history) see property values at other
|
||||
// times. Keyframes are part of the hashed structure (and the layer time is folded in), but
|
||||
// expressions may read other layers at those times, which the key cannot see: such layers
|
||||
// are not cached.
|
||||
if history_unkeyable(layer, None) {
|
||||
return None;
|
||||
}
|
||||
key_with(ctx, layer, scale, draft, blur, false)
|
||||
@@ -497,7 +498,7 @@ pub fn layer_key(ctx: &EvalCtx, layer: &Layer, scale: f64, draft: bool, blur: bo
|
||||
/// effects (see `Renderer::layer_input`). Unlike [`layer_key`], footage layers are cached here
|
||||
/// (keyed by item and source time), since Time effects read many neighbouring frames.
|
||||
pub fn input_key(ctx: &EvalCtx, layer: &Layer, scale: f64, draft: bool, blur: bool, effects: usize) -> Option<u64> {
|
||||
if layer.switches.adjustment {
|
||||
if layer.switches.adjustment || history_unkeyable(layer, Some(effects)) {
|
||||
return None;
|
||||
}
|
||||
let base = key_any(ctx, layer, scale, draft, blur, true, Some(effects))?;
|
||||
@@ -506,14 +507,26 @@ pub fn input_key(ctx: &EvalCtx, layer: &Layer, scale: f64, draft: bool, blur: bo
|
||||
Some(h.finish())
|
||||
}
|
||||
|
||||
/// Does the layer run an effect that reads the layer at other times (Echo, Timewarp…)?
|
||||
fn reads_other_times(layer: &Layer) -> bool {
|
||||
/// Does the layer run (among its first `effects` effects, all when `None`) an effect that reads
|
||||
/// the layer at other times (Echo, Timewarp…) or its own parameters' history (CC Particle
|
||||
/// World's Birth Rate, #1373)?
|
||||
fn reads_other_times(layer: &Layer, effects: Option<usize>) -> bool {
|
||||
layer.switches.effects
|
||||
&& layer.effects().is_some_and(|fx| {
|
||||
fx.groups().any(|g| g.enabled && matches!(&g.kind, effectcraft_project::GroupKind::Effect { effect } if effect.starts_with("ec.time.")))
|
||||
fx.groups().take(effects.unwrap_or(usize::MAX)).any(|g| {
|
||||
g.enabled
|
||||
&& matches!(&g.kind, effectcraft_project::GroupKind::Effect { effect }
|
||||
if effect.starts_with("ec.time.") || effectcraft_effects::reads_param_history(effect))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// The layer reads values at other times (see [`reads_other_times`]) and has expressions
|
||||
/// (outside Transform), whose results at those times the key can't see.
|
||||
fn history_unkeyable(layer: &Layer, effects: Option<usize>) -> bool {
|
||||
reads_other_times(layer, effects) && layer.props.children.iter().any(|c| matches!(c, Node::Group(g) if g.match_id != "transform" && has_expression(g)))
|
||||
}
|
||||
|
||||
fn has_expression(g: &PropGroup) -> bool {
|
||||
g.children.iter().any(|c| match c {
|
||||
Node::Prop(p) => p.has_expression(),
|
||||
|
||||
+3
-3
@@ -7,7 +7,7 @@ use effectcraft_project::{ItemId, ItemKind, Layer, LayerId, LayerSource, Node, P
|
||||
use std::hash::Hash;
|
||||
|
||||
pub(super) fn hash(h: &mut KeyHasher, ctx: &EvalCtx, layer: &Layer, effects: Option<usize>) -> Option<()> {
|
||||
let mut deps = Dependencies { h, path: vec![(ctx.comp_id, layer.id)], remaining: 1024, other_times: super::reads_other_times(layer) };
|
||||
let mut deps = Dependencies { h, path: vec![(ctx.comp_id, layer.id)], remaining: 1024, other_times: super::reads_other_times(layer, None) };
|
||||
deps.effects(ctx, layer, effects)
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ struct Dependencies<'a> {
|
||||
impl Dependencies<'_> {
|
||||
fn effects(&mut self, ctx: &EvalCtx, layer: &Layer, limit: Option<usize>) -> Option<()> {
|
||||
let previous = self.other_times;
|
||||
self.other_times |= super::reads_other_times(layer);
|
||||
self.other_times |= super::reads_other_times(layer, None);
|
||||
if layer.switches.effects
|
||||
&& let Some(fx) = layer.effects()
|
||||
{
|
||||
@@ -69,7 +69,7 @@ impl Dependencies<'_> {
|
||||
return None;
|
||||
}
|
||||
self.remaining = self.remaining.checked_sub(1)?;
|
||||
if (self.other_times || super::reads_other_times(layer)) && super::has_expression(&layer.props) {
|
||||
if (self.other_times || super::reads_other_times(layer, None)) && super::has_expression(&layer.props) {
|
||||
return None;
|
||||
}
|
||||
self.path.push(id);
|
||||
|
||||
Reference in New Issue
Block a user