mirror of
https://github.com/italicsjenga/vello.git
synced 2025-01-11 04:51:32 +11:00
d3b08e4c52
* Add blend and composition mode enums to API * Mirror these in the shaders * Add new public blend function to PietGpuRenderContext that mirrors clip * Plumb the modes through the pipeline from scene to kernel4
252 lines
10 KiB
GLSL
252 lines
10 KiB
GLSL
// SPDX-License-Identifier: Apache-2.0 OR MIT OR Unlicense
|
|
|
|
// 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
|
|
|
|
#include "mem.h"
|
|
#include "setup.h"
|
|
|
|
#define CHUNK_X 2
|
|
#define CHUNK_Y 4
|
|
#define CHUNK (CHUNK_X * CHUNK_Y)
|
|
#define CHUNK_DX (TILE_WIDTH_PX / CHUNK_X)
|
|
#define CHUNK_DY (TILE_HEIGHT_PX / CHUNK_Y)
|
|
layout(local_size_x = CHUNK_DX, local_size_y = CHUNK_DY) in;
|
|
|
|
layout(set = 0, binding = 1) restrict readonly buffer ConfigBuf {
|
|
Config conf;
|
|
};
|
|
|
|
#ifdef GRAY
|
|
layout(r8, set = 0, binding = 2) uniform restrict writeonly image2D image;
|
|
#else
|
|
layout(rgba8, set = 0, binding = 2) uniform restrict writeonly image2D image;
|
|
#endif
|
|
|
|
layout(rgba8, set = 0, binding = 3) uniform restrict readonly image2D image_atlas;
|
|
|
|
layout(rgba8, set = 0, binding = 4) uniform restrict readonly image2D gradients;
|
|
|
|
#include "ptcl.h"
|
|
#include "tile.h"
|
|
#include "blend.h"
|
|
|
|
#define MAX_BLEND_STACK 128
|
|
mediump vec3 tosRGB(mediump vec3 rgb) {
|
|
bvec3 cutoff = greaterThanEqual(rgb, vec3(0.0031308));
|
|
mediump vec3 below = vec3(12.92) * rgb;
|
|
mediump vec3 above = vec3(1.055) * pow(rgb, vec3(0.41666)) - vec3(0.055);
|
|
return mix(below, above, cutoff);
|
|
}
|
|
|
|
mediump vec3 fromsRGB(mediump vec3 srgb) {
|
|
// Formula from EXT_sRGB.
|
|
bvec3 cutoff = greaterThanEqual(srgb, vec3(0.04045));
|
|
mediump vec3 below = srgb / vec3(12.92);
|
|
mediump vec3 above = pow((srgb + vec3(0.055)) / vec3(1.055), vec3(2.4));
|
|
return mix(below, above, cutoff);
|
|
}
|
|
|
|
// unpacksRGB unpacks a color in the sRGB color space to a vec4 in the linear color
|
|
// space.
|
|
mediump vec4 unpacksRGB(uint srgba) {
|
|
mediump vec4 color = unpackUnorm4x8(srgba).wzyx;
|
|
return vec4(fromsRGB(color.rgb), color.a);
|
|
}
|
|
|
|
// packsRGB packs a color in the linear color space into its 8-bit sRGB equivalent.
|
|
uint packsRGB(mediump vec4 rgba) {
|
|
rgba = vec4(tosRGB(rgba.rgb), rgba.a);
|
|
return packUnorm4x8(rgba.wzyx);
|
|
}
|
|
|
|
uvec2 chunk_offset(uint i) {
|
|
return uvec2(i % CHUNK_X * CHUNK_DX, i / CHUNK_X * CHUNK_DY);
|
|
}
|
|
|
|
mediump vec4[CHUNK] fillImage(uvec2 xy, CmdImage cmd_img) {
|
|
mediump vec4 rgba[CHUNK];
|
|
for (uint i = 0; i < CHUNK; i++) {
|
|
ivec2 uv = ivec2(xy + chunk_offset(i)) + cmd_img.offset;
|
|
mediump vec4 fg_rgba;
|
|
fg_rgba = imageLoad(image_atlas, uv);
|
|
fg_rgba.rgb = fromsRGB(fg_rgba.rgb);
|
|
rgba[i] = fg_rgba;
|
|
}
|
|
return rgba;
|
|
}
|
|
|
|
void main() {
|
|
uint tile_ix = gl_WorkGroupID.y * conf.width_in_tiles + gl_WorkGroupID.x;
|
|
Alloc cmd_alloc = slice_mem(conf.ptcl_alloc, tile_ix * PTCL_INITIAL_ALLOC, PTCL_INITIAL_ALLOC);
|
|
CmdRef cmd_ref = CmdRef(cmd_alloc.offset);
|
|
|
|
uvec2 xy_uint = uvec2(gl_LocalInvocationID.x + TILE_WIDTH_PX * gl_WorkGroupID.x,
|
|
gl_LocalInvocationID.y + TILE_HEIGHT_PX * gl_WorkGroupID.y);
|
|
vec2 xy = vec2(xy_uint);
|
|
mediump vec4 rgba[CHUNK];
|
|
uint blend_stack[MAX_BLEND_STACK][CHUNK];
|
|
for (uint i = 0; i < CHUNK; i++) {
|
|
rgba[i] = vec4(0.0);
|
|
}
|
|
|
|
mediump float area[CHUNK];
|
|
uint clip_depth = 0;
|
|
bool mem_ok = mem_error == NO_ERROR;
|
|
while (mem_ok) {
|
|
uint tag = Cmd_tag(cmd_alloc, cmd_ref).tag;
|
|
if (tag == Cmd_End) {
|
|
break;
|
|
}
|
|
switch (tag) {
|
|
case Cmd_Stroke:
|
|
// Calculate distance field from all the line segments in this tile.
|
|
CmdStroke stroke = Cmd_Stroke_read(cmd_alloc, cmd_ref);
|
|
mediump float df[CHUNK];
|
|
for (uint k = 0; k < CHUNK; k++)
|
|
df[k] = 1e9;
|
|
TileSegRef tile_seg_ref = TileSegRef(stroke.tile_ref);
|
|
do {
|
|
TileSeg seg = TileSeg_read(new_alloc(tile_seg_ref.offset, TileSeg_size, mem_ok), tile_seg_ref);
|
|
vec2 line_vec = seg.vector;
|
|
for (uint k = 0; k < CHUNK; k++) {
|
|
vec2 dpos = xy + vec2(0.5, 0.5) - seg.origin;
|
|
dpos += vec2(chunk_offset(k));
|
|
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));
|
|
}
|
|
tile_seg_ref = seg.next;
|
|
} while (tile_seg_ref.offset != 0);
|
|
for (uint k = 0; k < CHUNK; k++) {
|
|
area[k] = clamp(stroke.half_width + 0.5 - df[k], 0.0, 1.0);
|
|
}
|
|
cmd_ref.offset += 4 + CmdStroke_size;
|
|
break;
|
|
case Cmd_Fill:
|
|
CmdFill fill = Cmd_Fill_read(cmd_alloc, cmd_ref);
|
|
for (uint k = 0; k < CHUNK; k++)
|
|
area[k] = float(fill.backdrop);
|
|
tile_seg_ref = TileSegRef(fill.tile_ref);
|
|
// Calculate coverage based on backdrop + coverage of each line segment
|
|
do {
|
|
TileSeg seg = TileSeg_read(new_alloc(tile_seg_ref.offset, TileSeg_size, mem_ok), tile_seg_ref);
|
|
for (uint k = 0; k < CHUNK; k++) {
|
|
vec2 my_xy = xy + vec2(chunk_offset(k));
|
|
vec2 start = seg.origin - my_xy;
|
|
vec2 end = start + seg.vector;
|
|
vec2 window = clamp(vec2(start.y, end.y), 0.0, 1.0);
|
|
if (window.x != window.y) {
|
|
vec2 t = (window - start.y) / seg.vector.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);
|
|
}
|
|
area[k] += sign(seg.vector.x) * clamp(my_xy.y - seg.y_edge + 1.0, 0.0, 1.0);
|
|
}
|
|
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);
|
|
}
|
|
cmd_ref.offset += 4 + CmdFill_size;
|
|
break;
|
|
case Cmd_Solid:
|
|
for (uint k = 0; k < CHUNK; k++) {
|
|
area[k] = 1.0;
|
|
}
|
|
cmd_ref.offset += 4;
|
|
break;
|
|
case Cmd_Alpha:
|
|
CmdAlpha alpha = Cmd_Alpha_read(cmd_alloc, cmd_ref);
|
|
for (uint k = 0; k < CHUNK; k++) {
|
|
area[k] = alpha.alpha;
|
|
}
|
|
cmd_ref.offset += 4 + CmdAlpha_size;
|
|
break;
|
|
case Cmd_Color:
|
|
CmdColor color = Cmd_Color_read(cmd_alloc, cmd_ref);
|
|
mediump vec4 fg = unpacksRGB(color.rgba_color);
|
|
for (uint k = 0; k < CHUNK; k++) {
|
|
mediump vec4 fg_k = fg * area[k];
|
|
rgba[k] = rgba[k] * (1.0 - fg_k.a) + fg_k;
|
|
}
|
|
cmd_ref.offset += 4 + CmdColor_size;
|
|
break;
|
|
case Cmd_LinGrad:
|
|
CmdLinGrad lin = Cmd_LinGrad_read(cmd_alloc, cmd_ref);
|
|
float d = lin.line_x * float(xy.x) + lin.line_y * float(xy.y) + lin.line_c;
|
|
for (uint k = 0; k < CHUNK; k++) {
|
|
vec2 chunk_xy = vec2(chunk_offset(k));
|
|
float my_d = d + lin.line_x * chunk_xy.x + lin.line_y * chunk_xy.y;
|
|
int x = int(round(clamp(my_d, 0.0, 1.0) * float(GRADIENT_WIDTH - 1)));
|
|
mediump vec4 fg_rgba = imageLoad(gradients, ivec2(x, int(lin.index)));
|
|
fg_rgba.rgb = fromsRGB(fg_rgba.rgb);
|
|
rgba[k] = fg_rgba;
|
|
}
|
|
cmd_ref.offset += 4 + CmdLinGrad_size;
|
|
break;
|
|
case Cmd_Image:
|
|
CmdImage fill_img = Cmd_Image_read(cmd_alloc, cmd_ref);
|
|
mediump vec4 img[CHUNK] = fillImage(xy_uint, fill_img);
|
|
for (uint k = 0; k < CHUNK; k++) {
|
|
mediump vec4 fg_k = img[k] * area[k];
|
|
rgba[k] = rgba[k] * (1.0 - fg_k.a) + fg_k;
|
|
}
|
|
cmd_ref.offset += 4 + CmdImage_size;
|
|
break;
|
|
case Cmd_BeginClip:
|
|
for (uint k = 0; k < CHUNK; k++) {
|
|
// We reject any inputs that might overflow in render_ctx.rs.
|
|
// The following is a sanity check so we don't corrupt memory should there be malformed inputs.
|
|
uint d = min(clip_depth, MAX_BLEND_STACK - 1);
|
|
blend_stack[d][k] = packsRGB(vec4(rgba[k]));
|
|
rgba[k] = vec4(0.0);
|
|
}
|
|
clip_depth++;
|
|
cmd_ref.offset += 4;
|
|
break;
|
|
case Cmd_EndClip:
|
|
CmdEndClip end_clip = Cmd_EndClip_read(cmd_alloc, cmd_ref);
|
|
uint blend_mode = uint(end_clip.blend >> 8);
|
|
uint comp_mode = uint(end_clip.blend & 0xFF);
|
|
clip_depth--;
|
|
for (uint k = 0; k < CHUNK; k++) {
|
|
uint d = min(clip_depth, MAX_BLEND_STACK - 1);
|
|
mediump vec4 bg = unpacksRGB(blend_stack[d][k]);
|
|
mediump vec4 fg = rgba[k] * area[k];
|
|
vec3 blend = mix_blend(bg.rgb, fg.rgb, blend_mode);
|
|
// Apply the blend color only where the foreground and background overlap.
|
|
fg.rgb = mix(fg.rgb, blend, float((fg.a * bg.a) > 0.0));
|
|
rgba[k] = mix_compose(bg.rgb, fg.rgb, bg.a, fg.a, comp_mode);
|
|
}
|
|
cmd_ref.offset += 4 + CmdEndClip_size;
|
|
break;
|
|
case Cmd_Jump:
|
|
cmd_ref = CmdRef(Cmd_Jump_read(cmd_alloc, cmd_ref).new_ref);
|
|
cmd_alloc.offset = cmd_ref.offset;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (uint i = 0; i < CHUNK; i++) {
|
|
#ifdef GRAY
|
|
// Just store the alpha value; later we can specialize this kernel more to avoid
|
|
// computing unneeded RGB colors.
|
|
imageStore(image, ivec2(xy_uint + chunk_offset(i)), vec4(rgba[i].a));
|
|
#else
|
|
imageStore(image, ivec2(xy_uint + chunk_offset(i)), vec4(tosRGB(rgba[i].rgb), rgba[i].a));
|
|
#endif
|
|
}
|
|
}
|