mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-01 14:08:38 -06:00
#783: Object Matte mattes persist in the disk cache
Problem: computed Object Matte segmentations lived only in the in-memory cache, so every restart or memory eviction segmented the shot again (After Effects 26.5 keeps Object Matte results in its disk cache). Fix: the persistent disk cache gets a third entry kind, Matte (folder `mattes`, LZ4-compressed FrameSeg bytes, same checksum, atomic writes, LRU eviction under the one size limit, purged with the rest). Entries are keyed by the segmentation's chain key and scale; chain keys already hash the Input Key (the layer's source, timing, masks and effects above), the prompt, the mode and the model, so any change simply misses. The effects crate gets a SegStore trait behind its memory cache: Chain::ensure reads from it on a memory miss and writes what it computes. The engine installs the session's DiskCache as the store (held weakly, so switching the disk cache off drops it). Roto Brush segmentations go through the same chain and are persisted too. Frozen mattes already persist in the project (the Frozen parameter). Tests: engine tests_roto::object_matte_mattes_persist_in_the_disk_cache (a second session on the same folder and project gets all tracked frames back without being able to read a single input frame, renders identically, and a moved prompt finds nothing); render disk_cache segmentations_round_trip_and_reopen (round trip, reopen, damaged file is a miss, size limit evicts). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
560767dfe7
commit
3466538d0e
@@ -7,7 +7,8 @@
|
||||
//! out to the frame), so it lives in a process-wide content-keyed cache ([`cached`]) that the
|
||||
//! engine's propagation job fills at full resolution and rendering fills on demand (reading
|
||||
//! neighbouring frames with [`crate::EffectHost::self_at`]). Undo, redo and reopening a project
|
||||
//! therefore never see stale mattes. **Freeze** stores the final mattes in the hidden **Frozen**
|
||||
//! therefore never see stale mattes. Behind the memory cache an optional [`SegStore`] (the
|
||||
//! persistent disk cache, set by the engine) keeps computed segmentations across restarts. **Freeze** stores the final mattes in the hidden **Frozen**
|
||||
//! parameter ([`FrozenCache`]); they are used while their key still matches.
|
||||
//!
|
||||
//! Rendering: binary segmentation → Roto Brush Matte (Shift Edge, Reduce Chatter, Feather,
|
||||
@@ -23,21 +24,21 @@
|
||||
//! higher working resolution with more iterations.
|
||||
|
||||
use std::collections::{BTreeMap, HashMap, VecDeque};
|
||||
use std::sync::{Arc, Mutex, OnceLock, RwLock};
|
||||
use std::sync::{Arc, Mutex, OnceLock, RwLock, Weak};
|
||||
|
||||
use effectcraft_keyframe::Value;
|
||||
use effectcraft_project::ParamUi;
|
||||
use effectcraft_raster::Image;
|
||||
use effectcraft_segment::MaskModel;
|
||||
use effectcraft_track::roto::refine::{self, MatteParams};
|
||||
use effectcraft_track::roto::{self as rb, FrameSeg, RotoData, SegOpts, rle};
|
||||
use effectcraft_track::roto::{self as rb, RotoData, SegOpts, rle};
|
||||
use rayon::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::warp_stab::param;
|
||||
use crate::{Buf, EffectCtx, EffectSpec, Params, num, p, popup, slider};
|
||||
|
||||
pub use effectcraft_track::roto::{Stroke, StrokeKind};
|
||||
pub use effectcraft_track::roto::{FrameSeg, Stroke, StrokeKind};
|
||||
|
||||
/// The effect's id.
|
||||
pub const ID: &str = "ec.matte.rotobrush";
|
||||
@@ -266,6 +267,37 @@ pub fn store(key: u64, scale: f64, seg: Arc<FrameSeg>) {
|
||||
}
|
||||
}
|
||||
|
||||
/// A second-level store for segmentations, keyed like the memory cache (chain key, scale): the
|
||||
/// persistent disk cache, so computed mattes survive the memory cache and restarts.
|
||||
pub trait SegStore: Send + Sync {
|
||||
fn get_seg(&self, key: u64, scale: f64) -> Option<FrameSeg>;
|
||||
fn put_seg(&self, key: u64, scale: f64, seg: &FrameSeg);
|
||||
}
|
||||
|
||||
fn seg_store_slot() -> &'static RwLock<Option<Weak<dyn SegStore>>> {
|
||||
static S: OnceLock<RwLock<Option<Weak<dyn SegStore>>>> = OnceLock::new();
|
||||
S.get_or_init(|| RwLock::new(None))
|
||||
}
|
||||
|
||||
/// Use `store` behind the memory cache (held weakly: once its owner drops it, e.g. the disk
|
||||
/// cache is switched off, it is no longer used). `None` turns it off.
|
||||
pub fn set_seg_store(store: Option<Weak<dyn SegStore>>) {
|
||||
if let Ok(mut s) = seg_store_slot().write() {
|
||||
*s = store;
|
||||
}
|
||||
}
|
||||
|
||||
fn seg_store() -> Option<Arc<dyn SegStore>> {
|
||||
seg_store_slot().read().ok()?.as_ref()?.upgrade()
|
||||
}
|
||||
|
||||
/// A segmentation from the second-level store, put into the memory cache.
|
||||
fn from_store(key: u64, scale: f64) -> Option<Arc<FrameSeg>> {
|
||||
let seg = Arc::new(seg_store()?.get_seg(key, scale)?);
|
||||
store(key, scale, seg.clone());
|
||||
Some(seg)
|
||||
}
|
||||
|
||||
/// Drop the cached segmentations of these chain keys (every scale).
|
||||
pub fn forget(keys: impl IntoIterator<Item = u64>) {
|
||||
let keys: std::collections::HashSet<u64> = keys.into_iter().collect();
|
||||
@@ -325,11 +357,11 @@ impl Chain {
|
||||
let sc = self.opts.scale;
|
||||
let lookup = |g: i64| -> Option<Arc<FrameSeg>> {
|
||||
let k = *self.keys.get(&g)?;
|
||||
if let Some(s) = cached(k, sc) {
|
||||
if let Some(s) = cached(k, sc).or_else(|| from_store(k, sc)) {
|
||||
return Some(s);
|
||||
}
|
||||
if sc != 1.0 {
|
||||
let full = cached(k, 1.0)?;
|
||||
let full = cached(k, 1.0).or_else(|| from_store(k, 1.0))?;
|
||||
let s = Arc::new(downscale(&full, sc));
|
||||
store(k, sc, s.clone());
|
||||
return Some(s);
|
||||
@@ -379,7 +411,11 @@ impl Chain {
|
||||
}
|
||||
};
|
||||
let seg = Arc::new(seg);
|
||||
store(*self.keys.get(&g)?, sc, seg.clone());
|
||||
let k = *self.keys.get(&g)?;
|
||||
store(k, sc, seg.clone());
|
||||
if let Some(st) = seg_store() {
|
||||
st.put_seg(k, sc, &seg);
|
||||
}
|
||||
cur = Some(seg);
|
||||
prev_img = Some(img);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user