Continuing work on clips

I realized there's a problem with encoding clip bboxes relative to the
current transform (see #36 for a more detailed explanation), so this is
changing it to absolute bboxes.

This more or less gets clips working. There are optimization
opportunities (all-clear and all-opaque mask tiles), and it doesn't deal
with overflow of the blend stack, but it seems to basically work.
This commit is contained in:
Raph Levien 2020-11-20 09:26:02 -08:00
parent f53d00e6bc
commit d14895b107
23 changed files with 268 additions and 161 deletions

View file

@ -4,16 +4,18 @@ piet_gpu! {
#[gpu_write] #[gpu_write]
mod annotated { mod annotated {
struct AnnoFill { struct AnnoFill {
rgba_color: u32, // The bbox is always first, as we take advantage of common
// layout when binning.
bbox: [f32; 4], bbox: [f32; 4],
rgba_color: u32,
} }
struct AnnoFillMask { struct AnnoFillMask {
mask: f32,
bbox: [f32; 4], bbox: [f32; 4],
mask: f32,
} }
struct AnnoStroke { struct AnnoStroke {
rgba_color: u32,
bbox: [f32; 4], bbox: [f32; 4],
rgba_color: u32,
// For the nonuniform scale case, this needs to be a 2x2 matrix. // For the nonuniform scale case, this needs to be a 2x2 matrix.
// That's expected to be uncommon, so we could special-case it. // That's expected to be uncommon, so we could special-case it.
linewidth: f32, linewidth: f32,

View file

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

View file

@ -34,6 +34,11 @@ piet_gpu! {
tile_ref: u32, tile_ref: u32,
backdrop: i32, backdrop: i32,
} }
// This is mostly here for expedience and can always be optimized
// out for pure clips, but will be useful for blend groups.
struct CmdBeginSolidClip {
alpha: f32,
}
struct CmdEndClip { struct CmdEndClip {
// This will be 1.0 for clips, but we can imagine blend groups. // This will be 1.0 for clips, but we can imagine blend groups.
alpha: f32, alpha: f32,
@ -55,6 +60,7 @@ piet_gpu! {
FillMask(CmdFillMask), FillMask(CmdFillMask),
FillMaskInv(CmdFillMask), FillMaskInv(CmdFillMask),
BeginClip(CmdBeginClip), BeginClip(CmdBeginClip),
BeginSolidClip(CmdBeginSolidClip),
EndClip(CmdEndClip), EndClip(CmdEndClip),
Stroke(CmdStroke), Stroke(CmdStroke),
Solid(CmdSolid), Solid(CmdSolid),

View file

@ -1,7 +1,7 @@
use piet_gpu_derive::piet_gpu; use piet_gpu_derive::piet_gpu;
pub use self::scene::{ pub use self::scene::{
BeginClip, CubicSeg, Element, EndClip, Fill, LineSeg, QuadSeg, SetLineWidth, Stroke, Transform, Clip, CubicSeg, Element, Fill, LineSeg, QuadSeg, SetLineWidth, Stroke, Transform,
}; };
piet_gpu! { piet_gpu! {
@ -38,15 +38,10 @@ piet_gpu! {
mat: [f32; 4], mat: [f32; 4],
translate: [f32; 2], translate: [f32; 2],
} }
struct BeginClip { struct Clip {
bbox: [f32; 4], bbox: [f32; 4],
// TODO: add alpha? // TODO: add alpha?
} }
struct EndClip {
// The delta between the BeginClip and EndClip element indices.
// It is stored as a delta to facilitate binary string concatenation.
delta: u32,
}
enum Element { enum Element {
Nop, Nop,
// Another approach to encoding would be to use a single // Another approach to encoding would be to use a single
@ -66,8 +61,8 @@ piet_gpu! {
Transform(Transform), Transform(Transform),
FillMask(FillMask), FillMask(FillMask),
FillMaskInv(FillMask), FillMaskInv(FillMask),
BeginClip(BeginClip), BeginClip(Clip),
EndClip(EndClip), EndClip(Clip),
} }
} }
} }

View file

@ -165,6 +165,38 @@ fn trace_ptcl(buf: &[u32]) {
} }
} }
} }
6 => {
let backdrop = buf[tile_offset / 4 + 2];
println!(" {:x}: begin_clip {}", tile_offset, backdrop);
let mut seg_chunk = buf[tile_offset / 4 + 1] as usize;
let n = buf[seg_chunk / 4] as usize;
let segs = buf[seg_chunk / 4 + 2] as usize;
println!(" chunk @{:x}: n={}, segs @{:x}", seg_chunk, n, segs);
for i in 0..n {
let x0 = f32::from_bits(buf[segs / 4 + i * 5]);
let y0 = f32::from_bits(buf[segs / 4 + i * 5 + 1]);
let x1 = f32::from_bits(buf[segs / 4 + i * 5 + 2]);
let y1 = f32::from_bits(buf[segs / 4 + i * 5 + 3]);
let y_edge = f32::from_bits(buf[segs / 4 + i * 5 + 4]);
println!(
" ({:.3}, {:.3}) - ({:.3}, {:.3}) | {:.3}",
x0, y0, x1, y1, y_edge
);
}
loop {
seg_chunk = buf[seg_chunk / 4 + 1] as usize;
if seg_chunk == 0 {
break;
}
}
}
7 => {
let backdrop = buf[tile_offset / 4 + 1];
println!("{:x}: solid_clip {:x}", tile_offset, backdrop);
}
8 => {
println!("{:x}: end_clip", tile_offset);
}
_ => { _ => {
println!("{:x}: {}", tile_offset, tag); println!("{:x}: {}", tile_offset, tag);
} }
@ -246,9 +278,9 @@ fn main() -> Result<(), Error> {
/* /*
let mut data: Vec<u32> = Default::default(); let mut data: Vec<u32> = Default::default();
device.read_buffer(&renderer.tile_buf, &mut data).unwrap(); renderer.tile_buf.read(&mut data).unwrap();
piet_gpu::dump_k1_data(&data); piet_gpu::dump_k1_data(&data);
//trace_ptcl(&data); trace_ptcl(&data);
*/ */
let mut img_data: Vec<u8> = Default::default(); let mut img_data: Vec<u8> = Default::default();

View file

@ -21,8 +21,8 @@ struct AnnotatedRef {
}; };
struct AnnoFill { struct AnnoFill {
uint rgba_color;
vec4 bbox; vec4 bbox;
uint rgba_color;
}; };
#define AnnoFill_size 20 #define AnnoFill_size 20
@ -32,8 +32,8 @@ AnnoFillRef AnnoFill_index(AnnoFillRef ref, uint index) {
} }
struct AnnoFillMask { struct AnnoFillMask {
float mask;
vec4 bbox; vec4 bbox;
float mask;
}; };
#define AnnoFillMask_size 20 #define AnnoFillMask_size 20
@ -43,8 +43,8 @@ AnnoFillMaskRef AnnoFillMask_index(AnnoFillMaskRef ref, uint index) {
} }
struct AnnoStroke { struct AnnoStroke {
uint rgba_color;
vec4 bbox; vec4 bbox;
uint rgba_color;
float linewidth; float linewidth;
}; };
@ -85,18 +85,18 @@ AnnoFill AnnoFill_read(AnnoFillRef ref) {
uint raw3 = annotated[ix + 3]; uint raw3 = annotated[ix + 3];
uint raw4 = annotated[ix + 4]; uint raw4 = annotated[ix + 4];
AnnoFill s; AnnoFill s;
s.rgba_color = raw0; s.bbox = vec4(uintBitsToFloat(raw0), uintBitsToFloat(raw1), uintBitsToFloat(raw2), uintBitsToFloat(raw3));
s.bbox = vec4(uintBitsToFloat(raw1), uintBitsToFloat(raw2), uintBitsToFloat(raw3), uintBitsToFloat(raw4)); s.rgba_color = raw4;
return s; return s;
} }
void AnnoFill_write(AnnoFillRef ref, AnnoFill s) { void AnnoFill_write(AnnoFillRef ref, AnnoFill s) {
uint ix = ref.offset >> 2; uint ix = ref.offset >> 2;
annotated[ix + 0] = s.rgba_color; annotated[ix + 0] = floatBitsToUint(s.bbox.x);
annotated[ix + 1] = floatBitsToUint(s.bbox.x); annotated[ix + 1] = floatBitsToUint(s.bbox.y);
annotated[ix + 2] = floatBitsToUint(s.bbox.y); annotated[ix + 2] = floatBitsToUint(s.bbox.z);
annotated[ix + 3] = floatBitsToUint(s.bbox.z); annotated[ix + 3] = floatBitsToUint(s.bbox.w);
annotated[ix + 4] = floatBitsToUint(s.bbox.w); annotated[ix + 4] = s.rgba_color;
} }
AnnoFillMask AnnoFillMask_read(AnnoFillMaskRef ref) { AnnoFillMask AnnoFillMask_read(AnnoFillMaskRef ref) {
@ -107,18 +107,18 @@ AnnoFillMask AnnoFillMask_read(AnnoFillMaskRef ref) {
uint raw3 = annotated[ix + 3]; uint raw3 = annotated[ix + 3];
uint raw4 = annotated[ix + 4]; uint raw4 = annotated[ix + 4];
AnnoFillMask s; AnnoFillMask s;
s.mask = uintBitsToFloat(raw0); s.bbox = vec4(uintBitsToFloat(raw0), uintBitsToFloat(raw1), uintBitsToFloat(raw2), uintBitsToFloat(raw3));
s.bbox = vec4(uintBitsToFloat(raw1), uintBitsToFloat(raw2), uintBitsToFloat(raw3), uintBitsToFloat(raw4)); s.mask = uintBitsToFloat(raw4);
return s; return s;
} }
void AnnoFillMask_write(AnnoFillMaskRef ref, AnnoFillMask s) { void AnnoFillMask_write(AnnoFillMaskRef ref, AnnoFillMask s) {
uint ix = ref.offset >> 2; uint ix = ref.offset >> 2;
annotated[ix + 0] = floatBitsToUint(s.mask); annotated[ix + 0] = floatBitsToUint(s.bbox.x);
annotated[ix + 1] = floatBitsToUint(s.bbox.x); annotated[ix + 1] = floatBitsToUint(s.bbox.y);
annotated[ix + 2] = floatBitsToUint(s.bbox.y); annotated[ix + 2] = floatBitsToUint(s.bbox.z);
annotated[ix + 3] = floatBitsToUint(s.bbox.z); annotated[ix + 3] = floatBitsToUint(s.bbox.w);
annotated[ix + 4] = floatBitsToUint(s.bbox.w); annotated[ix + 4] = floatBitsToUint(s.mask);
} }
AnnoStroke AnnoStroke_read(AnnoStrokeRef ref) { AnnoStroke AnnoStroke_read(AnnoStrokeRef ref) {
@ -130,19 +130,19 @@ AnnoStroke AnnoStroke_read(AnnoStrokeRef ref) {
uint raw4 = annotated[ix + 4]; uint raw4 = annotated[ix + 4];
uint raw5 = annotated[ix + 5]; uint raw5 = annotated[ix + 5];
AnnoStroke s; AnnoStroke s;
s.rgba_color = raw0; s.bbox = vec4(uintBitsToFloat(raw0), uintBitsToFloat(raw1), uintBitsToFloat(raw2), uintBitsToFloat(raw3));
s.bbox = vec4(uintBitsToFloat(raw1), uintBitsToFloat(raw2), uintBitsToFloat(raw3), uintBitsToFloat(raw4)); s.rgba_color = raw4;
s.linewidth = uintBitsToFloat(raw5); s.linewidth = uintBitsToFloat(raw5);
return s; return s;
} }
void AnnoStroke_write(AnnoStrokeRef ref, AnnoStroke s) { void AnnoStroke_write(AnnoStrokeRef ref, AnnoStroke s) {
uint ix = ref.offset >> 2; uint ix = ref.offset >> 2;
annotated[ix + 0] = s.rgba_color; annotated[ix + 0] = floatBitsToUint(s.bbox.x);
annotated[ix + 1] = floatBitsToUint(s.bbox.x); annotated[ix + 1] = floatBitsToUint(s.bbox.y);
annotated[ix + 2] = floatBitsToUint(s.bbox.y); annotated[ix + 2] = floatBitsToUint(s.bbox.z);
annotated[ix + 3] = floatBitsToUint(s.bbox.z); annotated[ix + 3] = floatBitsToUint(s.bbox.w);
annotated[ix + 4] = floatBitsToUint(s.bbox.w); annotated[ix + 4] = s.rgba_color;
annotated[ix + 5] = floatBitsToUint(s.linewidth); annotated[ix + 5] = floatBitsToUint(s.linewidth);
} }

View file

@ -57,6 +57,7 @@ void main() {
case Annotated_Fill: case Annotated_Fill:
case Annotated_FillMask: case Annotated_FillMask:
case Annotated_FillMaskInv: case Annotated_FillMaskInv:
case Annotated_BeginClip:
PathRef path_ref = PathRef(element_ix * Path_size); PathRef path_ref = PathRef(element_ix * Path_size);
Path path = Path_read(path_ref); Path path = Path_read(path_ref);
sh_row_width[th_ix] = path.bbox.z - path.bbox.x; sh_row_width[th_ix] = path.bbox.z - path.bbox.x;

Binary file not shown.

View file

@ -57,22 +57,20 @@ void main() {
tag = Annotated_tag(ref); tag = Annotated_tag(ref);
} }
int x0 = 0, y0 = 0, x1 = 0, y1 = 0; int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
float my_right_edge = INFINITY;
switch (tag) { switch (tag) {
case Annotated_Fill: case Annotated_Fill:
case Annotated_FillMask: case Annotated_FillMask:
case Annotated_FillMaskInv: case Annotated_FillMaskInv:
case Annotated_Stroke: case Annotated_Stroke:
// Note: we take advantage of the fact that fills and strokes case Annotated_BeginClip:
// have compatible layout. case Annotated_EndClip:
// Note: we take advantage of the fact that these drawing elements
// have the bbox at the same place in their layout.
AnnoFill fill = Annotated_Fill_read(ref); AnnoFill fill = Annotated_Fill_read(ref);
x0 = int(floor(fill.bbox.x * SX)); x0 = int(floor(fill.bbox.x * SX));
y0 = int(floor(fill.bbox.y * SY)); y0 = int(floor(fill.bbox.y * SY));
x1 = int(ceil(fill.bbox.z * SX)); x1 = int(ceil(fill.bbox.z * SX));
y1 = int(ceil(fill.bbox.w * SY)); y1 = int(ceil(fill.bbox.w * SY));
// It probably makes more sense to track x1, to avoid having to redo
// the rounding to tile coords.
my_right_edge = fill.bbox.z;
break; break;
} }
@ -131,7 +129,7 @@ void main() {
idx += count[my_slice - 1][bin_ix]; idx += count[my_slice - 1][bin_ix];
} }
uint out_offset = sh_chunk_start[bin_ix] + idx * BinInstance_size; uint out_offset = sh_chunk_start[bin_ix] + idx * BinInstance_size;
BinInstance_write(BinInstanceRef(out_offset), BinInstance(element_ix, my_right_edge)); BinInstance_write(BinInstanceRef(out_offset), BinInstance(element_ix));
} }
x++; x++;
if (x == x1) { if (x == x1) {

Binary file not shown.

View file

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

View file

@ -45,7 +45,6 @@ layout(set = 0, binding = 4) buffer PtclBuf {
#define N_PART_READ (1 << LG_N_PART_READ) #define N_PART_READ (1 << LG_N_PART_READ)
shared uint sh_elements[N_TILE]; shared uint sh_elements[N_TILE];
shared float sh_right_edge[N_TILE];
// Number of elements in the partition; prefix sum. // Number of elements in the partition; prefix sum.
shared uint sh_part_count[N_PART_READ]; shared uint sh_part_count[N_PART_READ];
@ -148,7 +147,6 @@ void main() {
BinInstanceRef inst_ref = BinInstanceRef(sh_part_elements[part_ix]); BinInstanceRef inst_ref = BinInstanceRef(sh_part_elements[part_ix]);
BinInstance inst = BinInstance_read(BinInstance_index(inst_ref, ix)); BinInstance inst = BinInstance_read(BinInstance_index(inst_ref, ix));
sh_elements[th_ix] = inst.element_ix; sh_elements[th_ix] = inst.element_ix;
sh_right_edge[th_ix] = inst.right_edge;
} }
barrier(); barrier();
@ -161,10 +159,8 @@ void main() {
uint tag = Annotated_Nop; uint tag = Annotated_Nop;
uint element_ix; uint element_ix;
AnnotatedRef ref; AnnotatedRef ref;
float right_edge = 0.0;
if (th_ix + rd_ix < wr_ix) { if (th_ix + rd_ix < wr_ix) {
element_ix = sh_elements[th_ix]; element_ix = sh_elements[th_ix];
right_edge = sh_right_edge[th_ix];
ref = AnnotatedRef(element_ix * Annotated_size); ref = AnnotatedRef(element_ix * Annotated_size);
tag = Annotated_tag(ref); tag = Annotated_tag(ref);
} }
@ -173,13 +169,11 @@ void main() {
uint tile_count; uint tile_count;
switch (tag) { switch (tag) {
case Annotated_Fill: case Annotated_Fill:
case Annotated_FillMask:
case Annotated_FillMaskInv:
case Annotated_Stroke: case Annotated_Stroke:
// Because the only elements we're processing right now are case Annotated_BeginClip:
// paths, we can just use the element index as the path index. case Annotated_EndClip:
// In future, when we're doing a bunch of stuff, the path index // We have one "path" for each element, even if the element isn't
// should probably be stored in the annotated element. // actually a path (currently EndClip, but images etc in the future).
uint path_ix = element_ix; uint path_ix = element_ix;
Path path = Path_read(PathRef(path_ix * Path_size)); Path path = Path_read(PathRef(path_ix * Path_size));
uint stride = path.bbox.z - path.bbox.x; uint stride = path.bbox.z - path.bbox.x;
@ -224,20 +218,23 @@ void main() {
el_ix = probe; el_ix = probe;
} }
} }
AnnotatedRef ref = AnnotatedRef(el_ix * Annotated_size); AnnotatedRef ref = AnnotatedRef(sh_elements[el_ix] * Annotated_size);
uint tag = Annotated_tag(ref); uint tag = Annotated_tag(ref);
uint seq_ix = ix - (el_ix > 0 ? sh_tile_count[el_ix - 1] : 0); uint seq_ix = ix - (el_ix > 0 ? sh_tile_count[el_ix - 1] : 0);
uint width = sh_tile_width[el_ix]; uint width = sh_tile_width[el_ix];
uint x = sh_tile_x0[el_ix] + seq_ix % width; uint x = sh_tile_x0[el_ix] + seq_ix % width;
uint y = sh_tile_y0[el_ix] + seq_ix / width; uint y = sh_tile_y0[el_ix] + seq_ix / width;
Tile tile = Tile_read(TileRef(sh_tile_base[el_ix] + (sh_tile_stride[el_ix] * y + x) * Tile_size)); bool include_tile;
// Include the path in the tile if if (tag == Annotated_BeginClip || tag == Annotated_EndClip) {
// - the tile contains at least a segment (tile offset non-zero) include_tile = true;
// - the tile is completely covered (backdrop non-zero) } else {
// - the tile is not covered and we're filling everything outside the path (backdrop zero, inverse fills). Tile tile = Tile_read(TileRef(sh_tile_base[el_ix] + (sh_tile_stride[el_ix] * y + x) * Tile_size));
bool inside = tile.backdrop != 0; // Include the path in the tile if
bool fill = tag != Annotated_FillMaskInv; // - the tile contains at least a segment (tile offset non-zero)
if (tile.tile.offset != 0 || inside == fill) { // - the tile is completely covered (backdrop non-zero)
include_tile = tile.backdrop != 0 || tile.tile.offset != 0;
}
if (include_tile) {
uint el_slice = el_ix / 32; uint el_slice = el_ix / 32;
uint el_mask = 1 << (el_ix & 31); uint el_mask = 1 << (el_ix & 31);
atomicOr(sh_bitmaps[el_slice][y * N_TILE_X + x], el_mask); atomicOr(sh_bitmaps[el_slice][y * N_TILE_X + x], el_mask);
@ -247,8 +244,7 @@ void main() {
barrier(); barrier();
// Output non-segment elements for this tile. The thread does a sequential walk // Output non-segment elements for this tile. The thread does a sequential walk
// through the non-segment elements, and for segments, count and backdrop are // through the non-segment elements.
// aggregated using bit counting.
uint slice_ix = 0; uint slice_ix = 0;
uint bitmap = sh_bitmaps[0][th_ix]; uint bitmap = sh_bitmaps[0][th_ix];
while (true) { while (true) {
@ -291,27 +287,27 @@ void main() {
} }
cmd_ref.offset += Cmd_size; cmd_ref.offset += Cmd_size;
break; break;
case Annotated_FillMask: case Annotated_BeginClip:
case Annotated_FillMaskInv:
tile = Tile_read(TileRef(sh_tile_base[element_ref_ix] tile = Tile_read(TileRef(sh_tile_base[element_ref_ix]
+ (sh_tile_stride[element_ref_ix] * tile_y + tile_x) * Tile_size)); + (sh_tile_stride[element_ref_ix] * tile_y + tile_x) * Tile_size));
AnnoFillMask fill_mask = Annotated_FillMask_read(ref);
alloc_cmd(cmd_ref, cmd_limit); alloc_cmd(cmd_ref, cmd_limit);
if (tile.tile.offset != 0) { if (tile.tile.offset != 0) {
CmdFillMask cmd_fill; CmdBeginClip cmd_begin_clip;
cmd_fill.tile_ref = tile.tile.offset; cmd_begin_clip.tile_ref = tile.tile.offset;
cmd_fill.backdrop = tile.backdrop; cmd_begin_clip.backdrop = tile.backdrop;
cmd_fill.mask = fill_mask.mask; Cmd_BeginClip_write(cmd_ref, cmd_begin_clip);
if (tag == Annotated_FillMask) {
Cmd_FillMask_write(cmd_ref, cmd_fill);
} else {
Cmd_FillMaskInv_write(cmd_ref, cmd_fill);
}
} else { } else {
Cmd_SolidMask_write(cmd_ref, CmdSolidMask(fill_mask.mask)); // TODO: here is where a bunch of optimization magic should happen
float alpha = tile.backdrop == 0 ? 0.0 : 1.0;
Cmd_BeginSolidClip_write(cmd_ref, CmdBeginSolidClip(alpha));
} }
cmd_ref.offset += Cmd_size; cmd_ref.offset += Cmd_size;
break; break;
case Annotated_EndClip:
alloc_cmd(cmd_ref, cmd_limit);
Cmd_EndClip_write(cmd_ref, CmdEndClip(1.0));
cmd_ref.offset += Cmd_size;
break;
case Annotated_Stroke: case Annotated_Stroke:
tile = Tile_read(TileRef(sh_tile_base[element_ref_ix] tile = Tile_read(TileRef(sh_tile_base[element_ref_ix]
+ (sh_tile_stride[element_ref_ix] * tile_y + tile_x) * Tile_size)); + (sh_tile_stride[element_ref_ix] * tile_y + tile_x) * Tile_size));

Binary file not shown.

View file

@ -132,9 +132,13 @@ State map_element(ElementRef ref) {
case Element_FillMask: case Element_FillMask:
case Element_FillMaskInv: case Element_FillMaskInv:
case Element_Stroke: case Element_Stroke:
case Element_BeginClip:
c.flags = FLAG_RESET_BBOX; c.flags = FLAG_RESET_BBOX;
c.path_count = 1; c.path_count = 1;
break; break;
case Element_EndClip:
c.path_count = 1;
break;
case Element_SetLineWidth: case Element_SetLineWidth:
SetLineWidth lw = Element_SetLineWidth_read(ref); SetLineWidth lw = Element_SetLineWidth_read(ref);
c.linewidth = lw.width; c.linewidth = lw.width;
@ -421,6 +425,21 @@ void main() {
out_ref = AnnotatedRef((st.path_count - 1) * Annotated_size); out_ref = AnnotatedRef((st.path_count - 1) * Annotated_size);
Annotated_FillMaskInv_write(out_ref, anno_fill_mask); Annotated_FillMaskInv_write(out_ref, anno_fill_mask);
break; break;
case Element_BeginClip:
Clip begin_clip = Element_BeginClip_read(this_ref);
AnnoClip anno_begin_clip = AnnoClip(begin_clip.bbox);
// This is the absolute bbox, it's been transformed during encoding.
anno_begin_clip.bbox = begin_clip.bbox;
out_ref = AnnotatedRef((st.path_count - 1) * Annotated_size);
Annotated_BeginClip_write(out_ref, anno_begin_clip);
break;
case Element_EndClip:
Clip end_clip = Element_EndClip_read(this_ref);
// This bbox is expected to be the same as the begin one.
AnnoClip anno_end_clip = AnnoClip(end_clip.bbox);
out_ref = AnnotatedRef((st.path_count - 1) * Annotated_size);
Annotated_EndClip_write(out_ref, anno_end_clip);
break;
} }
} }
} }

Binary file not shown.

View file

@ -149,12 +149,20 @@ void main() {
} }
blend_sp++; blend_sp++;
break; break;
case Cmd_BeginSolidClip:
CmdBeginSolidClip begin_solid_clip = Cmd_BeginSolidClip_read(cmd_ref);
float solid_alpha = begin_solid_clip.alpha;
for (uint k = 0; k < CHUNK; k++) {
blend_stack[blend_sp][k] = packUnorm4x8(vec4(rgb[k], solid_alpha));
}
blend_sp++;
break;
case Cmd_EndClip: case Cmd_EndClip:
CmdEndClip end_clip = Cmd_EndClip_read(cmd_ref); CmdEndClip end_clip = Cmd_EndClip_read(cmd_ref);
blend_sp--; blend_sp--;
for (uint k = 0; k < CHUNK; k++) { for (uint k = 0; k < CHUNK; k++) {
vec4 rgba = unpackUnorm4x8(blend_stack[blend_sp][k]); vec4 rgba = unpackUnorm4x8(blend_stack[blend_sp][k]);
rgb[k] = mix(rgb[k], rgba.rgb, end_clip.alpha * rgba.a); rgb[k] = mix(rgba.rgb, rgb[k], end_clip.alpha * rgba.a);
} }
break; break;
case Cmd_Solid: case Cmd_Solid:

Binary file not shown.

View file

@ -24,6 +24,10 @@ struct CmdBeginClipRef {
uint offset; uint offset;
}; };
struct CmdBeginSolidClipRef {
uint offset;
};
struct CmdEndClipRef { struct CmdEndClipRef {
uint offset; uint offset;
}; };
@ -122,6 +126,16 @@ CmdBeginClipRef CmdBeginClip_index(CmdBeginClipRef ref, uint index) {
return CmdBeginClipRef(ref.offset + index * CmdBeginClip_size); return CmdBeginClipRef(ref.offset + index * CmdBeginClip_size);
} }
struct CmdBeginSolidClip {
float alpha;
};
#define CmdBeginSolidClip_size 4
CmdBeginSolidClipRef CmdBeginSolidClip_index(CmdBeginSolidClipRef ref, uint index) {
return CmdBeginSolidClipRef(ref.offset + index * CmdBeginSolidClip_size);
}
struct CmdEndClip { struct CmdEndClip {
float alpha; float alpha;
}; };
@ -169,11 +183,12 @@ CmdJumpRef CmdJump_index(CmdJumpRef ref, uint index) {
#define Cmd_FillMask 4 #define Cmd_FillMask 4
#define Cmd_FillMaskInv 5 #define Cmd_FillMaskInv 5
#define Cmd_BeginClip 6 #define Cmd_BeginClip 6
#define Cmd_EndClip 7 #define Cmd_BeginSolidClip 7
#define Cmd_Stroke 8 #define Cmd_EndClip 8
#define Cmd_Solid 9 #define Cmd_Stroke 9
#define Cmd_SolidMask 10 #define Cmd_Solid 10
#define Cmd_Jump 11 #define Cmd_SolidMask 11
#define Cmd_Jump 12
#define Cmd_size 20 #define Cmd_size 20
CmdRef Cmd_index(CmdRef ref, uint index) { CmdRef Cmd_index(CmdRef ref, uint index) {
@ -318,6 +333,19 @@ void CmdBeginClip_write(CmdBeginClipRef ref, CmdBeginClip s) {
ptcl[ix + 1] = uint(s.backdrop); ptcl[ix + 1] = uint(s.backdrop);
} }
CmdBeginSolidClip CmdBeginSolidClip_read(CmdBeginSolidClipRef ref) {
uint ix = ref.offset >> 2;
uint raw0 = ptcl[ix + 0];
CmdBeginSolidClip s;
s.alpha = uintBitsToFloat(raw0);
return s;
}
void CmdBeginSolidClip_write(CmdBeginSolidClipRef ref, CmdBeginSolidClip s) {
uint ix = ref.offset >> 2;
ptcl[ix + 0] = floatBitsToUint(s.alpha);
}
CmdEndClip CmdEndClip_read(CmdEndClipRef ref) { CmdEndClip CmdEndClip_read(CmdEndClipRef ref) {
uint ix = ref.offset >> 2; uint ix = ref.offset >> 2;
uint raw0 = ptcl[ix + 0]; uint raw0 = ptcl[ix + 0];
@ -398,6 +426,10 @@ CmdBeginClip Cmd_BeginClip_read(CmdRef ref) {
return CmdBeginClip_read(CmdBeginClipRef(ref.offset + 4)); return CmdBeginClip_read(CmdBeginClipRef(ref.offset + 4));
} }
CmdBeginSolidClip Cmd_BeginSolidClip_read(CmdRef ref) {
return CmdBeginSolidClip_read(CmdBeginSolidClipRef(ref.offset + 4));
}
CmdEndClip Cmd_EndClip_read(CmdRef ref) { CmdEndClip Cmd_EndClip_read(CmdRef ref) {
return CmdEndClip_read(CmdEndClipRef(ref.offset + 4)); return CmdEndClip_read(CmdEndClipRef(ref.offset + 4));
} }
@ -452,6 +484,11 @@ void Cmd_BeginClip_write(CmdRef ref, CmdBeginClip s) {
CmdBeginClip_write(CmdBeginClipRef(ref.offset + 4), s); CmdBeginClip_write(CmdBeginClipRef(ref.offset + 4), s);
} }
void Cmd_BeginSolidClip_write(CmdRef ref, CmdBeginSolidClip s) {
ptcl[ref.offset >> 2] = Cmd_BeginSolidClip;
CmdBeginSolidClip_write(CmdBeginSolidClipRef(ref.offset + 4), s);
}
void Cmd_EndClip_write(CmdRef ref, CmdEndClip s) { void Cmd_EndClip_write(CmdRef ref, CmdEndClip s) {
ptcl[ref.offset >> 2] = Cmd_EndClip; ptcl[ref.offset >> 2] = Cmd_EndClip;
CmdEndClip_write(CmdEndClipRef(ref.offset + 4), s); CmdEndClip_write(CmdEndClipRef(ref.offset + 4), s);

View file

@ -32,11 +32,7 @@ struct TransformRef {
uint offset; uint offset;
}; };
struct BeginClipRef { struct ClipRef {
uint offset;
};
struct EndClipRef {
uint offset; uint offset;
}; };
@ -131,24 +127,14 @@ TransformRef Transform_index(TransformRef ref, uint index) {
return TransformRef(ref.offset + index * Transform_size); return TransformRef(ref.offset + index * Transform_size);
} }
struct BeginClip { struct Clip {
vec4 bbox; vec4 bbox;
}; };
#define BeginClip_size 16 #define Clip_size 16
BeginClipRef BeginClip_index(BeginClipRef ref, uint index) { ClipRef Clip_index(ClipRef ref, uint index) {
return BeginClipRef(ref.offset + index * BeginClip_size); return ClipRef(ref.offset + index * Clip_size);
}
struct EndClip {
uint clip_size;
};
#define EndClip_size 4
EndClipRef EndClip_index(EndClipRef ref, uint index) {
return EndClipRef(ref.offset + index * EndClip_size);
} }
#define Element_Nop 0 #define Element_Nop 0
@ -263,25 +249,17 @@ Transform Transform_read(TransformRef ref) {
return s; return s;
} }
BeginClip BeginClip_read(BeginClipRef ref) { Clip Clip_read(ClipRef ref) {
uint ix = ref.offset >> 2; uint ix = ref.offset >> 2;
uint raw0 = scene[ix + 0]; uint raw0 = scene[ix + 0];
uint raw1 = scene[ix + 1]; uint raw1 = scene[ix + 1];
uint raw2 = scene[ix + 2]; uint raw2 = scene[ix + 2];
uint raw3 = scene[ix + 3]; uint raw3 = scene[ix + 3];
BeginClip s; Clip s;
s.bbox = vec4(uintBitsToFloat(raw0), uintBitsToFloat(raw1), uintBitsToFloat(raw2), uintBitsToFloat(raw3)); s.bbox = vec4(uintBitsToFloat(raw0), uintBitsToFloat(raw1), uintBitsToFloat(raw2), uintBitsToFloat(raw3));
return s; return s;
} }
EndClip EndClip_read(EndClipRef ref) {
uint ix = ref.offset >> 2;
uint raw0 = scene[ix + 0];
EndClip s;
s.clip_size = raw0;
return s;
}
uint Element_tag(ElementRef ref) { uint Element_tag(ElementRef ref) {
return scene[ref.offset >> 2]; return scene[ref.offset >> 2];
} }
@ -334,11 +312,11 @@ FillMask Element_FillMaskInv_read(ElementRef ref) {
return FillMask_read(FillMaskRef(ref.offset + 4)); return FillMask_read(FillMaskRef(ref.offset + 4));
} }
BeginClip Element_BeginClip_read(ElementRef ref) { Clip Element_BeginClip_read(ElementRef ref) {
return BeginClip_read(BeginClipRef(ref.offset + 4)); return Clip_read(ClipRef(ref.offset + 4));
} }
EndClip Element_EndClip_read(ElementRef ref) { Clip Element_EndClip_read(ElementRef ref) {
return EndClip_read(EndClipRef(ref.offset + 4)); return Clip_read(ClipRef(ref.offset + 4));
} }

