From 7052fdcb2ed26384c8f9f5159d647eee8ca14a40 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 24 Jul 2025 13:23:54 +0200 Subject: [PATCH 1/6] Replace window state with channels and improve resize performance --- desktop/src/app.rs | 128 +++++++-------- desktop/src/cef.rs | 4 +- desktop/src/cef/internal/app.rs | 8 +- .../cef/internal/browser_process_handler.rs | 6 +- desktop/src/cef/internal/render_handler.rs | 11 +- desktop/src/main.rs | 148 +++++------------- desktop/src/render.rs | 11 +- 7 files changed, 111 insertions(+), 205 deletions(-) diff --git a/desktop/src/app.rs b/desktop/src/app.rs index fb447aa923..9b3014e419 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -1,11 +1,14 @@ use crate::CustomEvent; -use crate::WindowState; -use crate::WindowStateHandle; +use crate::FrameBuffer; +use crate::WindowSize; use crate::render::GraphicsState; use std::sync::Arc; +use std::sync::mpsc::Sender; use std::time::Duration; use std::time::Instant; +use tracing::instrument::WithSubscriber; use winit::application::ApplicationHandler; +use winit::dpi::PhysicalSize; use winit::event::StartCause; use winit::event::WindowEvent; use winit::event_loop::ActiveEventLoop; @@ -16,31 +19,42 @@ use winit::window::WindowId; use crate::cef; pub(crate) struct WinitApp { - pub(crate) window_state: WindowStateHandle, pub(crate) cef_context: cef::Context, pub(crate) window: Option>, cef_schedule: Option, + ui_dirty: bool, + ui_frame_buffer: Option, + window_size_sender: Sender, + _viewport_frame_buffer: Option, + graphics_state: Option, } impl WinitApp { - pub(crate) fn new(window_state: WindowStateHandle, cef_context: cef::Context) -> Self { + pub(crate) fn new(cef_context: cef::Context, window_size_sender: Sender) -> Self { Self { - window_state, cef_context, window: None, cef_schedule: Some(Instant::now()), + _viewport_frame_buffer: None, + ui_frame_buffer: None, + ui_dirty: false, + graphics_state: None, + window_size_sender, } } } impl ApplicationHandler for WinitApp { fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { - let timeout = Instant::now() + Duration::from_millis(10); + let timeout = Instant::now() + Duration::from_millis(1000); let wait_until = timeout.min(self.cef_schedule.unwrap_or(timeout)); event_loop.set_control_flow(ControlFlow::WaitUntil(wait_until)); } fn new_events(&mut self, _event_loop: &ActiveEventLoop, _cause: StartCause) { + if self.ui_frame_buffer.is_none() { + self.cef_context.work(); + } if let Some(schedule) = self.cef_schedule && schedule < Instant::now() { @@ -50,32 +64,31 @@ impl ApplicationHandler for WinitApp { } fn resumed(&mut self, event_loop: &ActiveEventLoop) { - self.window_state - .with(|s| { - if let WindowState { width: Some(w), height: Some(h), .. } = s { - let window = Arc::new( - event_loop - .create_window( - Window::default_attributes() - .with_title("CEF Offscreen Rendering") - .with_inner_size(winit::dpi::LogicalSize::new(*w as u32, *h as u32)), - ) - .unwrap(), - ); - let graphics_state = pollster::block_on(GraphicsState::new(window.clone())); + let window = Arc::new( + event_loop + .create_window( + Window::default_attributes() + .with_title("CEF Offscreen Rendering") + .with_inner_size(winit::dpi::LogicalSize::new(1200, 800)), + ) + .unwrap(), + ); + let graphics_state = pollster::block_on(GraphicsState::new(window.clone())); - self.window = Some(window.clone()); - s.graphics_state = Some(graphics_state); + self.window = Some(window); + self.graphics_state = Some(graphics_state); - tracing::info!("Winit window created and ready"); - } - }) - .unwrap(); + tracing::info!("Winit window created and ready"); } fn user_event(&mut self, _: &ActiveEventLoop, event: CustomEvent) { match event { - CustomEvent::UiUpdate => { + CustomEvent::UiUpdate(frame_buffer) => { + if let Some(graphics_state) = self.graphics_state.as_mut() { + graphics_state.update_texture(&frame_buffer); + self.ui_dirty = true; + } + self.ui_frame_buffer = Some(frame_buffer); if let Some(window) = &self.window { window.request_redraw(); } @@ -94,58 +107,33 @@ impl ApplicationHandler for WinitApp { tracing::info!("The close button was pressed; stopping"); event_loop.exit(); } - WindowEvent::Resized(physical_size) => { - self.window_state - .with(|s| { - let width = physical_size.width as usize; - let height = physical_size.height as usize; - s.width = Some(width); - s.height = Some(height); - if let Some(graphics_state) = &mut s.graphics_state { - graphics_state.resize(width, height); - } - }) - .unwrap(); + WindowEvent::Resized(PhysicalSize { width, height }) => { + let _ = self.window_size_sender.send(WindowSize::new(width as usize, height as usize)); self.cef_context.notify_of_resize(); } WindowEvent::RedrawRequested => { - self.cef_context.work(); + let Some(ref mut graphics_state) = self.graphics_state else { return }; + // Only rerender once we have a new ui texture to display + if self.ui_dirty { + self.ui_dirty = false; - self.window_state - .with(|s| { - if let WindowState { - width: Some(width), - height: Some(height), - graphics_state: Some(graphics_state), - ui_frame_buffer: ui_fb, - .. - } = s - { - if let Some(fb) = &*ui_fb { - graphics_state.update_texture(fb); - if fb.width() != *width && fb.height() != *height { - graphics_state.resize(*width, *height); - } - } else if let Some(window) = &self.window { - window.request_redraw(); - } - - match graphics_state.render() { - Ok(_) => {} - Err(wgpu::SurfaceError::Lost) => { - graphics_state.resize(*width, *height); - } - Err(wgpu::SurfaceError::OutOfMemory) => { - event_loop.exit(); - } - Err(e) => tracing::error!("{:?}", e), - } + match graphics_state.render() { + Ok(_) => {} + Err(wgpu::SurfaceError::Lost) => { + tracing::warn!("lost surface"); + } + Err(wgpu::SurfaceError::OutOfMemory) => { + event_loop.exit(); } - }) - .unwrap(); + Err(e) => tracing::error!("{:?}", e), + } + } } _ => {} } + + // Notify cef of possible input events + self.cef_context.work(); } } diff --git a/desktop/src/cef.rs b/desktop/src/cef.rs index c79b3a65bd..fab6c29840 100644 --- a/desktop/src/cef.rs +++ b/desktop/src/cef.rs @@ -11,13 +11,13 @@ pub(crate) use context::{Context, InitError, Initialized, Setup, SetupError}; pub(crate) trait CefEventHandler: Clone { fn window_size(&self) -> WindowSize; - fn draw(&self, frame_buffer: FrameBuffer) -> bool; + fn draw(&self, frame_buffer: FrameBuffer); /// Scheudule the main event loop to run the cef event loop after the timeout /// [`_cef_browser_process_handler_t::on_schedule_message_pump_work`] for more documentation. fn schedule_cef_message_loop_work(&self, scheduled_time: Instant); } -#[derive(Clone)] +#[derive(Clone, Copy)] pub(crate) struct WindowSize { pub(crate) width: usize, pub(crate) height: usize, diff --git a/desktop/src/cef/internal/app.rs b/desktop/src/cef/internal/app.rs index effed9d492..a4d9d2e022 100644 --- a/desktop/src/cef/internal/app.rs +++ b/desktop/src/cef/internal/app.rs @@ -11,7 +11,7 @@ pub(crate) struct AppImpl { object: *mut RcImpl<_cef_app_t, Self>, event_handler: H, } -impl AppImpl { +impl AppImpl { pub(crate) fn new(event_handler: H) -> Self { Self { object: std::ptr::null_mut(), @@ -20,7 +20,7 @@ impl AppImpl { } } -impl ImplApp for AppImpl { +impl ImplApp for AppImpl { fn browser_process_handler(&self) -> Option { Some(BrowserProcessHandler::new(BrowserProcessHandlerImpl::new(self.event_handler.clone()))) } @@ -34,7 +34,7 @@ impl ImplApp for AppImpl { } } -impl Clone for AppImpl { +impl Clone for AppImpl { fn clone(&self) -> Self { unsafe { let rc_impl = &mut *self.object; @@ -54,7 +54,7 @@ impl Rc for AppImpl { } } } -impl WrapApp for AppImpl { +impl WrapApp for AppImpl { fn wrap_rc(&mut self, object: *mut RcImpl<_cef_app_t, Self>) { self.object = object; } diff --git a/desktop/src/cef/internal/browser_process_handler.rs b/desktop/src/cef/internal/browser_process_handler.rs index 553540ba3a..cd4cb17500 100644 --- a/desktop/src/cef/internal/browser_process_handler.rs +++ b/desktop/src/cef/internal/browser_process_handler.rs @@ -20,7 +20,7 @@ impl BrowserProcessHandlerImpl { } } -impl ImplBrowserProcessHandler for BrowserProcessHandlerImpl { +impl ImplBrowserProcessHandler for BrowserProcessHandlerImpl { fn on_context_initialized(&self) { cef::register_scheme_handler_factory(Some(&CefString::from(GRAPHITE_SCHEME)), None, Some(&mut SchemeHandlerFactory::new(GraphiteSchemeHandlerFactory::new()))); } @@ -34,7 +34,7 @@ impl ImplBrowserProcessHandler for BrowserProcessHandlerImpl } } -impl Clone for BrowserProcessHandlerImpl { +impl Clone for BrowserProcessHandlerImpl { fn clone(&self) -> Self { unsafe { let rc_impl = &mut *self.object; @@ -54,7 +54,7 @@ impl Rc for BrowserProcessHandlerImpl { } } } -impl WrapBrowserProcessHandler for BrowserProcessHandlerImpl { +impl WrapBrowserProcessHandler for BrowserProcessHandlerImpl { fn wrap_rc(&mut self, object: *mut RcImpl<_cef_browser_process_handler_t, Self>) { self.object = object; } diff --git a/desktop/src/cef/internal/render_handler.rs b/desktop/src/cef/internal/render_handler.rs index cc4394daf7..9a0edacdc7 100644 --- a/desktop/src/cef/internal/render_handler.rs +++ b/desktop/src/cef/internal/render_handler.rs @@ -1,6 +1,6 @@ use cef::rc::{Rc, RcImpl}; use cef::sys::{_cef_render_handler_t, cef_base_ref_counted_t}; -use cef::{Browser, ImplBrowser, ImplBrowserHost, ImplRenderHandler, PaintElementType, Rect, WrapRenderHandler}; +use cef::{Browser, ImplRenderHandler, PaintElementType, Rect, WrapRenderHandler}; use crate::FrameBuffer; use crate::cef::CefEventHandler; @@ -32,7 +32,7 @@ impl ImplRenderHandler for RenderHandlerImpl { fn on_paint( &self, - browser: Option<&mut Browser>, + _browser: Option<&mut Browser>, _type_: PaintElementType, _dirty_rect_count: usize, _dirty_rects: Option<&Rect>, @@ -44,12 +44,7 @@ impl ImplRenderHandler for RenderHandlerImpl { let buffer_slice = unsafe { std::slice::from_raw_parts(buffer, buffer_size) }; let frame_buffer = FrameBuffer::new(buffer_slice.to_vec(), width as usize, height as usize).expect("Failed to create frame buffer"); - let draw_successful = self.event_handler.draw(frame_buffer); - if !draw_successful { - if let Some(browser) = browser { - browser.host().unwrap().was_resized(); - } - } + self.event_handler.draw(frame_buffer) } fn get_raw(&self) -> *mut _cef_render_handler_t { diff --git a/desktop/src/main.rs b/desktop/src/main.rs index ef92e23b7b..65bb443b78 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -1,16 +1,17 @@ use std::fmt::Debug; use std::process::exit; -use std::sync::{Arc, Mutex, MutexGuard, PoisonError}; +use std::sync::mpsc::Receiver; +use std::sync::{Arc, Mutex}; use std::time::Instant; use tracing_subscriber::EnvFilter; use winit::event_loop::{EventLoop, EventLoopProxy}; mod cef; -use cef::Setup; +use cef::{Setup, WindowSize}; mod render; -use render::{FrameBuffer, GraphicsState}; +use render::FrameBuffer; mod app; use app::WinitApp; @@ -19,120 +20,54 @@ mod dirs; #[derive(Debug)] pub(crate) enum CustomEvent { - UiUpdate, + UiUpdate(FrameBuffer), ScheduleBrowserWork(Instant), } -#[derive(Debug)] -pub(crate) struct WindowState { - width: Option, - height: Option, - ui_frame_buffer: Option, - _viewport_frame_buffer: Option, - graphics_state: Option, - event_loop_proxy: Option>, -} - -impl WindowState { - fn new() -> Self { - Self { - width: None, - height: None, - ui_frame_buffer: None, - _viewport_frame_buffer: None, - graphics_state: None, - event_loop_proxy: None, - } - } - - fn handle(self) -> WindowStateHandle { - WindowStateHandle { inner: Arc::new(Mutex::new(self)) } - } +#[derive(Clone)] +struct CefHandler { + window_size_receiver: Arc>, + event_loop_proxy: EventLoopProxy, } - -pub(crate) struct WindowStateHandle { - inner: Arc>, +struct WindowSizeReceiver { + receiver: Receiver, + window_size: WindowSize, } - -impl WindowStateHandle { - fn with<'a, P>(&self, p: P) -> Result<(), PoisonError>> - where - P: FnOnce(&mut WindowState), - { - match self.inner.lock() { - Ok(mut guard) => { - p(&mut guard); - Ok(()) - } - Err(_) => todo!("not error handling yet"), +impl WindowSizeReceiver { + fn new(window_size_receiver: Receiver) -> Self { + Self { + window_size: WindowSize { width: 1, height: 1 }, + receiver: window_size_receiver, } } } - -impl Clone for WindowStateHandle { - fn clone(&self) -> Self { - Self { inner: self.inner.clone() } - } -} - -#[derive(Clone)] -struct CefHandler { - window_state: WindowStateHandle, -} - impl CefHandler { - fn new(window_state: WindowStateHandle) -> Self { - Self { window_state } + fn new(window_size_receiver: Receiver, event_loop_proxy: EventLoopProxy) -> Self { + Self { + window_size_receiver: Arc::new(Mutex::new(WindowSizeReceiver::new(window_size_receiver))), + event_loop_proxy, + } } } impl cef::CefEventHandler for CefHandler { fn window_size(&self) -> cef::WindowSize { - let mut w = 1; - let mut h = 1; - - self.window_state - .with(|s| { - if let WindowState { - width: Some(width), - height: Some(height), - .. - } = s - { - w = *width; - h = *height; - } - }) - .unwrap(); - - cef::WindowSize::new(w, h) + let Ok(mut guard) = self.window_size_receiver.lock() else { + tracing::error!("Failed to lock window_size_receiver"); + return cef::WindowSize::new(1, 1); + }; + let WindowSizeReceiver { receiver, window_size } = &mut *guard; + for new_window_size in receiver.try_iter() { + *window_size = new_window_size; + } + *window_size } - - fn draw(&self, frame_buffer: FrameBuffer) -> bool { - let mut correct_size = true; - self.window_state - .with(|s| { - if let Some(event_loop_proxy) = &s.event_loop_proxy { - let _ = event_loop_proxy.send_event(CustomEvent::UiUpdate); - } - if frame_buffer.width() != s.width.unwrap_or(1) || frame_buffer.height() != s.height.unwrap_or(1) { - correct_size = false; - } else { - s.ui_frame_buffer = Some(frame_buffer); - } - }) - .unwrap(); - - correct_size + fn draw(&self, frame_buffer: FrameBuffer) { + let _ = self.event_loop_proxy.send_event(CustomEvent::UiUpdate(frame_buffer)); } fn schedule_cef_message_loop_work(&self, scheduled_time: std::time::Instant) { - self.window_state - .with(|s| { - let Some(event_loop_proxy) = &mut s.event_loop_proxy else { return }; - let _ = event_loop_proxy.send_event(CustomEvent::ScheduleBrowserWork(scheduled_time)); - }) - .unwrap(); + let _ = self.event_loop_proxy.send_event(CustomEvent::ScheduleBrowserWork(scheduled_time)); } } @@ -148,20 +83,11 @@ fn main() { } }; - let window_state = WindowState::new().handle(); - - window_state - .with(|s| { - s.width = Some(1200); - s.height = Some(800); - }) - .unwrap(); - let event_loop = EventLoop::::with_user_event().build().unwrap(); - window_state.with(|s| s.event_loop_proxy = Some(event_loop.create_proxy())).unwrap(); + let (send, recv) = std::sync::mpsc::channel(); - let cef_context = match cef_context.init(CefHandler::new(window_state.clone())) { + let cef_context = match cef_context.init(CefHandler::new(recv, event_loop.create_proxy())) { Ok(c) => c, Err(cef::InitError::InitializationFailed) => { tracing::error!("Cef initialization failed"); @@ -171,7 +97,7 @@ fn main() { tracing::info!("Cef initialized successfully"); - let mut winit_app = WinitApp::new(window_state, cef_context); + let mut winit_app = WinitApp::new(cef_context, send); event_loop.run_app(&mut winit_app).unwrap(); } diff --git a/desktop/src/render.rs b/desktop/src/render.rs index e6f07b46bc..5925ef210b 100644 --- a/desktop/src/render.rs +++ b/desktop/src/render.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use thiserror::Error; +use wgpu::PollType; use winit::window::Window; pub(crate) struct FrameBuffer { @@ -10,7 +11,7 @@ pub(crate) struct FrameBuffer { } impl std::fmt::Debug for FrameBuffer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("WindowState") + f.debug_struct("FrameBuffer") .field("width", &self.width) .field("height", &self.height) .field("len", &self.buffer.len()) @@ -223,12 +224,8 @@ impl GraphicsState { graphics_state } - pub(crate) fn resize(&mut self, width: usize, height: usize) { - if width > 0 && height > 0 && (self.config.width != width as u32 || self.config.height != height as u32) { - self.config.width = width as u32; - self.config.height = height as u32; - self.surface.configure(&self.device, &self.config); - } + pub(crate) fn poll(&self) { + let _ = self.device.poll(PollType::Poll); } pub(crate) fn update_texture(&mut self, frame_buffer: &FrameBuffer) { From e3c10b978352f3c4115fe7638a94cc1a9b377050 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 24 Jul 2025 13:32:20 +0200 Subject: [PATCH 2/6] Move Cef Handler into the cef module --- desktop/src/app.rs | 1 - desktop/src/cef.rs | 55 +++++++++++++++++++++++++++++++++++++++++-- desktop/src/main.rs | 53 ++--------------------------------------- desktop/src/render.rs | 7 +----- 4 files changed, 56 insertions(+), 60 deletions(-) diff --git a/desktop/src/app.rs b/desktop/src/app.rs index 9b3014e419..24065e57eb 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -6,7 +6,6 @@ use std::sync::Arc; use std::sync::mpsc::Sender; use std::time::Duration; use std::time::Instant; -use tracing::instrument::WithSubscriber; use winit::application::ApplicationHandler; use winit::dpi::PhysicalSize; use winit::event::StartCause; diff --git a/desktop/src/cef.rs b/desktop/src/cef.rs index fab6c29840..ef63672f79 100644 --- a/desktop/src/cef.rs +++ b/desktop/src/cef.rs @@ -1,5 +1,8 @@ -use crate::FrameBuffer; -use std::time::Instant; +use crate::{CustomEvent, FrameBuffer}; +use std::{ + sync::{Arc, Mutex, mpsc::Receiver}, + time::Instant, +}; mod context; mod dirs; @@ -8,6 +11,7 @@ mod internal; mod scheme_handler; pub(crate) use context::{Context, InitError, Initialized, Setup, SetupError}; +use winit::event_loop::EventLoopProxy; pub(crate) trait CefEventHandler: Clone { fn window_size(&self) -> WindowSize; @@ -28,3 +32,50 @@ impl WindowSize { Self { width, height } } } + +#[derive(Clone)] +pub(crate) struct CefHandler { + window_size_receiver: Arc>, + event_loop_proxy: EventLoopProxy, +} +struct WindowSizeReceiver { + receiver: Receiver, + window_size: WindowSize, +} +impl WindowSizeReceiver { + fn new(window_size_receiver: Receiver) -> Self { + Self { + window_size: WindowSize { width: 1, height: 1 }, + receiver: window_size_receiver, + } + } +} +impl CefHandler { + pub(crate) fn new(window_size_receiver: Receiver, event_loop_proxy: EventLoopProxy) -> Self { + Self { + window_size_receiver: Arc::new(Mutex::new(WindowSizeReceiver::new(window_size_receiver))), + event_loop_proxy, + } + } +} + +impl CefEventHandler for CefHandler { + fn window_size(&self) -> WindowSize { + let Ok(mut guard) = self.window_size_receiver.lock() else { + tracing::error!("Failed to lock window_size_receiver"); + return WindowSize::new(1, 1); + }; + let WindowSizeReceiver { receiver, window_size } = &mut *guard; + for new_window_size in receiver.try_iter() { + *window_size = new_window_size; + } + *window_size + } + fn draw(&self, frame_buffer: FrameBuffer) { + let _ = self.event_loop_proxy.send_event(CustomEvent::UiUpdate(frame_buffer)); + } + + fn schedule_cef_message_loop_work(&self, scheduled_time: std::time::Instant) { + let _ = self.event_loop_proxy.send_event(CustomEvent::ScheduleBrowserWork(scheduled_time)); + } +} diff --git a/desktop/src/main.rs b/desktop/src/main.rs index 65bb443b78..4ffb61e50f 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -1,11 +1,9 @@ use std::fmt::Debug; use std::process::exit; -use std::sync::mpsc::Receiver; -use std::sync::{Arc, Mutex}; use std::time::Instant; use tracing_subscriber::EnvFilter; -use winit::event_loop::{EventLoop, EventLoopProxy}; +use winit::event_loop::EventLoop; mod cef; use cef::{Setup, WindowSize}; @@ -24,53 +22,6 @@ pub(crate) enum CustomEvent { ScheduleBrowserWork(Instant), } -#[derive(Clone)] -struct CefHandler { - window_size_receiver: Arc>, - event_loop_proxy: EventLoopProxy, -} -struct WindowSizeReceiver { - receiver: Receiver, - window_size: WindowSize, -} -impl WindowSizeReceiver { - fn new(window_size_receiver: Receiver) -> Self { - Self { - window_size: WindowSize { width: 1, height: 1 }, - receiver: window_size_receiver, - } - } -} -impl CefHandler { - fn new(window_size_receiver: Receiver, event_loop_proxy: EventLoopProxy) -> Self { - Self { - window_size_receiver: Arc::new(Mutex::new(WindowSizeReceiver::new(window_size_receiver))), - event_loop_proxy, - } - } -} - -impl cef::CefEventHandler for CefHandler { - fn window_size(&self) -> cef::WindowSize { - let Ok(mut guard) = self.window_size_receiver.lock() else { - tracing::error!("Failed to lock window_size_receiver"); - return cef::WindowSize::new(1, 1); - }; - let WindowSizeReceiver { receiver, window_size } = &mut *guard; - for new_window_size in receiver.try_iter() { - *window_size = new_window_size; - } - *window_size - } - fn draw(&self, frame_buffer: FrameBuffer) { - let _ = self.event_loop_proxy.send_event(CustomEvent::UiUpdate(frame_buffer)); - } - - fn schedule_cef_message_loop_work(&self, scheduled_time: std::time::Instant) { - let _ = self.event_loop_proxy.send_event(CustomEvent::ScheduleBrowserWork(scheduled_time)); - } -} - fn main() { tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init(); @@ -87,7 +38,7 @@ fn main() { let (send, recv) = std::sync::mpsc::channel(); - let cef_context = match cef_context.init(CefHandler::new(recv, event_loop.create_proxy())) { + let cef_context = match cef_context.init(cef::CefHandler::new(recv, event_loop.create_proxy())) { Ok(c) => c, Err(cef::InitError::InitializationFailed) => { tracing::error!("Cef initialization failed"); diff --git a/desktop/src/render.rs b/desktop/src/render.rs index 5925ef210b..76805e5db6 100644 --- a/desktop/src/render.rs +++ b/desktop/src/render.rs @@ -1,7 +1,6 @@ use std::sync::Arc; use thiserror::Error; -use wgpu::PollType; use winit::window::Window; pub(crate) struct FrameBuffer { @@ -215,7 +214,7 @@ impl GraphicsState { let fb = FrameBuffer::new(initial_data, width, height) .map_err(|e| { - panic!("Failed to create initial FrameBuffer: {}", e); + panic!("Failed to create initial FrameBuffer: {e}"); }) .unwrap(); @@ -224,10 +223,6 @@ impl GraphicsState { graphics_state } - pub(crate) fn poll(&self) { - let _ = self.device.poll(PollType::Poll); - } - pub(crate) fn update_texture(&mut self, frame_buffer: &FrameBuffer) { let data = frame_buffer.buffer(); let width = frame_buffer.width() as u32; From 1afc8788117f7cf912feff6acbcfc41a89bff9e0 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 24 Jul 2025 13:43:38 +0200 Subject: [PATCH 3/6] Reuse textures --- desktop/src/render.rs | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/desktop/src/render.rs b/desktop/src/render.rs index 76805e5db6..96832df727 100644 --- a/desktop/src/render.rs +++ b/desktop/src/render.rs @@ -232,26 +232,28 @@ impl GraphicsState { self.config.width = width; self.config.height = height; self.surface.configure(&self.device, &self.config); + let texture = self.device.create_texture(&wgpu::TextureDescriptor { + label: Some("CEF Texture"), + size: wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + }); + self.texture = Some(texture); } - let texture = self.device.create_texture(&wgpu::TextureDescriptor { - label: Some("CEF Texture"), - size: wgpu::Extent3d { - width, - height, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Bgra8UnormSrgb, - usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - view_formats: &[], - }); + let Some(ref texture) = self.texture else { return }; self.queue.write_texture( wgpu::TexelCopyTextureInfo { - texture: &texture, + texture, mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All, @@ -286,7 +288,6 @@ impl GraphicsState { label: Some("texture_bind_group"), }); - self.texture = Some(texture); self.bind_group = Some(bind_group); } From 943943d70816dbb83dc99eab5c3100534c285a9b Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 24 Jul 2025 14:19:31 +0200 Subject: [PATCH 4/6] Test cef scheduling --- desktop/src/app.rs | 23 ++++++++++++++--------- desktop/src/cef.rs | 1 + 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/desktop/src/app.rs b/desktop/src/app.rs index 24065e57eb..2da7d71bb7 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -41,6 +41,17 @@ impl WinitApp { window_size_sender, } } + fn run_cef(&mut self) { + if self.ui_frame_buffer.is_none() { + self.cef_context.work(); + } + if let Some(schedule) = self.cef_schedule + && schedule < Instant::now() + { + self.cef_schedule = None; + self.cef_context.work(); + } + } } impl ApplicationHandler for WinitApp { @@ -51,15 +62,7 @@ impl ApplicationHandler for WinitApp { } fn new_events(&mut self, _event_loop: &ActiveEventLoop, _cause: StartCause) { - if self.ui_frame_buffer.is_none() { - self.cef_context.work(); - } - if let Some(schedule) = self.cef_schedule - && schedule < Instant::now() - { - self.cef_schedule = None; - self.cef_context.work(); - } + self.run_cef(); } fn resumed(&mut self, event_loop: &ActiveEventLoop) { @@ -81,6 +84,7 @@ impl ApplicationHandler for WinitApp { } fn user_event(&mut self, _: &ActiveEventLoop, event: CustomEvent) { + self.run_cef(); match event { CustomEvent::UiUpdate(frame_buffer) => { if let Some(graphics_state) = self.graphics_state.as_mut() { @@ -99,6 +103,7 @@ impl ApplicationHandler for WinitApp { } fn window_event(&mut self, event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent) { + self.run_cef(); let Some(event) = self.cef_context.handle_window_event(event) else { return }; match event { diff --git a/desktop/src/cef.rs b/desktop/src/cef.rs index ef63672f79..1bf50cd10b 100644 --- a/desktop/src/cef.rs +++ b/desktop/src/cef.rs @@ -77,5 +77,6 @@ impl CefEventHandler for CefHandler { fn schedule_cef_message_loop_work(&self, scheduled_time: std::time::Instant) { let _ = self.event_loop_proxy.send_event(CustomEvent::ScheduleBrowserWork(scheduled_time)); + println!("browser scheduled work"); } } From 5163c6b1a462b6ca53032b7f07637191f859778d Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 24 Jul 2025 15:03:05 +0200 Subject: [PATCH 5/6] Schedule self render if texture is outdated --- desktop/src/app.rs | 60 ++++++++++++++++++++++--------------------- desktop/src/cef.rs | 1 - desktop/src/main.rs | 2 +- desktop/src/render.rs | 14 ++++++++-- 4 files changed, 44 insertions(+), 33 deletions(-) diff --git a/desktop/src/app.rs b/desktop/src/app.rs index 2da7d71bb7..a348f7c6af 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -12,6 +12,7 @@ use winit::event::StartCause; use winit::event::WindowEvent; use winit::event_loop::ActiveEventLoop; use winit::event_loop::ControlFlow; +use winit::event_loop::EventLoopProxy; use winit::window::Window; use winit::window::WindowId; @@ -21,35 +22,24 @@ pub(crate) struct WinitApp { pub(crate) cef_context: cef::Context, pub(crate) window: Option>, cef_schedule: Option, - ui_dirty: bool, ui_frame_buffer: Option, window_size_sender: Sender, _viewport_frame_buffer: Option, graphics_state: Option, + event_loop_proxy: EventLoopProxy, } impl WinitApp { - pub(crate) fn new(cef_context: cef::Context, window_size_sender: Sender) -> Self { + pub(crate) fn new(cef_context: cef::Context, window_size_sender: Sender, event_loop_proxy: EventLoopProxy) -> Self { Self { cef_context, window: None, cef_schedule: Some(Instant::now()), _viewport_frame_buffer: None, ui_frame_buffer: None, - ui_dirty: false, graphics_state: None, window_size_sender, - } - } - fn run_cef(&mut self) { - if self.ui_frame_buffer.is_none() { - self.cef_context.work(); - } - if let Some(schedule) = self.cef_schedule - && schedule < Instant::now() - { - self.cef_schedule = None; - self.cef_context.work(); + event_loop_proxy, } } } @@ -62,7 +52,15 @@ impl ApplicationHandler for WinitApp { } fn new_events(&mut self, _event_loop: &ActiveEventLoop, _cause: StartCause) { - self.run_cef(); + if self.ui_frame_buffer.is_none() { + self.cef_context.work(); + } + if let Some(schedule) = self.cef_schedule + && schedule < Instant::now() + { + self.cef_schedule = None; + self.cef_context.work(); + } } fn resumed(&mut self, event_loop: &ActiveEventLoop) { @@ -84,12 +82,10 @@ impl ApplicationHandler for WinitApp { } fn user_event(&mut self, _: &ActiveEventLoop, event: CustomEvent) { - self.run_cef(); match event { CustomEvent::UiUpdate(frame_buffer) => { if let Some(graphics_state) = self.graphics_state.as_mut() { graphics_state.update_texture(&frame_buffer); - self.ui_dirty = true; } self.ui_frame_buffer = Some(frame_buffer); if let Some(window) = &self.window { @@ -97,13 +93,19 @@ impl ApplicationHandler for WinitApp { } } CustomEvent::ScheduleBrowserWork(instant) => { + if let Some(graphics_state) = self.graphics_state.as_mut() + && let Some(frame_buffer) = &self.ui_frame_buffer + && graphics_state.ui_texture_outdated(frame_buffer) + { + self.cef_context.work(); + let _ = self.event_loop_proxy.send_event(CustomEvent::ScheduleBrowserWork(Instant::now() + Duration::from_millis(1))); + } self.cef_schedule = Some(instant); } } } fn window_event(&mut self, event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent) { - self.run_cef(); let Some(event) = self.cef_context.handle_window_event(event) else { return }; match event { @@ -113,25 +115,25 @@ impl ApplicationHandler for WinitApp { } WindowEvent::Resized(PhysicalSize { width, height }) => { let _ = self.window_size_sender.send(WindowSize::new(width as usize, height as usize)); + if let Some(ref mut graphics_state) = self.graphics_state { + graphics_state.resize(width, height); + } self.cef_context.notify_of_resize(); } WindowEvent::RedrawRequested => { let Some(ref mut graphics_state) = self.graphics_state else { return }; // Only rerender once we have a new ui texture to display - if self.ui_dirty { - self.ui_dirty = false; - match graphics_state.render() { - Ok(_) => {} - Err(wgpu::SurfaceError::Lost) => { - tracing::warn!("lost surface"); - } - Err(wgpu::SurfaceError::OutOfMemory) => { - event_loop.exit(); - } - Err(e) => tracing::error!("{:?}", e), + match graphics_state.render() { + Ok(_) => {} + Err(wgpu::SurfaceError::Lost) => { + tracing::warn!("lost surface"); + } + Err(wgpu::SurfaceError::OutOfMemory) => { + event_loop.exit(); } + Err(e) => tracing::error!("{:?}", e), } } _ => {} diff --git a/desktop/src/cef.rs b/desktop/src/cef.rs index 1bf50cd10b..ef63672f79 100644 --- a/desktop/src/cef.rs +++ b/desktop/src/cef.rs @@ -77,6 +77,5 @@ impl CefEventHandler for CefHandler { fn schedule_cef_message_loop_work(&self, scheduled_time: std::time::Instant) { let _ = self.event_loop_proxy.send_event(CustomEvent::ScheduleBrowserWork(scheduled_time)); - println!("browser scheduled work"); } } diff --git a/desktop/src/main.rs b/desktop/src/main.rs index 4ffb61e50f..c8e9528e7d 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -48,7 +48,7 @@ fn main() { tracing::info!("Cef initialized successfully"); - let mut winit_app = WinitApp::new(cef_context, send); + let mut winit_app = WinitApp::new(cef_context, send, event_loop.create_proxy()); event_loop.run_app(&mut winit_app).unwrap(); } diff --git a/desktop/src/render.rs b/desktop/src/render.rs index 96832df727..6d3755a803 100644 --- a/desktop/src/render.rs +++ b/desktop/src/render.rs @@ -223,11 +223,13 @@ impl GraphicsState { graphics_state } - pub(crate) fn update_texture(&mut self, frame_buffer: &FrameBuffer) { - let data = frame_buffer.buffer(); + pub(crate) fn ui_texture_outdated(&self, frame_buffer: &FrameBuffer) -> bool { let width = frame_buffer.width() as u32; let height = frame_buffer.height() as u32; + self.config.width != width || self.config.height != height + } + pub(crate) fn resize(&mut self, width: u32, height: u32) { if width > 0 && height > 0 && (self.config.width != width || self.config.height != height) { self.config.width = width; self.config.height = height; @@ -248,6 +250,14 @@ impl GraphicsState { }); self.texture = Some(texture); } + } + + pub(crate) fn update_texture(&mut self, frame_buffer: &FrameBuffer) { + let data = frame_buffer.buffer(); + let width = frame_buffer.width() as u32; + let height = frame_buffer.height() as u32; + + self.resize(width, height); let Some(ref texture) = self.texture else { return }; From 569515f8cc4baad526cdc72257c4620b24bc10d5 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 24 Jul 2025 18:40:06 +0200 Subject: [PATCH 6/6] Address review comments --- desktop/src/app.rs | 6 ++---- desktop/src/main.rs | 6 +++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/desktop/src/app.rs b/desktop/src/app.rs index a348f7c6af..c713e6569d 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -46,15 +46,13 @@ impl WinitApp { impl ApplicationHandler for WinitApp { fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { - let timeout = Instant::now() + Duration::from_millis(1000); + // Set a timeout in case we miss any cef schedule requests + let timeout = Instant::now() + Duration::from_millis(100); let wait_until = timeout.min(self.cef_schedule.unwrap_or(timeout)); event_loop.set_control_flow(ControlFlow::WaitUntil(wait_until)); } fn new_events(&mut self, _event_loop: &ActiveEventLoop, _cause: StartCause) { - if self.ui_frame_buffer.is_none() { - self.cef_context.work(); - } if let Some(schedule) = self.cef_schedule && schedule < Instant::now() { diff --git a/desktop/src/main.rs b/desktop/src/main.rs index c8e9528e7d..f748b3111e 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -36,9 +36,9 @@ fn main() { let event_loop = EventLoop::::with_user_event().build().unwrap(); - let (send, recv) = std::sync::mpsc::channel(); + let (window_size_sender, window_size_receiver) = std::sync::mpsc::channel(); - let cef_context = match cef_context.init(cef::CefHandler::new(recv, event_loop.create_proxy())) { + let cef_context = match cef_context.init(cef::CefHandler::new(window_size_receiver, event_loop.create_proxy())) { Ok(c) => c, Err(cef::InitError::InitializationFailed) => { tracing::error!("Cef initialization failed"); @@ -48,7 +48,7 @@ fn main() { tracing::info!("Cef initialized successfully"); - let mut winit_app = WinitApp::new(cef_context, send, event_loop.create_proxy()); + let mut winit_app = WinitApp::new(cef_context, window_size_sender, event_loop.create_proxy()); event_loop.run_app(&mut winit_app).unwrap(); }