G5: the playing prefetch window starts at the first uncached frame, ahead × 3 wide

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 <noreply@anthropic.com>
This commit is contained in:
ded
2026-10-20 11:43:30 +03:00
co-authored by Claude Opus 5.5
parent ff20f8ec41
commit ac5799bd26
+22 -22
View File
@@ -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;
}