blob: 5353b0b69610df789c15a5ab4390a2e35d9afdcb [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0 OR MIT OR Unlicense
// The second dispatch of clip stack processing.
#version 450
#extension GL_GOOGLE_include_directive : enable
#include "mem.h"
#include "setup.h"
#define LG_WG_SIZE (7 + LG_WG_FACTOR)
#define WG_SIZE (1 << LG_WG_SIZE)
#define PARTITION_SIZE WG_SIZE
layout(local_size_x = WG_SIZE) in;
layout(binding = 1) readonly buffer ConfigBuf {
Config conf;
};
// Some of this is cut'n'paste duplication with the reduce pass, and
// arguably should be moved to a common .h file.
// The bicyclic monoid
struct ClipEl {
// index of parent node
uint parent_ix;
// bounding box
vec4 bbox;
};
struct Bic {
uint a;
uint b;
};
Bic bic_combine(Bic x, Bic y) {
uint m = min(x.b, y.a);
return Bic(x.a + y.a - m, x.b + y.b - m);
}
// Load path's bbox from bbox (as written by pathseg).
vec4 load_path_bbox(uint path_ix) {
uint base = (conf.path_bbox_alloc.offset >> 2) + 6 * path_ix;
float bbox_l = float(memory[base]) - 32768.0;
float bbox_t = float(memory[base + 1]) - 32768.0;
float bbox_r = float(memory[base + 2]) - 32768.0;
float bbox_b = float(memory[base + 3]) - 32768.0;
vec4 bbox = vec4(bbox_l, bbox_t, bbox_r, bbox_b);
return bbox;
}
vec4 bbox_intersect(vec4 a, vec4 b) {
return vec4(max(a.xy, b.xy), min(a.zw, b.zw));
}
shared Bic sh_bic[WG_SIZE * 2 - 2];
shared uint sh_stack[PARTITION_SIZE];
shared vec4 sh_stack_bbox[PARTITION_SIZE];
shared uint sh_link[PARTITION_SIZE];
shared vec4 sh_bbox[PARTITION_SIZE];
// This is adapted directly from the stack monoid impl.
// Return value is reference within partition if >= 0,
// otherwise reference to stack.
uint search_link(inout Bic bic) {
uint ix = gl_LocalInvocationID.x;
uint j = 0;
while (j < LG_WG_SIZE) {
uint base = 2 * WG_SIZE - (2u << (LG_WG_SIZE - j));
if (((ix >> j) & 1) != 0) {
Bic test = bic_combine(sh_bic[base + (ix >> j) - 1], bic);
if (test.b > 0) {
break;
}
bic = test;
ix -= 1u << j;
}
j++;
}
if (ix > 0) {
while (j > 0) {
j--;
uint base = 2 * WG_SIZE - (2u << (LG_WG_SIZE - j));
Bic test = bic_combine(sh_bic[base + (ix >> j) - 1], bic);
if (test.b == 0) {
bic = test;
ix -= 1u << j;
}
}
}
// ix is the smallest value such that reduce(ix..th).b == 0
if (ix > 0) {
return ix - 1;
} else {
return ~0u - bic.a;
}
}
Bic load_bic(uint ix) {
uint base = (conf.clip_bic_alloc.offset >> 2) + 2 * ix;
return Bic(memory[base], memory[base + 1]);
}
ClipEl load_clip_el(uint ix) {
uint base = (conf.clip_stack_alloc.offset >> 2) + 5 * ix;
uint parent_ix = memory[base];
float x0 = uintBitsToFloat(memory[base + 1]);
float y0 = uintBitsToFloat(memory[base + 2]);
float x1 = uintBitsToFloat(memory[base + 3]);
float y1 = uintBitsToFloat(memory[base + 4]);
vec4 bbox = vec4(x0, y0, x1, y1);
return ClipEl(parent_ix, bbox);
}
uint load_path_ix(uint ix) {
// This is one approach to a partial final block. Another would be
// to do a memset to the padding in the command queue.
if (ix < conf.n_clip) {
return memory[(conf.clip_alloc.offset >> 2) + ix];
} else {
// EndClip tags don't implicate further loads.
return 0x80000000;
}
}
void store_clip_bbox(uint ix, vec4 bbox) {
uint base = (conf.clip_bbox_alloc.offset >> 2) + 4 * ix;
memory[base] = floatBitsToUint(bbox.x);
memory[base + 1] = floatBitsToUint(bbox.y);
memory[base + 2] = floatBitsToUint(bbox.z);
memory[base + 3] = floatBitsToUint(bbox.w);
}
void main() {
// materialize stack up to the start of this partition. This
// is based on the pure stack monoid, but with two additions.
// First, (this only matters if the stack goes deeper than the
// partition size, which might be unlikely in practice), the
// topmost stack element from each partition is picked, then an
// exclusive scan of those. Also note that if this is skipped,
// a scan is not needed in the reduce stage.
// Second, after the stream compaction, do a scan of the retrieved
// bbox values.
uint th = gl_LocalInvocationID.x;
Bic bic = Bic(0, 0);
if (th < gl_WorkGroupID.x) {
bic = load_bic(th);
}
sh_bic[th] = bic;
for (uint i = 0; i < LG_WG_SIZE; i++) {
barrier();
if (th + (1u << i) < WG_SIZE) {
Bic other = sh_bic[th + (1u << i)];
bic = bic_combine(bic, other);
}
barrier();
sh_bic[th] = bic;
}
barrier();
uint stack_size = sh_bic[0].b;
// TODO: do bbox scan here (to unlock greater stack depth)
// binary search in stack
uint sp = PARTITION_SIZE - 1 - th;
uint ix = 0;
for (uint i = 0; i < LG_WG_SIZE; i++) {
uint probe = ix + (uint(PARTITION_SIZE / 2) >> i);
if (sp < sh_bic[probe].b) {
ix = probe;
}
}
// ix is largest value such that sp < sh_bic[ix].b (if any)
uint b = sh_bic[ix].b;
vec4 bbox = vec4(-1e9, -1e9, 1e9, 1e9);
if (sp < b) {
// maybe store the index here for future use?
ClipEl el = load_clip_el(ix * PARTITION_SIZE + b - sp - 1);
sh_stack[th] = el.parent_ix;
bbox = el.bbox;
// other element values here?
}
// forward scan of bbox values of prefix stack
for (uint i = 0; i < LG_WG_SIZE; i++) {
sh_stack_bbox[th] = bbox;
barrier();
if (th >= (1u << i)) {
bbox = bbox_intersect(sh_stack_bbox[th - (1u << i)], bbox);
}
barrier();
}
sh_stack_bbox[th] = bbox;
// Read input and compute bicyclic semigroup binary tree
uint inp = load_path_ix(gl_GlobalInvocationID.x);
bool is_push = int(inp) >= 0;
bic = Bic(1 - uint(is_push), uint(is_push));
sh_bic[th] = bic;
if (is_push) {
bbox = load_path_bbox(inp);
} else {
bbox = vec4(-1e9, -1e9, 1e9, 1e9);
}
uint inbase = 0;
for (uint i = 0; i < LG_WG_SIZE - 1; i++) {
uint outbase = 2 * WG_SIZE - (1u << (LG_WG_SIZE - i));
barrier();
if (th < (1u << (LG_WG_SIZE - 1 - i))) {
sh_bic[outbase + th] = bic_combine(sh_bic[inbase + th * 2], sh_bic[inbase + th * 2 + 1]);
}
inbase = outbase;
}
barrier();
// Search for predecessor node
bic = Bic(0, 0);
uint link = search_link(bic);
// we use N_SEQ > 1 convention here:
// link >= 0 is index within partition
// link < 0 is reference to stack
// We want grandparent bbox for pop nodes, so follow those links.
sh_link[th] = link;
barrier();
uint grandparent;
if (int(link) >= 0) {
grandparent = sh_link[link];
} else {
grandparent = link - 1;
}
// Resolve parent
uint parent;
if (int(link) >= 0) {
parent = gl_WorkGroupID.x * PARTITION_SIZE + link;
} else if (int(link + stack_size) >= 0) {
parent = sh_stack[PARTITION_SIZE + link];
} else {
parent = ~0u;
}
// bbox scan along parent links
for (uint i = 0; i < LG_WG_SIZE; i++) {
// sh_link was already stored for first iteration
if (i != 0) {
sh_link[th] = link;
}
sh_bbox[th] = bbox;
barrier();
if (int(link) >= 0) {
bbox = bbox_intersect(sh_bbox[link], bbox);
link = sh_link[link];
}
barrier();
}
if (int(link + stack_size) >= 0) {
bbox = bbox_intersect(sh_stack_bbox[PARTITION_SIZE + link], bbox);
}
// At this point, bbox is the reduction of bounding boxes along the tree.
sh_bbox[th] = bbox;
barrier();
uint path_ix = inp;
if (!is_push && gl_GlobalInvocationID.x < conf.n_clip) {
// Is this load expensive? If so, it's loaded earlier for in-partition
// and is in the ClipEl for cross-partition.
// If not, can probably get rid of it in the stack intermediate buf.
path_ix = load_path_ix(parent);
uint drawmonoid_out_base = (conf.drawmonoid_alloc.offset >> 2) + 4 * ~inp;
// Fix up drawmonoid so path_ix at EndClip matches BeginClip
memory[drawmonoid_out_base] = path_ix;
if (int(grandparent) >= 0) {
bbox = sh_bbox[grandparent];
} else if (int(grandparent + stack_size) >= 0) {
bbox = sh_stack_bbox[PARTITION_SIZE + grandparent];
} else {
bbox = vec4(-1e9, -1e9, 1e9, 1e9);
}
}
store_clip_bbox(gl_GlobalInvocationID.x, bbox);
}