From 29cfb8b63edc28517b16e3ba1da7790e360ed557 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Tue, 1 Dec 2020 18:13:33 +0100 Subject: [PATCH] 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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