From 877da4a98ef5f6f96924ea2f532847a21332ed92 Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Thu, 4 Jun 2020 10:39:08 -0700 Subject: [PATCH] Faster coarse raster Store a lot more tile context in shared memory and do the work from that. --- piet-gpu/shader/coarse.comp | 115 ++++++++++++++++-------------------- piet-gpu/shader/coarse.spv | Bin 29380 -> 25796 bytes 2 files changed, 50 insertions(+), 65 deletions(-) diff --git a/piet-gpu/shader/coarse.comp b/piet-gpu/shader/coarse.comp index 2a3ba56..e488fbf 100644 --- a/piet-gpu/shader/coarse.comp +++ b/piet-gpu/shader/coarse.comp @@ -51,6 +51,10 @@ shared uint sh_tile_width[N_TILE]; shared uint sh_tile_x0[N_TILE]; shared uint sh_tile_y0[N_TILE]; +// These are set up so base + tile_y * stride + tile_x points to a Tile. +shared uint sh_tile_base[N_TILE]; +shared uint sh_tile_stride[N_TILE]; + // scale factors useful for converting coordinates to tiles #define SX (1.0 / float(TILE_WIDTH_PX)) #define SY (1.0 / float(TILE_HEIGHT_PX)) @@ -76,9 +80,12 @@ void main() { vec2 xy0 = vec2(N_TILE_X * TILE_WIDTH_PX * gl_WorkGroupID.x, N_TILE_Y * TILE_HEIGHT_PX * gl_WorkGroupID.y); uint th_ix = gl_LocalInvocationID.x; - uint tile_x = N_TILE_X * gl_WorkGroupID.x + gl_LocalInvocationID.x % N_TILE_X; - uint tile_y = N_TILE_Y * gl_WorkGroupID.y + gl_LocalInvocationID.x / N_TILE_X; - uint this_tile_ix = tile_y * WIDTH_IN_TILES + tile_x; + // 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; + 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; @@ -151,53 +158,48 @@ void main() { // Read one element, compute coverage. uint tag = Annotated_Nop; + uint element_ix; AnnotatedRef ref; float right_edge = 0.0; if (th_ix + rd_ix < wr_ix) { - uint element_ix = sh_elements[th_ix]; + element_ix = sh_elements[th_ix]; right_edge = sh_right_edge[th_ix]; ref = AnnotatedRef(element_ix * Annotated_size); tag = Annotated_tag(ref); } // Bounding box of element in pixel coordinates. - float xmin, xmax, ymin, ymax; + uint tile_count; switch (tag) { case Annotated_Fill: case Annotated_Stroke: - // Note: we take advantage of the fact that fills and strokes - // have compatible layout. - AnnoFill fill = Annotated_Fill_read(ref); - xmin = fill.bbox.x; - xmax = fill.bbox.z; - ymin = fill.bbox.y; - ymax = fill.bbox.w; + // Because the only elements we're processing right now are + // paths, we can just use the element index as the path index. + // In future, when we're doing a bunch of stuff, the path index + // should probably be stored in the annotated element. + uint path_ix = element_ix; + Path path = Path_read(PathRef(path_ix * Path_size)); + uint stride = path.bbox.z - path.bbox.x; + sh_tile_stride[th_ix] = stride; + int dx = int(path.bbox.x) - int(bin_tile_x); + int dy = int(path.bbox.y) - int(bin_tile_y); + int x0 = clamp(dx, 0, N_TILE_X); + int y0 = clamp(dy, 0, N_TILE_Y); + int x1 = clamp(int(path.bbox.z) - int(bin_tile_x), 0, N_TILE_X); + int y1 = clamp(int(path.bbox.w) - int(bin_tile_y), 0, N_TILE_Y); + sh_tile_width[th_ix] = uint(x1 - x0); + sh_tile_x0[th_ix] = x0; + sh_tile_y0[th_ix] = y0; + tile_count = uint(x1 - x0) * uint(y1 - y0); + // base relative to bin + uint base = path.tiles.offset - uint(dy * stride + dx) * Tile_size; + sh_tile_base[th_ix] = base; break; default: - ymin = 0; - ymax = 0; + tile_count = 0; break; } - // Draw the coverage area into the bitmasks. This uses an algorithm - // that computes the coverage of a span for given scanline. - - // Compute bounding box in tiles and clip to this bin. - int x0 = int(floor((xmin - xy0.x) * SX)); - int x1 = int(ceil((xmax - xy0.x) * SX)); - int y0 = int(floor((ymin - xy0.y) * SY)); - int y1 = int(ceil((ymax - xy0.y) * SY)); - - x0 = clamp(x0, 0, N_TILE_X); - x1 = clamp(x1, x0, N_TILE_X); - y0 = clamp(y0, 0, N_TILE_Y); - y1 = clamp(y1, y0, N_TILE_Y); - - uint tile_count = uint((x1 - x0) * (y1 - y0)); - sh_tile_width[th_ix] = uint(x1 - x0); - sh_tile_x0[th_ix] = uint(x0); - sh_tile_y0[th_ix] = uint(y0); - // Prefix sum of sh_tile_count sh_tile_count[th_ix] = tile_count; for (uint i = 0; i < LG_N_TILE; i++) { @@ -223,19 +225,11 @@ void main() { uint width = sh_tile_width[el_ix]; uint x = sh_tile_x0[el_ix] + seq_ix % width; uint y = sh_tile_y0[el_ix] + seq_ix / width; - uint tile_x = x + gl_WorkGroupID.x * N_TILE_X; - uint tile_y = y + gl_WorkGroupID.y * N_TILE_Y; - uint element_ix = sh_elements[el_ix]; - Path path = Path_read(PathRef(element_ix * Path_size)); - if (tile_x >= path.bbox.x && tile_x < path.bbox.z && tile_y >= path.bbox.y && tile_y < path.bbox.w) { - uint stride = path.bbox.z - path.bbox.x; - uint tile_subix = (tile_y - path.bbox.y) * stride + tile_x - path.bbox.x; - Tile tile = Tile_read(Tile_index(path.tiles, tile_subix)); - if (tile.tile.offset != 0) { - uint el_slice = el_ix / 32; - uint el_mask = 1 << (el_ix & 31); - atomicOr(sh_bitmaps[el_slice][y * N_TILE_X + x], el_mask); - } + Tile tile = Tile_read(TileRef(sh_tile_base[el_ix] + (sh_tile_stride[el_ix] * y + x) * Tile_size)); + if (tile.tile.offset != 0) { + uint el_slice = el_ix / 32; + uint el_mask = 1 << (el_ix & 31); + atomicOr(sh_bitmaps[el_slice][y * N_TILE_X + x], el_mask); } } @@ -407,25 +401,16 @@ void main() { break; */ case Annotated_Stroke: - // Because the only elements we're processing right now are - // paths, we can just use the element index as the path index. - // In future, when we're doing a bunch of stuff, the path index - // should probably be stored in the annotated element. - uint path_ix = element_ix; - Path path = Path_read(PathRef(path_ix * Path_size)); - uint stride = path.bbox.z - path.bbox.x; - uint tile_subix = (tile_y - path.bbox.y) * stride + tile_x - path.bbox.x; - Tile tile = Tile_read(Tile_index(path.tiles, tile_subix)); - if (tile.tile.offset != 0) { - 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; - } + Tile 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; } } diff --git a/piet-gpu/shader/coarse.spv b/piet-gpu/shader/coarse.spv index 2cc0acfe36e52bfe32eb5cc7164197d9f8d4276e..c5a304bcf52b5c799a40b151b874cc92e7878e59 100644 GIT binary patch literal 25796 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 K=FK|$