diff --git a/lightningbeam-ui/lightningbeam-core/src/video.rs b/lightningbeam-ui/lightningbeam-core/src/video.rs index 8fb8552..67770d4 100644 --- a/lightningbeam-ui/lightningbeam-core/src/video.rs +++ b/lightningbeam-ui/lightningbeam-core/src/video.rs @@ -940,6 +940,17 @@ impl VideoManager { // Whether this pass wants (and can produce) a GPU frame. Gated on HW being configured at all // so that with software-only decode preview and export share one cache entry (no double-cache). let want_gpu = self.render_hardware_ok && self.hw_device.is_some(); + self.get_frame_inner(clip_id, timestamp, target_w, target_h, want_gpu) + } + + /// Like [`get_frame`](Self::get_frame) but always returns a CPU (RGBA) frame, ignoring the + /// render-pass hardware flag. For consumers that need pixel bytes (thumbnails, image readback) + /// regardless of whether a render pass last enabled GPU frames. + pub fn get_frame_cpu(&mut self, clip_id: &Uuid, timestamp: f64, target_w: u32, target_h: u32) -> Option> { + self.get_frame_inner(clip_id, timestamp, target_w, target_h, false) + } + + fn get_frame_inner(&mut self, clip_id: &Uuid, timestamp: f64, target_w: u32, target_h: u32, want_gpu: bool) -> Option> { // The cache key includes (target size, want_gpu): preview (GPU, preview res) and export // (CPU, export res) request the same clip/time and must not collide or cross representation. let timestamp_ms = (timestamp * 1000.0) as i64; diff --git a/lightningbeam-ui/lightningbeam-editor/src/panes/asset_library.rs b/lightningbeam-ui/lightningbeam-editor/src/panes/asset_library.rs index a76fc80..0132c8f 100644 --- a/lightningbeam-ui/lightningbeam-editor/src/panes/asset_library.rs +++ b/lightningbeam-ui/lightningbeam-editor/src/panes/asset_library.rs @@ -316,8 +316,10 @@ fn generate_video_thumbnail( let frame = { let mut video_mgr = video_manager.lock().ok()?; - // Small frame for the asset thumbnail (capped to native, aspect preserved). - video_mgr.get_frame(clip_id, timestamp, THUMBNAIL_SIZE, THUMBNAIL_SIZE)? + // Small CPU frame for the asset thumbnail (capped to native, aspect preserved). Force CPU: + // the render-pass hardware flag may be on, but the thumbnail needs RGBA bytes (a GPU frame + // has empty rgba_data → an all-black thumbnail). + video_mgr.get_frame_cpu(clip_id, timestamp, THUMBNAIL_SIZE, THUMBNAIL_SIZE)? }; let src_width = frame.width as usize;