diff --git a/crates/engine/src/tests/lut_files.rs b/crates/engine/src/tests/lut_files.rs index 57d163b36..0153a9997 100644 --- a/crates/engine/src/tests/lut_files.rs +++ b/crates/engine/src/tests/lut_files.rs @@ -186,3 +186,43 @@ fn an_overridden_lut_file_renders_again() { write(&overridden, WHITE, 2_000); assert_all(centre(&s), 1.0); } + +/// #3538: OCIO File Transform and a custom OCIO config (and the LUT it names) follow their files +/// through warm layer caches, and a render notes the config's LUT for the disk cache. +#[test] +fn ocio_files_render_again_from_warm_caches() { + let dir = tempfile::tempdir().unwrap(); + let (lut, config, grade) = (dir.path().join("file.cube"), dir.path().join("config.ocio"), dir.path().join("luts/grade.cube")); + std::fs::create_dir_all(dir.path().join("luts")).unwrap(); + write(&lut, BLACK, 1_000); + write(&grade, BLACK, 1_000); + let text = "ocio_profile_version: 2\nsearch_path: luts\nroles:\n scene_linear: lin\ncolorspaces:\n - !\n name: lin\n - !\n name: graded\n to_reference: ! {src: grade.cube, interpolation: linear}\n"; + write(&config, text, 1_000); + let mut s = session(None); + let file = shot(&mut s, "OCIO File Transform", &[("file", json!(lut.to_string_lossy()))]); + let custom = shot( + &mut s, + "OCIO Color Space Transform", + &[("config", json!(1)), ("configFile", json!(config.to_string_lossy())), ("sourceName", json!("graded")), ("destinationName", json!("lin"))], + ); + assert_all(colours(&s, file), 0.0); + assert_all(colours(&s, custom), 0.0); + // (the first key of a config's layer can't know the LUTs it names before it is read) + assert_all(colours(&s, custom), 0.0); + let misses = s.layer_cache.stats().misses; + assert_all(colours(&s, file), 0.0); + assert_all(colours(&s, custom), 0.0); + assert_eq!(s.layer_cache.stats().misses, misses, "unchanged files keep the cached layers"); + let log = ReadLog::new(); + let mut r = s.renderer(RenderOpts::default()); + r.reads = Some(&log); + let _ = r.comp_frame(custom.1, Tick::ZERO); + let files = log.into_files(); + assert!(files.get(&grade.to_string_lossy()).is_some(), "the render notes the config's LUT: {files:?}"); + let generation = effectcraft_effects::lut_generation(); + write(&lut, WHITE, 2_000); + assert_all(colours(&s, file), 1.0); + write(&grade, WHITE, 2_000); + assert_all(colours(&s, custom), 1.0); + assert!(effectcraft_effects::lut_generation() > generation, "the viewer's frames made before are dropped"); +} diff --git a/crates/render/src/cache/dependencies.rs b/crates/render/src/cache/dependencies.rs index 5e8a8845b..6f14ddcde 100644 --- a/crates/render/src/cache/dependencies.rs +++ b/crates/render/src/cache/dependencies.rs @@ -175,11 +175,8 @@ impl Dependencies<'_> { // Properties overrides, which the render applies: a LUT file one of // them names is read too (#3493). for o in crate::fx_host::essential_overrides(ctx, layer) { - if let effectcraft_keyframe::Value::Str(src) = &o.value - && let Some(path) = effectcraft_effects::lut_file(src) - { - crate::reads::ReadFile::new(path, crate::reads::FileStamp::native(path).unwrap_or(crate::reads::FileStamp::Missing)) - .hash(self.h); + if let effectcraft_keyframe::Value::Str(src) = &o.value { + crate::reads::source_files(src).hash(self.h); } } let nested = EvalCtx::new(ctx.project, item, comp, time); diff --git a/crates/render/src/reads.rs b/crates/render/src/reads.rs index 9021f1e42..73b7ce830 100644 --- a/crates/render/src/reads.rs +++ b/crates/render/src/reads.rs @@ -65,20 +65,31 @@ impl ReadFile { } } -/// The LUT files effect `g` of `layer` reads at the context time (Apply Color LUT, Lumetri's -/// Input LUT and Look), each with its identity now. A layer's cache key holds them and a render +/// The files effect `g` of `layer` reads at the context time (Apply Color LUT, Lumetri's Input +/// LUT and Look, OCIO File Transform, a custom OCIO config and its LUTs: see +/// [`effectcraft_effects::lut_params`]), each with its identity now. A layer's cache key holds them and a render /// notes them, so what was made with a file that changed since is not used (#3493). pub(crate) fn lut_files(ctx: &crate::EvalCtx, layer: &effectcraft_project::Layer, g: &effectcraft_project::PropGroup) -> Vec { let effectcraft_project::GroupKind::Effect { effect } = &g.kind else { return Vec::new() }; effectcraft_effects::lut_params(effect) .iter() .filter_map(|param| match ctx.value(layer, g.prop(param)?) { - effectcraft_keyframe::Value::Str(src) => { - let path = effectcraft_effects::lut_file(&src)?; - Some(ReadFile::new(path, FileStamp::native(path).unwrap_or(FileStamp::Missing))) - } + effectcraft_keyframe::Value::Str(src) => Some(source_files(&src)), _ => None, }) + .flatten() + .collect() +} + +/// The files a file parameter set to `src` reads (an OCIO config's also the LUTs it names, #3538), +/// each with its identity now. +pub(crate) fn source_files(src: &str) -> Vec { + effectcraft_effects::lut_files(src) + .into_iter() + .map(|path| { + let stamp = FileStamp::native(&path).unwrap_or(FileStamp::Missing); + ReadFile::new(path, stamp) + }) .collect() }