mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-24 10:09:01 -06:00
M5.6: the pinned original no longer evicts the photo's other sources (64 MP edits decoded the raw again)
MediaCache::enforce_budget never evicts the original of the photo on screen (set_on_screen), but it still counted it. A 64 MP original (Sony A7R VI: 9984×6656 linear float, 760 MiB) nearly fills the default cache share (768 MiB) alone, so the loop evicted everything else instead: the photo's preview and thumbnail sources, the rendered previews and the prefetched neighbours. Every edit at 1:1 (the whole-frame render and the thumbnail), every zoom out and every step to the next photo then decoded the raw again. The pinned original is now left out of what is compared with the budget. The rest still fits the share; the original sits over it, as the first commit of this PR already allowed. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
63bcaa147f
commit
dead4367b0
@@ -491,7 +491,11 @@ impl MediaCache {
|
||||
/// one just decoded) is never evicted here, nor is the original of the photo on screen
|
||||
/// ([`Self::set_on_screen`]).
|
||||
pub fn enforce_budget(&mut self) {
|
||||
let mut held = self.held();
|
||||
// The on-screen original is never evicted here, so it does not count either: counted, a 64 MP original (760 MiB)
|
||||
// nearly fills the default share (768 MiB) alone and everything else went instead — the photo's preview and
|
||||
// thumbnail sources and the neighbours' were decoded again from the raw on every edit, zoom-out and step.
|
||||
let pinned: usize = self.full.iter().filter(|e| Some(e.0) == self.on_screen).map(|e| e.1.bytes()).sum();
|
||||
let mut held = self.held().saturating_sub(pinned);
|
||||
while held > self.budget {
|
||||
let newest_preview = self.previews.iter().map(|e| e.2).max();
|
||||
let candidates = [
|
||||
@@ -1807,6 +1811,24 @@ mod tests {
|
||||
assert!(m.has_source(PhotoId(3), SourceLevel::Full));
|
||||
}
|
||||
|
||||
// Given the on-screen original alone over the budget, the photo's other sources and its neighbour's stay
|
||||
// (counted, it evicted them all: each edit decoded the raw again for the thumbnail and the whole-frame render)
|
||||
#[test]
|
||||
fn the_on_screen_original_does_not_evict_the_other_sources() {
|
||||
let img = |w: usize| Arc::new(Rgb32f::new(w, w));
|
||||
let mut m = MediaCache::default();
|
||||
let mb = 1 << 20;
|
||||
m.set_budget(20 * mb);
|
||||
m.set_on_screen(Some(PhotoId(1)));
|
||||
m.insert(PhotoId(1), SourceLevel::Preview, img(500));
|
||||
m.insert(PhotoId(1), SourceLevel::Thumb, img(200));
|
||||
m.insert(PhotoId(1), SourceLevel::Full, img(1500)); // 27 MB
|
||||
m.insert(PhotoId(2), SourceLevel::Preview, img(500)); // a prefetched neighbour
|
||||
for (id, level) in [(1, SourceLevel::Preview), (1, SourceLevel::Thumb), (1, SourceLevel::Full), (2, SourceLevel::Preview)] {
|
||||
assert!(m.has_source(PhotoId(id), level), "{id} {level:?}");
|
||||
}
|
||||
}
|
||||
|
||||
// A 64 MP original (797 MB as linear float) is bigger than the default cache share (768 MiB):
|
||||
// evicted on insert, every pan of a zoomed view decoded the raw again. The original of the photo
|
||||
// on screen stays, whatever is decoded after it (prefetched neighbours), until another is shown.
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
- A zoomed view keeps the GPU copy of the original while it is on screen: pausing for a few seconds between pans no
|
||||
longer frees it, which cost a re-upload of about a second on the next pan (800 MB for a 64 MP raw). It is freed
|
||||
once you zoom out or move on.
|
||||
- The kept original no longer pushes everything else out of the cache: on a 64 MP raw it filled the cache share
|
||||
alone, so the photo's preview and thumbnail sources and its neighbours' were decoded from the raw again on every
|
||||
edit at 1:1, every zoom out and every step to the next photo.
|
||||
|
||||
## 2026-10-11
|
||||
|
||||
|
||||
Reference in New Issue
Block a user