From 17a74fb370f782a89386e758727e50e2d1a0a176 Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Fri, 4 Nov 2022 13:15:05 -0700 Subject: [PATCH] Almost rendering tiger We cut'n'pasted the picosvg stuff, kinda ugly. It renders a number of paths of the tiger. I think the gap might be in prefix sums. --- Cargo.lock | 2 + piet-wgsl/Cargo.toml | 6 +- piet-wgsl/shader/coarse.wgsl | 5 +- piet-wgsl/src/main.rs | 1 + piet-wgsl/src/pico_svg.rs | 140 +++++++++++++++++++++++++++++++++++ piet-wgsl/src/render.rs | 6 +- piet-wgsl/src/test_scene.rs | 102 ++++++++++++++++++------- 7 files changed, 231 insertions(+), 31 deletions(-) create mode 100644 piet-wgsl/src/pico_svg.rs diff --git a/Cargo.lock b/Cargo.lock index f2669e2..d82ba57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1174,10 +1174,12 @@ dependencies = [ "bytemuck", "env_logger", "futures-intrusive", + "kurbo 0.8.3", "parking_lot", "piet-scene", "png", "pollster", + "roxmltree", "wgpu", ] diff --git a/piet-wgsl/Cargo.toml b/piet-wgsl/Cargo.toml index e7fa42d..b785eac 100644 --- a/piet-wgsl/Cargo.toml +++ b/piet-wgsl/Cargo.toml @@ -14,4 +14,8 @@ parking_lot = "0.12" bytemuck = { version = "1.12.1", features = ["derive"] } png = "0.17.6" -piet-scene = { path = "../piet-scene" } +piet-scene = { path = "../piet-scene", features = ["kurbo"] } + +# for picosvg, should be split out +roxmltree = "0.13" +kurbo = "0.8.3" diff --git a/piet-wgsl/shader/coarse.wgsl b/piet-wgsl/shader/coarse.wgsl index 642ce73..5943e51 100644 --- a/piet-wgsl/shader/coarse.wgsl +++ b/piet-wgsl/shader/coarse.wgsl @@ -85,7 +85,10 @@ var cmd_limit: u32; // Make sure there is space for a command of given size, plus a jump if needed fn alloc_cmd(size: u32) { if cmd_offset + size >= cmd_limit { - let new_cmd = atomicAdd(&bump.ptcl, PTCL_INCREMENT); + // We might be able to save a little bit of computation here + // by setting the initial value of the bump allocator. + let ptcl_dyn_start = config.width_in_tiles * config.height_in_tiles * PTCL_INITIAL_ALLOC; + let new_cmd = ptcl_dyn_start + atomicAdd(&bump.ptcl, PTCL_INCREMENT); // TODO: robust memory ptcl[cmd_offset] = CMD_JUMP; ptcl[cmd_offset + 1u] = new_cmd; diff --git a/piet-wgsl/src/main.rs b/piet-wgsl/src/main.rs index d8d53ad..8b00a26 100644 --- a/piet-wgsl/src/main.rs +++ b/piet-wgsl/src/main.rs @@ -24,6 +24,7 @@ use test_scene::dump_scene_info; use wgpu::{Device, Limits, Queue}; mod engine; +mod pico_svg; mod render; mod shaders; mod test_scene; diff --git a/piet-wgsl/src/pico_svg.rs b/piet-wgsl/src/pico_svg.rs new file mode 100644 index 0000000..eebe3ec --- /dev/null +++ b/piet-wgsl/src/pico_svg.rs @@ -0,0 +1,140 @@ +//! A loader for a tiny fragment of SVG + +use std::str::FromStr; + +use roxmltree::{Document, Node}; + +use kurbo::{Affine, BezPath}; + +use piet_scene::Color; + +pub struct PicoSvg { + pub items: Vec, +} + +pub enum Item { + Fill(FillItem), + Stroke(StrokeItem), +} + +pub struct StrokeItem { + pub width: f64, + pub color: Color, + pub path: BezPath, +} + +pub struct FillItem { + pub color: Color, + pub path: BezPath, +} + +struct Parser<'a> { + scale: f64, + items: &'a mut Vec, +} + +impl PicoSvg { + pub fn load(xml_string: &str, scale: f64) -> Result> { + let doc = Document::parse(xml_string)?; + let root = doc.root_element(); + let mut items = Vec::new(); + let mut parser = Parser::new(&mut items, scale); + for node in root.children() { + parser.rec_parse(node)?; + } + Ok(PicoSvg { items }) + } +} + +impl<'a> Parser<'a> { + fn new(items: &'a mut Vec, scale: f64) -> Parser<'a> { + Parser { scale, items } + } + + fn rec_parse(&mut self, node: Node) -> Result<(), Box> { + let transform = if self.scale >= 0.0 { + Affine::scale(self.scale) + } else { + Affine::new([-self.scale, 0.0, 0.0, self.scale, 0.0, 1536.0]) + }; + if node.is_element() { + match node.tag_name().name() { + "g" => { + for child in node.children() { + self.rec_parse(child)?; + } + } + "path" => { + let d = node.attribute("d").ok_or("missing 'd' attribute")?; + let bp = BezPath::from_svg(d)?; + let path = transform * bp; + // TODO: default fill color is black, but this is overridden in tiger to this logic. + if let Some(fill_color) = node.attribute("fill") { + if fill_color != "none" { + let color = parse_color(fill_color); + let color = modify_opacity(color, "fill-opacity", node); + self.items.push(Item::Fill(FillItem { + color, + path: path.clone(), + })); + } + } + if let Some(stroke_color) = node.attribute("stroke") { + if stroke_color != "none" { + let width = self.scale.abs() + * f64::from_str( + node.attribute("stroke-width").ok_or("missing width")?, + )?; + let color = parse_color(stroke_color); + let color = modify_opacity(color, "stroke-opacity", node); + self.items + .push(Item::Stroke(StrokeItem { width, color, path })); + } + } + } + _ => (), + } + } + Ok(()) + } +} + +fn parse_color(color: &str) -> Color { + if color.as_bytes()[0] == b'#' { + let mut hex = u32::from_str_radix(&color[1..], 16).unwrap(); + if color.len() == 4 { + hex = (hex >> 8) * 0x110000 + ((hex >> 4) & 0xf) * 0x1100 + (hex & 0xf) * 0x11; + } + let rgba = (hex << 8) + 0xff; + let (r, g, b, a) = ( + (rgba >> 24 & 255) as u8, + ((rgba >> 16) & 255) as u8, + ((rgba >> 8) & 255) as u8, + (rgba & 255) as u8, + ); + Color::rgba8(r, g, b, a) + } else if color.starts_with("rgb(") { + let mut iter = color[4..color.len() - 1].split(','); + let r = u8::from_str(iter.next().unwrap()).unwrap(); + let g = u8::from_str(iter.next().unwrap()).unwrap(); + let b = u8::from_str(iter.next().unwrap()).unwrap(); + Color::rgb8(r, g, b) + } else { + Color::rgba8(255, 0, 255, 0x80) + } +} + +fn modify_opacity(mut color: Color, attr_name: &str, node: Node) -> Color { + if let Some(opacity) = node.attribute(attr_name) { + let alpha = if opacity.ends_with("%") { + let pctg = opacity[..opacity.len() - 1].parse().unwrap_or(100.0); + pctg * 0.01 + } else { + opacity.parse().unwrap_or(1.0) + } as f64; + color.a = (alpha.min(1.0).max(0.0) * 255.0).round() as u8; + color + } else { + color + } +} diff --git a/piet-wgsl/src/render.rs b/piet-wgsl/src/render.rs index 04f4232..99c068e 100644 --- a/piet-wgsl/src/render.rs +++ b/piet-wgsl/src/render.rs @@ -220,7 +220,7 @@ pub fn render_full(scene: &Scene, shaders: &FullShaders) -> (Recording, BufProxy let bump_buf = BufProxy::new(BUMP_SIZE); // Not actually used yet. let clip_bbox_buf = BufProxy::new(1024); - let bin_data_buf = BufProxy::new(1 << 16); + let bin_data_buf = BufProxy::new(1 << 20); let width_in_bins = (config.width_in_tiles + 15) / 16; let height_in_bins = (config.height_in_tiles + 15) / 16; let n_bins = width_in_bins * height_in_bins; @@ -256,7 +256,7 @@ pub fn render_full(scene: &Scene, shaders: &FullShaders) -> (Recording, BufProxy ], ); - let segments_buf = BufProxy::new(1 << 20); + let segments_buf = BufProxy::new(1 << 24); recording.dispatch( shaders.path_coarse, (path_coarse_wgs, 1, 1), @@ -276,7 +276,7 @@ pub fn render_full(scene: &Scene, shaders: &FullShaders) -> (Recording, BufProxy (path_wgs, 1, 1), [config_buf, path_buf, tile_buf], ); - let ptcl_buf = BufProxy::new(1 << 20); + let ptcl_buf = BufProxy::new(1 << 24); recording.dispatch( shaders.coarse, (width_in_bins, height_in_bins, 1), diff --git a/piet-wgsl/src/test_scene.rs b/piet-wgsl/src/test_scene.rs index 2d28659..ab7d3e3 100644 --- a/piet-wgsl/src/test_scene.rs +++ b/piet-wgsl/src/test_scene.rs @@ -14,37 +14,37 @@ // // Also licensed under MIT license, at your choice. +use kurbo::BezPath; use piet_scene::{Affine, Brush, Color, Fill, PathElement, Point, Scene, SceneBuilder, Stroke}; +use crate::pico_svg::PicoSvg; + pub fn gen_test_scene() -> Scene { let mut scene = Scene::default(); let mut builder = SceneBuilder::for_scene(&mut scene); - let path = [ - PathElement::MoveTo(Point::new(100.0, 100.0)), - PathElement::LineTo(Point::new(500.0, 120.0)), - PathElement::LineTo(Point::new(300.0, 150.0)), - PathElement::LineTo(Point::new(200.0, 260.0)), - PathElement::LineTo(Point::new(150.0, 210.0)), - PathElement::Close, - ]; - let brush = Brush::Solid(Color::rgb8(0x40, 0x40, 0xff)); - builder.fill(Fill::NonZero, Affine::IDENTITY, &brush, None, &path); - let transform = Affine::translate(50.0, 50.0); - let brush = Brush::Solid(Color::rgba8(0xff, 0xff, 0x00, 0x80)); - builder.fill(Fill::NonZero, transform, &brush, None, &path); - let transform = Affine::translate(100.0, 100.0); - let style = Stroke { - width: 1.0, - join: piet_scene::Join::Round, - miter_limit: 1.4, - start_cap: piet_scene::Cap::Round, - end_cap: piet_scene::Cap::Round, - dash_pattern: [], - dash_offset: 0.0, - scale: true, - }; - let brush = Brush::Solid(Color::rgb8(0xa0, 0x00, 0x00)); - builder.stroke(&style, transform, &brush, None, &path); + if false { + let path = [ + PathElement::MoveTo(Point::new(100.0, 100.0)), + PathElement::LineTo(Point::new(500.0, 120.0)), + PathElement::LineTo(Point::new(300.0, 150.0)), + PathElement::LineTo(Point::new(200.0, 260.0)), + PathElement::LineTo(Point::new(150.0, 210.0)), + PathElement::Close, + ]; + let brush = Brush::Solid(Color::rgb8(0x40, 0x40, 0xff)); + builder.fill(Fill::NonZero, Affine::IDENTITY, &brush, None, &path); + let transform = Affine::translate(50.0, 50.0); + let brush = Brush::Solid(Color::rgba8(0xff, 0xff, 0x00, 0x80)); + builder.fill(Fill::NonZero, transform, &brush, None, &path); + let transform = Affine::translate(100.0, 100.0); + let style = simple_stroke(1.0); + let brush = Brush::Solid(Color::rgb8(0xa0, 0x00, 0x00)); + builder.stroke(&style, transform, &brush, None, &path); + } else { + let xml_str = std::str::from_utf8(include_bytes!("../../piet-gpu/Ghostscript_Tiger.svg")).unwrap(); + let svg = PicoSvg::load(xml_str, 6.0).unwrap(); + render_svg(&mut builder, &svg, false); + } scene } @@ -56,3 +56,53 @@ pub fn dump_scene_info(scene: &Scene) { bytemuck::cast_slice::(&data.pathseg_stream) ); } + +pub fn render_svg(sb: &mut SceneBuilder, svg: &PicoSvg, print_stats: bool) { + use crate::pico_svg::*; + let start = std::time::Instant::now(); + for item in svg.items.iter() { + match item { + Item::Fill(fill) => { + sb.fill( + Fill::NonZero, + Affine::IDENTITY, + &fill.color.into(), + None, + convert_bez_path(&fill.path), + ); + } + Item::Stroke(stroke) => { + sb.stroke( + &simple_stroke(stroke.width as f32), + Affine::IDENTITY, + &stroke.color.into(), + None, + convert_bez_path(&stroke.path), + ); + } + } + } + if print_stats { + println!("flattening and encoding time: {:?}", start.elapsed()); + } +} + +fn convert_bez_path<'a>(path: &'a BezPath) -> impl Iterator + 'a + Clone { + path.elements() + .iter() + .map(|el| PathElement::from_kurbo(*el)) +} + + +fn simple_stroke(width: f32) -> Stroke<[f32; 0]> { + Stroke { + width, + join: piet_scene::Join::Round, + miter_limit: 1.4, + start_cap: piet_scene::Cap::Round, + end_cap: piet_scene::Cap::Round, + dash_pattern: [], + dash_offset: 0.0, + scale: true, + } +}