diff --git a/Cargo.lock b/Cargo.lock index 2d33ed756e..b3a6562476 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "Inflector" @@ -632,7 +632,7 @@ checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" dependencies = [ "serde", "termcolor", - "unicode-width 0.1.14", + "unicode-width", ] [[package]] @@ -691,7 +691,7 @@ dependencies = [ "encode_unicode", "libc", "once_cell", - "unicode-width 0.2.1", + "unicode-width", "windows-sys 0.60.2", ] @@ -1796,12 +1796,9 @@ dependencies = [ name = "graphite-desktop" version = "0.1.0" dependencies = [ - "base64 0.22.1", - "bytemuck", "cef", - "graphite-editor", + "futures", "include_dir", - "pollster", "thiserror 2.0.12", "tracing", "tracing-subscriber", @@ -2332,7 +2329,7 @@ checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd" dependencies = [ "console", "portable-atomic", - "unicode-width 0.2.1", + "unicode-width", "unit-prefix", "web-time", ] @@ -3599,12 +3596,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "pollster" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" - [[package]] name = "portable-atomic" version = "1.11.1" @@ -5289,12 +5280,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - [[package]] name = "unicode-width" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index cf2a1ec0f6..68a179c852 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -154,6 +154,10 @@ tinyvec = { version = "1", features = ["std"] } criterion = { version = "0.5", features = ["html_reports"] } iai-callgrind = { version = "0.12.3" } ndarray = "0.16.1" +cef = "138.5.0" +include_dir = "0.7.4" +tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } +tracing = "0.1.41" [profile.dev] opt-level = 1 diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index fb4b2899a0..95a26926cf 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -8,25 +8,23 @@ repository = "" edition = "2024" rust-version = "1.87" -[features] -default = ["gpu"] -gpu = ["graphite-editor/gpu"] +# [features] +# default = ["gpu"] +# gpu = ["graphite-editor/gpu"] [dependencies] -# Local dependencies -graphite-editor = { path = "../editor", features = [ - "gpu", - "ron", - "vello", - "decouple-execution", -] } +# # Local dependencies +# graphite-editor = { path = "../editor", features = [ +# "gpu", +# "ron", +# "vello", +# "decouple-execution", +# ] } wgpu = { workspace = true } winit = { workspace = true, features = ["serde"] } -base64.workspace = true -thiserror.workspace = true -pollster = "0.3" -cef = "138.5.0" -tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } -tracing = "0.1.41" -bytemuck = { version = "1.23.1", features = ["derive"] } -include_dir = "0.7.4" +thiserror = { workspace = true } +futures = { workspace = true } +cef = { workspace = true } +include_dir = { workspace = true } +tracing-subscriber = { workspace = true } +tracing = { workspace = true } diff --git a/desktop/src/app.rs b/desktop/src/app.rs index fb447aa923..adf28d9282 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -1,8 +1,8 @@ -use crate::CustomEvent; -use crate::WindowState; -use crate::WindowStateHandle; +use crate::WindowSizeHandle; +use crate::WinitEvent; +use crate::cef::WindowSize; +use crate::render::FrameBuffer; use crate::render::GraphicsState; -use std::sync::Arc; use std::time::Duration; use std::time::Instant; use winit::application::ApplicationHandler; @@ -10,79 +10,90 @@ use winit::event::StartCause; use winit::event::WindowEvent; use winit::event_loop::ActiveEventLoop; use winit::event_loop::ControlFlow; -use winit::window::Window; +use winit::event_loop::EventLoopProxy; use winit::window::WindowId; use crate::cef; pub(crate) struct WinitApp { - pub(crate) window_state: WindowStateHandle, + pub(crate) event_loop_proxy: EventLoopProxy, + // + pub(crate) shared_render_data: WindowSizeHandle, + pub(crate) graphics_state: Option, pub(crate) cef_context: cef::Context, - pub(crate) window: Option>, - cef_schedule: Option, + // Cached frame buffer from CEF, used to check if mouse is on a transparent pixel + pub(crate) frame_buffer: Option, } impl WinitApp { - pub(crate) fn new(window_state: WindowStateHandle, cef_context: cef::Context) -> Self { + pub(crate) fn new(elp: EventLoopProxy, shared_render_data: WindowSizeHandle, cef_context: cef::Context) -> Self { Self { - window_state, + event_loop_proxy: elp, + shared_render_data, cef_context, - window: None, - cef_schedule: Some(Instant::now()), + graphics_state: None, + frame_buffer: None, } } } -impl ApplicationHandler for WinitApp { - fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { - let timeout = Instant::now() + Duration::from_millis(10); - 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 let Some(schedule) = self.cef_schedule - && schedule < Instant::now() - { - self.cef_schedule = None; - self.cef_context.work(); +impl ApplicationHandler for WinitApp { + // Runs on every event, but when resume time is reached (100x per second) it does the CEF work and queues a new timer. + fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) { + match cause { + // When the event loop starts running, queue the timer. + StartCause::Init => { + event_loop.set_control_flow(ControlFlow::WaitUntil(Instant::now() + Duration::from_millis(10))); + } + // When the timer expires, run the CEF work event and queue a new timer. + StartCause::ResumeTimeReached { .. } => { + self.cef_context.work(); + event_loop.set_control_flow(ControlFlow::WaitUntil(Instant::now() + Duration::from_millis(10))); + } + _ => {} } } 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())); - - self.window = Some(window.clone()); - s.graphics_state = Some(graphics_state); - - tracing::info!("Winit window created and ready"); - } - }) - .unwrap(); + self.graphics_state = Some(futures::executor::block_on(GraphicsState::new(event_loop))); } - fn user_event(&mut self, _: &ActiveEventLoop, event: CustomEvent) { + fn user_event(&mut self, _: &ActiveEventLoop, event: WinitEvent) { match event { - CustomEvent::UiUpdate => { - if let Some(window) = &self.window { - window.request_redraw(); - } - } - CustomEvent::ScheduleBrowserWork(instant) => { - self.cef_schedule = Some(instant); + WinitEvent::TryLoopCefWorkWhenResizing { window_size } => { + let Some(frame_buffer) = &self.frame_buffer else { + return; + }; + if window_size.width != frame_buffer.width() || window_size.height != frame_buffer.height() { + let _ = self.event_loop_proxy.send_event(WinitEvent::TryLoopCefWorkWhenResizing { window_size }); + self.cef_context.work(); + }; } + WinitEvent::UIUpdate { frame_buffer } => { + let Some(graphics_state) = &mut self.graphics_state else { + println!("Graphics state must be initialized in UIUpdate"); + return; + }; + graphics_state.update_ui_texture(&frame_buffer); + graphics_state.window.request_redraw(); + self.frame_buffer = Some(frame_buffer); + } // WinitEvent::ViewportResized { + // top_left + // } => { + // let Some(graphics_state) = &mut self.graphics_state else { + // println!("Graphics state must be initialized in load_frame_buffer"); + // return Err("Graphics state must be initialized".to_string()); + // }; + // graphics_state._viewport_top_left = top_left; + // } + // , + // WinitEvent::ViewportUpdate { texture } => { + // let Some(graphics_state) = &mut self.graphics_state else { + // println!("Graphics state must be initialized in load_frame_buffer"); + // return Err("Graphics state must be initialized".to_string()); + // }; + // graphics_state.viewport_texture = Some(texture.texture); + // } } } @@ -95,55 +106,20 @@ impl ApplicationHandler for WinitApp { 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(); + // The WaitUntil control flow for the timed event loop will not run when the window is being resized, so CEF needs to be manually worked + let window_size = WindowSize::new(physical_size.width, physical_size.height); + let _ = self.shared_render_data.with(|shared_render_data| { + *shared_render_data = Some(window_size.clone()); + }); self.cef_context.notify_of_resize(); + let _ = self.event_loop_proxy.send_event(WinitEvent::TryLoopCefWorkWhenResizing { window_size }); } - WindowEvent::RedrawRequested => { - self.cef_context.work(); - - 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), - } - } - }) - .unwrap(); + let Some(graphics_state) = &mut self.graphics_state else { + println!("Graphics state must be initialized before RedrawRequested"); + return; + }; + let _ = graphics_state.render(); } _ => {} } diff --git a/desktop/src/cef.rs b/desktop/src/cef.rs index eaa2170354..3d5f162115 100644 --- a/desktop/src/cef.rs +++ b/desktop/src/cef.rs @@ -1,29 +1,37 @@ -use crate::FrameBuffer; -use std::time::Instant; - mod context; mod input; mod internal; mod scheme_handler; -pub(crate) use context::{Context, InitError, Initialized, Setup, SetupError}; +use std::sync::{Arc, Mutex, MutexGuard, PoisonError}; -pub(crate) trait CefEventHandler: Clone { - fn window_size(&self) -> WindowSize; - fn draw(&self, frame_buffer: FrameBuffer) -> bool; - /// 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); -} +pub(crate) use context::{Context, InitError, Initialized, Setup, SetupError}; -#[derive(Clone)] +#[derive(Clone, Debug, PartialEq)] pub(crate) struct WindowSize { - pub(crate) width: usize, - pub(crate) height: usize, + pub(crate) width: u32, + pub(crate) height: u32, } impl WindowSize { - pub(crate) fn new(width: usize, height: usize) -> Self { + pub(crate) fn new(width: u32, height: u32) -> Self { Self { width, height } } } + +// Shared between the CEF render handler and the Winit app +#[derive(Clone, Default)] +pub(crate) struct WindowSizeHandle { + inner: Arc>>, +} + +impl WindowSizeHandle { + pub fn with

