Support click and drag for svgs, as well as scene fragment caching (#244)

* Support caching the image, and click and drag motion

* Remove debug print

* Clean up examples to have command line parsing

* Address review comments
This commit is contained in:
Daniel McNab 2023-01-13 19:30:08 +00:00 committed by GitHub
parent 02d8b28439
commit eec111c633
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 286 additions and 30 deletions

148
Cargo.lock generated
View file

@ -1014,6 +1014,43 @@ dependencies = [
"libloading", "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]] [[package]]
name = "cmake" name = "cmake"
version = "0.1.49" version = "0.1.49"
@ -1422,6 +1459,27 @@ dependencies = [
"serde", "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]] [[package]]
name = "euclid" name = "euclid"
version = "0.22.7" version = "0.22.7"
@ -1818,6 +1876,21 @@ dependencies = [
"unicode-segmentation", "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]] [[package]]
name = "hex" name = "hex"
version = "0.4.3" version = "0.4.3"
@ -1925,6 +1998,28 @@ dependencies = [
"mach", "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]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.4" version = "1.0.4"
@ -2064,6 +2159,12 @@ dependencies = [
"pkg-config", "pkg-config",
] ]
[[package]]
name = "linux-raw-sys"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.9" version = "0.4.9"
@ -2480,6 +2581,12 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
[[package]]
name = "os_str_bytes"
version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
[[package]] [[package]]
name = "overload" name = "overload"
version = "0.1.1" version = "0.1.1"
@ -2617,6 +2724,30 @@ dependencies = [
"toml", "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]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.47" version = "1.0.47"
@ -2785,6 +2916,20 @@ dependencies = [
"semver", "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]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.11" version = "1.0.11"
@ -3378,7 +3523,7 @@ version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e5bd22c71e77d60140b0bd5be56155a37e5bd14e24f5f87298040d0cc40d7" checksum = "0a6e5bd22c71e77d60140b0bd5be56155a37e5bd14e24f5f87298040d0cc40d7"
dependencies = [ dependencies = [
"heck", "heck 0.3.3",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn",
@ -4038,6 +4183,7 @@ dependencies = [
name = "with_winit" name = "with_winit"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"clap",
"console_error_panic_hook", "console_error_panic_hook",
"console_log", "console_log",
"pollster", "pollster",

View file

@ -1,5 +1,6 @@
[package] [package]
name = "with_winit" name = "with_winit"
description = "An example using vello to render to a winit window"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
publish = false publish = false
@ -13,6 +14,7 @@ winit = "0.27.5"
pollster = "0.2.5" pollster = "0.2.5"
# for picosvg # for picosvg
roxmltree = "0.13" roxmltree = "0.13"
clap = { version = "4.1.0", features = ["derive"] }
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.7" console_error_panic_hook = "0.1.7"

View file

@ -18,10 +18,35 @@ mod pico_svg;
mod simple_text; mod simple_text;
mod test_scene; 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}; 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<PathBuf>,
/// When rendering an svg, what scale to use
#[arg(long)]
scale: Option<f64>,
/// Which scene (index) to start on
/// Switch between scenes with left and right arrow keys
#[arg(long)]
scene: Option<i32>,
}
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}; use winit::{event::*, event_loop::ControlFlow};
let mut render_cx = RenderContext::new().unwrap(); let mut render_cx = RenderContext::new().unwrap();
let size = window.inner_size(); 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 renderer = Renderer::new(&device_handle.device).unwrap();
let mut simple_text = simple_text::SimpleText::new(); let mut simple_text = simple_text::SimpleText::new();
let mut current_frame = 0usize; let mut current_frame = 0usize;
let mut scene_ix = 0usize;
let mut scene = Scene::new(); 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_loop.run(move |event, _, control_flow| match event {
Event::WindowEvent { Event::WindowEvent {
ref event, ref event,
@ -45,6 +104,9 @@ async fn run(event_loop: EventLoop<()>, window: Window) {
match input.virtual_keycode { match input.virtual_keycode {
Some(VirtualKeyCode::Left) => scene_ix = scene_ix.saturating_sub(1), Some(VirtualKeyCode::Left) => scene_ix = scene_ix.saturating_sub(1),
Some(VirtualKeyCode::Right) => scene_ix = scene_ix.saturating_add(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); render_cx.resize_surface(&mut surface, size.width, size.height);
window.request_redraw(); 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 => { Event::MainEventsCleared => {
@ -64,14 +153,28 @@ async fn run(event_loop: EventLoop<()>, window: Window) {
let height = surface.config.height; let height = surface.config.height;
let device_handle = &render_cx.devices[surface.dev_id]; let device_handle = &render_cx.devices[surface.dev_id];
let mut builder = SceneBuilder::for_scene(&mut scene); 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), 0 => test_scene::render_anim_frame(&mut builder, &mut simple_text, current_frame),
1 => test_scene::render_blend_grid(&mut builder), 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), 3 => test_scene::render_brush_transform(&mut builder, current_frame),
4 => test_scene::render_funky_paths(&mut builder), 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(); builder.finish();
let surface_texture = surface let surface_texture = surface
@ -96,6 +199,7 @@ async fn run(event_loop: EventLoop<()>, window: Window) {
} }
fn main() { fn main() {
let args = Args::parse();
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
{ {
use winit::{dpi::LogicalSize, window::WindowBuilder}; use winit::{dpi::LogicalSize, window::WindowBuilder};
@ -103,9 +207,10 @@ fn main() {
let window = WindowBuilder::new() let window = WindowBuilder::new()
.with_inner_size(LogicalSize::new(1044, 800)) .with_inner_size(LogicalSize::new(1044, 800))
.with_resizable(true) .with_resizable(true)
.with_title("Vello demo")
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();
pollster::block_on(run(event_loop, window)); pollster::block_on(run(event_loop, window, args));
} }
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
{ {

View file

@ -1,3 +1,5 @@
use std::time::Instant;
use crate::pico_svg::PicoSvg; use crate::pico_svg::PicoSvg;
use crate::simple_text::SimpleText; use crate::simple_text::SimpleText;
use vello::kurbo::{Affine, BezPath, Ellipse, PathEl, Point, Rect}; 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) { pub fn render_svg(sb: &mut SceneBuilder, svg: &PicoSvg) {
use crate::pico_svg::*; use crate::pico_svg::*;
for item in &svg.items { for item in &svg.items {
@ -75,13 +73,25 @@ pub fn render_svg(sb: &mut SceneBuilder, svg: &PicoSvg) {
} }
} }
#[allow(unused)] pub fn render_svg_scene(
pub fn render_tiger(sb: &mut SceneBuilder) { sb: &mut SceneBuilder,
use super::pico_svg::*; scene: &mut Option<SceneFragment>,
let xml_str = xform: Affine,
std::str::from_utf8(include_bytes!("../../assets/Ghostscript_Tiger.svg")).unwrap(); svg: &str,
let svg = PicoSvg::load(xml_str, 6.0).unwrap(); scale: f64,
render_svg(sb, &svg); ) {
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) { pub fn render_scene(sb: &mut SceneBuilder) {
@ -91,7 +101,6 @@ pub fn render_scene(sb: &mut SceneBuilder) {
//render_tiger(sb, false); //render_tiger(sb, false);
} }
#[allow(unused)]
fn render_cardioid(sb: &mut SceneBuilder) { fn render_cardioid(sb: &mut SceneBuilder) {
let n = 601; let n = 601;
let dth = std::f64::consts::PI * 2.0 / (n as f64); let dth = std::f64::consts::PI * 2.0 / (n as f64);
@ -113,13 +122,12 @@ fn render_cardioid(sb: &mut SceneBuilder) {
sb.stroke( sb.stroke(
&Stroke::new(2.0), &Stroke::new(2.0),
Affine::IDENTITY, Affine::IDENTITY,
Color::rgb8(0, 0, 0), Color::rgb8(0, 0, 255),
None, None,
&path, &path,
); );
} }
#[allow(unused)]
fn render_clip_test(sb: &mut SceneBuilder) { fn render_clip_test(sb: &mut SceneBuilder) {
const N: usize = 16; const N: usize = 16;
const X0: f64 = 50.0; const X0: f64 = 50.0;
@ -145,7 +153,7 @@ fn render_clip_test(sb: &mut SceneBuilder) {
sb.fill( sb.fill(
Fill::NonZero, Fill::NonZero,
Affine::IDENTITY, Affine::IDENTITY,
&Brush::Solid(Color::rgb8(0, 0, 0)), &Brush::Solid(Color::rgb8(0, 255, 0)),
None, None,
&rect, &rect,
); );
@ -154,7 +162,6 @@ fn render_clip_test(sb: &mut SceneBuilder) {
} }
} }
#[allow(unused)]
fn render_alpha_test(sb: &mut SceneBuilder) { fn render_alpha_test(sb: &mut SceneBuilder) {
// Alpha compositing tests. // Alpha compositing tests.
sb.fill( sb.fill(
@ -187,7 +194,6 @@ fn render_alpha_test(sb: &mut SceneBuilder) {
sb.pop_layer(); sb.pop_layer();
} }
#[allow(unused)]
pub fn render_blend_grid(sb: &mut SceneBuilder) { pub fn render_blend_grid(sb: &mut SceneBuilder) {
const BLEND_MODES: &[Mix] = &[ const BLEND_MODES: &[Mix] = &[
Mix::Normal, 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) { 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 // 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.)); 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(); sb.pop_layer();
} }
#[allow(unused)]
fn blend_square(blend: BlendMode) -> SceneFragment { fn blend_square(blend: BlendMode) -> SceneFragment {
let mut fragment = SceneFragment::default(); let mut fragment = SceneFragment::default();
let mut sb = SceneBuilder::for_fragment(&mut fragment); let mut sb = SceneBuilder::for_fragment(&mut fragment);
@ -269,7 +273,6 @@ fn blend_square(blend: BlendMode) -> SceneFragment {
fragment fragment
} }
#[allow(unused)]
pub fn render_anim_frame(sb: &mut SceneBuilder, text: &mut SimpleText, i: usize) { pub fn render_anim_frame(sb: &mut SceneBuilder, text: &mut SimpleText, i: usize) {
use PathEl::*; use PathEl::*;
let rect = Rect::from_origin_size(Point::new(0.0, 0.0), (1000.0, 1000.0)); let rect = Rect::from_origin_size(Point::new(0.0, 0.0), (1000.0, 1000.0));

View file

@ -330,7 +330,7 @@ pub fn render_encoding_full(
// in storage rather than workgroup memory. // in storage rather than workgroup memory.
let n_path_aligned = align_up(n_paths as usize, 256); 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 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; let path_wgs = (n_paths + shaders::PATH_BBOX_WG - 1) / shaders::PATH_BBOX_WG;
recording.dispatch( recording.dispatch(
shaders.tile_alloc, shaders.tile_alloc,