Skip to content

Commit

Permalink
Force encoding of transform/style following glyph run (linebender#425)
Browse files Browse the repository at this point in the history
Fixes linebender#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.
  • Loading branch information
dfrg authored Jan 26, 2024
1 parent 1c06c30 commit 944ce63
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 39 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ kurbo = "0.10.4"
[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" }
Expand Down
38 changes: 30 additions & 8 deletions crates/encoding/src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,19 @@ pub struct Encoding {
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()
Expand All @@ -66,6 +76,7 @@ impl Encoding {
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 {
Expand Down Expand Up @@ -93,6 +104,7 @@ impl Encoding {
.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;
Expand Down Expand Up @@ -141,6 +153,7 @@ impl Encoding {
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));
Expand Down Expand Up @@ -168,19 +181,19 @@ impl Encoding {

/// 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;
}
}

Expand All @@ -189,9 +202,12 @@ impl Encoding {
/// 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
Expand Down Expand Up @@ -381,6 +397,12 @@ impl Encoding {
}
}

/// 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) {
Expand Down
43 changes: 13 additions & 30 deletions examples/scenes/src/simple_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -136,35 +136,18 @@ impl SimpleText {
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,
);
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,5 +346,9 @@ impl<'a> DrawGlyphs<'a> {
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();
}
}

0 comments on commit 944ce63

Please sign in to comment.