Fix rendering artifacts with strokes

A number of things weren't being computed correctly for strokes,
including bounding boxes and also incorrectly applying y_edge logic
(which is only correct for fills).
This commit is contained in:
Raph Levien 2022-11-26 16:51:22 -08:00
parent cf9b5c5805
commit 3c0752f6ae
4 changed files with 32 additions and 25 deletions

View file

@ -113,6 +113,7 @@ fn main(
// classic memory vs ALU tradeoff.
let cubic = cubics[global_id.x];
let path = paths[cubic.path_ix];
let is_stroke = (cubic.flags & CUBIC_IS_STROKE) != 0u;
let bbox = vec4<i32>(path.bbox);
let p0 = cubic.p0;
let p1 = cubic.p1;
@ -169,15 +170,15 @@ fn main(
}
// Output line segment lp0..lp1
let xymin = min(lp0, lp1);
let xymax = max(lp0, lp1);
let xymin = min(lp0, lp1) - cubic.stroke;
let xymax = max(lp0, lp1) + cubic.stroke;
let dp = lp1 - lp0;
let recip_dx = 1.0 / dp.x;
let invslope = select(dp.x / dp.y, 1.0e9, abs(dp.y) < 1.0e-9);
let c = 0.5 * abs(invslope);
let b = invslope;
let SX = 1.0 / f32(TILE_WIDTH);
let SY = 1.0 / f32(TILE_HEIGHT);
let c = (cubic.stroke.x + abs(invslope) * (0.5 * f32(TILE_HEIGHT) + cubic.stroke.y)) * SX;
let b = invslope;
let a = (lp0.x - (lp0.y - 0.5 * f32(TILE_HEIGHT)) * b) * SX;
var x0 = i32(floor(xymin.x * SX));
var x1 = i32(floor(xymax.x * SX) + 1.0);
@ -200,7 +201,7 @@ fn main(
for (var y = y0; y < y1; y += 1) {
let tile_y0 = f32(y) * f32(TILE_HEIGHT);
let xbackdrop = max(xray + 1, bbox.x);
if xymin.y < tile_y0 && xbackdrop < bbox.z {
if !is_stroke && xymin.y < tile_y0 && xbackdrop < bbox.z {
let backdrop = select(-1, 1, dp.y < 0.0);
let tile_ix = base + xbackdrop;
atomicAdd(&tiles[tile_ix].backdrop, backdrop);
@ -226,22 +227,25 @@ fn main(
let old = atomicExchange(&tiles[tile_ix].segments, seg_ix);
tile_seg.origin = lp0;
tile_seg.delta = dp;
var y_edge = mix(lp0.y, lp1.y, (tile_x0 - lp0.x) * recip_dx);
if xymin.x < tile_x0 {
let p = vec2(tile_x0, y_edge);
if dp.x < 0.0 {
tile_seg.delta = p - lp0;
} else {
tile_seg.origin = p;
tile_seg.delta = lp1 - p;
var y_edge = 0.0;
if !is_stroke {
y_edge = mix(lp0.y, lp1.y, (tile_x0 - lp0.x) * recip_dx);
if xymin.x < tile_x0 {
let p = vec2(tile_x0, y_edge);
if dp.x < 0.0 {
tile_seg.delta = p - lp0;
} else {
tile_seg.origin = p;
tile_seg.delta = lp1 - p;
}
if tile_seg.delta.x == 0.0 {
tile_seg.delta.x = sign(dp.x) * 1e-9;
}
}
if tile_seg.delta.x == 0.0 {
tile_seg.delta.x = sign(dp.x) * 1e-9;
if x <= min_xray || max_xray < x {
y_edge = 1e9;
}
}
if x <= min_xray || max_xray < x {
y_edge = 1e9;
}
tile_seg.y_edge = y_edge;
tile_seg.next = old;
segments[seg_ix] = tile_seg;

View file

@ -129,9 +129,8 @@ fn main(
var tag_byte = (tag_word >> shift) & 0xffu;
let out = &path_bboxes[tm.path_ix];
var linewidth: f32;
let linewidth = bitcast<f32>(scene[config.linewidth_base + tm.linewidth_ix]);
if (tag_byte & PATH_TAG_PATH) != 0u {
linewidth = bitcast<f32>(scene[config.linewidth_base + tm.linewidth_ix]);
(*out).linewidth = linewidth;
(*out).trans_ix = tm.trans_ix;
}
@ -182,14 +181,16 @@ fn main(
p1 = mix(p1, p0, 1.0 / 3.0);
}
}
var stroke = vec2<f32>(0.0, 0.0);
if linewidth >= 0.0 {
// See https://www.iquilezles.org/www/articles/ellipses/ellipses.htm
// This is the correct bounding box, but we're not handling rendering
// in the isotropic case, so it may mismatch.
let stroke = 0.5 * linewidth * vec2(length(transform.matrx.xz), length(transform.matrx.yw));
stroke = 0.5 * linewidth * vec2(length(transform.matrx.xz), length(transform.matrx.yw));
bbox += vec4(-stroke, stroke);
}
cubics[global_id.x] = Cubic(p0, p1, p2, p3, tm.path_ix, 0u);
let flags = u32(linewidth >= 0.0);
cubics[global_id.x] = Cubic(p0, p1, p2, p3, stroke, tm.path_ix, flags);
// Update bounding box using atomics only. Computing a monoid is a
// potential future optimization.
if bbox.z > bbox.x || bbox.w > bbox.y {

View file

@ -5,7 +5,9 @@ struct Cubic {
p1: vec2<f32>,
p2: vec2<f32>,
p3: vec2<f32>,
stroke: vec2<f32>,
path_ix: u32,
// Needed?
padding: u32,
flags: u32,
}
let CUBIC_IS_STROKE = 1u;

View file

@ -11,7 +11,7 @@ use crate::{
const TAG_MONOID_SIZE: u64 = 12;
const TAG_MONOID_FULL_SIZE: u64 = 20;
const PATH_BBOX_SIZE: u64 = 24;
const CUBIC_SIZE: u64 = 40;
const CUBIC_SIZE: u64 = 48;
const DRAWMONOID_SIZE: u64 = 16;
const MAX_DRAWINFO_SIZE: u64 = 44;
const CLIP_BIC_SIZE: u64 = 8;