mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-12-23 10:11:54 +00:00
Improve event loop
This commit is contained in:
parent
30abc92900
commit
22391cae13
11 changed files with 203 additions and 326 deletions
25
Cargo.lock
generated
25
Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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<WinitEvent>,
|
||||
//
|
||||
pub(crate) shared_render_data: WindowSizeHandle,
|
||||
pub(crate) graphics_state: Option<GraphicsState>,
|
||||
pub(crate) cef_context: cef::Context<cef::Initialized>,
|
||||
pub(crate) window: Option<Arc<Window>>,
|
||||
cef_schedule: Option<Instant>,
|
||||
// Cached frame buffer from CEF, used to check if mouse is on a transparent pixel
|
||||
pub(crate) frame_buffer: Option<FrameBuffer>,
|
||||
}
|
||||
|
||||
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 {
|
||||
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<CustomEvent> 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<WinitEvent> 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<CustomEvent> 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();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,42 @@
|
|||
use crate::FrameBuffer;
|
||||
use std::time::Instant;
|
||||
|
||||
mod context;
|
||||
mod input;
|
||||
mod internal;
|
||||
mod scheme_handler;
|
||||
|
||||
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
|
||||
|
||||
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;
|
||||
/// 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);
|
||||
fn on_paint(&self, buffer: *const u8, width: u32, height: u32);
|
||||
}
|
||||
|
||||
#[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<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(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,9 +117,7 @@ impl Context<Initialized> {
|
|||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
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};
|
||||
|
|
@ -28,10 +26,6 @@ impl<H: CefEventHandler> 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<H: CefEventHandler> Clone for BrowserProcessHandlerImpl<H> {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
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;
|
||||
|
||||
pub(crate) struct RenderHandlerImpl<H: CefEventHandler> {
|
||||
|
|
@ -32,7 +31,7 @@ impl<H: CefEventHandler> ImplRenderHandler for RenderHandlerImpl<H> {
|
|||
|
||||
fn on_paint(
|
||||
&self,
|
||||
browser: Option<&mut Browser>,
|
||||
_browser: Option<&mut Browser>,
|
||||
_type_: PaintElementType,
|
||||
_dirty_rect_count: usize,
|
||||
_dirty_rects: Option<&Rect>,
|
||||
|
|
@ -40,16 +39,7 @@ impl<H: CefEventHandler> ImplRenderHandler for RenderHandlerImpl<H> {
|
|||
width: ::std::os::raw::c_int,
|
||||
height: ::std::os::raw::c_int,
|
||||
) {
|
||||
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 draw_successful = self.event_handler.draw(frame_buffer);
|
||||
if !draw_successful {
|
||||
if let Some(browser) = browser {
|
||||
browser.host().unwrap().was_resized();
|
||||
}
|
||||
}
|
||||
self.event_handler.on_paint(buffer, width as u32, height as u32);
|
||||
}
|
||||
|
||||
fn get_raw(&self) -> *mut _cef_render_handler_t {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
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};
|
||||
|
|
@ -10,127 +8,57 @@ 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<usize>,
|
||||
height: Option<usize>,
|
||||
ui_frame_buffer: Option<FrameBuffer>,
|
||||
_viewport_frame_buffer: Option<FrameBuffer>,
|
||||
graphics_state: Option<GraphicsState>,
|
||||
event_loop_proxy: Option<EventLoopProxy<CustomEvent>>,
|
||||
}
|
||||
|
||||
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<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() }
|
||||
}
|
||||
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 },
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct CefHandler {
|
||||
window_state: WindowStateHandle,
|
||||
event_loop_proxy: EventLoopProxy<WinitEvent>,
|
||||
window_state: WindowSizeHandle,
|
||||
}
|
||||
|
||||
impl CefHandler {
|
||||
fn new(window_state: WindowStateHandle) -> Self {
|
||||
Self { window_state }
|
||||
fn new(event_loop_proxy: EventLoopProxy<WinitEvent>, window_state: WindowSizeHandle) -> Self {
|
||||
Self { event_loop_proxy, window_state }
|
||||
}
|
||||
}
|
||||
|
||||
impl cef::CefEventHandler for CefHandler {
|
||||
fn window_size(&self) -> cef::WindowSize {
|
||||
let mut w = 1;
|
||||
let mut h = 1;
|
||||
|
||||
let mut window_size = cef::WindowSize::new(1, 1);
|
||||
self.window_state
|
||||
.with(|s| {
|
||||
if let WindowState {
|
||||
width: Some(width),
|
||||
height: Some(height),
|
||||
..
|
||||
} = s
|
||||
{
|
||||
w = *width;
|
||||
h = *height;
|
||||
if let Some(s) = s {
|
||||
window_size = cef::WindowSize::new(s.width as u32, s.height as u32);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
cef::WindowSize::new(w, h)
|
||||
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();
|
||||
fn on_paint(&self, buffer: *const u8, width: u32, height: u32) {
|
||||
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, height).expect("Failed to create frame buffer");
|
||||
|
||||
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();
|
||||
let _ = self.event_loop_proxy.send_event(WinitEvent::UIUpdate { frame_buffer });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -146,20 +74,11 @@ fn main() {
|
|||
}
|
||||
};
|
||||
|
||||
let window_state = WindowState::new().handle();
|
||||
let shared_window_data = WindowSizeHandle::default();
|
||||
|
||||
window_state
|
||||
.with(|s| {
|
||||
s.width = Some(1200);
|
||||
s.height = Some(800);
|
||||
})
|
||||
.unwrap();
|
||||
let event_loop = EventLoop::<WinitEvent>::with_user_event().build().unwrap();
|
||||
|
||||
let event_loop = EventLoop::<CustomEvent>::with_user_event().build().unwrap();
|
||||
|
||||
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())) {
|
||||
let cef_context = match cef_context.init(CefHandler::new(event_loop.create_proxy(), shared_window_data.clone())) {
|
||||
Ok(c) => c,
|
||||
Err(cef::InitError::InitializationFailed) => {
|
||||
tracing::error!("Cef initialization failed");
|
||||
|
|
@ -169,7 +88,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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<u8>,
|
||||
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<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 };
|
||||
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<Window>,
|
||||
surface: wgpu::Surface<'static>,
|
||||
config: wgpu::SurfaceConfiguration,
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
config: wgpu::SurfaceConfiguration,
|
||||
texture: Option<wgpu::Texture>,
|
||||
bind_group: Option<wgpu::BindGroup>,
|
||||
render_pipeline: wgpu::RenderPipeline,
|
||||
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 {
|
||||
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 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 {
|
||||
|
|
|
|||
|
|
@ -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 ----------": "",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue