mirror of
https://github.com/italicsjenga/vello.git
synced 2025-01-22 09:26:33 +11:00
Merge pull request #314 from linebender/failure
A bit of polishing on the demo
This commit is contained in:
commit
03545e5d9a
8 changed files with 324 additions and 35 deletions
|
@ -104,8 +104,11 @@ async fn render(mut scenes: SceneSet, index: usize, args: &Args) -> Result<()> {
|
|||
resolution: None,
|
||||
base_color: None,
|
||||
interactive: false,
|
||||
complexity: 0,
|
||||
};
|
||||
(example_scene.function)(&mut builder, &mut scene_params);
|
||||
example_scene
|
||||
.function
|
||||
.render(&mut builder, &mut scene_params);
|
||||
let mut transform = Affine::IDENTITY;
|
||||
let (width, height) = if let Some(resolution) = scene_params.resolution {
|
||||
let ratio = resolution.x / resolution.y;
|
||||
|
|
|
@ -16,6 +16,7 @@ vello_svg = { path = "../../integrations/vello_svg" }
|
|||
anyhow = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
image = "0.24.5"
|
||||
rand = "0.8.5"
|
||||
instant = { workspace = true }
|
||||
|
||||
# Used for the `download` command
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
pub mod download;
|
||||
mod images;
|
||||
mod mmark;
|
||||
mod simple_text;
|
||||
mod svg;
|
||||
mod test_scenes;
|
||||
|
@ -25,6 +26,7 @@ pub struct SceneParams<'a> {
|
|||
pub images: &'a mut ImageCache,
|
||||
pub resolution: Option<Vec2>,
|
||||
pub base_color: Option<vello::peniko::Color>,
|
||||
pub complexity: usize,
|
||||
}
|
||||
|
||||
pub struct SceneConfig {
|
||||
|
@ -34,11 +36,20 @@ pub struct SceneConfig {
|
|||
}
|
||||
|
||||
pub struct ExampleScene {
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub function: Box<dyn FnMut(&mut SceneBuilder, &mut SceneParams)>,
|
||||
pub function: Box<dyn TestScene>,
|
||||
pub config: SceneConfig,
|
||||
}
|
||||
|
||||
pub trait TestScene {
|
||||
fn render(&mut self, sb: &mut SceneBuilder, params: &mut SceneParams);
|
||||
}
|
||||
|
||||
impl<F: FnMut(&mut SceneBuilder, &mut SceneParams)> TestScene for F {
|
||||
fn render(&mut self, sb: &mut SceneBuilder, params: &mut SceneParams) {
|
||||
self(sb, params);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SceneSet {
|
||||
pub scenes: Vec<ExampleScene>,
|
||||
}
|
||||
|
|
201
examples/scenes/src/mmark.rs
Normal file
201
examples/scenes/src/mmark.rs
Normal file
|
@ -0,0 +1,201 @@
|
|||
//! A benchmark based on MotionMark 1.2's path benchmark.
|
||||
//! This is roughly comparable to:
|
||||
//!
|
||||
//! https://browserbench.org/MotionMark1.2/developer.html?warmup-length=2000&warmup-frame-count=30&first-frame-minimum-length=0&test-interval=15&display=minimal&tiles=big&controller=adaptive&frame-rate=50&time-measurement=performance&suite-name=MotionMark&test-name=Paths&complexity=1
|
||||
//!
|
||||
//! However, at this point it cannot be directly compared, as we don't accurately
|
||||
//! implement the stroke style parameters, and it has not been carefully validated.
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
use vello::peniko::Color;
|
||||
use vello::{
|
||||
kurbo::{Affine, BezPath, CubicBez, Line, ParamCurve, PathSeg, Point, QuadBez},
|
||||
peniko::Stroke,
|
||||
SceneBuilder,
|
||||
};
|
||||
|
||||
use crate::{SceneParams, TestScene};
|
||||
|
||||
const WIDTH: usize = 1600;
|
||||
const HEIGHT: usize = 900;
|
||||
|
||||
const GRID_WIDTH: i64 = 80;
|
||||
const GRID_HEIGHT: i64 = 40;
|
||||
|
||||
pub struct MMark {
|
||||
elements: Vec<Element>,
|
||||
}
|
||||
|
||||
struct Element {
|
||||
seg: PathSeg,
|
||||
color: Color,
|
||||
width: f64,
|
||||
is_split: bool,
|
||||
grid_point: GridPoint,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct GridPoint(i64, i64);
|
||||
|
||||
impl MMark {
|
||||
pub fn new(n: usize) -> MMark {
|
||||
let mut result = MMark { elements: vec![] };
|
||||
result.resize(n);
|
||||
result
|
||||
}
|
||||
|
||||
fn resize(&mut self, n: usize) {
|
||||
let old_n = self.elements.len();
|
||||
match n.cmp(&old_n) {
|
||||
Ordering::Less => self.elements.truncate(n),
|
||||
Ordering::Greater => {
|
||||
let mut last = self
|
||||
.elements
|
||||
.last()
|
||||
.map(|e| e.grid_point)
|
||||
.unwrap_or(GridPoint(GRID_WIDTH / 2, GRID_HEIGHT / 2));
|
||||
self.elements.extend((old_n..n).map(|_| {
|
||||
let element = Element::new_rand(last);
|
||||
last = element.grid_point;
|
||||
element
|
||||
}));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TestScene for MMark {
|
||||
fn render(&mut self, sb: &mut SceneBuilder, params: &mut SceneParams) {
|
||||
let c = params.complexity;
|
||||
let n = if c < 10 {
|
||||
(c + 1) * 1000
|
||||
} else {
|
||||
((c - 8) * 10000).min(120_000)
|
||||
};
|
||||
self.resize(n);
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut path = BezPath::new();
|
||||
let len = self.elements.len();
|
||||
for (i, element) in self.elements.iter_mut().enumerate() {
|
||||
if path.is_empty() {
|
||||
path.move_to(element.seg.start());
|
||||
}
|
||||
match element.seg {
|
||||
PathSeg::Line(l) => path.line_to(l.p1),
|
||||
PathSeg::Quad(q) => path.quad_to(q.p1, q.p2),
|
||||
PathSeg::Cubic(c) => path.curve_to(c.p1, c.p2, c.p3),
|
||||
}
|
||||
if element.is_split || i == len {
|
||||
// This gets color and width from the last element, original
|
||||
// gets it from the first, but this should not matter.
|
||||
sb.stroke(
|
||||
&Stroke::new(element.width as f32),
|
||||
Affine::IDENTITY,
|
||||
element.color,
|
||||
None,
|
||||
&path,
|
||||
);
|
||||
path.truncate(0); // Should have clear method, to avoid allocations.
|
||||
}
|
||||
if rng.gen::<f32>() > 0.995 {
|
||||
element.is_split ^= true;
|
||||
}
|
||||
}
|
||||
let label = format!("mmark test: {} path elements (up/down to adjust)", n);
|
||||
params.text.add(
|
||||
sb,
|
||||
None,
|
||||
40.0,
|
||||
None,
|
||||
Affine::translate((100.0, 1100.0)),
|
||||
&label,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const COLORS: &[Color] = &[
|
||||
Color::rgb8(0x10, 0x10, 0x10),
|
||||
Color::rgb8(0x80, 0x80, 0x80),
|
||||
Color::rgb8(0xc0, 0xc0, 0xc0),
|
||||
Color::rgb8(0x10, 0x10, 0x10),
|
||||
Color::rgb8(0x80, 0x80, 0x80),
|
||||
Color::rgb8(0xc0, 0xc0, 0xc0),
|
||||
Color::rgb8(0xe0, 0x10, 0x40),
|
||||
];
|
||||
|
||||
impl Element {
|
||||
fn new_rand(last: GridPoint) -> Element {
|
||||
let mut rng = rand::thread_rng();
|
||||
let seg_type = rng.gen_range(0..4);
|
||||
let next = GridPoint::random_point(last);
|
||||
let (grid_point, seg) = if seg_type < 2 {
|
||||
(
|
||||
next,
|
||||
PathSeg::Line(Line::new(last.coordinate(), next.coordinate())),
|
||||
)
|
||||
} else if seg_type < 3 {
|
||||
let p2 = GridPoint::random_point(next);
|
||||
(
|
||||
p2,
|
||||
PathSeg::Quad(QuadBez::new(
|
||||
last.coordinate(),
|
||||
next.coordinate(),
|
||||
p2.coordinate(),
|
||||
)),
|
||||
)
|
||||
} else {
|
||||
let p2 = GridPoint::random_point(next);
|
||||
let p3 = GridPoint::random_point(next);
|
||||
(
|
||||
p3,
|
||||
PathSeg::Cubic(CubicBez::new(
|
||||
last.coordinate(),
|
||||
next.coordinate(),
|
||||
p2.coordinate(),
|
||||
p3.coordinate(),
|
||||
)),
|
||||
)
|
||||
};
|
||||
let color = *COLORS.choose(&mut rng).unwrap();
|
||||
let width = rng.gen::<f64>().powi(5) * 20.0 + 1.0;
|
||||
let is_split = rng.gen();
|
||||
Element {
|
||||
seg,
|
||||
color,
|
||||
width,
|
||||
is_split,
|
||||
grid_point,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const OFFSETS: &[(i64, i64)] = &[(-4, 0), (2, 0), (1, -2), (1, 2)];
|
||||
|
||||
impl GridPoint {
|
||||
fn random_point(last: GridPoint) -> GridPoint {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let offset = OFFSETS.choose(&mut rng).unwrap();
|
||||
let mut x = last.0 + offset.0;
|
||||
if !(0..=GRID_WIDTH).contains(&x) {
|
||||
x -= offset.0 * 2;
|
||||
}
|
||||
let mut y = last.1 + offset.1;
|
||||
if !(0..=GRID_HEIGHT).contains(&y) {
|
||||
y -= offset.1 * 2;
|
||||
}
|
||||
GridPoint(x, y)
|
||||
}
|
||||
|
||||
fn coordinate(&self) -> Point {
|
||||
let scale_x = WIDTH as f64 / ((GRID_WIDTH + 1) as f64);
|
||||
let scale_y = HEIGHT as f64 / ((GRID_HEIGHT + 1) as f64);
|
||||
Point::new(
|
||||
(self.0 as f64 + 0.5) * scale_x,
|
||||
100.0 + (self.1 as f64 + 0.5) * scale_y,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -24,9 +24,23 @@ macro_rules! scene {
|
|||
}
|
||||
|
||||
pub fn test_scenes() -> SceneSet {
|
||||
// For WASM below, must be mutable
|
||||
#[allow(unused_mut)]
|
||||
let mut scenes = vec![
|
||||
let splash_scene = ExampleScene {
|
||||
config: SceneConfig {
|
||||
animated: false,
|
||||
name: "splash_with_tiger".to_owned(),
|
||||
},
|
||||
function: Box::new(splash_with_tiger()),
|
||||
};
|
||||
let mmark_scene = ExampleScene {
|
||||
config: SceneConfig {
|
||||
animated: false,
|
||||
name: "mmark".to_owned(),
|
||||
},
|
||||
function: Box::new(crate::mmark::MMark::new(80_000)),
|
||||
};
|
||||
let scenes = vec![
|
||||
splash_scene,
|
||||
mmark_scene,
|
||||
scene!(funky_paths),
|
||||
scene!(cardioid_and_friends),
|
||||
scene!(animated_text: animated),
|
||||
|
@ -38,14 +52,6 @@ pub fn test_scenes() -> SceneSet {
|
|||
scene!(labyrinth),
|
||||
scene!(base_color_test: animated),
|
||||
];
|
||||
#[cfg(any(target_arch = "wasm32", target_os = "android"))]
|
||||
scenes.push(ExampleScene {
|
||||
config: SceneConfig {
|
||||
animated: false,
|
||||
name: "included_tiger".to_owned(),
|
||||
},
|
||||
function: Box::new(included_tiger()),
|
||||
});
|
||||
|
||||
SceneSet { scenes }
|
||||
}
|
||||
|
@ -491,15 +497,6 @@ fn blend_grid(sb: &mut SceneBuilder, _: &mut SceneParams) {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "wasm32", target_os = "android"))]
|
||||
fn included_tiger() -> impl FnMut(&mut SceneBuilder, &mut SceneParams) {
|
||||
let contents = include_str!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/../assets/Ghostscript_Tiger.svg"
|
||||
));
|
||||
crate::svg::svg_function_of("Ghostscript Tiger".to_string(), move || contents)
|
||||
}
|
||||
|
||||
// Support functions
|
||||
|
||||
fn render_cardioid(sb: &mut SceneBuilder) {
|
||||
|
@ -841,3 +838,39 @@ fn make_diamond(cx: f64, cy: f64) -> [PathEl; 5] {
|
|||
PathEl::ClosePath,
|
||||
]
|
||||
}
|
||||
|
||||
fn splash_screen(sb: &mut SceneBuilder, params: &mut SceneParams) {
|
||||
let strings = [
|
||||
"Vello test",
|
||||
" Arrow keys: switch scenes",
|
||||
" Space: reset transform",
|
||||
" S: toggle stats",
|
||||
" V: toggle vsync",
|
||||
" Q, E: rotate",
|
||||
];
|
||||
// Tweak to make it fit with tiger
|
||||
let a = Affine::scale(0.12) * Affine::translate((-90.0, -50.0));
|
||||
for (i, s) in strings.iter().enumerate() {
|
||||
let text_size = if i == 0 { 60.0 } else { 40.0 };
|
||||
params.text.add(
|
||||
sb,
|
||||
None,
|
||||
text_size,
|
||||
None,
|
||||
a * Affine::translate((100.0, 100.0 + 60.0 * i as f64)),
|
||||
s,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn splash_with_tiger() -> impl FnMut(&mut SceneBuilder, &mut SceneParams) {
|
||||
let contents = include_str!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/../assets/Ghostscript_Tiger.svg"
|
||||
));
|
||||
let mut tiger = crate::svg::svg_function_of("Ghostscript Tiger".to_string(), move || contents);
|
||||
move |sb, params| {
|
||||
tiger(sb, params);
|
||||
splash_screen(sb, params);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,4 +45,4 @@ android_logger = "0.13.0"
|
|||
console_error_panic_hook = "0.1.7"
|
||||
console_log = "0.2"
|
||||
wasm-bindgen-futures = "0.4.33"
|
||||
web-sys = "0.3.60"
|
||||
web-sys = { version = "0.3.60", features = [ "HtmlCollection", "Text" ] }
|
||||
|
|
|
@ -117,6 +117,7 @@ fn run(
|
|||
let mut prior_position: Option<Vec2> = None;
|
||||
// We allow looping left and right through the scenes, so use a signed index
|
||||
let mut scene_ix: i32 = 0;
|
||||
let mut complexity: usize = 0;
|
||||
if let Some(set_scene) = args.scene {
|
||||
scene_ix = set_scene;
|
||||
}
|
||||
|
@ -138,6 +139,8 @@ fn run(
|
|||
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::Up) => complexity += 1,
|
||||
Some(VirtualKeyCode::Down) => complexity = complexity.saturating_sub(1),
|
||||
Some(key @ VirtualKeyCode::Q) | Some(key @ VirtualKeyCode::E) => {
|
||||
if let Some(prior_position) = prior_position {
|
||||
let is_clockwise = key == VirtualKeyCode::E;
|
||||
|
@ -302,8 +305,11 @@ fn run(
|
|||
resolution: None,
|
||||
base_color: None,
|
||||
interactive: true,
|
||||
complexity,
|
||||
};
|
||||
(example_scene.function)(&mut builder, &mut scene_params);
|
||||
example_scene
|
||||
.function
|
||||
.render(&mut builder, &mut scene_params);
|
||||
|
||||
// If the user specifies a base color in the CLI we use that. Otherwise we use any
|
||||
// color specified by the scene. The default is black.
|
||||
|
@ -421,7 +427,7 @@ fn run(
|
|||
let size = window.inner_size();
|
||||
let surface_future = render_cx.create_surface(&window, size.width, size.height);
|
||||
// We need to block here, in case a Suspended event appeared
|
||||
let surface = pollster::block_on(surface_future);
|
||||
let surface = pollster::block_on(surface_future).expect("Error creating surface");
|
||||
render_state = {
|
||||
let render_state = RenderState { window, surface };
|
||||
renderers.resize_with(render_cx.devices.len(), || None);
|
||||
|
@ -461,6 +467,27 @@ enum UserEvent {
|
|||
HotReload,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn display_error_message() -> Option<()> {
|
||||
let window = web_sys::window()?;
|
||||
let document = window.document()?;
|
||||
let elements = document.get_elements_by_tag_name("body");
|
||||
let body = elements.item(0)?;
|
||||
body.set_inner_html(
|
||||
r#"<style>
|
||||
p {
|
||||
margin: 2em 10em;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
</style>
|
||||
<p><a href="https://caniuse.com/webgpu">WebGPU</a>
|
||||
is not enabled. Make sure your browser is updated to
|
||||
<a href="https://chromiumdash.appspot.com/schedule">Chrome M113</a> or
|
||||
another browser compatible with WebGPU.</p>"#,
|
||||
);
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub fn main() -> Result<()> {
|
||||
// TODO: initializing both env_logger and console_logger fails on wasm.
|
||||
// Figure out a more principled approach.
|
||||
|
@ -497,16 +524,21 @@ pub fn main() -> Result<()> {
|
|||
web_sys::window()
|
||||
.and_then(|win| win.document())
|
||||
.and_then(|doc| doc.body())
|
||||
.and_then(|body| body.append_child(&web_sys::Element::from(canvas)).ok())
|
||||
.and_then(|body| body.append_child(canvas.as_ref()).ok())
|
||||
.expect("couldn't append canvas to document body");
|
||||
_ = web_sys::HtmlElement::from(canvas).focus();
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
let size = window.inner_size();
|
||||
let surface = render_cx
|
||||
.create_surface(&window, size.width, size.height)
|
||||
.await;
|
||||
let render_state = RenderState { window, surface };
|
||||
// No error handling here; if the event loop has finished, we don't need to send them the surface
|
||||
run(event_loop, args, scenes, render_cx, render_state);
|
||||
if let Ok(surface) = surface {
|
||||
let render_state = RenderState { window, surface };
|
||||
// No error handling here; if the event loop has finished, we don't need to send them the surface
|
||||
run(event_loop, args, scenes, render_cx, render_state);
|
||||
} else {
|
||||
_ = display_error_message();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
18
src/util.rs
18
src/util.rs
|
@ -50,12 +50,20 @@ impl RenderContext {
|
|||
}
|
||||
|
||||
/// Creates a new surface for the specified window and dimensions.
|
||||
pub async fn create_surface<W>(&mut self, window: &W, width: u32, height: u32) -> RenderSurface
|
||||
pub async fn create_surface<W>(
|
||||
&mut self,
|
||||
window: &W,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<RenderSurface>
|
||||
where
|
||||
W: HasRawWindowHandle + HasRawDisplayHandle,
|
||||
{
|
||||
let surface = unsafe { self.instance.create_surface(window) }.unwrap();
|
||||
let dev_id = self.device(Some(&surface)).await.unwrap();
|
||||
let surface = unsafe { self.instance.create_surface(window) }?;
|
||||
let dev_id = self
|
||||
.device(Some(&surface))
|
||||
.await
|
||||
.ok_or("Error creating device")?;
|
||||
|
||||
let device_handle = &self.devices[dev_id];
|
||||
let capabilities = surface.get_capabilities(&device_handle.adapter);
|
||||
|
@ -75,12 +83,12 @@ impl RenderContext {
|
|||
view_formats: vec![],
|
||||
};
|
||||
surface.configure(&self.devices[dev_id].device, &config);
|
||||
RenderSurface {
|
||||
Ok(RenderSurface {
|
||||
surface,
|
||||
config,
|
||||
dev_id,
|
||||
format,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Resizes the surface to the new dimensions.
|
||||
|
|
Loading…
Add table
Reference in a new issue