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

25
Cargo.lock generated
View file

@ -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"

View file

@ -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

View file

@ -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 }

View file

@ -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();
}
_ => {}
}

View file

@ -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(())
}
}

View file

@ -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();
}
}

View file

@ -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> {

View file

@ -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 {

View file

@ -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();
}

View file

@ -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 {

View file

@ -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 ----------": "",