mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-26 02:57:34 -06:00
Merge #1920: Preview: keep rendering past the cached frames after a loop or restart (@ded-achtung)
This commit is contained in:
+25
-17
@@ -1238,26 +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).
|
||||
let window = (ahead * 3).min(self.frames_that_fit(&c, scale) - 1);
|
||||
for k in 0..window {
|
||||
// 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 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);
|
||||
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 !self.frames.is_cached(&FrameKey { frame: f, ..series }) {
|
||||
let Some(f) = frame(k) else { break };
|
||||
if uncached(f) {
|
||||
self.request_frame(cid, f, scale);
|
||||
queued += 1;
|
||||
}
|
||||
|
||||
@@ -259,6 +259,45 @@ fn cache_frames_when_idle_fills_the_work_area() {
|
||||
assert_eq!(n, 240, "the work area is cached while idle");
|
||||
}
|
||||
|
||||
/// A preview that plays into frames it already cached (after a loop) keeps rendering the next
|
||||
/// uncached ones: the prefetch window used to end within the cached run, and the workers stood
|
||||
/// idle until the playhead came near its end.
|
||||
#[test]
|
||||
fn playback_prefetches_past_the_cached_frames_ahead() {
|
||||
let mut h = harness();
|
||||
let cid = h.state().session.active_comp_id().unwrap();
|
||||
let series = h.state().shown_series(cid);
|
||||
let scale = series.scale as f64 / 1000.0;
|
||||
// The first 120 frames (5 s) are cached: more than the widest old window (16 workers × 6).
|
||||
for f in 0..120 {
|
||||
h.state().request_frame(cid, f, scale);
|
||||
}
|
||||
for _ in 0..4000 {
|
||||
if h.state().frames.cached_frames(&series).len() >= 120 && h.state().frames.inflight() == 0 {
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(2));
|
||||
}
|
||||
assert_eq!(h.state().frames.cached_frames(&series), (0..120).collect::<Vec<_>>());
|
||||
h.state_mut().session.set_time(Tick::ZERO);
|
||||
invoke(&mut h, "playback.toggle", json!({}));
|
||||
h.run_steps(2);
|
||||
assert!(h.state().playback.playing);
|
||||
let cur = h.state().session.time();
|
||||
assert!(cur < Tick::from_seconds_f64(1.0), "{cur:?}");
|
||||
for _ in 0..4000 {
|
||||
if h.state().frames.cached_frames(&series).iter().any(|&f| f >= 120) {
|
||||
break;
|
||||
}
|
||||
h.step();
|
||||
std::thread::sleep(std::time::Duration::from_millis(1));
|
||||
if h.state().session.time() >= Tick::from_seconds_f64(1.0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert!(h.state().frames.cached_frames(&series).iter().any(|&f| f >= 120), "frames past the cached run render while it plays the first second");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cache_frames_when_idle_survives_an_inverted_work_area() {
|
||||
let mut h = harness();
|
||||
|
||||
Reference in New Issue
Block a user