| // Copyright 2024 the Vello Authors | |
| // SPDX-License-Identifier: Apache-2.0 OR MIT | |
| //! Simple example. | |
| use anyhow::Result; | |
| use std::sync::Arc; | |
| use vello::kurbo::{Affine, Circle, Ellipse, Line, RoundedRect, Stroke}; | |
| use vello::peniko::Color; | |
| use vello::peniko::color::palette; | |
| use vello::util::{RenderContext, RenderSurface}; | |
| use vello::{AaConfig, Renderer, RendererOptions, Scene}; | |
| use winit::application::ApplicationHandler; | |
| use winit::dpi::LogicalSize; | |
| use winit::event::WindowEvent; | |
| use winit::event_loop::{ActiveEventLoop, EventLoop}; | |
| use winit::window::Window; | |
| use vello::wgpu::{self, CurrentSurfaceTexture}; | |
| #[derive(Debug)] | |
| enum RenderState { | |
| /// `RenderSurface` and `Window` for active rendering. | |
| Active { | |
| surface: Box<RenderSurface<'static>>, | |
| valid_surface: bool, | |
| window: Arc<Window>, | |
| }, | |
| /// Cache a window so that it can be reused when the app is resumed after being suspended. | |
| Suspended(Option<Arc<Window>>), | |
| } | |
| struct SimpleVelloApp { | |
| /// 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 | |
| renderers: Vec<Option<Renderer>>, | |
| /// State for our example where we store the winit Window and the wgpu Surface | |
| state: RenderState, | |
| /// 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, | |
| } | |
| impl ApplicationHandler for SimpleVelloApp { | |
| 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 { | |
| surface: Box::new(surface), | |
| valid_surface: true, | |
| window, | |
| }; | |
| } | |
| fn suspended(&mut self, _event_loop: &ActiveEventLoop) { | |
| if let RenderState::Active { window, .. } = &self.state { | |
| self.state = RenderState::Suspended(Some(window.clone())); | |
| } | |
| } | |
| fn window_event( | |
| &mut self, | |
| event_loop: &ActiveEventLoop, | |
| window_id: winit::window::WindowId, | |
| event: WindowEvent, | |
| ) { | |
| // Only process events for our window, and only when we have a surface. | |
| let (surface, valid_surface, window) = match &mut self.state { | |
| RenderState::Active { | |
| surface, | |
| valid_surface, | |
| window, | |
| } if window.id() == window_id => (surface, valid_surface, window), | |
| _ => 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) => { | |
| if size.width != 0 && size.height != 0 { | |
| self.context | |
| .resize_surface(surface, size.width, size.height); | |
| *valid_surface = true; | |
| } else { | |
| *valid_surface = false; | |
| } | |
| } | |
| // This is where all the rendering happens | |
| WindowEvent::RedrawRequested => { | |
| if !*valid_surface { | |
| return; | |
| } | |
| // 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 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]; | |
| // Render to a texture, which we will later copy into the surface | |
| self.renderers[surface.dev_id] | |
| .as_mut() | |
| .unwrap() | |
| .render_to_texture( | |
| &device_handle.device, | |
| &device_handle.queue, | |
| &self.scene, | |
| &surface.target_view, | |
| &vello::RenderParams { | |
| base_color: palette::css::BLACK, // Background color | |
| width, | |
| height, | |
| antialiasing_method: AaConfig::Msaa16, | |
| }, | |
| ) | |
| .expect("failed to render to surface"); | |
| // Get the surface's texture | |
| let surface_texture = match surface.surface.get_current_texture() { | |
| CurrentSurfaceTexture::Success(surface_texture) => surface_texture, | |
| CurrentSurfaceTexture::Outdated | CurrentSurfaceTexture::Suboptimal(_) => { | |
| self.context.configure_surface(surface); | |
| window.request_redraw(); | |
| return; | |
| } | |
| CurrentSurfaceTexture::Occluded | CurrentSurfaceTexture::Timeout => { | |
| window.request_redraw(); | |
| return; | |
| } | |
| CurrentSurfaceTexture::Lost => panic!("Surface was lost"), | |
| CurrentSurfaceTexture::Validation => { | |
| panic!("Validation error getting surface") | |
| } | |
| }; | |
| // Perform the copy | |
| let mut encoder = | |
| device_handle | |
| .device | |
| .create_command_encoder(&wgpu::CommandEncoderDescriptor { | |
| label: Some("Surface Blit"), | |
| }); | |
| surface.blitter.copy( | |
| &device_handle.device, | |
| &mut encoder, | |
| &surface.target_view, | |
| &surface_texture | |
| .texture | |
| .create_view(&wgpu::TextureViewDescriptor::default()), | |
| ); | |
| device_handle.queue.submit([encoder.finish()]); | |
| // Queue the texture to be presented on the surface | |
| device_handle.queue.present(surface_texture); | |
| device_handle.device.poll(wgpu::PollType::Poll).unwrap(); | |
| } | |
| _ => {} | |
| } | |
| } | |
| } | |
| fn main() -> Result<()> { | |
| // Setup a bunch of state: | |
| let mut app = SimpleVelloApp { | |
| 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 app) | |
| .expect("Couldn't run event loop"); | |
| Ok(()) | |
| } | |
| /// Helper function that creates a Winit window and returns it (wrapped in an Arc for sharing between threads) | |
| fn create_winit_window(event_loop: &ActiveEventLoop) -> Arc<Window> { | |
| let attr = Window::default_attributes() | |
| .with_inner_size(LogicalSize::new(1044, 800)) | |
| .with_resizable(true) | |
| .with_title("Vello Shapes"); | |
| Arc::new(event_loop.create_window(attr).unwrap()) | |
| } | |
| /// Helper function that creates a vello `Renderer` for a given `RenderContext` and `RenderSurface` | |
| fn create_vello_renderer(render_cx: &RenderContext, surface: &RenderSurface<'_>) -> Renderer { | |
| Renderer::new( | |
| &render_cx.devices[surface.dev_id].device, | |
| RendererOptions::default(), | |
| ) | |
| .expect("Couldn't create renderer") | |
| } | |
| /// Add shapes to a vello scene. This does not actually render the shapes, but adds them | |
| /// to the Scene data structure which represents a set of objects to draw. | |
| fn add_shapes_to_scene(scene: &mut Scene) { | |
| // Draw an outlined rectangle | |
| let stroke = Stroke::new(6.0); | |
| let rect = RoundedRect::new(10.0, 10.0, 240.0, 240.0, 20.0); | |
| let rect_stroke_color = Color::new([0.9804, 0.702, 0.5294, 1.]); | |
| scene.stroke(&stroke, Affine::IDENTITY, rect_stroke_color, None, &rect); | |
| // Draw a filled circle | |
| let circle = Circle::new((420.0, 200.0), 120.0); | |
| let circle_fill_color = Color::new([0.9529, 0.5451, 0.6588, 1.]); | |
| scene.fill( | |
| vello::peniko::Fill::NonZero, | |
| Affine::IDENTITY, | |
| circle_fill_color, | |
| None, | |
| &circle, | |
| ); | |
| // Draw a filled ellipse | |
| let ellipse = Ellipse::new((250.0, 420.0), (100.0, 160.0), -90.0); | |
| let ellipse_fill_color = Color::new([0.7961, 0.651, 0.9686, 1.]); | |
| scene.fill( | |
| vello::peniko::Fill::NonZero, | |
| Affine::IDENTITY, | |
| ellipse_fill_color, | |
| None, | |
| &ellipse, | |
| ); | |
| // Draw a straight line | |
| let line = Line::new((260.0, 20.0), (620.0, 100.0)); | |
| let line_stroke_color = Color::new([0.5373, 0.7059, 0.9804, 1.]); | |
| scene.stroke(&stroke, Affine::IDENTITY, line_stroke_color, None, &line); | |
| } |