Improve event loop

This commit is contained in:
Adam
2025-07-23 19:35:12 -07:00
parent 30abc92900
commit 22391cae13
11 changed files with 203 additions and 326 deletions
Generated
+5 -20
View File
@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 4
[[package]] [[package]]
name = "Inflector" name = "Inflector"
@@ -632,7 +632,7 @@ checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81"
dependencies = [ dependencies = [
"serde", "serde",
"termcolor", "termcolor",
"unicode-width 0.1.14", "unicode-width",
] ]
[[package]] [[package]]
@@ -691,7 +691,7 @@ dependencies = [
"encode_unicode", "encode_unicode",
"libc", "libc",
"once_cell", "once_cell",
"unicode-width 0.2.1", "unicode-width",
"windows-sys 0.60.2", "windows-sys 0.60.2",
] ]
@@ -1796,12 +1796,9 @@ dependencies = [
name = "graphite-desktop" name = "graphite-desktop"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"base64 0.22.1",
"bytemuck",
"cef", "cef",
"graphite-editor", "futures",
"include_dir", "include_dir",
"pollster",
"thiserror 2.0.12", "thiserror 2.0.12",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
@@ -2332,7 +2329,7 @@ checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd"
dependencies = [ dependencies = [
"console", "console",
"portable-atomic", "portable-atomic",
"unicode-width 0.2.1", "unicode-width",
"unit-prefix", "unit-prefix",
"web-time", "web-time",
] ]
@@ -3599,12 +3596,6 @@ dependencies = [
"windows-sys 0.59.0", "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]] [[package]]
name = "portable-atomic" name = "portable-atomic"
version = "1.11.1" version = "1.11.1"
@@ -5289,12 +5280,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"
[[package]]
name = "unicode-width"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.2.1" version = "0.2.1"
+4
View File
@@ -154,6 +154,10 @@ tinyvec = { version = "1", features = ["std"] }
criterion = { version = "0.5", features = ["html_reports"] } criterion = { version = "0.5", features = ["html_reports"] }
iai-callgrind = { version = "0.12.3" } iai-callgrind = { version = "0.12.3" }
ndarray = "0.16.1" 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] [profile.dev]
opt-level = 1 opt-level = 1
+16 -18
View File
@@ -8,25 +8,23 @@ repository = ""
edition = "2024" edition = "2024"
rust-version = "1.87" rust-version = "1.87"
[features] # [features]
default = ["gpu"] # default = ["gpu"]
gpu = ["graphite-editor/gpu"] # gpu = ["graphite-editor/gpu"]
[dependencies] [dependencies]
# Local dependencies # # Local dependencies
graphite-editor = { path = "../editor", features = [ # graphite-editor = { path = "../editor", features = [
"gpu", # "gpu",
"ron", # "ron",
"vello", # "vello",
"decouple-execution", # "decouple-execution",
] } # ] }
wgpu = { workspace = true } wgpu = { workspace = true }
winit = { workspace = true, features = ["serde"] } winit = { workspace = true, features = ["serde"] }
base64.workspace = true thiserror = { workspace = true }
thiserror.workspace = true futures = { workspace = true }
pollster = "0.3" cef = { workspace = true }
cef = "138.5.0" include_dir = { workspace = true }
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } tracing-subscriber = { workspace = true }
tracing = "0.1.41" tracing = { workspace = true }
bytemuck = { version = "1.23.1", features = ["derive"] }
include_dir = "0.7.4"
+76 -100
View File
@@ -1,8 +1,8 @@
use crate::CustomEvent; use crate::WindowSizeHandle;
use crate::WindowState; use crate::WinitEvent;
use crate::WindowStateHandle; use crate::cef::WindowSize;
use crate::render::FrameBuffer;
use crate::render::GraphicsState; use crate::render::GraphicsState;
use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use std::time::Instant; use std::time::Instant;
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
@@ -10,79 +10,90 @@ use winit::event::StartCause;
use winit::event::WindowEvent; use winit::event::WindowEvent;
use winit::event_loop::ActiveEventLoop; use winit::event_loop::ActiveEventLoop;
use winit::event_loop::ControlFlow; use winit::event_loop::ControlFlow;
use winit::window::Window; use winit::event_loop::EventLoopProxy;
use winit::window::WindowId; use winit::window::WindowId;
use crate::cef; use crate::cef;
pub(crate) struct WinitApp { pub(crate) struct WinitApp {
pub(crate) window_state: WindowStateHandle, pub(crate) event_loop_proxy: EventLoopProxy<WinitEvent>,
//
pub(crate) shared_render_data: WindowSizeHandle,
pub(crate) graphics_state: Option<GraphicsState>,
pub(crate) cef_context: cef::Context<cef::Initialized>, pub(crate) cef_context: cef::Context<cef::Initialized>,
pub(crate) window: Option<Arc<Window>>, // Cached frame buffer from CEF, used to check if mouse is on a transparent pixel
cef_schedule: Option<Instant>, pub(crate) frame_buffer: Option<FrameBuffer>,
} }
impl WinitApp { impl WinitApp {
pub(crate) fn new(window_state: WindowStateHandle, cef_context: cef::Context<cef::Initialized>) -> Self { pub(crate) fn new(elp: EventLoopProxy<WinitEvent>, shared_render_data: WindowSizeHandle, cef_context: cef::Context<cef::Initialized>) -> Self {
Self { Self {
window_state, event_loop_proxy: elp,
shared_render_data,
cef_context, cef_context,
window: None, graphics_state: None,
cef_schedule: Some(Instant::now()), frame_buffer: None,
} }
} }
} }
impl ApplicationHandler<CustomEvent> for WinitApp { impl ApplicationHandler<WinitEvent> for WinitApp {
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { // Runs on every event, but when resume time is reached (100x per second) it does the CEF work and queues a new timer.
let timeout = Instant::now() + Duration::from_millis(10); fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
let wait_until = timeout.min(self.cef_schedule.unwrap_or(timeout)); match cause {
event_loop.set_control_flow(ControlFlow::WaitUntil(wait_until)); // When the event loop starts running, queue the timer.
} StartCause::Init => {
event_loop.set_control_flow(ControlFlow::WaitUntil(Instant::now() + Duration::from_millis(10)));
fn new_events(&mut self, _event_loop: &ActiveEventLoop, _cause: StartCause) { }
if let Some(schedule) = self.cef_schedule // When the timer expires, run the CEF work event and queue a new timer.
&& schedule < Instant::now() StartCause::ResumeTimeReached { .. } => {
{ self.cef_context.work();
self.cef_schedule = None; event_loop.set_control_flow(ControlFlow::WaitUntil(Instant::now() + Duration::from_millis(10)));
self.cef_context.work(); }
_ => {}
} }
} }
fn resumed(&mut self, event_loop: &ActiveEventLoop) { fn resumed(&mut self, event_loop: &ActiveEventLoop) {
self.window_state self.graphics_state = Some(futures::executor::block_on(GraphicsState::new(event_loop)));
.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();
} }
fn user_event(&mut self, _: &ActiveEventLoop, event: CustomEvent) { fn user_event(&mut self, _: &ActiveEventLoop, event: WinitEvent) {
match event { match event {
CustomEvent::UiUpdate => { WinitEvent::TryLoopCefWorkWhenResizing { window_size } => {
if let Some(window) = &self.window { let Some(frame_buffer) = &self.frame_buffer else {
window.request_redraw(); return;
} };
} if window_size.width != frame_buffer.width() || window_size.height != frame_buffer.height() {
CustomEvent::ScheduleBrowserWork(instant) => { let _ = self.event_loop_proxy.send_event(WinitEvent::TryLoopCefWorkWhenResizing { window_size });
self.cef_schedule = Some(instant); 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<CustomEvent> for WinitApp {
event_loop.exit(); event_loop.exit();
} }
WindowEvent::Resized(physical_size) => { WindowEvent::Resized(physical_size) => {
self.window_state // 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
.with(|s| { let window_size = WindowSize::new(physical_size.width, physical_size.height);
let width = physical_size.width as usize; let _ = self.shared_render_data.with(|shared_render_data| {
let height = physical_size.height as usize; *shared_render_data = Some(window_size.clone());
s.width = Some(width); });
s.height = Some(height);
if let Some(graphics_state) = &mut s.graphics_state {
graphics_state.resize(width, height);
}
})
.unwrap();
self.cef_context.notify_of_resize(); self.cef_context.notify_of_resize();
let _ = self.event_loop_proxy.send_event(WinitEvent::TryLoopCefWorkWhenResizing { window_size });
} }
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
self.cef_context.work(); let Some(graphics_state) = &mut self.graphics_state else {
println!("Graphics state must be initialized before RedrawRequested");
self.window_state return;
.with(|s| { };
if let WindowState { let _ = graphics_state.render();
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();
} }
_ => {} _ => {}
} }
+24 -11
View File
@@ -1,29 +1,42 @@
use crate::FrameBuffer;
use std::time::Instant;
mod context; mod context;
mod input; mod input;
mod internal; mod internal;
mod scheme_handler; mod scheme_handler;
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
pub(crate) use context::{Context, InitError, Initialized, Setup, SetupError}; pub(crate) use context::{Context, InitError, Initialized, Setup, SetupError};
pub(crate) trait CefEventHandler: Clone { pub(crate) trait CefEventHandler: Clone {
fn window_size(&self) -> WindowSize; fn window_size(&self) -> WindowSize;
fn draw(&self, frame_buffer: FrameBuffer) -> bool; fn on_paint(&self, buffer: *const u8, width: u32, height: u32);
/// 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, Debug, PartialEq)]
pub(crate) struct WindowSize { pub(crate) struct WindowSize {
pub(crate) width: usize, pub(crate) width: u32,
pub(crate) height: usize, pub(crate) height: u32,
} }
impl WindowSize { impl WindowSize {
pub(crate) fn new(width: usize, height: usize) -> Self { pub(crate) fn new(width: u32, height: u32) -> Self {
Self { width, height } Self { width, height }
} }
} }
// Shared between the CEF render handler and the Winit app
#[derive(Clone, Default)]
pub(crate) struct WindowSizeHandle {
inner: Arc<Mutex<Option<WindowSize>>>,
}
impl WindowSizeHandle {
pub fn with<P>(&self, p: P) -> Result<(), PoisonError<MutexGuard<Option<WindowSize>>>>
where
P: FnOnce(&mut Option<WindowSize>),
{
let mut guard = self.inner.lock()?;
p(&mut guard);
Ok(())
}
}
+1 -3
View File
@@ -117,9 +117,7 @@ impl Context<Initialized> {
} }
pub(crate) fn notify_of_resize(&self) { pub(crate) fn notify_of_resize(&self) {
if let Some(browser) = &self.browser { self.browser.as_ref().expect("browser must be initialized").host().unwrap().was_resized();
browser.host().unwrap().was_resized();
}
} }
} }
@@ -1,5 +1,3 @@
use std::time::{Duration, Instant};
use cef::rc::{Rc, RcImpl}; 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::sys::{_cef_browser_process_handler_t, cef_base_ref_counted_t, cef_browser_process_handler_t};
use cef::{CefString, ImplBrowserProcessHandler, SchemeHandlerFactory, WrapBrowserProcessHandler}; use cef::{CefString, ImplBrowserProcessHandler, SchemeHandlerFactory, WrapBrowserProcessHandler};
@@ -28,10 +26,6 @@ impl<H: CefEventHandler> ImplBrowserProcessHandler for BrowserProcessHandlerImpl
fn get_raw(&self) -> *mut _cef_browser_process_handler_t { fn get_raw(&self) -> *mut _cef_browser_process_handler_t {
self.object.cast() 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<H: CefEventHandler> Clone for BrowserProcessHandlerImpl<H> { impl<H: CefEventHandler> Clone for BrowserProcessHandlerImpl<H> {
+3 -13
View File
@@ -1,8 +1,7 @@
use cef::rc::{Rc, RcImpl}; use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_render_handler_t, cef_base_ref_counted_t}; 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; use crate::cef::CefEventHandler;
pub(crate) struct RenderHandlerImpl<H: CefEventHandler> { pub(crate) struct RenderHandlerImpl<H: CefEventHandler> {
@@ -32,7 +31,7 @@ impl<H: CefEventHandler> ImplRenderHandler for RenderHandlerImpl<H> {
fn on_paint( fn on_paint(
&self, &self,
browser: Option<&mut Browser>, _browser: Option<&mut Browser>,
_type_: PaintElementType, _type_: PaintElementType,
_dirty_rect_count: usize, _dirty_rect_count: usize,
_dirty_rects: Option<&Rect>, _dirty_rects: Option<&Rect>,
@@ -40,16 +39,7 @@ impl<H: CefEventHandler> ImplRenderHandler for RenderHandlerImpl<H> {
width: ::std::os::raw::c_int, width: ::std::os::raw::c_int,
height: ::std::os::raw::c_int, height: ::std::os::raw::c_int,
) { ) {
let buffer_size = (width * height * 4) as usize; self.event_handler.on_paint(buffer, width as u32, height as u32);
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();
}
}
} }
fn get_raw(&self) -> *mut _cef_render_handler_t { fn get_raw(&self) -> *mut _cef_render_handler_t {
+29 -110
View File
@@ -1,7 +1,5 @@
use std::fmt::Debug; use std::fmt::Debug;
use std::process::exit; use std::process::exit;
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
use std::time::Instant;
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
use winit::event_loop::{EventLoop, EventLoopProxy}; use winit::event_loop::{EventLoop, EventLoopProxy};
@@ -10,127 +8,57 @@ mod cef;
use cef::Setup; use cef::Setup;
mod render; mod render;
use render::{FrameBuffer, GraphicsState}; use render::FrameBuffer;
mod app; mod app;
use app::WinitApp; use app::WinitApp;
#[derive(Debug)] use crate::cef::{WindowSize, WindowSizeHandle};
pub(crate) enum CustomEvent {
UiUpdate,
ScheduleBrowserWork(Instant),
}
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct WindowState { pub(crate) enum WinitEvent {
width: Option<usize>, // Constantly run CEF when resizing until the cef ui overlay matches the current window size
height: Option<usize>, // This is because the ResumeTimeReached event loop does not run when the window is being resized
ui_frame_buffer: Option<FrameBuffer>, TryLoopCefWorkWhenResizing { window_size: WindowSize },
_viewport_frame_buffer: Option<FrameBuffer>, // Called from the on_paint callback in OffscreenRenderHandler, and if the buffer is different than the previous buffer size
graphics_state: Option<GraphicsState>, UIUpdate { frame_buffer: FrameBuffer },
event_loop_proxy: Option<EventLoopProxy<CustomEvent>>, // 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
impl WindowState { // ViewportUpdate { texture: wgpu::TextureView },
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<Mutex<WindowState>>,
}
impl WindowStateHandle {
fn with<'a, P>(&self, p: P) -> Result<(), PoisonError<MutexGuard<'a, WindowState>>>
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)] #[derive(Clone)]
struct CefHandler { struct CefHandler {
window_state: WindowStateHandle, event_loop_proxy: EventLoopProxy<WinitEvent>,
window_state: WindowSizeHandle,
} }
impl CefHandler { impl CefHandler {
fn new(window_state: WindowStateHandle) -> Self { fn new(event_loop_proxy: EventLoopProxy<WinitEvent>, window_state: WindowSizeHandle) -> Self {
Self { window_state } Self { event_loop_proxy, window_state }
} }
} }
impl cef::CefEventHandler for CefHandler { impl cef::CefEventHandler for CefHandler {
fn window_size(&self) -> cef::WindowSize { fn window_size(&self) -> cef::WindowSize {
let mut w = 1; let mut window_size = cef::WindowSize::new(1, 1);
let mut h = 1;
self.window_state self.window_state
.with(|s| { .with(|s| {
if let WindowState { if let Some(s) = s {
width: Some(width), window_size = cef::WindowSize::new(s.width as u32, s.height as u32);
height: Some(height),
..
} = s
{
w = *width;
h = *height;
} }
}) })
.unwrap(); .unwrap();
window_size
cef::WindowSize::new(w, h)
} }
fn draw(&self, frame_buffer: FrameBuffer) -> bool { fn on_paint(&self, buffer: *const u8, width: u32, height: u32) {
let mut correct_size = true; let buffer_size = (width * height * 4) as usize;
self.window_state let buffer_slice = unsafe { std::slice::from_raw_parts(buffer, buffer_size) };
.with(|s| { let frame_buffer = FrameBuffer::new(buffer_slice.to_vec(), width, height).expect("Failed to create frame buffer");
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 let _ = self.event_loop_proxy.send_event(WinitEvent::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();
} }
} }
@@ -146,20 +74,11 @@ fn main() {
} }
}; };
let window_state = WindowState::new().handle(); let shared_window_data = WindowSizeHandle::default();
window_state let event_loop = EventLoop::<WinitEvent>::with_user_event().build().unwrap();
.with(|s| {
s.width = Some(1200);
s.height = Some(800);
})
.unwrap();
let event_loop = EventLoop::<CustomEvent>::with_user_event().build().unwrap(); let cef_context = match cef_context.init(CefHandler::new(event_loop.create_proxy(), shared_window_data.clone())) {
window_state.with(|s| s.event_loop_proxy = Some(event_loop.create_proxy())).unwrap();
let cef_context = match cef_context.init(CefHandler::new(window_state.clone())) {
Ok(c) => c, Ok(c) => c,
Err(cef::InitError::InitializationFailed) => { Err(cef::InitError::InitializationFailed) => {
tracing::error!("Cef initialization failed"); tracing::error!("Cef initialization failed");
@@ -169,7 +88,7 @@ fn main() {
tracing::info!("Cef initialized successfully"); 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(); event_loop.run_app(&mut winit_app).unwrap();
} }
+44 -45
View File
@@ -1,16 +1,17 @@
use std::sync::Arc; use std::sync::Arc;
use thiserror::Error; use thiserror::Error;
use winit::window::Window; use winit::{event_loop::ActiveEventLoop, window::Window};
pub(crate) struct FrameBuffer { pub(crate) struct FrameBuffer {
buffer: Vec<u8>, buffer: Vec<u8>,
width: usize, width: u32,
height: usize, height: u32,
} }
impl std::fmt::Debug for FrameBuffer { impl std::fmt::Debug for FrameBuffer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WindowState") f.debug_struct("FrameBuffer")
.field("width", &self.width) .field("width", &self.width)
.field("height", &self.height) .field("height", &self.height)
.field("len", &self.buffer.len()) .field("len", &self.buffer.len())
@@ -21,11 +22,11 @@ impl std::fmt::Debug for FrameBuffer {
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub(crate) enum FrameBufferError { pub(crate) enum FrameBufferError {
#[error("Invalid buffer size {buffer_size}, expected {expected_size} for width {width} multiplied with height {height} multiplied by 4 channels")] #[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 { impl FrameBuffer {
pub(crate) fn new(buffer: Vec<u8>, width: usize, height: usize) -> Result<Self, FrameBufferError> { pub(crate) fn new(buffer: Vec<u8>, width: u32, height: u32) -> Result<Self, FrameBufferError> {
let fb = Self { buffer, width, height }; let fb = Self { buffer, width, height };
fb.validate_size()?; fb.validate_size()?;
Ok(fb) Ok(fb)
@@ -35,19 +36,19 @@ impl FrameBuffer {
&self.buffer &self.buffer
} }
pub(crate) fn width(&self) -> usize { pub(crate) fn width(&self) -> u32 {
self.width self.width
} }
pub(crate) fn height(&self) -> usize { pub(crate) fn height(&self) -> u32 {
self.height self.height
} }
fn validate_size(&self) -> Result<(), FrameBufferError> { 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 { Err(FrameBufferError::InvalidSize {
buffer_size: self.buffer.len(), buffer_size: self.buffer.len(),
expected_size: self.width * self.height * 4, expected_size: (self.width * self.height * 4) as usize,
width: self.width, width: self.width,
height: self.height, height: self.height,
}) })
@@ -59,18 +60,35 @@ impl FrameBuffer {
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct GraphicsState { pub(crate) struct GraphicsState {
pub window: Arc<Window>,
surface: wgpu::Surface<'static>, surface: wgpu::Surface<'static>,
config: wgpu::SurfaceConfiguration,
device: wgpu::Device, device: wgpu::Device,
queue: wgpu::Queue, queue: wgpu::Queue,
config: wgpu::SurfaceConfiguration,
texture: Option<wgpu::Texture>,
bind_group: Option<wgpu::BindGroup>,
render_pipeline: wgpu::RenderPipeline, render_pipeline: wgpu::RenderPipeline,
sampler: wgpu::Sampler, sampler: wgpu::Sampler,
// Cached texture for UI rendering
ui_texture: Option<wgpu::Texture>,
ui_bind_group: Option<wgpu::BindGroup>,
// Cached texture for node graph output
// viewport_texture: Option<wgpu::Texture>,
// // Returned from CEF js event callback
// viewport_top_left: (u32, u32),
} }
impl GraphicsState { impl GraphicsState {
pub(crate) async fn new(window: Arc<Window>) -> 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 size = window.inner_size();
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
@@ -78,7 +96,7 @@ impl GraphicsState {
..Default::default() ..Default::default()
}); });
let surface = instance.create_surface(window).unwrap(); let surface = instance.create_surface(window.clone()).unwrap();
let adapter = instance let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
@@ -196,49 +214,30 @@ impl GraphicsState {
cache: None, cache: None,
}); });
let mut graphics_state = Self { let graphics_state = Self {
window,
surface, surface,
device, device,
queue, queue,
config, config,
texture: None, ui_texture: None,
bind_group: None, ui_bind_group: None,
render_pipeline, render_pipeline,
sampler, 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 graphics_state
} }
pub(crate) fn resize(&mut self, width: usize, height: usize) { pub(crate) fn update_ui_texture(&mut self, frame_buffer: &FrameBuffer) {
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) {
let data = frame_buffer.buffer(); let data = frame_buffer.buffer();
let width = frame_buffer.width() as u32; let width = frame_buffer.width() as u32;
let height = frame_buffer.height() as u32; let height = frame_buffer.height() as u32;
if width > 0 && height > 0 && (self.config.width != width || self.config.height != height) { // Resize the surface if the dimensions changed
self.config.width = width; if self.config.width != width || self.config.height != height {
self.config.height = height; self.config.height = height;
self.config.width = width;
self.surface.configure(&self.device, &self.config); self.surface.configure(&self.device, &self.config);
} }
@@ -294,8 +293,8 @@ impl GraphicsState {
label: Some("texture_bind_group"), label: Some("texture_bind_group"),
}); });
self.texture = Some(texture); self.ui_texture = Some(texture);
self.bind_group = Some(bind_group); self.ui_bind_group = Some(bind_group);
} }
pub(crate) fn render(&mut self) -> Result<(), wgpu::SurfaceError> { pub(crate) fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
@@ -321,7 +320,7 @@ impl GraphicsState {
}); });
render_pass.set_pipeline(&self.render_pipeline); 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.set_bind_group(0, bind_group, &[]);
render_pass.draw(0..6, 0..1); // Draw 3 vertices for fullscreen triangle render_pass.draw(0..6, 0..1); // Draw 3 vertices for fullscreen triangle
} else { } else {
+1
View File
@@ -4,6 +4,7 @@
"scripts": { "scripts": {
"---------- DEV SERVER ----------": "", "---------- DEV SERVER ----------": "",
"start": "cd frontend && npm start", "start": "cd frontend && npm start",
"start-desktop": "cd frontend && npm run build && cargo run -p graphite-desktop",
"profiling": "cd frontend && npm run profiling", "profiling": "cd frontend && npm run profiling",
"production": "cd frontend && npm run production", "production": "cd frontend && npm run production",
"---------- BUILDS ----------": "", "---------- BUILDS ----------": "",