Mostly working strokes

The fat line in coarse path rendering is not done, but when lines are thin that mostly looks ok. Onward to tiger!
This commit is contained in:
Raph Levien 2022-11-04 12:40:54 -07:00
parent 92d6b1188f
commit 7ae5aa7491
8 changed files with 97 additions and 14 deletions

View file

@ -50,9 +50,12 @@ var<storage> paths: array<Path>;
var<storage> tiles: array<Tile>;
@group(0) @binding(7)
var<storage, read_write> bump: BumpAllocators;
var<storage> info: array<u32>;
@group(0) @binding(8)
var<storage, read_write> bump: BumpAllocators;
@group(0) @binding(9)
var<storage, read_write> ptcl: array<u32>;
@ -95,15 +98,23 @@ fn write_path(tile: Tile, linewidth: f32) {
// TODO: take flags
// TODO: handle stroke
alloc_cmd(3u);
if tile.segments != 0u {
let fill = CmdFill(tile.segments, tile.backdrop);
ptcl[cmd_offset] = CMD_FILL;
ptcl[cmd_offset + 1u] = fill.tile;
ptcl[cmd_offset + 2u] = u32(fill.backdrop);
cmd_offset += 3u;
if linewidth < 0.0 {
if tile.segments != 0u {
let fill = CmdFill(tile.segments, tile.backdrop);
ptcl[cmd_offset] = CMD_FILL;
ptcl[cmd_offset + 1u] = fill.tile;
ptcl[cmd_offset + 2u] = u32(fill.backdrop);
cmd_offset += 3u;
} else {
ptcl[cmd_offset] = CMD_SOLID;
cmd_offset += 1u;
}
} else {
ptcl[cmd_offset] = CMD_SOLID;
cmd_offset += 1u;
let stroke = CmdStroke(tile.segments, 0.5 * linewidth);
ptcl[cmd_offset] = CMD_STROKE;
ptcl[cmd_offset + 1u] = stroke.tile;
ptcl[cmd_offset + 2u] = bitcast<u32>(stroke.half_width);
cmd_offset += 3u;
}
}
@ -287,15 +298,14 @@ fn main(
let drawtag = scene[config.drawtag_base + drawobj_ix];
let dm = draw_monoids[drawobj_ix];
let dd = config.drawdata_base + dm.scene_offset;
// TODO: set up draw info from monoid
let di = dm.info_offset;
if clip_zero_depth == 0u {
let tile_ix = sh_tile_base[el_ix] + sh_tile_stride[el_ix] * tile_y + tile_x;
let tile = tiles[tile_ix];
switch drawtag {
// DRAWTAG_FILL_COLOR
case 0x44u: {
// TODO: get linewidth from draw object
let linewidth = -1.0;
let linewidth = bitcast<f32>(info[di]);
let rgba_color = scene[dd];
write_path(tile, linewidth);
write_color(CmdColor(rgba_color));

View file

@ -51,6 +51,12 @@ fn read_fill(cmd_ix: u32) -> CmdFill {
return CmdFill(tile, backdrop);
}
fn read_stroke(cmd_ix: u32) -> CmdStroke {
let tile = ptcl[cmd_ix + 1u];
let half_width = bitcast<f32>(ptcl[cmd_ix + 2u]);
return CmdStroke(tile, half_width);
}
fn read_color(cmd_ix: u32) -> CmdColor {
let rgba_color = ptcl[cmd_ix + 1u];
return CmdColor(rgba_color);
@ -105,6 +111,32 @@ fn fill_path(tile: Tile, xy: vec2<f32>) -> array<f32, PIXELS_PER_THREAD> {
return area;
}
fn stroke_path(seg: u32, half_width: f32, xy: vec2<f32>) -> array<f32, PIXELS_PER_THREAD> {
var df: array<f32, PIXELS_PER_THREAD>;
for (var i = 0u; i < PIXELS_PER_THREAD; i += 1u) {
df[i] = 1e9;
}
var segment_ix = seg;
while segment_ix != 0u {
let segment = segments[segment_ix];
let delta = segment.delta;
let dpos0 = xy + vec2<f32>(0.5, 0.5) - segment.origin;
let scale = 1.0 / dot(delta, delta);
for (var i = 0u; i < PIXELS_PER_THREAD; i += 1u) {
let dpos = vec2<f32>(dpos0.x + f32(i), dpos0.y);
let t = clamp(dot(dpos, delta) * scale, 0.0, 1.0);
// performance idea: hoist sqrt out of loop
df[i] = min(df[i], length(delta * t - dpos));
}
segment_ix = segment.next;
}
for (var i = 0u; i < PIXELS_PER_THREAD; i += 1u) {
// reuse array; return alpha rather than distance
df[i] = clamp(half_width + 0.5 - df[i], 0.0, 1.0);
}
return df;
}
@compute @workgroup_size(4, 16)
fn main(
@builtin(global_invocation_id) global_id: vec3<u32>,
@ -132,6 +164,12 @@ fn main(
area = fill_path(tile, xy);
cmd_ix += 3u;
}
// CMD_STROKE
case 2u: {
let stroke = read_stroke(cmd_ix);
area = stroke_path(stroke.tile, stroke.half_width, xy);
cmd_ix += 3u;
}
// CMD_SOLID
case 3u: {
for (var i = 0u; i < PIXELS_PER_THREAD; i += 1u) {

View file

@ -143,8 +143,10 @@ fn main(
var tag_byte = (tag_word >> shift) & 0xffu;
let out = &path_bboxes[tm.path_ix];
var linewidth: f32;
if (tag_byte & PATH_TAG_PATH) != 0u {
(*out).linewidth = -1.0; // TODO: plumb linewidth
linewidth = bitcast<f32>(scene[config.linewidth_base + tm.linewidth_ix]);
(*out).linewidth = linewidth;
(*out).trans_ix = tm.trans_ix;
}
// Decode path data
@ -195,6 +197,13 @@ fn main(
p1 = mix(p1, p0, 1.0 / 3.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<f32>(length(transform.matrx.xz), length(transform.matrx.yw));
bbox += vec4<f32>(-stroke, stroke);
}
cubics[global_id.x] = Cubic(p0, p1, p2, p3, tm.path_ix, 0u);
// Update bounding box using atomics only. Computing a monoid is a
// potential future optimization.

View file

@ -30,6 +30,7 @@ struct Config {
drawdata_base: u32,
transform_base: u32,
linewidth_base: u32,
}
// Geometry of tiles and bins

View file

@ -25,6 +25,7 @@ let PTCL_HEADROOM = 2u;
// Tags for PTCL commands
let CMD_END = 0u;
let CMD_FILL = 1u;
let CMD_STROKE = 2u;
let CMD_SOLID = 3u;
let CMD_COLOR = 5u;
let CMD_JUMP = 11u;
@ -37,6 +38,11 @@ struct CmdFill {
backdrop: i32,
}
struct CmdStroke {
tile: u32,
half_width: f32,
}
struct CmdJump {
new_ix: u32,
}

View file

@ -31,6 +31,7 @@ struct Config {
drawtag_base: u32,
drawdata_base: u32,
transform_base: u32,
linewidth_base: u32,
}
#[repr(C)]
@ -137,6 +138,8 @@ pub fn render_full(scene: &Scene, shaders: &FullShaders) -> (Recording, BufProxy
scene.extend(&data.drawdata_stream);
let transform_base = size_to_words(scene.len());
scene.extend(bytemuck::cast_slice(&data.transform_stream));
let linewidth_base = size_to_words(scene.len());
scene.extend(bytemuck::cast_slice(&data.linewidth_stream));
let n_path = data.n_path;
// TODO: calculate for real when we do rectangles
@ -151,6 +154,7 @@ pub fn render_full(scene: &Scene, shaders: &FullShaders) -> (Recording, BufProxy
drawtag_base,
drawdata_base,
transform_base,
linewidth_base,
};
println!("{:?}", config);
let scene_buf = recording.upload(scene);
@ -284,6 +288,7 @@ pub fn render_full(scene: &Scene, shaders: &FullShaders) -> (Recording, BufProxy
bin_data_buf,
path_buf,
tile_buf,
info_buf,
bump_buf,
ptcl_buf,
],

View file

@ -241,6 +241,7 @@ pub fn full_shaders(device: &Device, engine: &mut Engine) -> Result<FullShaders,
BindType::BufReadOnly,
BindType::BufReadOnly,
BindType::BufReadOnly,
BindType::BufReadOnly,
BindType::Buffer,
BindType::Buffer,
],

View file

@ -14,7 +14,7 @@
//
// Also licensed under MIT license, at your choice.
use piet_scene::{Affine, Brush, Color, Fill, PathElement, Point, Scene, SceneBuilder};
use piet_scene::{Affine, Brush, Color, Fill, PathElement, Point, Scene, SceneBuilder, Stroke};
pub fn gen_test_scene() -> Scene {
let mut scene = Scene::default();
@ -32,6 +32,19 @@ pub fn gen_test_scene() -> Scene {
let transform = Affine::translate(50.0, 50.0);
let brush = Brush::Solid(Color::rgba8(0xff, 0xff, 0x00, 0x80));
builder.fill(Fill::NonZero, transform, &brush, None, &path);
let transform = Affine::translate(100.0, 100.0);
let style = Stroke {
width: 1.0,
join: piet_scene::Join::Round,
miter_limit: 1.4,
start_cap: piet_scene::Cap::Round,
end_cap: piet_scene::Cap::Round,
dash_pattern: [],
dash_offset: 0.0,
scale: true,
};
let brush = Brush::Solid(Color::rgb8(0xa0, 0x00, 0x00));
builder.stroke(&style, transform, &brush, None, &path);
scene
}