(&self, p: P) -> Result<(), PoisonError>>> + where + P: FnOnce(&mut Option), + { + let mut guard = self.inner.lock()?; + p(&mut guard); + Ok(()) + } +} diff --git a/desktop/src/cef/context.rs b/desktop/src/cef/context.rs index a19172e189..75339786b7 100644 --- a/desktop/src/cef/context.rs +++ b/desktop/src/cef/context.rs @@ -3,10 +3,14 @@ use cef::{App, BrowserSettings, Client, DictionaryValue, ImplBrowser, ImplBrowse use cef::{Browser, CefString, Settings, api_hash, args::Args, execute_process}; use thiserror::Error; use winit::event::WindowEvent; +use winit::event_loop::EventLoopProxy; +use crate::WinitEvent; +use crate::cef::WindowSizeHandle; + +use super::input; use super::input::InputState; use super::scheme_handler::{FRONTEND_DOMAIN, GRAPHITE_SCHEME}; -use super::{CefEventHandler, input}; use super::internal::{AppImpl, ClientImpl, NonBrowserAppImpl, RenderHandlerImpl}; @@ -57,7 +61,7 @@ impl Context { }) } - pub(crate) fn init(self, event_handler: impl CefEventHandler) -> Result, InitError> { + pub(crate) fn init(self, event_loop_proxy: EventLoopProxy, shared_window_size: WindowSizeHandle) -> Result, InitError> { let settings = Settings { windowless_rendering_enabled: 1, multi_threaded_message_loop: 0, @@ -66,14 +70,14 @@ impl Context { }; // Attention! Wrapping this in an extra App is necessary, otherwise the program still compiles but segfaults - let mut cef_app = App::new(AppImpl::new(event_handler.clone())); + let mut cef_app = App::new(AppImpl::new()); let result = initialize(Some(self.args.as_main_args()), Some(&settings), Some(&mut cef_app), std::ptr::null_mut()); if result != 1 { return Err(InitError::InitializationFailed); } - let render_handler = RenderHandlerImpl::new(event_handler.clone()); + let render_handler = RenderHandlerImpl::new(event_loop_proxy, shared_window_size); let mut client = Client::new(ClientImpl::new(RenderHandler::new(render_handler))); let url = CefString::from(format!("{GRAPHITE_SCHEME}://{FRONTEND_DOMAIN}/").as_str()); @@ -117,9 +121,7 @@ impl Context { } pub(crate) fn notify_of_resize(&self) { - if let Some(browser) = &self.browser { - browser.host().unwrap().was_resized(); - } + self.browser.as_ref().expect("browser must be initialized").host().unwrap().was_resized(); } } diff --git a/desktop/src/cef/internal/app.rs b/desktop/src/cef/internal/app.rs index effed9d492..ca68cf23f4 100644 --- a/desktop/src/cef/internal/app.rs +++ b/desktop/src/cef/internal/app.rs @@ -2,27 +2,22 @@ use cef::rc::{Rc, RcImpl}; use cef::sys::{_cef_app_t, cef_base_ref_counted_t}; use cef::{BrowserProcessHandler, ImplApp, SchemeRegistrar, WrapApp}; -use crate::cef::CefEventHandler; use crate::cef::scheme_handler::GraphiteSchemeHandlerFactory; use super::browser_process_handler::BrowserProcessHandlerImpl; -pub(crate) struct AppImpl { +pub(crate) struct AppImpl { object: *mut RcImpl<_cef_app_t, Self>, - event_handler: H, } -impl AppImpl { - pub(crate) fn new(event_handler: H) -> Self { - Self { - object: std::ptr::null_mut(), - event_handler, - } +impl AppImpl { + pub(crate) fn new() -> Self { + Self { object: std::ptr::null_mut() } } } -impl ImplApp for AppImpl { +impl ImplApp for AppImpl { fn browser_process_handler(&self) -> Option { - Some(BrowserProcessHandler::new(BrowserProcessHandlerImpl::new(self.event_handler.clone()))) + Some(BrowserProcessHandler::new(BrowserProcessHandlerImpl::new())) } fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) { @@ -34,19 +29,16 @@ impl ImplApp for AppImpl { } } -impl Clone for AppImpl { +impl Clone for AppImpl { fn clone(&self) -> Self { unsafe { let rc_impl = &mut *self.object; rc_impl.interface.add_ref(); } - Self { - object: self.object, - event_handler: self.event_handler.clone(), - } + Self { object: self.object } } } -impl Rc for AppImpl { +impl Rc for AppImpl { fn as_base(&self) -> &cef_base_ref_counted_t { unsafe { let base = &*self.object; @@ -54,7 +46,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..dc6514b82a 100644 --- a/desktop/src/cef/internal/browser_process_handler.rs +++ b/desktop/src/cef/internal/browser_process_handler.rs @@ -1,26 +1,19 @@ -use std::time::{Duration, Instant}; - use cef::rc::{Rc, RcImpl}; use cef::sys::{_cef_browser_process_handler_t, cef_base_ref_counted_t, cef_browser_process_handler_t}; use cef::{CefString, ImplBrowserProcessHandler, SchemeHandlerFactory, WrapBrowserProcessHandler}; -use crate::cef::CefEventHandler; use crate::cef::scheme_handler::{GRAPHITE_SCHEME, GraphiteSchemeHandlerFactory}; -pub(crate) struct BrowserProcessHandlerImpl { +pub(crate) struct BrowserProcessHandlerImpl { object: *mut RcImpl, - event_handler: H, } -impl BrowserProcessHandlerImpl { - pub(crate) fn new(event_handler: H) -> Self { - Self { - object: std::ptr::null_mut(), - event_handler, - } +impl BrowserProcessHandlerImpl { + pub(crate) fn new() -> Self { + Self { object: std::ptr::null_mut() } } } -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()))); } @@ -28,25 +21,18 @@ impl ImplBrowserProcessHandler for BrowserProcessHandlerImpl fn get_raw(&self) -> *mut _cef_browser_process_handler_t { self.object.cast() } - - fn on_schedule_message_pump_work(&self, delay_ms: i64) { - self.event_handler.schedule_cef_message_loop_work(Instant::now() + Duration::from_millis(delay_ms as u64)); - } } -impl Clone for BrowserProcessHandlerImpl { +impl Clone for BrowserProcessHandlerImpl { fn clone(&self) -> Self { unsafe { let rc_impl = &mut *self.object; rc_impl.interface.add_ref(); } - Self { - object: self.object, - event_handler: self.event_handler.clone(), - } + Self { object: self.object } } } -impl Rc for BrowserProcessHandlerImpl { +impl Rc for BrowserProcessHandlerImpl { fn as_base(&self) -> &cef_base_ref_counted_t { unsafe { let base = &*self.object; @@ -54,7 +40,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..bbf29be3f8 100644 --- a/desktop/src/cef/internal/render_handler.rs +++ b/desktop/src/cef/internal/render_handler.rs @@ -1,38 +1,44 @@ 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 winit::event_loop::EventLoopProxy; -use crate::FrameBuffer; -use crate::cef::CefEventHandler; +use crate::WinitEvent; +use crate::cef::WindowSizeHandle; +use crate::render::FrameBuffer; -pub(crate) struct RenderHandlerImpl { +pub(crate) struct RenderHandlerImpl { object: *mut RcImpl<_cef_render_handler_t, Self>, - event_handler: H, + event_loop_proxy: EventLoopProxy, + window_size: WindowSizeHandle, } -impl RenderHandlerImpl { - pub(crate) fn new(event_handler: H) -> Self { + +impl RenderHandlerImpl { + pub(crate) fn new(event_loop_proxy: EventLoopProxy, window_size: WindowSizeHandle) -> Self { Self { object: std::ptr::null_mut(), - event_handler, + event_loop_proxy, + window_size, } } } -impl ImplRenderHandler for RenderHandlerImpl { +impl ImplRenderHandler for RenderHandlerImpl { fn view_rect(&self, _browser: Option<&mut Browser>, rect: Option<&mut Rect>) { if let Some(rect) = rect { - let view = self.event_handler.window_size(); - *rect = Rect { - x: 0, - y: 0, - width: view.width as i32, - height: view.height as i32, - }; + let _ = self.window_size.with(|window_size| { + *rect = Rect { + x: 0, + y: 0, + width: window_size.as_ref().map(|w| w.width).unwrap_or(1) as i32, + height: window_size.as_ref().map(|w| w.height).unwrap_or(1) as i32, + }; + }); } } fn on_paint( &self, - browser: Option<&mut Browser>, + _browser: Option<&mut Browser>, _type_: PaintElementType, _dirty_rect_count: usize, _dirty_rects: Option<&Rect>, @@ -42,14 +48,9 @@ impl ImplRenderHandler for RenderHandlerImpl { ) { let buffer_size = (width * height * 4) as usize; 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 frame_buffer = FrameBuffer::new(buffer_slice.to_vec(), width as u32, height as u32).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(); - } - } + let _ = self.event_loop_proxy.send_event(WinitEvent::UIUpdate { frame_buffer }); } fn get_raw(&self) -> *mut _cef_render_handler_t { @@ -57,7 +58,7 @@ impl ImplRenderHandler for RenderHandlerImpl { } } -impl Clone for RenderHandlerImpl { +impl Clone for RenderHandlerImpl { fn clone(&self) -> Self { unsafe { let rc_impl = &mut *self.object; @@ -65,11 +66,12 @@ impl Clone for RenderHandlerImpl { } Self { object: self.object, - event_handler: self.event_handler.clone(), + event_loop_proxy: self.event_loop_proxy.clone(), + window_size: self.window_size.clone(), } } } -impl Rc for RenderHandlerImpl { +impl Rc for RenderHandlerImpl { fn as_base(&self) -> &cef_base_ref_counted_t { unsafe { let base = &*self.object; @@ -77,7 +79,7 @@ impl Rc for RenderHandlerImpl { } } } -impl WrapRenderHandler for RenderHandlerImpl { +impl WrapRenderHandler for RenderHandlerImpl { fn wrap_rc(&mut self, object: *mut RcImpl<_cef_render_handler_t, Self>) { self.object = object; } diff --git a/desktop/src/main.rs b/desktop/src/main.rs index 824f469d0a..4c57b90bdd 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -1,137 +1,31 @@ use std::fmt::Debug; use std::process::exit; -use std::sync::{Arc, Mutex, MutexGuard, PoisonError}; -use std::time::Instant; use tracing_subscriber::EnvFilter; -use winit::event_loop::{EventLoop, EventLoopProxy}; +use winit::event_loop::EventLoop; mod cef; use cef::Setup; mod render; -use render::{FrameBuffer, GraphicsState}; +use render::FrameBuffer; mod app; use app::WinitApp; -#[derive(Debug)] -pub(crate) enum CustomEvent { - UiUpdate, - ScheduleBrowserWork(Instant), -} +use crate::cef::{WindowSize, WindowSizeHandle}; #[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)) } - } -} - -pub(crate) struct WindowStateHandle { - inner: Arc>, -} - -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 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 } - } -} - -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) - } - - 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 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(); - } +pub(crate) enum WinitEvent { + // Constantly run CEF when resizing until the cef ui overlay matches the current window size + // This is because the ResumeTimeReached event loop does not run when the window is being resized + TryLoopCefWorkWhenResizing { window_size: WindowSize }, + // Called from the on_paint callback in OffscreenRenderHandler, and if the buffer is different than the previous buffer size + UIUpdate { frame_buffer: FrameBuffer }, + // Called from the javascript binding to onResize for the canvas + // ViewportResized { top_left: (u32, u32) }, + // // Called from the editor if the render node is evaluated and returns an UpdateViewport message + // ViewportUpdate { texture: wgpu::TextureView }, } fn main() { @@ -146,20 +40,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(); + let shared_window_data = WindowSizeHandle::default(); - window_state.with(|s| s.event_loop_proxy = Some(event_loop.create_proxy())).unwrap(); + let event_loop = EventLoop::::with_user_event().build().unwrap(); - let cef_context = match cef_context.init(CefHandler::new(window_state.clone())) { + let cef_context = match cef_context.init(event_loop.create_proxy(), shared_window_data.clone()) { Ok(c) => c, Err(cef::InitError::InitializationFailed) => { tracing::error!("Cef initialization failed"); @@ -169,7 +54,7 @@ fn main() { tracing::info!("Cef initialized successfully"); - let mut winit_app = WinitApp::new(window_state, cef_context); + let mut winit_app = WinitApp::new(event_loop.create_proxy(), shared_window_data, cef_context); event_loop.run_app(&mut winit_app).unwrap(); } diff --git a/desktop/src/render.rs b/desktop/src/render.rs index e6f07b46bc..5263930ff5 100644 --- a/desktop/src/render.rs +++ b/desktop/src/render.rs @@ -1,16 +1,17 @@ use std::sync::Arc; use thiserror::Error; -use winit::window::Window; +use winit::{event_loop::ActiveEventLoop, window::Window}; pub(crate) struct FrameBuffer { buffer: Vec, - width: usize, - height: usize, + width: u32, + height: u32, } + 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()) @@ -21,11 +22,11 @@ impl std::fmt::Debug for FrameBuffer { #[derive(Error, Debug)] pub(crate) enum FrameBufferError { #[error("Invalid buffer size {buffer_size}, expected {expected_size} for width {width} multiplied with height {height} multiplied by 4 channels")] - InvalidSize { buffer_size: usize, expected_size: usize, width: usize, height: usize }, + InvalidSize { buffer_size: usize, expected_size: usize, width: u32, height: u32 }, } impl FrameBuffer { - pub(crate) fn new(buffer: Vec, width: usize, height: usize) -> Result { + pub(crate) fn new(buffer: Vec, width: u32, height: u32) -> Result { let fb = Self { buffer, width, height }; fb.validate_size()?; Ok(fb) @@ -35,19 +36,19 @@ impl FrameBuffer { &self.buffer } - pub(crate) fn width(&self) -> usize { + pub(crate) fn width(&self) -> u32 { self.width } - pub(crate) fn height(&self) -> usize { + pub(crate) fn height(&self) -> u32 { self.height } fn validate_size(&self) -> Result<(), FrameBufferError> { - if self.buffer.len() != self.width * self.height * 4 { + if self.buffer.len() != (self.width * self.height * 4) as usize { Err(FrameBufferError::InvalidSize { buffer_size: self.buffer.len(), - expected_size: self.width * self.height * 4, + expected_size: (self.width * self.height * 4) as usize, width: self.width, height: self.height, }) @@ -59,18 +60,35 @@ impl FrameBuffer { #[derive(Debug)] pub(crate) struct GraphicsState { + pub window: Arc, surface: wgpu::Surface<'static>, + config: wgpu::SurfaceConfiguration, device: wgpu::Device, queue: wgpu::Queue, - config: wgpu::SurfaceConfiguration, - texture: Option, - bind_group: Option, render_pipeline: wgpu::RenderPipeline, sampler: wgpu::Sampler, + + // Cached texture for UI rendering + ui_texture: Option, + ui_bind_group: Option, + // Cached texture for node graph output + // viewport_texture: Option, + // // Returned from CEF js event callback + // viewport_top_left: (u32, u32), } impl GraphicsState { - pub(crate) async fn new(window: Arc) -> Self { + pub(crate) async fn new(event_loop: &ActiveEventLoop) -> Self { + let window = Arc::new( + event_loop + .create_window( + Window::default_attributes() + .with_title("CEF Offscreen Rendering Test") + .with_inner_size(winit::dpi::LogicalSize::new(800, 600)), + ) + .unwrap(), + ); + let size = window.inner_size(); let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { @@ -78,7 +96,7 @@ impl GraphicsState { ..Default::default() }); - let surface = instance.create_surface(window).unwrap(); + let surface = instance.create_surface(window.clone()).unwrap(); let adapter = instance .request_adapter(&wgpu::RequestAdapterOptions { @@ -196,49 +214,30 @@ impl GraphicsState { cache: None, }); - let mut graphics_state = Self { + let graphics_state = Self { + window, surface, device, queue, config, - texture: None, - bind_group: None, + ui_texture: None, + ui_bind_group: None, render_pipeline, sampler, }; - // Initialize with a test pattern so we always have something to render - let width = 800; - let height = 600; - let initial_data = vec![34u8; width * height * 4]; // Gray texture #222222FF - - let fb = FrameBuffer::new(initial_data, width, height) - .map_err(|e| { - panic!("Failed to create initial FrameBuffer: {}", e); - }) - .unwrap(); - - graphics_state.update_texture(&fb); - 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 update_texture(&mut self, frame_buffer: &FrameBuffer) { + pub(crate) fn update_ui_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; - if width > 0 && height > 0 && (self.config.width != width || self.config.height != height) { - self.config.width = width; + // Resize the surface if the dimensions changed + if self.config.width != width || self.config.height != height { self.config.height = height; + self.config.width = width; self.surface.configure(&self.device, &self.config); } @@ -294,8 +293,8 @@ impl GraphicsState { label: Some("texture_bind_group"), }); - self.texture = Some(texture); - self.bind_group = Some(bind_group); + self.ui_texture = Some(texture); + self.ui_bind_group = Some(bind_group); } pub(crate) fn render(&mut self) -> Result<(), wgpu::SurfaceError> { @@ -321,7 +320,7 @@ impl GraphicsState { }); render_pass.set_pipeline(&self.render_pipeline); - if let Some(bind_group) = &self.bind_group { + if let Some(bind_group) = &self.ui_bind_group { render_pass.set_bind_group(0, bind_group, &[]); render_pass.draw(0..6, 0..1); // Draw 3 vertices for fullscreen triangle } else { diff --git a/package.json b/package.json index 5528659282..ce74983719 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "scripts": { "---------- DEV SERVER ----------": "", "start": "cd frontend && npm start", + "start-desktop": "cd frontend && npm run build && cargo run -p graphite-desktop", "profiling": "cd frontend && npm run profiling", "production": "cd frontend && npm run production", "---------- BUILDS ----------": "",