mirror of
https://github.com/italicsjenga/vello.git
synced 2024-10-17 23:11:30 +11:00
b571e0d10c
All segments given to path coarse raster are cubics. Flatten to quadratics. This works but the quality is not (yet) good.
173 lines
6.6 KiB
Plaintext
173 lines
6.6 KiB
Plaintext
// Coarse rasterization of path segments.
|
|
|
|
// Allocation and initialization of tiles for paths.
|
|
|
|
#version 450
|
|
#extension GL_GOOGLE_include_directive : enable
|
|
|
|
#include "setup.h"
|
|
|
|
#define LG_COARSE_WG 5
|
|
#define COARSE_WG (1 << LG_COARSE_WG)
|
|
|
|
layout(local_size_x = COARSE_WG, local_size_y = 1) in;
|
|
|
|
layout(set = 0, binding = 0) buffer PathSegBuf {
|
|
uint[] pathseg;
|
|
};
|
|
|
|
layout(set = 0, binding = 1) buffer AllocBuf {
|
|
uint n_paths;
|
|
uint n_pathseg;
|
|
uint alloc;
|
|
};
|
|
|
|
layout(set = 0, binding = 2) buffer TileBuf {
|
|
uint[] tile;
|
|
};
|
|
|
|
#include "pathseg.h"
|
|
#include "tile.h"
|
|
|
|
// scale factors useful for converting coordinates to tiles
|
|
#define SX (1.0 / float(TILE_WIDTH_PX))
|
|
#define SY (1.0 / float(TILE_HEIGHT_PX))
|
|
|
|
#define Q_ACCURACY 0.025
|
|
#define MAX_HYPOT2 (432.0 * Q_ACCURACY * Q_ACCURACY)
|
|
|
|
vec2 eval_cubic(vec2 p0, vec2 p1, vec2 p2, vec2 p3, float t) {
|
|
float mt = 1.0 - t;
|
|
return p0 * (mt * mt * mt) + (p1 * (mt * mt * 3.0) + (p2 * (mt * 3.0) + p3 * t) * t) * t;
|
|
}
|
|
|
|
void main() {
|
|
uint element_ix = gl_GlobalInvocationID.x;
|
|
PathSegRef ref = PathSegRef(element_ix * PathSeg_size);
|
|
|
|
uint tag = PathSeg_Nop;
|
|
if (element_ix < n_pathseg) {
|
|
tag = PathSeg_tag(ref);
|
|
}
|
|
// Setup for coverage algorithm.
|
|
float a, b, c;
|
|
// Bounding box of element in pixel coordinates.
|
|
float xmin, xmax, ymin, ymax;
|
|
PathStrokeLine line;
|
|
float dx;
|
|
switch (tag) {
|
|
/*
|
|
case PathSeg_FillLine:
|
|
case PathSeg_StrokeLine:
|
|
line = PathSeg_StrokeLine_read(ref);
|
|
xmin = min(line.p0.x, line.p1.x) - line.stroke.x;
|
|
xmax = max(line.p0.x, line.p1.x) + line.stroke.x;
|
|
ymin = min(line.p0.y, line.p1.y) - line.stroke.y;
|
|
ymax = max(line.p0.y, line.p1.y) + line.stroke.y;
|
|
dx = line.p1.x - line.p0.x;
|
|
float dy = line.p1.y - line.p0.y;
|
|
// Set up for per-scanline coverage formula, below.
|
|
float invslope = abs(dy) < 1e-9 ? 1e9 : dx / dy;
|
|
c = (line.stroke.x + abs(invslope) * (0.5 * float(TILE_HEIGHT_PX) + line.stroke.y)) * SX;
|
|
b = invslope; // Note: assumes square tiles, otherwise scale.
|
|
a = (line.p0.x - (line.p0.y - 0.5 * float(TILE_HEIGHT_PX)) * b) * SX;
|
|
break;
|
|
*/
|
|
case PathSeg_FillCubic:
|
|
case PathSeg_StrokeCubic:
|
|
PathStrokeCubic cubic = PathSeg_StrokeCubic_read(ref);
|
|
vec2 err_v = 3.0 * (cubic.p2 - cubic.p1) + cubic.p0 - cubic.p3;
|
|
float err = err_v.x * err_v.x + err_v.y * err_v.y;
|
|
// The number of quadratics.
|
|
uint n = max(uint(ceil(pow(err * (1.0 / MAX_HYPOT2), 1.0 / 6.0))), 1);
|
|
vec2 p0 = cubic.p0;
|
|
float step = 1.0 / float(n);
|
|
uint path_ix = cubic.path_ix;
|
|
Path path = Path_read(PathRef(path_ix * Path_size));
|
|
ivec4 bbox = ivec4(path.bbox);
|
|
for (int i = 0; i < n; i++) {
|
|
// TODO: probably need special logic to make sure it's manifold
|
|
float t = float(i + 1) * step;
|
|
vec2 p2 = eval_cubic(cubic.p0, cubic.p1, cubic.p2, cubic.p3, t);
|
|
/*
|
|
vec2 p1 = eval_cubic(cubic.p0, cubic.p1, cubic.p2, cubic.p3, t - 0.5 * step);
|
|
p1 = 2.0 * p1 - 0.5 * (p0 + p2);
|
|
*/
|
|
|
|
xmin = min(p0.x, p2.x) - cubic.stroke.x;
|
|
xmax = max(p0.x, p2.x) + cubic.stroke.x;
|
|
ymin = min(p0.y, p2.y) - cubic.stroke.y;
|
|
ymax = max(p0.y, p2.y) + cubic.stroke.y;
|
|
float dx = p2.x - p0.x;
|
|
float dy = p2.y - p0.y;
|
|
// Set up for per-scanline coverage formula, below.
|
|
float invslope = abs(dy) < 1e-9 ? 1e9 : dx / dy;
|
|
c = (cubic.stroke.x + abs(invslope) * (0.5 * float(TILE_HEIGHT_PX) + cubic.stroke.y)) * SX;
|
|
b = invslope; // Note: assumes square tiles, otherwise scale.
|
|
a = (p0.x - (p0.y - 0.5 * float(TILE_HEIGHT_PX)) * b) * SX;
|
|
|
|
int x0 = int(floor((xmin) * SX));
|
|
int x1 = int(ceil((xmax) * SX));
|
|
int y0 = int(floor((ymin) * SY));
|
|
int y1 = int(ceil((ymax) * SY));
|
|
|
|
x0 = clamp(x0, bbox.x, bbox.z);
|
|
y0 = clamp(y0, bbox.y, bbox.w);
|
|
x1 = clamp(x1, bbox.x, bbox.z);
|
|
y1 = clamp(y1, bbox.y, bbox.w);
|
|
float xc = a + b * float(y0);
|
|
int stride = bbox.z - bbox.x;
|
|
int base = (y0 - bbox.y) * stride - bbox.x;
|
|
// TODO: can be tighter, use c to bound width
|
|
uint n_tile_alloc = uint((x1 - x0) * (y1 - y0));
|
|
// Consider using subgroups to aggregate atomic add.
|
|
uint tile_offset = atomicAdd(alloc, n_tile_alloc * TileSeg_size);
|
|
TileSeg tile_seg;
|
|
for (int y = y0; y < y1; y++) {
|
|
float tile_y0 = float(y * TILE_HEIGHT_PX);
|
|
if (tag == PathSeg_FillCubic && min(p0.y, p2.y) <= tile_y0) {
|
|
int xray = max(int(ceil(xc - 0.5 * b)), bbox.x);
|
|
if (xray < bbox.z) {
|
|
int backdrop = p2.y < p0.y ? 1 : -1;
|
|
TileRef tile_ref = Tile_index(path.tiles, uint(base + xray));
|
|
uint tile_el = tile_ref.offset >> 2;
|
|
atomicAdd(tile[tile_el + 1], backdrop);
|
|
}
|
|
}
|
|
int xx0 = clamp(int(floor(xc - c)), x0, x1);
|
|
int xx1 = clamp(int(ceil(xc + c)), x0, x1);
|
|
for (int x = xx0; x < xx1; x++) {
|
|
float tile_x0 = float(x * TILE_WIDTH_PX);
|
|
TileRef tile_ref = Tile_index(path.tiles, uint(base + x));
|
|
uint tile_el = tile_ref.offset >> 2;
|
|
uint old = atomicExchange(tile[tile_el], tile_offset);
|
|
tile_seg.start = p0;
|
|
tile_seg.end = p2;
|
|
float y_edge = 0.0;
|
|
if (tag == PathSeg_FillCubic) {
|
|
y_edge = mix(p0.y, p2.y, (tile_x0 - p0.x) / dx);
|
|
if (min(p0.x, p2.x) < tile_x0 && y_edge >= tile_y0 && y_edge < tile_y0 + TILE_HEIGHT_PX) {
|
|
if (p0.x > p2.x) {
|
|
tile_seg.end = vec2(tile_x0, y_edge);
|
|
} else {
|
|
tile_seg.start = vec2(tile_x0, y_edge);
|
|
}
|
|
} else {
|
|
y_edge = 1e9;
|
|
}
|
|
}
|
|
tile_seg.y_edge = y_edge;
|
|
tile_seg.next.offset = old;
|
|
TileSeg_write(TileSegRef(tile_offset), tile_seg);
|
|
tile_offset += TileSeg_size;
|
|
}
|
|
xc += b;
|
|
base += stride;
|
|
}
|
|
|
|
p0 = p2;
|
|
}
|
|
break;
|
|
}
|
|
}
|