G5: a reduced still's cache key includes its nominal size (#1812)

Review: two footage items of one file with different nominal sizes could share a sized entry,
though one is resampled and the other kept as decoded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ded
2026-10-20 10:42:22 +03:00
co-authored by Claude Opus 5.5
parent e14cdd6009
commit 4cf6580b19
2 changed files with 12 additions and 5 deletions
+8 -2
View File
@@ -45,6 +45,9 @@ struct Key {
matte: [u32; 3],
/// A movie frame made at this size for a reduced-resolution render ((0, 0): as decoded).
size: (u32, u32),
/// A still at a reduced `size`: the footage's nominal size, which decides whether it is
/// resampled (see `Inner::still_at_size`); (0, 0) otherwise.
nominal: (u32, u32),
}
struct Entry {
@@ -678,7 +681,7 @@ impl Inner {
FloatCurve::Gamma24 => 2,
} << 2);
let matte = if footage.alpha == AlphaMode::Premultiplied { footage.premul_color.map(f32::to_bits) } else { [0; 3] };
let key = |path: &str, frame| Key { path: path.into(), frame, interp, matte, size: (0, 0) };
let key = |path: &str, frame| Key { path: path.into(), frame, interp, matte, size: (0, 0), nominal: (0, 0) };
match footage.kind {
FootageKind::Sequence if !footage.sequence.is_empty() => {
let i = Self::frame_index(footage.frame_rate, t, footage.sequence_frames(), footage.loop_count);
@@ -719,6 +722,9 @@ impl Inner {
// A still asked for at its own size is the unsized frame (one decode, one entry).
if let Some(size) = size.filter(|s| movie || *s != (footage.width, footage.height)) {
loc.key.size = size;
if !movie {
loc.key.nominal = (footage.width, footage.height);
}
}
let mut c = lock(&self.cache);
// sequential playback of a movie: read the next frame ahead
@@ -1074,7 +1080,7 @@ mod tests {
fn lru_respects_budget_and_recycles() {
let mut c = Lru::default();
let img = || Arc::new(Image::new(16, 16)); // 4 KiB + overhead
let k = |i| Key { path: "a".into(), frame: i, interp: 0, matte: [0; 3], size: (0, 0) };
let k = |i| Key { path: "a".into(), frame: i, interp: 0, matte: [0; 3], size: (0, 0), nominal: (0, 0) };
let budget = 3 * (4096 + 64);
for i in 0..10 {
c.insert(k(i), img(), budget);
+4 -3
View File
@@ -610,11 +610,12 @@ fn stills_at_reduced_sizes_are_resampled_once() {
}
// Pixels larger than the nominal size (a smart object's) are resampled as the renderer does;
// smaller ones are left as they are (the renderer scales the layer up instead).
// One pool, one file, one requested size: the two decisions don't share a cache entry.
let larger = Footage { width: 306, height: 48, ..f.clone() };
let sized = MediaPool::new().frame_at_size(ItemId(1), &larger, Tick::ZERO, 153, 24).expect("frame");
assert_eq!(*sized, effectcraft_raster::resample(&full, 153, 24));
let smaller = Footage { width: 1226, height: 194, ..f.clone() };
let kept = MediaPool::new().frame_at_size(ItemId(1), &smaller, Tick::ZERO, 306, 48).expect("frame");
let sized = pool.frame_at_size(ItemId(2), &larger, Tick::ZERO, 153, 24).expect("frame");
assert_eq!(*sized, effectcraft_raster::resample(&full, 153, 24));
let kept = pool.frame_at_size(ItemId(3), &smaller, Tick::ZERO, 153, 24).expect("frame");
assert_eq!(*kept, *full);
let _ = std::fs::remove_dir_all(&dir);
}