Merge pull request #50 from eliasnaur/master

Avoid inconsistent line-tile intersections
This commit is contained in:
Elias Naur 2020-12-02 20:03:10 +01:00 committed by GitHub
commit e582f6b388
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 76 additions and 46 deletions

View file

@ -13,8 +13,8 @@ piet_gpu! {
} }
// Segments within a tile are represented as a linked list. // Segments within a tile are represented as a linked list.
struct TileSeg { struct TileSeg {
start: [f32; 2], origin: [f32; 2],
end: [f32; 2], vector: [f32; 2],
y_edge: f32, y_edge: f32,
next: Ref<TileSeg>, next: Ref<TileSeg>,
} }

View file

@ -65,8 +65,8 @@ float[CHUNK] computeArea(vec2 xy, int backdrop, uint tile_ref) {
TileSeg seg = TileSeg_read(tile_seg_ref); TileSeg seg = TileSeg_read(tile_seg_ref);
for (uint k = 0; k < CHUNK; k++) { for (uint k = 0; k < CHUNK; k++) {
vec2 my_xy = vec2(xy.x, xy.y + float(k * CHUNK_DY)); vec2 my_xy = vec2(xy.x, xy.y + float(k * CHUNK_DY));
vec2 start = seg.start - my_xy; vec2 start = seg.origin - my_xy;
vec2 end = seg.end - my_xy; vec2 end = start + seg.vector;
vec2 window = clamp(vec2(start.y, end.y), 0.0, 1.0); vec2 window = clamp(vec2(start.y, end.y), 0.0, 1.0);
if (window.x != window.y) { if (window.x != window.y) {
vec2 t = (window - start.y) / (end.y - start.y); vec2 t = (window - start.y) / (end.y - start.y);
@ -79,7 +79,7 @@ float[CHUNK] computeArea(vec2 xy, int backdrop, uint tile_ref) {
float a = (b + 0.5 * (d * d - c * c) - xmin) / (xmax - xmin); float a = (b + 0.5 * (d * d - c * c) - xmin) / (xmax - xmin);
area[k] += a * (window.x - window.y); area[k] += a * (window.x - window.y);
} }
area[k] += sign(end.x - start.x) * clamp(my_xy.y - seg.y_edge + 1.0, 0.0, 1.0); area[k] += sign(seg.vector.x) * clamp(my_xy.y - seg.y_edge + 1.0, 0.0, 1.0);
} }
tile_seg_ref = seg.next; tile_seg_ref = seg.next;
} while (tile_seg_ref.offset != 0); } while (tile_seg_ref.offset != 0);
@ -131,9 +131,9 @@ void main() {
TileSegRef tile_seg_ref = TileSegRef(stroke.tile_ref); TileSegRef tile_seg_ref = TileSegRef(stroke.tile_ref);
do { do {
TileSeg seg = TileSeg_read(tile_seg_ref); TileSeg seg = TileSeg_read(tile_seg_ref);
vec2 line_vec = seg.end - seg.start; vec2 line_vec = seg.vector;
for (uint k = 0; k < CHUNK; k++) { for (uint k = 0; k < CHUNK; k++) {
vec2 dpos = xy + vec2(0.5, 0.5) - seg.start; vec2 dpos = xy + vec2(0.5, 0.5) - seg.origin;
dpos.y += float(k * CHUNK_DY); dpos.y += float(k * CHUNK_DY);
float t = clamp(dot(line_vec, dpos) / dot(line_vec, line_vec), 0.0, 1.0); 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)); df[k] = min(df[k], length(line_vec * t - dpos));

Binary file not shown.

View file

@ -101,12 +101,6 @@ void main() {
if (element_ix < n_pathseg) { if (element_ix < n_pathseg) {
tag = PathSeg_tag(ref); 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) { switch (tag) {
case PathSeg_FillCubic: case PathSeg_FillCubic:
case PathSeg_StrokeCubic: case PathSeg_StrokeCubic:
@ -162,22 +156,24 @@ void main() {
} }
// Output line segment // Output line segment
xmin = min(p0.x, p1.x) - cubic.stroke.x;
xmax = max(p0.x, p1.x) + cubic.stroke.x; // Bounding box of element in pixel coordinates.
ymin = min(p0.y, p1.y) - cubic.stroke.y; float xmin = min(p0.x, p1.x) - cubic.stroke.x;
ymax = max(p0.y, p1.y) + cubic.stroke.y; float xmax = max(p0.x, p1.x) + cubic.stroke.x;
float ymin = min(p0.y, p1.y) - cubic.stroke.y;
float ymax = max(p0.y, p1.y) + cubic.stroke.y;
float dx = p1.x - p0.x; float dx = p1.x - p0.x;
float dy = p1.y - p0.y; float dy = p1.y - p0.y;
// Set up for per-scanline coverage formula, below. // Set up for per-scanline coverage formula, below.
float invslope = abs(dy) < 1e-9 ? 1e9 : dx / dy; 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; float c = (cubic.stroke.x + abs(invslope) * (0.5 * float(TILE_HEIGHT_PX) + cubic.stroke.y)) * SX;
b = invslope; // Note: assumes square tiles, otherwise scale. float b = invslope; // Note: assumes square tiles, otherwise scale.
a = (p0.x - (p0.y - 0.5 * float(TILE_HEIGHT_PX)) * b) * SX; float a = (p0.x - (p0.y - 0.5 * float(TILE_HEIGHT_PX)) * b) * SX;
int x0 = int(floor((xmin) * SX)); int x0 = int(floor(xmin * SX));
int x1 = int(ceil((xmax) * SX)); int x1 = int(floor(xmax * SX) + 1);
int y0 = int(floor((ymin) * SY)); int y0 = int(floor(ymin * SY));
int y1 = int(ceil((ymax) * SY)); int y1 = int(floor(ymax * SY) + 1);
x0 = clamp(x0, bbox.x, bbox.z); x0 = clamp(x0, bbox.x, bbox.z);
y0 = clamp(y0, bbox.y, bbox.w); y0 = clamp(y0, bbox.y, bbox.w);
@ -191,36 +187,69 @@ void main() {
// Consider using subgroups to aggregate atomic add. // Consider using subgroups to aggregate atomic add.
uint tile_offset = atomicAdd(alloc, n_tile_alloc * TileSeg_size); uint tile_offset = atomicAdd(alloc, n_tile_alloc * TileSeg_size);
TileSeg tile_seg; TileSeg tile_seg;
int xray = int(floor(p0.x*SX));
int last_xray = int(floor(p1.x*SX));
if (p0.y > p1.y) {
int tmp = xray;
xray = last_xray;
last_xray = tmp;
}
for (int y = y0; y < y1; y++) { for (int y = y0; y < y1; y++) {
float tile_y0 = float(y * TILE_HEIGHT_PX); int xbackdrop = max(xray + 1, bbox.x);
if (tag == PathSeg_FillCubic && min(p0.y, p1.y) <= tile_y0) { if (tag == PathSeg_FillCubic && y > y0 && xbackdrop < bbox.z) {
int xray = max(int(ceil(xc - 0.5 * b)), bbox.x);
if (xray < bbox.z) {
int backdrop = p1.y < p0.y ? 1 : -1; int backdrop = p1.y < p0.y ? 1 : -1;
TileRef tile_ref = Tile_index(path.tiles, uint(base + xray)); TileRef tile_ref = Tile_index(path.tiles, uint(base + xbackdrop));
uint tile_el = tile_ref.offset >> 2; uint tile_el = tile_ref.offset >> 2;
atomicAdd(tile[tile_el + 1], backdrop); atomicAdd(tile[tile_el + 1], backdrop);
} }
}
int xx0 = clamp(int(floor(xc - c)), x0, x1); int xx0 = clamp(int(floor(xc - c)), x0, x1);
int xx1 = clamp(int(ceil(xc + c)), x0, x1); int xx1 = clamp(int(ceil(xc + c)), x0, x1);
xx1 = max(xx1, xray + 1);
// next_xray is the xray for the next scanline; it is derived
// by left edge intersections computed below.
int next_xray = xray;
for (int x = xx0; x < xx1; x++) { for (int x = xx0; x < xx1; x++) {
float tile_x0 = float(x * TILE_WIDTH_PX); float tile_x0 = float(x * TILE_WIDTH_PX);
TileRef tile_ref = Tile_index(path.tiles, uint(base + x)); TileRef tile_ref = Tile_index(path.tiles, uint(base + x));
uint tile_el = tile_ref.offset >> 2; uint tile_el = tile_ref.offset >> 2;
uint old = atomicExchange(tile[tile_el], tile_offset); uint old = atomicExchange(tile[tile_el], tile_offset);
tile_seg.start = p0; tile_seg.origin = p0;
tile_seg.end = p1; tile_seg.vector = p1 - p0;
float y_edge = 0.0; float y_edge = 0.0;
if (tag == PathSeg_FillCubic) { if (tag == PathSeg_FillCubic) {
float tile_y0 = float(y * TILE_HEIGHT_PX);
y_edge = mix(p0.y, p1.y, (tile_x0 - p0.x) / dx); y_edge = mix(p0.y, p1.y, (tile_x0 - p0.x) / dx);
if (min(p0.x, p1.x) < tile_x0 && y_edge >= tile_y0 && y_edge < tile_y0 + TILE_HEIGHT_PX) { if (min(p0.x, p1.x) < tile_x0 && y_edge >= tile_y0 && y_edge < tile_y0 + TILE_HEIGHT_PX) {
// Left edge intersection.
vec2 p = vec2(tile_x0, y_edge);
if (p0.x > p1.x) { if (p0.x > p1.x) {
tile_seg.end = vec2(tile_x0, y_edge); tile_seg.vector = p - p0;
} else { } else {
tile_seg.start = vec2(tile_x0, y_edge); tile_seg.origin = p;
tile_seg.vector = p1 - p;
} }
} else { // kernel4 uses sign(vector.x) for the sign of the intersection backdrop.
// Nudge zeroes towards the intended sign.
if (tile_seg.vector.x == 0) {
tile_seg.vector.x += sign(p1.x - p0.x)*1e-9;
}
// Move next_xray consistently with previous intersections.
if (x > next_xray && next_xray >= xray) {
next_xray = x;
} else if (x <= next_xray && next_xray <= xray) {
next_xray = x - 1;
}
}
// Force last xray on the last scanline for consistency with later
// line segments.
if (y == y1 - 1) {
next_xray = last_xray;
}
// Drop inconsistent intersections.
if (x <= min(xray, next_xray) || max(xray, next_xray) < x) {
y_edge = 1e9; y_edge = 1e9;
} }
} }
@ -231,6 +260,7 @@ void main() {
} }
xc += b; xc += b;
base += stride; base += stride;
xray = next_xray;
} }
n_out += 1; n_out += 1;

Binary file not shown.

View file

@ -35,8 +35,8 @@ TileRef Tile_index(TileRef ref, uint index) {
} }
struct TileSeg { struct TileSeg {
vec2 start; vec2 origin;
vec2 end; vec2 vector;
float y_edge; float y_edge;
TileSegRef next; TileSegRef next;
}; };
@ -90,8 +90,8 @@ TileSeg TileSeg_read(TileSegRef ref) {
uint raw4 = tile[ix + 4]; uint raw4 = tile[ix + 4];
uint raw5 = tile[ix + 5]; uint raw5 = tile[ix + 5];
TileSeg s; TileSeg s;
s.start = vec2(uintBitsToFloat(raw0), uintBitsToFloat(raw1)); s.origin = vec2(uintBitsToFloat(raw0), uintBitsToFloat(raw1));
s.end = vec2(uintBitsToFloat(raw2), uintBitsToFloat(raw3)); s.vector = vec2(uintBitsToFloat(raw2), uintBitsToFloat(raw3));
s.y_edge = uintBitsToFloat(raw4); s.y_edge = uintBitsToFloat(raw4);
s.next = TileSegRef(raw5); s.next = TileSegRef(raw5);
return s; return s;
@ -99,10 +99,10 @@ TileSeg TileSeg_read(TileSegRef ref) {
void TileSeg_write(TileSegRef ref, TileSeg s) { void TileSeg_write(TileSegRef ref, TileSeg s) {
uint ix = ref.offset >> 2; uint ix = ref.offset >> 2;
tile[ix + 0] = floatBitsToUint(s.start.x); tile[ix + 0] = floatBitsToUint(s.origin.x);
tile[ix + 1] = floatBitsToUint(s.start.y); tile[ix + 1] = floatBitsToUint(s.origin.y);
tile[ix + 2] = floatBitsToUint(s.end.x); tile[ix + 2] = floatBitsToUint(s.vector.x);
tile[ix + 3] = floatBitsToUint(s.end.y); tile[ix + 3] = floatBitsToUint(s.vector.y);
tile[ix + 4] = floatBitsToUint(s.y_edge); tile[ix + 4] = floatBitsToUint(s.y_edge);
tile[ix + 5] = s.next.offset; tile[ix + 5] = s.next.offset;
} }