From 7f4a6523a81ce89fdacf64f9be32b4834c276ec5 Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Wed, 3 Jun 2020 17:55:42 -0700 Subject: [PATCH] Filter sparse tiles Have a more-parallel read of the tile structures based on bbox coverage, and only set the bit when the tile isn't empty. This is a speedup, but there is some duplicated work and it is possible to improve it further. --- piet-gpu/shader/coarse.comp | 103 ++++++++++++++++++------------------ piet-gpu/shader/coarse.spv | Bin 26696 -> 29976 bytes 2 files changed, 51 insertions(+), 52 deletions(-) diff --git a/piet-gpu/shader/coarse.comp b/piet-gpu/shader/coarse.comp index 28efd16..692eeca 100644 --- a/piet-gpu/shader/coarse.comp +++ b/piet-gpu/shader/coarse.comp @@ -44,16 +44,12 @@ shared uint sh_part_count[N_PART_READ]; shared uint sh_part_elements[N_PART_READ]; shared uint sh_bitmaps[N_SLICE][N_TILE]; -shared uint sh_backdrop[N_SLICE][N_TILE]; -shared uint sh_bd_sign[N_SLICE]; -shared uint sh_is_segment[N_SLICE]; -// Shared state for parallel segment output stage - -// Count of total number of segments in each tile, then -// inclusive prefix sum of same. -shared uint sh_seg_count[N_TILE]; -shared uint sh_seg_alloc; +shared uint sh_tile_count[N_TILE]; +// The width of the tile rect for the element, intersected with this bin +shared uint sh_tile_width[N_TILE]; +shared uint sh_tile_x0[N_TILE]; +shared uint sh_tile_y0[N_TILE]; // scale factors useful for converting coordinates to tiles #define SX (1.0 / float(TILE_WIDTH_PX)) @@ -70,30 +66,6 @@ void alloc_cmd(inout CmdRef cmd_ref, inout uint cmd_limit) { } } -#define CHUNK_ALLOC_SLAB 16 - -uint alloc_chunk_remaining; -uint alloc_chunk_offset; - -SegChunkRef alloc_seg_chunk() { - if (alloc_chunk_remaining == 0) { - alloc_chunk_offset = atomicAdd(alloc, CHUNK_ALLOC_SLAB * SegChunk_size); - alloc_chunk_remaining = CHUNK_ALLOC_SLAB; - } - uint offset = alloc_chunk_offset; - alloc_chunk_offset += SegChunk_size; - alloc_chunk_remaining--; - return SegChunkRef(offset); -} - -// Accumulate delta to backdrop. -// -// Each bit for which bd_bitmap is 1 and bd_sign is 1 counts as +1, and each -// bit for which bd_bitmap is 1 and bd_sign is 0 counts as -1. -int count_backdrop(uint bd_bitmap, uint bd_sign) { - return bitCount(bd_bitmap & bd_sign) - bitCount(bd_bitmap & ~bd_sign); -} - void main() { // Could use either linear or 2d layouts for both dispatch and // invocations within the workgroup. We'll use variables to abstract. @@ -110,13 +82,6 @@ void main() { CmdRef cmd_ref = CmdRef(this_tile_ix * PTCL_INITIAL_ALLOC); uint cmd_limit = cmd_ref.offset + PTCL_INITIAL_ALLOC - 2 * Cmd_size; - // Allocation and management of segment output - SegChunkRef first_seg_chunk = SegChunkRef(0); - SegChunkRef last_chunk_ref = SegChunkRef(0); - uint last_chunk_n = 0; - SegmentRef last_chunk_segs = SegmentRef(0); - alloc_chunk_remaining = 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 uint rd_ix = 0; @@ -125,17 +90,10 @@ 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; - if (th_ix < N_SLICE) { - sh_bd_sign[th_ix] = 0; - } int backdrop = 0; while (true) { for (uint i = 0; i < N_SLICE; i++) { sh_bitmaps[i][th_ix] = 0; - sh_backdrop[i][th_ix] = 0; - } - if (th_ix < N_SLICE) { - sh_is_segment[th_ix] = 0; } // parallel read of input partitions @@ -204,8 +162,6 @@ void main() { // Bounding box of element in pixel coordinates. float xmin, xmax, ymin, ymax; - uint my_slice = th_ix / 32; - uint my_mask = 1 << (th_ix & 31); switch (tag) { case Annotated_Fill: case Annotated_Stroke: @@ -231,15 +187,58 @@ void main() { 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); - for (uint y = y0; y < y1; y++) { - for (uint x = x0; x < x1; x++) { - atomicOr(sh_bitmaps[my_slice][y * N_TILE_X + x], my_mask); + + 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++) { + barrier(); + if (th_ix >= (1 << i)) { + tile_count += sh_tile_count[th_ix - (1 << i)]; + } + barrier(); + sh_tile_count[th_ix] = tile_count; + } + barrier(); + uint total_tile_count = sh_tile_count[N_TILE - 1]; + for (uint ix = th_ix; ix < total_tile_count; ix += N_TILE) { + // Binary search to find element + uint el_ix = 0; + for (uint i = 0; i < LG_N_TILE; i++) { + uint probe = el_ix + ((N_TILE / 2) >> i); + if (ix >= sh_tile_count[probe - 1]) { + el_ix = probe; + } + } + uint seq_ix = ix - (el_ix > 0 ? sh_tile_count[el_ix - 1] : 0); + 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); + } } } + barrier(); // We've computed coverage and other info for each element in the input, now for diff --git a/piet-gpu/shader/coarse.spv b/piet-gpu/shader/coarse.spv index 6b2afafb9dfc2fd16bd974762c9ddfc43ac15b64..b3a90c89d27c70e089535afb0615701fd3ec8fec 100644 GIT binary patch literal 29976 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%0DCwFZ9k{{hGHJ##CZyAoXp`b!D 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 md3^7!eY1Y