From 64daf843b0dc4af10b5f0e8a56ff11b40fe31d73 Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Tue, 12 May 2020 19:54:19 -0700 Subject: [PATCH] Better output allocation in binning --- piet-gpu/shader/binning.comp | 69 +++++++++++++++++++++++------------ piet-gpu/shader/binning.spv | Bin 14804 -> 16068 bytes 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/piet-gpu/shader/binning.comp b/piet-gpu/shader/binning.comp index bf7bbae..241d637 100644 --- a/piet-gpu/shader/binning.comp +++ b/piet-gpu/shader/binning.comp @@ -49,16 +49,16 @@ shared uint sh_my_tile; void main() { BinChunkRef chunk_ref = BinChunkRef((gl_LocalInvocationID.x * N_WG + gl_WorkGroupID.x) * BIN_INITIAL_ALLOC); - uint chunk_limit = chunk_ref.offset + BIN_INITIAL_ALLOC - BinInstance_size; + uint wr_limit = chunk_ref.offset + BIN_INITIAL_ALLOC; uint chunk_n = 0; - BinInstanceRef instance_ref = BinInstanceRef(chunk_ref.offset + BinChunk_size); + uint my_n_elements = n_elements; while (true) { if (gl_LocalInvocationID.x == 0) { sh_my_tile = atomicAdd(tile_ix, 1); } barrier(); uint my_tile = sh_my_tile; - if (my_tile * N_TILE >= n_elements) { + if (my_tile * N_TILE >= my_n_elements) { break; } @@ -70,7 +70,10 @@ void main() { // Read inputs and determine coverage of bins uint element_ix = my_tile * N_TILE + gl_LocalInvocationID.x; AnnotatedRef ref = AnnotatedRef(element_ix * Annotated_size); - uint tag = Annotated_tag(ref); + uint tag = Annotated_Nop; + if (element_ix < my_n_elements) { + tag = Annotated_tag(ref); + } int x0 = 0, y0 = 0, x1 = 0, y1 = 0; switch (tag) { case Annotated_Line: @@ -119,18 +122,43 @@ void main() { element_count += bitCount(bitmaps[i][gl_LocalInvocationID.x]); } // element_count is number of elements covering bin for this invocation. - if (element_count > 0 && chunk_n > 0) { - uint new_chunk = instance_ref.offset; - if (new_chunk + min(32, element_count * 4) > chunk_limit) { - new_chunk = atomicAdd(alloc, BIN_ALLOC); - chunk_limit = new_chunk + BIN_ALLOC - BinInstance_size; - } - BinChunk_write(chunk_ref, BinChunk(chunk_n, BinChunkRef(new_chunk))); - chunk_ref = BinChunkRef(new_chunk); - instance_ref = BinInstanceRef(new_chunk + BinChunk_size); - chunk_n = 0; + if (element_count == 0) { + continue; + } + uint chunk_end; + uint chunk_new_start; + // Refactor to reduce code duplication? + if (chunk_n > 0) { + uint next_chunk = chunk_ref.offset + BinChunk_size + chunk_n * 4; + if (next_chunk + BinChunk_size + min(24, element_count * 4) > wr_limit) { + uint alloc_amount = max(BIN_ALLOC, BinChunk_size + element_count * 4); + if (alloc_amount - BIN_ALLOC < 64) { + alloc_amount = BIN_ALLOC; + } + next_chunk = atomicAdd(alloc, alloc_amount); + wr_limit = next_chunk + alloc_amount; + } + BinChunk_write(chunk_ref, BinChunk(chunk_n, BinChunkRef(next_chunk))); + chunk_ref = BinChunkRef(next_chunk); + } + BinInstanceRef instance_ref = BinInstanceRef(chunk_ref.offset + BinChunk_size); + if (instance_ref.offset + element_count * 4 > wr_limit) { + chunk_end = wr_limit; + chunk_n = (wr_limit - instance_ref.offset) / 4; + uint alloc_amount = max(BIN_ALLOC, BinChunk_size + (element_count - chunk_n) * 4); + if (alloc_amount - BIN_ALLOC < 64) { + alloc_amount = BIN_ALLOC; + } + chunk_new_start = atomicAdd(alloc, alloc_amount); + wr_limit = chunk_new_start + alloc_amount; + BinChunk_write(chunk_ref, BinChunk(chunk_n, BinChunkRef(chunk_new_start))); + chunk_ref = BinChunkRef(chunk_new_start); + chunk_new_start += BinChunk_size; + chunk_n = element_count - chunk_n; + } else { + chunk_end = ~0; + chunk_n = element_count; } - // TODO: allocate output here // Iterate over bits set. uint slice_ix = 0; @@ -149,17 +177,10 @@ void main() { element_ix = my_tile * N_TILE + slice_ix * 32 + findLSB(bitmap); // At this point, element_ix refers to an element that covers this bin. - // TODO: batch allocated based on element_count; this is divergent - if (instance_ref.offset > chunk_limit) { - uint new_chunk = atomicAdd(alloc, BIN_ALLOC); - BinChunk_write(chunk_ref, BinChunk(chunk_n, BinChunkRef(new_chunk))); - chunk_ref = BinChunkRef(new_chunk); - instance_ref = BinInstanceRef(new_chunk + BinChunk_size); - chunk_n = 0; - chunk_limit = new_chunk + BIN_ALLOC - BinInstance_size; + if (instance_ref.offset == chunk_end) { + instance_ref.offset = chunk_new_start; } BinInstance_write(instance_ref, BinInstance(element_ix)); - chunk_n++; instance_ref.offset += BinInstance_size; // clear LSB bitmap &= bitmap - 1; diff --git a/piet-gpu/shader/binning.spv b/piet-gpu/shader/binning.spv index 4cc5d36f5d587a905b5158380672988de7e0d49d..52e04b35f6d4dd38ac7849309a3663f1f7b9e570 100644 GIT binary patch literal 16068 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