From ac5799bd260fe7539f1fd019a4f29ef49e8a2cf3 Mon Sep 17 00:00:00 2001 From: ded Date: Tue, 20 Oct 2026 11:43:30 +0300 Subject: [PATCH] =?UTF-8?q?G5:=20the=20playing=20prefetch=20window=20start?= =?UTF-8?q?s=20at=20the=20first=20uncached=20frame,=20ahead=20=C3=97=203?= =?UTF-8?q?=20wide?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Looking up to the whole RAM preview for uncached frames let the workers spread over the preview while the playhead waited. On the GPU each worker then built a Classic 3D atlas for its own set of layers, and on a cold start of the #1812 project the device ran out of memory far more often (first pass 1-56 % of real time instead of 63-81 %). The window now keeps its old width but starts at the first frame ahead that isn't cached, so a loop or a restart still renders on at once. Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/lib.rs | 44 +++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 541081af..2762188a 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -1238,34 +1238,34 @@ impl EffectcraftApp { } let step = plan.map_or(1, |p| p.step); // Never further ahead than the RAM preview holds: those frames would evict the ones the - // preview shows first, or be evicted before it gets there (#595). A playing preview - // looks that far for the next uncached frames: after a loop the frames just ahead are - // cached, and a shorter window left the workers idle until the playhead came near the - // end of the cached run. + // preview shows first, or be evicted before it gets there (#595). The window starts at the + // first uncached frame ahead: after a loop or a restart the frames just ahead are cached, + // and a window from the playhead left the workers idle until it came near the end of the + // cached run. It stays ahead × 3 frames wide: workers spread over the whole preview render + // frames with different layers at once (on the GPU, an atlas each: #1812). let fit_ahead = self.frames_that_fit(&c, scale) - 1; - let window = match plan.filter(|_| self.playback.playing) { - Some(p) => fit_ahead.min((p.end - p.start) / p.step.max(1) + 1), - None => (ahead * 3).min(fit_ahead), + let frame = |k: i64| match plan { + Some(p) => p.frame_after(cur, k), + None => { + let f = cur + k; + Some(if f > wb { wa + (f - wb - 1).rem_euclid((wb - wa + 1).max(1)) } else { f }) + } }; let cached = self.frames.cached_frames(&series); - for k in 0..window { + let uncached = |f: i64| cached.binary_search(&f).is_err(); + let first = match plan.filter(|_| self.playback.playing) { + Some(p) => (0..fit_ahead.min((p.end - p.start) / p.step.max(1) + 1)) + .map_while(|k| frame(k).map(|f| (k, f))) + .find(|&(_, f)| uncached(f)) + .map_or(0, |(k, _)| k), + None => 0, + }; + for k in first..(first + ahead * 3).min(fit_ahead) { if queued >= ahead as usize || !video { break; } - let f = match plan { - Some(p) => match p.frame_after(cur, k) { - Some(f) => f, - None => break, - }, - None => { - let mut f = cur + k; - if f > wb { - f = wa + (f - wb - 1).rem_euclid((wb - wa + 1).max(1)); - } - f - } - }; - if cached.binary_search(&f).is_err() { + let Some(f) = frame(k) else { break }; + if uncached(f) { self.request_frame(cid, f, scale); queued += 1; }