Export speed #1: reuse the Vello renderer + ImageCache across frames
The export pump built a fresh `vello::Renderer` (full wgpu pipeline init, ~tens of ms) AND an empty `ImageCache` on every egui repaint — i.e. per output frame. That was the dominant per-frame cost (and the code comment already flagged it). Now the renderer + image cache are built once on the first export pump, reused for every frame, and dropped when the export finishes. Also fixes a latent bug: the throwaway export ImageCache had no container path, so with Phase 4 Tier 1 (lazy image bytes) images stored only in the container wouldn't render in exports. The persistent cache now gets the container path. Audit found the original "per-frame seek" theory was wrong — export decodes the source sequentially; readback is already async/triple-buffered. Remaining wins: cache swscale contexts (rebuilt per frame), GPU YUV conversion, decouple from the repaint cadence.
This commit is contained in:
parent
5f3cb41c18
commit
0a0d7cd0a9
|
|
@ -1072,6 +1072,11 @@ struct EditorApp {
|
||||||
preferences_dialog: preferences::dialog::PreferencesDialog,
|
preferences_dialog: preferences::dialog::PreferencesDialog,
|
||||||
/// Export orchestrator for background exports
|
/// Export orchestrator for background exports
|
||||||
export_orchestrator: Option<export::ExportOrchestrator>,
|
export_orchestrator: Option<export::ExportOrchestrator>,
|
||||||
|
/// Vello renderer + image cache reused across all frames of an in-progress export
|
||||||
|
/// (built once on the first export pump, dropped when export ends) — building a new
|
||||||
|
/// renderer per frame was the dominant export cost.
|
||||||
|
export_renderer: Option<vello::Renderer>,
|
||||||
|
export_image_cache: Option<lightningbeam_core::renderer::ImageCache>,
|
||||||
/// GPU-rendered effect thumbnail generator
|
/// GPU-rendered effect thumbnail generator
|
||||||
effect_thumbnail_generator: Option<EffectThumbnailGenerator>,
|
effect_thumbnail_generator: Option<EffectThumbnailGenerator>,
|
||||||
|
|
||||||
|
|
@ -1333,6 +1338,8 @@ impl EditorApp {
|
||||||
large_media_prompt: None,
|
large_media_prompt: None,
|
||||||
preferences_dialog: preferences::dialog::PreferencesDialog::default(),
|
preferences_dialog: preferences::dialog::PreferencesDialog::default(),
|
||||||
export_orchestrator: None,
|
export_orchestrator: None,
|
||||||
|
export_renderer: None,
|
||||||
|
export_image_cache: None,
|
||||||
effect_thumbnail_generator: None, // Initialized when GPU available
|
effect_thumbnail_generator: None, // Initialized when GPU available
|
||||||
|
|
||||||
// Debug test mode (F5)
|
// Debug test mode (F5)
|
||||||
|
|
@ -6208,17 +6215,17 @@ impl eframe::App for EditorApp {
|
||||||
self.render_large_media_prompt(ctx);
|
self.render_large_media_prompt(ctx);
|
||||||
|
|
||||||
// Render video frames incrementally (if video export in progress)
|
// Render video frames incrementally (if video export in progress)
|
||||||
if let Some(orchestrator) = &mut self.export_orchestrator {
|
let exporting = self.export_orchestrator.as_ref().map_or(false, |o| o.is_exporting());
|
||||||
if orchestrator.is_exporting() {
|
if exporting {
|
||||||
// Get GPU resources from eframe's wgpu render state
|
|
||||||
if let Some(render_state) = frame.wgpu_render_state() {
|
if let Some(render_state) = frame.wgpu_render_state() {
|
||||||
let device = &render_state.device;
|
let device = &render_state.device;
|
||||||
let queue = &render_state.queue;
|
let queue = &render_state.queue;
|
||||||
|
|
||||||
// Create temporary renderer and image cache for export
|
// Build the renderer + image cache ONCE per export (reused every frame).
|
||||||
// Note: Creating a new renderer per frame is inefficient but simple
|
// The image cache is given the container path so lazily-paged image assets
|
||||||
// TODO: Reuse renderer across frames by storing it in EditorApp
|
// (Phase 4 Tier 1) decode during export.
|
||||||
let mut temp_renderer = vello::Renderer::new(
|
if self.export_renderer.is_none() {
|
||||||
|
self.export_renderer = vello::Renderer::new(
|
||||||
device,
|
device,
|
||||||
vello::RendererOptions {
|
vello::RendererOptions {
|
||||||
use_cpu: false,
|
use_cpu: false,
|
||||||
|
|
@ -6227,17 +6234,23 @@ impl eframe::App for EditorApp {
|
||||||
pipeline_cache: None,
|
pipeline_cache: None,
|
||||||
},
|
},
|
||||||
).ok();
|
).ok();
|
||||||
|
let mut ic = lightningbeam_core::renderer::ImageCache::new();
|
||||||
|
ic.set_container_path(self.current_file_path.clone());
|
||||||
|
self.export_image_cache = Some(ic);
|
||||||
|
}
|
||||||
|
|
||||||
let mut temp_image_cache = lightningbeam_core::renderer::ImageCache::new();
|
if let (Some(renderer), Some(image_cache), Some(orchestrator)) = (
|
||||||
|
self.export_renderer.as_mut(),
|
||||||
if let Some(renderer) = &mut temp_renderer {
|
self.export_image_cache.as_mut(),
|
||||||
|
self.export_orchestrator.as_mut(),
|
||||||
|
) {
|
||||||
// Drive incremental video export.
|
// Drive incremental video export.
|
||||||
if let Ok(has_more) = orchestrator.render_next_video_frame(
|
if let Ok(has_more) = orchestrator.render_next_video_frame(
|
||||||
self.action_executor.document_mut(),
|
self.action_executor.document_mut(),
|
||||||
device,
|
device,
|
||||||
queue,
|
queue,
|
||||||
renderer,
|
renderer,
|
||||||
&mut temp_image_cache,
|
image_cache,
|
||||||
&self.video_manager,
|
&self.video_manager,
|
||||||
Some(&self.raster_store),
|
Some(&self.raster_store),
|
||||||
) {
|
) {
|
||||||
|
|
@ -6252,7 +6265,7 @@ impl eframe::App for EditorApp {
|
||||||
device,
|
device,
|
||||||
queue,
|
queue,
|
||||||
renderer,
|
renderer,
|
||||||
&mut temp_image_cache,
|
image_cache,
|
||||||
&self.video_manager,
|
&self.video_manager,
|
||||||
self.selection.raster_floating.as_ref(),
|
self.selection.raster_floating.as_ref(),
|
||||||
Some(&self.raster_store),
|
Some(&self.raster_store),
|
||||||
|
|
@ -6263,7 +6276,10 @@ impl eframe::App for EditorApp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else if self.export_renderer.is_some() {
|
||||||
|
// Export finished — free the renderer + cache.
|
||||||
|
self.export_renderer = None;
|
||||||
|
self.export_image_cache = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Poll export orchestrator for progress
|
// Poll export orchestrator for progress
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue