blob: 84b8822bc477d7d3d25fc42f4d2dfc1afbd10b80 [file] [log] [blame]
// Copyright 2024 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
use anyhow::Result;
use std::num::NonZeroUsize;
use std::sync::Arc;
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::{
dpi::LogicalSize,
event::*,
event_loop::{ControlFlow, EventLoop},
window::{Window, WindowBuilder},
};
// Simple struct to hold the state of the renderer
pub struct ActiveRenderState<'s> {
// The fields MUST be in this order, so that the surface is dropped before the window
surface: RenderSurface<'s>,
window: Arc<Window>,
}
enum RenderState<'s> {
Active(ActiveRenderState<'s>),
// Cache a window so that it can be reused when the app is resumed after being suspended
Suspended(Option<Arc<Window>>),
}
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().unwrap();
// An array of renderers, one per wgpu device
let mut renderers: Vec<Option<Renderer>> = vec![];
// State for our example where we store the winit Window and the wgpu Surface
let mut render_state = RenderState::Suspended(None);
// 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()?;
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);
}
// 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);
}
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);
}
_ => {}
}
}
_ => {}
})
.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: &winit::event_loop::EventLoopWindowTarget<()>) -> Arc<Window> {
Arc::new(
WindowBuilder::new()
.with_inner_size(LogicalSize::new(1044, 800))
.with_resizable(true)
.with_title("Vello Shapes")
.build(event_loop)
.unwrap(),
)
}
/// Helper function that creates a vello Renderer for a given RenderContext and Surface
fn create_vello_renderer(render_cx: &RenderContext, surface: &RenderSurface) -> Renderer {
Renderer::new(
&render_cx.devices[surface.dev_id].device,
RendererOptions {
surface_format: Some(surface.format),
use_cpu: false,
antialiasing_support: vello::AaSupport::all(),
num_init_threads: NonZeroUsize::new(1),
},
)
.expect("Could 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::rgb(0.9804, 0.702, 0.5294);
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::rgb(0.9529, 0.5451, 0.6588);
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::rgb(0.7961, 0.651, 0.9686);
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::rgb(0.5373, 0.7059, 0.9804);
scene.stroke(&stroke, Affine::IDENTITY, line_stroke_color, None, &line);
}