mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-11 00:14:41 +00:00
Preview: playback steps down a resolution when it can't keep up
Settings > Previews > Lower Resolution When Playback Can't Keep Up (on by default): a preview at Resolution Auto that stalls on uncached frames and shows under 80 % of its frame rate for two seconds plays on at half the scale, and two seconds later at a quarter if still slow, no lower than the Adaptive Resolution Limit. Stopping goes back to the viewer's resolution and Cache Frames When Idle fills it in. An explicit Resolution, the comp's or the Preview panel's, is kept as set. First playback of a 1080p comp with a blur (cache_bench --play): 9.0 to 22.1 frames/s on the CPU (about 27 once at Half), 4.1 to 16.1 with Mercury Auto on an integrated GPU; plain clips that keep up stay at Full.
This commit is contained in:
@@ -170,6 +170,7 @@ pub fn pages() -> Vec<Page> {
|
||||
title: "Previews",
|
||||
items: vec![
|
||||
s("previews.adaptiveResolutionLimit", "Adaptive Resolution Limit", Kind::Choice(ON_OFF_RES), true),
|
||||
s("previews.adaptivePlayback", "Lower Resolution When Playback Can't Keep Up", B, true),
|
||||
s("previews.showInternalWireframes", "Show Internal Wireframes", B, true),
|
||||
s("previews.cacheFramesWhenIdle", "Cache Frames When Idle", B, true),
|
||||
s("previews.fastPreviews", "Fast Previews (Draft 3D, Faster Effects)", B, true),
|
||||
|
||||
@@ -92,6 +92,9 @@ page!(CompositionPrefs {
|
||||
page!(Previews {
|
||||
/// Largest downsampling while playing back: `1/2`, `1/4`, `1/8`.
|
||||
adaptive_resolution_limit: String = "1/8".into(),
|
||||
/// A preview at Resolution Auto that renders slower than real time steps down a resolution
|
||||
/// (½, then ¼, no further than the Adaptive Resolution Limit) for the rest of that play.
|
||||
adaptive_playback: bool = true,
|
||||
show_internal_wireframes: bool = false,
|
||||
cache_frames_when_idle: bool = true,
|
||||
fast_previews: bool = false,
|
||||
|
||||
@@ -35,6 +35,7 @@ impl EffectcraftApp {
|
||||
plan: Some(plan),
|
||||
caching: plan.cache_first && plan.video,
|
||||
res: preset.resolution.factor(preset.custom_resolution),
|
||||
adaptive: None,
|
||||
overlays: preset.include_overlays,
|
||||
layer_controls: preset.include_layer_controls,
|
||||
restore_max,
|
||||
|
||||
@@ -340,6 +340,8 @@ impl EffectcraftApp {
|
||||
self.session.set_time(fr.tick_of(f));
|
||||
self.playback.frame_shown(now);
|
||||
}
|
||||
// Dropping frames: a lower resolution may keep up (the rate shown says so).
|
||||
self.step_down(now, pass.scale);
|
||||
}
|
||||
ctx.request_repaint();
|
||||
true
|
||||
@@ -421,6 +423,7 @@ impl EffectcraftApp {
|
||||
} else {
|
||||
// Not cached yet: hold the clock at the current frame (cache first, then play).
|
||||
self.playback.waiting = true;
|
||||
self.step_down(now, pass.scale);
|
||||
let next = plan.and_then(|p| p.frame_after(cur, 1)).unwrap_or(if cur + 1 > wb { wa } else { cur + 1 });
|
||||
if self.frames.is_cached(&FrameKey { frame: next, ..series }) {
|
||||
self.session.set_time(fr.tick_of(next));
|
||||
@@ -431,6 +434,31 @@ impl EffectcraftApp {
|
||||
ctx.request_repaint();
|
||||
}
|
||||
|
||||
/// Settings ▸ Previews ▸ Lower Resolution When Playback Can't Keep Up: a preview at
|
||||
/// Resolution Auto stalled on a frame that isn't cached and shows under 80 % of its frame
|
||||
/// rate two seconds after it started or last stepped down (the first frames at a new scale,
|
||||
/// and the GPU's warm-up frames, take a moment), so it plays on at half `scale`, no lower than
|
||||
/// the Adaptive Resolution Limit. An explicit Resolution is kept.
|
||||
// ponytail: it never steps back up during a play, and frames cached at the higher scale aren't
|
||||
// shown once it stepped down; add both if loops over a partly cached comp need them.
|
||||
fn step_down(&mut self, now: f64, scale: f64) {
|
||||
let pb = &self.playback;
|
||||
let since = pb.adaptive.map_or(pb.starting_since, |(_, t)| t);
|
||||
let step = pb.plan.map_or(1, |p| p.step).max(1) as f64;
|
||||
let lower = scale * 0.5;
|
||||
if !self.session.prefs.previews.adaptive_playback
|
||||
|| pb.res.is_some()
|
||||
|| !self.viewer_res().is_auto()
|
||||
|| now - since < 2.0
|
||||
|| pb.achieved_fps().is_some_and(|f| f * step >= pb.fps * 0.8)
|
||||
|| lower < self.session.prefs.adaptive_limit() - 1e-9
|
||||
{
|
||||
return;
|
||||
}
|
||||
self.playback.adaptive = Some((lower, now));
|
||||
self.playback.shown_at.clear();
|
||||
}
|
||||
|
||||
/// While the project keeps changing (an edit within [`EDIT_QUIET`] seconds), how long until
|
||||
/// it counts as settled.
|
||||
fn editing(&mut self, now: f64) -> Option<f64> {
|
||||
|
||||
@@ -98,6 +98,13 @@ impl EffectcraftApp {
|
||||
// play back, and frames cached by a preview stay on screen when it stops.
|
||||
let res = self.viewer_res();
|
||||
let full = res.scale(zoom, ppp);
|
||||
// A preview that couldn't keep up stepped down (`step_down`).
|
||||
if self.playback.playing
|
||||
&& res.is_auto()
|
||||
&& let Some((s, _)) = self.playback.adaptive
|
||||
{
|
||||
return full.min(s);
|
||||
}
|
||||
// Fast Previews ▸ Adaptive Resolution / Fast Draft: lower resolution while dragging.
|
||||
let (_, k) = self.session.state.viewer.fast_previews.render(self.ui.viewer.interacting || self.ui.viewer.property_interacting);
|
||||
if k < 1.0 && res.is_auto() {
|
||||
|
||||
@@ -190,6 +190,9 @@ pub struct Playback {
|
||||
pub caching: bool,
|
||||
/// Resolution override while playing (pixel step), `None` = the viewer's.
|
||||
pub res: Option<u32>,
|
||||
/// Settings ▸ Previews ▸ Lower Resolution When Playback Can't Keep Up: the lower render
|
||||
/// scale this play stepped down to, and when (seconds). See `step_down`.
|
||||
pub adaptive: Option<(f64, f64)>,
|
||||
/// Include Overlays / Include Layer Controls.
|
||||
pub overlays: bool,
|
||||
pub layer_controls: bool,
|
||||
|
||||
@@ -316,3 +316,53 @@ fn cache_frames_when_idle_survives_an_inverted_work_area() {
|
||||
}
|
||||
assert!(!h.state().playback.playing);
|
||||
}
|
||||
|
||||
/// Settings ▸ Previews ▸ Lower Resolution When Playback Can't Keep Up: a preview at Auto that
|
||||
/// stalls for two seconds steps down to half its scale, two more to a quarter; an explicit Resolution and
|
||||
/// the pref turned off keep the scale, and stopping goes back to the viewer's.
|
||||
#[test]
|
||||
fn playback_that_cannot_keep_up_steps_down_a_resolution() {
|
||||
let stall = |h: &mut Harness<'_, EffectcraftApp>, secs: f64| {
|
||||
for _ in 0..(secs / 0.1).round() as usize {
|
||||
h.state_mut().clock_mut().advance(0.1);
|
||||
h.step();
|
||||
}
|
||||
};
|
||||
let play = |h: &mut Harness<'_, EffectcraftApp>| {
|
||||
h.state_mut().use_manual_clock();
|
||||
invoke(h, "playback.toggle", json!({}));
|
||||
h.step();
|
||||
assert!(h.state().playback.playing);
|
||||
};
|
||||
let mut h = harness();
|
||||
// No room in the RAM preview: frames never stay cached and every pass stalls, as with a comp
|
||||
// too heavy to render in real time.
|
||||
h.state().frames.set_budget(0);
|
||||
play(&mut h);
|
||||
stall(&mut h, 1.5);
|
||||
assert_eq!(h.state().viewer_scale(1.0, 1.0), 1.0, "it waits two seconds before it steps down");
|
||||
stall(&mut h, 0.7);
|
||||
assert_eq!(h.state().viewer_scale(1.0, 1.0), 0.5);
|
||||
stall(&mut h, 1.5);
|
||||
assert_eq!(h.state().viewer_scale(1.0, 1.0), 0.5, "and two more before the next step");
|
||||
stall(&mut h, 0.7);
|
||||
assert_eq!(h.state().viewer_scale(1.0, 1.0), 0.25);
|
||||
invoke(&mut h, "playback.toggle", json!({}));
|
||||
assert!(!h.state().playback.playing);
|
||||
assert_eq!(h.state().viewer_scale(1.0, 1.0), 1.0, "stopped: the viewer's resolution again");
|
||||
|
||||
// An explicit Resolution is kept.
|
||||
let cid = h.state().session.active_comp_id().unwrap();
|
||||
std::sync::Arc::make_mut(&mut h.state_mut().session.project).comp_mut(cid).unwrap().resolution = effectcraft_engine::project::Resolution::Full;
|
||||
play(&mut h);
|
||||
stall(&mut h, 2.5);
|
||||
assert_eq!(h.state().viewer_scale(1.0, 1.0), 1.0);
|
||||
invoke(&mut h, "playback.toggle", json!({}));
|
||||
|
||||
// So is Auto with the pref off.
|
||||
std::sync::Arc::make_mut(&mut h.state_mut().session.project).comp_mut(cid).unwrap().resolution = effectcraft_engine::project::Resolution::Auto;
|
||||
h.state_mut().session.prefs.previews.adaptive_playback = false;
|
||||
play(&mut h);
|
||||
stall(&mut h, 2.5);
|
||||
assert_eq!(h.state().viewer_scale(1.0, 1.0), 1.0);
|
||||
}
|
||||
|
||||
+7
-1
@@ -52,7 +52,13 @@ Commands (CLI, MCP, control channel):
|
||||
- `autoSave.saveOnRenderStart`: Save When Starting Render Queue
|
||||
- `composition.showRenderingProgress`: Show Rendering Progress in Info Panel and Flowchart
|
||||
- `previews.adaptiveResolutionLimit`: Adaptive Resolution Limit (the lowest resolution Fast Previews ▸
|
||||
Adaptive Resolution drops to while you drag; a playing preview keeps the viewer's resolution)
|
||||
Adaptive Resolution drops to while you drag, and the lowest a playing preview steps down to)
|
||||
- `previews.adaptivePlayback`: Lower Resolution When Playback Can't Keep Up. On by default. A
|
||||
preview at Resolution Auto that stalls on frames and shows under 80 % of its frame rate steps
|
||||
down a resolution (half the scale after two seconds, a quarter two seconds later if still slow,
|
||||
no lower than the Adaptive Resolution Limit) for the rest of that play; stopping goes back to the viewer's resolution, and Cache
|
||||
Frames When Idle fills it in. An explicit Resolution (the comp's or the Preview panel's) is
|
||||
kept as set
|
||||
- `previews.cacheFramesWhenIdle`: Cache Frames When Idle (Composition ▸ Preview): after a second
|
||||
without input or edits, the viewer renders the work area into the RAM preview in the
|
||||
background, from the current time on, until it is cached or the budget is full. On by default,
|
||||
|
||||
Reference in New Issue
Block a user