Merge pull request #44 from linebender/clip

First cut at clipping
This commit is contained in:
Raph Levien 2020-11-22 18:13:30 -08:00 committed by GitHub
commit 180047da51
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 579 additions and 99 deletions

View file

@ -4,26 +4,33 @@ 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,
} }
struct AnnoClip {
bbox: [f32; 4],
}
enum Annotated { enum Annotated {
Nop, Nop,
Stroke(AnnoStroke), Stroke(AnnoStroke),
Fill(AnnoFill), Fill(AnnoFill),
FillMask(AnnoFillMask), FillMask(AnnoFillMask),
FillMaskInv(AnnoFillMask), FillMaskInv(AnnoFillMask),
BeginClip(AnnoClip),
EndClip(AnnoClip),
} }
} }
} }

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

@ -30,6 +30,19 @@ piet_gpu! {
backdrop: i32, backdrop: i32,
mask: f32, mask: f32,
} }
struct CmdBeginClip {
tile_ref: u32,
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 {
// This will be 1.0 for clips, but we can imagine blend groups.
alpha: f32,
}
struct CmdSolid { struct CmdSolid {
rgba_color: u32, rgba_color: u32,
} }
@ -46,6 +59,9 @@ piet_gpu! {
Fill(CmdFill), Fill(CmdFill),
FillMask(CmdFillMask), FillMask(CmdFillMask),
FillMaskInv(CmdFillMask), FillMaskInv(CmdFillMask),
BeginClip(CmdBeginClip),
BeginSolidClip(CmdBeginSolidClip),
EndClip(CmdEndClip),
Stroke(CmdStroke), Stroke(CmdStroke),
Solid(CmdSolid), Solid(CmdSolid),
SolidMask(CmdSolidMask), SolidMask(CmdSolidMask),

View file

@ -1,6 +1,8 @@
use piet_gpu_derive::piet_gpu; use piet_gpu_derive::piet_gpu;
pub use self::scene::{CubicSeg, Element, Fill, LineSeg, QuadSeg, SetLineWidth, Stroke, Transform}; pub use self::scene::{
Clip, CubicSeg, Element, Fill, LineSeg, QuadSeg, SetLineWidth, Stroke, Transform,
};
piet_gpu! { piet_gpu! {
#[rust_encode] #[rust_encode]
@ -36,6 +38,10 @@ piet_gpu! {
mat: [f32; 4], mat: [f32; 4],
translate: [f32; 2], translate: [f32; 2],
} }
struct Clip {
bbox: [f32; 4],
// TODO: add alpha?
}
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
@ -55,6 +61,8 @@ piet_gpu! {
Transform(Transform), Transform(Transform),
FillMask(FillMask), FillMask(FillMask),
FillMaskInv(FillMask), FillMaskInv(FillMask),
BeginClip(Clip),
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

@ -12,13 +12,17 @@ struct AnnoStrokeRef {
uint offset; uint offset;
}; };
struct AnnoClipRef {
uint offset;
};
struct AnnotatedRef { struct AnnotatedRef {
uint offset; uint offset;
}; };
struct AnnoFill { struct AnnoFill {
uint rgba_color;
vec4 bbox; vec4 bbox;
uint rgba_color;
}; };
#define AnnoFill_size 20 #define AnnoFill_size 20
@ -28,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
@ -39,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;
}; };
@ -50,11 +54,23 @@ AnnoStrokeRef AnnoStroke_index(AnnoStrokeRef ref, uint index) {
return AnnoStrokeRef(ref.offset + index * AnnoStroke_size); return AnnoStrokeRef(ref.offset + index * AnnoStroke_size);
} }
struct AnnoClip {
vec4 bbox;
};
#define AnnoClip_size 16
AnnoClipRef AnnoClip_index(AnnoClipRef ref, uint index) {
return AnnoClipRef(ref.offset + index * AnnoClip_size);
}
#define Annotated_Nop 0 #define Annotated_Nop 0
#define Annotated_Stroke 1 #define Annotated_Stroke 1
#define Annotated_Fill 2 #define Annotated_Fill 2
#define Annotated_FillMask 3 #define Annotated_FillMask 3
#define Annotated_FillMaskInv 4 #define Annotated_FillMaskInv 4
#define Annotated_BeginClip 5
#define Annotated_EndClip 6
#define Annotated_size 28 #define Annotated_size 28
AnnotatedRef Annotated_index(AnnotatedRef ref, uint index) { AnnotatedRef Annotated_index(AnnotatedRef ref, uint index) {
@ -69,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) {
@ -91,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) {
@ -114,22 +130,41 @@ 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);
} }
AnnoClip AnnoClip_read(AnnoClipRef 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];
AnnoClip s;
s.bbox = vec4(uintBitsToFloat(raw0), uintBitsToFloat(raw1), uintBitsToFloat(raw2), uintBitsToFloat(raw3));
return s;
}
void AnnoClip_write(AnnoClipRef ref, AnnoClip s) {
uint ix = ref.offset >> 2;
annotated[ix + 0] = floatBitsToUint(s.bbox.x);
annotated[ix + 1] = floatBitsToUint(s.bbox.y);
annotated[ix + 2] = floatBitsToUint(s.bbox.z);
annotated[ix + 3] = floatBitsToUint(s.bbox.w);
}
uint Annotated_tag(AnnotatedRef ref) { uint Annotated_tag(AnnotatedRef ref) {
return annotated[ref.offset >> 2]; return annotated[ref.offset >> 2];
} }
@ -150,6 +185,14 @@ AnnoFillMask Annotated_FillMaskInv_read(AnnotatedRef ref) {
return AnnoFillMask_read(AnnoFillMaskRef(ref.offset + 4)); return AnnoFillMask_read(AnnoFillMaskRef(ref.offset + 4));
} }
AnnoClip Annotated_BeginClip_read(AnnotatedRef ref) {
return AnnoClip_read(AnnoClipRef(ref.offset + 4));
}
AnnoClip Annotated_EndClip_read(AnnotatedRef ref) {
return AnnoClip_read(AnnoClipRef(ref.offset + 4));
}
void Annotated_Nop_write(AnnotatedRef ref) { void Annotated_Nop_write(AnnotatedRef ref) {
annotated[ref.offset >> 2] = Annotated_Nop; annotated[ref.offset >> 2] = Annotated_Nop;
} }
@ -174,3 +217,13 @@ void Annotated_FillMaskInv_write(AnnotatedRef ref, AnnoFillMask s) {
AnnoFillMask_write(AnnoFillMaskRef(ref.offset + 4), s); AnnoFillMask_write(AnnoFillMaskRef(ref.offset + 4), s);
} }
void Annotated_BeginClip_write(AnnotatedRef ref, AnnoClip s) {
annotated[ref.offset >> 2] = Annotated_BeginClip;
AnnoClip_write(AnnoClipRef(ref.offset + 4), s);
}
void Annotated_EndClip_write(AnnotatedRef ref, AnnoClip s) {
annotated[ref.offset >> 2] = Annotated_EndClip;
AnnoClip_write(AnnoClipRef(ref.offset + 4), s);
}

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);
} }
@ -176,10 +172,10 @@ void main() {
case Annotated_FillMask: case Annotated_FillMask:
case Annotated_FillMaskInv: 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 +220,25 @@ 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;
bool include_tile;
if (tag == Annotated_BeginClip || tag == Annotated_EndClip) {
include_tile = true;
} else {
Tile tile = Tile_read(TileRef(sh_tile_base[el_ix] + (sh_tile_stride[el_ix] * y + x) * Tile_size)); Tile tile = Tile_read(TileRef(sh_tile_base[el_ix] + (sh_tile_stride[el_ix] * y + x) * Tile_size));
// Include the path in the tile if // Include the path in the tile if
// - the tile contains at least a segment (tile offset non-zero) // - the tile contains at least a segment (tile offset non-zero)
// - the tile is completely covered (backdrop non-zero) // - the tile is completely covered (backdrop non-zero)
// - the tile is not covered and we're filling everything outside the path (backdrop zero, inverse fills).
bool inside = tile.backdrop != 0; bool inside = tile.backdrop != 0;
bool fill = tag != Annotated_FillMaskInv; bool fill = tag != Annotated_FillMaskInv;
if (tile.tile.offset != 0 || inside == fill) { include_tile = tile.tile.offset != 0 || inside == fill;
}
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 +248,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,6 +291,27 @@ void main() {
} }
cmd_ref.offset += Cmd_size; cmd_ref.offset += Cmd_size;
break; break;
case Annotated_BeginClip:
tile = Tile_read(TileRef(sh_tile_base[element_ref_ix]
+ (sh_tile_stride[element_ref_ix] * tile_y + tile_x) * Tile_size));
alloc_cmd(cmd_ref, cmd_limit);
if (tile.tile.offset != 0) {
CmdBeginClip cmd_begin_clip;
cmd_begin_clip.tile_ref = tile.tile.offset;
cmd_begin_clip.backdrop = tile.backdrop;
Cmd_BeginClip_write(cmd_ref, cmd_begin_clip);
} else {
// 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;
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_FillMask: case Annotated_FillMask:
case Annotated_FillMaskInv: case Annotated_FillMaskInv:
tile = Tile_read(TileRef(sh_tile_base[element_ref_ix] tile = Tile_read(TileRef(sh_tile_base[element_ref_ix]

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

@ -27,6 +27,8 @@ layout(rgba8, set = 0, binding = 2) uniform writeonly image2D image;
#include "ptcl.h" #include "ptcl.h"
#include "tile.h" #include "tile.h"
#define BLEND_STACK_SIZE 4
// Calculate coverage based on backdrop + coverage of each line segment // Calculate coverage based on backdrop + coverage of each line segment
float[CHUNK] computeArea(vec2 xy, int backdrop, uint tile_ref) { float[CHUNK] computeArea(vec2 xy, int backdrop, uint tile_ref) {
// Probably better to store as float, but conversion is no doubt cheap. // Probably better to store as float, but conversion is no doubt cheap.
@ -69,6 +71,8 @@ void main() {
vec2 xy = vec2(xy_uint); vec2 xy = vec2(xy_uint);
vec3 rgb[CHUNK]; vec3 rgb[CHUNK];
float mask[CHUNK]; float mask[CHUNK];
uint blend_stack[BLEND_STACK_SIZE][CHUNK];
uint blend_sp = 0;
for (uint i = 0; i < CHUNK; i++) { for (uint i = 0; i < CHUNK; i++) {
rgb[i] = vec3(0.5); rgb[i] = vec3(0.5);
mask[i] = 1.0; mask[i] = 1.0;
@ -137,6 +141,30 @@ void main() {
mask[k] = mix(mask[k], fill_mask.mask, 1.0 - area[k]); mask[k] = mix(mask[k], fill_mask.mask, 1.0 - area[k]);
} }
break; break;
case Cmd_BeginClip:
CmdBeginClip begin_clip = Cmd_BeginClip_read(cmd_ref);
area = computeArea(xy, begin_clip.backdrop, begin_clip.tile_ref);
for (uint k = 0; k < CHUNK; k++) {
blend_stack[blend_sp][k] = packUnorm4x8(vec4(rgb[k], clamp(abs(area[k]), 0.0, 1.0)));
}
blend_sp++;
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:
CmdEndClip end_clip = Cmd_EndClip_read(cmd_ref);
blend_sp--;
for (uint k = 0; k < CHUNK; k++) {
vec4 rgba = unpackUnorm4x8(blend_stack[blend_sp][k]);
rgb[k] = mix(rgba.rgb, rgb[k], end_clip.alpha * rgba.a);
}
break;
case Cmd_Solid: case Cmd_Solid:
CmdSolid solid = Cmd_Solid_read(cmd_ref); CmdSolid solid = Cmd_Solid_read(cmd_ref);
fg_rgba = unpackUnorm4x8(solid.rgba_color).wzyx; fg_rgba = unpackUnorm4x8(solid.rgba_color).wzyx;

Binary file not shown.

Binary file not shown.

View file

@ -20,6 +20,18 @@ struct CmdFillMaskRef {
uint offset; uint offset;
}; };
struct CmdBeginClipRef {
uint offset;
};
struct CmdBeginSolidClipRef {
uint offset;
};
struct CmdEndClipRef {
uint offset;
};
struct CmdSolidRef { struct CmdSolidRef {
uint offset; uint offset;
}; };
@ -103,6 +115,37 @@ CmdFillMaskRef CmdFillMask_index(CmdFillMaskRef ref, uint index) {
return CmdFillMaskRef(ref.offset + index * CmdFillMask_size); return CmdFillMaskRef(ref.offset + index * CmdFillMask_size);
} }
struct CmdBeginClip {
uint tile_ref;
int backdrop;
};
#define CmdBeginClip_size 8
CmdBeginClipRef CmdBeginClip_index(CmdBeginClipRef ref, uint index) {
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 {
float alpha;
};
#define CmdEndClip_size 4
CmdEndClipRef CmdEndClip_index(CmdEndClipRef ref, uint index) {
return CmdEndClipRef(ref.offset + index * CmdEndClip_size);
}
struct CmdSolid { struct CmdSolid {
uint rgba_color; uint rgba_color;
}; };
@ -139,10 +182,13 @@ CmdJumpRef CmdJump_index(CmdJumpRef ref, uint index) {
#define Cmd_Fill 3 #define Cmd_Fill 3
#define Cmd_FillMask 4 #define Cmd_FillMask 4
#define Cmd_FillMaskInv 5 #define Cmd_FillMaskInv 5
#define Cmd_Stroke 6 #define Cmd_BeginClip 6
#define Cmd_Solid 7 #define Cmd_BeginSolidClip 7
#define Cmd_SolidMask 8 #define Cmd_EndClip 8
#define Cmd_Jump 9 #define Cmd_Stroke 9
#define Cmd_Solid 10
#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) {
@ -271,6 +317,48 @@ void CmdFillMask_write(CmdFillMaskRef ref, CmdFillMask s) {
ptcl[ix + 2] = floatBitsToUint(s.mask); ptcl[ix + 2] = floatBitsToUint(s.mask);
} }
CmdBeginClip CmdBeginClip_read(CmdBeginClipRef ref) {
uint ix = ref.offset >> 2;
uint raw0 = ptcl[ix + 0];
uint raw1 = ptcl[ix + 1];
CmdBeginClip s;
s.tile_ref = raw0;
s.backdrop = int(raw1);
return s;
}
void CmdBeginClip_write(CmdBeginClipRef ref, CmdBeginClip s) {
uint ix = ref.offset >> 2;
ptcl[ix + 0] = s.tile_ref;
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) {
uint ix = ref.offset >> 2;
uint raw0 = ptcl[ix + 0];
CmdEndClip s;
s.alpha = uintBitsToFloat(raw0);
return s;
}
void CmdEndClip_write(CmdEndClipRef ref, CmdEndClip s) {
uint ix = ref.offset >> 2;
ptcl[ix + 0] = floatBitsToUint(s.alpha);
}
CmdSolid CmdSolid_read(CmdSolidRef ref) { CmdSolid CmdSolid_read(CmdSolidRef ref) {
uint ix = ref.offset >> 2; uint ix = ref.offset >> 2;
uint raw0 = ptcl[ix + 0]; uint raw0 = ptcl[ix + 0];
@ -334,6 +422,18 @@ CmdFillMask Cmd_FillMaskInv_read(CmdRef ref) {
return CmdFillMask_read(CmdFillMaskRef(ref.offset + 4)); return CmdFillMask_read(CmdFillMaskRef(ref.offset + 4));
} }
CmdBeginClip Cmd_BeginClip_read(CmdRef ref) {
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) {
return CmdEndClip_read(CmdEndClipRef(ref.offset + 4));
}
CmdStroke Cmd_Stroke_read(CmdRef ref) { CmdStroke Cmd_Stroke_read(CmdRef ref) {
return CmdStroke_read(CmdStrokeRef(ref.offset + 4)); return CmdStroke_read(CmdStrokeRef(ref.offset + 4));
} }
@ -379,6 +479,21 @@ void Cmd_FillMaskInv_write(CmdRef ref, CmdFillMask s) {
CmdFillMask_write(CmdFillMaskRef(ref.offset + 4), s); CmdFillMask_write(CmdFillMaskRef(ref.offset + 4), s);
} }
void Cmd_BeginClip_write(CmdRef ref, CmdBeginClip s) {
ptcl[ref.offset >> 2] = Cmd_BeginClip;
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) {
ptcl[ref.offset >> 2] = Cmd_EndClip;
CmdEndClip_write(CmdEndClipRef(ref.offset + 4), s);
}
void Cmd_Stroke_write(CmdRef ref, CmdStroke s) { void Cmd_Stroke_write(CmdRef ref, CmdStroke s) {
ptcl[ref.offset >> 2] = Cmd_Stroke; ptcl[ref.offset >> 2] = Cmd_Stroke;
CmdStroke_write(CmdStrokeRef(ref.offset + 4), s); CmdStroke_write(CmdStrokeRef(ref.offset + 4), s);

View file

@ -32,6 +32,10 @@ struct TransformRef {
uint offset; uint offset;
}; };
struct ClipRef {
uint offset;
};
struct ElementRef { struct ElementRef {
uint offset; uint offset;
}; };
@ -123,6 +127,16 @@ TransformRef Transform_index(TransformRef ref, uint index) {
return TransformRef(ref.offset + index * Transform_size); return TransformRef(ref.offset + index * Transform_size);
} }
struct Clip {
vec4 bbox;
};
#define Clip_size 16
ClipRef Clip_index(ClipRef ref, uint index) {
return ClipRef(ref.offset + index * Clip_size);
}
#define Element_Nop 0 #define Element_Nop 0
#define Element_StrokeLine 1 #define Element_StrokeLine 1
#define Element_FillLine 2 #define Element_FillLine 2
@ -136,6 +150,8 @@ TransformRef Transform_index(TransformRef ref, uint index) {
#define Element_Transform 10 #define Element_Transform 10
#define Element_FillMask 11 #define Element_FillMask 11
#define Element_FillMaskInv 12 #define Element_FillMaskInv 12
#define Element_BeginClip 13
#define Element_EndClip 14
#define Element_size 36 #define Element_size 36
ElementRef Element_index(ElementRef ref, uint index) { ElementRef Element_index(ElementRef ref, uint index) {
@ -233,6 +249,17 @@ Transform Transform_read(TransformRef ref) {
return s; return s;
} }
Clip Clip_read(ClipRef ref) {
uint ix = ref.offset >> 2;
uint raw0 = scene[ix + 0];
uint raw1 = scene[ix + 1];
uint raw2 = scene[ix + 2];
uint raw3 = scene[ix + 3];
Clip s;
s.bbox = vec4(uintBitsToFloat(raw0), uintBitsToFloat(raw1), uintBitsToFloat(raw2), uintBitsToFloat(raw3));
return s;
}
uint Element_tag(ElementRef ref) { uint Element_tag(ElementRef ref) {
return scene[ref.offset >> 2]; return scene[ref.offset >> 2];
} }
@ -285,3 +312,11 @@ FillMask Element_FillMaskInv_read(ElementRef ref) {
return FillMask_read(FillMaskRef(ref.offset + 4)); return FillMask_read(FillMaskRef(ref.offset + 4));
} }
Clip Element_BeginClip_read(ElementRef ref) {
return Clip_read(ClipRef(ref.offset + 4));
}
Clip Element_EndClip_read(ElementRef ref) {
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

@ -2,11 +2,12 @@ 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::{CubicSeg, Element, Fill, LineSeg, QuadSeg, SetLineWidth, Stroke}; use piet_gpu_types::scene::{
Clip, CubicSeg, Element, Fill, LineSeg, QuadSeg, SetLineWidth, Stroke, Transform,
};
use piet::{ use piet::{
kurbo::Size, kurbo::{Affine, Insets, PathEl, Point, Rect, Shape, Size},
kurbo::{Affine, PathEl, Point, Rect, Shape},
HitTestPosition, TextAttribute, TextStorage, HitTestPosition, TextAttribute, TextStorage,
}; };
@ -33,8 +34,14 @@ pub struct PietGpuRenderContext {
stroke_width: f32, stroke_width: f32,
// We're tallying these cpu-side for expedience, but will probably // We're tallying these cpu-side for expedience, but will probably
// move this to some kind of readback from element processing. // move this to some kind of readback from element processing.
/// The count of elements that make it through to coarse rasterization.
path_count: usize, path_count: usize,
/// The count of path segment elements.
pathseg_count: usize, pathseg_count: usize,
cur_transform: Affine,
state_stack: Vec<State>,
clip_stack: Vec<ClipElement>,
} }
#[derive(Clone)] #[derive(Clone)]
@ -43,6 +50,23 @@ pub enum PietGpuBrush {
Gradient, Gradient,
} }
#[derive(Default)]
struct 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,
n_clip: usize,
}
struct ClipElement {
/// Index of BeginClip element in element vec, for bbox fixup.
begin_ix: usize,
bbox: Option<Rect>,
}
const TOLERANCE: f64 = 0.25; const TOLERANCE: f64 = 0.25;
impl PietGpuRenderContext { impl PietGpuRenderContext {
@ -58,6 +82,9 @@ impl PietGpuRenderContext {
stroke_width, stroke_width,
path_count: 0, path_count: 0,
pathseg_count: 0, pathseg_count: 0,
cur_transform: Affine::default(),
state_stack: Vec::new(),
clip_stack: Vec::new(),
} }
} }
@ -96,17 +123,19 @@ impl RenderContext for PietGpuRenderContext {
fn clear(&mut self, _color: Color) {} fn clear(&mut self, _color: Color) {}
fn stroke(&mut self, shape: impl Shape, brush: &impl IntoBrush<Self>, width: f64) { fn stroke(&mut self, shape: impl Shape, brush: &impl IntoBrush<Self>, width: f64) {
let width = width as f32; let width_f32 = width as f32;
if self.stroke_width != width { if self.stroke_width != width_f32 {
self.elements self.elements
.push(Element::SetLineWidth(SetLineWidth { width })); .push(Element::SetLineWidth(SetLineWidth { width: width_f32 }));
self.stroke_width = width; self.stroke_width = width_f32;
} }
let brush = brush.make_brush(self, || shape.bounding_box()).into_owned(); let brush = brush.make_brush(self, || shape.bounding_box()).into_owned();
let path = shape.path_elements(TOLERANCE);
self.encode_path(path, false);
match brush { match brush {
PietGpuBrush::Solid(rgba_color) => { PietGpuBrush::Solid(rgba_color) => {
// Note: the bbox contribution of stroke becomes more complicated with miter joins.
self.accumulate_bbox(|| shape.bounding_box() + Insets::uniform(width * 0.5));
let path = shape.path_elements(TOLERANCE);
self.encode_path(path, false);
let stroke = Stroke { rgba_color }; let stroke = Stroke { rgba_color };
self.elements.push(Element::Stroke(stroke)); self.elements.push(Element::Stroke(stroke));
self.path_count += 1; self.path_count += 1;
@ -126,21 +155,36 @@ impl RenderContext for PietGpuRenderContext {
fn fill(&mut self, shape: impl Shape, brush: &impl IntoBrush<Self>) { fn fill(&mut self, shape: impl Shape, brush: &impl IntoBrush<Self>) {
let brush = brush.make_brush(self, || shape.bounding_box()).into_owned(); let brush = brush.make_brush(self, || shape.bounding_box()).into_owned();
if let PietGpuBrush::Solid(rgba_color) = brush {
// Note: we might get a good speedup from using an approximate bounding box.
// Perhaps that should be added to kurbo.
self.accumulate_bbox(|| shape.bounding_box());
let path = shape.path_elements(TOLERANCE); let path = shape.path_elements(TOLERANCE);
self.encode_path(path, true); self.encode_path(path, true);
match brush {
PietGpuBrush::Solid(rgba_color) => {
let fill = Fill { rgba_color }; let fill = Fill { rgba_color };
self.elements.push(Element::Fill(fill)); self.elements.push(Element::Fill(fill));
self.path_count += 1; self.path_count += 1;
} }
_ => (),
}
} }
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 path = shape.path_elements(TOLERANCE);
self.encode_path(path, true);
let begin_ix = self.elements.len();
self.elements.push(Element::BeginClip(Clip {
bbox: Default::default(),
}));
self.clip_stack.push(ClipElement {
bbox: None,
begin_ix,
});
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 {
&mut self.inner_text &mut self.inner_text
@ -149,15 +193,46 @@ 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(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> {
Ok(()) if let Some(state) = self.state_stack.pop() {
if state.rel_transform != Affine::default() {
let a_inv = state.rel_transform.inverse();
self.elements
.push(Element::Transform(to_scene_transform(a_inv)));
} }
self.cur_transform = state.transform;
for _ in 0..state.n_clip {
self.pop_clip();
}
Ok(())
} else {
Err(Error::StackUnbalance)
}
}
fn finish(&mut self) -> Result<(), Error> { fn finish(&mut self) -> Result<(), Error> {
for _ in 0..self.clip_stack.len() {
self.pop_clip();
}
Ok(()) Ok(())
} }
fn transform(&mut self, _transform: Affine) {}
fn transform(&mut self, transform: Affine) {
self.elements
.push(Element::Transform(to_scene_transform(transform)));
if let Some(tos) = self.state_stack.last_mut() {
tos.rel_transform *= transform;
}
self.cur_transform *= transform;
}
fn make_image( fn make_image(
&mut self, &mut self,
@ -189,7 +264,13 @@ impl RenderContext for PietGpuRenderContext {
fn blurred_rect(&mut self, _rect: Rect, _blur_radius: f64, _brush: &impl IntoBrush<Self>) {} fn blurred_rect(&mut self, _rect: Rect, _blur_radius: f64, _brush: &impl IntoBrush<Self>) {}
fn current_transform(&self) -> Affine { fn current_transform(&self) -> Affine {
Default::default() self.cur_transform
}
fn with_save(&mut self, f: impl FnOnce(&mut Self) -> Result<(), Error>) -> Result<(), Error> {
self.save()?;
// Always try to restore the stack, even if `f` errored.
f(self).and(self.restore())
} }
} }
@ -316,6 +397,48 @@ impl PietGpuRenderContext {
} }
} }
} }
fn pop_clip(&mut self) {
let tos = self.clip_stack.pop().unwrap();
let bbox = tos.bbox.unwrap_or_default();
let bbox_f32_4 = rect_to_f32_4(bbox);
self.elements
.push(Element::EndClip(Clip { bbox: bbox_f32_4 }));
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 {
self.union_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) {
if !self.clip_stack.is_empty() {
let bbox = f();
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 {
Some(old_bbox.union(bbox))
} else {
Some(bbox)
};
}
}
} }
impl Text for PietGpuText { impl Text for PietGpuText {
@ -410,3 +533,20 @@ impl IntoBrush<PietGpuRenderContext> for PietGpuBrush {
fn to_f32_2(point: Point) -> [f32; 2] { fn to_f32_2(point: Point) -> [f32; 2] {
[point.x as f32, point.y as f32] [point.x as f32, point.y as f32]
} }
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,
]
}
fn to_scene_transform(transform: Affine) -> Transform {
let c = transform.as_coeffs();
Transform {
mat: [c[0] as f32, c[1] as f32, c[2] as f32, c[3] as f32],
translate: [c[4] as f32, c[5] as f32],
}
}