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

Transition all pipelines to new draw_flags field for fill rule #399

Merged
merged 6 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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
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 part of the
/// draw object stream but gets used after the draw objects get 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
40 changes: 38 additions & 2 deletions examples/scenes/src/test_scenes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,43 @@ fn fill_types(sb: &mut SceneBuilder, params: &mut SceneParams) {
sb.fill(
rule.0,
Affine::translate((0., 10.)) * t,
Color::BLACK,
Color::YELLOW,
None,
&rule.2,
);
}

// Draw blends
let t = Affine::translate((700., 0.)) * t;
for (i, rule) in rules.iter().enumerate() {
let t = Affine::translate(((i % 2) as f64 * 306., (i / 2) as f64 * 340.)) * t;
params.text.add(sb, None, 24., None, t, rule.1);
let t = Affine::translate((0., 5.)) * t * scale;
sb.fill(
Fill::NonZero,
t,
&Brush::Solid(Color::rgb8(128, 128, 128)),
None,
&rect,
);
sb.fill(
rule.0,
Affine::translate((0., 10.)) * t,
Color::YELLOW,
None,
&rule.2,
);
sb.fill(
rule.0,
Affine::translate((0., 10.)) * t * Affine::rotate(0.06),
Color::rgba(0., 1., 0.7, 0.6),
None,
&rule.2,
);
sb.fill(
rule.0,
Affine::translate((0., 10.)) * t * Affine::rotate(-0.06),
Color::rgba(0.9, 0.7, 0.5, 0.6),
None,
&rule.2,
);
Expand Down Expand Up @@ -373,7 +409,7 @@ fn longpathdash(cap: Cap) -> impl FnMut(&mut SceneBuilder, &mut SceneParams) {
sb.stroke(
&Stroke::new(1.0).with_caps(cap).with_dashes(0.0, [1.0, 1.0]),
Affine::translate((50.0, 50.0)),
Color::rgb8(255, 255, 0),
Color::YELLOW,
None,
&path,
);
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
62 changes: 33 additions & 29 deletions shader/coarse.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,7 @@ 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) {
// We overload the "segments" field to store both count (written by
// path_count stage) and segment allocation (used by path_tiling and
// fine).
Expand All @@ -93,21 +92,18 @@ fn write_path(tile: Tile, tile_ix: u32, linewidth: f32) -> bool {
tiles[tile_ix].segment_count_or_ix = ~seg_ix;
alloc_cmd(4u);
ptcl[cmd_offset] = CMD_FILL;
let even_odd = (draw_flags & DRAW_INFO_FLAGS_FILL_RULE_BIT) != 0u;
let size_and_rule = (n_segs << 1u) | u32(even_odd);
let fill = CmdFill(size_and_rule, seg_ix, tile.backdrop);
ptcl[cmd_offset + 1u] = fill.size_and_rule;
ptcl[cmd_offset + 2u] = fill.seg_data;
ptcl[cmd_offset + 3u] = u32(fill.backdrop);
cmd_offset += 4u;
} else {
if even_odd && (abs(tile.backdrop) & 1) == 0 {
return false;
}
alloc_cmd(1u);
ptcl[cmd_offset] = CMD_SOLID;
cmd_offset += 1u;
}
return true;
}

fn write_color(color: CmdColor) {
Expand Down Expand Up @@ -310,7 +306,20 @@ fn main(
let blend = scene[dd];
is_blend = blend != BLEND_CLIP;
}
let include_tile = tile.segment_count_or_ix != 0u || (tile.backdrop == 0) == is_clip || is_blend;

let di = draw_monoids[drawobj_ix].info_offset;
let draw_flags = info_bin_data[di];
let even_odd = (draw_flags & DRAW_INFO_FLAGS_FILL_RULE_BIT) != 0u;
let n_segs = tile.segment_count_or_ix;

// If this draw object represents an even-odd fill and we know that no line segment
// crosses this tile and then this draw object should not contribute to the tile if its
// backdrop (i.e. the winding number of its top-left corner) is even.
//
// NOTE: A clip should never get encoded with an even-odd fill.
let even_odd_discard = n_segs == 0u && even_odd && (abs(tile.backdrop) & 1) == 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my first pass, I was worried that this would be incorrect for even-odd clips. But I now see this can't be encoded, so my concern is more one of style than correctness.

If we ever do support even-odd clips (which is eoclip in PostScript and Core Graphics, W* in PDF), then we'd want to make the change. I think a version that gets this correct is just as concise, and potentially clearer, so suggest something along these lines:

    let backdrop_clear = select(tile.backdrop, tile.backdrop & 1, even_odd) == 0;
    let include_tile = n_segs != 0u || (backdrop_clear == is_clip) || is_blend;

But I wouldn't block the PR on this, it is correct as written.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like your formulation better than what I have as it is concise and doesn't rule out even-odd clips (which I wasn't aware of until now).

let include_tile = !even_odd_discard
&& (n_segs != 0u || (tile.backdrop == 0) == is_clip || is_blend);
if include_tile {
let el_slice = el_ix / 32u;
let el_mask = 1u << (el_ix & 31u);
Expand All @@ -336,6 +345,7 @@ fn main(
continue;
}
}

let el_ix = slice_ix * 32u + firstTrailingBit(bitmap);
drawobj_ix = sh_drawobj_ix[el_ix];
// clear LSB of bitmap, using bit magic
Expand All @@ -344,42 +354,35 @@ fn main(
let dm = draw_monoids[drawobj_ix];
let dd = config.drawdata_base + dm.scene_offset;
let di = dm.info_offset;
let draw_flags = info_bin_data[di];
if clip_zero_depth == 0u {
let tile_ix = sh_tile_base[el_ix] + sh_tile_stride[el_ix] * tile_y + tile_x;
let tile = tiles[tile_ix];
switch drawtag {
// DRAWTAG_FILL_COLOR
case 0x44u: {
let linewidth = bitcast<f32>(info_bin_data[di]);
if write_path(tile, tile_ix, linewidth) {
let rgba_color = scene[dd];
write_color(CmdColor(rgba_color));
}
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 index = scene[dd];
let info_offset = di + 1u;
write_grad(CMD_LIN_GRAD, index, info_offset);
}
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 index = scene[dd];
let info_offset = di + 1u;
write_grad(CMD_RAD_GRAD, index, info_offset);
}
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) {
write_image(di + 1u);
}
write_path(tile, tile_ix, draw_flags);
write_image(di + 1u);
}
// DRAWTAG_BEGIN_CLIP
case 0x9u: {
Expand All @@ -395,7 +398,8 @@ fn main(
// DRAWTAG_END_CLIP
case 0x21u: {
clip_depth -= 1u;
write_path(tile, tile_ix, -1.0);
// A clip shape is always a non-zero fill (draw_flags=0).
write_path(tile, tile_ix, /*draw_flags=*/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
19 changes: 10 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,18 @@ 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 for now
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
Loading