View file

@ -50,8 +50,10 @@ void main() {
case Annotated_FillMask: case Annotated_FillMask:
case Annotated_FillMaskInv: case Annotated_FillMaskInv:
case Annotated_Stroke: case Annotated_Stroke:
// Note: we take advantage of the fact that fills and strokes case Annotated_BeginClip:
// have compatible layout. case Annotated_EndClip:
// Note: we take advantage of the fact that fills, strokes, and
// clips have compatible layout.
AnnoFill fill = Annotated_Fill_read(ref); AnnoFill fill = Annotated_Fill_read(ref);
x0 = int(floor(fill.bbox.x * SX)); x0 = int(floor(fill.bbox.x * SX));
y0 = int(floor(fill.bbox.y * SY)); y0 = int(floor(fill.bbox.y * SY));
@ -67,6 +69,11 @@ void main() {
Path path; Path path;
path.bbox = uvec4(x0, y0, x1, y1); path.bbox = uvec4(x0, y0, x1, y1);
uint tile_count = (x1 - x0) * (y1 - y0); uint tile_count = (x1 - x0) * (y1 - y0);
if (tag == Annotated_EndClip) {
// Don't actually allocate tiles for an end clip, but we do want
// the path structure (especially bbox) allocated for it.
tile_count = 0;
}
sh_tile_count[th_ix] = tile_count; sh_tile_count[th_ix] = tile_count;
// Prefix sum of sh_tile_count // Prefix sum of sh_tile_count

Binary file not shown.

View file

@ -7,7 +7,7 @@ pub use render_ctx::PietGpuRenderContext;
use rand::{Rng, RngCore}; use rand::{Rng, RngCore};
use piet::kurbo::{BezPath, Circle, Line, Point, Vec2}; use piet::kurbo::{BezPath, Circle, Point, Vec2};
use piet::{Color, ImageFormat, RenderContext}; use piet::{Color, ImageFormat, RenderContext};
use piet_gpu_types::encoder::Encode; use piet_gpu_types::encoder::Encode;
@ -52,16 +52,23 @@ pub fn render_scene(rc: &mut impl RenderContext) {
let circle = Circle::new(center, radius); let circle = Circle::new(center, radius);
rc.fill(circle, &color); rc.fill(circle, &color);
} }
/* let _ = rc.save();
let mut path = BezPath::new(); let mut path = BezPath::new();
path.move_to((100.0, 1150.0)); path.move_to((200.0, 150.0));
path.line_to((200.0, 1200.0)); path.line_to((100.0, 200.0));
path.line_to((150.0, 1250.0)); path.line_to((150.0, 250.0));
path.close_path();
rc.clip(path);
let mut path = BezPath::new();
path.move_to((100.0, 150.0));
path.line_to((200.0, 200.0));
path.line_to((150.0, 250.0));
path.close_path(); path.close_path();
rc.fill(path, &Color::rgb8(128, 0, 128)); rc.fill(path, &Color::rgb8(128, 0, 128));
*/ let _ = rc.restore();
rc.stroke( rc.stroke(
Line::new((100.0, 100.0), (200.0, 150.0)), piet::kurbo::Line::new((100.0, 100.0), (200.0, 150.0)),
&Color::WHITE, &Color::WHITE,
5.0, 5.0,
); );

View file

@ -1,9 +1,9 @@
use std::{borrow::Cow, convert::TryInto, ops::RangeBounds}; use std::{borrow::Cow, ops::RangeBounds};
use piet_gpu_types::encoder::{Encode, Encoder}; use piet_gpu_types::encoder::{Encode, Encoder};
use piet_gpu_types::scene::{ use piet_gpu_types::scene::{
BeginClip, CubicSeg, Element, EndClip, Fill, LineSeg, QuadSeg, SetLineWidth, Stroke, Transform, Clip, CubicSeg, Element, Fill, LineSeg, QuadSeg, SetLineWidth, Stroke, Transform,
}; };
use piet::{ use piet::{
@ -53,6 +53,10 @@ pub enum PietGpuBrush {
#[derive(Default)] #[derive(Default)]
struct State { struct State {
/// The transform relative to the parent state. /// The transform relative to the parent state.
rel_transform: Affine,
/// The transform at the parent state.
///
/// This invariant should hold: transform * rel_transform = cur_transform
transform: Affine, transform: Affine,
n_clip: usize, n_clip: usize,
} }
@ -61,8 +65,6 @@ struct ClipElement {
/// Index of BeginClip element in element vec, for bbox fixup. /// Index of BeginClip element in element vec, for bbox fixup.
begin_ix: usize, begin_ix: usize,
bbox: Option<Rect>, bbox: Option<Rect>,
/// The transform relative to the next clip element on the stack.
transform: Affine,
} }
const TOLERANCE: f64 = 0.25; const TOLERANCE: f64 = 0.25;
@ -168,18 +170,20 @@ impl RenderContext for PietGpuRenderContext {
fn fill_even_odd(&mut self, _shape: impl Shape, _brush: &impl IntoBrush<Self>) {} fn fill_even_odd(&mut self, _shape: impl Shape, _brush: &impl IntoBrush<Self>) {}
fn clip(&mut self, shape: impl Shape) { fn clip(&mut self, shape: impl Shape) {
let begin_ix = self.elements.len();
let path = shape.path_elements(TOLERANCE); let path = shape.path_elements(TOLERANCE);
self.encode_path(path, true); self.encode_path(path, true);
self.elements.push(Element::BeginClip(BeginClip { let begin_ix = self.elements.len();
self.elements.push(Element::BeginClip(Clip {
bbox: Default::default(), bbox: Default::default(),
})); }));
self.clip_stack.push(ClipElement { self.clip_stack.push(ClipElement {
bbox: None, bbox: None,
begin_ix, begin_ix,
transform: Affine::default(),
}); });
self.path_count += 1; self.path_count += 1;
if let Some(tos) = self.state_stack.last_mut() {
tos.n_clip += 1;
}
} }
fn text(&mut self) -> &mut Self::Text { fn text(&mut self) -> &mut Self::Text {
@ -189,18 +193,22 @@ impl RenderContext for PietGpuRenderContext {
fn draw_text(&mut self, _layout: &Self::TextLayout, _pos: impl Into<Point>) {} fn draw_text(&mut self, _layout: &Self::TextLayout, _pos: impl Into<Point>) {}
fn save(&mut self) -> Result<(), Error> { fn save(&mut self) -> Result<(), Error> {
self.state_stack.push(Default::default()); self.state_stack.push(State {
rel_transform: Affine::default(),
transform: self.cur_transform,
n_clip: 0,
});
Ok(()) Ok(())
} }
fn restore(&mut self) -> Result<(), Error> { fn restore(&mut self) -> Result<(), Error> {
if let Some(state) = self.state_stack.pop() { if let Some(state) = self.state_stack.pop() {
if state.transform != Affine::default() { if state.rel_transform != Affine::default() {
let a_inv = state.transform.inverse(); let a_inv = state.rel_transform.inverse();
self.elements self.elements
.push(Element::Transform(to_scene_transform(a_inv))); .push(Element::Transform(to_scene_transform(a_inv)));
self.cur_transform *= a_inv;
} }
self.cur_transform = state.transform;
for _ in 0..state.n_clip { for _ in 0..state.n_clip {
self.pop_clip(); self.pop_clip();
} }
@ -211,6 +219,9 @@ impl RenderContext for PietGpuRenderContext {
} }
fn finish(&mut self) -> Result<(), Error> { fn finish(&mut self) -> Result<(), Error> {
for _ in 0..self.clip_stack.len() {
self.pop_clip();
}
Ok(()) Ok(())
} }
@ -218,10 +229,7 @@ impl RenderContext for PietGpuRenderContext {
self.elements self.elements
.push(Element::Transform(to_scene_transform(transform))); .push(Element::Transform(to_scene_transform(transform)));
if let Some(tos) = self.state_stack.last_mut() { if let Some(tos) = self.state_stack.last_mut() {
tos.transform *= transform; tos.rel_transform *= transform;
}
if let Some(tos) = self.clip_stack.last_mut() {
tos.transform *= transform;
} }
self.cur_transform *= transform; self.cur_transform *= transform;
} }
@ -392,23 +400,38 @@ impl PietGpuRenderContext {
fn pop_clip(&mut self) { fn pop_clip(&mut self) {
let tos = self.clip_stack.pop().unwrap(); let tos = self.clip_stack.pop().unwrap();
let delta = (self.elements.len() - tos.begin_ix).try_into().unwrap(); let bbox = tos.bbox.unwrap_or_default();
self.elements.push(Element::EndClip(EndClip { delta })); let bbox_f32_4 = rect_to_f32_4(bbox);
self.elements
.push(Element::EndClip(Clip { bbox: bbox_f32_4 }));
self.path_count += 1; self.path_count += 1;
if let Element::BeginClip(begin_clip) = &mut self.elements[tos.begin_ix] {
begin_clip.bbox = bbox_f32_4;
} else {
unreachable!("expected BeginClip, not found");
}
if let Some(bbox) = tos.bbox { if let Some(bbox) = tos.bbox {
if let Element::BeginClip(begin_clip) = &mut self.elements[tos.begin_ix] { self.union_bbox(bbox);
begin_clip.bbox = rect_to_f32_4(bbox);
} else {
unreachable!("expected BeginClip, not found");
}
self.accumulate_bbox(|| bbox);
} }
} }
/// Accumulate a bbox.
///
/// The bbox is given lazily as a closure, relative to the current transform.
/// It's lazy because we don't need to compute it unless we're inside a clip.
fn accumulate_bbox(&mut self, f: impl FnOnce() -> Rect) { fn accumulate_bbox(&mut self, f: impl FnOnce() -> Rect) {
if let Some(tos) = self.clip_stack.last_mut() { if !self.clip_stack.is_empty() {
let bbox = f(); let bbox = f();
let bbox = tos.transform.transform_rect_bbox(bbox); let bbox = self.cur_transform.transform_rect_bbox(bbox);
self.union_bbox(bbox);
}
}
/// Accumulate an absolute bbox.
///
/// The bbox is given already transformed into surface coordinates.
fn union_bbox(&mut self, bbox: Rect) {
if let Some(tos) = self.clip_stack.last_mut() {
tos.bbox = if let Some(old_bbox) = tos.bbox { tos.bbox = if let Some(old_bbox) = tos.bbox {
Some(old_bbox.union(bbox)) Some(old_bbox.union(bbox))
} else { } else {
@ -512,7 +535,12 @@ fn to_f32_2(point: Point) -> [f32; 2] {
} }
fn rect_to_f32_4(rect: Rect) -> [f32; 4] { fn rect_to_f32_4(rect: Rect) -> [f32; 4] {
[rect.x0 as f32, rect.y0 as f32, rect.x1 as f32, rect.y1 as f32] [
rect.x0 as f32,
rect.y0 as f32,
rect.x1 as f32,
rect.y1 as f32,
]
} }
fn to_scene_transform(transform: Affine) -> Transform { fn to_scene_transform(transform: Affine) -> Transform {