kernel4.comp raw
1 // SPDX-License-Identifier: Apache-2.0 OR MIT OR Unlicense
2
3 // This is "kernel 4" in a 4-kernel pipeline. It renders the commands
4 // in the per-tile command list to an image.
5
6 // Right now, this kernel stores the image in a buffer, but a better
7 // plan is to use a texture. This is because of limited support.
8
9 #version 450
10 #extension GL_GOOGLE_include_directive : enable
11 #ifdef ENABLE_IMAGE_INDICES
12 #extension GL_EXT_nonuniform_qualifier : enable
13 #endif
14
15 #include "mem.h"
16 #include "setup.h"
17
18 #define CHUNK_X 2
19 #define CHUNK_Y 4
20 #define CHUNK CHUNK_X * CHUNK_Y
21 #define CHUNK_DX (TILE_WIDTH_PX / CHUNK_X)
22 #define CHUNK_DY (TILE_HEIGHT_PX / CHUNK_Y)
23 layout(local_size_x = CHUNK_DX, local_size_y = CHUNK_DY) in;
24
25 layout(set = 0, binding = 1) restrict readonly buffer ConfigBuf {
26 Config conf;
27 };
28
29 layout(rgba8, set = 0, binding = 2) uniform restrict writeonly image2D image;
30
31 #ifdef ENABLE_IMAGE_INDICES
32 layout(rgba8, set = 0, binding = 3) uniform restrict readonly image2D images[];
33 #else
34 layout(rgba8, set = 0, binding = 3) uniform restrict readonly image2D images[1];
35 #endif
36
37 #include "ptcl.h"
38 #include "tile.h"
39
40 mediump vec3 tosRGB(mediump vec3 rgb) {
41 bvec3 cutoff = greaterThanEqual(rgb, vec3(0.0031308));
42 mediump vec3 below = vec3(12.92)*rgb;
43 mediump vec3 above = vec3(1.055)*pow(rgb, vec3(0.41666)) - vec3(0.055);
44 return mix(below, above, cutoff);
45 }
46
47 mediump vec3 fromsRGB(mediump vec3 srgb) {
48 // Formula from EXT_sRGB.
49 bvec3 cutoff = greaterThanEqual(srgb, vec3(0.04045));
50 mediump vec3 below = srgb/vec3(12.92);
51 mediump vec3 above = pow((srgb + vec3(0.055))/vec3(1.055), vec3(2.4));
52 return mix(below, above, cutoff);
53 }
54
55 // unpacksRGB unpacks a color in the sRGB color space to a vec4 in the linear color
56 // space.
57 mediump vec4 unpacksRGB(uint srgba) {
58 mediump vec4 color = unpackUnorm4x8(srgba).wzyx;
59 return vec4(fromsRGB(color.rgb), color.a);
60 }
61
62 // packsRGB packs a color in the linear color space into its 8-bit sRGB equivalent.
63 uint packsRGB(mediump vec4 rgba) {
64 rgba = vec4(tosRGB(rgba.rgb), rgba.a);
65 return packUnorm4x8(rgba.wzyx);
66 }
67
68 uvec2 chunk_offset(uint i) {
69 return uvec2(i % CHUNK_X * CHUNK_DX, i / CHUNK_X * CHUNK_DY);
70 }
71
72 mediump vec4[CHUNK] fillImage(uvec2 xy, CmdImage cmd_img) {
73 mediump vec4 rgba[CHUNK];
74 for (uint i = 0; i < CHUNK; i++) {
75 ivec2 uv = ivec2(xy + chunk_offset(i)) + cmd_img.offset;
76 mediump vec4 fg_rgba;
77 #ifdef ENABLE_IMAGE_INDICES
78 fg_rgba = imageLoad(images[cmd_img.index], uv);
79 #else
80 fg_rgba = imageLoad(images[0], uv);
81 #endif
82 fg_rgba.rgb = fromsRGB(fg_rgba.rgb);
83 rgba[i] = fg_rgba;
84 }
85 return rgba;
86 }
87
88 void main() {
89 uint tile_ix = gl_WorkGroupID.y * conf.width_in_tiles + gl_WorkGroupID.x;
90 Alloc cmd_alloc = slice_mem(conf.ptcl_alloc, tile_ix * PTCL_INITIAL_ALLOC, PTCL_INITIAL_ALLOC);
91 CmdRef cmd_ref = CmdRef(cmd_alloc.offset);
92
93 // Read scrach space allocation, written first in the command list.
94 Alloc scratch_alloc = alloc_read(cmd_alloc, cmd_ref.offset);
95 cmd_ref.offset += Alloc_size;
96
97 uvec2 xy_uint = uvec2(gl_LocalInvocationID.x + TILE_WIDTH_PX * gl_WorkGroupID.x, gl_LocalInvocationID.y + TILE_HEIGHT_PX * gl_WorkGroupID.y);
98 vec2 xy = vec2(xy_uint);
99 mediump vec4 rgba[CHUNK];
100 for (uint i = 0; i < CHUNK; i++) {
101 rgba[i] = vec4(0.0);
102 // TODO: remove this debug image support when the actual image method is plumbed.
103 #ifdef DEBUG_IMAGES
104 #ifdef ENABLE_IMAGE_INDICES
105 if (xy_uint.x < 1024 && xy_uint.y < 1024) {
106 rgba[i] = imageLoad(images[gl_WorkGroupID.x / 64], ivec2(xy_uint + chunk_offset(i))/4);
107 }
108 #else
109 if (xy_uint.x < 1024 && xy_uint.y < 1024) {
110 rgb[i] = imageLoad(images[0], ivec2(xy_uint + chunk_offset(i))/4).rgb;
111 }
112 #endif
113 #endif
114 }
115
116 mediump float area[CHUNK];
117 uint clip_depth = 0;
118 bool mem_ok = mem_error == NO_ERROR;
119 while (mem_ok) {
120 uint tag = Cmd_tag(cmd_alloc, cmd_ref).tag;
121 if (tag == Cmd_End) {
122 break;
123 }
124 switch (tag) {
125 case Cmd_Stroke:
126 // Calculate distance field from all the line segments in this tile.
127 CmdStroke stroke = Cmd_Stroke_read(cmd_alloc, cmd_ref);
128 mediump float df[CHUNK];
129 for (uint k = 0; k < CHUNK; k++) df[k] = 1e9;
130 TileSegRef tile_seg_ref = TileSegRef(stroke.tile_ref);
131 do {
132 TileSeg seg = TileSeg_read(new_alloc(tile_seg_ref.offset, TileSeg_size, mem_ok), tile_seg_ref);
133 vec2 line_vec = seg.vector;
134 for (uint k = 0; k < CHUNK; k++) {
135 vec2 dpos = xy + vec2(0.5, 0.5) - seg.origin;
136 dpos += vec2(chunk_offset(k));
137 float t = clamp(dot(line_vec, dpos) / dot(line_vec, line_vec), 0.0, 1.0);
138 df[k] = min(df[k], length(line_vec * t - dpos));
139 }
140 tile_seg_ref = seg.next;
141 } while (tile_seg_ref.offset != 0);
142 for (uint k = 0; k < CHUNK; k++) {
143 area[k] = clamp(stroke.half_width + 0.5 - df[k], 0.0, 1.0);
144 }
145 cmd_ref.offset += 4 + CmdStroke_size;
146 break;
147 case Cmd_Fill:
148 CmdFill fill = Cmd_Fill_read(cmd_alloc, cmd_ref);
149 for (uint k = 0; k < CHUNK; k++) area[k] = float(fill.backdrop);
150 tile_seg_ref = TileSegRef(fill.tile_ref);
151 // Calculate coverage based on backdrop + coverage of each line segment
152 do {
153 TileSeg seg = TileSeg_read(new_alloc(tile_seg_ref.offset, TileSeg_size, mem_ok), tile_seg_ref);
154 for (uint k = 0; k < CHUNK; k++) {
155 vec2 my_xy = xy + vec2(chunk_offset(k));
156 vec2 start = seg.origin - my_xy;
157 vec2 end = start + seg.vector;
158 vec2 window = clamp(vec2(start.y, end.y), 0.0, 1.0);
159 if (window.x != window.y) {
160 vec2 t = (window - start.y) / seg.vector.y;
161 vec2 xs = vec2(mix(start.x, end.x, t.x), mix(start.x, end.x, t.y));
162 float xmin = min(min(xs.x, xs.y), 1.0) - 1e-6;
163 float xmax = max(xs.x, xs.y);
164 float b = min(xmax, 1.0);
165 float c = max(b, 0.0);
166 float d = max(xmin, 0.0);
167 float a = (b + 0.5 * (d * d - c * c) - xmin) / (xmax - xmin);
168 area[k] += a * (window.x - window.y);
169 }
170 area[k] += sign(seg.vector.x) * clamp(my_xy.y - seg.y_edge + 1.0, 0.0, 1.0);
171 }
172 tile_seg_ref = seg.next;
173 } while (tile_seg_ref.offset != 0);
174 for (uint k = 0; k < CHUNK; k++) {
175 area[k] = min(abs(area[k]), 1.0);
176 }
177 cmd_ref.offset += 4 + CmdFill_size;
178 break;
179 case Cmd_Solid:
180 for (uint k = 0; k < CHUNK; k++) {
181 area[k] = 1.0;
182 }
183 cmd_ref.offset += 4;
184 break;
185 case Cmd_Alpha:
186 CmdAlpha alpha = Cmd_Alpha_read(cmd_alloc, cmd_ref);
187 for (uint k = 0; k < CHUNK; k++) {
188 area[k] = alpha.alpha;
189 }
190 cmd_ref.offset += 4 + CmdAlpha_size;
191 break;
192 case Cmd_Color:
193 CmdColor color = Cmd_Color_read(cmd_alloc, cmd_ref);
194 mediump vec4 fg = unpacksRGB(color.rgba_color);
195 for (uint k = 0; k < CHUNK; k++) {
196 mediump vec4 fg_k = fg * area[k];
197 rgba[k] = rgba[k] * (1.0 - fg_k.a) + fg_k;
198 }
199 cmd_ref.offset += 4 + CmdColor_size;
200 break;
201 case Cmd_Image:
202 CmdImage fill_img = Cmd_Image_read(cmd_alloc, cmd_ref);
203 mediump vec4 img[CHUNK] = fillImage(xy_uint, fill_img);
204 for (uint k = 0; k < CHUNK; k++) {
205 mediump vec4 fg_k = img[k] * area[k];
206 rgba[k] = rgba[k] * (1.0 - fg_k.a) + fg_k;
207 }
208 cmd_ref.offset += 4 + CmdImage_size;
209 break;
210 case Cmd_BeginClip:
211 uint base_ix = (scratch_alloc.offset >> 2) + CLIP_STATE_SIZE * (clip_depth * TILE_WIDTH_PX * TILE_HEIGHT_PX +
212 gl_LocalInvocationID.x + TILE_WIDTH_PX * gl_LocalInvocationID.y);
213 for (uint k = 0; k < CHUNK; k++) {
214 uvec2 offset = chunk_offset(k);
215 uint srgb = packsRGB(vec4(rgba[k]));
216 mediump float alpha = clamp(abs(area[k]), 0.0, 1.0);
217 write_mem(scratch_alloc, base_ix + 0 + CLIP_STATE_SIZE * (offset.x + offset.y * TILE_WIDTH_PX), srgb);
218 write_mem(scratch_alloc, base_ix + 1 + CLIP_STATE_SIZE * (offset.x + offset.y * TILE_WIDTH_PX), floatBitsToUint(alpha));
219 rgba[k] = vec4(0.0);
220 }
221 clip_depth++;
222 cmd_ref.offset += 4;
223 break;
224 case Cmd_EndClip:
225 clip_depth--;
226 base_ix = (scratch_alloc.offset >> 2) + CLIP_STATE_SIZE * (clip_depth * TILE_WIDTH_PX * TILE_HEIGHT_PX +
227 gl_LocalInvocationID.x + TILE_WIDTH_PX * gl_LocalInvocationID.y);
228 for (uint k = 0; k < CHUNK; k++) {
229 uvec2 offset = chunk_offset(k);
230 uint srgb = read_mem(scratch_alloc, base_ix + 0 + CLIP_STATE_SIZE * (offset.x + offset.y * TILE_WIDTH_PX));
231 uint alpha = read_mem(scratch_alloc, base_ix + 1 + CLIP_STATE_SIZE * (offset.x + offset.y * TILE_WIDTH_PX));
232 mediump vec4 bg = unpacksRGB(srgb);
233 mediump vec4 fg = rgba[k] * area[k] * uintBitsToFloat(alpha);
234 rgba[k] = bg * (1.0 - fg.a) + fg;
235 }
236 cmd_ref.offset += 4;
237 break;
238 case Cmd_Jump:
239 cmd_ref = CmdRef(Cmd_Jump_read(cmd_alloc, cmd_ref).new_ref);
240 cmd_alloc.offset = cmd_ref.offset;
241 break;
242 }
243 }
244
245 for (uint i = 0; i < CHUNK; i++) {
246 imageStore(image, ivec2(xy_uint + chunk_offset(i)), vec4(tosRGB(rgba[i].rgb), rgba[i].a));
247 }
248 }
249