Progress on wiring up fills

Write the right_edge to the binning output.

More work on encoding the fill/stroke distinction and plumbing that
through the pipeline. This is a bit unsatisfying because of the code
duplication; having an extra fill/stroke bool might be better, but I
want to avoid making the structs bigger (this could be solved by
better packing in the struct encoding).

Fills are plumbed through to the last stage. Backdrop is WIP.
This commit is contained in:
Raph Levien 2020-05-20 07:38:52 -07:00
parent 03da52cff8
commit 076e6d600d
14 changed files with 175 additions and 67 deletions

View file

@ -3,7 +3,14 @@ use piet_gpu_derive::piet_gpu;
piet_gpu! {
#[gpu_write]
mod annotated {
struct AnnoLineSeg {
struct AnnoFillLineSeg {
p0: [f32; 2],
p1: [f32; 2],
// A note: the layout of this struct is shared with
// AnnoStrokeLineSeg. In that case, we actually write
// [0.0, 0.0] as the stroke field, to minimize divergence.
}
struct AnnoStrokeLineSeg {
p0: [f32; 2],
p1: [f32; 2],
// halfwidth in both x and y for binning
@ -35,8 +42,8 @@ piet_gpu! {
}
enum Annotated {
Nop,
// The segments need a flag to indicate fill/stroke
Line(AnnoLineSeg),
FillLine(AnnoFillLineSeg),
StrokeLine(AnnoStrokeLineSeg),
Quad(AnnoQuadSeg),
Cubic(AnnoCubicSeg),
Stroke(AnnoStroke),

View file

@ -7,6 +7,9 @@ piet_gpu! {
mod bins {
struct BinInstance {
element_ix: u32,
// Right edge of the bounding box of the associated fill
// element; used in backdrop computation.
right_edge: f32,
}
struct BinChunk {

View file

@ -85,8 +85,15 @@ piet_gpu! {
}
enum Element {
Nop,
// The segments need a flag to indicate fill/stroke
Line(LineSeg),
// Another approach to encoding would be to use a single
// variant but have a bool for fill/stroke. This could be
// packed into the tag, so the on-the-wire representation
// would be very similar to what's here.
StrokeLine(LineSeg),
FillLine(LineSeg),
// Note: we'll need to handle the stroke/fill distinction
// for these as well, when we do flattening on the GPU.
Quad(QuadSeg),
Cubic(CubicSeg),
Stroke(Stroke),

View file

@ -1,6 +1,10 @@
// Code auto-generated by piet-gpu-derive
struct AnnoLineSegRef {
struct AnnoFillLineSegRef {
uint offset;
};
struct AnnoStrokeLineSegRef {
uint offset;
};
@ -24,16 +28,27 @@ struct AnnotatedRef {
uint offset;
};
struct AnnoLineSeg {
struct AnnoFillLineSeg {
vec2 p0;
vec2 p1;
};
#define AnnoFillLineSeg_size 16
AnnoFillLineSegRef AnnoFillLineSeg_index(AnnoFillLineSegRef ref, uint index) {
return AnnoFillLineSegRef(ref.offset + index * AnnoFillLineSeg_size);
}
struct AnnoStrokeLineSeg {
vec2 p0;
vec2 p1;
vec2 stroke;
};
#define AnnoLineSeg_size 24
#define AnnoStrokeLineSeg_size 24
AnnoLineSegRef AnnoLineSeg_index(AnnoLineSegRef ref, uint index) {
return AnnoLineSegRef(ref.offset + index * AnnoLineSeg_size);
AnnoStrokeLineSegRef AnnoStrokeLineSeg_index(AnnoStrokeLineSegRef ref, uint index) {
return AnnoStrokeLineSegRef(ref.offset + index * AnnoStrokeLineSeg_size);
}
struct AnnoQuadSeg {
@ -87,18 +102,39 @@ AnnoStrokeRef AnnoStroke_index(AnnoStrokeRef ref, uint index) {
}
#define Annotated_Nop 0
#define Annotated_Line 1
#define Annotated_Quad 2
#define Annotated_Cubic 3
#define Annotated_Stroke 4
#define Annotated_Fill 5
#define Annotated_FillLine 1
#define Annotated_StrokeLine 2
#define Annotated_Quad 3
#define Annotated_Cubic 4
#define Annotated_Stroke 5
#define Annotated_Fill 6
#define Annotated_size 44
AnnotatedRef Annotated_index(AnnotatedRef ref, uint index) {
return AnnotatedRef(ref.offset + index * Annotated_size);
}
AnnoLineSeg AnnoLineSeg_read(AnnoLineSegRef ref) {
AnnoFillLineSeg AnnoFillLineSeg_read(AnnoFillLineSegRef ref) {
uint ix = ref.offset >> 2;
uint raw0 = annotated[ix + 0];
uint raw1 = annotated[ix + 1];
uint raw2 = annotated[ix + 2];
uint raw3 = annotated[ix + 3];
AnnoFillLineSeg s;
s.p0 = vec2(uintBitsToFloat(raw0), uintBitsToFloat(raw1));
s.p1 = vec2(uintBitsToFloat(raw2), uintBitsToFloat(raw3));
return s;
}
void AnnoFillLineSeg_write(AnnoFillLineSegRef ref, AnnoFillLineSeg s) {
uint ix = ref.offset >> 2;
annotated[ix + 0] = floatBitsToUint(s.p0.x);
annotated[ix + 1] = floatBitsToUint(s.p0.y);
annotated[ix + 2] = floatBitsToUint(s.p1.x);
annotated[ix + 3] = floatBitsToUint(s.p1.y);
}
AnnoStrokeLineSeg AnnoStrokeLineSeg_read(AnnoStrokeLineSegRef ref) {
uint ix = ref.offset >> 2;
uint raw0 = annotated[ix + 0];
uint raw1 = annotated[ix + 1];
@ -106,14 +142,14 @@ AnnoLineSeg AnnoLineSeg_read(AnnoLineSegRef ref) {
uint raw3 = annotated[ix + 3];
uint raw4 = annotated[ix + 4];
uint raw5 = annotated[ix + 5];
AnnoLineSeg s;
AnnoStrokeLineSeg s;
s.p0 = vec2(uintBitsToFloat(raw0), uintBitsToFloat(raw1));
s.p1 = vec2(uintBitsToFloat(raw2), uintBitsToFloat(raw3));
s.stroke = vec2(uintBitsToFloat(raw4), uintBitsToFloat(raw5));
return s;
}
void AnnoLineSeg_write(AnnoLineSegRef ref, AnnoLineSeg s) {
void AnnoStrokeLineSeg_write(AnnoStrokeLineSegRef ref, AnnoStrokeLineSeg s) {
uint ix = ref.offset >> 2;
annotated[ix + 0] = floatBitsToUint(s.p0.x);
annotated[ix + 1] = floatBitsToUint(s.p0.y);
@ -239,8 +275,12 @@ uint Annotated_tag(AnnotatedRef ref) {
return annotated[ref.offset >> 2];
}
AnnoLineSeg Annotated_Line_read(AnnotatedRef ref) {
return AnnoLineSeg_read(AnnoLineSegRef(ref.offset + 4));
AnnoFillLineSeg Annotated_FillLine_read(AnnotatedRef ref) {
return AnnoFillLineSeg_read(AnnoFillLineSegRef(ref.offset + 4));
}
AnnoStrokeLineSeg Annotated_StrokeLine_read(AnnotatedRef ref) {
return AnnoStrokeLineSeg_read(AnnoStrokeLineSegRef(ref.offset + 4));
}
AnnoQuadSeg Annotated_Quad_read(AnnotatedRef ref) {
@ -263,9 +303,14 @@ void Annotated_Nop_write(AnnotatedRef ref) {
annotated[ref.offset >> 2] = Annotated_Nop;
}
void Annotated_Line_write(AnnotatedRef ref, AnnoLineSeg s) {
annotated[ref.offset >> 2] = Annotated_Line;
AnnoLineSeg_write(AnnoLineSegRef(ref.offset + 4), s);
void Annotated_FillLine_write(AnnotatedRef ref, AnnoFillLineSeg s) {
annotated[ref.offset >> 2] = Annotated_FillLine;
AnnoFillLineSeg_write(AnnoFillLineSegRef(ref.offset + 4), s);
}
void Annotated_StrokeLine_write(AnnotatedRef ref, AnnoStrokeLineSeg s) {
annotated[ref.offset >> 2] = Annotated_StrokeLine;
AnnoStrokeLineSeg_write(AnnoStrokeLineSegRef(ref.offset + 4), s);
}
void Annotated_Quad_write(AnnotatedRef ref, AnnoQuadSeg s) {

View file

@ -84,8 +84,9 @@ void main() {
int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
float my_right_edge = INFINITY;
switch (tag) {
case Annotated_Line:
AnnoLineSeg line = Annotated_Line_read(ref);
case Annotated_FillLine:
case Annotated_StrokeLine:
AnnoStrokeLineSeg line = Annotated_StrokeLine_read(ref);
x0 = int(floor((min(line.p0.x, line.p1.x) - line.stroke.x) * SX));
y0 = int(floor((min(line.p0.y, line.p1.y) - line.stroke.y) * SY));
x1 = int(ceil((max(line.p0.x, line.p1.x) + line.stroke.x) * SX));
@ -107,7 +108,7 @@ void main() {
// If the last element in this partition is a fill edge, then we need to do a
// look-forward to find the right edge of its corresponding fill. That data is
// recorded in aggregates computed in the element processing pass.
if (gl_LocalInvocationID.x == N_TILE - 1 && tag == Annotated_Line) {
if (gl_LocalInvocationID.x == N_TILE - 1 && tag == Annotated_FillLine) {
uint aggregate_ix = (my_tile + 1) * ELEMENT_BINNING_RATIO;
// This is sequential but the expectation is that the amount of
// look-forward is small (performance may degrade in the case
@ -165,9 +166,9 @@ void main() {
uint chunk_new_start;
// Refactor to reduce code duplication?
if (chunk_n > 0) {
uint next_chunk = chunk_ref.offset + BinChunk_size + chunk_n * 4;
if (next_chunk + BinChunk_size + min(24, element_count * 4) > wr_limit) {
uint alloc_amount = max(BIN_ALLOC, BinChunk_size + element_count * 4);
uint next_chunk = chunk_ref.offset + BinChunk_size + chunk_n * BinInstance_size;
if (next_chunk + BinChunk_size + min(24, element_count * BinInstance_size) > wr_limit) {
uint alloc_amount = max(BIN_ALLOC, BinChunk_size + element_count * BinInstance_size);
// could try to reduce fragmentation if BIN_ALLOC is only a bit above needed
next_chunk = atomicAdd(alloc, alloc_amount);
wr_limit = next_chunk + alloc_amount;
@ -176,10 +177,10 @@ void main() {
chunk_ref = BinChunkRef(next_chunk);
}
BinInstanceRef instance_ref = BinInstanceRef(chunk_ref.offset + BinChunk_size);
if (instance_ref.offset + element_count * 4 > wr_limit) {
if (instance_ref.offset + element_count * BinInstance_size > wr_limit) {
chunk_end = wr_limit;
chunk_n = (wr_limit - instance_ref.offset) / 4;
uint alloc_amount = max(BIN_ALLOC, BinChunk_size + (element_count - chunk_n) * 4);
chunk_n = (wr_limit - instance_ref.offset) / BinInstance_size;
uint alloc_amount = max(BIN_ALLOC, BinChunk_size + (element_count - chunk_n) * BinInstance_size);
chunk_new_start = atomicAdd(alloc, alloc_amount);
wr_limit = chunk_new_start + alloc_amount;
BinChunk_write(chunk_ref, BinChunk(chunk_n, BinChunkRef(chunk_new_start)));
@ -209,11 +210,11 @@ void main() {
if (my_slice > 0) {
idx += count[my_slice - 1][bin_ix];
}
uint out_offset = sh_chunk_start[bin_ix] + idx * 4;
uint out_offset = sh_chunk_start[bin_ix] + idx * BinInstance_size;
if (out_offset >= sh_chunk_end[bin_ix]) {
out_offset += sh_chunk_jump[bin_ix];
}
BinInstance_write(BinInstanceRef(out_offset), BinInstance(element_ix));
BinInstance_write(BinInstanceRef(out_offset), BinInstance(element_ix, my_right_edge));
}
x++;
if (x == x1) {

Binary file not shown.

View file

@ -10,9 +10,10 @@ struct BinChunkRef {
struct BinInstance {
uint element_ix;
float right_edge;
};
#define BinInstance_size 4
#define BinInstance_size 8
BinInstanceRef BinInstance_index(BinInstanceRef ref, uint index) {
return BinInstanceRef(ref.offset + index * BinInstance_size);
@ -32,14 +33,17 @@ BinChunkRef BinChunk_index(BinChunkRef ref, uint index) {
BinInstance BinInstance_read(BinInstanceRef ref) {
uint ix = ref.offset >> 2;
uint raw0 = bins[ix + 0];
uint raw1 = bins[ix + 1];
BinInstance s;
s.element_ix = raw0;
s.right_edge = uintBitsToFloat(raw1);
return s;
}
void BinInstance_write(BinInstanceRef ref, BinInstance s) {
uint ix = ref.offset >> 2;
bins[ix + 0] = s.element_ix;
bins[ix + 1] = floatBitsToUint(s.right_edge);
}
BinChunk BinChunk_read(BinChunkRef ref) {

View file

@ -30,6 +30,7 @@ layout(set = 0, binding = 3) buffer PtclBuf {
#define N_RINGBUF 512
shared uint sh_elements[N_RINGBUF];
shared float sh_right_edge[N_RINGBUF];
shared uint sh_chunk[N_WG];
shared uint sh_chunk_next[N_WG];
shared uint sh_chunk_n[N_WG];
@ -41,6 +42,7 @@ shared uint sh_selected_n;
shared uint sh_elements_ref;
shared uint sh_bitmaps[N_SLICE][N_TILE];
shared uint sh_backdrop[N_SLICE][N_TILE];
// scale factors useful for converting coordinates to tiles
#define SX (1.0 / float(TILE_WIDTH_PX))
@ -113,6 +115,7 @@ void main() {
while (true) {
for (uint i = 0; i < N_SLICE; i++) {
sh_bitmaps[i][th_ix] = 0;
sh_backdrop[i][th_ix] = 0;
}
while (wr_ix - rd_ix <= N_TILE) {
@ -157,8 +160,10 @@ void main() {
}
BinInstanceRef inst_ref = BinInstanceRef(sh_elements_ref);
if (th_ix < chunk_n) {
uint el = BinInstance_read(BinInstance_index(inst_ref, th_ix)).element_ix;
sh_elements[(wr_ix + th_ix) % N_RINGBUF] = el;
BinInstance inst = BinInstance_read(BinInstance_index(inst_ref, th_ix));
uint wr_el_ix = (wr_ix + th_ix) % N_RINGBUF;
sh_elements[wr_el_ix] = inst.element_ix;
sh_right_edge[wr_el_ix] = inst.right_edge;
}
wr_ix += chunk_n;
}
@ -180,8 +185,9 @@ void main() {
// Bounding box of element in pixel coordinates.
float xmin, xmax, ymin, ymax;
switch (tag) {
case Annotated_Line:
AnnoLineSeg line = Annotated_Line_read(ref);
case Annotated_FillLine:
case Annotated_StrokeLine:
AnnoStrokeLineSeg line = Annotated_StrokeLine_read(ref);
xmin = min(line.p0.x, line.p1.x) - line.stroke.x;
xmax = max(line.p0.x, line.p1.x) + line.stroke.x;
ymin = min(line.p0.y, line.p1.y) - line.stroke.y;
@ -214,7 +220,7 @@ void main() {
break;
}
// Draw the coverage area into the bitmaks. This uses an algorithm
// Draw the coverage area into the bitmasks. This uses an algorithm
// that computes the coverage of a span for given scanline.
// Compute bounding box in tiles and clip to this bin.
@ -263,15 +269,27 @@ void main() {
tag = Annotated_tag(ref);
switch (tag) {
case Annotated_Line:
AnnoLineSeg line = Annotated_Line_read(ref);
case Annotated_FillLine:
case Annotated_StrokeLine:
AnnoStrokeLineSeg line = Annotated_StrokeLine_read(ref);
Segment seg = Segment(line.p0, line.p1);
alloc_chunk(chunk_n_segs, seg_chunk_ref, first_seg_chunk, seg_limit);
Segment_write(SegmentRef(seg_chunk_ref.offset + SegChunk_size + Segment_size * chunk_n_segs), seg);
chunk_n_segs++;
break;
case Annotated_Fill:
chunk_n_segs = 0;
if (chunk_n_segs > 0) {
AnnoFill fill = Annotated_Fill_read(ref);
SegChunk_write(seg_chunk_ref, SegChunk(chunk_n_segs, SegChunkRef(0)));
seg_chunk_ref.offset += SegChunk_size + Segment_size * chunk_n_segs;
CmdFill cmd_fill;
cmd_fill.seg_ref = first_seg_chunk.offset;
cmd_fill.rgba_color = fill.rgba_color;
alloc_cmd(cmd_ref, cmd_limit);
Cmd_Fill_write(cmd_ref, cmd_fill);
cmd_ref.offset += Cmd_size;
chunk_n_segs = 0;
}
break;
case Annotated_Stroke:
if (chunk_n_segs > 0) {

Binary file not shown.

View file

@ -98,8 +98,9 @@ State map_element(ElementRef ref) {
c.linewidth = 1.0; // TODO should be 0.0
c.flags = 0;
switch (tag) {
case Element_Line:
LineSeg line = Element_Line_read(ref);
case Element_FillLine:
case Element_StrokeLine:
LineSeg line = Element_FillLine_read(ref);
c.bbox.xy = min(line.p0, line.p1);
c.bbox.zw = max(line.p0, line.p1);
break;
@ -272,13 +273,22 @@ void main() {
AnnotatedRef out_ref = AnnotatedRef((ix + i) * Annotated_size);
uint tag = Element_tag(this_ref);
switch (tag) {
case Element_Line:
LineSeg line = Element_Line_read(this_ref);
AnnoLineSeg anno_line;
case Element_FillLine:
case Element_StrokeLine:
LineSeg line = Element_StrokeLine_read(this_ref);
AnnoStrokeLineSeg anno_line;
anno_line.p0 = st.mat.xz * line.p0.x + st.mat.yw * line.p0.y + st.translate;
anno_line.p1 = st.mat.xz * line.p1.x + st.mat.yw * line.p1.y + st.translate;
anno_line.stroke = get_linewidth(st);
Annotated_Line_write(out_ref, anno_line);
if (tag == Element_StrokeLine) {
anno_line.stroke = get_linewidth(st);
} else {
anno_line.stroke = vec2(0.0);
}
// We do encoding a bit by hand to minimize divergence. Another approach
// would be to have a fill/stroke bool.
uint out_tag = tag == Element_FillLine ? Annotated_FillLine : Annotated_StrokeLine;
annotated[out_ref.offset >> 2] = out_tag;
AnnoStrokeLineSeg_write(AnnoStrokeLineSegRef(out_ref.offset + 4), anno_line);
break;
case Element_Stroke:
Stroke stroke = Element_Stroke_read(this_ref);

Binary file not shown.

View file

@ -238,13 +238,14 @@ TransformRef Transform_index(TransformRef ref, uint index) {
}
#define Element_Nop 0
#define Element_Line 1
#define Element_Quad 2
#define Element_Cubic 3
#define Element_Stroke 4
#define Element_Fill 5
#define Element_SetLineWidth 6
#define Element_Transform 7
#define Element_StrokeLine 1
#define Element_FillLine 2
#define Element_Quad 3
#define Element_Cubic 4
#define Element_Stroke 5
#define Element_Fill 6
#define Element_SetLineWidth 7
#define Element_Transform 8
#define Element_size 36
ElementRef Element_index(ElementRef ref, uint index) {
@ -446,7 +447,11 @@ uint Element_tag(ElementRef ref) {
return scene[ref.offset >> 2];
}
LineSeg Element_Line_read(ElementRef ref) {
LineSeg Element_StrokeLine_read(ElementRef ref) {
return LineSeg_read(LineSegRef(ref.offset + 4));
}
LineSeg Element_FillLine_read(ElementRef ref) {
return LineSeg_read(LineSegRef(ref.offset + 4));
}

View file

@ -61,8 +61,8 @@ impl PicoSvg {
for item in &self.items {
match item {
Item::Fill(fill_item) => {
//rc.fill(&fill_item.path, &fill_item.color);
rc.stroke(&fill_item.path, &fill_item.color, 1.0);
rc.fill(&fill_item.path, &fill_item.color);
//rc.stroke(&fill_item.path, &fill_item.color, 1.0);
}
Item::Stroke(stroke_item) => {
rc.stroke(&stroke_item.path, &stroke_item.color, stroke_item.width);

View file

@ -94,7 +94,7 @@ impl RenderContext for PietGpuRenderContext {
}
let brush = brush.make_brush(self, || shape.bounding_box()).into_owned();
let path = shape.to_bez_path(TOLERANCE);
self.encode_path(path);
self.encode_path(path, false);
match brush {
PietGpuBrush::Solid(rgba_color) => {
let stroke = Stroke { rgba_color };
@ -116,7 +116,7 @@ impl RenderContext for PietGpuRenderContext {
fn fill(&mut self, shape: impl Shape, brush: &impl IntoBrush<Self>) {
let brush = brush.make_brush(self, || shape.bounding_box()).into_owned();
let path = shape.to_bez_path(TOLERANCE);
self.encode_path(path);
self.encode_path(path, true);
match brush {
PietGpuBrush::Solid(rgba_color) => {
let fill = Fill { rgba_color };
@ -198,7 +198,15 @@ impl RenderContext for PietGpuRenderContext {
}
impl PietGpuRenderContext {
fn encode_path(&mut self, path: impl Iterator<Item = PathEl>) {
fn encode_line_seg(&mut self, seg: LineSeg, is_fill: bool) {
if is_fill {
self.elements.push(Element::FillLine(seg));
} else {
self.elements.push(Element::StrokeLine(seg));
}
}
fn encode_path(&mut self, path: impl Iterator<Item = PathEl>, is_fill: bool) {
let flatten = true;
if flatten {
let mut start_pt = None;
@ -215,7 +223,7 @@ impl PietGpuRenderContext {
p0: last_pt.unwrap(),
p1: scene_pt,
};
self.elements.push(Element::Line(seg));
self.encode_line_seg(seg, is_fill);
last_pt = Some(scene_pt);
}
PathEl::ClosePath => {
@ -224,7 +232,7 @@ impl PietGpuRenderContext {
p0: last,
p1: start,
};
self.elements.push(Element::Line(seg));
self.encode_line_seg(seg, is_fill);
}
}
_ => (),
@ -246,7 +254,7 @@ impl PietGpuRenderContext {
p0: last_pt.unwrap(),
p1: scene_pt,
};
self.elements.push(Element::Line(seg));
self.encode_line_seg(seg, is_fill);
last_pt = Some(scene_pt);
}
PathEl::QuadTo(p1, p2) => {
@ -279,7 +287,7 @@ impl PietGpuRenderContext {
p0: last,
p1: start,
};
self.elements.push(Element::Line(seg));
self.encode_line_seg(seg, is_fill);
}
}
}