multicam: throttle angle refresh, shrink pre-render window
- MulticamPanel: playback angle refresh runs one cycle per 3 ticks instead of re-requesting every source every tick — each angle decode is a keyframe-scanning FFmpeg seek that stole worker capacity from the main viewer - PreRender frames default 120 -> 12: a window larger than what the pool can render in real time queues far ahead of the playhead, so the painted frame lags seconds behind (playback frozen); a smaller window keeps the backlog bounded - render_graph_frame: OAK_PERF clip-level timing
This commit is contained in:
@@ -246,8 +246,14 @@ enum FullResTarget {
|
||||
// that fall out of the window (or whose params were invalidated) release
|
||||
// their slots back to the workers.
|
||||
|
||||
/// The default forward pre-render window (frames).
|
||||
pub const DEFAULT_PREVIEW_WINDOW_FORWARD: i64 = 120;
|
||||
/// The default forward pre-render window (frames). Kept small: each
|
||||
/// frame is slow on some hardware (multi-clip montage decodes through
|
||||
/// FFmpeg), and a large window queues far ahead of the playhead — the
|
||||
/// workers never catch up, the painted frame lags seconds behind the
|
||||
/// crown and playback looks frozen. A shorter window bounds the backlog
|
||||
/// to what the pool can render in a fraction of a second, so the
|
||||
/// displayed frame tracks the playhead instead of chasing it.
|
||||
pub const DEFAULT_PREVIEW_WINDOW_FORWARD: i64 = 12;
|
||||
/// Config key for the forward pre-render window size (frames).
|
||||
pub const CONFIG_KEY_PREVIEW_WINDOW: &str = "PlaybackPreRenderFrames";
|
||||
|
||||
|
||||
@@ -61,6 +61,13 @@ use crate::panels::chip;
|
||||
/// The number of sources refreshed per tick during playback (the rest keep
|
||||
/// their last frame until their turn — the task's round-robin throttle).
|
||||
const PLAYBACK_REFRESH_PER_TICK: i32 = 2;
|
||||
/// A refresh cycle runs once every this many ticks (a tick is one frame
|
||||
/// at the sequence rate). Decoding one angle is an FFmpeg keyframe-
|
||||
/// scanning seek (~10-50 ms even on a 720p proxy); re-requesting ALL
|
||||
/// sources EVERY tick made those seek costs compound and starved the
|
||||
/// main viewer's worker pool. The grid still refreshes each source in
|
||||
/// turn, just at a third of the frame rate.
|
||||
const PLAYBACK_REFRESH_STRIDE: i32 = 3;
|
||||
|
||||
/// One pending switch (the C++ `MulticamWidget`'s `play_queue_`): the
|
||||
/// switch applies once the program playhead reaches `target`.
|
||||
@@ -92,6 +99,10 @@ pub struct MulticamPanel<E: AppEngine> {
|
||||
play_queue: VecDeque<QueuedSwitch>,
|
||||
/// Round-robin cursor over the sources (playback refresh throttle).
|
||||
refresh_cursor: i32,
|
||||
/// Tick counter for the playback refresh cycle (stride-scaled).
|
||||
refresh_tick_toggle: u64,
|
||||
/// The refresh cycle seen last (dedups re-requests within a cycle).
|
||||
last_refresh_cycle: i64,
|
||||
/// The last program playhead (detects a jump / rest).
|
||||
last_playhead: i64,
|
||||
/// Set when a switch cleared the frames: the next refresh pass covers
|
||||
@@ -121,6 +132,8 @@ impl<E: AppEngine> MulticamPanel<E> {
|
||||
frames: HashMap::new(),
|
||||
play_queue: VecDeque::new(),
|
||||
refresh_cursor: 0,
|
||||
refresh_tick_toggle: 0,
|
||||
last_refresh_cycle: -1,
|
||||
last_playhead: i64::MIN,
|
||||
full_refresh: true,
|
||||
focus: _cx.focus_handle(),
|
||||
@@ -177,12 +190,21 @@ impl<E: AppEngine> MulticamPanel<E> {
|
||||
}
|
||||
self.refresh_cursor = 0;
|
||||
} else {
|
||||
// Playback: round-robin a couple of sources per tick.
|
||||
for _ in 0..state.source_count.min(PLAYBACK_REFRESH_PER_TICK) {
|
||||
let source = self.refresh_cursor % state.source_count;
|
||||
self.refresh_cursor += 1;
|
||||
self.request_angle(source, cx);
|
||||
// Playback: one refresh cycle per PLAYBACK_REFRESH_STRIDE
|
||||
// ticks, round-robin a couple of sources per cycle (the old
|
||||
// per-tick round-robin requested decodes faster than they can
|
||||
// complete — each is a keyframe-scanning seek and stole the
|
||||
// main viewer's worker capacity).
|
||||
let cycle = (self.refresh_tick_toggle / PLAYBACK_REFRESH_STRIDE as u64) as i64;
|
||||
if cycle != self.last_refresh_cycle {
|
||||
self.last_refresh_cycle = cycle;
|
||||
for _ in 0..state.source_count.min(PLAYBACK_REFRESH_PER_TICK) {
|
||||
let source = self.refresh_cursor % state.source_count;
|
||||
self.refresh_cursor += 1;
|
||||
self.request_angle(source, cx);
|
||||
}
|
||||
}
|
||||
self.refresh_tick_toggle = self.refresh_tick_toggle.wrapping_add(1);
|
||||
}
|
||||
self.full_refresh = false;
|
||||
self.last_playhead = playhead;
|
||||
|
||||
@@ -1304,11 +1304,21 @@ pub fn render_graph_frame(
|
||||
let mut perf_collect = perf.then(std::time::Instant::now);
|
||||
let mut perf_collect_ms = 0.0f64;
|
||||
let mut frames: Vec<Frame> = Vec::new();
|
||||
for clip in clips {
|
||||
let mut perf_clip_hist: Vec<(oak_node::id::NodeId, f64)> = Vec::new();
|
||||
for clip in clips.clone() {
|
||||
let clip_sw = perf.then(std::time::Instant::now);
|
||||
let request = oak_node::traverser::EvalRequest::new(clip, time);
|
||||
let table = traverser.evaluate(graph, &request, &mut hooks).map_err(|e| {
|
||||
Error::Failed(format!("graph evaluation of clip {clip:?} failed: {e:?}"))
|
||||
})?;
|
||||
if perf {
|
||||
perf_clip_hist.push((
|
||||
clip,
|
||||
clip_sw
|
||||
.map(|t| t.elapsed().as_secs_f64() * 1000.0)
|
||||
.unwrap_or(0.0),
|
||||
));
|
||||
}
|
||||
if let Some(t0) = &perf_collect {
|
||||
perf_collect_ms += t0.elapsed().as_secs_f64() * 1000.0;
|
||||
perf_collect = Some(std::time::Instant::now());
|
||||
@@ -1337,6 +1347,9 @@ pub fn render_graph_frame(
|
||||
let composite_ms = composite_started
|
||||
.map(|t| t.elapsed().as_secs_f64() * 1000.0)
|
||||
.unwrap_or(0.0);
|
||||
for (clip, ms) in &perf_clip_hist {
|
||||
eprintln!("[perf] clip {clip:?} evaluate {ms:.1}ms");
|
||||
}
|
||||
eprintln!(
|
||||
"[perf] graph frame time {time:?} size {size:?}: evaluate {perf_collect_ms:.1}ms composite {composite_ms:.1}ms total {}ms",
|
||||
perf_collect_ms + composite_ms
|
||||
|
||||
Reference in New Issue
Block a user