2020-04-22 12:30:14 +10:00
|
|
|
// This is "kernel 4" in a 4-kernel pipeline. It renders the commands
|
|
|
|
// in the per-tile command list to an image.
|
|
|
|
|
|
|
|
// Right now, this kernel stores the image in a buffer, but a better
|
|
|
|
// plan is to use a texture. This is because of limited support.
|
|
|
|
|
|
|
|
#version 450
|
|
|
|
#extension GL_GOOGLE_include_directive : enable
|
2020-11-19 10:54:11 +11:00
|
|
|
#extension GL_EXT_nonuniform_qualifier : enable
|
2020-04-22 12:30:14 +10:00
|
|
|
|
2020-06-15 07:32:59 +10:00
|
|
|
#include "setup.h"
|
|
|
|
|
2020-05-26 08:45:06 +10:00
|
|
|
#define CHUNK 8
|
2020-06-15 07:32:59 +10:00
|
|
|
#define CHUNK_DY (TILE_HEIGHT_PX / CHUNK)
|
|
|
|
layout(local_size_x = TILE_WIDTH_PX, local_size_y = CHUNK_DY) in;
|
2020-04-22 12:30:14 +10:00
|
|
|
|
2020-05-26 08:45:06 +10:00
|
|
|
// Same concern that this should be readonly as in kernel 3.
|
2020-04-22 12:30:14 +10:00
|
|
|
layout(set = 0, binding = 0) buffer PtclBuf {
|
|
|
|
uint[] ptcl;
|
|
|
|
};
|
|
|
|
|
2020-06-04 02:28:43 +10:00
|
|
|
layout(set = 0, binding = 1) buffer TileBuf {
|
|
|
|
uint[] tile;
|
|
|
|
};
|
|
|
|
|
2020-11-22 06:39:23 +11:00
|
|
|
layout(set = 0, binding = 2) buffer ClipScratchBuf {
|
|
|
|
uint[] clip_scratch;
|
|
|
|
};
|
|
|
|
|
|
|
|
layout(rgba8, set = 0, binding = 3) uniform writeonly image2D image;
|
2020-04-22 12:30:14 +10:00
|
|
|
|
2020-11-26 07:43:42 +11:00
|
|
|
layout(set = 0, binding = 4) uniform sampler2D textures[];
|
2020-11-19 10:54:11 +11:00
|
|
|
|
2020-04-22 12:30:14 +10:00
|
|
|
#include "ptcl.h"
|
2020-06-04 02:28:43 +10:00
|
|
|
#include "tile.h"
|
2020-04-22 12:30:14 +10:00
|
|
|
|
2020-11-20 06:53:59 +11:00
|
|
|
#define BLEND_STACK_SIZE 4
|
|
|
|
|
2020-11-22 06:39:23 +11:00
|
|
|
// Layout of clip_scratch buffer:
|
|
|
|
// [0] is the alloc bump offset (in units of 32 bit words, initially 0)
|
|
|
|
// Starting at 1 is a sequence of frames.
|
|
|
|
// Each frame is WIDTH * HEIGHT 32-bit words, then a link reference.
|
|
|
|
|
|
|
|
#define CLIP_LINK_OFFSET (TILE_WIDTH_PX * TILE_HEIGHT_PX)
|
|
|
|
#define CLIP_BUF_SIZE (CLIP_LINK_OFFSET + 1)
|
|
|
|
|
|
|
|
shared uint sh_clip_alloc;
|
|
|
|
|
|
|
|
// Allocate a scratch buffer for clipping. Unlike offsets in the rest of the code,
|
|
|
|
// it counts 32-bit words.
|
|
|
|
uint alloc_clip_buf(uint link) {
|
|
|
|
if (gl_LocalInvocationID.x == 0 && gl_LocalInvocationID.y == 0) {
|
|
|
|
uint alloc = atomicAdd(clip_scratch[0], CLIP_BUF_SIZE) + 1;
|
|
|
|
sh_clip_alloc = alloc;
|
|
|
|
clip_scratch[alloc + CLIP_LINK_OFFSET] = link;
|
|
|
|
}
|
|
|
|
barrier();
|
|
|
|
return sh_clip_alloc;
|
|
|
|
}
|
|
|
|
|
2020-10-09 21:43:29 +11:00
|
|
|
// Calculate coverage based on backdrop + coverage of each line segment
|
|
|
|
float[CHUNK] computeArea(vec2 xy, int backdrop, uint tile_ref) {
|
|
|
|
// Probably better to store as float, but conversion is no doubt cheap.
|
|
|
|
float area[CHUNK];
|
|
|
|
for (uint k = 0; k < CHUNK; k++) area[k] = float(backdrop);
|
|
|
|
TileSegRef tile_seg_ref = TileSegRef(tile_ref);
|
|
|
|
do {
|
|
|
|
TileSeg seg = TileSeg_read(tile_seg_ref);
|
|
|
|
for (uint k = 0; k < CHUNK; k++) {
|
|
|
|
vec2 my_xy = vec2(xy.x, xy.y + float(k * CHUNK_DY));
|
2020-12-02 04:06:09 +11:00
|
|
|
vec2 start = seg.origin - my_xy;
|
|
|
|
vec2 end = start + seg.vector;
|
2020-10-09 21:43:29 +11:00
|
|
|
vec2 window = clamp(vec2(start.y, end.y), 0.0, 1.0);
|
|
|
|
if (window.x != window.y) {
|
|
|
|
vec2 t = (window - start.y) / (end.y - start.y);
|
|
|
|
vec2 xs = vec2(mix(start.x, end.x, t.x), mix(start.x, end.x, t.y));
|
|
|
|
float xmin = min(min(xs.x, xs.y), 1.0) - 1e-6;
|
|
|
|
float xmax = max(xs.x, xs.y);
|
|
|
|
float b = min(xmax, 1.0);
|
|
|
|
float c = max(b, 0.0);
|
|
|
|
float d = max(xmin, 0.0);
|
|
|
|
float a = (b + 0.5 * (d * d - c * c) - xmin) / (xmax - xmin);
|
|
|
|
area[k] += a * (window.x - window.y);
|
|
|
|
}
|
2020-12-02 04:06:09 +11:00
|
|
|
area[k] += sign(seg.vector.x) * clamp(my_xy.y - seg.y_edge + 1.0, 0.0, 1.0);
|
2020-10-09 21:43:29 +11:00
|
|
|
}
|
|
|
|
tile_seg_ref = seg.next;
|
|
|
|
} while (tile_seg_ref.offset != 0);
|
|
|
|
for (uint k = 0; k < CHUNK; k++) {
|
|
|
|
area[k] = min(abs(area[k]), 1.0);
|
|
|
|
}
|
|
|
|
return area;
|
|
|
|
}
|
|
|
|
|
2020-04-22 12:30:14 +10:00
|
|
|
void main() {
|
|
|
|
uint tile_ix = gl_WorkGroupID.y * WIDTH_IN_TILES + gl_WorkGroupID.x;
|
|
|
|
CmdRef cmd_ref = CmdRef(tile_ix * PTCL_INITIAL_ALLOC);
|
|
|
|
|
2020-05-26 08:45:06 +10:00
|
|
|
uvec2 xy_uint = uvec2(gl_GlobalInvocationID.x, gl_LocalInvocationID.y + TILE_HEIGHT_PX * gl_WorkGroupID.y);
|
2020-04-22 12:30:14 +10:00
|
|
|
vec2 xy = vec2(xy_uint);
|
2020-05-26 08:45:06 +10:00
|
|
|
vec3 rgb[CHUNK];
|
2020-10-09 21:43:29 +11:00
|
|
|
float mask[CHUNK];
|
2020-11-20 06:53:59 +11:00
|
|
|
uint blend_stack[BLEND_STACK_SIZE][CHUNK];
|
2020-11-22 06:39:23 +11:00
|
|
|
uint blend_spill = 0;
|
2020-11-20 06:53:59 +11:00
|
|
|
uint blend_sp = 0;
|
2020-11-22 06:39:23 +11:00
|
|
|
uint clip_tos = 0;
|
2020-05-26 08:45:06 +10:00
|
|
|
for (uint i = 0; i < CHUNK; i++) {
|
|
|
|
rgb[i] = vec3(0.5);
|
2020-11-26 07:43:42 +11:00
|
|
|
if (xy_uint.x < 1024 && xy_uint.y < 1024) {
|
|
|
|
rgb[i] = texture(textures[gl_WorkGroupID.x / 64], vec2(xy_uint.x, xy_uint.y + CHUNK_DY * i) / 1024.0).rgb;
|
2020-11-19 10:54:11 +11:00
|
|
|
}
|
2020-10-09 21:43:29 +11:00
|
|
|
mask[i] = 1.0;
|
2020-05-26 08:45:06 +10:00
|
|
|
}
|
2020-04-22 12:30:14 +10:00
|
|
|
|
|
|
|
while (true) {
|
|
|
|
uint tag = Cmd_tag(cmd_ref);
|
|
|
|
if (tag == Cmd_End) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
switch (tag) {
|
|
|
|
case Cmd_Circle:
|
|
|
|
CmdCircle circle = Cmd_Circle_read(cmd_ref);
|
2020-04-29 04:02:19 +10:00
|
|
|
vec4 fg_rgba = unpackUnorm4x8(circle.rgba_color).wzyx;
|
2020-05-26 08:45:06 +10:00
|
|
|
for (uint i = 0; i < CHUNK; i++) {
|
|
|
|
float dy = float(i * CHUNK_DY);
|
|
|
|
float r = length(vec2(xy.x, xy.y + dy) + vec2(0.5, 0.5) - circle.center.xy);
|
|
|
|
float alpha = clamp(0.5 + circle.radius - r, 0.0, 1.0);
|
|
|
|
// TODO: sRGB
|
2020-10-09 21:43:29 +11:00
|
|
|
rgb[i] = mix(rgb[i], fg_rgba.rgb, mask[i] * alpha * fg_rgba.a);
|
2020-05-26 08:45:06 +10:00
|
|
|
}
|
2020-04-26 03:15:22 +10:00
|
|
|
break;
|
2020-04-29 04:02:19 +10:00
|
|
|
case Cmd_Stroke:
|
2020-06-28 23:37:27 +10:00
|
|
|
// Calculate distance field from all the line segments in this tile.
|
2020-04-29 04:02:19 +10:00
|
|
|
CmdStroke stroke = Cmd_Stroke_read(cmd_ref);
|
2020-05-26 08:45:06 +10:00
|
|
|
float df[CHUNK];
|
|
|
|
for (uint k = 0; k < CHUNK; k++) df[k] = 1e9;
|
2020-06-04 02:28:43 +10:00
|
|
|
TileSegRef tile_seg_ref = TileSegRef(stroke.tile_ref);
|
2020-04-29 15:25:57 +10:00
|
|
|
do {
|
2020-06-04 02:28:43 +10:00
|
|
|
TileSeg seg = TileSeg_read(tile_seg_ref);
|
2020-12-02 04:06:09 +11:00
|
|
|
vec2 line_vec = seg.vector;
|
2020-06-04 02:28:43 +10:00
|
|
|
for (uint k = 0; k < CHUNK; k++) {
|
2020-12-02 04:06:09 +11:00
|
|
|
vec2 dpos = xy + vec2(0.5, 0.5) - seg.origin;
|
2020-06-04 02:28:43 +10:00
|
|
|
dpos.y += float(k * CHUNK_DY);
|
|
|
|
float t = clamp(dot(line_vec, dpos) / dot(line_vec, line_vec), 0.0, 1.0);
|
|
|
|
df[k] = min(df[k], length(line_vec * t - dpos));
|
2020-05-06 02:13:07 +10:00
|
|
|
}
|
2020-06-04 02:28:43 +10:00
|
|
|
tile_seg_ref = seg.next;
|
|
|
|
} while (tile_seg_ref.offset != 0);
|
2020-04-29 04:02:19 +10:00
|
|
|
fg_rgba = unpackUnorm4x8(stroke.rgba_color).wzyx;
|
2020-05-26 08:45:06 +10:00
|
|
|
for (uint k = 0; k < CHUNK; k++) {
|
|
|
|
float alpha = clamp(stroke.half_width + 0.5 - df[k], 0.0, 1.0);
|
2020-10-09 21:43:29 +11:00
|
|
|
rgb[k] = mix(rgb[k], fg_rgba.rgb, mask[k] * alpha * fg_rgba.a);
|
2020-05-26 08:45:06 +10:00
|
|
|
}
|
2020-04-29 04:02:19 +10:00
|
|
|
break;
|
2020-05-01 10:06:01 +10:00
|
|
|
case Cmd_Fill:
|
|
|
|
CmdFill fill = Cmd_Fill_read(cmd_ref);
|
2020-05-26 08:45:06 +10:00
|
|
|
float area[CHUNK];
|
2020-10-09 21:43:29 +11:00
|
|
|
area = computeArea(xy, fill.backdrop, fill.tile_ref);
|
2020-05-01 10:06:01 +10:00
|
|
|
fg_rgba = unpackUnorm4x8(fill.rgba_color).wzyx;
|
2020-05-26 08:45:06 +10:00
|
|
|
for (uint k = 0; k < CHUNK; k++) {
|
2020-10-09 21:43:29 +11:00
|
|
|
rgb[k] = mix(rgb[k], fg_rgba.rgb, mask[k] * area[k] * fg_rgba.a);
|
|
|
|
}
|
|
|
|
break;
|
2020-11-20 06:53:59 +11:00
|
|
|
case Cmd_BeginClip:
|
2020-11-21 04:26:02 +11:00
|
|
|
case Cmd_BeginSolidClip:
|
2020-11-22 06:39:23 +11:00
|
|
|
uint blend_slot = blend_sp % BLEND_STACK_SIZE;
|
|
|
|
if (blend_sp == blend_spill + BLEND_STACK_SIZE) {
|
|
|
|
// spill to scratch buffer
|
|
|
|
clip_tos = alloc_clip_buf(clip_tos);
|
|
|
|
uint base_ix = clip_tos + gl_LocalInvocationID.x + TILE_WIDTH_PX * gl_LocalInvocationID.y;
|
|
|
|
for (uint k = 0; k < CHUNK; k++) {
|
|
|
|
clip_scratch[base_ix + k * TILE_WIDTH_PX * CHUNK_DY] = blend_stack[blend_slot][k];
|
|
|
|
}
|
|
|
|
blend_spill++;
|
|
|
|
}
|
|
|
|
if (tag == Cmd_BeginClip) {
|
|
|
|
CmdBeginClip begin_clip = Cmd_BeginClip_read(cmd_ref);
|
|
|
|
area = computeArea(xy, begin_clip.backdrop, begin_clip.tile_ref);
|
|
|
|
for (uint k = 0; k < CHUNK; k++) {
|
|
|
|
blend_stack[blend_slot][k] = packUnorm4x8(vec4(rgb[k], clamp(abs(area[k]), 0.0, 1.0)));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
CmdBeginSolidClip begin_solid_clip = Cmd_BeginSolidClip_read(cmd_ref);
|
|
|
|
float solid_alpha = begin_solid_clip.alpha;
|
|
|
|
for (uint k = 0; k < CHUNK; k++) {
|
|
|
|
blend_stack[blend_slot][k] = packUnorm4x8(vec4(rgb[k], solid_alpha));
|
|
|
|
}
|
2020-11-21 04:26:02 +11:00
|
|
|
}
|
|
|
|
blend_sp++;
|
|
|
|
break;
|
2020-11-20 06:53:59 +11:00
|
|
|
case Cmd_EndClip:
|
|
|
|
CmdEndClip end_clip = Cmd_EndClip_read(cmd_ref);
|
2020-11-22 06:39:23 +11:00
|
|
|
blend_slot = (blend_sp - 1) % BLEND_STACK_SIZE;
|
|
|
|
if (blend_sp == blend_spill) {
|
|
|
|
uint base_ix = clip_tos + gl_LocalInvocationID.x + TILE_WIDTH_PX * gl_LocalInvocationID.y;
|
|
|
|
for (uint k = 0; k < CHUNK; k++) {
|
|
|
|
blend_stack[blend_slot][k] = clip_scratch[base_ix + k * TILE_WIDTH_PX * CHUNK_DY];
|
|
|
|
}
|
|
|
|
clip_tos = clip_scratch[clip_tos + CLIP_LINK_OFFSET];
|
|
|
|
blend_spill--;
|
|
|
|
}
|
2020-11-20 06:53:59 +11:00
|
|
|
blend_sp--;
|
|
|
|
for (uint k = 0; k < CHUNK; k++) {
|
2020-11-22 06:39:23 +11:00
|
|
|
vec4 rgba = unpackUnorm4x8(blend_stack[blend_slot][k]);
|
2020-11-21 04:26:02 +11:00
|
|
|
rgb[k] = mix(rgba.rgb, rgb[k], end_clip.alpha * rgba.a);
|
2020-11-20 06:53:59 +11:00
|
|
|
}
|
|
|
|
break;
|
2020-05-03 02:50:36 +10:00
|
|
|
case Cmd_Solid:
|
|
|
|
CmdSolid solid = Cmd_Solid_read(cmd_ref);
|
|
|
|
fg_rgba = unpackUnorm4x8(solid.rgba_color).wzyx;
|
2020-05-26 08:45:06 +10:00
|
|
|
for (uint k = 0; k < CHUNK; k++) {
|
2020-10-09 21:43:29 +11:00
|
|
|
rgb[k] = mix(rgb[k], fg_rgba.rgb, mask[k] * fg_rgba.a);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case Cmd_SolidMask:
|
|
|
|
CmdSolidMask solid_mask = Cmd_SolidMask_read(cmd_ref);
|
|
|
|
for (uint k = 0; k < CHUNK; k++) {
|
|
|
|
mask[k] = solid_mask.mask;
|
2020-05-26 08:45:06 +10:00
|
|
|
}
|
2020-05-03 02:50:36 +10:00
|
|
|
break;
|
2020-04-26 03:15:22 +10:00
|
|
|
case Cmd_Jump:
|
|
|
|
cmd_ref = CmdRef(Cmd_Jump_read(cmd_ref).new_ref);
|
|
|
|
continue;
|
2020-04-22 12:30:14 +10:00
|
|
|
}
|
|
|
|
cmd_ref.offset += Cmd_size;
|
|
|
|
}
|
|
|
|
|
2020-05-26 08:45:06 +10:00
|
|
|
// TODO: sRGB
|
|
|
|
for (uint i = 0; i < CHUNK; i++) {
|
|
|
|
imageStore(image, ivec2(xy_uint.x, xy_uint.y + CHUNK_DY * i), vec4(rgb[i], 1.0));
|
|
|
|
}
|
2020-04-22 12:30:14 +10:00
|
|
|
}
|