mirror of
https://github.com/italicsjenga/vello.git
synced 2025-01-10 12:41:30 +11:00
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.
This commit is contained in:
parent
7ae5aa7491
commit
17a74fb370
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1174,10 +1174,12 @@ dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"futures-intrusive",
|
"futures-intrusive",
|
||||||
|
"kurbo 0.8.3",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"piet-scene",
|
"piet-scene",
|
||||||
"png",
|
"png",
|
||||||
"pollster",
|
"pollster",
|
||||||
|
"roxmltree",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -14,4 +14,8 @@ parking_lot = "0.12"
|
||||||
bytemuck = { version = "1.12.1", features = ["derive"] }
|
bytemuck = { version = "1.12.1", features = ["derive"] }
|
||||||
png = "0.17.6"
|
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"
|
||||||
|
|
|
@ -85,7 +85,10 @@ var<private> cmd_limit: u32;
|
||||||
// Make sure there is space for a command of given size, plus a jump if needed
|
// Make sure there is space for a command of given size, plus a jump if needed
|
||||||
fn alloc_cmd(size: u32) {
|
fn alloc_cmd(size: u32) {
|
||||||
if cmd_offset + size >= cmd_limit {
|
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
|
// TODO: robust memory
|
||||||
ptcl[cmd_offset] = CMD_JUMP;
|
ptcl[cmd_offset] = CMD_JUMP;
|
||||||
ptcl[cmd_offset + 1u] = new_cmd;
|
ptcl[cmd_offset + 1u] = new_cmd;
|
||||||
|
|
|
@ -24,6 +24,7 @@ use test_scene::dump_scene_info;
|
||||||
use wgpu::{Device, Limits, Queue};
|
use wgpu::{Device, Limits, Queue};
|
||||||
|
|
||||||
mod engine;
|
mod engine;
|
||||||
|
mod pico_svg;
|
||||||
mod render;
|
mod render;
|
||||||
mod shaders;
|
mod shaders;
|
||||||
mod test_scene;
|
mod test_scene;
|
||||||
|
|
140
piet-wgsl/src/pico_svg.rs
Normal file
140
piet-wgsl/src/pico_svg.rs
Normal file
|
@ -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<Item>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Item>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PicoSvg {
|
||||||
|
pub fn load(xml_string: &str, scale: f64) -> Result<PicoSvg, Box<dyn std::error::Error>> {
|
||||||
|
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<Item>, scale: f64) -> Parser<'a> {
|
||||||
|
Parser { scale, items }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rec_parse(&mut self, node: Node) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -220,7 +220,7 @@ pub fn render_full(scene: &Scene, shaders: &FullShaders) -> (Recording, BufProxy
|
||||||
let bump_buf = BufProxy::new(BUMP_SIZE);
|
let bump_buf = BufProxy::new(BUMP_SIZE);
|
||||||
// Not actually used yet.
|
// Not actually used yet.
|
||||||
let clip_bbox_buf = BufProxy::new(1024);
|
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 width_in_bins = (config.width_in_tiles + 15) / 16;
|
||||||
let height_in_bins = (config.height_in_tiles + 15) / 16;
|
let height_in_bins = (config.height_in_tiles + 15) / 16;
|
||||||
let n_bins = width_in_bins * height_in_bins;
|
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(
|
recording.dispatch(
|
||||||
shaders.path_coarse,
|
shaders.path_coarse,
|
||||||
(path_coarse_wgs, 1, 1),
|
(path_coarse_wgs, 1, 1),
|
||||||
|
@ -276,7 +276,7 @@ pub fn render_full(scene: &Scene, shaders: &FullShaders) -> (Recording, BufProxy
|
||||||
(path_wgs, 1, 1),
|
(path_wgs, 1, 1),
|
||||||
[config_buf, path_buf, tile_buf],
|
[config_buf, path_buf, tile_buf],
|
||||||
);
|
);
|
||||||
let ptcl_buf = BufProxy::new(1 << 20);
|
let ptcl_buf = BufProxy::new(1 << 24);
|
||||||
recording.dispatch(
|
recording.dispatch(
|
||||||
shaders.coarse,
|
shaders.coarse,
|
||||||
(width_in_bins, height_in_bins, 1),
|
(width_in_bins, height_in_bins, 1),
|
||||||
|
|
|
@ -14,37 +14,37 @@
|
||||||
//
|
//
|
||||||
// Also licensed under MIT license, at your choice.
|
// Also licensed under MIT license, at your choice.
|
||||||
|
|
||||||
|
use kurbo::BezPath;
|
||||||
use piet_scene::{Affine, Brush, Color, Fill, PathElement, Point, Scene, SceneBuilder, Stroke};
|
use piet_scene::{Affine, Brush, Color, Fill, PathElement, Point, Scene, SceneBuilder, Stroke};
|
||||||
|
|
||||||
|
use crate::pico_svg::PicoSvg;
|
||||||
|
|
||||||
pub fn gen_test_scene() -> Scene {
|
pub fn gen_test_scene() -> Scene {
|
||||||
let mut scene = Scene::default();
|
let mut scene = Scene::default();
|
||||||
let mut builder = SceneBuilder::for_scene(&mut scene);
|
let mut builder = SceneBuilder::for_scene(&mut scene);
|
||||||
let path = [
|
if false {
|
||||||
PathElement::MoveTo(Point::new(100.0, 100.0)),
|
let path = [
|
||||||
PathElement::LineTo(Point::new(500.0, 120.0)),
|
PathElement::MoveTo(Point::new(100.0, 100.0)),
|
||||||
PathElement::LineTo(Point::new(300.0, 150.0)),
|
PathElement::LineTo(Point::new(500.0, 120.0)),
|
||||||
PathElement::LineTo(Point::new(200.0, 260.0)),
|
PathElement::LineTo(Point::new(300.0, 150.0)),
|
||||||
PathElement::LineTo(Point::new(150.0, 210.0)),
|
PathElement::LineTo(Point::new(200.0, 260.0)),
|
||||||
PathElement::Close,
|
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 brush = Brush::Solid(Color::rgb8(0x40, 0x40, 0xff));
|
||||||
let transform = Affine::translate(50.0, 50.0);
|
builder.fill(Fill::NonZero, Affine::IDENTITY, &brush, None, &path);
|
||||||
let brush = Brush::Solid(Color::rgba8(0xff, 0xff, 0x00, 0x80));
|
let transform = Affine::translate(50.0, 50.0);
|
||||||
builder.fill(Fill::NonZero, transform, &brush, None, &path);
|
let brush = Brush::Solid(Color::rgba8(0xff, 0xff, 0x00, 0x80));
|
||||||
let transform = Affine::translate(100.0, 100.0);
|
builder.fill(Fill::NonZero, transform, &brush, None, &path);
|
||||||
let style = Stroke {
|
let transform = Affine::translate(100.0, 100.0);
|
||||||
width: 1.0,
|
let style = simple_stroke(1.0);
|
||||||
join: piet_scene::Join::Round,
|
let brush = Brush::Solid(Color::rgb8(0xa0, 0x00, 0x00));
|
||||||
miter_limit: 1.4,
|
builder.stroke(&style, transform, &brush, None, &path);
|
||||||
start_cap: piet_scene::Cap::Round,
|
} else {
|
||||||
end_cap: piet_scene::Cap::Round,
|
let xml_str = std::str::from_utf8(include_bytes!("../../piet-gpu/Ghostscript_Tiger.svg")).unwrap();
|
||||||
dash_pattern: [],
|
let svg = PicoSvg::load(xml_str, 6.0).unwrap();
|
||||||
dash_offset: 0.0,
|
render_svg(&mut builder, &svg, false);
|
||||||
scale: true,
|
}
|
||||||
};
|
|
||||||
let brush = Brush::Solid(Color::rgb8(0xa0, 0x00, 0x00));
|
|
||||||
builder.stroke(&style, transform, &brush, None, &path);
|
|
||||||
scene
|
scene
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,3 +56,53 @@ pub fn dump_scene_info(scene: &Scene) {
|
||||||
bytemuck::cast_slice::<u8, f32>(&data.pathseg_stream)
|
bytemuck::cast_slice::<u8, f32>(&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<Item = PathElement> + '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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue