From aa83d782ed5b8f9e9e0aa226ff11a64e3d63b3c2 Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Thu, 30 Apr 2020 17:06:01 -0700 Subject: [PATCH] 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); + } + _ => (), } }