From 37a7076daff8bc9b54213e670e5c54599defb82b Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Thu, 12 Nov 2026 15:31:18 -0800 Subject: [PATCH] Images: count a JPEG cached at the reduced levels it is drawn at as cached A JPEG drawn small is decoded at a reduced level and has no full-size entry, so every later render queued it again and started decode threads that found it cached. --- crates/render/src/images.rs | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/crates/render/src/images.rs b/crates/render/src/images.rs index 16890b591..1524cb31c 100644 --- a/crates/render/src/images.rs +++ b/crates/render/src/images.rs @@ -282,6 +282,18 @@ pub(crate) fn warm(wants: Vec) -> usize { } } } + // The reduced JPEG level [`reduced`] takes for each drawing of an asset, when every drawing + // of it takes one. + let targets: HashMap<(usize, u32), Vec> = assets + .iter() + .filter_map(|(key, wanted)| { + let levels = wanted + .iter() + .map(|w| reduced_sizes(&w.bytes, w.page, w.width_pt, w.on_screen).and_then(|s| u8::try_from(s.len() - 1).ok()).filter(|l| *l > 0)) + .collect::>>()?; + Some((*key, levels)) + }) + .collect(); // The bytes each asset in view holds in the cache, when it is cached or failed, and the bytes // of everything else cached. Its levels become the most recently used, so the stores below // evict other images first. @@ -295,8 +307,11 @@ pub(crate) fn warm(wants: Vec) -> usize { *held.entry((k.0, k.1)).or_default() += size_of(&e.pm); } } - // Smaller levels alone don't make an asset cached. - held.retain(|k, _| c.map.contains_key(&(k.0, k.1, 0))); + // Smaller levels alone don't make an asset cached, unless they are the reduced JPEG levels + // every drawing of it takes. + held.retain(|k, _| { + c.map.contains_key(&(k.0, k.1, 0)) || targets.get(k).is_some_and(|ls| ls.iter().all(|l| c.map.contains_key(&(k.0, k.1, *l)))) + }); let in_view = held.values().sum::(); for k in index.keys() { if c.bad.contains_key(k) { @@ -616,6 +631,17 @@ mod tests { Arc::new(out) } + #[test] + fn warm_counts_cached_reduced_jpeg_levels_as_cached() { + // Drawn at a tenth of their pixels, two JPEGs decode at a reduced level only; once that + // level is cached, a later warm has nothing to decode. + let (a, b) = (jpeg(1024, 768), jpeg(1024, 768)); + let small = |bytes: &Arc>| Want { bytes: bytes.clone(), page: 0, width_pt: 1024.0, on_screen: 0.1 }; + assert!(warm(vec![small(&a), small(&b)]) > 0, "decoded ahead"); + assert!(lookup((Arc::as_ptr(&a) as usize, 0, 0)).is_none(), "no full-size decode is kept"); + assert_eq!(warm(vec![small(&a), small(&b)]), 0, "nothing left to decode"); + } + #[test] fn screen_levels_of_a_jpeg_need_no_full_size_decode() { let bytes = jpeg(1024, 768);