From 4138f8a5165257622880157e49b6485c6c66c73b Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Fri, 27 Nov 2020 08:42:21 -0800 Subject: [PATCH 1/4] Optimize clips Optimize tiles with clip masks that are all-zero or all-one. Part of #36 --- piet-gpu/shader/coarse.comp | 176 ++++++++++++++++++++++-------------- piet-gpu/shader/coarse.spv | Bin 42212 -> 43964 bytes piet-gpu/src/lib.rs | 8 +- 3 files changed, 113 insertions(+), 71 deletions(-) diff --git a/piet-gpu/shader/coarse.comp b/piet-gpu/shader/coarse.comp index 12ebb98..a2be143 100644 --- a/piet-gpu/shader/coarse.comp +++ b/piet-gpu/shader/coarse.comp @@ -84,11 +84,22 @@ void main() { // Coordinates of top left of bin, in tiles. uint bin_tile_x = N_TILE_X * gl_WorkGroupID.x; uint bin_tile_y = N_TILE_Y * gl_WorkGroupID.y; + + // Per-tile state uint tile_x = gl_LocalInvocationID.x % N_TILE_X; uint tile_y = gl_LocalInvocationID.x / N_TILE_X; uint this_tile_ix = (bin_tile_y + tile_y) * WIDTH_IN_TILES + bin_tile_x + tile_x; CmdRef cmd_ref = CmdRef(this_tile_ix * PTCL_INITIAL_ALLOC); uint cmd_limit = cmd_ref.offset + PTCL_INITIAL_ALLOC - 2 * Cmd_size; + // The nesting depth of the clip stack + uint clip_depth = 0; + // State for the "clip zero" optimization. If it's nonzero, then we are + // currently in a clip for which the entire tile has an alpha of zero, and + // the value is the depth after the "begin clip" of that element. + uint clip_zero_depth = 0; + // State for the "clip one" optimization. If bit `i` is set, then that means + // that the clip pushed at depth `i` has an alpha of all one. + uint clip_one_mask = 0; // I'm sure we can figure out how to do this with at least one fewer register... // Items up to rd_ix have been read from sh_elements @@ -98,6 +109,7 @@ void main() { // Items between part_start_ix and ready_ix are ready to be transferred from sh_part_elements uint part_start_ix = 0; uint ready_ix = 0; + while (true) { for (uint i = 0; i < N_SLICE; i++) { sh_bitmaps[i][th_ix] = 0; @@ -274,77 +286,105 @@ void main() { ref = AnnotatedRef(element_ix * Annotated_size); tag = Annotated_tag(ref); - switch (tag) { - case Annotated_Fill: - Tile tile = Tile_read(TileRef(sh_tile_base[element_ref_ix] - + (sh_tile_stride[element_ref_ix] * tile_y + tile_x) * Tile_size)); - AnnoFill fill = Annotated_Fill_read(ref); - alloc_cmd(cmd_ref, cmd_limit); - if (tile.tile.offset != 0) { - CmdFill cmd_fill; - cmd_fill.tile_ref = tile.tile.offset; - cmd_fill.backdrop = tile.backdrop; - cmd_fill.rgba_color = fill.rgba_color; - Cmd_Fill_write(cmd_ref, cmd_fill); - } else { - Cmd_Solid_write(cmd_ref, CmdSolid(fill.rgba_color)); - } - cmd_ref.offset += Cmd_size; - break; - case Annotated_BeginClip: - tile = Tile_read(TileRef(sh_tile_base[element_ref_ix] - + (sh_tile_stride[element_ref_ix] * tile_y + tile_x) * Tile_size)); - alloc_cmd(cmd_ref, cmd_limit); - if (tile.tile.offset != 0) { - CmdBeginClip cmd_begin_clip; - cmd_begin_clip.tile_ref = tile.tile.offset; - cmd_begin_clip.backdrop = tile.backdrop; - Cmd_BeginClip_write(cmd_ref, cmd_begin_clip); - } else { - // TODO: here is where a bunch of optimization magic should happen - float alpha = tile.backdrop == 0 ? 0.0 : 1.0; - Cmd_BeginSolidClip_write(cmd_ref, CmdBeginSolidClip(alpha)); - } - cmd_ref.offset += Cmd_size; - break; - case Annotated_EndClip: - alloc_cmd(cmd_ref, cmd_limit); - Cmd_EndClip_write(cmd_ref, CmdEndClip(1.0)); - cmd_ref.offset += Cmd_size; - break; - case Annotated_FillMask: - case Annotated_FillMaskInv: - tile = Tile_read(TileRef(sh_tile_base[element_ref_ix] - + (sh_tile_stride[element_ref_ix] * tile_y + tile_x) * Tile_size)); - AnnoFillMask fill_mask = Annotated_FillMask_read(ref); - alloc_cmd(cmd_ref, cmd_limit); - if (tile.tile.offset != 0) { - CmdFillMask cmd_fill; - cmd_fill.tile_ref = tile.tile.offset; - cmd_fill.backdrop = tile.backdrop; - cmd_fill.mask = fill_mask.mask; - if (tag == Annotated_FillMask) { - Cmd_FillMask_write(cmd_ref, cmd_fill); + if (clip_zero_depth == 0) { + switch (tag) { + case Annotated_Fill: + Tile tile = Tile_read(TileRef(sh_tile_base[element_ref_ix] + + (sh_tile_stride[element_ref_ix] * tile_y + tile_x) * Tile_size)); + AnnoFill fill = Annotated_Fill_read(ref); + alloc_cmd(cmd_ref, cmd_limit); + if (tile.tile.offset != 0) { + CmdFill cmd_fill; + cmd_fill.tile_ref = tile.tile.offset; + cmd_fill.backdrop = tile.backdrop; + cmd_fill.rgba_color = fill.rgba_color; + Cmd_Fill_write(cmd_ref, cmd_fill); } else { - Cmd_FillMaskInv_write(cmd_ref, cmd_fill); + Cmd_Solid_write(cmd_ref, CmdSolid(fill.rgba_color)); } - } else { - Cmd_SolidMask_write(cmd_ref, CmdSolidMask(fill_mask.mask)); + cmd_ref.offset += Cmd_size; + break; + case Annotated_BeginClip: + tile = Tile_read(TileRef(sh_tile_base[element_ref_ix] + + (sh_tile_stride[element_ref_ix] * tile_y + tile_x) * Tile_size)); + if (tile.tile.offset == 0 && tile.backdrop == 0) { + clip_zero_depth = clip_depth + 1; + } else if (tile.tile.offset == 0 && clip_depth < 32) { + clip_one_mask |= (1 << clip_depth); + } else { + alloc_cmd(cmd_ref, cmd_limit); + if (tile.tile.offset != 0) { + CmdBeginClip cmd_begin_clip; + cmd_begin_clip.tile_ref = tile.tile.offset; + cmd_begin_clip.backdrop = tile.backdrop; + Cmd_BeginClip_write(cmd_ref, cmd_begin_clip); + } else { + // TODO: here is where a bunch of optimization magic should happen + float alpha = tile.backdrop == 0 ? 0.0 : 1.0; + Cmd_BeginSolidClip_write(cmd_ref, CmdBeginSolidClip(alpha)); + } + cmd_ref.offset += Cmd_size; + if (clip_depth < 32) { + clip_one_mask &= ~(1 << clip_depth); + } + } + clip_depth++; + break; + case Annotated_EndClip: + clip_depth--; + if (clip_depth >= 32 || (clip_one_mask & (1 << clip_depth)) == 0) { + alloc_cmd(cmd_ref, cmd_limit); + Cmd_EndClip_write(cmd_ref, CmdEndClip(1.0)); + cmd_ref.offset += Cmd_size; + } + break; + case Annotated_FillMask: + case Annotated_FillMaskInv: + tile = Tile_read(TileRef(sh_tile_base[element_ref_ix] + + (sh_tile_stride[element_ref_ix] * tile_y + tile_x) * Tile_size)); + AnnoFillMask fill_mask = Annotated_FillMask_read(ref); + alloc_cmd(cmd_ref, cmd_limit); + if (tile.tile.offset != 0) { + CmdFillMask cmd_fill; + cmd_fill.tile_ref = tile.tile.offset; + cmd_fill.backdrop = tile.backdrop; + cmd_fill.mask = fill_mask.mask; + if (tag == Annotated_FillMask) { + Cmd_FillMask_write(cmd_ref, cmd_fill); + } else { + Cmd_FillMaskInv_write(cmd_ref, cmd_fill); + } + } else { + Cmd_SolidMask_write(cmd_ref, CmdSolidMask(fill_mask.mask)); + } + cmd_ref.offset += Cmd_size; + break; + case Annotated_Stroke: + tile = Tile_read(TileRef(sh_tile_base[element_ref_ix] + + (sh_tile_stride[element_ref_ix] * tile_y + tile_x) * Tile_size)); + AnnoStroke stroke = Annotated_Stroke_read(ref); + CmdStroke cmd_stroke; + cmd_stroke.tile_ref = tile.tile.offset; + cmd_stroke.half_width = 0.5 * stroke.linewidth; + cmd_stroke.rgba_color = stroke.rgba_color; + alloc_cmd(cmd_ref, cmd_limit); + Cmd_Stroke_write(cmd_ref, cmd_stroke); + cmd_ref.offset += Cmd_size; + break; + } + } else { + // In "clip zero" state, suppress all drawing + switch (tag) { + case Annotated_BeginClip: + clip_depth++; + break; + case Annotated_EndClip: + if (clip_depth == clip_zero_depth) { + clip_zero_depth = 0; + } + clip_depth--; + break; } - cmd_ref.offset += Cmd_size; - break; - case Annotated_Stroke: - tile = Tile_read(TileRef(sh_tile_base[element_ref_ix] - + (sh_tile_stride[element_ref_ix] * tile_y + tile_x) * Tile_size)); - AnnoStroke stroke = Annotated_Stroke_read(ref); - CmdStroke cmd_stroke; - cmd_stroke.tile_ref = tile.tile.offset; - cmd_stroke.half_width = 0.5 * stroke.linewidth; - cmd_stroke.rgba_color = stroke.rgba_color; - alloc_cmd(cmd_ref, cmd_limit); - Cmd_Stroke_write(cmd_ref, cmd_stroke); - cmd_ref.offset += Cmd_size; - break; } } barrier(); diff --git a/piet-gpu/shader/coarse.spv b/piet-gpu/shader/coarse.spv index 98dde1b67218125314ac038ecdef8ad4b1dccea4..c7693302f046404a62e2640a6d47c1eedbe91b21 100644 GIT binary patch literal 43964 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 N{{4j1%|7SH{{aHc!tVe8 literal 42212 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 diff --git a/piet-gpu/src/lib.rs b/piet-gpu/src/lib.rs index 0a6152d..85b2e0c 100644 --- a/piet-gpu/src/lib.rs +++ b/piet-gpu/src/lib.rs @@ -99,9 +99,11 @@ fn render_cardioid(rc: &mut impl RenderContext) { fn render_clip_test(rc: &mut impl RenderContext) { const N: usize = 16; const X0: f64 = 50.0; - const Y0: f64 = 50.0; - const X1: f64 = 100.0; - const Y1: f64 = 100.0; + const Y0: f64 = 450.0; + // Note: if it gets much larger, it will exceed the 1MB scratch buffer. + // But this is a pretty demanding test. + const X1: f64 = 550.0; + const Y1: f64 = 950.0; let step = 1.0 / ((N + 1) as f64); for i in 0..N { let t = ((i + 1) as f64) * step; From 2068171f96c492c71040cd70edafd65b4b50b4b5 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Tue, 1 Dec 2020 17:59:37 +0100 Subject: [PATCH 2/4] path_coarse.comp: tighten variable scopes, delete unused variables No functional changes. Signed-off-by: Elias Naur --- piet-gpu/shader/path_coarse.comp | 30 +++++++++++++----------------- piet-gpu/shader/path_coarse.spv | Bin 28876 -> 28916 bytes 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/piet-gpu/shader/path_coarse.comp b/piet-gpu/shader/path_coarse.comp index 658af0e..069d13b 100644 --- a/piet-gpu/shader/path_coarse.comp +++ b/piet-gpu/shader/path_coarse.comp @@ -101,12 +101,6 @@ void main() { if (element_ix < n_pathseg) { tag = PathSeg_tag(ref); } - // Setup for coverage algorithm. - float a, b, c; - // Bounding box of element in pixel coordinates. - float xmin, xmax, ymin, ymax; - PathStrokeLine line; - float dx; switch (tag) { case PathSeg_FillCubic: case PathSeg_StrokeCubic: @@ -162,22 +156,24 @@ void main() { } // Output line segment - xmin = min(p0.x, p1.x) - cubic.stroke.x; - xmax = max(p0.x, p1.x) + cubic.stroke.x; - ymin = min(p0.y, p1.y) - cubic.stroke.y; - ymax = max(p0.y, p1.y) + cubic.stroke.y; + + // Bounding box of element in pixel coordinates. + float xmin = min(p0.x, p1.x) - cubic.stroke.x; + float xmax = max(p0.x, p1.x) + cubic.stroke.x; + float ymin = min(p0.y, p1.y) - cubic.stroke.y; + float ymax = max(p0.y, p1.y) + cubic.stroke.y; float dx = p1.x - p0.x; float dy = p1.y - p0.y; // Set up for per-scanline coverage formula, below. float invslope = abs(dy) < 1e-9 ? 1e9 : dx / dy; - c = (cubic.stroke.x + abs(invslope) * (0.5 * float(TILE_HEIGHT_PX) + cubic.stroke.y)) * SX; - b = invslope; // Note: assumes square tiles, otherwise scale. - a = (p0.x - (p0.y - 0.5 * float(TILE_HEIGHT_PX)) * b) * SX; + float c = (cubic.stroke.x + abs(invslope) * (0.5 * float(TILE_HEIGHT_PX) + cubic.stroke.y)) * SX; + float b = invslope; // Note: assumes square tiles, otherwise scale. + float a = (p0.x - (p0.y - 0.5 * float(TILE_HEIGHT_PX)) * b) * SX; - int x0 = int(floor((xmin) * SX)); - int x1 = int(ceil((xmax) * SX)); - int y0 = int(floor((ymin) * SY)); - int y1 = int(ceil((ymax) * SY)); + int x0 = int(floor(xmin * SX)); + int x1 = int(ceil(xmax * SX)); + int y0 = int(floor(ymin * SY)); + int y1 = int(ceil(ymax * SY)); x0 = clamp(x0, bbox.x, bbox.z); y0 = clamp(y0, bbox.y, bbox.w); diff --git a/piet-gpu/shader/path_coarse.spv b/piet-gpu/shader/path_coarse.spv index f82a031295caaf30b5e4075547981235bfe9d90c..3ba2cff61fa355dc14b10e644fa7c579fdf4a0bf 100644 GIT binary patch delta 6341 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 JF#gtF{|Cj*3D^Jt From 19f4d9fa95fb1867687443f581c04c10083a8985 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Tue, 1 Dec 2020 18:06:09 +0100 Subject: [PATCH 3/4] change tile segment representation to (origin, vector) Eliminates the precision loss of the subtraction in the sign(end.x - start.x) expression in kernel4. That's important for the next change that avoids inconsistent line intersections in path_coarse. Updates #23 Signed-off-by: Elias Naur --- piet-gpu-types/src/tile.rs | 4 ++-- piet-gpu/shader/kernel4.comp | 10 +++++----- piet-gpu/shader/kernel4.spv | Bin 31784 -> 31680 bytes piet-gpu/shader/path_coarse.comp | 10 ++++++---- piet-gpu/shader/path_coarse.spv | Bin 28916 -> 29056 bytes piet-gpu/shader/tile.h | 16 ++++++++-------- 6 files changed, 21 insertions(+), 19 deletions(-) diff --git a/piet-gpu-types/src/tile.rs b/piet-gpu-types/src/tile.rs index 18318e3..38ee93b 100644 --- a/piet-gpu-types/src/tile.rs +++ b/piet-gpu-types/src/tile.rs @@ -13,8 +13,8 @@ piet_gpu! { } // Segments within a tile are represented as a linked list. struct TileSeg { - start: [f32; 2], - end: [f32; 2], + origin: [f32; 2], + vector: [f32; 2], y_edge: f32, next: Ref, } diff --git a/piet-gpu/shader/kernel4.comp b/piet-gpu/shader/kernel4.comp index 72ab396..bf9ec44 100644 --- a/piet-gpu/shader/kernel4.comp +++ b/piet-gpu/shader/kernel4.comp @@ -65,8 +65,8 @@ float[CHUNK] computeArea(vec2 xy, int backdrop, uint tile_ref) { TileSeg seg = TileSeg_read(tile_seg_ref); for (uint k = 0; k < CHUNK; k++) { vec2 my_xy = vec2(xy.x, xy.y + float(k * CHUNK_DY)); - vec2 start = seg.start - my_xy; - vec2 end = seg.end - my_xy; + vec2 start = seg.origin - my_xy; + vec2 end = start + seg.vector; vec2 window = clamp(vec2(start.y, end.y), 0.0, 1.0); if (window.x != window.y) { vec2 t = (window - start.y) / (end.y - start.y); @@ -79,7 +79,7 @@ float[CHUNK] computeArea(vec2 xy, int backdrop, uint tile_ref) { float a = (b + 0.5 * (d * d - c * c) - xmin) / (xmax - xmin); area[k] += a * (window.x - window.y); } - area[k] += sign(end.x - start.x) * clamp(my_xy.y - seg.y_edge + 1.0, 0.0, 1.0); + area[k] += sign(seg.vector.x) * clamp(my_xy.y - seg.y_edge + 1.0, 0.0, 1.0); } tile_seg_ref = seg.next; } while (tile_seg_ref.offset != 0); @@ -131,9 +131,9 @@ void main() { TileSegRef tile_seg_ref = TileSegRef(stroke.tile_ref); do { TileSeg seg = TileSeg_read(tile_seg_ref); - vec2 line_vec = seg.end - seg.start; + vec2 line_vec = seg.vector; for (uint k = 0; k < CHUNK; k++) { - vec2 dpos = xy + vec2(0.5, 0.5) - seg.start; + vec2 dpos = xy + vec2(0.5, 0.5) - seg.origin; dpos.y += float(k * CHUNK_DY); float t = clamp(dot(line_vec, dpos) / dot(line_vec, line_vec), 0.0, 1.0); df[k] = min(df[k], length(line_vec * t - dpos)); diff --git a/piet-gpu/shader/kernel4.spv b/piet-gpu/shader/kernel4.spv index 808d7077de31636dd101bade005c49aba8df1c5a..fffdc4a97ab267eb9762f51ebb82bfe4503ccb49 100644 GIT binary patch literal 31680 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 2; uint old = atomicExchange(tile[tile_el], tile_offset); - tile_seg.start = p0; - tile_seg.end = p1; + tile_seg.origin = p0; + tile_seg.vector = p1 - p0; float y_edge = 0.0; if (tag == PathSeg_FillCubic) { y_edge = mix(p0.y, p1.y, (tile_x0 - p0.x) / dx); if (min(p0.x, p1.x) < tile_x0 && y_edge >= tile_y0 && y_edge < tile_y0 + TILE_HEIGHT_PX) { + vec2 p = vec2(tile_x0, y_edge); if (p0.x > p1.x) { - tile_seg.end = vec2(tile_x0, y_edge); + tile_seg.vector = p - p0; } else { - tile_seg.start = vec2(tile_x0, y_edge); + tile_seg.origin = p; + tile_seg.vector = p1 - p; } } else { y_edge = 1e9; diff --git a/piet-gpu/shader/path_coarse.spv b/piet-gpu/shader/path_coarse.spv index 3ba2cff61fa355dc14b10e644fa7c579fdf4a0bf..952863cb4c91267d7d3174a3aa3a37127969a46b 100644 GIT binary patch delta 5731 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 E|Fk!@Y5)KL diff --git a/piet-gpu/shader/tile.h b/piet-gpu/shader/tile.h index d7659ff..b6c5e14 100644 --- a/piet-gpu/shader/tile.h +++ b/piet-gpu/shader/tile.h @@ -35,8 +35,8 @@ TileRef Tile_index(TileRef ref, uint index) { } struct TileSeg { - vec2 start; - vec2 end; + vec2 origin; + vec2 vector; float y_edge; TileSegRef next; }; @@ -90,8 +90,8 @@ TileSeg TileSeg_read(TileSegRef ref) { uint raw4 = tile[ix + 4]; uint raw5 = tile[ix + 5]; TileSeg s; - s.start = vec2(uintBitsToFloat(raw0), uintBitsToFloat(raw1)); - s.end = vec2(uintBitsToFloat(raw2), uintBitsToFloat(raw3)); + s.origin = vec2(uintBitsToFloat(raw0), uintBitsToFloat(raw1)); + s.vector = vec2(uintBitsToFloat(raw2), uintBitsToFloat(raw3)); s.y_edge = uintBitsToFloat(raw4); s.next = TileSegRef(raw5); return s; @@ -99,10 +99,10 @@ TileSeg TileSeg_read(TileSegRef ref) { void TileSeg_write(TileSegRef ref, TileSeg s) { uint ix = ref.offset >> 2; - tile[ix + 0] = floatBitsToUint(s.start.x); - tile[ix + 1] = floatBitsToUint(s.start.y); - tile[ix + 2] = floatBitsToUint(s.end.x); - tile[ix + 3] = floatBitsToUint(s.end.y); + tile[ix + 0] = floatBitsToUint(s.origin.x); + tile[ix + 1] = floatBitsToUint(s.origin.y); + tile[ix + 2] = floatBitsToUint(s.vector.x); + tile[ix + 3] = floatBitsToUint(s.vector.y); tile[ix + 4] = floatBitsToUint(s.y_edge); tile[ix + 5] = s.next.offset; } From 29cfb8b63edc28517b16e3ba1da7790e360ed557 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Tue, 1 Dec 2020 18:13:33 +0100 Subject: [PATCH 4/4] eliminate inconsistent line intersections from path_coarse.comp The finite precision of floating point computations can lead the coarse renderer into inconsistent tile intersections, which implies impossible line segments such as lines with gaps or double intersections. The winding number algorithm is sensitive to these errors which show up as incorrectly filled paths. This change forces all intersections to be consistent. First, the floating point top edge intersection test is removed; top edge intersections are completely determined by left edge intersections. Then, left edge intersections are inserted from the tile with the last top edge intersection. The next top edge is then fixed to be the last tile with a left edge intersection. More details in the patch comments. Fixes #23 Signed-off-by: Elias Naur --- piet-gpu/shader/path_coarse.comp | 56 ++++++++++++++++++++++++------- piet-gpu/shader/path_coarse.spv | Bin 29056 -> 30896 bytes 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/piet-gpu/shader/path_coarse.comp b/piet-gpu/shader/path_coarse.comp index b018973..eb3509b 100644 --- a/piet-gpu/shader/path_coarse.comp +++ b/piet-gpu/shader/path_coarse.comp @@ -171,9 +171,9 @@ void main() { float a = (p0.x - (p0.y - 0.5 * float(TILE_HEIGHT_PX)) * b) * SX; int x0 = int(floor(xmin * SX)); - int x1 = int(ceil(xmax * SX)); + int x1 = int(floor(xmax * SX) + 1); int y0 = int(floor(ymin * SY)); - int y1 = int(ceil(ymax * SY)); + int y1 = int(floor(ymax * SY) + 1); x0 = clamp(x0, bbox.x, bbox.z); y0 = clamp(y0, bbox.y, bbox.w); @@ -187,19 +187,30 @@ void main() { // Consider using subgroups to aggregate atomic add. uint tile_offset = atomicAdd(alloc, n_tile_alloc * TileSeg_size); TileSeg tile_seg; + + int xray = int(floor(p0.x*SX)); + int last_xray = int(floor(p1.x*SX)); + if (p0.y > p1.y) { + int tmp = xray; + xray = last_xray; + last_xray = tmp; + } for (int y = y0; y < y1; y++) { - float tile_y0 = float(y * TILE_HEIGHT_PX); - if (tag == PathSeg_FillCubic && min(p0.y, p1.y) <= tile_y0) { - int xray = max(int(ceil(xc - 0.5 * b)), bbox.x); - if (xray < bbox.z) { - int backdrop = p1.y < p0.y ? 1 : -1; - TileRef tile_ref = Tile_index(path.tiles, uint(base + xray)); - uint tile_el = tile_ref.offset >> 2; - atomicAdd(tile[tile_el + 1], backdrop); - } + int xbackdrop = max(xray + 1, bbox.x); + if (tag == PathSeg_FillCubic && y > y0 && xbackdrop < bbox.z) { + int backdrop = p1.y < p0.y ? 1 : -1; + TileRef tile_ref = Tile_index(path.tiles, uint(base + xbackdrop)); + uint tile_el = tile_ref.offset >> 2; + atomicAdd(tile[tile_el + 1], backdrop); } + int xx0 = clamp(int(floor(xc - c)), x0, x1); int xx1 = clamp(int(ceil(xc + c)), x0, x1); + xx1 = max(xx1, xray + 1); + + // next_xray is the xray for the next scanline; it is derived + // by left edge intersections computed below. + int next_xray = xray; for (int x = xx0; x < xx1; x++) { float tile_x0 = float(x * TILE_WIDTH_PX); TileRef tile_ref = Tile_index(path.tiles, uint(base + x)); @@ -209,8 +220,10 @@ void main() { tile_seg.vector = p1 - p0; float y_edge = 0.0; if (tag == PathSeg_FillCubic) { + float tile_y0 = float(y * TILE_HEIGHT_PX); y_edge = mix(p0.y, p1.y, (tile_x0 - p0.x) / dx); if (min(p0.x, p1.x) < tile_x0 && y_edge >= tile_y0 && y_edge < tile_y0 + TILE_HEIGHT_PX) { + // Left edge intersection. vec2 p = vec2(tile_x0, y_edge); if (p0.x > p1.x) { tile_seg.vector = p - p0; @@ -218,7 +231,25 @@ void main() { tile_seg.origin = p; tile_seg.vector = p1 - p; } - } else { + // kernel4 uses sign(vector.x) for the sign of the intersection backdrop. + // Nudge zeroes towards the intended sign. + if (tile_seg.vector.x == 0) { + tile_seg.vector.x += sign(p1.x - p0.x)*1e-9; + } + // Move next_xray consistently with previous intersections. + if (x > next_xray && next_xray >= xray) { + next_xray = x; + } else if (x <= next_xray && next_xray <= xray) { + next_xray = x - 1; + } + } + // Force last xray on the last scanline for consistency with later + // line segments. + if (y == y1 - 1) { + next_xray = last_xray; + } + // Drop inconsistent intersections. + if (x <= min(xray, next_xray) || max(xray, next_xray) < x) { y_edge = 1e9; } } @@ -229,6 +260,7 @@ void main() { } xc += b; base += stride; + xray = next_xray; } n_out += 1; diff --git a/piet-gpu/shader/path_coarse.spv b/piet-gpu/shader/path_coarse.spv index 952863cb4c91267d7d3174a3aa3a37127969a46b..767bbda65aa53889f1e3bd5a6aaac9595df6fb08 100644 GIT binary patch delta 8340 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