Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to CPU-side stroke encoding #368

Merged
merged 2 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ wgpu-profiler = { workspace = true, optional = true }
[workspace.dependencies]
bytemuck = { version = "1.12.1", features = ["derive"] }
fello = { git = "https://github.com/dfrg/fount", rev = "dadbcf75695f035ca46766bfd60555d05bd421b1" }
peniko = { git = "https://github.com/linebender/peniko", rev = "cafdac9a211a0fb2fec5656bd663d1ac770bcc81" }
peniko = { git = "https://github.com/linebender/peniko", rev = "629fc3325b016a8c98b1cd6204cb4ddf1c6b3daa" }

# NOTE: Make sure to keep this in sync with the version badge in README.md
wgpu = { version = "0.17" }
Expand Down
10 changes: 7 additions & 3 deletions crates/encoding/src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use super::{DrawColor, DrawTag, PathEncoder, PathTag, Transform};

use peniko::{kurbo::Shape, BlendMode, BrushRef, Color};
use peniko::{kurbo::Shape, BlendMode, BrushRef, Color, Fill};

#[cfg(feature = "full")]
use {
Expand Down Expand Up @@ -163,8 +163,12 @@ impl Encoding {
}
}

/// Encodes a linewidth.
pub fn encode_linewidth(&mut self, linewidth: f32) {
/// Encodes a fill style.
pub fn encode_fill_style(&mut self, fill: Fill) {
let linewidth = match fill {
Fill::NonZero => -1.0,
Fill::EvenOdd => -2.0,
};
if self.linewidths.last() != Some(&linewidth) {
self.path_tags.push(PathTag::LINEWIDTH);
self.linewidths.push(linewidth);
Expand Down
72 changes: 62 additions & 10 deletions crates/encoding/src/glyph_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ use std::collections::HashMap;

use super::{Encoding, StreamOffsets};

use fello::scale::Scaler;
use fello::scale::{Pen, Scaler};
use fello::GlyphId;
use peniko::{Fill, Style};
use peniko::{
kurbo::{BezPath, Shape},
Fill, Style,
};

#[derive(Copy, Clone, PartialEq, Eq, Hash, Default, Debug)]
pub struct GlyphKey {
Expand Down Expand Up @@ -36,20 +39,37 @@ impl GlyphCache {
style: &Style,
scaler: &mut Scaler,
) -> Option<CachedRange> {
let is_fill = matches!(style, Style::Fill(_));
let is_var = !scaler.normalized_coords().is_empty();
let encoding_cache = &mut self.encoding;
let mut encode_glyph = || {
let start = encoding_cache.stream_offsets();
let fill = match style {
Style::Fill(fill) => *fill,
Style::Stroke(_) => Fill::NonZero,
};
encoding_cache.encode_fill_style(fill);
let mut path = encoding_cache.encode_path(true);
match style {
Style::Fill(Fill::NonZero) => encoding_cache.encode_linewidth(-1.0),
Style::Fill(Fill::EvenOdd) => encoding_cache.encode_linewidth(-2.0),
Style::Stroke(stroke) => encoding_cache.encode_linewidth(stroke.width),
Style::Fill(_) => {
scaler
.outline(GlyphId::new(key.glyph_id as u16), &mut path)
.ok()?;
}
Style::Stroke(stroke) => {
const STROKE_TOLERANCE: f64 = 0.01;
let mut pen = BezPathPen::default();
scaler
.outline(GlyphId::new(key.glyph_id as u16), &mut pen)
.ok()?;
let stroked = peniko::kurbo::stroke(
pen.0.path_elements(STROKE_TOLERANCE),
stroke,
&Default::default(),
STROKE_TOLERANCE,
);
path.shape(&stroked);
}
}
let mut path = encoding_cache.encode_path(is_fill);
scaler
.outline(GlyphId::new(key.glyph_id as u16), &mut path)
.ok()?;
if path.finish(false) == 0 {
return None;
}
Expand Down Expand Up @@ -89,3 +109,35 @@ impl CachedRange {
}
}
}

// A wrapper newtype so we can implement the `Pen` trait. Arguably, this could
// be in the fello crate, but will go away when we do GPU-side stroking.
#[derive(Default)]
struct BezPathPen(BezPath);

impl Pen for BezPathPen {
fn move_to(&mut self, x: f32, y: f32) {
self.0.move_to((x as f64, y as f64));
}

fn line_to(&mut self, x: f32, y: f32) {
self.0.line_to((x as f64, y as f64));
}

fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
self.0
.quad_to((cx0 as f64, cy0 as f64), (x as f64, y as f64));
}

fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
self.0.curve_to(
(cx0 as f64, cy0 as f64),
(cx1 as f64, cy1 as f64),
(x as f64, y as f64),
);
}

fn close(&mut self) {
self.0.close_path();
}
}
5 changes: 2 additions & 3 deletions examples/scenes/src/mmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ 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,
kurbo::{Affine, BezPath, CubicBez, Line, ParamCurve, PathSeg, Point, QuadBez, Stroke},
SceneBuilder,
};

Expand Down Expand Up @@ -92,7 +91,7 @@ impl TestScene for MMark {
// 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),
&Stroke::new(element.width),
Affine::IDENTITY,
element.color,
None,
Expand Down
2 changes: 1 addition & 1 deletion examples/scenes/src/test_scenes.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{ExampleScene, SceneConfig, SceneParams, SceneSet};
use vello::kurbo::{Affine, BezPath, Ellipse, PathEl, Point, Rect};
use vello::kurbo::{Affine, BezPath, Ellipse, PathEl, Point, Rect, Stroke};
use vello::peniko::*;
use vello::*;

Expand Down
6 changes: 3 additions & 3 deletions examples/with_winit/src/stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
use scenes::SimpleText;
use std::{collections::VecDeque, time::Duration};
use vello::{
kurbo::{Affine, Line, PathEl, Rect},
peniko::{Brush, Color, Fill, Stroke},
kurbo::{Affine, Line, PathEl, Rect, Stroke},
peniko::{Brush, Color, Fill},
BumpAllocators, SceneBuilder,
};
use wgpu_profiler::GpuTimerScopeResult;
Expand Down Expand Up @@ -174,7 +174,7 @@ impl Snapshot {
&format!("{}", t),
);
sb.stroke(
&Stroke::new((graph_max_height * 0.01) as f32),
&Stroke::new(graph_max_height * 0.01),
offset * Affine::translate((left_margin_padding, (1. - y) * graph_max_height)),
Color::WHITE,
None,
Expand Down
6 changes: 3 additions & 3 deletions integrations/vello_svg/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
use std::convert::Infallible;

use usvg::NodeExt;
use vello::kurbo::{Affine, BezPath, Rect};
use vello::peniko::{Brush, Color, Fill, Stroke};
use vello::kurbo::{Affine, BezPath, Rect, Stroke};
use vello::peniko::{Brush, Color, Fill};
use vello::SceneBuilder;

pub use usvg;
Expand Down Expand Up @@ -133,7 +133,7 @@ pub fn render_tree_with<F: FnMut(&mut SceneBuilder, &usvg::Node) -> Result<(), E
{
// FIXME: handle stroke options such as linecap, linejoin, etc.
sb.stroke(
&Stroke::new(stroke.width.get() as f32),
&Stroke::new(stroke.width.get()),
transform,
&brush,
Some(brush_transform),
Expand Down
27 changes: 22 additions & 5 deletions src/glyph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use {
};

pub use fello;
use peniko::kurbo::Shape;
pub use vello_encoding::Glyph;

/// General context for creating scene fragments for glyph outlines.
Expand Down Expand Up @@ -102,13 +103,29 @@ impl<'a> GlyphProvider<'a> {
}

pub fn encode_glyph(&mut self, gid: u16, style: &Style, encoding: &mut Encoding) -> Option<()> {
let fill = match style {
Style::Fill(fill) => *fill,
Style::Stroke(_) => Fill::NonZero,
};
encoding.encode_fill_style(fill);
let mut path = encoding.encode_path(true);
match style {
Style::Fill(Fill::NonZero) => encoding.encode_linewidth(-1.0),
Style::Fill(Fill::EvenOdd) => encoding.encode_linewidth(-2.0),
Style::Stroke(stroke) => encoding.encode_linewidth(stroke.width),
Style::Fill(_) => {
self.scaler.outline(GlyphId::new(gid), &mut path).ok()?;
}
Style::Stroke(stroke) => {
const STROKE_TOLERANCE: f64 = 0.01;
let mut pen = BezPathPen::default();
self.scaler.outline(GlyphId::new(gid), &mut pen).ok()?;
let stroked = peniko::kurbo::stroke(
pen.0.path_elements(STROKE_TOLERANCE),
stroke,
&Default::default(),
STROKE_TOLERANCE,
);
path.shape(&stroked);
}
}
let mut path = encoding.encode_path(matches!(style, Style::Fill(_)));
self.scaler.outline(GlyphId::new(gid), &mut path).ok()?;
if path.finish(false) != 0 {
Some(())
} else {
Expand Down
45 changes: 24 additions & 21 deletions src/scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
// Also licensed under MIT license, at your choice.

use fello::NormalizedCoord;
use peniko::kurbo::{Affine, Rect, Shape};
use peniko::{BlendMode, BrushRef, Color, Fill, Font, Image, Stroke, StyleRef};
use peniko::kurbo::{Affine, Rect, Shape, Stroke};
use peniko::{BlendMode, BrushRef, Color, Fill, Font, Image, StyleRef};
use vello_encoding::{Encoding, Glyph, GlyphRun, Patch, Transform};

/// Encoded definition of a scene and associated resources.
Expand Down Expand Up @@ -100,7 +100,7 @@ impl<'a> SceneBuilder<'a> {
let blend = blend.into();
self.scene
.encode_transform(Transform::from_kurbo(&transform));
self.scene.encode_linewidth(-1.0);
self.scene.encode_fill_style(Fill::NonZero);
if !self.scene.encode_shape(shape, true) {
// If the layer shape is invalid, encode a valid empty path. This suppresses
// all drawing until the layer is popped.
Expand All @@ -126,10 +126,7 @@ impl<'a> SceneBuilder<'a> {
) {
self.scene
.encode_transform(Transform::from_kurbo(&transform));
self.scene.encode_linewidth(match style {
Fill::NonZero => -1.0,
Fill::EvenOdd => -2.0,
});
self.scene.encode_fill_style(style);
if self.scene.encode_shape(shape, true) {
if let Some(brush_transform) = brush_transform {
if self
Expand All @@ -152,20 +149,26 @@ impl<'a> SceneBuilder<'a> {
brush_transform: Option<Affine>,
shape: &impl Shape,
) {
self.scene
.encode_transform(Transform::from_kurbo(&transform));
self.scene.encode_linewidth(style.width);
if self.scene.encode_shape(shape, false) {
if let Some(brush_transform) = brush_transform {
if self
.scene
.encode_transform(Transform::from_kurbo(&(transform * brush_transform)))
{
self.scene.swap_last_path_tags();
}
}
self.scene.encode_brush(brush, 1.0);
}
// The setting for tolerance are a compromise. For most applications,
// shape tolerance doesn't matter, as the input is likely Bézier paths,
// which is exact. Note that shape tolerance is hard-coded as 0.1 in
// the encoding crate.
//
// Stroke tolerance is a different matter. Generally, the cost scales
// with inverse O(n^6), so there is moderate rendering cost to setting
// too fine a value. On the other hand, error scales with the transform
// applied post-stroking, so may exceed visible threshold. When we do
// GPU-side stroking, the transform will be known. In the meantime,
// this is a compromise.
const SHAPE_TOLERANCE: f64 = 0.01;
const STROKE_TOLERANCE: f64 = SHAPE_TOLERANCE;
let stroked = peniko::kurbo::stroke(
shape.path_elements(SHAPE_TOLERANCE),
style,
&Default::default(),
STROKE_TOLERANCE,
);
self.fill(Fill::NonZero, transform, brush, brush_transform, &stroked);
}

/// Draws an image at its natural size with the given transform.
Expand Down