From 2057f9cd6c08e9343fc01047aa07adc8d4ec5c6e Mon Sep 17 00:00:00 2001 From: Ishi Tatsuyuki Date: Wed, 17 Feb 2021 00:12:54 +0900 Subject: [PATCH 1/7] Bitmask lookup table based supersampling Many thanks to Venemo on #dri-devel for helping out with intrusction level parallelism. The tiles can now divided in both x and y direction, which allows for: - Less divergence on typical inputs - Faster texture stores through coalescing --- piet-gpu-hal/src/hub.rs | 4 +- piet-gpu-hal/src/lib.rs | 3 + piet-gpu-hal/src/vulkan.rs | 6 +- piet-gpu-types/src/ptcl.rs | 6 +- piet-gpu-types/src/scene.rs | 1 - piet-gpu/shader/coarse.comp | 171 +++++++++++++++++++------------ piet-gpu/shader/coarse.spv | Bin 58380 -> 59336 bytes piet-gpu/shader/kernel4.comp | 190 +++++++++++++++++++++-------------- piet-gpu/shader/kernel4.spv | Bin 38540 -> 33980 bytes piet-gpu/shader/ptcl.h | 57 +++-------- piet-gpu/src/lib.rs | 111 ++++++++++++++++++-- piet-gpu/src/render_ctx.rs | 101 +++++++++++++------ 12 files changed, 417 insertions(+), 233 deletions(-) diff --git a/piet-gpu-hal/src/hub.rs b/piet-gpu-hal/src/hub.rs index a52040f6..c534afaf 100644 --- a/piet-gpu-hal/src/hub.rs +++ b/piet-gpu-hal/src/hub.rs @@ -11,6 +11,7 @@ use crate::vulkan; use crate::DescriptorSetBuilder as DescriptorSetBuilderTrait; use crate::PipelineBuilder as PipelineBuilderTrait; use crate::{Device, Error, SamplerParams}; +use crate::vulkan::Format; pub type MemFlags = ::MemFlags; pub type Semaphore = ::Semaphore; @@ -152,9 +153,10 @@ impl Session { &self, width: u32, height: u32, + format: Format, mem_flags: MemFlags, ) -> Result { - let image = self.0.device.create_image2d(width, height, mem_flags)?; + let image = self.0.device.create_image2d(width, height, format, mem_flags)?; Ok(Image(Arc::new(ImageInner { image, session: Arc::downgrade(&self.0), diff --git a/piet-gpu-hal/src/lib.rs b/piet-gpu-hal/src/lib.rs index 2848774d..c16f1788 100644 --- a/piet-gpu-hal/src/lib.rs +++ b/piet-gpu-hal/src/lib.rs @@ -1,3 +1,5 @@ +use crate::vulkan::Format; + /// The cross-platform abstraction for a GPU device. /// /// This abstraction is inspired by gfx-hal, but is specialized to the needs of piet-gpu. @@ -57,6 +59,7 @@ pub trait Device: Sized { &self, width: u32, height: u32, + format: Format, mem_flags: Self::MemFlags, ) -> Result; diff --git a/piet-gpu-hal/src/vulkan.rs b/piet-gpu-hal/src/vulkan.rs index 20d15e4b..b20d14bb 100644 --- a/piet-gpu-hal/src/vulkan.rs +++ b/piet-gpu-hal/src/vulkan.rs @@ -10,6 +10,7 @@ use ash::{vk, Device, Entry, Instance}; use once_cell::sync::Lazy; use crate::{Device as DeviceTrait, Error, ImageLayout, SamplerParams}; +pub use ash::vk::Format; pub struct VkInstance { /// Retain the dynamic lib. @@ -460,6 +461,7 @@ impl crate::Device for VkDevice { &self, width: u32, height: u32, + format: Format, mem_flags: Self::MemFlags, ) -> Result { let device = &self.device.device; @@ -476,7 +478,7 @@ impl crate::Device for VkDevice { let image = device.create_image( &vk::ImageCreateInfo::builder() .image_type(vk::ImageType::TYPE_2D) - .format(vk::Format::R8G8B8A8_UNORM) + .format(format) .extent(extent) .mip_levels(1) .array_layers(1) @@ -505,7 +507,7 @@ impl crate::Device for VkDevice { &vk::ImageViewCreateInfo::builder() .view_type(vk::ImageViewType::TYPE_2D) .image(image) - .format(vk::Format::R8G8B8A8_UNORM) + .format(format) .subresource_range(vk::ImageSubresourceRange { aspect_mask: vk::ImageAspectFlags::COLOR, base_mip_level: 0, diff --git a/piet-gpu-types/src/ptcl.rs b/piet-gpu-types/src/ptcl.rs index 41cebc7c..eab3527d 100644 --- a/piet-gpu-types/src/ptcl.rs +++ b/piet-gpu-types/src/ptcl.rs @@ -21,9 +21,6 @@ piet_gpu! { index: u32, offset: [i16; 2], } - struct CmdAlpha { - alpha: f32, - } struct CmdJump { new_ref: u32, } @@ -32,12 +29,13 @@ piet_gpu! { Fill(CmdFill), Stroke(CmdStroke), Solid, - Alpha(CmdAlpha), Color(CmdColor), Image(CmdImage), BeginClip, EndClip, Jump(CmdJump), + SaveStencil, + RestoreStencil, } } } diff --git a/piet-gpu-types/src/scene.rs b/piet-gpu-types/src/scene.rs index c4a9b9ae..37152726 100644 --- a/piet-gpu-types/src/scene.rs +++ b/piet-gpu-types/src/scene.rs @@ -38,7 +38,6 @@ piet_gpu! { } struct Clip { bbox: [f32; 4], - // TODO: add alpha? } struct SetFillMode { fill_mode: u32, diff --git a/piet-gpu/shader/coarse.comp b/piet-gpu/shader/coarse.comp index a4837bd5..c3c3ee84 100644 --- a/piet-gpu/shader/coarse.comp +++ b/piet-gpu/shader/coarse.comp @@ -71,10 +71,16 @@ Alloc read_tile_alloc(uint el_ix) { #endif // The maximum number of commands per annotated element. -#define ANNO_COMMANDS 2 +#define ANNO_COMMANDS 3 -// Perhaps cmd_alloc should be a global? This is a style question. -bool alloc_cmd(inout Alloc cmd_alloc, inout CmdRef cmd_ref, inout uint cmd_limit) { +Alloc cmd_alloc; +Alloc alpha_cmd_alloc; +CmdRef cmd_ref; +CmdRef alpha_cmd_ref; +uint cmd_limit; +uint alpha_cmd_limit; + +bool alloc_cmd() { if (cmd_ref.offset < cmd_limit) { return true; } @@ -91,6 +97,62 @@ bool alloc_cmd(inout Alloc cmd_alloc, inout CmdRef cmd_ref, inout uint cmd_limit return true; } +bool alloc_cmd_rev() { + if (alpha_cmd_ref.offset >= alpha_cmd_limit) { + return true; + } + MallocResult new_cmd = malloc(PTCL_INITIAL_ALLOC); + if (new_cmd.failed) { + return false; + } + CmdJump jump = CmdJump(alpha_cmd_ref.offset); + alpha_cmd_alloc = new_cmd.alloc; + alpha_cmd_ref = CmdRef(alpha_cmd_alloc.offset + PTCL_INITIAL_ALLOC - Cmd_size); + Cmd_Jump_write(alpha_cmd_alloc, alpha_cmd_ref, jump); + alpha_cmd_limit = alpha_cmd_alloc.offset + ANNO_COMMANDS * Cmd_size; + return true; +} + +void write_fill(AnnotatedTag tag, Tile tile, float stroke_width) { + if (fill_mode_from_flags(tag.flags) == MODE_NONZERO) { + if (tile.tile.offset != 0) { + CmdFill cmd_fill = CmdFill(tile.tile.offset, tile.backdrop); + Cmd_Fill_write(cmd_alloc, cmd_ref, cmd_fill); + cmd_ref.offset += 4 + CmdFill_size; + } else { + Cmd_Solid_write(cmd_alloc, cmd_ref); + cmd_ref.offset += 4; + } + } else { + CmdStroke cmd_stroke = CmdStroke(tile.tile.offset, 0.5 * stroke_width); + Cmd_Stroke_write(cmd_alloc, cmd_ref, cmd_stroke); + cmd_ref.offset += 4 + CmdStroke_size; + } +} + +void write_fill_rev(AnnotatedTag tag, Tile tile, float stroke_width) { + if (fill_mode_from_flags(tag.flags) == MODE_NONZERO) { + if (tile.tile.offset != 0) { + CmdFill cmd_fill = CmdFill(tile.tile.offset, tile.backdrop); + alpha_cmd_ref.offset -= 4 + CmdFill_size; + Cmd_Fill_write(alpha_cmd_alloc, alpha_cmd_ref, cmd_fill); + } else { + alpha_cmd_ref.offset -= 4; + Cmd_Solid_write(alpha_cmd_alloc, alpha_cmd_ref); + } + } else { + CmdStroke cmd_stroke = CmdStroke(tile.tile.offset, 0.5 * stroke_width); + alpha_cmd_ref.offset -= 4 + CmdStroke_size; + Cmd_Stroke_write(alpha_cmd_alloc, alpha_cmd_ref, cmd_stroke); + } + + alpha_cmd_ref.offset -= 4; + Cmd_RestoreStencil_write(alpha_cmd_alloc, alpha_cmd_ref); + + Cmd_SaveStencil_write(cmd_alloc, cmd_ref); + cmd_ref.offset += 4; +} + void main() { if (mem_error != NO_ERROR) { return; @@ -112,12 +174,18 @@ void main() { uint tile_x = gl_LocalInvocationID.x % N_TILE_X; uint tile_y = gl_LocalInvocationID.x / N_TILE_X; uint this_tile_ix = (bin_tile_y + tile_y) * conf.width_in_tiles + bin_tile_x + tile_x; - Alloc cmd_alloc = slice_mem(conf.ptcl_alloc, this_tile_ix * PTCL_INITIAL_ALLOC, PTCL_INITIAL_ALLOC); - CmdRef cmd_ref = CmdRef(cmd_alloc.offset); + cmd_alloc = slice_mem(conf.ptcl_alloc, this_tile_ix * PTCL_INITIAL_ALLOC * 2, PTCL_INITIAL_ALLOC); + cmd_ref = CmdRef(cmd_alloc.offset); // Reserve space for the maximum number of commands and a potential jump. - uint cmd_limit = cmd_ref.offset + PTCL_INITIAL_ALLOC - (ANNO_COMMANDS + 1) * Cmd_size; + cmd_limit = cmd_ref.offset + PTCL_INITIAL_ALLOC - (ANNO_COMMANDS + 1) * Cmd_size; + alpha_cmd_alloc = slice_mem(conf.ptcl_alloc, PTCL_INITIAL_ALLOC * (this_tile_ix * 2 + 1), PTCL_INITIAL_ALLOC); + alpha_cmd_ref = CmdRef(alpha_cmd_alloc.offset + PTCL_INITIAL_ALLOC - Cmd_size); + if (bin_tile_x + tile_x < conf.width_in_tiles && bin_tile_y + tile_y < conf.height_in_tiles) { + Cmd_End_write(alpha_cmd_alloc, alpha_cmd_ref); + } + alpha_cmd_limit = alpha_cmd_ref.offset + ANNO_COMMANDS * Cmd_size; // The nesting depth of the clip stack - uint clip_depth = 0; + uint clip_depth = 1; // State for the "clip zero" optimization. If it's nonzero, then we are // currently in a clip for which the entire tile has an alpha of zero, and // the value is the depth after the "begin clip" of that element. @@ -277,7 +345,7 @@ void main() { } if (include_tile) { uint el_slice = el_ix / 32; - uint el_mask = 1 << (el_ix & 31); + uint el_mask = 1 << (el_ix & 31u); atomicOr(sh_bitmaps[el_slice][y * N_TILE_X + x], el_mask); } } @@ -317,49 +385,38 @@ void main() { Tile tile = Tile_read(read_tile_alloc(element_ref_ix), TileRef(sh_tile_base[element_ref_ix] + (sh_tile_stride[element_ref_ix] * tile_y + tile_x) * Tile_size)); AnnoColor fill = Annotated_Color_read(conf.anno_alloc, ref); - if (!alloc_cmd(cmd_alloc, cmd_ref, cmd_limit)) { - break; - } - if (fill_mode_from_flags(tag.flags) == MODE_NONZERO) { - if (tile.tile.offset != 0) { - CmdFill cmd_fill = CmdFill(tile.tile.offset, tile.backdrop); - Cmd_Fill_write(cmd_alloc, cmd_ref, cmd_fill); - cmd_ref.offset += 4 + CmdFill_size; - } else { - Cmd_Solid_write(cmd_alloc, cmd_ref); - cmd_ref.offset += 4; + if (unpackUnorm4x8(fill.rgba_color).wzyx.a == 1.0) { + if (!alloc_cmd()) { + break; + } + write_fill(tag, tile, fill.linewidth); + Cmd_Color_write(cmd_alloc, cmd_ref, CmdColor(fill.rgba_color)); + cmd_ref.offset += 4 + CmdColor_size; + if (tile.tile.offset == 0) { + // Tile is fully occluded. See include_tile logic above for the invariant. + clip_zero_depth = clip_depth; } } else { - CmdStroke cmd_stroke = CmdStroke(tile.tile.offset, 0.5 * fill.linewidth); - Cmd_Stroke_write(cmd_alloc, cmd_ref, cmd_stroke); - cmd_ref.offset += 4 + CmdStroke_size; + if (!alloc_cmd_rev()) { + break; + } + alpha_cmd_ref.offset -= 4 + CmdColor_size; + Cmd_Color_write(alpha_cmd_alloc, alpha_cmd_ref, CmdColor(fill.rgba_color)); + write_fill_rev(tag, tile, fill.linewidth); } - Cmd_Color_write(cmd_alloc, cmd_ref, CmdColor(fill.rgba_color)); - cmd_ref.offset += 4 + CmdColor_size; break; case Annotated_Image: tile = Tile_read(read_tile_alloc(element_ref_ix), TileRef(sh_tile_base[element_ref_ix] + (sh_tile_stride[element_ref_ix] * tile_y + tile_x) * Tile_size)); AnnoImage fill_img = Annotated_Image_read(conf.anno_alloc, ref); - if (!alloc_cmd(cmd_alloc, cmd_ref, cmd_limit)) { + // For now, use back-to-front drawing for all images. + // Optimizations for opaque images can be done in the future. + if (!alloc_cmd_rev()) { break; } - if (fill_mode_from_flags(tag.flags) == MODE_NONZERO) { - if (tile.tile.offset != 0) { - CmdFill cmd_fill = CmdFill(tile.tile.offset, tile.backdrop); - Cmd_Fill_write(cmd_alloc, cmd_ref, cmd_fill); - cmd_ref.offset += 4 + CmdFill_size; - } else { - Cmd_Solid_write(cmd_alloc, cmd_ref); - cmd_ref.offset += 4; - } - } else { - CmdStroke cmd_stroke = CmdStroke(tile.tile.offset, 0.5 * fill_img.linewidth); - Cmd_Stroke_write(cmd_alloc, cmd_ref, cmd_stroke); - cmd_ref.offset += 4 + CmdStroke_size; - } - Cmd_Image_write(cmd_alloc, cmd_ref, CmdImage(fill_img.index, fill_img.offset)); - cmd_ref.offset += 4 + CmdImage_size; + alpha_cmd_ref.offset -= 4 + CmdImage_size; + Cmd_Image_write(alpha_cmd_alloc, alpha_cmd_ref, CmdImage(fill_img.index, fill_img.offset)); + write_fill_rev(tag, tile, fill_img.linewidth); break; case Annotated_BeginClip: tile = Tile_read(read_tile_alloc(element_ref_ix), TileRef(sh_tile_base[element_ref_ix] @@ -367,44 +424,27 @@ void main() { if (tile.tile.offset == 0 && tile.backdrop == 0) { clip_zero_depth = clip_depth + 1; } else if (tile.tile.offset == 0 && clip_depth < 32) { - clip_one_mask |= (1 << clip_depth); + clip_one_mask |= (1u << clip_depth); } else { AnnoBeginClip begin_clip = Annotated_BeginClip_read(conf.anno_alloc, ref); - if (!alloc_cmd(cmd_alloc, cmd_ref, cmd_limit)) { + if (!alloc_cmd()) { break; } - if (fill_mode_from_flags(tag.flags) == MODE_NONZERO) { - if (tile.tile.offset != 0) { - CmdFill cmd_fill = CmdFill(tile.tile.offset, tile.backdrop); - Cmd_Fill_write(cmd_alloc, cmd_ref, cmd_fill); - cmd_ref.offset += 4 + CmdFill_size; - } else { - // TODO: here is where a bunch of optimization magic should happen - float alpha = tile.backdrop == 0 ? 0.0 : 1.0; - Cmd_Alpha_write(cmd_alloc, cmd_ref, CmdAlpha(alpha)); - cmd_ref.offset += 4 + CmdAlpha_size; - } - } else { - CmdStroke cmd_stroke = CmdStroke(tile.tile.offset, 0.5 * begin_clip.linewidth); - Cmd_Stroke_write(cmd_alloc, cmd_ref, cmd_stroke); - cmd_ref.offset += 4 + CmdStroke_size; - } + write_fill(tag, tile, begin_clip.linewidth); Cmd_BeginClip_write(cmd_alloc, cmd_ref); cmd_ref.offset += 4; if (clip_depth < 32) { - clip_one_mask &= ~(1 << clip_depth); + clip_one_mask &= ~(1u << clip_depth); } } clip_depth++; break; case Annotated_EndClip: clip_depth--; - if (clip_depth >= 32 || (clip_one_mask & (1 << clip_depth)) == 0) { - if (!alloc_cmd(cmd_alloc, cmd_ref, cmd_limit)) { + if (clip_depth >= 32 || (clip_one_mask & (1u << clip_depth)) == 0) { + if (!alloc_cmd()) { break; } - Cmd_Solid_write(cmd_alloc, cmd_ref); - cmd_ref.offset += 4; Cmd_EndClip_write(cmd_alloc, cmd_ref); cmd_ref.offset += 4; } @@ -431,6 +471,7 @@ void main() { if (rd_ix >= ready_ix && partition_ix >= n_partitions) break; } if (bin_tile_x + tile_x < conf.width_in_tiles && bin_tile_y + tile_y < conf.height_in_tiles) { - Cmd_End_write(cmd_alloc, cmd_ref); + CmdJump jump = CmdJump(alpha_cmd_ref.offset); + Cmd_Jump_write(cmd_alloc, cmd_ref, jump); } } diff --git a/piet-gpu/shader/coarse.spv b/piet-gpu/shader/coarse.spv index 763da55dfe178000cde8215b1f25e0e200f1a2dc..13115df7ef7019c958c2472b01da558c2e53a8d6 100644 GIT binary patch literal 59336 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 literal 58380 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 diff --git a/piet-gpu/shader/kernel4.comp b/piet-gpu/shader/kernel4.comp index fc2523e4..0c0b6665 100644 --- a/piet-gpu/shader/kernel4.comp +++ b/piet-gpu/shader/kernel4.comp @@ -14,7 +14,7 @@ #include "setup.h" #define CHUNK_X 2 -#define CHUNK_Y 4 +#define CHUNK_Y 2 #define CHUNK CHUNK_X * CHUNK_Y #define CHUNK_DX (TILE_WIDTH_PX / CHUNK_X) #define CHUNK_DY (TILE_HEIGHT_PX / CHUNK_Y) @@ -26,6 +26,8 @@ layout(set = 0, binding = 1) readonly buffer ConfigBuf { layout(rgba8, set = 0, binding = 2) uniform writeonly image2D image; +layout(r32ui, set = 0, binding = 3) uniform uimage2D winding_lut; + #if GL_EXT_nonuniform_qualifier layout(rgba8, set = 0, binding = 3) uniform readonly image2D images[]; #else @@ -35,8 +37,6 @@ layout(rgba8, set = 0, binding = 3) uniform readonly image2D images[1]; #include "ptcl.h" #include "tile.h" -#define BLEND_STACK_SIZE 4 - // Layout of a clip scratch frame: // Each frame is WIDTH * HEIGHT 32-bit words, then a link reference. @@ -46,6 +46,37 @@ layout(rgba8, set = 0, binding = 3) uniform readonly image2D images[1]; shared MallocResult sh_clip_alloc; +// The lookup table is implemented based on the description of Efficient GPU Path Rendering Using Scanline Rasterization, +// Li et al. 2016. The coordinate mapping could potentially use some improvement (since the paper does not mention how +// they derived it), but for now it's implemented verbatim from the paper's description. +// The lookup table is precomputed in piet_gpu::Renderer::make_winding_lut. +uint getLut(vec2 n, float c) { + // Storing these variables into a temporary helps the compiler to generate code that is capable of instruction-level + // parallelism, which avoids stalling the pipeline before the sampled bitmasks are actually used. + uint mask = 0; + uint signBit = floatBitsToUint(c) & 0x80000000u; + n = uintBitsToFloat(floatBitsToUint(n) ^ uvec2(signBit)); + c = abs(c); + uint base = int(signBit) >> 31; + if (c < 1.) { + vec2 tex_coord = (0.5 - 0.5 * c) * n + vec2(0.5, 0.5); + uvec4 tex = imageLoad(winding_lut, ivec2(vec2(imageSize(winding_lut)) * tex_coord)); + mask = tex.r; + } + return base ^ mask; +} + +// Same as getLut, but specialized for n = (0, 1) and uses the distance from top edge instead of center. +// A trick is used in piet_gpu::Renderer::make_winding_lut so that this can be done with arithmetic only. +uint vertLut(float y) { + uint idx = uint(clamp(y + 0.5 / 32., 0., 1.) * 32.); + // ~0 << idx, but in a weird way to avoid UB on idx == 32 + // base = idx == 32 ? 1 : 0; + uint base = idx >> 5; + // Hopefully all implementation satisfies 0 << 32 == 0 (but this is undefined) + return (base - 1) << idx; +} + // Allocate a scratch buffer for clipping. MallocResult alloc_clip_buf(uint link) { if (gl_LocalInvocationID.x == 0 && gl_LocalInvocationID.y == 0) { @@ -112,23 +143,28 @@ void main() { } uint tile_ix = gl_WorkGroupID.y * conf.width_in_tiles + gl_WorkGroupID.x; - Alloc cmd_alloc = slice_mem(conf.ptcl_alloc, tile_ix * PTCL_INITIAL_ALLOC, PTCL_INITIAL_ALLOC); + Alloc cmd_alloc = slice_mem(conf.ptcl_alloc, tile_ix * PTCL_INITIAL_ALLOC * 2, PTCL_INITIAL_ALLOC); CmdRef cmd_ref = CmdRef(cmd_alloc.offset); uvec2 xy_uint = uvec2(gl_LocalInvocationID.x + TILE_WIDTH_PX * gl_WorkGroupID.x, gl_LocalInvocationID.y + TILE_HEIGHT_PX * gl_WorkGroupID.y); vec2 xy = vec2(xy_uint); vec3 rgb[CHUNK]; - uint blend_stack[BLEND_STACK_SIZE][CHUNK]; - uint blend_spill = 0; - uint blend_sp = 0; + uint stencil[CHUNK]; + uint coverage[CHUNK]; + uint stencil_stack[256][CHUNK]; + uint clip_depth = 0; + uint alpha_stencil_stack[256][CHUNK]; + uint alpha_depth = 0; + Alloc clip_tos = new_alloc(0, 0); for (uint i = 0; i < CHUNK; i++) { - rgb[i] = vec3(0.5); + rgb[i] = vec3(0.0); #ifdef ENABLE_IMAGE_INDICES if (xy_uint.x < 1024 && xy_uint.y < 1024) { - rgb[i] = imageLoad(images[gl_WorkGroupID.x / 64], ivec2(xy_uint + chunk_offset(i))/4).rgb; + // rgb[i] = imageLoad(images[gl_WorkGroupID.x / 64], ivec2(xy_uint.x, xy_uint.y + CHUNK_DY * i)/4).rgb; } #endif + stencil[i] = 0; } float area[CHUNK]; @@ -141,76 +177,84 @@ void main() { case Cmd_Stroke: // Calculate distance field from all the line segments in this tile. CmdStroke stroke = Cmd_Stroke_read(cmd_alloc, cmd_ref); - float df[CHUNK]; - for (uint k = 0; k < CHUNK; k++) df[k] = 1e9; TileSegRef tile_seg_ref = TileSegRef(stroke.tile_ref); + for (uint k = 0; k < CHUNK; k++) { + coverage[k] = 0; + } do { TileSeg seg = TileSeg_read(new_alloc(tile_seg_ref.offset, TileSeg_size), tile_seg_ref); - vec2 line_vec = seg.vector; - for (uint k = 0; k < CHUNK; k++) { - vec2 dpos = xy + vec2(0.5, 0.5) - seg.origin; - dpos += vec2(chunk_offset(k)); - float t = clamp(dot(line_vec, dpos) / dot(line_vec, line_vec), 0.0, 1.0); - df[k] = min(df[k], length(line_vec * t - dpos)); + float len2 = dot(seg.vector, seg.vector); + if (len2 > 0.) { + // Compute the stroke area with a rectanglar implicit test. + float rlen = inversesqrt(len2); + float len = rlen * len2; + vec2 u = seg.vector * rlen; + vec2 n = vec2(-u.y, u.x); + for (uint k = 0; k < CHUNK; k++) { + vec2 my_xy = xy + vec2(chunk_offset(k)); + vec2 dpos = seg.origin - my_xy - vec2(0.5, 0.5); + float kp = dot(dpos, n); + uint par = getLut(n, kp + stroke.half_width) ^ getLut(n, kp - stroke.half_width); + float ko = dot(dpos, u); + uint ortho = getLut(u, ko) ^ getLut(u, ko + len); + coverage[k] |= par & ortho; + } } tile_seg_ref = seg.next; } while (tile_seg_ref.offset != 0); - for (uint k = 0; k < CHUNK; k++) { - area[k] = clamp(stroke.half_width + 0.5 - df[k], 0.0, 1.0); - } cmd_ref.offset += 4 + CmdStroke_size; break; case Cmd_Fill: CmdFill fill = Cmd_Fill_read(cmd_alloc, cmd_ref); - for (uint k = 0; k < CHUNK; k++) area[k] = float(fill.backdrop); + for (uint k = 0; k < CHUNK; k++) coverage[k] = (fill.backdrop % 2 == 0 ? 0 : ~0); tile_seg_ref = TileSegRef(fill.tile_ref); - // Calculate coverage based on backdrop + coverage of each line segment do { TileSeg seg = TileSeg_read(new_alloc(tile_seg_ref.offset, TileSeg_size), tile_seg_ref); + vec2 o = vec2(0.5, 0.5); + vec2 n = normalize(vec2(-seg.vector.y, seg.vector.x)); + // Make sure that the normal vector points the right side (we want the area to the right of the line). + // The code below flips n if n.x < 0. + uint signBit = floatBitsToUint(n.x) & 0x80000000u; + n = vec2(abs(n.x), uintBitsToFloat(floatBitsToUint(n.y) ^ signBit)); for (uint k = 0; k < CHUNK; k++) { vec2 my_xy = xy + vec2(chunk_offset(k)); vec2 start = seg.origin - my_xy; vec2 end = start + seg.vector; - vec2 window = clamp(vec2(start.y, end.y), 0.0, 1.0); - if (window.x != window.y) { - vec2 t = (window - start.y) / seg.vector.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[k] += a * (window.x - window.y); - } - area[k] += sign(seg.vector.x) * clamp(my_xy.y - seg.y_edge + 1.0, 0.0, 1.0); + // The horizontal ray test is calculated with lookup tables as an AND of three half-planes: + // Two of them are used to confine the range of y, and this is implemented using a logically equivalent + // method with XOR. While the left edge is already clipped to tile boundary (see path_corase.comp), there + // is no equivalent preprocessing performed on the right edge. However this is fine: when combined with the + // third half-plane, which corresponds to the line, this will always yield the correct coverage. + uint y_range = vertLut(start.y) ^ vertLut(end.y); + float c = dot(n, start - o); + coverage[k] ^= y_range & getLut(n, c); + + // Calculate the vertical ray (also called y-edge). + coverage[k] ^= vertLut(seg.y_edge - my_xy.y); } tile_seg_ref = seg.next; } while (tile_seg_ref.offset != 0); - for (uint k = 0; k < CHUNK; k++) { - area[k] = min(abs(area[k]), 1.0); - } cmd_ref.offset += 4 + CmdFill_size; break; case Cmd_Solid: for (uint k = 0; k < CHUNK; k++) { - area[k] = 1.0; + coverage[k] = ~0u; } cmd_ref.offset += 4; break; - case Cmd_Alpha: - CmdAlpha alpha = Cmd_Alpha_read(cmd_alloc, cmd_ref); - for (uint k = 0; k < CHUNK; k++) { - area[k] = alpha.alpha; - } - cmd_ref.offset += 4 + CmdAlpha_size; - break; case Cmd_Color: CmdColor color = Cmd_Color_read(cmd_alloc, cmd_ref); vec4 fg = unpacksRGB(color.rgba_color); for (uint k = 0; k < CHUNK; k++) { - vec4 fg_k = fg * area[k]; - rgb[k] = rgb[k] * (1.0 - fg_k.a) + fg_k.rgb; + float area = float(bitCount(coverage[k] & ~stencil[k])) / 32.; + if (fg.a == 1.0) { + rgb[k] = rgb[k] + fg.rgb * area; + // only update stencil if opaque + stencil[k] |= coverage[k]; + } else { + vec4 fg_k = fg * area; + rgb[k] = rgb[k] * (1.0 - fg_k.a) + fg_k.rgb; + } } cmd_ref.offset += 4 + CmdColor_size; break; @@ -218,48 +262,38 @@ void main() { CmdImage fill_img = Cmd_Image_read(cmd_alloc, cmd_ref); vec4 img[CHUNK] = fillImage(xy_uint, fill_img); for (uint k = 0; k < CHUNK; k++) { - vec4 fg_k = img[k] * area[k]; + float area = float(bitCount(coverage[k] & ~stencil[k])) / 32.; + vec4 fg_k = img[k] * area; rgb[k] = rgb[k] * (1.0 - fg_k.a) + fg_k.rgb; } cmd_ref.offset += 4 + CmdImage_size; break; case Cmd_BeginClip: - uint blend_slot = blend_sp % BLEND_STACK_SIZE; - if (blend_sp == blend_spill + BLEND_STACK_SIZE) { - // spill to scratch buffer - MallocResult m = alloc_clip_buf(clip_tos.offset); - if (m.failed) { - return; - } - clip_tos = m.alloc; - uint base_ix = (clip_tos.offset >> 2) + gl_LocalInvocationID.x + TILE_WIDTH_PX * gl_LocalInvocationID.y; - for (uint k = 0; k < CHUNK; k++) { - uvec2 offset = chunk_offset(k); - write_mem(clip_tos, base_ix + offset.x + offset.y * TILE_WIDTH_PX, blend_stack[blend_slot][k]); - } - blend_spill++; - } for (uint k = 0; k < CHUNK; k++) { - blend_stack[blend_slot][k] = packsRGB(vec4(rgb[k], clamp(abs(area[k]), 0.0, 1.0))); + stencil_stack[clip_depth][k] = stencil[k]; + stencil[k] |= ~coverage[k]; } - blend_sp++; + clip_depth++; cmd_ref.offset += 4; break; case Cmd_EndClip: - blend_slot = (blend_sp - 1) % BLEND_STACK_SIZE; - if (blend_sp == blend_spill) { - uint base_ix = (clip_tos.offset >> 2) + gl_LocalInvocationID.x + TILE_WIDTH_PX * gl_LocalInvocationID.y; - for (uint k = 0; k < CHUNK; k++) { - uvec2 offset = chunk_offset(k); - blend_stack[blend_slot][k] = read_mem(clip_tos, base_ix + offset.x + offset.y * TILE_WIDTH_PX); - } - clip_tos.offset = read_mem(clip_tos, (clip_tos.offset >> 2) + CLIP_LINK_OFFSET); - blend_spill--; + clip_depth--; + for (uint k = 0; k < CHUNK; k++) { + stencil[k] = stencil_stack[clip_depth][k]; } - blend_sp--; + cmd_ref.offset += 4; + break; + case Cmd_SaveStencil: + for (uint k = 0; k < CHUNK; k++) { + alpha_stencil_stack[alpha_depth][k] = stencil[k]; + } + alpha_depth++; + cmd_ref.offset += 4; + break; + case Cmd_RestoreStencil: + alpha_depth--; for (uint k = 0; k < CHUNK; k++) { - vec4 rgba = unpacksRGB(blend_stack[blend_slot][k]); - rgb[k] = mix(rgba.rgb, rgb[k], area[k] * rgba.a); + stencil[k] = alpha_stencil_stack[alpha_depth][k]; } cmd_ref.offset += 4; break; diff --git a/piet-gpu/shader/kernel4.spv b/piet-gpu/shader/kernel4.spv index 323bb2363765344a6d1c3b69e76a1835e1b28c90..1b78cf8622cc84dd4f409987ea633724b62969c6 100644 GIT binary patch literal 33980 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
>%sNqyjmY@F7=#O8-UeQa$aqSmh;Ma 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 diff --git a/piet-gpu/shader/ptcl.h b/piet-gpu/shader/ptcl.h index 6267fc53..9dbc351f 100644 --- a/piet-gpu/shader/ptcl.h +++ b/piet-gpu/shader/ptcl.h @@ -18,10 +18,6 @@ struct CmdImageRef { uint offset; }; -struct CmdAlphaRef { - uint offset; -}; - struct CmdJumpRef { uint offset; }; @@ -73,16 +69,6 @@ CmdImageRef CmdImage_index(CmdImageRef ref, uint index) { return CmdImageRef(ref.offset + index * CmdImage_size); } -struct CmdAlpha { - float alpha; -}; - -#define CmdAlpha_size 4 - -CmdAlphaRef CmdAlpha_index(CmdAlphaRef ref, uint index) { - return CmdAlphaRef(ref.offset + index * CmdAlpha_size); -} - struct CmdJump { uint new_ref; }; @@ -97,12 +83,13 @@ CmdJumpRef CmdJump_index(CmdJumpRef ref, uint index) { #define Cmd_Fill 1 #define Cmd_Stroke 2 #define Cmd_Solid 3 -#define Cmd_Alpha 4 -#define Cmd_Color 5 -#define Cmd_Image 6 -#define Cmd_BeginClip 7 -#define Cmd_EndClip 8 -#define Cmd_Jump 9 +#define Cmd_Color 4 +#define Cmd_Image 5 +#define Cmd_BeginClip 6 +#define Cmd_EndClip 7 +#define Cmd_Jump 8 +#define Cmd_SaveStencil 9 +#define Cmd_RestoreStencil 10 #define Cmd_size 12 CmdRef Cmd_index(CmdRef ref, uint index) { @@ -175,19 +162,6 @@ void CmdImage_write(Alloc a, CmdImageRef ref, CmdImage s) { write_mem(a, ix + 1, (uint(s.offset.x) & 0xffff) | (uint(s.offset.y) << 16)); } -CmdAlpha CmdAlpha_read(Alloc a, CmdAlphaRef ref) { - uint ix = ref.offset >> 2; - uint raw0 = read_mem(a, ix + 0); - CmdAlpha s; - s.alpha = uintBitsToFloat(raw0); - return s; -} - -void CmdAlpha_write(Alloc a, CmdAlphaRef ref, CmdAlpha s) { - uint ix = ref.offset >> 2; - write_mem(a, ix + 0, floatBitsToUint(s.alpha)); -} - CmdJump CmdJump_read(Alloc a, CmdJumpRef ref) { uint ix = ref.offset >> 2; uint raw0 = read_mem(a, ix + 0); @@ -214,10 +188,6 @@ CmdStroke Cmd_Stroke_read(Alloc a, CmdRef ref) { return CmdStroke_read(a, CmdStrokeRef(ref.offset + 4)); } -CmdAlpha Cmd_Alpha_read(Alloc a, CmdRef ref) { - return CmdAlpha_read(a, CmdAlphaRef(ref.offset + 4)); -} - CmdColor Cmd_Color_read(Alloc a, CmdRef ref) { return CmdColor_read(a, CmdColorRef(ref.offset + 4)); } @@ -248,11 +218,6 @@ void Cmd_Solid_write(Alloc a, CmdRef ref) { write_mem(a, ref.offset >> 2, Cmd_Solid); } -void Cmd_Alpha_write(Alloc a, CmdRef ref, CmdAlpha s) { - write_mem(a, ref.offset >> 2, Cmd_Alpha); - CmdAlpha_write(a, CmdAlphaRef(ref.offset + 4), s); -} - void Cmd_Color_write(Alloc a, CmdRef ref, CmdColor s) { write_mem(a, ref.offset >> 2, Cmd_Color); CmdColor_write(a, CmdColorRef(ref.offset + 4), s); @@ -276,3 +241,11 @@ void Cmd_Jump_write(Alloc a, CmdRef ref, CmdJump s) { CmdJump_write(a, CmdJumpRef(ref.offset + 4), s); } +void Cmd_SaveStencil_write(Alloc a, CmdRef ref) { + write_mem(a, ref.offset >> 2, Cmd_SaveStencil); +} + +void Cmd_RestoreStencil_write(Alloc a, CmdRef ref) { + write_mem(a, ref.offset >> 2, Cmd_RestoreStencil); +} + diff --git a/piet-gpu/src/lib.rs b/piet-gpu/src/lib.rs index 3e46eae3..aa7801ff 100644 --- a/piet-gpu/src/lib.rs +++ b/piet-gpu/src/lib.rs @@ -14,6 +14,7 @@ use piet_gpu_types::encoder::Encode; use piet_gpu_hal::{hub}; use piet_gpu_hal::{CmdBuf, Error, ImageLayout, MemFlags}; +use piet_gpu_hal::vulkan::Format; use pico_svg::PicoSvg; @@ -27,8 +28,18 @@ const WIDTH_IN_TILES: usize = 128; const HEIGHT_IN_TILES: usize = 96; const PTCL_INITIAL_ALLOC: usize = 1024; +const LG_WG_FACTOR: usize = 1; +const WG_FACTOR: usize = (1 << LG_WG_FACTOR); + +const N_TILE_X: usize = 16; +const N_TILE_Y: usize = 8 * WG_FACTOR; + const N_CIRCLES: usize = 0; +fn ceil_div(a: usize, b: usize) -> usize { + (a + b - 1) / b +} + pub fn render_svg(rc: &mut impl RenderContext, filename: &str, scale: f64) { let xml_str = std::fs::read_to_string(filename).unwrap(); let start = std::time::Instant::now(); @@ -76,6 +87,7 @@ pub fn render_scene(rc: &mut impl RenderContext) { render_clip_test(rc); render_alpha_test(rc); //render_tiger(rc); + rc.finish().unwrap(); } #[allow(unused)] @@ -119,7 +131,7 @@ fn render_clip_test(rc: &mut impl RenderContext) { rc.clip(path); } let rect = piet::kurbo::Rect::new(X0, Y0, X1, Y1); - rc.fill(rect, &Color::BLACK); + rc.fill(rect, &Color::WHITE); for _ in 0..N { rc.restore(); } @@ -222,6 +234,7 @@ pub struct Renderer { // Keep a reference to the image so that it is not destroyed. _bg_image: hub::Image, + _winding_lut: hub::Image, } impl Renderer { @@ -250,7 +263,7 @@ impl Renderer { scene_buf_host.write(&scene)?; let state_buf = session.create_buffer(1 * 1024 * 1024, dev)?; - let image_dev = session.create_image2d(WIDTH as u32, HEIGHT as u32, dev)?; + let image_dev = session.create_image2d(WIDTH as u32, HEIGHT as u32, Format::R8G8B8A8_UNORM, dev)?; const CONFIG_SIZE: u64 = 10*4; // Size of Config in setup.h. let mut config_buf_host = session.create_buffer(CONFIG_SIZE, host)?; @@ -268,7 +281,7 @@ impl Renderer { let bin_base = alloc; alloc += ((n_paths + 255) & !255) * BIN_SIZE; let ptcl_base = alloc; - alloc += WIDTH_IN_TILES * HEIGHT_IN_TILES * PTCL_INITIAL_ALLOC; + alloc += WIDTH_IN_TILES * HEIGHT_IN_TILES * PTCL_INITIAL_ALLOC * 2; let pathseg_base = alloc; alloc += (n_pathseg * PATHSEG_SIZE + 3) & !3; let anno_base = alloc; @@ -277,7 +290,7 @@ impl Renderer { alloc += (n_trans * TRANS_SIZE + 3) & !3; config_buf_host.write(&[n_paths as u32, n_pathseg as u32, WIDTH_IN_TILES as u32, HEIGHT_IN_TILES as u32, tile_base as u32, bin_base as u32, ptcl_base as u32, pathseg_base as u32, anno_base as u32, trans_base as u32])?; - let mut memory_buf_host = session.create_buffer(2*4, host)?; + let mut memory_buf_host = session.create_buffer(2 * 4, host)?; let memory_buf_dev = session.create_buffer(128 * 1024 * 1024, dev)?; memory_buf_host.write(&[alloc as u32, 0 /* Overflow flag */])?; @@ -325,6 +338,7 @@ impl Renderer { )?; let bg_image = Self::make_test_bg_image(&session); + let winding_lut = Self::make_winding_lut(&session); let k4_code = include_bytes!("../shader/kernel4.spv"); // This is an arbitrary limit on the number of textures that can be referenced by @@ -340,13 +354,13 @@ impl Renderer { let k4_pipeline = session .pipeline_builder() .add_buffers(2) - .add_images(1) + .add_images(2) .add_textures(max_textures) .create_compute_pipeline(&session, k4_code)?; let k4_ds = session .descriptor_set_builder() .add_buffers(&[&memory_buf_dev, &config_buf_dev]) - .add_images(&[&image_dev]) + .add_images(&[&image_dev, &winding_lut]) .add_textures(&[&bg_image]) .build(&session, &k4_pipeline)?; @@ -377,6 +391,7 @@ impl Renderer { n_paths, n_pathseg, _bg_image: bg_image, + _winding_lut: winding_lut, }) } @@ -440,7 +455,7 @@ impl Renderer { cmd_buf.dispatch( &self.coarse_pipeline, &self.coarse_ds, - ((WIDTH as u32 + 255) / 256, (HEIGHT as u32 + 255) / 256, 1), + (ceil_div(WIDTH, N_TILE_X * TILE_W) as u32, ceil_div(HEIGHT, N_TILE_Y * TILE_H) as u32, 1), ); cmd_buf.write_timestamp(&query_pool, 6); cmd_buf.memory_barrier(); @@ -464,17 +479,27 @@ impl Renderer { height: usize, buf: &[u8], format: ImageFormat, + ) -> Result { + if format != ImageFormat::RgbaPremul { + return Err("unsupported image format".into()); + } + Self::make_image_internal(session, width, height, buf, Format::R8G8B8A8_UNORM) + } + + pub fn make_image_internal( + session: &hub::Session, + width: usize, + height: usize, + buf: &[u8], + format: Format, ) -> Result { unsafe { - if format != ImageFormat::RgbaPremul { - return Err("unsupported image format".into()); - } let host_mem_flags = MemFlags::host_coherent(); let dev_mem_flags = MemFlags::device_local(); let mut buffer = session.create_buffer(buf.len() as u64, host_mem_flags)?; buffer.write(buf)?; let image = - session.create_image2d(width.try_into()?, height.try_into()?, dev_mem_flags)?; + session.create_image2d(width.try_into()?, height.try_into()?, format, dev_mem_flags)?; let mut cmd_buf = session.cmd_buf()?; cmd_buf.begin(); cmd_buf.image_barrier( @@ -511,4 +536,68 @@ impl Renderer { } Self::make_image(session, WIDTH, HEIGHT, &buf, ImageFormat::RgbaPremul).unwrap() } + + fn make_winding_lut(session: &hub::Session) -> hub::Image { + // TODO: generate this at build time + const WIDTH: usize = 256; + const HEIGHT: usize = 256; + let mut buf = Vec::with_capacity(WIDTH * HEIGHT * 4); + // Sobol sequence, generated with PyTorch and normalized to have a mean of (0.5, 0.5). + let mut sample_points = [ + (0.015625, 0.015625), + (0.515625, 0.515625), + (0.765625, 0.265625), + (0.265625, 0.765625), + (0.390625, 0.390625), + (0.890625, 0.890625), + (0.640625, 0.140625), + (0.140625, 0.640625), + (0.203125, 0.328125), + (0.703125, 0.828125), + (0.953125, 0.078125), + (0.453125, 0.578125), + (0.328125, 0.203125), + (0.828125, 0.703125), + (0.578125, 0.453125), + (0.078125, 0.953125), + (0.109375, 0.484375), + (0.609375, 0.984375), + (0.859375, 0.234375), + (0.359375, 0.734375), + (0.484375, 0.109375), + (0.984375, 0.609375), + (0.734375, 0.359375), + (0.234375, 0.859375), + (0.171875, 0.171875), + (0.671875, 0.671875), + (0.921875, 0.421875), + (0.421875, 0.921875), + (0.296875, 0.296875), + (0.796875, 0.796875), + (0.546875, 0.046875), + (0.046875, 0.546875) + ]; + // Sort the y sample points so lookups for vertical rays can be done using pure arithmetic + // Should have just sorted beforehand though... + sample_points.sort_unstable_by_key(|(x, y)| (y * 64.) as u32); + for yi in 0..HEIGHT { + for xi in 0..WIDTH { + let x = (xi as f64 + 0.5) / WIDTH as f64; + let y = (yi as f64 + 0.5) / HEIGHT as f64; + let dvec = (x - 0.5, y - 0.5); + let len = (dvec.0 * dvec.0 + dvec.1 * dvec.1).sqrt(); + let n = (dvec.0 / len, dvec.1 / len); + let c = 1. - 2. * len; + let mut mask: u32 = 0; + for (bit, &(sx, sy)) in sample_points.iter().enumerate() { + if n.0 * (sx - 0.5) + n.1 * (sy - 0.5) > c { + mask |= 1 << bit as u32; + } + } + let ne_mask = mask.to_ne_bytes(); + buf.extend_from_slice(&ne_mask); + } + } + Self::make_image_internal(session, WIDTH, HEIGHT, &buf, Format::R32_UINT).unwrap() + } } diff --git a/piet-gpu/src/render_ctx.rs b/piet-gpu/src/render_ctx.rs index e80d67d3..d7628ceb 100644 --- a/piet-gpu/src/render_ctx.rs +++ b/piet-gpu/src/render_ctx.rs @@ -28,11 +28,17 @@ pub struct PietGpuText; pub struct PietGpuRenderContext { encoder: Encoder, + // The elements are drawn in a mostly front-to-back order, but the API accepts elements in + // back-to-front order instead. Therefore, in each callback we first buffer the commands first, + // then reverse it in flush() and store it in elements. In get_scene_buf(), we reverse the order + // of element again, therefore the overall drawing order is reversed, but the structure of each + // command remains to be the order specified in individual functions. + element_tmp: Vec, elements: Vec, // Will probably need direct accesss to hal Device to create images etc. inner_text: PietGpuText, - stroke_width: f32, - fill_mode: FillMode, + stroke_width: Option, + fill_mode: Option, // We're tallying these cpu-side for expedience, but will probably // move this to some kind of readback from element processing. /// The count of elements that make it through to coarse rasterization. @@ -68,6 +74,7 @@ struct ClipElement { /// Index of BeginClip element in element vec, for bbox fixup. begin_ix: usize, bbox: Option, + encoded_path: Vec, } #[derive(Clone,Copy,PartialEq)] @@ -83,15 +90,16 @@ const TOLERANCE: f64 = 0.25; impl PietGpuRenderContext { pub fn new() -> PietGpuRenderContext { let encoder = Encoder::new(); + let element_tmp = Vec::new(); let elements = Vec::new(); let inner_text = PietGpuText; - let stroke_width = 0.0; PietGpuRenderContext { encoder, + element_tmp, elements, inner_text, - stroke_width, - fill_mode: FillMode::Nonzero, + stroke_width: None, + fill_mode: None, path_count: 0, pathseg_count: 0, trans_count: 0, @@ -102,6 +110,8 @@ impl PietGpuRenderContext { } pub fn get_scene_buf(&mut self) -> &[u8] { + // TODO: iterator based reverse + self.elements.reverse(); self.elements.encode(&mut self.encoder); self.encoder.buf() } @@ -117,13 +127,19 @@ impl PietGpuRenderContext { pub fn trans_count(&self) -> usize { self.trans_count } + + fn flush(&mut self) { + self.elements.extend(self.element_tmp.drain(..).rev()); + } } fn set_fill_mode(ctx: &mut PietGpuRenderContext, fill_mode: FillMode) { - if ctx.fill_mode != fill_mode { - ctx.elements - .push(Element::SetFillMode(SetFillMode { fill_mode: fill_mode as u32 })); - ctx.fill_mode = fill_mode; + if ctx.fill_mode != Some(fill_mode) { + if let Some(cur_mode) = ctx.fill_mode { + ctx.element_tmp + .push(Element::SetFillMode(SetFillMode { fill_mode: cur_mode as u32 })); + } + ctx.fill_mode = Some(fill_mode); } } @@ -149,12 +165,6 @@ impl RenderContext for PietGpuRenderContext { fn stroke(&mut self, shape: impl Shape, brush: &impl IntoBrush, width: f64) { let width_f32 = width as f32; - if self.stroke_width != width_f32 { - self.elements - .push(Element::SetLineWidth(SetLineWidth { width: width_f32 })); - self.stroke_width = width_f32; - } - set_fill_mode(self, FillMode::Stroke); let brush = brush.make_brush(self, || shape.bounding_box()).into_owned(); match brush { PietGpuBrush::Solid(rgba_color) => { @@ -163,11 +173,22 @@ impl RenderContext for PietGpuRenderContext { let path = shape.path_elements(TOLERANCE); self.encode_path(path, false); let stroke = FillColor { rgba_color }; - self.elements.push(Element::FillColor(stroke)); + self.element_tmp.push(Element::FillColor(stroke)); self.path_count += 1; } _ => (), } + // Push the stroke width for the paths drawn *after* us, not *ours*. The last path is + // handled by finish(). + if self.stroke_width != Some(width_f32) { + if let Some(stroke_width) = self.stroke_width { + self.element_tmp + .push(Element::SetLineWidth(SetLineWidth { width: stroke_width })); + } + self.stroke_width = Some(width_f32); + } + set_fill_mode(self, FillMode::Stroke); + self.flush(); } fn stroke_styled( @@ -186,32 +207,38 @@ impl RenderContext for PietGpuRenderContext { // Perhaps that should be added to kurbo. self.accumulate_bbox(|| shape.bounding_box()); let path = shape.path_elements(TOLERANCE); - set_fill_mode(self, FillMode::Nonzero); self.encode_path(path, true); let fill = FillColor { rgba_color }; - self.elements.push(Element::FillColor(fill)); + self.element_tmp.push(Element::FillColor(fill)); self.path_count += 1; + set_fill_mode(self, FillMode::Nonzero); } + self.flush(); } fn fill_even_odd(&mut self, _shape: impl Shape, _brush: &impl IntoBrush) {} fn clip(&mut self, shape: impl Shape) { - set_fill_mode(self, FillMode::Nonzero); let path = shape.path_elements(TOLERANCE); self.encode_path(path, true); + let encoded_path = std::mem::take(&mut self.element_tmp); + // WARNING: Be careful to not push anything after Element::Clip let begin_ix = self.elements.len(); - self.elements.push(Element::BeginClip(Clip { + self.element_tmp.push(Element::EndClip(Clip { bbox: Default::default(), })); + self.path_count += 1; self.clip_stack.push(ClipElement { bbox: None, begin_ix, + encoded_path, }); self.path_count += 1; + set_fill_mode(self, FillMode::Nonzero); if let Some(tos) = self.state_stack.last_mut() { tos.n_clip += 1; } + self.flush(); } fn text(&mut self) -> &mut Self::Text { @@ -226,6 +253,7 @@ impl RenderContext for PietGpuRenderContext { transform: self.cur_transform, n_clip: 0, }); + self.flush(); Ok(()) } @@ -233,7 +261,7 @@ impl RenderContext for PietGpuRenderContext { if let Some(state) = self.state_stack.pop() { if state.rel_transform != Affine::default() { let a_inv = state.rel_transform.inverse(); - self.elements + self.element_tmp .push(Element::Transform(to_scene_transform(a_inv))); self.trans_count += 1; } @@ -241,6 +269,7 @@ impl RenderContext for PietGpuRenderContext { for _ in 0..state.n_clip { self.pop_clip(); } + self.flush(); Ok(()) } else { Err(Error::StackUnbalance) @@ -248,6 +277,17 @@ impl RenderContext for PietGpuRenderContext { } fn finish(&mut self) -> Result<(), Error> { + if let Some(stroke_width) = self.stroke_width.take() { + self.elements + .push(Element::SetLineWidth(SetLineWidth { width: stroke_width })); + } + if let Some(fill_mode) = self.fill_mode.take() { + self.elements + .push(Element::SetFillMode(SetFillMode { fill_mode: fill_mode as u32 })); + } + for _ in 0..self.state_stack.len() { + self.restore()?; + } for _ in 0..self.clip_stack.len() { self.pop_clip(); } @@ -255,13 +295,14 @@ impl RenderContext for PietGpuRenderContext { } fn transform(&mut self, transform: Affine) { - self.elements + self.element_tmp .push(Element::Transform(to_scene_transform(transform))); self.trans_count += 1; if let Some(tos) = self.state_stack.last_mut() { tos.rel_transform *= transform; } self.cur_transform *= transform; + self.flush(); } fn make_image( @@ -306,17 +347,17 @@ impl RenderContext for PietGpuRenderContext { impl PietGpuRenderContext { fn encode_line_seg(&mut self, seg: LineSeg) { - self.elements.push(Element::Line(seg)); + self.element_tmp.push(Element::Line(seg)); self.pathseg_count += 1; } fn encode_quad_seg(&mut self, seg: QuadSeg) { - self.elements.push(Element::Quad(seg)); + self.element_tmp.push(Element::Quad(seg)); self.pathseg_count += 1; } fn encode_cubic_seg(&mut self, seg: CubicSeg) { - self.elements.push(Element::Cubic(seg)); + self.element_tmp.push(Element::Cubic(seg)); self.pathseg_count += 1; } @@ -435,17 +476,19 @@ impl PietGpuRenderContext { 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.element_tmp.extend(tos.encoded_path); + self.element_tmp + .push(Element::BeginClip(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; + if let Element::EndClip(end_clip) = &mut self.elements[tos.begin_ix] { + end_clip.bbox = bbox_f32_4; } else { unreachable!("expected BeginClip, not found"); } if let Some(bbox) = tos.bbox { self.union_bbox(bbox); } + self.flush(); } /// Accumulate a bbox. From 722ac4fdb807227f502d131ea19b6d7c9e47288e Mon Sep 17 00:00:00 2001 From: Ishi Tatsuyuki Date: Mon, 5 Apr 2021 14:05:05 +0900 Subject: [PATCH 2/7] Bugfixes --- piet-gpu/src/lib.rs | 1 + piet-gpu/src/render_ctx.rs | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/piet-gpu/src/lib.rs b/piet-gpu/src/lib.rs index aa7801ff..d5e6014e 100644 --- a/piet-gpu/src/lib.rs +++ b/piet-gpu/src/lib.rs @@ -49,6 +49,7 @@ pub fn render_svg(rc: &mut impl RenderContext, filename: &str, scale: f64) { let start = std::time::Instant::now(); svg.render(rc); println!("flattening and encoding time: {:?}", start.elapsed()); + rc.finish().unwrap(); } pub fn render_scene(rc: &mut impl RenderContext) { diff --git a/piet-gpu/src/render_ctx.rs b/piet-gpu/src/render_ctx.rs index d7628ceb..17104077 100644 --- a/piet-gpu/src/render_ctx.rs +++ b/piet-gpu/src/render_ctx.rs @@ -227,14 +227,12 @@ impl RenderContext for PietGpuRenderContext { self.element_tmp.push(Element::EndClip(Clip { bbox: Default::default(), })); - self.path_count += 1; self.clip_stack.push(ClipElement { bbox: None, begin_ix, encoded_path, }); self.path_count += 1; - set_fill_mode(self, FillMode::Nonzero); if let Some(tos) = self.state_stack.last_mut() { tos.n_clip += 1; } @@ -485,6 +483,7 @@ impl PietGpuRenderContext { } else { unreachable!("expected BeginClip, not found"); } + set_fill_mode(self, FillMode::Nonzero); if let Some(bbox) = tos.bbox { self.union_bbox(bbox); } From 777dd7dcd980cfbc00db804b66793c0fdee7a901 Mon Sep 17 00:00:00 2001 From: Ishi Tatsuyuki Date: Tue, 6 Apr 2021 21:54:58 +0900 Subject: [PATCH 3/7] Optimization --- piet-gpu/shader/kernel4.comp | 6 +++++- piet-gpu/shader/kernel4.spv | Bin 33980 -> 34248 bytes 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/piet-gpu/shader/kernel4.comp b/piet-gpu/shader/kernel4.comp index 0c0b6665..43126e73 100644 --- a/piet-gpu/shader/kernel4.comp +++ b/piet-gpu/shader/kernel4.comp @@ -44,6 +44,8 @@ layout(rgba8, set = 0, binding = 3) uniform readonly image2D images[1]; #define CLIP_LINK_OFFSET (TILE_WIDTH_PX * TILE_HEIGHT_PX) #define CLIP_BUF_SIZE (CLIP_LINK_OFFSET + 1) +#define WINDING_LUT_SIZE 256 + shared MallocResult sh_clip_alloc; // The lookup table is implemented based on the description of Efficient GPU Path Rendering Using Scanline Rasterization, @@ -60,7 +62,7 @@ uint getLut(vec2 n, float c) { uint base = int(signBit) >> 31; if (c < 1.) { vec2 tex_coord = (0.5 - 0.5 * c) * n + vec2(0.5, 0.5); - uvec4 tex = imageLoad(winding_lut, ivec2(vec2(imageSize(winding_lut)) * tex_coord)); + uvec4 tex = imageLoad(winding_lut, ivec2(vec2(WINDING_LUT_SIZE * tex_coord))); mask = tex.r; } return base ^ mask; @@ -191,6 +193,7 @@ void main() { vec2 u = seg.vector * rlen; vec2 n = vec2(-u.y, u.x); for (uint k = 0; k < CHUNK; k++) { + if (stencil[k] == ~0u) continue; vec2 my_xy = xy + vec2(chunk_offset(k)); vec2 dpos = seg.origin - my_xy - vec2(0.5, 0.5); float kp = dot(dpos, n); @@ -217,6 +220,7 @@ void main() { uint signBit = floatBitsToUint(n.x) & 0x80000000u; n = vec2(abs(n.x), uintBitsToFloat(floatBitsToUint(n.y) ^ signBit)); for (uint k = 0; k < CHUNK; k++) { + if (stencil[k] == ~0u) continue; vec2 my_xy = xy + vec2(chunk_offset(k)); vec2 start = seg.origin - my_xy; vec2 end = start + seg.vector; diff --git a/piet-gpu/shader/kernel4.spv b/piet-gpu/shader/kernel4.spv index 1b78cf8622cc84dd4f409987ea633724b62969c6..c03a662bfad057c22612041aae01da43ab140f35 100644 GIT binary patch literal 34248 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 Date: Sat, 10 Apr 2021 20:59:48 +0900 Subject: [PATCH 4/7] Fix binding index for images --- piet-gpu/shader/kernel4.comp | 4 ++-- piet-gpu/shader/kernel4.spv | Bin 34248 -> 34248 bytes 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/piet-gpu/shader/kernel4.comp b/piet-gpu/shader/kernel4.comp index 43126e73..a51befc2 100644 --- a/piet-gpu/shader/kernel4.comp +++ b/piet-gpu/shader/kernel4.comp @@ -29,9 +29,9 @@ layout(rgba8, set = 0, binding = 2) uniform writeonly image2D image; layout(r32ui, set = 0, binding = 3) uniform uimage2D winding_lut; #if GL_EXT_nonuniform_qualifier -layout(rgba8, set = 0, binding = 3) uniform readonly image2D images[]; +layout(rgba8, set = 0, binding = 4) uniform readonly image2D images[]; #else -layout(rgba8, set = 0, binding = 3) uniform readonly image2D images[1]; +layout(rgba8, set = 0, binding = 4) uniform readonly image2D images[1]; #endif #include "ptcl.h" diff --git a/piet-gpu/shader/kernel4.spv b/piet-gpu/shader/kernel4.spv index c03a662bfad057c22612041aae01da43ab140f35..02e719f3f78ab5804a5e09f6ca52a1273c2b38c2 100644 GIT binary patch delta 16 XcmX@n&2*xhX+wo1Bg^JWN&g%GI7$Wa delta 16 XcmX@n&2*xhX+wo1BlG4;N&g%GI79{T From d6564abae82bae85e7b38773c5363315a790784d Mon Sep 17 00:00:00 2001 From: Ishi Tatsuyuki Date: Sun, 11 Apr 2021 19:42:36 +0900 Subject: [PATCH 5/7] Move vector normalization to path_coarse.comp Performing the normalization in path_coarse.comp reduces redundant computation as each thread in SIMD computes for a different path. Gives a slight overall performance boost. The normal vector direction was flipped to simplify logic. --- piet-gpu-types/src/tile.rs | 1 + piet-gpu/shader/kernel4.comp | 37 ++++++++++++------------------- piet-gpu/shader/kernel4.spv | Bin 34248 -> 33876 bytes piet-gpu/shader/path_coarse.comp | 31 +++++++++++++++++--------- piet-gpu/shader/path_coarse.spv | Bin 42804 -> 43812 bytes piet-gpu/shader/tile.h | 14 +++++++----- 6 files changed, 44 insertions(+), 39 deletions(-) diff --git a/piet-gpu-types/src/tile.rs b/piet-gpu-types/src/tile.rs index 27e87f43..f64e6e74 100644 --- a/piet-gpu-types/src/tile.rs +++ b/piet-gpu-types/src/tile.rs @@ -15,6 +15,7 @@ piet_gpu! { struct TileSeg { origin: [f32; 2], vector: [f32; 2], + len: f32, y_edge: f32, next: Ref, } diff --git a/piet-gpu/shader/kernel4.comp b/piet-gpu/shader/kernel4.comp index a51befc2..2a0c6b4c 100644 --- a/piet-gpu/shader/kernel4.comp +++ b/piet-gpu/shader/kernel4.comp @@ -185,23 +185,18 @@ void main() { } do { TileSeg seg = TileSeg_read(new_alloc(tile_seg_ref.offset, TileSeg_size), tile_seg_ref); - float len2 = dot(seg.vector, seg.vector); - if (len2 > 0.) { - // Compute the stroke area with a rectanglar implicit test. - float rlen = inversesqrt(len2); - float len = rlen * len2; - vec2 u = seg.vector * rlen; - vec2 n = vec2(-u.y, u.x); - for (uint k = 0; k < CHUNK; k++) { - if (stencil[k] == ~0u) continue; - vec2 my_xy = xy + vec2(chunk_offset(k)); - vec2 dpos = seg.origin - my_xy - vec2(0.5, 0.5); - float kp = dot(dpos, n); - uint par = getLut(n, kp + stroke.half_width) ^ getLut(n, kp - stroke.half_width); - float ko = dot(dpos, u); - uint ortho = getLut(u, ko) ^ getLut(u, ko + len); - coverage[k] |= par & ortho; - } + // Compute the stroke area with a rectanglar implicit test. + vec2 u = seg.vector; + vec2 n = vec2(u.y, -u.x); + for (uint k = 0; k < CHUNK; k++) { + if (stencil[k] == ~0u) continue; + vec2 my_xy = xy + vec2(chunk_offset(k)); + vec2 dpos = seg.origin - my_xy - vec2(0.5, 0.5); + float kp = dot(dpos, n); + uint par = getLut(n, kp + stroke.half_width) ^ getLut(n, kp - stroke.half_width); + float ko = dot(dpos, u); + uint ortho = getLut(u, ko) ^ getLut(u, ko + seg.len); + coverage[k] |= par & ortho; } tile_seg_ref = seg.next; } while (tile_seg_ref.offset != 0); @@ -214,16 +209,12 @@ void main() { do { TileSeg seg = TileSeg_read(new_alloc(tile_seg_ref.offset, TileSeg_size), tile_seg_ref); vec2 o = vec2(0.5, 0.5); - vec2 n = normalize(vec2(-seg.vector.y, seg.vector.x)); - // Make sure that the normal vector points the right side (we want the area to the right of the line). - // The code below flips n if n.x < 0. - uint signBit = floatBitsToUint(n.x) & 0x80000000u; - n = vec2(abs(n.x), uintBitsToFloat(floatBitsToUint(n.y) ^ signBit)); + vec2 n = vec2(seg.vector.y, -seg.vector.x); for (uint k = 0; k < CHUNK; k++) { if (stencil[k] == ~0u) continue; vec2 my_xy = xy + vec2(chunk_offset(k)); vec2 start = seg.origin - my_xy; - vec2 end = start + seg.vector; + vec2 end = start + seg.vector * seg.len; // The horizontal ray test is calculated with lookup tables as an AND of three half-planes: // Two of them are used to confine the range of y, and this is implemented using a logically equivalent // method with XOR. While the left edge is already clipped to tile boundary (see path_corase.comp), there diff --git a/piet-gpu/shader/kernel4.spv b/piet-gpu/shader/kernel4.spv index 02e719f3f78ab5804a5e09f6ca52a1273c2b38c2..3eb2514ee85577d21e9f4e275e3af629a2d43af0 100644 GIT binary patch literal 33876 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 Vx;NcNw)eAiGuZz8oQeIM{Wt140E7Sl literal 34248 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 p1.x) { - tile_seg.vector = p - p0; + tile_seg.len *= max(1e-9, t); } else { - tile_seg.origin = p; - tile_seg.vector = p1 - p; - } - // kernel4 uses sign(vector.x) for the sign of the intersection backdrop. - // Nudge zeroes towards the intended sign. - if (tile_seg.vector.x == 0) { - tile_seg.vector.x = sign(p1.x - p0.x)*1e-9; + tile_seg.origin = vec2(tile_x0, y_edge); + tile_seg.len *= max(1e-9, 1 - t); } } if (x <= min_xray || max_xray < x) { diff --git a/piet-gpu/shader/path_coarse.spv b/piet-gpu/shader/path_coarse.spv index b4cd985aaeeea5b51864b9168b1465fec8216d4d..85e872ae9513d06735471307291599a68dad4511 100644 GIT binary patch literal 43812 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 BsXG7w literal 42804 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 diff --git a/piet-gpu/shader/tile.h b/piet-gpu/shader/tile.h index e11329c4..c926845c 100644 --- a/piet-gpu/shader/tile.h +++ b/piet-gpu/shader/tile.h @@ -43,11 +43,12 @@ TileRef Tile_index(TileRef ref, uint index) { struct TileSeg { vec2 origin; vec2 vector; + float len; float y_edge; TileSegRef next; }; -#define TileSeg_size 24 +#define TileSeg_size 28 TileSegRef TileSeg_index(TileSegRef ref, uint index) { return TileSegRef(ref.offset + index * TileSeg_size); @@ -106,11 +107,13 @@ TileSeg TileSeg_read(Alloc a, TileSegRef ref) { uint raw3 = read_mem(a, ix + 3); uint raw4 = read_mem(a, ix + 4); uint raw5 = read_mem(a, ix + 5); + uint raw6 = read_mem(a, ix + 6); TileSeg s; s.origin = vec2(uintBitsToFloat(raw0), uintBitsToFloat(raw1)); s.vector = vec2(uintBitsToFloat(raw2), uintBitsToFloat(raw3)); - s.y_edge = uintBitsToFloat(raw4); - s.next = TileSegRef(raw5); + s.len = uintBitsToFloat(raw4); + s.y_edge = uintBitsToFloat(raw5); + s.next = TileSegRef(raw6); return s; } @@ -120,8 +123,9 @@ void TileSeg_write(Alloc a, TileSegRef ref, TileSeg s) { write_mem(a, ix + 1, floatBitsToUint(s.origin.y)); write_mem(a, ix + 2, floatBitsToUint(s.vector.x)); write_mem(a, ix + 3, floatBitsToUint(s.vector.y)); - write_mem(a, ix + 4, floatBitsToUint(s.y_edge)); - write_mem(a, ix + 5, s.next.offset); + write_mem(a, ix + 4, floatBitsToUint(s.len)); + write_mem(a, ix + 5, floatBitsToUint(s.y_edge)); + write_mem(a, ix + 6, s.next.offset); } TransformSeg TransformSeg_read(Alloc a, TransformSegRef ref) { From ef8fb90816676d3f417bf1f666a3e37fba4ef062 Mon Sep 17 00:00:00 2001 From: Tatsuyuki Ishi Date: Sun, 11 Apr 2021 13:20:40 +0900 Subject: [PATCH 6/7] Encode premultiplied alpha in render_ctx.rs --- piet-gpu/src/lib.rs | 10 ++----- piet-gpu/src/render_ctx.rs | 58 +++++++++++++++++++++++++------------- 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/piet-gpu/src/lib.rs b/piet-gpu/src/lib.rs index d5e6014e..e4a78133 100644 --- a/piet-gpu/src/lib.rs +++ b/piet-gpu/src/lib.rs @@ -140,16 +140,12 @@ fn render_clip_test(rc: &mut impl RenderContext) { #[allow(unused)] fn render_alpha_test(rc: &mut impl RenderContext) { - // Alpha compositing tests. kernel4 expects colors encoded in alpha-premultiplied sRGB: - // - // [α,sRGB(α⋅R),sRGB(α⋅G),sRGB(α⋅B)] - // - // See also http://ssp.impulsetrain.com/gamma-premult.html. + // Alpha compositing tests. rc.fill(diamond(Point::new(1024.0, 100.0)), &Color::Rgba32(0xff0000ff)); - rc.fill(diamond(Point::new(1024.0, 125.0)), &Color::Rgba32(0x00ba0080)); + rc.fill(diamond(Point::new(1024.0, 125.0)), &Color::Rgba32(0x00ff0080)); rc.save(); rc.clip(diamond(Point::new(1024.0, 150.0))); - rc.fill(diamond(Point::new(1024.0, 175.0)), &Color::Rgba32(0x0000ba80)); + rc.fill(diamond(Point::new(1024.0, 175.0)), &Color::Rgba32(0x0000ff80)); rc.restore(); } diff --git a/piet-gpu/src/render_ctx.rs b/piet-gpu/src/render_ctx.rs index 17104077..0562e5d8 100644 --- a/piet-gpu/src/render_ctx.rs +++ b/piet-gpu/src/render_ctx.rs @@ -1,21 +1,19 @@ use std::{borrow::Cow, ops::RangeBounds}; -use piet_gpu_types::encoder::{Encode, Encoder}; - -use piet_gpu_types::scene::{ - Clip, CubicSeg, Element, FillColor, SetFillMode, LineSeg, QuadSeg, SetLineWidth, Transform, -}; - use piet::{ - kurbo::{Affine, Insets, PathEl, Point, Rect, Shape, Size}, - HitTestPosition, TextAttribute, TextStorage, + HitTestPosition, + kurbo::{Affine, Insets, PathEl, Point, Rect, Shape, Size}, TextAttribute, TextStorage, }; - use piet::{ Color, Error, FixedGradient, FontFamily, HitTestPoint, ImageFormat, InterpolationMode, IntoBrush, LineMetric, RenderContext, StrokeStyle, Text, TextLayout, TextLayoutBuilder, }; +use piet_gpu_types::encoder::{Encode, Encoder}; +use piet_gpu_types::scene::{ + Clip, CubicSeg, Element, FillColor, LineSeg, QuadSeg, SetFillMode, SetLineWidth, Transform, +}; + pub struct PietGpuImage; #[derive(Clone)] @@ -77,7 +75,7 @@ struct ClipElement { encoded_path: Vec, } -#[derive(Clone,Copy,PartialEq)] +#[derive(Clone, Copy, PartialEq)] enum FillMode { // Fill path according to the non-zero winding rule. Nonzero = 0, @@ -154,7 +152,14 @@ impl RenderContext for PietGpuRenderContext { } fn solid_brush(&mut self, color: Color) -> Self::Brush { - PietGpuBrush::Solid(color.as_rgba_u32()) + // kernel4 expects colors encoded in alpha-premultiplied sRGB: + // + // [α,sRGB(α⋅R),sRGB(α⋅G),sRGB(α⋅B)] + // + // See also http://ssp.impulsetrain.com/gamma-premult.html. + let (r, g, b, a) = color.as_rgba(); + let premul = Color::rgba(to_srgb(from_srgb(r) * a), to_srgb(from_srgb(g) * a), to_srgb(from_srgb(b) * a), a); + PietGpuBrush::Solid(premul.as_rgba_u32()) } fn gradient(&mut self, _gradient: impl Into) -> Result { @@ -197,8 +202,7 @@ impl RenderContext for PietGpuRenderContext { _brush: &impl IntoBrush, _width: f64, _style: &StrokeStyle, - ) { - } + ) {} fn fill(&mut self, shape: impl Shape, brush: &impl IntoBrush) { let brush = brush.make_brush(self, || shape.bounding_box()).into_owned(); @@ -318,8 +322,7 @@ impl RenderContext for PietGpuRenderContext { _image: &Self::Image, _rect: impl Into, _interp: InterpolationMode, - ) { - } + ) {} fn draw_image_area( &mut self, @@ -327,8 +330,7 @@ impl RenderContext for PietGpuRenderContext { _src_rect: impl Into, _dst_rect: impl Into, _interp: InterpolationMode, - ) { - } + ) {} fn blurred_rect(&mut self, _rect: Rect, _blur_radius: f64, _brush: &impl IntoBrush) {} @@ -359,7 +361,7 @@ impl PietGpuRenderContext { self.pathseg_count += 1; } - fn encode_path(&mut self, path: impl Iterator, is_fill: bool) { + fn encode_path(&mut self, path: impl Iterator, is_fill: bool) { if is_fill { self.encode_path_inner(path.flat_map(|el| { match el { @@ -374,7 +376,7 @@ impl PietGpuRenderContext { } } - fn encode_path_inner(&mut self, path: impl Iterator) { + fn encode_path_inner(&mut self, path: impl Iterator) { let flatten = false; if flatten { let mut start_pt = None; @@ -625,3 +627,21 @@ fn to_scene_transform(transform: Affine) -> Transform { translate: [c[4] as f32, c[5] as f32], } } + +fn to_srgb(f: f64) -> f64 { + if f <= 0.0031308 { + f * 12.92 + } else { + let a = 0.055; + (1. + a) * f64::powf(f, f64::recip(2.4)) - a + } +} + +fn from_srgb(f: f64) -> f64 { + if f <= 0.04045 { + f / 12.92 + } else { + let a = 0.055; + f64::powf((f + a) * f64::recip(1. + a), 2.4) + } +} \ No newline at end of file From 9aba124f28aed8e877f35986bf2a7613d418f9d0 Mon Sep 17 00:00:00 2001 From: Ishi Tatsuyuki Date: Mon, 12 Apr 2021 12:53:12 +0900 Subject: [PATCH 7/7] Cut down winding lookups more aggressively Inspired by the original kernel4 code, this change avoids the expensive lookup whenever it's determined that the lookup result would be discarded (bit AND against a zero). It's unfortunate that we end up doing a lot of redundant work in kernel4, but at least we gain up to 10% of improvement for now. --- piet-gpu/shader/kernel4.comp | 6 ++++-- piet-gpu/shader/kernel4.spv | Bin 33876 -> 33964 bytes 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/piet-gpu/shader/kernel4.comp b/piet-gpu/shader/kernel4.comp index 2a0c6b4c..6d4fb1e1 100644 --- a/piet-gpu/shader/kernel4.comp +++ b/piet-gpu/shader/kernel4.comp @@ -221,8 +221,10 @@ void main() { // is no equivalent preprocessing performed on the right edge. However this is fine: when combined with the // third half-plane, which corresponds to the line, this will always yield the correct coverage. uint y_range = vertLut(start.y) ^ vertLut(end.y); - float c = dot(n, start - o); - coverage[k] ^= y_range & getLut(n, c); + if (y_range != 0) { + float c = dot(n, start - o); + coverage[k] ^= y_range & getLut(n, c); + } // Calculate the vertical ray (also called y-edge). coverage[k] ^= vertLut(seg.y_edge - my_xy.y); diff --git a/piet-gpu/shader/kernel4.spv b/piet-gpu/shader/kernel4.spv index 3eb2514ee85577d21e9f4e275e3af629a2d43af0..15832585965c639b40d0e0d6107427580d2f172b 100644 GIT binary patch delta 7610 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 IPF^?mJvAp@hyVZp delta 7500 zcmZ9Q3Aj~N8HP7^V9*?|a=|MqAmRYd12?=X3MeWnh@c?i6)%W_C=QjH2h>W^C_4^4 z4y9R|rKX_`mX?)MrAca|P@1I8nVM-@z3*XvJo|c{XS2TV`-kDkMh zHfnAf-mfV76dM%%i_`AiXx6~mW-UcAwcyLjo8h{*7PDH4TY4E=id%c}=HfPZin$wHE#?9Ey!k1n zxp-#1Z{K1~{o(fZ!Edcsnv0EFlc`@Zu>NX$M{URTN=wlNt`>b@FWy{Cf-fjHVTyQB z_xzrcPwVB|Qq1hd`xZU$$=wSVE}lEJ=bZBvp6f>SEm{XQ43dUzomlkLZyD6qXa36j z2aRkR?M%b;;BzQFA|DEFYVhU;A6(i_T{)m*9V2G7GOXR_HyhS6K^kv9|^aY!)G_q!!~mSNBgZ}?kVd(7_4>; zG1f2d;xU)G#fd${i(+sVG#>1sVQ6fj{1mv8TZjo9lVAea_$+-QSS^R9O|7~(3(0(k zw$ztQ+`RKJq(ga!=+CdWgOC%>9OtTi_8y9|JZ@o8K04 zZ}{HWrhrdoG)9{~e&eWT)@fih&n=5S4z8~MvF@OPcW60I)4^9VVrV!_WmMC724jx; zc(A+a=uyvPoW8fRycvjWF{9U_++rbSy?yOqgId4 zDPZ;Rx!`Kdsqj`})Z^1rgJ^i1RrrFO2kzm?3v)i$#OhDtvkN|_fz=noeKRcpt6k16 z%1;NYZBriI9I%?j`yM<4?9SfKd$49D1}#L;a9?>77J=2?!0Ao4e=*pPw?f;QJeTme zQ{t@XEU>E%KfB~k?l;^L`0Es=j?b>eQp9s08Vs@z&IMcH(foGOejZru-1-ZLx7W@G zxuzAQ)cOIi9_q_V?So*mYRl9g0z0)O+EH@-QtAcZYUvl2+<3pJE`oFDwL(LJJ`C0n zr;EX!vdQA(A*(+^A`f{fSk2hPEdwXc;^g|-&hEYY?0$C{=^kAMagUtD1N;a$yQ2N0 zaDCKGcqyY=iuxG1y4uT2p50mwcem7ySxU~2gO^*mPPWn&U=!O)Ddb9cL&wL$J^{Cj z)N^Djz$VjXoCSXpY_1&or@(4Hen`O{6h(d0DgA3#A(}*|RQzeM6}vK5EZ48Pg>I$X zw}o5km>sEu-I2UVuLi5-eXLE5_<@aAj|qHRkJd6UC^!jqd`hg{)AAj- zd#kRGSK~pjx_lV?A@JQiUMKC}1?!{kc|5?VmN~u$c8;zZ3EcjNOCeY1`*5$0xhIH4@ak=r>u2g6=rKzh%4pfopy#SQ3)bf7RrxvNFL?YI`%4>E2f+Gu{R(W7 z+?-#7)pB#>&+(YdSaJ5`x8Rr9!|>n1%@zJa$=!3m@m}=zk+(!coPG~}k!Kv8q|-~_ zH9RIap{rEWFEMMu)rtO4@=WwH+$*e}m{-6r*Z3#b@sD7|1pNtYaVAgDpTX++s{IRC z&Dg~K6`VMWmFwr0yZ!E(qd9BdmcK!)K0WYvuv%J4o0Z@*WhZ2e;uq={e}MqyxN2E*XxU>b=2MjIm0C!s;l`oT&+60np^P} zW4iNguv_71cfP~;E{`8q`rp#-n3etyY>qT1Q`QQ_bSFabAGpa(E>3VCYzBun!7V(z zxwNP8THrRGe%j*K3f8u6JL%xQ5D(l5TxElUU4<*M5XU6wrwyL{?hjYXj%iafKD*?C za()A_2K4-KgSS?2+-;~EZ<`rjW+QDlpM=}U*uGj~cJUg)!+?_{X z_eUigume#;&T(t7e_1oR30mULSINM{Ng?d2TC;GD&}o7jn>nvCxUt5r{MEId(GDc8?gauY|e3aj*w`v+jlZ+8C#EZ635 z_JhFYG7o}A#Bv6{`aIo9%XmcIOIGM+f3qGo}JLXu92Ad