From 65d9316356c5141fe1dfb7f6ddbfd629f40b6594 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Tue, 9 Jul 2024 21:13:45 +0900 Subject: [PATCH 1/2] Migrate examples to winit's new "app" API --- Cargo.lock | 109 +++++++++++++- examples/simple/Cargo.toml | 2 +- examples/simple/src/main.rs | 274 +++++++++++++++++++----------------- 3 files changed, 249 insertions(+), 136 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8d18edb9d..aecc9a255 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1443,6 +1443,30 @@ dependencies = [ "objc2-quartz-core", ] +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.5.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + [[package]] name = "objc2-core-data" version = "0.2.2" @@ -1467,6 +1491,18 @@ dependencies = [ "objc2-metal", ] +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2", + "objc2-contacts", + "objc2-foundation", +] + [[package]] name = "objc2-encode" version = "4.0.3" @@ -1486,6 +1522,18 @@ dependencies = [ "objc2", ] +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + [[package]] name = "objc2-metal" version = "0.2.2" @@ -1511,6 +1559,61 @@ dependencies = [ "objc2-metal", ] +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.5.0", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.5.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -3064,14 +3167,15 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winit" -version = "0.30.0" +version = "0.30.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea9e6d5d66cbf702e0dd820302144f51b69a95acdc495dd98ca280ff206562b1" +checksum = "49f45a7b7e2de6af35448d7718dab6d95acec466eb3bb7a56f4d31d1af754004" dependencies = [ "ahash", "android-activity", "atomic-waker", "bitflags 2.5.0", + "block2", "bytemuck", "calloop", "cfg_aliases 0.2.1", @@ -3087,6 +3191,7 @@ dependencies = [ "objc2", "objc2-app-kit", "objc2-foundation", + "objc2-ui-kit", "orbclient", "percent-encoding", "pin-project", diff --git a/examples/simple/Cargo.toml b/examples/simple/Cargo.toml index 02e4e3d74..b41486e9f 100644 --- a/examples/simple/Cargo.toml +++ b/examples/simple/Cargo.toml @@ -15,4 +15,4 @@ workspace = true vello = { version = "0.2.0", path = "../../vello" } anyhow = "1.0.86" pollster = "0.3.0" -winit = "0.30.0" +winit = "0.30.3" diff --git a/examples/simple/src/main.rs b/examples/simple/src/main.rs index 1b4245b97..9f335d7f4 100644 --- a/examples/simple/src/main.rs +++ b/examples/simple/src/main.rs @@ -8,6 +8,7 @@ use vello::kurbo::{Affine, Circle, Ellipse, Line, RoundedRect, Stroke}; use vello::peniko::Color; use vello::util::{RenderContext, RenderSurface}; use vello::{AaConfig, Renderer, RendererOptions, Scene}; +use winit::application::ApplicationHandler; use winit::dpi::LogicalSize; use winit::event::*; use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; @@ -27,149 +28,156 @@ enum RenderState<'s> { Suspended(Option>), } -fn main() -> Result<()> { - // Setup a bunch of state: - - // The vello RenderContext which is a global context that lasts for the lifetime of the application - let mut render_cx = RenderContext::new(); +struct RenderStateWithContext<'s> { + // The vello RenderContext which is a global context that lasts for the + // lifetime of the application + context: RenderContext, // An array of renderers, one per wgpu device - let mut renderers: Vec> = vec![]; + renderers: Vec>, // State for our example where we store the winit Window and the wgpu Surface - let mut render_state = RenderState::Suspended(None); + state: RenderState<'s>, - // A vello Scene which is a data structure which allows one to build up a description a scene to be drawn - // (with paths, fills, images, text, etc) which is then passed to a renderer for rendering - let mut scene = Scene::new(); - - // Create and run a winit event loop - let event_loop = EventLoop::new()?; - #[allow(deprecated)] - event_loop - .run(move |event, event_loop| match event { - // Setup renderer. In winit apps it is recommended to do setup in Event::Resumed - // for best cross-platform compatibility - Event::Resumed => { - let RenderState::Suspended(cached_window) = &mut render_state else { - return; - }; - - // Get the winit window cached in a previous Suspended event or else create a new window - let window = cached_window - .take() - .unwrap_or_else(|| create_winit_window(event_loop)); - - // Create a vello Surface - let size = window.inner_size(); - let surface_future = render_cx.create_surface( - window.clone(), - size.width, - size.height, - wgpu::PresentMode::AutoVsync, - ); - let surface = pollster::block_on(surface_future).expect("Error creating surface"); - - // Create a vello Renderer for the surface (using its device id) - renderers.resize_with(render_cx.devices.len(), || None); - renderers[surface.dev_id] - .get_or_insert_with(|| create_vello_renderer(&render_cx, &surface)); - - // Save the Window and Surface to a state variable - render_state = RenderState::Active(ActiveRenderState { window, surface }); - - event_loop.set_control_flow(ControlFlow::Poll); - } + // A vello Scene which is a data structure which allows one to build up a + // description a scene to be drawn (with paths, fills, images, text, etc) + // which is then passed to a renderer for rendering + scene: Scene, +} - // Save window state on suspend - Event::Suspended => { - if let RenderState::Active(state) = &render_state { - render_state = RenderState::Suspended(Some(state.window.clone())); - } - event_loop.set_control_flow(ControlFlow::Wait); +impl<'s> ApplicationHandler for RenderStateWithContext<'s> { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + let RenderState::Suspended(cached_window) = &mut self.state else { + return; + }; + + // Get the winit window cached in a previous Suspended event or else create a new window + let window = cached_window + .take() + .unwrap_or_else(|| create_winit_window(event_loop)); + + // Create a vello Surface + let size = window.inner_size(); + let surface_future = self.context.create_surface( + window.clone(), + size.width, + size.height, + wgpu::PresentMode::AutoVsync, + ); + let surface = pollster::block_on(surface_future).expect("Error creating surface"); + + // Create a vello Renderer for the surface (using its device id) + self.renderers + .resize_with(self.context.devices.len(), || None); + self.renderers[surface.dev_id] + .get_or_insert_with(|| create_vello_renderer(&self.context, &surface)); + + // Save the Window and Surface to a state variable + self.state = RenderState::Active(ActiveRenderState { window, surface }); + + event_loop.set_control_flow(ControlFlow::Poll); + } + + fn suspended(&mut self, event_loop: &ActiveEventLoop) { + if let RenderState::Active(state) = &self.state { + self.state = RenderState::Suspended(Some(state.window.clone())); + } + event_loop.set_control_flow(ControlFlow::Wait); + } + + fn window_event( + &mut self, + event_loop: &ActiveEventLoop, + window_id: winit::window::WindowId, + event: WindowEvent, + ) { + // Ignore the event (return from the function) if + // - we have no render_state + // - OR the window id of the event doesn't match the window id of our render_state + // + // Else extract a mutable reference to the render state from its containing option for use below + let render_state = match &mut self.state { + RenderState::Active(state) if state.window.id() == window_id => state, + _ => return, + }; + + match event { + // Exit the event loop when a close is requested (e.g. window's close button is pressed) + WindowEvent::CloseRequested => event_loop.exit(), + + // Resize the surface when the window is resized + WindowEvent::Resized(size) => { + self.context + .resize_surface(&mut render_state.surface, size.width, size.height); + render_state.window.request_redraw(); } - Event::WindowEvent { - ref event, - window_id, - } => { - // Ignore the event (return from the function) if - // - we have no render_state - // - OR the window id of the event doesn't match the window id of our render_state - // - // Else extract a mutable reference to the render state from its containing option for use below - let render_state = match &mut render_state { - RenderState::Active(state) if state.window.id() == window_id => state, - _ => return, - }; - - match event { - // Exit the event loop when a close is requested (e.g. window's close button is pressed) - WindowEvent::CloseRequested => event_loop.exit(), - - // Resize the surface when the window is resized - WindowEvent::Resized(size) => { - render_cx.resize_surface( - &mut render_state.surface, - size.width, - size.height, - ); - render_state.window.request_redraw(); - } - - // This is where all the rendering happens - WindowEvent::RedrawRequested => { - // Empty the scene of objects to draw. You could create a new Scene each time, but in this case - // the same Scene is reused so that the underlying memory allocation can also be reused. - scene.reset(); - - // Re-add the objects to draw to the scene. - add_shapes_to_scene(&mut scene); - - // Get the RenderSurface (surface + config) - let surface = &render_state.surface; - - // Get the window size - let width = surface.config.width; - let height = surface.config.height; - - // Get a handle to the device - let device_handle = &render_cx.devices[surface.dev_id]; - - // Get the surface's texture - let surface_texture = surface - .surface - .get_current_texture() - .expect("failed to get surface texture"); - - // Render to the surface's texture - renderers[surface.dev_id] - .as_mut() - .unwrap() - .render_to_surface( - &device_handle.device, - &device_handle.queue, - &scene, - &surface_texture, - &vello::RenderParams { - base_color: Color::BLACK, // Background color - width, - height, - antialiasing_method: AaConfig::Msaa16, - }, - ) - .expect("failed to render to surface"); - - // Queue the texture to be presented on the surface - surface_texture.present(); - - device_handle.device.poll(wgpu::Maintain::Poll); - } - _ => {} - } + // This is where all the rendering happens + WindowEvent::RedrawRequested => { + // Empty the scene of objects to draw. You could create a new Scene each time, but in this case + // the same Scene is reused so that the underlying memory allocation can also be reused. + self.scene.reset(); + + // Re-add the objects to draw to the scene. + add_shapes_to_scene(&mut self.scene); + + // Get the RenderSurface (surface + config) + let surface = &render_state.surface; + + // Get the window size + let width = surface.config.width; + let height = surface.config.height; + + // Get a handle to the device + let device_handle = &self.context.devices[surface.dev_id]; + + // Get the surface's texture + let surface_texture = surface + .surface + .get_current_texture() + .expect("failed to get surface texture"); + + // Render to the surface's texture + self.renderers[surface.dev_id] + .as_mut() + .unwrap() + .render_to_surface( + &device_handle.device, + &device_handle.queue, + &self.scene, + &surface_texture, + &vello::RenderParams { + base_color: Color::BLACK, // Background color + width, + height, + antialiasing_method: AaConfig::Msaa16, + }, + ) + .expect("failed to render to surface"); + + // Queue the texture to be presented on the surface + surface_texture.present(); + + device_handle.device.poll(wgpu::Maintain::Poll); } _ => {} - }) + } + } +} + +fn main() -> Result<()> { + // Setup a bunch of state: + let mut state = RenderStateWithContext { + context: RenderContext::new(), + renderers: vec![], + state: RenderState::Suspended(None), + scene: Scene::new(), + }; + + // Create and run a winit event loop + let event_loop = EventLoop::new()?; + event_loop + .run_app(&mut state) .expect("Couldn't run event loop"); Ok(()) } From 44631253d2c562a2a71b5f4fc0286420fa6dba10 Mon Sep 17 00:00:00 2001 From: Hiroaki Yutani Date: Mon, 15 Jul 2024 21:29:01 +0900 Subject: [PATCH 2/2] Rename to SimpleVelloApp --- examples/simple/src/main.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/simple/src/main.rs b/examples/simple/src/main.rs index 9f335d7f4..5e5c581ad 100644 --- a/examples/simple/src/main.rs +++ b/examples/simple/src/main.rs @@ -28,7 +28,7 @@ enum RenderState<'s> { Suspended(Option>), } -struct RenderStateWithContext<'s> { +struct SimpleVelloApp<'s> { // The vello RenderContext which is a global context that lasts for the // lifetime of the application context: RenderContext, @@ -45,7 +45,7 @@ struct RenderStateWithContext<'s> { scene: Scene, } -impl<'s> ApplicationHandler for RenderStateWithContext<'s> { +impl<'s> ApplicationHandler for SimpleVelloApp<'s> { fn resumed(&mut self, event_loop: &ActiveEventLoop) { let RenderState::Suspended(cached_window) = &mut self.state else { return; @@ -167,7 +167,7 @@ impl<'s> ApplicationHandler for RenderStateWithContext<'s> { fn main() -> Result<()> { // Setup a bunch of state: - let mut state = RenderStateWithContext { + let mut app = SimpleVelloApp { context: RenderContext::new(), renderers: vec![], state: RenderState::Suspended(None), @@ -177,7 +177,7 @@ fn main() -> Result<()> { // Create and run a winit event loop let event_loop = EventLoop::new()?; event_loop - .run_app(&mut state) + .run_app(&mut app) .expect("Couldn't run event loop"); Ok(()) }