diff --git a/Cargo.lock b/Cargo.lock index 7f533df..6815187 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1014,6 +1014,43 @@ dependencies = [ "libloading", ] +[[package]] +name = "clap" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa91278560fc226a5d9d736cc21e485ff9aad47d26b8ffe1f54cba868b684b9f" +dependencies = [ + "bitflags", + "clap_derive", + "clap_lex", + "is-terminal", + "once_cell", + "strsim", + "termcolor", +] + +[[package]] +name = "clap_derive" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" +dependencies = [ + "heck 0.4.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "cmake" version = "0.1.49" @@ -1422,6 +1459,27 @@ dependencies = [ "serde", ] +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "euclid" version = "0.22.7" @@ -1818,6 +1876,21 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + [[package]] name = "hex" version = "0.4.3" @@ -1925,6 +1998,28 @@ dependencies = [ "mach", ] +[[package]] +name = "io-lifetimes" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" +dependencies = [ + "libc", + "windows-sys 0.42.0", +] + +[[package]] +name = "is-terminal" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" +dependencies = [ + "hermit-abi", + "io-lifetimes", + "rustix", + "windows-sys 0.42.0", +] + [[package]] name = "itoa" version = "1.0.4" @@ -2064,6 +2159,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + [[package]] name = "lock_api" version = "0.4.9" @@ -2480,6 +2581,12 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +[[package]] +name = "os_str_bytes" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" + [[package]] name = "overload" version = "0.1.1" @@ -2617,6 +2724,30 @@ dependencies = [ "toml", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.47" @@ -2785,6 +2916,20 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.36.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.42.0", +] + [[package]] name = "ryu" version = "1.0.11" @@ -3378,7 +3523,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e5bd22c71e77d60140b0bd5be56155a37e5bd14e24f5f87298040d0cc40d7" dependencies = [ - "heck", + "heck 0.3.3", "proc-macro2", "quote", "syn", @@ -4038,6 +4183,7 @@ dependencies = [ name = "with_winit" version = "0.1.0" dependencies = [ + "clap", "console_error_panic_hook", "console_log", "pollster", diff --git a/examples/with_winit/Cargo.toml b/examples/with_winit/Cargo.toml index 0c22baa..aeb7cd5 100644 --- a/examples/with_winit/Cargo.toml +++ b/examples/with_winit/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "with_winit" +description = "An example using vello to render to a winit window" version.workspace = true edition.workspace = true publish = false @@ -13,6 +14,7 @@ winit = "0.27.5" pollster = "0.2.5" # for picosvg roxmltree = "0.13" +clap = { version = "4.1.0", features = ["derive"] } [target.'cfg(target_arch = "wasm32")'.dependencies] console_error_panic_hook = "0.1.7" diff --git a/examples/with_winit/src/main.rs b/examples/with_winit/src/main.rs index 555acce..c17f131 100644 --- a/examples/with_winit/src/main.rs +++ b/examples/with_winit/src/main.rs @@ -18,10 +18,35 @@ mod pico_svg; mod simple_text; mod test_scene; -use vello::{util::RenderContext, Renderer, Scene, SceneBuilder}; +use std::{borrow::Cow, path::PathBuf}; + +use clap::Parser; +use vello::{ + kurbo::{Affine, Vec2}, + util::RenderContext, + Renderer, Scene, SceneBuilder, +}; use winit::{event_loop::EventLoop, window::Window}; -async fn run(event_loop: EventLoop<()>, window: Window) { +#[derive(Parser, Debug)] +#[command(about, long_about = None)] +struct Args { + /// Path to the svg file to render. If not set, the GhostScript Tiger will be rendered + #[arg(long)] + #[cfg(not(target_arch = "wasm32"))] + svg: Option, + /// When rendering an svg, what scale to use + #[arg(long)] + scale: Option, + /// Which scene (index) to start on + /// Switch between scenes with left and right arrow keys + #[arg(long)] + scene: Option, +} + +const TIGER: &'static str = include_str!("../../assets/Ghostscript_Tiger.svg"); + +async fn run(event_loop: EventLoop<()>, window: Window, args: Args) { use winit::{event::*, event_loop::ControlFlow}; let mut render_cx = RenderContext::new().unwrap(); let size = window.inner_size(); @@ -32,8 +57,42 @@ async fn run(event_loop: EventLoop<()>, window: Window) { let mut renderer = Renderer::new(&device_handle.device).unwrap(); let mut simple_text = simple_text::SimpleText::new(); let mut current_frame = 0usize; - let mut scene_ix = 0usize; let mut scene = Scene::new(); + let mut cached_svg_scene = None; + let mut drag = Vec2::default(); + let mut scale = 1f64; + let mut mouse_down = false; + let mut prior_position = None; + let mut svg_static_scale = 1.0; + // We allow looping left and right through the scenes, so use a signed index + let mut scene_ix: i32 = 0; + #[cfg(not(target_arch = "wasm32"))] + let svg_string: Cow<'static, str> = match args.svg { + Some(path) => { + // If an svg file has been specified, show that by default + scene_ix = 2; + let start = std::time::Instant::now(); + eprintln!("Reading svg from {path:?}"); + let svg = std::fs::read_to_string(path) + .expect("Provided path did not point to a file which could be read") + .into(); + eprintln!("Finished reading svg, took {:?}", start.elapsed()); + svg + } + None => { + svg_static_scale = 6.0; + TIGER.into() + } + }; + #[cfg(target_arch = "wasm32")] + let svg_string: Cow<'static, str> = TIGER.into(); + // These are set after choosing the svg, as they overwrite the defaults specified there + if let Some(set_scene) = args.scene { + scene_ix = set_scene; + } + if let Some(set_scale) = args.scale { + svg_static_scale = set_scale; + } event_loop.run(move |event, _, control_flow| match event { Event::WindowEvent { ref event, @@ -45,6 +104,9 @@ async fn run(event_loop: EventLoop<()>, window: Window) { match input.virtual_keycode { Some(VirtualKeyCode::Left) => scene_ix = scene_ix.saturating_sub(1), Some(VirtualKeyCode::Right) => scene_ix = scene_ix.saturating_add(1), + Some(VirtualKeyCode::Escape) => { + *control_flow = ControlFlow::Exit; + } _ => {} } } @@ -53,6 +115,33 @@ async fn run(event_loop: EventLoop<()>, window: Window) { render_cx.resize_surface(&mut surface, size.width, size.height); window.request_redraw(); } + WindowEvent::MouseInput { state, button, .. } => { + if button == &MouseButton::Left { + mouse_down = state == &ElementState::Pressed; + } + } + WindowEvent::MouseWheel { delta, .. } => { + if let MouseScrollDelta::PixelDelta(delta) = delta { + scale += delta.y * 0.1; + scale = scale.clamp(0.1, 10.0); + } + if let MouseScrollDelta::LineDelta(_, y) = delta { + scale += *y as f64 * 0.1; + scale = scale.clamp(0.1, 10.0); + } + } + WindowEvent::CursorLeft { .. } => { + prior_position = None; + } + WindowEvent::CursorMoved { position, .. } => { + let position = Vec2::new(position.x, position.y); + if mouse_down { + if let Some(prior) = prior_position { + drag += (position - prior) * (1.0 / scale); + } + } + prior_position = Some(position); + } _ => {} }, Event::MainEventsCleared => { @@ -64,14 +153,28 @@ async fn run(event_loop: EventLoop<()>, window: Window) { let height = surface.config.height; let device_handle = &render_cx.devices[surface.dev_id]; let mut builder = SceneBuilder::for_scene(&mut scene); - const N_SCENES: usize = 6; - match scene_ix % N_SCENES { + + const N_SCENES: i32 = 6; + // Allow looping forever + scene_ix = scene_ix.rem_euclid(N_SCENES); + // Remainder operation allows negative results, which isn't the right semantics + match scene_ix { 0 => test_scene::render_anim_frame(&mut builder, &mut simple_text, current_frame), 1 => test_scene::render_blend_grid(&mut builder), - 2 => test_scene::render_tiger(&mut builder), + 2 => { + let transform = Affine::scale(scale) * Affine::translate(drag); + test_scene::render_svg_scene( + &mut builder, + &mut cached_svg_scene, + transform, + &svg_string, + svg_static_scale, + ) + } 3 => test_scene::render_brush_transform(&mut builder, current_frame), 4 => test_scene::render_funky_paths(&mut builder), - _ => test_scene::render_scene(&mut builder), + 5 => test_scene::render_scene(&mut builder), + _ => unreachable!("N_SCENES is too large"), } builder.finish(); let surface_texture = surface @@ -96,6 +199,7 @@ async fn run(event_loop: EventLoop<()>, window: Window) { } fn main() { + let args = Args::parse(); #[cfg(not(target_arch = "wasm32"))] { use winit::{dpi::LogicalSize, window::WindowBuilder}; @@ -103,9 +207,10 @@ fn main() { let window = WindowBuilder::new() .with_inner_size(LogicalSize::new(1044, 800)) .with_resizable(true) + .with_title("Vello demo") .build(&event_loop) .unwrap(); - pollster::block_on(run(event_loop, window)); + pollster::block_on(run(event_loop, window, args)); } #[cfg(target_arch = "wasm32")] { diff --git a/examples/with_winit/src/test_scene.rs b/examples/with_winit/src/test_scene.rs index 310f59b..c8ddd5a 100644 --- a/examples/with_winit/src/test_scene.rs +++ b/examples/with_winit/src/test_scene.rs @@ -1,3 +1,5 @@ +use std::time::Instant; + use crate::pico_svg::PicoSvg; use crate::simple_text::SimpleText; use vello::kurbo::{Affine, BezPath, Ellipse, PathEl, Point, Rect}; @@ -45,10 +47,6 @@ pub fn render_funky_paths(sb: &mut SceneBuilder) { ); } -#[allow(unused)] -const N_CIRCLES: usize = 0; - -#[allow(unused)] pub fn render_svg(sb: &mut SceneBuilder, svg: &PicoSvg) { use crate::pico_svg::*; for item in &svg.items { @@ -75,13 +73,25 @@ pub fn render_svg(sb: &mut SceneBuilder, svg: &PicoSvg) { } } -#[allow(unused)] -pub fn render_tiger(sb: &mut SceneBuilder) { - use super::pico_svg::*; - let xml_str = - std::str::from_utf8(include_bytes!("../../assets/Ghostscript_Tiger.svg")).unwrap(); - let svg = PicoSvg::load(xml_str, 6.0).unwrap(); - render_svg(sb, &svg); +pub fn render_svg_scene( + sb: &mut SceneBuilder, + scene: &mut Option, + xform: Affine, + svg: &str, + scale: f64, +) { + let scene_frag = scene.get_or_insert_with(|| { + use super::pico_svg::*; + let start = Instant::now(); + eprintln!("Starting to parse svg"); + let svg = PicoSvg::load(svg, scale).unwrap(); + eprintln!("Parsing svg took {:?}", start.elapsed()); + let mut new_scene = SceneFragment::new(); + let mut builder = SceneBuilder::for_fragment(&mut new_scene); + render_svg(&mut builder, &svg); + new_scene + }); + sb.append(&scene_frag, Some(xform)); } pub fn render_scene(sb: &mut SceneBuilder) { @@ -91,7 +101,6 @@ pub fn render_scene(sb: &mut SceneBuilder) { //render_tiger(sb, false); } -#[allow(unused)] fn render_cardioid(sb: &mut SceneBuilder) { let n = 601; let dth = std::f64::consts::PI * 2.0 / (n as f64); @@ -113,13 +122,12 @@ fn render_cardioid(sb: &mut SceneBuilder) { sb.stroke( &Stroke::new(2.0), Affine::IDENTITY, - Color::rgb8(0, 0, 0), + Color::rgb8(0, 0, 255), None, &path, ); } -#[allow(unused)] fn render_clip_test(sb: &mut SceneBuilder) { const N: usize = 16; const X0: f64 = 50.0; @@ -145,7 +153,7 @@ fn render_clip_test(sb: &mut SceneBuilder) { sb.fill( Fill::NonZero, Affine::IDENTITY, - &Brush::Solid(Color::rgb8(0, 0, 0)), + &Brush::Solid(Color::rgb8(0, 255, 0)), None, &rect, ); @@ -154,7 +162,6 @@ fn render_clip_test(sb: &mut SceneBuilder) { } } -#[allow(unused)] fn render_alpha_test(sb: &mut SceneBuilder) { // Alpha compositing tests. sb.fill( @@ -187,7 +194,6 @@ fn render_alpha_test(sb: &mut SceneBuilder) { sb.pop_layer(); } -#[allow(unused)] pub fn render_blend_grid(sb: &mut SceneBuilder) { const BLEND_MODES: &[Mix] = &[ Mix::Normal, @@ -216,7 +222,6 @@ pub fn render_blend_grid(sb: &mut SceneBuilder) { } } -#[allow(unused)] fn render_blend_square(sb: &mut SceneBuilder, blend: BlendMode, transform: Affine) { // Inspired by https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode let rect = Rect::from_origin_size(Point::new(0., 0.), (200., 200.)); @@ -260,7 +265,6 @@ fn render_blend_square(sb: &mut SceneBuilder, blend: BlendMode, transform: Affin sb.pop_layer(); } -#[allow(unused)] fn blend_square(blend: BlendMode) -> SceneFragment { let mut fragment = SceneFragment::default(); let mut sb = SceneBuilder::for_fragment(&mut fragment); @@ -269,7 +273,6 @@ fn blend_square(blend: BlendMode) -> SceneFragment { fragment } -#[allow(unused)] pub fn render_anim_frame(sb: &mut SceneBuilder, text: &mut SimpleText, i: usize) { use PathEl::*; let rect = Rect::from_origin_size(Point::new(0.0, 0.0), (1000.0, 1000.0)); diff --git a/src/render.rs b/src/render.rs index 619b8f9..4ae51bd 100644 --- a/src/render.rs +++ b/src/render.rs @@ -330,7 +330,7 @@ pub fn render_encoding_full( // in storage rather than workgroup memory. let n_path_aligned = align_up(n_paths as usize, 256); let path_buf = ResourceProxy::new_buf(n_path_aligned as u64 * PATH_SIZE); - let tile_buf = ResourceProxy::new_buf(1 << 20); + let tile_buf = ResourceProxy::new_buf(1 << 24); let path_wgs = (n_paths + shaders::PATH_BBOX_WG - 1) / shaders::PATH_BBOX_WG; recording.dispatch( shaders.tile_alloc,