From aa83d782ed5b8f9e9e0aa226ff11a64e3d63b3c2 Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Thu, 30 Apr 2020 17:06:01 -0700 Subject: [PATCH 1/3] Fills Adds fills, and has more or less working tiger render (with artifacts). --- Cargo.lock | 16 ++++ piet-gpu-types/src/fill_seg.rs | 37 ++++++++ piet-gpu-types/src/lib.rs | 1 + piet-gpu-types/src/main.rs | 1 + piet-gpu-types/src/ptcl.rs | 6 +- piet-gpu/Cargo.toml | 1 + piet-gpu/Ghostscript_Tiger.svg | 142 ++++++++++++++++++++++++++++ piet-gpu/shader/build.ninja | 6 +- piet-gpu/shader/fill_seg.h | 130 ++++++++++++++++++++++++++ piet-gpu/shader/kernel1.comp | 31 ++++++- piet-gpu/shader/kernel1.spv | Bin 16320 -> 18416 bytes piet-gpu/shader/kernel2f.comp | 165 +++++++++++++++++++++++++++++++++ piet-gpu/shader/kernel2f.spv | Bin 0 -> 21220 bytes piet-gpu/shader/kernel3.comp | 27 +++++- piet-gpu/shader/kernel3.spv | Bin 18936 -> 24976 bytes piet-gpu/shader/kernel4.comp | 38 +++++++- piet-gpu/shader/kernel4.spv | Bin 12328 -> 19572 bytes piet-gpu/shader/ptcl.h | 20 ++-- piet-gpu/shader/setup.h | 6 +- piet-gpu/src/main.rs | 87 ++++++++++++++--- piet-gpu/src/pico_svg.rs | 80 ++++++++++++++++ piet-gpu/src/render_ctx.rs | 26 +++++- 22 files changed, 785 insertions(+), 35 deletions(-) create mode 100644 piet-gpu-types/src/fill_seg.rs create mode 100644 piet-gpu/Ghostscript_Tiger.svg create mode 100644 piet-gpu/shader/fill_seg.h create mode 100644 piet-gpu/shader/kernel2f.comp create mode 100644 piet-gpu/shader/kernel2f.spv create mode 100644 piet-gpu/src/pico_svg.rs diff --git a/Cargo.lock b/Cargo.lock index 5f9b877..af141b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -139,6 +139,7 @@ dependencies = [ "piet-gpu-types", "png", "rand", + "roxmltree", ] [[package]] @@ -243,6 +244,15 @@ dependencies = [ "rand_core", ] +[[package]] +name = "roxmltree" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5001f134077069d87f77c8b9452b690df2445f7a43f1c7ca4a1af8dd505789d" +dependencies = [ + "xmlparser", +] + [[package]] name = "syn" version = "1.0.17" @@ -287,3 +297,9 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "xmlparser" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccb4240203dadf40be2de9369e5c6dec1bf427528115b030baca3334c18362d7" diff --git a/piet-gpu-types/src/fill_seg.rs b/piet-gpu-types/src/fill_seg.rs new file mode 100644 index 0000000..2242a84 --- /dev/null +++ b/piet-gpu-types/src/fill_seg.rs @@ -0,0 +1,37 @@ +use piet_gpu_derive::piet_gpu; + +// Structures representing segments for fill items. + +// There is some cut'n'paste here from stroke segments, which can be +// traced to the fact that buffers in GLSL are basically global. +// Maybe there's a way to address that, but in the meantime living +// with the duplication is easiest. + +piet_gpu! { + #[gpu_write] + mod fill_seg { + struct FillTileHeader { + n: u32, + items: Ref, + } + + struct FillItemHeader { + backdrop: i32, + segments: Ref, + } + + // TODO: strongly consider using f16. If so, these would be + // relative to the tile. We're doing f32 for now to minimize + // divergence from piet-metal originals. + struct FillSegment { + start: [f32; 2], + end: [f32; 2], + } + + struct FillSegChunk { + n: u32, + next: Ref, + // Segments follow (could represent this as a variable sized array). + } + } +} diff --git a/piet-gpu-types/src/lib.rs b/piet-gpu-types/src/lib.rs index db9516f..d85df70 100644 --- a/piet-gpu-types/src/lib.rs +++ b/piet-gpu-types/src/lib.rs @@ -1,4 +1,5 @@ pub mod encoder; +pub mod fill_seg; pub mod ptcl; pub mod scene; pub mod segment; diff --git a/piet-gpu-types/src/main.rs b/piet-gpu-types/src/main.rs index 834f1b6..c0b9d7e 100644 --- a/piet-gpu-types/src/main.rs +++ b/piet-gpu-types/src/main.rs @@ -7,6 +7,7 @@ fn main() { "scene" => print!("{}", piet_gpu_types::scene::gen_gpu_scene()), "tilegroup" => print!("{}", piet_gpu_types::tilegroup::gen_gpu_tilegroup()), "segment" => print!("{}", piet_gpu_types::segment::gen_gpu_segment()), + "fill_seg" => print!("{}", piet_gpu_types::fill_seg::gen_gpu_fill_seg()), "ptcl" => print!("{}", piet_gpu_types::ptcl::gen_gpu_ptcl()), "test" => print!("{}", piet_gpu_types::test::gen_gpu_test()), _ => println!("Oops, unknown module name"), diff --git a/piet-gpu-types/src/ptcl.rs b/piet-gpu-types/src/ptcl.rs index 36274c4..911f2c8 100644 --- a/piet-gpu-types/src/ptcl.rs +++ b/piet-gpu-types/src/ptcl.rs @@ -19,8 +19,10 @@ piet_gpu! { rgba_color: u32, } struct CmdFill { - start: [f32; 2], - end: [f32; 2], + // Should be Ref if we had cross-module references. + seg_ref: u32, + backdrop: i32, + rgba_color: u32, } struct CmdFillEdge { // The sign is only one bit. diff --git a/piet-gpu/Cargo.toml b/piet-gpu/Cargo.toml index b082868..2555e62 100644 --- a/piet-gpu/Cargo.toml +++ b/piet-gpu/Cargo.toml @@ -17,3 +17,4 @@ kurbo = "0.5.11" piet = "0.0.12" png = "0.16.2" rand = "0.7.3" +roxmltree = "0.11" diff --git a/piet-gpu/Ghostscript_Tiger.svg b/piet-gpu/Ghostscript_Tiger.svg new file mode 100644 index 0000000..033611d --- /dev/null +++ b/piet-gpu/Ghostscript_Tiger.svg @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/piet-gpu/shader/build.ninja b/piet-gpu/shader/build.ninja index 4a8181f..0aaecae 100644 --- a/piet-gpu/shader/build.ninja +++ b/piet-gpu/shader/build.ninja @@ -13,6 +13,8 @@ build kernel1.spv: glsl kernel1.comp | scene.h tilegroup.h setup.h build kernel2s.spv: glsl kernel2s.comp | scene.h tilegroup.h segment.h setup.h -build kernel3.spv: glsl kernel3.comp | scene.h tilegroup.h segment.h ptcl.h setup.h +build kernel2f.spv: glsl kernel2f.comp | scene.h tilegroup.h fill_seg.h setup.h -build kernel4.spv: glsl kernel4.comp | ptcl.h segment.h setup.h +build kernel3.spv: glsl kernel3.comp | scene.h tilegroup.h segment.h fill_seg.h ptcl.h setup.h + +build kernel4.spv: glsl kernel4.comp | ptcl.h segment.h fill_seg.h setup.h diff --git a/piet-gpu/shader/fill_seg.h b/piet-gpu/shader/fill_seg.h new file mode 100644 index 0000000..abe199f --- /dev/null +++ b/piet-gpu/shader/fill_seg.h @@ -0,0 +1,130 @@ +// Code auto-generated by piet-gpu-derive + +struct FillTileHeaderRef { + uint offset; +}; + +struct FillItemHeaderRef { + uint offset; +}; + +struct FillSegmentRef { + uint offset; +}; + +struct FillSegChunkRef { + uint offset; +}; + +struct FillTileHeader { + uint n; + FillItemHeaderRef items; +}; + +#define FillTileHeader_size 8 + +FillTileHeaderRef FillTileHeader_index(FillTileHeaderRef ref, uint index) { + return FillTileHeaderRef(ref.offset + index * FillTileHeader_size); +} + +struct FillItemHeader { + int backdrop; + FillSegChunkRef segments; +}; + +#define FillItemHeader_size 8 + +FillItemHeaderRef FillItemHeader_index(FillItemHeaderRef ref, uint index) { + return FillItemHeaderRef(ref.offset + index * FillItemHeader_size); +} + +struct FillSegment { + vec2 start; + vec2 end; +}; + +#define FillSegment_size 16 + +FillSegmentRef FillSegment_index(FillSegmentRef ref, uint index) { + return FillSegmentRef(ref.offset + index * FillSegment_size); +} + +struct FillSegChunk { + uint n; + FillSegChunkRef next; +}; + +#define FillSegChunk_size 8 + +FillSegChunkRef FillSegChunk_index(FillSegChunkRef ref, uint index) { + return FillSegChunkRef(ref.offset + index * FillSegChunk_size); +} + +FillTileHeader FillTileHeader_read(FillTileHeaderRef ref) { + uint ix = ref.offset >> 2; + uint raw0 = fill_seg[ix + 0]; + uint raw1 = fill_seg[ix + 1]; + FillTileHeader s; + s.n = raw0; + s.items = FillItemHeaderRef(raw1); + return s; +} + +void FillTileHeader_write(FillTileHeaderRef ref, FillTileHeader s) { + uint ix = ref.offset >> 2; + fill_seg[ix + 0] = s.n; + fill_seg[ix + 1] = s.items.offset; +} + +FillItemHeader FillItemHeader_read(FillItemHeaderRef ref) { + uint ix = ref.offset >> 2; + uint raw0 = fill_seg[ix + 0]; + uint raw1 = fill_seg[ix + 1]; + FillItemHeader s; + s.backdrop = int(raw0); + s.segments = FillSegChunkRef(raw1); + return s; +} + +void FillItemHeader_write(FillItemHeaderRef ref, FillItemHeader s) { + uint ix = ref.offset >> 2; + fill_seg[ix + 0] = uint(s.backdrop); + fill_seg[ix + 1] = s.segments.offset; +} + +FillSegment FillSegment_read(FillSegmentRef ref) { + uint ix = ref.offset >> 2; + uint raw0 = fill_seg[ix + 0]; + uint raw1 = fill_seg[ix + 1]; + uint raw2 = fill_seg[ix + 2]; + uint raw3 = fill_seg[ix + 3]; + FillSegment s; + s.start = vec2(uintBitsToFloat(raw0), uintBitsToFloat(raw1)); + s.end = vec2(uintBitsToFloat(raw2), uintBitsToFloat(raw3)); + return s; +} + +void FillSegment_write(FillSegmentRef ref, FillSegment s) { + uint ix = ref.offset >> 2; + fill_seg[ix + 0] = floatBitsToUint(s.start.x); + fill_seg[ix + 1] = floatBitsToUint(s.start.y); + fill_seg[ix + 2] = floatBitsToUint(s.end.x); + fill_seg[ix + 3] = floatBitsToUint(s.end.y); +} + +FillSegChunk FillSegChunk_read(FillSegChunkRef ref) { + uint ix = ref.offset >> 2; + uint raw0 = fill_seg[ix + 0]; + uint raw1 = fill_seg[ix + 1]; + FillSegChunk s; + s.n = raw0; + s.next = FillSegChunkRef(raw1); + return s; +} + +void FillSegChunk_write(FillSegChunkRef ref, FillSegChunk s) { + uint ix = ref.offset >> 2; + fill_seg[ix + 0] = s.n; + fill_seg[ix + 1] = s.next.offset; +} + diff --git a/piet-gpu/shader/kernel1.comp b/piet-gpu/shader/kernel1.comp index ce99005..6b76c53 100644 --- a/piet-gpu/shader/kernel1.comp +++ b/piet-gpu/shader/kernel1.comp @@ -52,10 +52,19 @@ void main() { TileGroupRef stroke_start = TileGroupRef(tg_ref.offset + TILEGROUP_STROKE_START); ChunkRef stroke_chunk_start = ChunkRef(stroke_start.offset + 4); InstanceRef stroke_ref = InstanceRef(stroke_chunk_start.offset + Chunk_size); - uint stroke_limit = stroke_start.offset + TILEGROUP_INITIAL_ALLOC - Instance_size; + uint stroke_limit = stroke_start.offset + TILEGROUP_INITIAL_STROKE_ALLOC - Instance_size; uint stroke_chunk_n = 0; uint stroke_n = 0; + // State for fill references. All this is a bit cut'n'paste, but making a + // proper abstraction isn't easy. + TileGroupRef fill_start = TileGroupRef(tg_ref.offset + TILEGROUP_FILL_START); + ChunkRef fill_chunk_start = ChunkRef(fill_start.offset + 4); + InstanceRef fill_ref = InstanceRef(fill_chunk_start.offset + Chunk_size); + uint fill_limit = fill_start.offset + TILEGROUP_INITIAL_FILL_ALLOC - Instance_size; + uint fill_chunk_n = 0; + uint fill_n = 0; + vec2 xy0 = vec2(gl_GlobalInvocationID.xy) * vec2(TILEGROUP_WIDTH_PX, TILEGROUP_HEIGHT_PX); PietItemRef root = PietItemRef(0); SimpleGroup group = PietItem_Group_read(root); @@ -100,6 +109,20 @@ void main() { Instance_write(stroke_ref, ins); stroke_chunk_n++; stroke_ref.offset += Instance_size; + } else if (tag == PietItem_Fill) { + if (fill_ref.offset > fill_limit) { + uint new_fill = atomicAdd(alloc, TILEGROUP_FILL_ALLOC); + Chunk_write(fill_chunk_start, Chunk(fill_chunk_n, ChunkRef(new_fill))); + fill_chunk_start = ChunkRef(new_fill); + fill_ref = InstanceRef(new_fill + Chunk_size); + fill_n += fill_chunk_n; + fill_chunk_n = 0; + fill_limit = new_fill + TILEGROUP_FILL_ALLOC - Instance_size; + } + Instance_write(fill_ref, ins); + fill_chunk_n++; + fill_ref.offset += Instance_size; + } } if (is_group) { @@ -129,4 +152,10 @@ void main() { Chunk_write(stroke_chunk_start, Chunk(stroke_chunk_n, ChunkRef(0))); } tilegroup[stroke_start.offset >> 2] = stroke_n; + + fill_n += fill_chunk_n; + if (fill_n > 0) { + Chunk_write(fill_chunk_start, Chunk(fill_chunk_n, ChunkRef(0))); + } + tilegroup[fill_start.offset >> 2] = fill_n; } diff --git a/piet-gpu/shader/kernel1.spv b/piet-gpu/shader/kernel1.spv index 8430d74cfd8bc38871df6cce137a081d4d2602c8..358151d2c1cc1b4fad315b3039ac3abce100f60b 100644 GIT binary patch literal 18416 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 literal 16320 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 seg_limit) { + seg_chunk_ref.offset = atomicAdd(alloc, SEG_CHUNK_ALLOC); + seg_limit = seg_chunk_ref.offset + SEG_CHUNK_ALLOC - FillSegment_size; + } + first_seg_chunk = seg_chunk_ref; + } else if (seg_chunk_ref.offset + FillSegChunk_size + FillSegment_size * chunk_n_segs > seg_limit) { + uint new_chunk_ref = atomicAdd(alloc, SEG_CHUNK_ALLOC); + seg_limit = new_chunk_ref + SEG_CHUNK_ALLOC - FillSegment_size; + FillSegChunk_write(seg_chunk_ref, FillSegChunk(chunk_n_segs, FillSegChunkRef(new_chunk_ref))); + seg_chunk_ref.offset = new_chunk_ref; + chunk_n_segs = 0; + } + +} + +void main() { + uint tile_ix = gl_GlobalInvocationID.y * WIDTH_IN_TILES + gl_GlobalInvocationID.x; + uint tilegroup_ix = gl_GlobalInvocationID.y * WIDTH_IN_TILEGROUPS + + (gl_GlobalInvocationID.x / TILEGROUP_WIDTH_TILES); + vec2 xy0 = vec2(gl_GlobalInvocationID.xy) * vec2(TILE_WIDTH_PX, TILE_HEIGHT_PX); + TileGroupRef fill_start = TileGroupRef(tilegroup_ix * TILEGROUP_STRIDE + TILEGROUP_FILL_START); + uint fill_n = tilegroup[fill_start.offset >> 2]; + + FillTileHeaderRef tile_header_ref = FillTileHeaderRef(tile_ix * FillTileHeader_size); + if (fill_n > 0) { + ChunkRef chunk_ref = ChunkRef(fill_start.offset + 4); + Chunk chunk = Chunk_read(chunk_ref); + InstanceRef fill_ref = InstanceRef(chunk_ref.offset + Chunk_size); + FillItemHeaderRef item_header = FillItemHeaderRef(atomicAdd(alloc, fill_n * FillItemHeader_size)); + FillTileHeader_write(tile_header_ref, FillTileHeader(fill_n, item_header)); + FillSegChunkRef seg_chunk_ref = FillSegChunkRef(0); + uint seg_limit = 0; + // Iterate through items; fill_n holds count remaining. + while (true) { + if (chunk.chunk_n == 0) { + chunk_ref = chunk.next; + if (chunk_ref.offset == 0) { + break; + } + chunk = Chunk_read(chunk_ref); + fill_ref = InstanceRef(chunk_ref.offset + Chunk_size); + } + Instance ins = Instance_read(fill_ref); + PietFill fill = PietItem_Fill_read(PietItemRef(ins.item_ref)); + + // Process the fill polyline item. + uint max_n_segs = fill.n_points - 1; + uint chunk_n_segs = 0; + int backdrop = 0; + FillSegChunkRef seg_chunk_ref; + FillSegChunkRef first_seg_chunk = FillSegChunkRef(0); + vec2 start = Point_read(fill.points).xy; + for (uint j = 0; j < max_n_segs; j++) { + fill.points.offset += Point_size; + vec2 end = Point_read(fill.points).xy; + + // Process one segment. + + // TODO: I think this would go more smoothly (and be easier to + // make numerically robust) if it were based on clipping the line + // to the tile box. See: + // https://tavianator.com/fast-branchless-raybounding-box-intersections/ + vec2 xymin = min(start, end); + vec2 xymax = max(start, end); + float a = end.y - start.y; + float b = start.x - end.x; + float c = -(a * start.x + b * start.y); + vec2 xy1 = xy0 + vec2(TILE_WIDTH_PX, TILE_HEIGHT_PX); + float ytop = max(xy0.y, xymin.y); + float ybot = min(xy1.y, xymax.y); + float s00 = sign(b * ytop + a * xy0.x + c); + float s01 = sign(b * ytop + a * xy1.x + c); + float s10 = sign(b * ybot + a * xy0.x + c); + float s11 = sign(b * ybot + a * xy1.x + c); + float sTopLeft = sign(b * xy0.y + a * xy0.x + c); + if (sTopLeft == sign(a) && xymin.y <= xy0.y && xymax.y > xy0.y) { + backdrop -= int(s00); + } + + // This is adapted from piet-metal but could be improved. + + if (max(xymin.x, xy0.x) < min(xymax.x, xy1.x) + && ytop < ybot + && s00 * s01 + s00 * s10 + s00 * s11 < 3.0) + { + if (xymin.x < xy0.x) { + float yEdge = mix(start.y, end.y, (start.x - xy0.x) / b); + if (yEdge >= xy0.y && yEdge < xy1.y) { + // This is encoded the same as a general fill segment, but could be + // special-cased, either here or in rendering. (It was special-cased + // in piet-metal). + FillSegment edge_seg; + if (b > 0.0) { + end = vec2(xy0.x, yEdge); + edge_seg.start = end; + edge_seg.end = vec2(xy0.x, xy1.y); + } else { + start = vec2(xy0.x, yEdge); + edge_seg.start = vec2(xy0.x, xy1.y); + edge_seg.end = start; + } + alloc_chunk(chunk_n_segs, seg_chunk_ref, first_seg_chunk, seg_limit); + FillSegment_write(FillSegmentRef(seg_chunk_ref.offset + FillSegChunk_size + FillSegment_size * chunk_n_segs), edge_seg); + chunk_n_segs++; + } + } + alloc_chunk(chunk_n_segs, seg_chunk_ref, first_seg_chunk, seg_limit); + FillSegment seg = FillSegment(start, end); + FillSegment_write(FillSegmentRef(seg_chunk_ref.offset + FillSegChunk_size + FillSegment_size * chunk_n_segs), seg); + chunk_n_segs++; + } + + start = end; + } + FillItemHeader_write(item_header, FillItemHeader(backdrop, first_seg_chunk)); + if (chunk_n_segs != 0) { + FillSegChunk_write(seg_chunk_ref, FillSegChunk(chunk_n_segs, FillSegChunkRef(0))); + seg_chunk_ref.offset += FillSegChunk_size + FillSegment_size * chunk_n_segs; + } + + fill_ref.offset += Instance_size; + chunk.chunk_n--; + item_header.offset += FillItemHeader_size; + } + } else { + // As an optimization, we could just write 0 for the size. + FillTileHeader_write(tile_header_ref, FillTileHeader(fill_n, FillItemHeaderRef(0))); + } +} diff --git a/piet-gpu/shader/kernel2f.spv b/piet-gpu/shader/kernel2f.spv new file mode 100644 index 0000000000000000000000000000000000000000..960741ee7a3b86336e30a2b0921c73e671476087 GIT binary patch literal 21220 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Úq?o#H^z8tKNdc;`) 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 diff --git a/piet-gpu/shader/kernel4.comp b/piet-gpu/shader/kernel4.comp index e30372a..d369f0e 100644 --- a/piet-gpu/shader/kernel4.comp +++ b/piet-gpu/shader/kernel4.comp @@ -19,12 +19,18 @@ layout(set = 0, binding = 1) buffer SegmentBuf { uint[] segment; }; -layout(set = 0, binding = 2) buffer ImageBuf { +// Used readonly +layout(set = 0, binding = 2) buffer FillSegBuf { + uint[] fill_seg; +}; + +layout(set = 0, binding = 3) buffer ImageBuf { uint[] image; }; #include "ptcl.h" #include "segment.h" +#include "fill_seg.h" #include "setup.h" @@ -70,6 +76,36 @@ void main() { alpha = clamp(stroke.half_width + 0.5 - df, 0.0, 1.0); rgb = mix(rgb, fg_rgba.rgb, alpha * fg_rgba.a); break; + case Cmd_Fill: + CmdFill fill = Cmd_Fill_read(cmd_ref); + // Probably better to store as float, but conversion is no doubt cheap. + float area = float(fill.backdrop); + FillSegChunkRef fill_seg_chunk_ref = FillSegChunkRef(fill.seg_ref); + do { + FillSegChunk seg_chunk = FillSegChunk_read(fill_seg_chunk_ref); + for (int i = 0; i < seg_chunk.n; i++) { + FillSegment seg = FillSegment_read(FillSegmentRef(fill_seg_chunk_ref.offset + FillSegChunk_size + FillSegment_size * i)); + vec2 start = seg.start - xy; + vec2 end = seg.end - xy; + vec2 window = clamp(vec2(start.y, end.y), 0.0, 1.0); + if (window.x != window.y) { + vec2 t = (window - start.y) / (end.y - start.y); + vec2 xs = vec2(mix(start.x, end.x, t.x), mix(start.x, end.x, t.y)); + float xmin = min(min(xs.x, xs.y), 1.0) - 1e-6; + float xmax = max(xs.x, xs.y); + float b = min(xmax, 1.0); + float c = max(b, 0.0); + float d = max(xmin, 0.0); + float a = (b + 0.5 * (d * d - c * c) - xmin) / (xmax - xmin); + area += a * (window.x - window.y); + } + } + fill_seg_chunk_ref = seg_chunk.next; + } while (fill_seg_chunk_ref.offset != 0); + fg_rgba = unpackUnorm4x8(fill.rgba_color).wzyx; + alpha = min(abs(area), 1.0); + rgb = mix(rgb, fg_rgba.rgb, alpha * fg_rgba.a); + break; case Cmd_Jump: cmd_ref = CmdRef(Cmd_Jump_read(cmd_ref).new_ref); continue; diff --git a/piet-gpu/shader/kernel4.spv b/piet-gpu/shader/kernel4.spv index 99067bbca2aba8223f0bf7624c25dcf2c0035577..aab4107e6338ba502f27fc50ae175af3bd6d0d4b 100644 GIT binary patch literal 19572 zcmZ{r34m5r-NrA@fGCK9JBos+;D-CYfvC8kptzQX!^{99!_qJ)mTQRGu4!qDEt)M_ zrj|>lmZni!R<>xXnw4d{mTTqr`@QddW)Ab^&GVh-dH&1!pL5T>_q>i@%Npa?tg1oP zQ2w`fOVzkWR%@VC)v#(vqn|c?*7QkBdpjoWvF9#2jHsH9`m9^6Sq-Ldr7djhT0~b} ziQ&_<^@vv=Nn5XO#4(j3U$SKO(%y5rJ7;%yE$W=z z(bd!0-rIFnr+z~Vzph2?-OD;khrxK1H4J8KCRR;d*fF)Mr@g!L_|Cad)WHp1ym;>1 zrJcPM%3$VN_||@EUhSQWdOLfXv1-ntr>&!F*;2J3jacW?GjC4Y?Doaoi}4*?4QHK- zoU?m6+d3vL?d@6C-n&y-)lSQH-F1KbTdH-;L7CR>df_#zw`cK!vUYW^vi4E%K3}kN zRP$QeIggR&HtJNf_W5nyb7!C3)zLe@S-<-Ks((ZDzPtnL@2jl8{tfVN)<3w~xEXtR zS9f<~<*iB0tH1WvYBZcN^lQ{D_bTgFv+g-Hx`$O`(CeH7tFG(m zuX%HPQ}dS1xTBUWT+&ywnpau#I5_$BYt$_FTGV-VW3AmyTf=Lgfi=|m`)e4FZ?lHh zYTIU9-}rK`{_)qUwuh5@c5mCfVnY$qOxf5=+u_s9Ijovg`!^qif#s|ow^I?fTOH@3 zy>XO#y`|cr^w~Mcis77CKAQzkbIOlNyifkI!@( zW2<}h*Epn_1+R0iwvOsvSx5Qm>o}r139XF%UxlWCay?HZu32kKbt<@Px%Z{Dnyub; z_AYb{-bO#Ox4rw2Wz9Vx_gaEx@4aaka2bClJ@GzH4erYh?xWb?XEpimP2N(S2Uyy$ zwN&q?PaRi+%Q~)U^1i&+HhG=5rMe!TJU4@r=eDL_-+boBTB;}d{93E$z)RYC+7{Bg2G0-RuMXgAa1;CeTdE=OjI%blf8IPP@Oqzn3mSV! z&1-4%oxzwb)reYi{PmkT`sTPvlh<=>t;RI@?&dIdJUnyW9h_SBZu<4D-2qKr=WVGD zDdLVU;%2NCH@G?uUhmO*4nrE6h?j`fv zYW@s-F2^d4dyU-B!#UV-P;2IT0p34WOZ6hWgT3c^ysR&0vUWcVjoGUYZX9I#^m?2@ z9B#@yt?;gT{_BFh?apoVYJP*O&Ebvn^I4sZSBpMdp>-@-?Cm9{rJ4x$W1{T@u=}WK zYpqU%_m9gyg12#eZQ9qYPOJUhcjZK!M+de(xz69YTE3QQK0NbS+VpSKaqenTk0(uYfnd%dORA;Iq3Hbu2!+IiG9b&H1!cH-MKjQ|ETOcwzH*iTu6>Z{uy$t`mwZ)iCddqZk{c;ca9Uy_;L6Qr&l+Rx!0pl?vY^gsu@43wwW{W>x0$xHQwJ< ziQfclyqfcHj$>(qXy$W`^;J*4&A{r$n$O=n$u|~kJ~eIQ=o9A}%g55RZA+iH31H*Y zob$xmrY_%+-ZcpK<6;frdw`u|_ Ak~@~?`;2uUcuktx7UcF^@0!~j>tK2{{fwPT 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 j4cHv> 2; - ptcl[ix + 0] = floatBitsToUint(s.start.x); - ptcl[ix + 1] = floatBitsToUint(s.start.y); - ptcl[ix + 2] = floatBitsToUint(s.end.x); - ptcl[ix + 3] = floatBitsToUint(s.end.y); + ptcl[ix + 0] = s.seg_ref; + ptcl[ix + 1] = uint(s.backdrop); + ptcl[ix + 2] = s.rgba_color; } CmdFillEdge CmdFillEdge_read(CmdFillEdgeRef ref) { diff --git a/piet-gpu/shader/setup.h b/piet-gpu/shader/setup.h index 2bebabe..3d9cd53 100644 --- a/piet-gpu/shader/setup.h +++ b/piet-gpu/shader/setup.h @@ -19,10 +19,14 @@ // there is a region of size TILEGROUP_STRIDE for each tilegroup. // At offset 0 are the main instances, encoded with Jump. At offset // TILEGROUP_STROKE_START are the stroke instances, encoded with -// Head and Link. +// Head and Link. Similarly for fill. #define TILEGROUP_STRIDE 2048 #define TILEGROUP_STROKE_START 1024 +#define TILEGROUP_FILL_START 1536 #define TILEGROUP_STROKE_ALLOC 1024 +#define TILEGROUP_FILL_ALLOC 1024 +#define TILEGROUP_INITIAL_STROKE_ALLOC 512 +#define TILEGROUP_INITIAL_FILL_ALLOC 512 // TODO: compute all these diff --git a/piet-gpu/src/main.rs b/piet-gpu/src/main.rs index 4416487..c40b4d5 100644 --- a/piet-gpu/src/main.rs +++ b/piet-gpu/src/main.rs @@ -10,9 +10,11 @@ use piet::{Color, RenderContext}; use piet_gpu_hal::vulkan::VkInstance; use piet_gpu_hal::{CmdBuf, Device, MemFlags}; +mod pico_svg; mod render_ctx; use render_ctx::PietGpuRenderContext; +use pico_svg::PicoSvg; const WIDTH: usize = 2048; const HEIGHT: usize = 1536; @@ -44,14 +46,22 @@ fn render_scene(rc: &mut impl RenderContext) { let circle = Circle::new(center, radius); rc.fill(circle, &color); } + let mut path = BezPath::new(); + path.move_to((100.0, 1150.0)); + path.line_to((200.0, 1200.0)); + path.line_to((150.0, 1250.0)); + path.close_path(); + rc.fill(path, &Color::rgb8(128, 0, 128)); rc.stroke( Line::new((100.0, 100.0), (200.0, 150.0)), &Color::WHITE, 5.0, ); - render_cardioid(rc); + //render_cardioid(rc); + render_tiger(rc); } +#[allow(unused)] fn render_cardioid(rc: &mut impl RenderContext) { let n = 91; let dth = std::f64::consts::PI * 2.0 / (n as f64); @@ -69,6 +79,17 @@ fn render_cardioid(rc: &mut impl RenderContext) { rc.stroke(&path, &Color::BLACK, 2.0); } +fn render_tiger(rc: &mut impl RenderContext) { + let xml_str = std::str::from_utf8(include_bytes!("../Ghostscript_Tiger.svg")).unwrap(); + let start = std::time::Instant::now(); + let svg = PicoSvg::load(xml_str, 8.0).unwrap(); + println!("parsing time: {:?}", start.elapsed()); + + let start = std::time::Instant::now(); + svg.render(rc); + println!("flattening and encoding time: {:?}", start.elapsed()); +} + #[allow(unused)] fn dump_scene(buf: &[u8]) { for i in 0..(buf.len() / 4) { @@ -107,6 +128,7 @@ fn main() { let tilegroup_buf = device.create_buffer(4 * 1024 * 1024, dev).unwrap(); let ptcl_buf = device.create_buffer(48 * 1024 * 1024, dev).unwrap(); let segment_buf = device.create_buffer(64 * 1024 * 1024, dev).unwrap(); + let fill_seg_buf = device.create_buffer(64 * 1024 * 1024, dev).unwrap(); let image_buf = device .create_buffer((WIDTH * HEIGHT * 4) as u64, host) .unwrap(); @@ -144,6 +166,26 @@ fn main() { ) .unwrap(); + let k2f_alloc_buf_host = device.create_buffer(4, host).unwrap(); + let k2f_alloc_buf_dev = device.create_buffer(4, dev).unwrap(); + let k2f_alloc_start = WIDTH_IN_TILES * HEIGHT_IN_TILES * K2_PER_TILE_SIZE; + device + .write_buffer(&k2f_alloc_buf_host, &[k2f_alloc_start as u32]) + .unwrap(); + let k2f_code = include_bytes!("../shader/kernel2f.spv"); + let k2f_pipeline = device.create_simple_compute_pipeline(k2f_code, 4).unwrap(); + let k2f_ds = device + .create_descriptor_set( + &k2f_pipeline, + &[ + &scene_dev, + &tilegroup_buf, + &fill_seg_buf, + &k2f_alloc_buf_dev, + ], + ) + .unwrap(); + let k3_alloc_buf_host = device.create_buffer(4, host).unwrap(); let k3_alloc_buf_dev = device.create_buffer(4, dev).unwrap(); let k3_alloc_start = WIDTH_IN_TILES * HEIGHT_IN_TILES * PTCL_INITIAL_ALLOC; @@ -151,7 +193,7 @@ fn main() { .write_buffer(&k3_alloc_buf_host, &[k3_alloc_start as u32]) .unwrap(); let k3_code = include_bytes!("../shader/kernel3.spv"); - let k3_pipeline = device.create_simple_compute_pipeline(k3_code, 5).unwrap(); + let k3_pipeline = device.create_simple_compute_pipeline(k3_code, 6).unwrap(); let k3_ds = device .create_descriptor_set( &k3_pipeline, @@ -159,6 +201,7 @@ fn main() { &scene_dev, &tilegroup_buf, &segment_buf, + &fill_seg_buf, &ptcl_buf, &k3_alloc_buf_dev, ], @@ -166,18 +209,26 @@ fn main() { .unwrap(); let k4_code = include_bytes!("../shader/kernel4.spv"); - let k4_pipeline = device.create_simple_compute_pipeline(k4_code, 3).unwrap(); + let k4_pipeline = device.create_simple_compute_pipeline(k4_code, 4).unwrap(); let k4_ds = device - .create_descriptor_set(&k4_pipeline, &[&ptcl_buf, &segment_buf, &image_dev]) + .create_descriptor_set( + &k4_pipeline, + &[&ptcl_buf, &segment_buf, &fill_seg_buf, &image_dev], + ) .unwrap(); - let query_pool = device.create_query_pool(5).unwrap(); + let query_pool = device.create_query_pool(6).unwrap(); let mut cmd_buf = device.create_cmd_buf().unwrap(); cmd_buf.begin(); cmd_buf.copy_buffer(&scene_buf, &scene_dev); + // Note: we could use one alloc buf and reuse it. But we'll stick with + // multiple ones for clarity. cmd_buf.copy_buffer(&k1_alloc_buf_host, &k1_alloc_buf_dev); cmd_buf.copy_buffer(&k2s_alloc_buf_host, &k2s_alloc_buf_dev); + cmd_buf.copy_buffer(&k2f_alloc_buf_host, &k2f_alloc_buf_dev); cmd_buf.copy_buffer(&k3_alloc_buf_host, &k3_alloc_buf_dev); + // Note: these clears aren't necessary, and are here to make inspection + // of the buffers cleaner. Can likely be removed. cmd_buf.clear_buffer(&tilegroup_buf); cmd_buf.clear_buffer(&ptcl_buf); cmd_buf.memory_barrier(); @@ -196,20 +247,30 @@ fn main() { ((WIDTH / 512) as u32, (HEIGHT / 16) as u32, 1), ); cmd_buf.write_timestamp(&query_pool, 2); + // Note: this barrier is not necessary (k2f does not depend on + // k2s output), but I'm keeping it here to increase transparency + // of performance. + cmd_buf.memory_barrier(); + cmd_buf.dispatch( + &k2f_pipeline, + &k2f_ds, + ((WIDTH / 512) as u32, (HEIGHT / 16) as u32, 2), + ); + cmd_buf.write_timestamp(&query_pool, 3); cmd_buf.memory_barrier(); cmd_buf.dispatch( &k3_pipeline, &k3_ds, - ((WIDTH / 512) as u32, (HEIGHT / 16) as u32, 1), + ((WIDTH / 512) as u32, (HEIGHT / 16) as u32, 3), ); - cmd_buf.write_timestamp(&query_pool, 3); + cmd_buf.write_timestamp(&query_pool, 4); cmd_buf.memory_barrier(); cmd_buf.dispatch( &k4_pipeline, &k4_ds, ((WIDTH / TILE_W) as u32, (HEIGHT / TILE_H) as u32, 1), ); - cmd_buf.write_timestamp(&query_pool, 4); + cmd_buf.write_timestamp(&query_pool, 5); cmd_buf.memory_barrier(); cmd_buf.copy_buffer(&image_dev, &image_buf); cmd_buf.finish(); @@ -217,17 +278,21 @@ fn main() { let timestamps = device.reap_query_pool(query_pool).unwrap(); println!("Kernel 1 time: {:.3}ms", timestamps[0] * 1e3); println!( - "Kernel 2 time: {:.3}ms", + "Kernel 2s time: {:.3}ms", (timestamps[1] - timestamps[0]) * 1e3 ); println!( - "Kernel 3 time: {:.3}ms", + "Kernel 2f time: {:.3}ms", (timestamps[2] - timestamps[1]) * 1e3 ); println!( - "Render time: {:.3}ms", + "Kernel 3 time: {:.3}ms", (timestamps[3] - timestamps[2]) * 1e3 ); + println!( + "Render time: {:.3}ms", + (timestamps[4] - timestamps[3]) * 1e3 + ); /* let mut k1_data: Vec = Default::default(); diff --git a/piet-gpu/src/pico_svg.rs b/piet-gpu/src/pico_svg.rs new file mode 100644 index 0000000..2423dda --- /dev/null +++ b/piet-gpu/src/pico_svg.rs @@ -0,0 +1,80 @@ +//! A loader for a tiny fragment of SVG + +use std::str::FromStr; + +use roxmltree::Document; + +use kurbo::BezPath; + +use piet::{Color, RenderContext}; + +pub struct PicoSvg { + items: Vec, +} + +pub enum Item { + Fill(FillItem), + Stroke(StrokeItem), +} + +pub struct StrokeItem { + width: f64, + color: Color, + path: BezPath, +} + +pub struct FillItem { + color: Color, + path: BezPath, +} + +impl PicoSvg { + pub fn load(xml_string: &str, scale: f64) -> Result> { + let doc = Document::parse(xml_string)?; + let root = doc.root_element(); + let g = root.first_element_child().ok_or("no root element")?; + let mut items = Vec::new(); + for el in g.children() { + if el.is_element() { + let d = el.attribute("d").ok_or("missing 'd' attribute")?; + let bp = BezPath::from_svg(d)?; + let path = kurbo::Affine::scale(scale) * bp; + if let Some(fill_color) = el.attribute("fill") { + let color = parse_color(fill_color); + items.push(Item::Fill(FillItem { color, path: path.clone() })); + } + if let Some(stroke_color) = el.attribute("stroke") { + let width = f64::from_str(el.attribute("stroke-width").ok_or("missing width")?)?; + let color = parse_color(stroke_color); + items.push(Item::Stroke(StrokeItem { width, color, path })); + } + } + } + Ok(PicoSvg { items }) + } + + pub fn render(&self, rc: &mut impl RenderContext) { + for item in &self.items { + match item { + Item::Fill(fill_item) => { + rc.fill(&fill_item.path, &fill_item.color); + } + Item::Stroke(stroke_item) => { + rc.stroke(&stroke_item.path, &stroke_item.color, stroke_item.width); + } + } + } + } +} + +fn parse_color(color: &str) -> Color { + if color.as_bytes()[0] == b'#' { + let mut hex = u32::from_str_radix(&color[1..], 16).unwrap(); + if color.len() == 4 { + hex = (hex >> 8) * 0x110000 + ((hex >> 4) & 0xf) * 0x1100 + (hex & 0xf) * 0x11; + } + Color::from_rgba32_u32((hex << 8) + 0xff) + } else { + Color::from_rgba32_u32(0xff00ff80) + } +} diff --git a/piet-gpu/src/render_ctx.rs b/piet-gpu/src/render_ctx.rs index f5b6897..a0560da 100644 --- a/piet-gpu/src/render_ctx.rs +++ b/piet-gpu/src/render_ctx.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use piet_gpu_types::encoder::{Encode, Encoder, Ref}; use piet_gpu_types::scene; -use piet_gpu_types::scene::{Bbox, PietCircle, PietItem, PietStrokePolyLine, SimpleGroup}; +use piet_gpu_types::scene::{Bbox, PietCircle, PietFill, PietItem, PietStrokePolyLine, SimpleGroup}; use piet::kurbo::{Affine, PathEl, Point, Rect, Shape}; @@ -119,6 +119,7 @@ impl RenderContext for PietGpuRenderContext { n_points, points, }; + let bbox = bbox.inset(-0.5 * width); self.push_item(PietItem::Poly(poly_line), bbox); } _ => (), @@ -135,10 +136,11 @@ impl RenderContext for PietGpuRenderContext { } fn fill(&mut self, shape: impl Shape, brush: &impl IntoBrush) { + let bbox = shape.bounding_box(); let brush = brush.make_brush(self, || shape.bounding_box()).into_owned(); - match shape.as_circle() { - Some(circle) => match brush { + if let Some(circle) = shape.as_circle() { + match brush { PietGpuBrush::Solid(rgba_color) => { let piet_circle = PietCircle { rgba_color, @@ -149,8 +151,22 @@ impl RenderContext for PietGpuRenderContext { self.push_item(PietItem::Circle(piet_circle), bbox); } _ => {} - }, - None => {} + } + return; + } + let path = shape.to_bez_path(TOLERANCE); + let (n_points, points) = flatten_shape(&mut self.encoder, path); + match brush { + PietGpuBrush::Solid(rgba_color) => { + let fill = PietFill { + flags: 0, + rgba_color, + n_points, + points, + }; + self.push_item(PietItem::Fill(fill), bbox); + } + _ => (), } } From dcdd35e0b879b852afdf127d425f80ee697d3b46 Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Sat, 2 May 2020 09:50:36 -0700 Subject: [PATCH 2/3] Implement solid color cmd Avoids empty fill segment list, which was a minor bug. Also increase tolerance to 0.25 to juice performance. --- piet-gpu/shader/kernel2f.spv | Bin 21220 -> 21048 bytes piet-gpu/shader/kernel3.comp | 8 +++++++- piet-gpu/shader/kernel3.spv | Bin 24976 -> 26532 bytes piet-gpu/shader/kernel4.comp | 5 +++++ piet-gpu/shader/kernel4.spv | Bin 19572 -> 20832 bytes piet-gpu/src/render_ctx.rs | 2 +- 6 files changed, 13 insertions(+), 2 deletions(-) diff --git a/piet-gpu/shader/kernel2f.spv b/piet-gpu/shader/kernel2f.spv index 960741ee7a3b86336e30a2b0921c73e671476087..36e7b9475cc5d71b962abffe64ffda55eb72107c 100644 GIT binary patch delta 4543 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 delta 4702 zcmZ9Qdz95x701uL7Y4AIiSY)OgcdkyGj|X&l?g^?Ak0A{d?3<4ES8Y2<%(LiYOR*5 z^$TRoo~FO7NGtJ?DVEZx;R8%7dp}4Ei_9!b#~zBQfJOEB{J7io;|}Y1zTfZOXPy-?ccXq_ z&G{?OyWoRsFI;)in)ejeap`&V_piEW)q9)#g%yBe&FdL^QZ0Rw*}s>$UVT2n zP}`fT1s*`Rdp?CWhp`rZ0{qipHSO}xfS+cc^`s!b9FDf6a6E~PG%D^RaBSclC;2S6 z#;^rr(<|WW;d5oaqo>}Nqg=^o=V+$(IWWP~F$`B|j}=$tyZh?d=TWXIsb2ukV3haz zB3wP9ZUh^(o8fxxTbuR$64+h!iCnJ+cbE7z;9dr6vf>854ADu$P0c`G0UJ!4w{b1A zf8K_+uQJrU4RLI?3G8hIzfQ~;A+9C49;{EHr|iHL-hfbEbR&G25sSVC)<=C^Gtf<7 zgK3M+z78(Oxf#BU5piw->!V)8$;fCk#9$hujNbsO`=qih;-t>ex8DR?30p;ft6-&W zXZFuZ>2n)H%}R-*0k(k07k!nF%sw)Iq-XN*9Y~{$NaIfMw;1Y%{x)-j)^-;|&Cucq zeRsa8yWVvV%H1XP-h9*Sde?m@_md6kE~3pLmd z5!gA};TK>x_8wSJY-(aV!2X$-KEGtBnV2{>{T0|c#*^|9u$o_vds1&L{RR+$H2#|L zB*Q@Bxcr^P)jf)`lM&&63-;Vgsox=l`5jmvb+2JRHC5AJ`|rVOM*R@{G59XV3b6ii zePY)?fY;;Sd~oHD_l(aTM^g`(pcKKZD(SV{>o0eqM!Du>w{i z_W28#T=Jbg9YcQw%cBlYfy;Gx8m^DJ=X!=Ss70)?qMFa3jFr^Cfm=`VcQo}l$v?oZ zQ;(DU6I`yMT)(LLvtTm_|K|?)XRjPxL8tQ3Er9`ib@~_hDFS$u=fQGqCt`aMyqlrz z1!j5J_JGHl>s|uOBj&y;kIC&#<6aE=82x;-nbOPr=Nx7?GpLL8iN*g0YjaZ*lgFn2 z0lP+0;2 z!3|BH8hi;{J&T3oATHLpl(L>oobh0FlkpW%ZqOlY6T#lMHs2QI{iJ}DCV@|3E?z6a zk;oxn6Nx=vRpaTS811hSvFL#vM6&&z?TPp{aE^c-&44E1E2>;qCMF-qn%N!( zF_=a(lSk0kg55-$iOIG3FgqOV$13I6Jv9@oZZiA|$dZEDo9&yxW~kAh1oCLAqro;6 ziwav*=NNdosoo4%EBBO|J#;$#J@n>y(LEkPaW&3fSC&}Mk za+CEpSAUHsXk>;_Sxs$``$a?H+t0S^H344-a#hfbf=KSRUs zM^)A0=xSqVy)=DoqxnC~)qRkzYQbv!o)gFKz4!P%cb+(T)~t!MhmIZSn>f%vqi^Dr z{=vS9;`P*FR(Oz?>dR*e4G5O`5Xy?4iLqlZVz%>r#w&{rp5mDIi3^LJNEz}vp&(9_cN8=Q97q=}Pf4$K^^^VioJPVSpA)K_TM zb8ym>{yDSFd68;qcsu6E#LKkm>>V?@TFMx${JX1Vn(@2z4-A;SxvP1Vxi1HI9{sfG zr{*h);z8o8eC))?1$bpMp0!|C12 zUi$yGM)i?v98;~1Ht(GCtafBdETdfaYQ(75-CeEO#M^DCZ@Q~>f7HC%YhAcn2kyN3 zO&ppuZTOToS}{fItgMZHJ;$Qe`sLXAz|WI#Gk?--#aM4R#v;{*WeoT7{}aLU>Vv4x zVUcRna=hF7KaAgGczk!YIe51jvxg?lnA|!Q)=AB)U8nA9OL+XWxxZ>&{npNexE!zU zS!j=6xY`Du@gq`x2vV= zn|kXvvfhhTKEFriHe!7vO&x2T#@Kp|J(WLJ%!c*#kD9vC`cpT| z?a(@VM>_{6%yW1QxW2b~s^jW;>fcoYLR~rMb?- zCk;%U*t`%#pLeZGR;S`S9Cx0z4yV>yPkXJkjySwj*LE_ovd&Y$^*Xz&)9N`q8m(2D zyP8*fja}6laB|VFwLZC5xjr@5*Lfz@=Uv|t)!FDx%z0MVB-F7sRae$Oyos*uQ^d*I z&H`s`=hX4{ojK4y#l^WcHLv#Cx~ucx)>%Jo?unY$$n!U*x)3eC^CX>_I!1# z*7S15C&w$mb&g%tRdrm;*_vDK)y{9B>RLE+PuzLNlzHZ6U{i^@7T4fg&$C2zV>7OK zW$e7UGqjy@?KtALHuvj?V~(zF0N1&XscvrKdy4+g#M;s4Ic&!MQW5*h!?BmBZf#=M zci;aIy>_G)!?$Vc8T8d6{?~@%FJ9e|_#V>#CA@ycnY+D+`;8**x0<-_{{IlReMlH* zJ?<{zesehPg4KQCNdp5jCr_L_efSfZLy=9K)A~m4s_wxLkJdBNZ}fW`KF~kC-`@b| zxSzhChqK@2KL8I-I(h>-cz5+f`hEE^>DyrrC$c`1YF@L|>5cJdOQ~Z#2_}Z07%khc z=<72wx_Ta*xn2gh=jy8dTJw$RTI1fJ&wb&8Z`P#2N&fyi--7VOSrlBxS+eHM{9VIZP%(|pHmt;F9DHLvC!tF_-h zY&6&KcpYQ-d_4tE&d-1|*K@VsaLz9key{)yZ z{~C8y@6@q|bL-}ZIk_zgPHszp+jZ!wmZ|yhxRr`=>lEYO2c9rAdB9>>E4kM!G<);e zpwYy;4v!sO9bDj91wM8dcU9+t%e_3WgJ0IcFYn;jckmlJ_}4r5Z5{lc4t{S3e+2ID zFwZpB%JT_6&gbCpeNb!GqV;@iYVs(4UY?&Pz(@Gw-tZ^k<(i-B;LpIlrzWy_-TYnD zzqL*qm^gl5=3$ctcAIh3%*m66`e)AAZKqbep6XAHf9w8qP)i?Oy@r0wu^Y5<__sdw zt80so8a?tk-P7>)bGo})2#udl{+>3m^|vn9vKU&K`xtoZeBz;f=??o!aOUMNPIWC- z?y$di1Rq_!AKpJ>b}QBfBW&H(#@dR%!MGorM{CVc&#}dbab49|_~h2#X>0qq4*O2p zo4>buhIfY#HZ>g7*4q27>X?pkAMfBNb?{S0@b2m?cvDZ8(OTo#BYb!ll=J7UdJns* zOGns8S69H3&-Ej0UDb^p{N@gR%Lv|G-2rdyT`QZqHg}Hj>8c)Re3H+DBkZHA@57VN z<0EWc)lWM3(;fU59sGH?`DCsaI_xiZ@K-wcYaRTJ4&K$(KD*;NtB){I4-J{WQFXQ6 zJ)C>l7O&q$8r)$OgKA?=3->ov9TH^T|R>qUBN-Q1)t$1(1%Xt0{d6n_h63^eAGM;=@{9|9A zz-7GrR+RD7;_vT98BcD!dOzCF)pESg-;DPeTaK4IzMJNL_}p9wykf)WC$M}u+LBZO zGyeY5s=6jiGsgUTXtjG+q>v{Y0EHfL3+6{9p^l1t72Q2Uha5p zi_<6O7_jlwRwnZ$8k_#cbNrI@>iRl<8TyR(tU6w8WmMOuElry-mZ#UIzcKvhA&IdH zSU)vm^wMi{e*46*w!Jj{jj<+uVyp$$PtEz>)7aGI>(aZ1@b`nwCwyaYZ^MnfIk=~# zbyZu?8(VEnzpg@+Qj4N<>t2oy)<%`uKWh~BZ<9eXf+ zJ$7{KZ?d-eOt82b8~nw(~XjjQIl_uJ6@%$alUIdFZnt%}c4^rL8w z)n?4&=o9mJu>NYs^lx_B%9tm@_0eX`ljx1x|EtV12Y1 z^ON+(bgVXG`gf}Mp9|JsO`Ct4itYTyre>@Q=#AwVZTeqCAODNN#!}OENnyLRv8fsB zGJ0bpWEWy)pZ58{>Hqi&|It9Ern)H)ov>^*P(V-p_z|+ z{vCRAFeiP!OP?IHt&5Mfe}Lu{{xDd-l~~gw^p1CZ{$9(C{UdsP)V6N8e@9R=j$MXulb>G@Afb6&ac zFlyPan<35}emC6rfTi$y2y9H>X*^@b{1KS{v@~PNeXmhVjK?G#lJMWa*GBR^mugUd7iJle_F6aQ6sFWmUAgY~6r**%+YNve~_t;hZ9*?$|Z zW=o8B^kL!akeTaVkpyEq4ZIX)x$&+td47sxe+Rxe!g|c?I(&~aC+|huX3T4N?;9_N z{}ZK~-ZlH&c*b!B_nSHVS#-aZ!*_xEy&OIl?)P%I@%&y6_Z<7J9KIiRzmvl~e|{&2 zPsZ+da=7vQP7Zf|zmrSucXGJ*quh;qK7K2QYxi3@-0^-Zhx@$nTRHp)>~|L2=dj<&v73+I$>C?h{Z0;_ z3-?<&+TRB|2-^%iF z+(kYk{y_7&;r+HP_f!x49~=ENeB>{{)qH+r+>2nx`Ma8NFTvGMZpOU~cC7k#+`Znb zYKid*xO475qp4@^zkt>J{mi((!aL{w8=CqV&D?(ntNA=hKK}q4Pn-GptWYz5pFeNV zjJr1#@_G2rMpO4xSG#MWBc4uOKtuGcD}3&mrBj&=LBMo zH9nfU<9$xaGk!GK`el3lITrs_!0wrI_>7mEgZED_csF`=?fxdHWsR$X)o#My-_X^-YQ7V` zf&Us{FLThgI!(sU4Ow7$l7p2Xh}ocR9U$&K%O=|X!ieYXaySu>w9;8Bh37<#!j;~fX?X>1>- zmv4!MXwj1f?IlDK3y@wp5 zE%CktR&$JT<%#!Yu)f-iE6=#EfRo3~V7WHe@l~*SJMI>GdGh!g_*Q%zqb>Eh6>Pk+ zK3|8c=iJ=}Rx9tJ+u<4Kd~*G~XL3gD%X?@$xbq%C9(RGq(6XOX(C&t-8}Ckfx#!5g z$$b+%i(Xy(9rS8BN8bV`Ub&WUqif4rz5`bC8z5u93opkSUrpcm+yi#b@;%7CzZXqg z?xFj@YPpB*2YY!BY5N}ScQo%Iv3ut|^bk1r(1T#PYxnQC4}&+PS2wo@=+%|;KSEQ_d3Y3T{OpTs{4reJ`{fMyV_;)DR+~9}pI$xZ^a-%% zL)+u@a&5-@DcEzZ?I-l|oYN=4-Y<^PmUuq{t2xHF^2B=ztgklX$}{fg;N>psY%=J1rxxWUM8~Y7f zZLnIo_y2-tob$=`bMLc1_T^ddOq9>g?cvFT<+Z+f%RJ^oQ%@eFz-sRl<3_{%R(70u z$n|rc+%p>{XTMWzrIgX^E=da zIHy|9+>gO(spI2dHJ|4`Z=az53C({-=semp_NQQN#x-|&Y(E2+ai4~(`R$arKL;nS z_Kf`nSetRJfjqWfgPrSmHs5!k-_X=z|1H=tu|ES=n?OAlr2iewG0)JfoN38(cJ5I(`T=r2hU&dmpk}h3;sIzje>iA z{BU!8_WlL1^OSr4B3wOt{}Na&dv6YEsnwsrYT5f&z-r~*{~4Y=(w?z@0c&%QT#G!m zzk$oRe}}8RO3U8=1Dv?pGxjyGHse|od2Ig#J6CG(CfGV<-{ktl{x-O?M*l)n&zk=O zR{J;2=cqM)ho){0)?My>?^&1+z9_xA^LSs50-J}=X2;0&OWvcwt|e!(3$EsP-}QTF zj?X^H^>MssZ2rdX`4i{-Er)GIn%D9)&)>52E6_ZDE79ltEyy_67Vf{lUvmHb{gN-+ z!B;N0|LkRrhNspG!JW4}j|-!z=R7U~R?9lgNiB6+46K&DSsbiZp2sopoEz;Ky98L9 z^>=OZ*p>p9ahHaxJw?7bkMD*juJ(*w2CU7v)<_=Pa^N!V3UD?57M-{&f)iJJ#;yd` zW?XA6k8Krj8Mha%R^C6W!V_0}#;yj|W?c779^0B=Ym+@&3+z7T{*mhw|M!4hbNtr^ z>z{Kj*C+n#fL%-c-wW11`z_Ze{_BCwG56j2U^T}tz}~zM?D%zQu1Btq<30QD2W$7f z5a+(=#kM-lYgL-}#VYix(Y!C#pwE5a|MsLj_nUQa|Mw@QeQXEcx!}8l_h@)(urb_u z%ll#zH1*sUn}XF+Cv#Fuoi+!n<-XVgtXAF^Tf%c5v}f#AU~SgVwaH`K23*G77Oqy_ z7u&%TS9`{857uT}Yb1|t9Jq|T16-}VFLs0{uJ(-G39QYy)>pUCye_9{|?weIm|%;``w`G_Uv2 zyieAmUz_HA@?QGncp&4{%X9n@H1(Y0gTQL8BhSBs;n@%ES<@k4ZRR9SPX6AlPxD%r zmYmiDo74N~y?1@jn!-5m&E$9}T)#5MNoeZ1cMb!q<@bJZ{3pZJt(`ID{@$BQAJ}(1 zb#s*`SD!%}(!AbJORgJ$&2=MsbM-fQfN^E6hr{(NbL~e{Pp(IR)spKn_#X*ZH&mXPyxvq@= zY`D6)8dGkrz1Ze}eMYOBt30`GiM|cZYb%<$Zb82_&0M!_;riccI)-uPn&>9Mp2QPXVi?Hm8Es%IEbbw9}kNd&Zsy z)@EGSB9HA1a2fYZxLWzV{v(!b1gpjVIk0oYei2wL-*y*+9i#4?e%oCF zcFsJD<@y=Zb9QNC_Y8`22FGFBiRQHf%`>KVORGg6mi2`fW7zjCHo(!4%I zGuQp-KTI>%kI?7dxu0?6z4LGff4ty713%U9oRtUQ#x2+OAews4%0pna+)K`>mhh@pT*hdL$Do6 z^ZF>weKy`in)^J7KKuL(zchC1 zDNa56upLhGno6^tlj*0?tY<%c>iH_;%6h)u!QUx(*Ql!Ue-p2N>ic)>#x3jn4>a}E z_cgFu>g$|psl(e~wbb`tV70Qo|AxOsa~|y(`ya42>uc`v*jVa_xbwl)%KK&%JaM&W z>}aqy<5~lGY&~G-N)6_RTbJyUT%Y(a05*pEJcShA1y^(Y5%2}Uj?aA~*T?bhH%^O`|(pN%(@=01OnKKr~VAO+1JG7@B9`oBmqci#(lOu47) zqN!(3*8{6%PmQ6LHEsx2%bsooR?D7tbM7~Wn~(FTJN8}lo1km6zP{IO3RcT|*k<4j zXpS?kd!(jc=HDD#&c6j*Eqm|$Tf#HHdgk8>U0deg8myN2w*hB<<2t{Zewlw;a5?{W z+Gxu+&j;t<9`5|^bLJk4rk=USfz>ki4&cmfOy^e9FLUn*F6Z6}uJ$VPpGiWiQ)yl&(L8I%)1Tbn6X{Q(dG01Iu%_8~kMK z<+oB<4@CPj}InJxyOgV z9aGl+qiE{c6f{W1ebFUz}3npKms=K7M$ z>G0&D-CWdMQ)ezWqIq3MOD@+p*msy4Xy)}rdiTM1oO6lgKFIYsui?r247f4Myw5~a zPu`ydtCe|w3ZA^Ro3~o-f1k5w!8>E0ji#PG{4`iCfBzU{+&OS{W17F*Gcbhhe6Z`( z=33Z#v_V70P-pM$4<+Fg%Y`7Ux1yfgO2XzHooC1ADG?=*6`6s~Se^Osw{Gq7C& zcAeT>i#+wa5_~o`$7t)U->ulZZlR@qUv04OGGC)vzpv9iUqVw){k{xVE9>_ac~ow2`)rk?tJ4Xl>>okK3S!qtsw{&MSg z9=6-Tu2Y+9k*9v&0Q)=Z7;T;P`xZ8@yJ)H3-3|74=$ka__icLX=kL@##7X`1xwqk2 z;~j8gl=Zt4O+EFy3#?Yw@0;+{PrK_;yPKBS-;#I4{x+I=>h~S6TIzQZ{@;bG8`J#d z*6$K*_kmrfHrFCg{k{jj5}RYRb=L1bY+m1`rGED`*yr`VH0$?0dh4h52yx2#JyGzv z*qTj^lAJBXG?;C6TK26PB z#m@Bv_8)^Er8(D2WNqxn;p+Y_J!S)2JJ+QxIs62y=HDKy^H0HQ`nqPhzE@%U8TcN0 zZLVLQ{dpR!zcy=|~9w#@wtaOO7tFX3v&a2|Plf7STr{QMfO=DJR$4zBsP VVD;b7jA3on{P%5BH~XBA{|8p;_f7x+ literal 24976 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

