Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Example overhaul #4

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
[workspace]
resolver = "2"
members = ["examples/with_winit", "examples/run_wasm", "examples/scenes"]
members = [
"examples/with_winit",
"examples/run_wasm",
"examples/scenes",
"examples/tiger",
"examples/util",
]

[workspace.package]
edition = "2021"
Expand Down
13 changes: 13 additions & 0 deletions examples/tiger/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "tiger"
edition.workspace = true
version.workspace = true
license.workspace = true
repository.workspace = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
util = { path = "../util" }
vello = { workspace = true }
vello_svg = { path = "../.." }
34 changes: 34 additions & 0 deletions examples/tiger/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2024 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT

use std::path::Path;

use vello::Scene;

fn main() {
// Reading the svg file into a string
let assets_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../assets/")
.canonicalize()
.unwrap();
let tiger_path = assets_dir.join("Ghostscript_Tiger.svg");
let tiger_source =
std::fs::read_to_string(tiger_path).expect("Couldn't read svg file {tiger_path:?}");

// Parsing the source into an usvg tree
let fontdb = vello_svg::usvg::fontdb::Database::new();
let svg = vello_svg::usvg::Tree::from_str(
&tiger_source,
&vello_svg::usvg::Options::default(),
&fontdb,
)
.expect("Failed to parse svg file {tiger_path:?}");

// Rendering the tree onto a vello scene
let mut scene = Scene::new();
vello_svg::render_tree(&mut scene, &svg);

// Display the svg
util::display(svg.size().width() as u32, svg.size().height() as u32, scene)
.expect("Error while displaying svg");
}
15 changes: 15 additions & 0 deletions examples/util/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "util"
edition.workspace = true
version.workspace = true
license.workspace = true
repository.workspace = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
vello = { workspace = true }
pollster = "0.3"
wgpu = "0.19.3"
winit = "0.29.12"
anyhow = "1.0.81"
169 changes: 169 additions & 0 deletions examples/util/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// Copyright 2024 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT

use std::{num::NonZeroUsize, sync::Arc};

use vello::{
peniko::Color,
util::{RenderContext, RenderSurface},
AaConfig, Renderer, RendererOptions, Scene,
};
use winit::{
dpi::LogicalSize,
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::{Window, WindowBuilder},
};

// Simple struct to hold the state of the renderer
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>>),
}

pub fn display(width: u32, height: u32, scene: Scene) -> anyhow::Result<()> {
// 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);

// 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(|| {
Arc::new(
WindowBuilder::new()
.with_inner_size(LogicalSize::new(width, height))
.with_resizable(false)
.with_title("Vello SVG Example Display")
.build(event_loop)
.unwrap(),
)
});

// 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(|| {
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("Couldn't create renderer")
});

// 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(),

// This is where all the rendering happens
WindowEvent::RedrawRequested => {
// 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(())
}