Force encoding of transform/style following glyph run (#425)
Fixes #424 which describes a case where encoding a glyph run breaks the optimization that omits duplicate transforms/styles from their respective streams.
This uses flags in the encoding to force the next transform/style to be emitted. An alternative implementation would be to unconditionally encode a transform/style after each glyph run but this seemed like the better approach to me.
Also changes SimpleText::add to use the glyph run API. This means all text in the vello demos now uses that code path rather than encoding glyph outlines directly.
diff --git a/Cargo.toml b/Cargo.toml
index 246c3e3..1e0efd6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -58,7 +58,7 @@
[workspace.dependencies]
bytemuck = { version = "1.12.1", features = ["derive"] }
skrifa = "0.15.4"
-peniko = { git = "https://github.com/linebender/peniko", rev = "629fc3325b016a8c98b1cd6204cb4ddf1c6b3daa" }
+peniko = { git = "https://github.com/linebender/peniko", rev = "8717635681dedfab3e9f3741fcbc7f3318a82ff0" }
# NOTE: Make sure to keep this in sync with the version badge in README.md
wgpu = { version = "0.18" }
diff --git a/crates/encoding/src/encoding.rs b/crates/encoding/src/encoding.rs
index 019de23..a9eee83 100644
--- a/crates/encoding/src/encoding.rs
+++ b/crates/encoding/src/encoding.rs
@@ -41,9 +41,19 @@
pub n_clips: u32,
/// Number of unclosed clips/layers.
pub n_open_clips: u32,
+ /// Flags that capture the current state of the encoding.
+ pub flags: u32,
}
impl Encoding {
+ /// Forces encoding of the next transform even if it matches
+ /// the current transform in the stream.
+ pub const FORCE_NEXT_TRANSFORM: u32 = 1;
+
+ /// Forces encoding of the next style even if it matches
+ /// the current style in the stream.
+ pub const FORCE_NEXT_STYLE: u32 = 2;
+
/// Creates a new encoding.
pub fn new() -> Self {
Self::default()
@@ -66,6 +76,7 @@
self.n_path_segments = 0;
self.n_clips = 0;
self.n_open_clips = 0;
+ self.flags = 0;
#[cfg(feature = "full")]
self.resources.reset();
if !is_fragment {
@@ -93,6 +104,7 @@
.glyph_runs
.extend(other.resources.glyph_runs.iter().cloned().map(|mut run| {
run.glyphs.start += glyphs_base;
+ run.glyphs.end += glyphs_base;
run.normalized_coords.start += coords_base;
run.stream_offsets.path_tags += offsets.path_tags;
run.stream_offsets.path_data += offsets.path_data;
@@ -141,6 +153,7 @@
self.n_path_segments += other.n_path_segments;
self.n_clips += other.n_clips;
self.n_open_clips += other.n_open_clips;
+ self.flags = other.flags;
if let Some(transform) = *transform {
self.transforms
.extend(other.transforms.iter().map(|x| transform * *x));
@@ -168,19 +181,19 @@
/// Encodes a fill style.
pub fn encode_fill_style(&mut self, fill: Fill) {
- let style = Style::from_fill(fill);
- if self.styles.last() != Some(&style) {
- self.path_tags.push(PathTag::STYLE);
- self.styles.push(style);
- }
+ self.encode_style(Style::from_fill(fill));
}
/// Encodes a stroke style.
pub fn encode_stroke_style(&mut self, stroke: &Stroke) {
- let style = Style::from_stroke(stroke);
- if self.styles.last() != Some(&style) {
+ self.encode_style(Style::from_stroke(stroke));
+ }
+
+ fn encode_style(&mut self, style: Style) {
+ if self.flags & Self::FORCE_NEXT_STYLE != 0 || self.styles.last() != Some(&style) {
self.path_tags.push(PathTag::STYLE);
self.styles.push(style);
+ self.flags &= !Self::FORCE_NEXT_STYLE;
}
}
@@ -189,9 +202,12 @@
/// If the given transform is different from the current one, encodes it and
/// returns true. Otherwise, encodes nothing and returns false.
pub fn encode_transform(&mut self, transform: Transform) -> bool {
- if self.transforms.last() != Some(&transform) {
+ if self.flags & Self::FORCE_NEXT_TRANSFORM != 0
+ || self.transforms.last() != Some(&transform)
+ {
self.path_tags.push(PathTag::TRANSFORM);
self.transforms.push(transform);
+ self.flags &= !Self::FORCE_NEXT_TRANSFORM;
true
} else {
false
@@ -381,6 +397,12 @@
}
}
+ /// Forces the next transform and style to be encoded even if they match
+ /// the current state.
+ pub fn force_next_transform_and_style(&mut self) {
+ self.flags |= Self::FORCE_NEXT_TRANSFORM | Self::FORCE_NEXT_STYLE;
+ }
+
// Swap the last two tags in the path tag stream; used for transformed
// gradients.
pub fn swap_last_path_tags(&mut self) {
diff --git a/examples/scenes/src/simple_text.rs b/examples/scenes/src/simple_text.rs
index 060a015..a79d3b6 100644
--- a/examples/scenes/src/simple_text.rs
+++ b/examples/scenes/src/simple_text.rs
@@ -17,7 +17,7 @@
use std::sync::Arc;
use vello::{
- glyph::{Glyph, GlyphContext},
+ glyph::Glyph,
kurbo::Affine,
peniko::{Blob, Brush, BrushRef, Font, StyleRef},
skrifa::{raw::FontRef, MetadataProvider},
@@ -136,35 +136,18 @@
transform: Affine,
text: &str,
) {
- let default_font = FontRef::new(ROBOTO_FONT).unwrap();
- let font = font.and_then(to_font_ref).unwrap_or(default_font);
- let font_size = vello::skrifa::instance::Size::new(size);
- let var_loc = vello::skrifa::instance::LocationRef::default();
- let charmap = font.charmap();
- let metrics = font.metrics(font_size, var_loc);
- let line_height = metrics.ascent - metrics.descent + metrics.leading;
- let glyph_metrics = font.glyph_metrics(font_size, var_loc);
- let mut pen_x = 0f64;
- let mut pen_y = 0f64;
- let vars: [(&str, f32); 0] = [];
- let mut gcx = GlyphContext::new();
- let mut provider = gcx.new_provider(&font, size, false, &vars);
- for ch in text.chars() {
- if ch == '\n' {
- pen_y += line_height as f64;
- pen_x = 0.0;
- continue;
- }
- let gid = charmap.map(ch).unwrap_or_default();
- let advance = glyph_metrics.advance_width(gid).unwrap_or_default() as f64;
- if let Some(glyph) = provider.get(gid.to_u16(), brush) {
- let xform = transform
- * Affine::translate((pen_x, pen_y))
- * Affine::scale_non_uniform(1.0, -1.0);
- builder.append(&glyph, Some(xform));
- }
- pen_x += advance;
- }
+ use vello::peniko::{Color, Fill};
+ let brush = brush.unwrap_or(&Brush::Solid(Color::WHITE));
+ self.add_run(
+ builder,
+ font,
+ size,
+ brush,
+ transform,
+ None,
+ Fill::NonZero,
+ text,
+ );
}
}
diff --git a/src/scene.rs b/src/scene.rs
index 084bd24..86ebe97 100644
--- a/src/scene.rs
+++ b/src/scene.rs
@@ -346,5 +346,9 @@
resources.glyph_runs.push(self.run);
resources.patches.push(Patch::GlyphRun { index });
self.encoding.encode_brush(self.brush, self.brush_alpha);
+ // Glyph run resolve step affects transform and style state in a way
+ // that is opaque to the current encoding.
+ // See <https://github.com/linebender/vello/issues/424>
+ self.encoding.force_next_transform_and_style();
}
}