Phase 4: prefetch upcoming images during playback
`assets_needed_at(document, time)` (core) enumerates the image asset ids referenced by the visible vector layers' active keyframes at a time (top-level + group children). During playback the stage decodes the images needed ~0.5s ahead into the bounded ImageCache, so a keyframe that swaps image fills doesn't hitch when the playhead reaches it. Gated on is_playing; nested clip-instance recursion + background decode are refinements. Completes Phase 4 (image asset paging: Tier 2 decoded-cache LRU, Tier 1 lazy bytes, playback prefetch).
This commit is contained in:
parent
3d0a334014
commit
0883c77e2b
|
|
@ -158,6 +158,28 @@ impl Default for ImageCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Image asset ids referenced by the visible vector layers' active keyframes at `time`
|
||||||
|
/// (top-level + group children). Used to prefetch/decode images ahead during playback.
|
||||||
|
/// (Recursing into nested clip instances is a refinement.)
|
||||||
|
pub fn assets_needed_at(document: &Document, time: f64) -> Vec<Uuid> {
|
||||||
|
let mut ids = Vec::new();
|
||||||
|
for layer in document.all_layers() {
|
||||||
|
if let crate::layer::AnyLayer::Vector(vl) = layer {
|
||||||
|
if !vl.layer.visible {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(kf) = vl.keyframe_at(time) {
|
||||||
|
for fill in &kf.graph.fills {
|
||||||
|
if let Some(id) = fill.image_fill {
|
||||||
|
ids.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ids
|
||||||
|
}
|
||||||
|
|
||||||
/// Decode image bytes to a premultiplied tiny-skia Pixmap (CPU render path).
|
/// Decode image bytes to a premultiplied tiny-skia Pixmap (CPU render path).
|
||||||
fn decode_image_to_pixmap(data: &[u8]) -> Option<tiny_skia::Pixmap> {
|
fn decode_image_to_pixmap(data: &[u8]) -> Option<tiny_skia::Pixmap> {
|
||||||
let img = image::load_from_memory(data).ok()?;
|
let img = image::load_from_memory(data).ok()?;
|
||||||
|
|
|
||||||
|
|
@ -439,6 +439,8 @@ struct VelloRenderContext {
|
||||||
onion: crate::panes::OnionSkinSettings,
|
onion: crate::panes::OnionSkinSettings,
|
||||||
/// `.beam` container path for lazily paging image-asset bytes in the ImageCache.
|
/// `.beam` container path for lazily paging image-asset bytes in the ImageCache.
|
||||||
container_path: Option<std::path::PathBuf>,
|
container_path: Option<std::path::PathBuf>,
|
||||||
|
/// Whether playback is active (gates image prefetch).
|
||||||
|
is_playing: bool,
|
||||||
/// Active layer for tool operations
|
/// Active layer for tool operations
|
||||||
active_layer_id: Option<uuid::Uuid>,
|
active_layer_id: Option<uuid::Uuid>,
|
||||||
/// Delta for drag preview (world space)
|
/// Delta for drag preview (world space)
|
||||||
|
|
@ -1047,6 +1049,22 @@ impl egui_wgpu::CallbackTrait for VelloCallback {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prefetch: during playback, decode images the upcoming frames will need
|
||||||
|
// (a short lookahead) into the bounded cache, so a keyframe that swaps image
|
||||||
|
// fills doesn't hitch when the playhead reaches it.
|
||||||
|
if self.ctx.is_playing {
|
||||||
|
const PREFETCH_LOOKAHEAD: f64 = 0.5;
|
||||||
|
let ahead = lightningbeam_core::renderer::assets_needed_at(
|
||||||
|
&self.ctx.document,
|
||||||
|
self.ctx.playback_time + PREFETCH_LOOKAHEAD,
|
||||||
|
);
|
||||||
|
for id in ahead {
|
||||||
|
if let Some(asset) = self.ctx.document.get_image_asset(&id) {
|
||||||
|
let _ = image_cache.get_or_decode(asset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
drop(image_cache);
|
drop(image_cache);
|
||||||
let _t_after_scene_build = std::time::Instant::now();
|
let _t_after_scene_build = std::time::Instant::now();
|
||||||
|
|
||||||
|
|
@ -12078,6 +12096,7 @@ impl PaneRenderer for StagePane {
|
||||||
tool_state: shared.tool_state.clone(),
|
tool_state: shared.tool_state.clone(),
|
||||||
onion: shared.onion,
|
onion: shared.onion,
|
||||||
container_path: shared.container_path.clone(),
|
container_path: shared.container_path.clone(),
|
||||||
|
is_playing: *shared.is_playing,
|
||||||
active_layer_id: *shared.active_layer_id,
|
active_layer_id: *shared.active_layer_id,
|
||||||
drag_delta,
|
drag_delta,
|
||||||
selection: shared.selection.clone(),
|
selection: shared.selection.clone(),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue