Skip to content

Commit

Permalink
Encode fill rule in "draw flags" and fully abandon "linewidth"
Browse files Browse the repository at this point in the history
* Pipelines downstream of flatten (draw_leaf, coarse) now extract the
  fill rule using a "draw flags" field. Flatten still writes this to the
  path bounding-box structure, which gets propagated to the draw info
  list, and eventually translates to the fill rule written to the PTCL.

* draw_leaf no longer deals with transforming the linewidth for strokes.
  This was a leftover from the previous architecture and the logic is no
  longer needed.

* bbox_clear used to contain a duplicate PathBbox data structure. It now
  uses the one from shader/shared/bbox.wgsl

This continues the work outlined in #303
  • Loading branch information
armansito committed Oct 27, 2023
1 parent b5e3ae5 commit 26af286
Show file tree
Hide file tree
Showing 12 changed files with 60 additions and 56 deletions.
5 changes: 5 additions & 0 deletions crates/encoding/src/draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ impl DrawTag {
}
}

/// The first word of each draw info stream entry contains the flags. This is not a part of the
/// draw object stream but get used after the draw objects have been reduced on the GPU.
/// 0 represents a non-zero fill. 1 represents an even-odd fill.
pub const DRAW_INFO_FLAGS_FILL_RULE_BIT: u32 = 1;

/// Draw object bounding box.
#[derive(Copy, Clone, Pod, Zeroable, Debug, Default)]
#[repr(C)]
Expand Down
2 changes: 1 addition & 1 deletion crates/encoding/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub use config::{
};
pub use draw::{
DrawBbox, DrawBeginClip, DrawColor, DrawImage, DrawLinearGradient, DrawMonoid,
DrawRadialGradient, DrawTag,
DrawRadialGradient, DrawTag, DRAW_INFO_FLAGS_FILL_RULE_BIT,
};
pub use encoding::{Encoding, StreamOffsets};
pub use math::Transform;
Expand Down
4 changes: 2 additions & 2 deletions crates/encoding/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,8 +380,8 @@ pub struct PathBbox {
pub x1: i32,
/// Maximum y value.
pub y1: i32,
/// Line width.
pub linewidth: f32,
/// Style flags
pub draw_flags: u32,
/// Index into the transform stream.
pub trans_ix: u32,
}
Expand Down
10 changes: 1 addition & 9 deletions shader/bbox_clear.wgsl
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT OR Unlicense

#import config
#import bbox

@group(0) @binding(0)
var<uniform> config: Config;

struct PathBbox {
x0: i32,
y0: i32,
x1: i32,
y1: i32,
linewidth: f32,
trans_ix: u32,
}

@group(0) @binding(1)
var<storage, read_write> path_bboxes: array<PathBbox>;

Expand Down
22 changes: 11 additions & 11 deletions shader/coarse.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ fn alloc_cmd(size: u32) {
}
}

fn write_path(tile: Tile, tile_ix: u32, linewidth: f32) -> bool {
let even_odd = linewidth < -1.0;
fn write_path(tile: Tile, tile_ix: u32, draw_flags: u32) -> bool {
let even_odd = (draw_flags & DRAW_INFO_FLAGS_FILL_RULE_BIT) != 0u;
// We overload the "segments" field to store both count (written by
// path_count stage) and segment allocation (used by path_tiling and
// fine).
Expand Down Expand Up @@ -350,34 +350,34 @@ fn main(
switch drawtag {
// DRAWTAG_FILL_COLOR
case 0x44u: {
let linewidth = bitcast<f32>(info_bin_data[di]);
if write_path(tile, tile_ix, linewidth) {
let draw_flags = info_bin_data[di];
if write_path(tile, tile_ix, draw_flags) {
let rgba_color = scene[dd];
write_color(CmdColor(rgba_color));
}
}
// DRAWTAG_FILL_LIN_GRADIENT
case 0x114u: {
let linewidth = bitcast<f32>(info_bin_data[di]);
if write_path(tile, tile_ix, linewidth) {
let draw_flags = info_bin_data[di];
if write_path(tile, tile_ix, draw_flags) {
let index = scene[dd];
let info_offset = di + 1u;
write_grad(CMD_LIN_GRAD, index, info_offset);
}
}
// DRAWTAG_FILL_RAD_GRADIENT
case 0x29cu: {
let linewidth = bitcast<f32>(info_bin_data[di]);
if write_path(tile, tile_ix, linewidth) {
let draw_flags = info_bin_data[di];
if write_path(tile, tile_ix, draw_flags) {
let index = scene[dd];
let info_offset = di + 1u;
write_grad(CMD_RAD_GRAD, index, info_offset);
}
}
// DRAWTAG_FILL_IMAGE
case 0x248u: {
let linewidth = bitcast<f32>(info_bin_data[di]);
if write_path(tile, tile_ix, linewidth) {
let draw_flags = info_bin_data[di];
if write_path(tile, tile_ix, draw_flags) {
write_image(di + 1u);
}
}
Expand All @@ -395,7 +395,7 @@ fn main(
// DRAWTAG_END_CLIP
case 0x21u: {
clip_depth -= 1u;
write_path(tile, tile_ix, -1.0);
write_path(tile, tile_ix, 0u);
let blend = scene[dd];
let alpha = bitcast<f32>(scene[dd + 1u]);
write_end_clip(CmdEndClip(blend, alpha));
Expand Down
17 changes: 6 additions & 11 deletions shader/draw_leaf.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -109,25 +109,20 @@ fn main(
// let y1 = f32(bbox.y1);
// let bbox_f = vec4(x0, y0, x1, y1);
var transform = Transform();
var linewidth = bbox.linewidth;
if linewidth >= 0.0 || tag_word == DRAWTAG_FILL_LIN_GRADIENT || tag_word == DRAWTAG_FILL_RAD_GRADIENT ||
let draw_flags = bbox.draw_flags;
if tag_word == DRAWTAG_FILL_LIN_GRADIENT || tag_word == DRAWTAG_FILL_RAD_GRADIENT ||
tag_word == DRAWTAG_FILL_IMAGE
{
transform = read_transform(config.transform_base, bbox.trans_ix);
}
if linewidth >= 0.0 {
// Note: doesn't deal with anisotropic case
let matrx = transform.matrx;
linewidth *= sqrt(abs(matrx.x * matrx.w - matrx.y * matrx.z));
}
switch tag_word {
// DRAWTAG_FILL_COLOR
case 0x44u: {
info[di] = bitcast<u32>(linewidth);
info[di] = draw_flags;
}
// DRAWTAG_FILL_LIN_GRADIENT
case 0x114u: {
info[di] = bitcast<u32>(linewidth);
info[di] = draw_flags;
var p0 = bitcast<vec2<f32>>(vec2(scene[dd + 1u], scene[dd + 2u]));
var p1 = bitcast<vec2<f32>>(vec2(scene[dd + 3u], scene[dd + 4u]));
p0 = transform_apply(transform, p0);
Expand All @@ -146,7 +141,7 @@ fn main(
// on the algorithm at <https://skia.org/docs/dev/design/conical/>
// This epsilon matches what Skia uses
let GRADIENT_EPSILON = 1.0 / f32(1 << 12u);
info[di] = bitcast<u32>(linewidth);
info[di] = draw_flags;
var p0 = bitcast<vec2<f32>>(vec2(scene[dd + 1u], scene[dd + 2u]));
var p1 = bitcast<vec2<f32>>(vec2(scene[dd + 3u], scene[dd + 4u]));
var r0 = bitcast<f32>(scene[dd + 5u]);
Expand Down Expand Up @@ -226,7 +221,7 @@ fn main(
}
// DRAWTAG_FILL_IMAGE
case 0x248u: {
info[di] = bitcast<u32>(linewidth);
info[di] = draw_flags;
let inv = transform_inverse(transform);
info[di + 1u] = bitcast<u32>(inv.matrx.x);
info[di + 2u] = bitcast<u32>(inv.matrx.y);
Expand Down
20 changes: 11 additions & 9 deletions shader/flatten.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Flatten curves to lines

#import config
#import drawtag
#import pathtag
#import segment
#import cubic
Expand All @@ -22,7 +23,7 @@ struct AtomicPathBbox {
y0: atomic<i32>,
x1: atomic<i32>,
y1: atomic<i32>,
linewidth: f32,
draw_flags: u32,
trans_ix: u32,
}

Expand Down Expand Up @@ -221,12 +222,9 @@ fn main(
let style_flags = scene[config.style_base + tm.style_ix];
// TODO: We assume all paths are fills at the moment. This is where we will extract the stroke
// vs fill state using STYLE_FLAGS_STYLE_BIT.
// TODO: The downstream pipelines still use the old floating-point linewidth/fill encoding scheme
// This will change to represent the fill rule as a single bit inside the bounding box and draw
// info data structures.
let linewidth = select(-2.0, -1.0, (style_flags & STYLE_FLAGS_FILL_BIT) == 0u);
let draw_flags = select(DRAW_INFO_FLAGS_FILL_RULE_BIT, 0u, (style_flags & STYLE_FLAGS_FILL_BIT) == 0u);
if (tag_byte & PATH_TAG_PATH) != 0u {
(*out).linewidth = linewidth;
(*out).draw_flags = draw_flags;
(*out).trans_ix = tm.trans_ix;
}
// Decode path data
Expand Down Expand Up @@ -277,15 +275,19 @@ fn main(
}
}
var stroke = vec2(0.0, 0.0);
if linewidth >= 0.0 {
let is_stroke = (style_flags & STYLE_FLAGS_STYLE_BIT) != 0u;
/*
// TODO: the stroke handling here is dead code until the new explansion and flattening logic
// is implemented.
if is_stroke {
// See https://www.iquilezles.org/www/articles/ellipses/ellipses.htm
// This is the correct bounding box, but we're not handling rendering
// in the isotropic case, so it may mismatch.
stroke = 0.5 * linewidth * vec2(length(transform.mat.xz), length(transform.mat.yw));
bbox += vec4(-stroke, stroke);
}
let flags = u32(linewidth >= 0.0);
flatten_cubic(Cubic(p0, p1, p2, p3, stroke, tm.path_ix, flags));
*/
flatten_cubic(Cubic(p0, p1, p2, p3, stroke, tm.path_ix, u32(is_stroke)));
// Update bounding box using atomics only. Computing a monoid is a
// potential future optimization.
if bbox.z > bbox.x || bbox.w > bbox.y {
Expand Down
6 changes: 5 additions & 1 deletion shader/shared/bbox.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
// but contains a link to the active transform, mostly for gradients.
// Coordinates are integer pixels (for the convenience of atomic update)
// but will probably become fixed-point fractions for rectangles.
//
// TODO: This also carries a `draw_flags` field that contains information that gets propagated to
// the draw info stream. This is currently only used for the fill rule. If the other bits remain
// unused we could possibly pack this into some other field, such as the the MSB of `trans_ix`.
struct PathBbox {
x0: i32,
y0: i32,
x1: i32,
y1: i32,
linewidth: f32,
draw_flags: u32,
trans_ix: u32,
}

Expand Down
5 changes: 5 additions & 0 deletions shader/shared/drawtag.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ let DRAWTAG_FILL_IMAGE = 0x248u;
let DRAWTAG_BEGIN_CLIP = 0x9u;
let DRAWTAG_END_CLIP = 0x21u;

/// The first word of each draw info stream entry contains the flags. This is not a part of the
/// draw object stream but get used after the draw objects have been reduced on the GPU.
/// 0 represents a non-zero fill. 1 represents an even-odd fill.
let DRAW_INFO_FLAGS_FILL_RULE_BIT = 1u;

fn draw_monoid_identity() -> DrawMonoid {
return DrawMonoid();
}
Expand Down
5 changes: 4 additions & 1 deletion src/cpu_shader/coarse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,10 @@ fn coarse_main(
let n_segs = tile.segment_count_or_ix;
let include_tile = n_segs != 0 || (tile.backdrop == 0) == is_clip || is_blend;
if include_tile {
// TODO: get drawinfo (linewidth for fills)
// TODO: The first word of the info buffer (`info[di]`) contains flags that
// indicate the even-odd vs non-zero fill rule. Read that here and pass it
// to `write_path` so it gets propagated to the PTCL (see
// `PathBbox::FLAGS_FILL_STYLE_BIT` for its interpretation).
match DrawTag(drawtag) {
DrawTag::COLOR => {
tile_state.write_path(config, bump, ptcl, tile);
Expand Down
10 changes: 5 additions & 5 deletions src/cpu_shader/draw_leaf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ fn draw_leaf_main(
{
let bbox = path_bbox[m.path_ix as usize];
let transform = Transform::read(config.layout.transform_base, bbox.trans_ix, scene);
let linewidth = bbox.linewidth;
let flags = bbox.draw_flags;
match tag_word {
DrawTag::COLOR => {
info[di] = f32::to_bits(linewidth);
info[di] = flags;
}
DrawTag::LINEAR_GRADIENT => {
info[di] = f32::to_bits(linewidth);
info[di] = flags;
let p0 = Vec2::new(
f32::from_bits(scene[dd as usize + 1]),
f32::from_bits(scene[dd as usize + 2]),
Expand All @@ -67,7 +67,7 @@ fn draw_leaf_main(
info[di + 3] = f32::to_bits(line_c);
}
DrawTag::RADIAL_GRADIENT => {
info[di] = f32::to_bits(linewidth);
info[di] = flags;
let p0 = Vec2::new(
f32::from_bits(scene[dd as usize + 1]),
f32::from_bits(scene[dd as usize + 2]),
Expand Down Expand Up @@ -108,7 +108,7 @@ fn draw_leaf_main(
info[di + 19] = f32::to_bits(roff);
}
DrawTag::IMAGE => {
info[di] = f32::to_bits(linewidth);
info[di] = flags;
let z = transform.0;
let inv_det = (z[0] * z[3] - z[1] * z[2]).recip();
let inv_mat = [
Expand Down
10 changes: 4 additions & 6 deletions src/cpu_shader/flatten.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::cpu_dispatch::CpuBinding;
use super::util::{Transform, Vec2};
use vello_encoding::{
BumpAllocators, ConfigUniform, LineSoup, Monoid, PathBbox, PathMonoid, Style,
DRAW_INFO_FLAGS_FILL_RULE_BIT,
};

fn to_minus_one_quarter(x: f32) -> f32 {
Expand Down Expand Up @@ -218,13 +219,10 @@ fn flatten_main(
let out = &mut path_bboxes[tm.path_ix as usize];
// TODO: We assume all paths are fills at the moment. This is where we will extract the
// stroke vs fill state using STYLE_FLAGS_STYLE_BIT.
// TODO: The downstream pipelines still use the old floating-point linewidth/fill
// encoding scheme. This will change to represent the fill rule as a single bit inside
// the bounding box and draw info data structures.
out.linewidth = if (style_flags & Style::FLAGS_FILL_BIT) == 0 {
-1.0
out.draw_flags = if (style_flags & Style::FLAGS_FILL_BIT) == 0 {
0
} else {
-2.0
DRAW_INFO_FLAGS_FILL_RULE_BIT
};
out.trans_ix = tm.trans_ix;
}
Expand Down

0 comments on commit 26af286

Please sign in to comment.