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

Don't encode empty strokes #785

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
7 changes: 6 additions & 1 deletion vello/src/scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,14 @@ impl Scene {

const GPU_STROKES: bool = true; // Set this to `true` to enable GPU-side stroking
if GPU_STROKES {
if style.width == 0. {
return;
}

let t = Transform::from_kurbo(&transform);
self.encoding.encode_transform(t);
self.encoding.encode_stroke_style(style);
let encoded_stroke = self.encoding.encode_stroke_style(style);
debug_assert!(encoded_stroke, "Stroke width is non-zero");

// We currently don't support dashing on the GPU. If the style has a dash pattern, then
// we convert it into stroked paths on the CPU and encode those as individual draw
Expand Down
13 changes: 11 additions & 2 deletions vello_encoding/src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,17 @@ impl Encoding {
}

/// Encodes a stroke style.
pub fn encode_stroke_style(&mut self, stroke: &Stroke) {
self.encode_style(Style::from_stroke(stroke));
///
/// Returns false if the stroke had zero width and so couldn't be encoded.
#[must_use]
pub fn encode_stroke_style(&mut self, stroke: &Stroke) -> bool {
let style = Style::from_stroke(stroke);
if let Some(style) = style {
self.encode_style(style);
true
} else {
false
}
}

fn encode_style(&mut self, style: Style) {
Expand Down
5 changes: 3 additions & 2 deletions vello_encoding/src/glyph_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ impl GlyphCache {
// TODO: we're ignoring dashing for now
let style_bits = match style {
Style::Fill(fill) => super::path::Style::from_fill(*fill),
Style::Stroke(stroke) => super::path::Style::from_stroke(stroke),
Style::Stroke(stroke) => super::path::Style::from_stroke(stroke)?,
};
let style_bits: [u32; 2] = bytemuck::cast(style_bits);
Some(GlyphCacheSession {
Expand Down Expand Up @@ -175,7 +175,8 @@ impl GlyphCacheSession<'_> {
true
}
Style::Stroke(stroke) => {
encoding_ptr.encode_stroke_style(stroke);
let encoded_stroke = encoding_ptr.encode_stroke_style(stroke);
debug_assert!(encoded_stroke, "Stroke width is non-zero");
false
}
};
Expand Down
16 changes: 11 additions & 5 deletions vello_encoding/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,13 @@ impl Style {
}
}

pub fn from_stroke(stroke: &Stroke) -> Self {
/// Creates a style from a stroke.
///
/// As it isn't meaningful to encode a zero width stroke, returns None if the width is zero.
pub fn from_stroke(stroke: &Stroke) -> Option<Self> {
if stroke.width == 0.0 {
return None;
}
let style = Self::FLAGS_STYLE_BIT;
let join = match stroke.join {
Join::Bevel => Self::FLAGS_JOIN_BITS_BEVEL,
Expand All @@ -96,10 +102,10 @@ impl Style {
Cap::Round => Self::FLAGS_END_CAP_BITS_ROUND,
};
let miter_limit = crate::math::f32_to_f16(stroke.miter_limit as f32) as u32;
Self {
Some(Self {
flags_and_miter_limit: style | join | start_cap | end_cap | miter_limit,
line_width: stroke.width as f32,
}
})
}

#[cfg(test)]
Expand Down Expand Up @@ -839,7 +845,7 @@ mod tests {
fn test_fill_style() {
assert_eq!(Some(Fill::NonZero), Style::from_fill(Fill::NonZero).fill());
assert_eq!(Some(Fill::EvenOdd), Style::from_fill(Fill::EvenOdd).fill());
assert_eq!(None, Style::from_stroke(&Stroke::default()).fill());
assert_eq!(None, Style::from_stroke(&Stroke::default()).unwrap().fill());
}

#[test]
Expand All @@ -856,7 +862,7 @@ mod tests {
.with_end_cap(end)
.with_join(join)
.with_miter_limit(0.);
let encoded = Style::from_stroke(&stroke);
let encoded = Style::from_stroke(&stroke).unwrap();
assert_eq!(Some(stroke.width), encoded.stroke_width());
assert_eq!(Some(stroke.join), encoded.stroke_join());
assert_eq!(Some(stroke.start_cap), encoded.stroke_start_cap());
Expand Down
3 changes: 3 additions & 0 deletions vello_tests/snapshots/stroke_width_zero.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions vello_tests/snapshots/text_stroke_width_zero.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 45 additions & 1 deletion vello_tests/tests/regression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@

//! Tests to ensure that certain issues which don't deserve a test scene don't regress

use scenes::SimpleText;
use vello::{
kurbo::{Affine, RoundedRect, Stroke},
kurbo::{Affine, Rect, RoundedRect, Stroke},
peniko::color::palette,
AaConfig, Scene,
};
Expand All @@ -24,3 +25,46 @@ fn rounded_rectangle_watertight() {
.unwrap()
.assert_mean_less_than(0.001);
}

/// Test created from <https://github.com/linebender/vello/issues/662>
#[test]
#[cfg_attr(skip_gpu_tests, ignore)]
fn stroke_width_zero() {
let mut scene = Scene::new();
let stroke = Stroke::new(0.0);
let rect = Rect::new(10.0, 10.0, 40.0, 40.0);
let rect_stroke_color = palette::css::PEACH_PUFF;
scene.stroke(&stroke, Affine::IDENTITY, rect_stroke_color, None, &rect);
let mut params = TestParams::new("stroke_width_zero", 50, 50);
params.anti_aliasing = AaConfig::Msaa16;
snapshot_test_sync(scene, &params)
.unwrap()
.assert_mean_less_than(0.001);
}

#[test]
#[cfg_attr(skip_gpu_tests, ignore)]
#[expect(clippy::cast_possible_truncation, reason = "Test code")]
fn text_stroke_width_zero() {
let font_size = 12.;
let mut scene = Scene::new();
let mut simple_text = SimpleText::new();
simple_text.add_run(
&mut scene,
None,
font_size,
palette::css::WHITE,
Affine::translate((0., f64::from(font_size))),
None,
&Stroke::new(0.),
"Testing text",
);
let params = TestParams::new(
"text_stroke_width_zero",
(font_size * 6.) as _,
(font_size * 1.25).ceil() as _,
);
snapshot_test_sync(scene, &params)
.unwrap()
.assert_mean_less_than(0.001);
}
Loading