TD_Gnd@){QDE{TlY2h*^$`vDfi?k zH1&L291T|U@5vK)3^;L)BiFAS$MNL(yBz->B>Qt5zV46n@ZB&AuI3f(_(sEROgo{` z+^hNgnNSztL^R{|*Y17kpFe3k39fxMEoW>F*qk=)%UQQ$=})F>$$@N08`Lwwvr_ieYM$-neK|OJwI_BpSetoWYk6$10GD|$ zhpVljCGQpBz>JDy9Vsq_-8FXo35p)#eN;wImUiHSS{aIuLK*T?!MnZ zeuHv=aF;vIe{t@W&A?mIyf&wKuWU-c1k?uMu4Tj7r5 z8f9K@1gq!XcoSGH^RfoD_}>Or%e`?sSgpJ_-U82=(4N>kz}j42=OT~oZQwHR+u>^E zz3~os@@h})JHgt_>zc@8yAxdIy$h~Z-WzwrlUI9U?*VHwuWKuh?LA=UkhOg;*!9l& zlj{@z_kpb~=kNVswVb~XfQ`@jlk1cC4}z^B<9!IMfA&ePkN%#q57V@J{=_+d+k$tX zd2L7Y{P}O`wx@aicBIew`xv-9fA_WV``h@J3jQGYp@ygCkHa0OJb#~ntLOZE60DYa zS%X^E;ZtC>oWDl6QPfUV8* zw+;O_X==tlO#cYY_?$nvK8gPp*cvk4x54^npXB=J?-~0JO}pn$obxvoybH~1Cz|JP z3jNMB&)=@}Ie(9W%k%fcfJ+VIlYjgdrRUX?-QwMT)*EG{14!#8=jhe2{%u4M$gc+yME%V-z4z==$KTM@vmSp3tGOQeP4o|N=BGV#_$OGKHHlNx2H=BfUI)@r(?MWs zI)vUmU6;OVLcOP{(T^#5z-kj|e#3euI>G9$wK?T}2V2V|@J96N)+$e} z+oB&%^E$NE>h(JeY^_JoTdVJs4T*EC)Vf|P&)T)FkEWhlHvp@p)~$)33|F^SbIPsN zS~ddj(2QlR^3>{k@MxOXk+iJqQDAF5rls@eWb|7SSJt{IT)%Q%H$ziTt($|@QtKx8 zZvj`gR&&a&)mpX!Z`q7xt@6}56@4bn>sVT9odLGid$Yg> zsdYQBT55eR{@cUVt<{`zYn_U1NANE6>eebxt=?ZJ(7a~RQtROE$@k849k!^fN!A}66*zlbHJ>ia9&V4U5^*m4Z2CFT>&$(KYTAm>X zfYq{A2ZGgx@z3+;Ao%_?$I+hHgTdO&>)hnA9SSb<9tKw{fBzf~PhRbbJp!!FysnWv zwxhsh-lO4aFD>dl2A;gy6MHOJn|WPpd2BPmW!~f9YHNzTv*5|AJ+a4wwVBsFlgBn2 z?Aqja`bl85*yn(qW9%n`)$(n63fLHR_x%RynG5zTxF3!w*DpDr2X=nRc`8^f_IY6I zjD0>>En}VrHby;To(^`*o0>7@`kB*vVnJi~ei7$>nXirJbsEk4Wgh+MH1C(^)93y; zgShg3Ij4?(cI@V=+CCP&o89UJ`WOC z?(;|+e@Po(-Nvsd__g5c8lE*6f;(<`4u;Xxa}HhvR?9iCCbg{7#bC9ZgH>R)@*G?O zzli2I+7tU?ur~M0xyfU@6kO(gDO{~Q2QPysulB^g9IVZ}u8};p%fMycSHRWE=i(Z8 z@@h})CG_P)&_ser4xfwXvR3Z~s~PXU Xya#N2_F1lv@$Th&8@u}~cAx(j&fb}+ literal 19572 zcmZ{r34m5r-NrA@fGCK9JBos+;D-CYfvC8kptzQX!^{99!_qJ)mTQRGu4!qDEt)M_ zrj|>lmZni!R<>xXnw4d{mTTqr`@QddW)Ab^&GVh-dH&1!pL5T>_q>i@%Npa?tg1oP zQ2w`fOVzkWR%@VC)v#(vqn|c?*7QkBdpjoWvF9#2jHsH9`m9^6Sq-Ldr7djhT0~b} ziQ&_<^@vv=Nn5XO#4(j3U$SKO(%y5rJ7;%yE$W=z z(bd!0-rIFnr+z~Vzph2?-OD;khrxK1H4J8KCRR;d*fF)Mr@g!L_|Cad)WHp1ym;>1 zrJcPM%3$VN_||@EUhSQWdOLfXv1-ntr>&!F*;2J3jacW?GjC4Y?Doaoi}4*?4QHK- zoU?m6+d3vL?d@6C-n&y-)lSQH-F1KbTdH-;L7CR>df_#zw`cK!vUYW^vi4E%K3}kN zRP$QeIggR&HtJNf_W5nyb7!C3)zLe@S-<-Ks((ZDzPtnL@2jl8{tfVN)<3w~xEXtR zS9f<~<*iB0tH1WvYBZcN^lQ{D_bTgFv+g-Hx`$O`(CeH7tFG(m zuX%HPQ}dS1xTBUWT+&ywnpau#I5_$BYt$_FTGV-VW3AmyTf=Lgfi=|m`)e4FZ?lHh zYTIU9-}rK`{_)qUwuh5@c5mCfVnY$qOxf5=+u_s9Ijovg`!^qif#s|ow^I?fTOH@3 zy>XO#y`|cr^w~Mcis77CKAQzkbIOlNyifkI!@( zW2<}h*Epn_1+R0iwvOsvSx5Qm>o}r139XF%UxlWCay?HZu32kKbt<@Px%Z{Dnyub; z_AYb{-bO#Ox4rw2Wz9Vx_gaEx@4aaka2bClJ@GzH4erYh?xWb?XEpimP2N(S2Uyy$ zwN&q?PaRi+%Q~)U^1i&+HhG=5rMe!TJU4@r=eDL_-+boBTB;}d{93E$z)RYC+7{Bg2G0-RuMXgAa1;CeTdE=OjI%blf8IPP@Oqzn3mSV! z&1-4%oxzwb)reYi{PmkT`sTPvlh<=>t;RI@?&dIdJUnyW9h_SBZu<4D-2qKr=WVGD zDdLVU;%2NCH@G?uUhmO*4nrE6h?j`fv zYW@s-F2^d4dyU-B!#UV-P;2IT0p34WOZ6hWgT3c^ysR&0vUWcVjoGUYZX9I#^m?2@ z9B#@yt?;gT{_BFh?apoVYJP*O&Ebvn^I4sZSBpMdp>-@-?Cm9{rJ4x$W1{T@u=}WK zYpqU%_m9gyg12#eZQ9qYPOJUhcjZK!M+de(xz69YTE3QQK0NbS+VpSKaqenTk0(uYfnd%dORA;Iq3Hbu2!+IiG9b&H1!cH-MKjQ|ETOcwzH*iTu6>Z{uy$t`mwZ)iCddqZk{c;ca9Uy_;L6Qr&l+Rx!0pl?vY^gsu@43wwW{W>x0$xHQwJ< ziQfclyqfcHj$>(qXy$W`^;J*4&A{r$n$O=n$u|~kJ~eIQ=o9A}%g55RZA+iH31H*Y zob$xmrY_%+-ZcpK<6;frdw`u|_ Ak~@~?`;2uUcuktx7UcF^@0!~j>tK2{{fwPT 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 PietGpuRenderContext { From 1797914ac8205a890e6bfb50574744cb7b0c8b83 Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Sat, 2 May 2020 16:20:04 -0700 Subject: [PATCH 3/3] Fix artifacts We were overwriting `end` when clipping, then reusing it when reading the path, for continuity. --- piet-gpu/shader/kernel2f.comp | 8 +++++--- piet-gpu/shader/kernel2f.spv | Bin 21048 -> 21108 bytes 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/piet-gpu/shader/kernel2f.comp b/piet-gpu/shader/kernel2f.comp index 14a6ead..7ea93bd 100644 --- a/piet-gpu/shader/kernel2f.comp +++ b/piet-gpu/shader/kernel2f.comp @@ -119,6 +119,8 @@ void main() { && ytop < ybot && s00 * s01 + s00 * s10 + s00 * s11 < 3.0) { + // avoid overwriting `end` so that it can be used as start + vec2 enc_end = end; if (xymin.x < xy0.x) { float yEdge = mix(start.y, end.y, (start.x - xy0.x) / b); if (yEdge >= xy0.y && yEdge < xy1.y) { @@ -127,8 +129,8 @@ void main() { // in piet-metal). FillSegment edge_seg; if (b > 0.0) { - end = vec2(xy0.x, yEdge); - edge_seg.start = end; + enc_end = vec2(xy0.x, yEdge); + edge_seg.start = enc_end; edge_seg.end = vec2(xy0.x, xy1.y); } else { start = vec2(xy0.x, yEdge); @@ -141,7 +143,7 @@ void main() { } } alloc_chunk(chunk_n_segs, seg_chunk_ref, first_seg_chunk, seg_limit); - FillSegment seg = FillSegment(start, end); + FillSegment seg = FillSegment(start, enc_end); FillSegment_write(FillSegmentRef(seg_chunk_ref.offset + FillSegChunk_size + FillSegment_size * chunk_n_segs), seg); chunk_n_segs++; } diff --git a/piet-gpu/shader/kernel2f.spv b/piet-gpu/shader/kernel2f.spv index 36e7b9475cc5d71b962abffe64ffda55eb72107c..75a7a39616532d313668ff49c7fbd7b0ec2b8681 100644 GIT binary patch delta 3642 zcmZ9PS!`8R6o&Wh1SqYPA_@g7qsoko6)F@#Eh-ctCO#1Y6%!&c8clpK7r_T#&3WQY z5?%xmv1mXAah?ZM#2FQpNd-}4P}J|c-USCP?(YBp*V=2Zz0Nv=ThrF+(Lxc zbhw2GJ_B0F@%F6Hx2b593-T)5GUZoUrr^`DTdLqjZOZWp@L@UL3?Gr>?l=A{ADU(( zOgt-4yOieU_-OdN&S(0ruPEHqxu}2Ev#o`aU4Xid!=9#e*K$hBDR1RkTF&+0N`}?f zzZ>%taP{!nknJ4S(Do$C2F4avPG;psu%7D2iM)x~-zlPKdy1iUB2|=dMC;7f3~9(V zkLi`I9n&NGjQNMbJ+i;YbnO-%Tivht9Ba2SR)Ps0M|1fDf?^#>!;qG zJH~a}!K9l;F*bvrW$0%$Y$ojy??td0nbefZvm<+GY(w!Sq#cay<)gn0)>Yl3zsl^- zqicJGq2|%W5&kuBIeaPl>+pe$WvmUKH{j|~<~J)+F}YB~NdiV-=VP>&*WxX(8(VW0 zlr}Z7cfkHkOrN(IY9=O*P2UAu$LGoid>^dleS16&uS_2TB9O)p7~e1qB#ukBvvlb` zLfOfP@E?OccRS&2$WOrfs7F((>974$u#IX|p8>nU-siW2^_S}tyM6{nms(t`$ z2H}73fB(^Ej;^3n{^*v#06x`!0q-kh|BUZjJOuPazcL>#yG+96H}C=W&~^kYkLACE zx0h{4!SZHqVy5&5e{N&`nc0}G)hE^-18Xzxz5;iQf<`y}6YL_5CMFNtaj?O(nV39+ zo&dXvHWQOuoOWWK1RKki4F6Mb^%bdDT`I_55XA*74yV82WSAZ%kVQKUp95E4QP%ze zyH;B}hnR-nzhHHf@jtmSTd`HZy|UVTb5#`i(@c1@R0+PG*+m*7kt(o>#HQ77wP4-?fL)}~>n0CdJ=kE{OiUg@dxG6Wn~BM_`Izbj_J#?5 zG2F+NH(@J1n0h1Vq`?4wAE-qWT>?J8i7rJ`kIT{rtd{SgzVLhxHNx#7^;p->9&oY7 zXrlh$ehicG<3w)ut(@j^unB7OLq#4vGyrT5xkg)5XCOG=LxbRI_E0wiX&jW*H#gP~ zMvjjVi#9lGX>J@c1oa3kvK|Uoizd`IG<&eQv3LdQewbU^MOVVTSQdLS{3^IU>iYOu z$%`v(#K%nh){=|;&eA`QHxk@}zdqXHc%#6cV124Cmfo|Y5fqo;?E7^r*q`rLZDSZ} z-W%cwbT!yOCfkPpIJnw2v^dZNxXoaKe)!47e)Em1q?;n!NeFWYqLaqhWHQ)I;>psi zA6z}^dkxr5(iUAf1$=%NPDN9XbuD(2q5)#zwcuE2rt%hsPl%|3bNEjOuVeH4GqV*< zJvN%5KVzCR)$&<;9k_oibR&h5W7%P5wrNU3aTe-K#!6J5tk;9pqL;L(Mc5m_c1J#) z8`0Dwoi?zVHH&q#;jzwa56umQs)wN?8QN%2Gl0vIrGg7?f3(V$g&uBv3IS5`)o%3o{B{c>iB< zNkWW*g0)5g1;u?wMHEz2#078z1z8mEd4~C9!jRMRzVEl3?|kRXKR>57Wwvk1RF!21 zbxe{>QpvwN+N#!9)jXR`lC?8dtZYq^qyzuSyEG0$VT?^JB`RT#xe(mL57iThy+Lv_eUD5D9EBF~srV8dJqPsAUfRALp9loN#E0WcjBw5tH zvDe;gt~=0;viD>%uG5+KmGIecw;c8sxLXZAJ8kPzms34aqE3U`g7BISw*|rVFQ=g3 zv(uWsb$Qj!C8|+2D7>b@ZBlUUHYxZ7_|OvG2p?9$-OJb#J{Laz3hza)l4SlBAsG!{ z*uKB-mTYED`=kBS?jv)Ylr4LT&6F|hXcKMg#Jm}<9zI*rlf!D$d1ESb+liV))H7K1 zdYW-2DND97`#DbrZCe>?XOnCJN2a!PPkn9A=diRfLTv|MNOz4Hl$O_bOHYjHk)9gX zEx#T0MaFv8w5lE87Z~bM=}vGB^KhMywnYz6&+9TgC zuo{)+3+>qrSx59Lg2U+*H1*xZX1oeEP~B#{#_Y#tXxqb3vl-&Z^E$Y=SYfR<-~$<# z*GjjJtH{3zRgS*D1vX6m9O)vh^J%V+XY)4LWjzD^UDC4NWsb7+d5589S>h<`J+M99 znRI9_B>VuP=oiyOA2CNFZ67keW|&AE$GX>Rlm8fHFC)@_0(RFebgwgMJ_YNe?vY$z z3u^jn{|u~V)`#Hx;eM}|fc2N_6RUm>_QZ-8I=wWkL(Lax>fv)B-8ioH&Vwii7*WZW zU_I4iwXeXgwxPJ1TtAy{AKbHhkIaYMbM70ILyXw-x8Tw}e+Sn`-8~$qUbV<_*eo^Q zqZ}?#k63m72b3d>sQO2+p6XHcPvFuX$@Pn#{tR}_@IPAgk6p_3Dcz;JC4NZZnf!|m zBq$UQmYaMcZTby-ESp|Gp>O_J(dBofKZ-6s(oceS7i}lN@nSP`9vX%r@C-(UN4pd_3RCKgu*MQ>=_**HDJJ1E}9WZ$X!&rTyfGV&y7qNJG6i^K| zMjM6X6NNB#1)Hp3Bv2knyMbLqn}x}>`4H+3_6rld7VbmIFJTUQ54b++#`|NhmP_)N zFHuj3ZSkoU3-&@&k7MZ#Rx7T8EQE>RuWOJt0;DE0<$=^YvXS1Y|kYTlVA*{Baj=k)Hz%9?=~Y?lk##)0YD z#(wpKP*1>OAA{j)ky6{>bVp;qd_C$>n8kR>ZiM@6vHg?aL*V+T>*H(UP2g?#xTfz? zan)g<%m}Nd#E?&s2f1G#=cpU!v LENri7THf