diff --git a/piet-wgsl/shader/coarse.wgsl b/piet-wgsl/shader/coarse.wgsl index 7a2ecf9..642ce73 100644 --- a/piet-wgsl/shader/coarse.wgsl +++ b/piet-wgsl/shader/coarse.wgsl @@ -50,9 +50,12 @@ var paths: array; var tiles: array; @group(0) @binding(7) -var bump: BumpAllocators; +var info: array; @group(0) @binding(8) +var bump: BumpAllocators; + +@group(0) @binding(9) var ptcl: array; @@ -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(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(info[di]); let rgba_color = scene[dd]; write_path(tile, linewidth); write_color(CmdColor(rgba_color)); diff --git a/piet-wgsl/shader/fine.wgsl b/piet-wgsl/shader/fine.wgsl index ad3772c..c347630 100644 --- a/piet-wgsl/shader/fine.wgsl +++ b/piet-wgsl/shader/fine.wgsl @@ -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(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) -> array { return area; } +fn stroke_path(seg: u32, half_width: f32, xy: vec2) -> array { + var df: array; + 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(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(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, @@ -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) { diff --git a/piet-wgsl/shader/pathseg.wgsl b/piet-wgsl/shader/pathseg.wgsl index d2c6a6a..b4f83e1 100644 --- a/piet-wgsl/shader/pathseg.wgsl +++ b/piet-wgsl/shader/pathseg.wgsl @@ -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(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(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); // Update bounding box using atomics only. Computing a monoid is a // potential future optimization. diff --git a/piet-wgsl/shader/shared/config.wgsl b/piet-wgsl/shader/shared/config.wgsl index b43f35b..73f4054 100644 --- a/piet-wgsl/shader/shared/config.wgsl +++ b/piet-wgsl/shader/shared/config.wgsl @@ -30,6 +30,7 @@ struct Config { drawdata_base: u32, transform_base: u32, + linewidth_base: u32, } // Geometry of tiles and bins diff --git a/piet-wgsl/shader/shared/ptcl.wgsl b/piet-wgsl/shader/shared/ptcl.wgsl index 7121f84..8d6e869 100644 --- a/piet-wgsl/shader/shared/ptcl.wgsl +++ b/piet-wgsl/shader/shared/ptcl.wgsl @@ -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, } diff --git a/piet-wgsl/src/render.rs b/piet-wgsl/src/render.rs index 9202ef0..04f4232 100644 --- a/piet-wgsl/src/render.rs +++ b/piet-wgsl/src/render.rs @@ -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, ], diff --git a/piet-wgsl/src/shaders.rs b/piet-wgsl/src/shaders.rs index f918d96..b659170 100644 --- a/piet-wgsl/src/shaders.rs +++ b/piet-wgsl/src/shaders.rs @@ -241,6 +241,7 @@ pub fn full_shaders(device: &Device, engine: &mut Engine) -> Result 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 }