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

View file

@ -51,6 +51,12 @@ fn read_fill(cmd_ix: u32) -> CmdFill {
return CmdFill(tile, backdrop); 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 { fn read_color(cmd_ix: u32) -> CmdColor {
let rgba_color = ptcl[cmd_ix + 1u]; let rgba_color = ptcl[cmd_ix + 1u];
return CmdColor(rgba_color); return CmdColor(rgba_color);
@ -105,6 +111,32 @@ fn fill_path(tile: Tile, xy: vec2<f32>) -> array<f32, PIXELS_PER_THREAD> {
return area; 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) @compute @workgroup_size(4, 16)
fn main( fn main(
@builtin(global_invocation_id) global_id: vec3<u32>, @builtin(global_invocation_id) global_id: vec3<u32>,
@ -132,6 +164,12 @@ fn main(
area = fill_path(tile, xy); area = fill_path(tile, xy);
cmd_ix += 3u; 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 // CMD_SOLID
case 3u: { case 3u: {
for (var i = 0u; i < PIXELS_PER_THREAD; i += 1u) { 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; var tag_byte = (tag_word >> shift) & 0xffu;
let out = &path_bboxes[tm.path_ix]; let out = &path_bboxes[tm.path_ix];
var linewidth: f32;
if (tag_byte & PATH_TAG_PATH) != 0u { 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; (*out).trans_ix = tm.trans_ix;
} }
// Decode path data // Decode path data
@ -195,6 +197,13 @@ fn main(
p1 = mix(p1, p0, 1.0 / 3.0); 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); cubics[global_id.x] = Cubic(p0, p1, p2, p3, tm.path_ix, 0u);
// Update bounding box using atomics only. Computing a monoid is a // Update bounding box using atomics only. Computing a monoid is a
// potential future optimization. // potential future optimization.

View file

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

View file

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

View file

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

View file

@ -14,7 +14,7 @@
// //
// Also licensed under MIT license, at your choice. // 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 { pub fn gen_test_scene() -> Scene {
let mut scene = Scene::default(); let mut scene = Scene::default();
@ -32,6 +32,19 @@ pub fn gen_test_scene() -> Scene {
let transform = Affine::translate(50.0, 50.0); let transform = Affine::translate(50.0, 50.0);
let brush = Brush::Solid(Color::rgba8(0xff, 0xff, 0x00, 0x80)); let brush = Brush::Solid(Color::rgba8(0xff, 0xff, 0x00, 0x80));
builder.fill(Fill::NonZero, transform, &brush, None, &path); 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 scene
} }