From 15cd306af6b2baa99904b2c4155c328f69df81eb Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Sun, 30 Apr 2023 23:11:57 -0400 Subject: [PATCH 01/17] Extend modes for gradients This patch implements the pad, repeat and reflect extend modes for gradient brushes. Adds a new example demonstrating the functionality. Also fixes a few bugs: * Clamps alpha in blend.wgsl for the `blend_compose` function. The `Plus` mode was generating `alpha > 1.0` leading to incorrect rendering. * Small change to radial gradients in fine.wgsl to reject pixels outside the cone when the circles don't nest. This requires further work to properly extend the cone when one of the radii is not 0. --- crates/encoding/src/encoding.rs | 18 +++++--- crates/encoding/src/resolve.rs | 12 +++++- examples/scenes/src/test_scenes.rs | 67 ++++++++++++++++++++++++++++++ shader/fine.wgsl | 50 +++++++++++++++++----- shader/shared/blend.wgsl | 3 +- shader/shared/ptcl.wgsl | 2 + 6 files changed, 133 insertions(+), 19 deletions(-) diff --git a/crates/encoding/src/encoding.rs b/crates/encoding/src/encoding.rs index 65dcbbb..1fc4a1d 100644 --- a/crates/encoding/src/encoding.rs +++ b/crates/encoding/src/encoding.rs @@ -113,11 +113,13 @@ impl Encoding { Patch::Ramp { draw_data_offset: offset, stops, + extend, } => { let stops = stops.start + stops_base..stops.end + stops_base; Patch::Ramp { draw_data_offset: offset + offsets.draw_data, stops, + extend: *extend, } } Patch::GlyphRun { index } => Patch::GlyphRun { @@ -264,9 +266,9 @@ impl Encoding { gradient: DrawLinearGradient, color_stops: impl Iterator, alpha: f32, - _extend: Extend, + extend: Extend, ) { - self.add_ramp(color_stops, alpha); + self.add_ramp(color_stops, alpha, extend); self.draw_tags.push(DrawTag::LINEAR_GRADIENT); self.draw_data .extend_from_slice(bytemuck::bytes_of(&gradient)); @@ -278,9 +280,9 @@ impl Encoding { gradient: DrawRadialGradient, color_stops: impl Iterator, alpha: f32, - _extend: Extend, + extend: Extend, ) { - self.add_ramp(color_stops, alpha); + self.add_ramp(color_stops, alpha, extend); self.draw_tags.push(DrawTag::RADIAL_GRADIENT); self.draw_data .extend_from_slice(bytemuck::bytes_of(&gradient)); @@ -331,7 +333,12 @@ impl Encoding { self.path_tags.swap(len - 1, len - 2); } - fn add_ramp(&mut self, color_stops: impl Iterator, alpha: f32) { + fn add_ramp( + &mut self, + color_stops: impl Iterator, + alpha: f32, + extend: Extend, + ) { let offset = self.draw_data.len(); let stops_start = self.color_stops.len(); if alpha != 1.0 { @@ -343,6 +350,7 @@ impl Encoding { self.patches.push(Patch::Ramp { draw_data_offset: offset, stops: stops_start..self.color_stops.len(), + extend, }); } } diff --git a/crates/encoding/src/resolve.rs b/crates/encoding/src/resolve.rs index 0f72939..4e6dbc6 100644 --- a/crates/encoding/src/resolve.rs +++ b/crates/encoding/src/resolve.rs @@ -4,7 +4,7 @@ use std::ops::Range; use bytemuck::{Pod, Zeroable}; -use peniko::Image; +use peniko::{Extend, Image}; use super::{ glyph_cache::{CachedRange, GlyphCache, GlyphKey}, @@ -221,11 +221,13 @@ impl Resolver { ResolvedPatch::Ramp { draw_data_offset, ramp_id, + extend, } => { if pos < *draw_data_offset { data.extend_from_slice(&encoding.draw_data[pos..*draw_data_offset]); } - data.extend_from_slice(bytemuck::bytes_of(ramp_id)); + let index_mode = (ramp_id << 2) | *extend as u32; + data.extend_from_slice(bytemuck::bytes_of(&index_mode)); pos = *draw_data_offset + 4; } ResolvedPatch::GlyphRun { .. } => {} @@ -340,11 +342,13 @@ impl Resolver { Patch::Ramp { draw_data_offset, stops, + extend, } => { let ramp_id = self.ramp_cache.add(&encoding.color_stops[stops.clone()]); self.patches.push(ResolvedPatch::Ramp { draw_data_offset: *draw_data_offset + sizes.draw_data, ramp_id, + extend: *extend, }); } Patch::GlyphRun { index } => { @@ -472,6 +476,8 @@ pub enum Patch { draw_data_offset: usize, /// Range of the gradient stops in the resource set. stops: Range, + /// Extend mode for the gradient. + extend: Extend, }, /// Glyph run resource. GlyphRun { @@ -501,6 +507,8 @@ enum ResolvedPatch { draw_data_offset: usize, /// Resolved ramp index. ramp_id: u32, + /// Extend mode for the gradient. + extend: Extend, }, GlyphRun { /// Index of the original glyph run in the encoding. diff --git a/examples/scenes/src/test_scenes.rs b/examples/scenes/src/test_scenes.rs index 5e4ca8e..e5aff38 100644 --- a/examples/scenes/src/test_scenes.rs +++ b/examples/scenes/src/test_scenes.rs @@ -30,6 +30,7 @@ pub fn test_scenes() -> SceneSet { scene!(funky_paths), scene!(cardioid_and_friends), scene!(animated_text: animated), + scene!(gradient_extend), scene!(brush_transform: animated), scene!(blend_grid), scene!(conflation_artifacts), @@ -249,6 +250,72 @@ fn brush_transform(sb: &mut SceneBuilder, params: &mut SceneParams) { ); } +fn gradient_extend(sb: &mut SceneBuilder, params: &mut SceneParams) { + fn square( + sb: &mut SceneBuilder, + is_radial: bool, + transform: Affine, + brush_transform: Option, + extend: Extend, + ) { + let colors = [Color::RED, Color::GREEN, Color::BLUE]; + let width = 400f64; + let height = 400f64; + let gradient: Brush = if is_radial { + Gradient::new_radial((width * 0.5, height * 0.5), (width * 0.25) as f32) + .with_stops(colors) + .with_extend(extend) + .into() + } else { + Gradient::new_linear((width * 0.35, height * 0.5), (width * 0.65, height * 0.5)) + .with_stops(colors) + .with_extend(extend) + .into() + }; + sb.fill( + Fill::NonZero, + transform, + &gradient, + brush_transform, + &Rect::new(0.0, 0.0, width, height), + ); + } + let extend_modes = [Extend::Pad, Extend::Repeat, Extend::Reflect]; + for x in 0..3 { + let extend = extend_modes[x]; + for y in 0..2 { + let is_radial = y & 1 != 0; + let transform = Affine::translate((x as f64 * 450.0 + 50.0, y as f64 * 450.0 + 100.0)); + square(sb, is_radial, transform, None, extend); + } + } + let white = Color::WHITE.into(); + params.text.add( + sb, + None, + 32.0, + Some(&white), + Affine::translate((50.0, 70.0)), + "Pad", + ); + params.text.add( + sb, + None, + 32.0, + Some(&white), + Affine::translate((500.0, 70.0)), + "Repeat", + ); + params.text.add( + sb, + None, + 32.0, + Some(&white), + Affine::translate((950.0, 70.0)), + "Reflect", + ); +} + fn blend_grid(sb: &mut SceneBuilder, _: &mut SceneParams) { const BLEND_MODES: &[Mix] = &[ Mix::Normal, diff --git a/shader/fine.wgsl b/shader/fine.wgsl index ed47bcb..42970e5 100644 --- a/shader/fine.wgsl +++ b/shader/fine.wgsl @@ -61,16 +61,20 @@ fn read_color(cmd_ix: u32) -> CmdColor { } fn read_lin_grad(cmd_ix: u32) -> CmdLinGrad { - let index = ptcl[cmd_ix + 1u]; + let index_mode = ptcl[cmd_ix + 1u]; + let index = index_mode >> 2u; + let extend_mode = index_mode & 0x3u; let info_offset = ptcl[cmd_ix + 2u]; let line_x = bitcast(info[info_offset]); let line_y = bitcast(info[info_offset + 1u]); let line_c = bitcast(info[info_offset + 2u]); - return CmdLinGrad(index, line_x, line_y, line_c); + return CmdLinGrad(index, extend_mode, line_x, line_y, line_c); } fn read_rad_grad(cmd_ix: u32) -> CmdRadGrad { - let index = ptcl[cmd_ix + 1u]; + let index_mode = ptcl[cmd_ix + 1u]; + let index = index_mode >> 2u; + let extend_mode = index_mode & 0x3u; let info_offset = ptcl[cmd_ix + 2u]; let m0 = bitcast(info[info_offset]); let m1 = bitcast(info[info_offset + 1u]); @@ -81,7 +85,7 @@ fn read_rad_grad(cmd_ix: u32) -> CmdRadGrad { let c1 = vec2(bitcast(info[info_offset + 6u]), bitcast(info[info_offset + 7u])); let ra = bitcast(info[info_offset + 8u]); let roff = bitcast(info[info_offset + 9u]); - return CmdRadGrad(index, matrx, xlat, c1, ra, roff); + return CmdRadGrad(index, extend_mode, matrx, xlat, c1, ra, roff); } fn read_image(cmd_ix: u32) -> CmdImage { @@ -108,6 +112,25 @@ fn read_end_clip(cmd_ix: u32) -> CmdEndClip { return CmdEndClip(blend, alpha); } +fn extend_mode(t: f32, mode: u32) -> f32 { + // This can be replaced with two selects, exchanging the cost + // of a branch for additional ALU + switch mode { + // PAD + case 0u: { + return clamp(t, 0.0, 1.0); + } + // REPEAT + case 1u: { + return fract(t); + } + // REFLECT (2) + default: { + return abs(t - 2.0 * round(0.5 * t)); + } + } +} + #else @group(0) @binding(3) @@ -262,7 +285,7 @@ fn main( let d = lin.line_x * xy.x + lin.line_y * xy.y + lin.line_c; for (var i = 0u; i < PIXELS_PER_THREAD; i += 1u) { let my_d = d + lin.line_x * f32(i); - let x = i32(round(clamp(my_d, 0.0, 1.0) * f32(GRADIENT_WIDTH - 1))); + let x = i32(round(extend_mode(my_d, lin.extend_mode) * f32(GRADIENT_WIDTH - 1))); let fg_rgba = textureLoad(gradients, vec2(x, i32(lin.index)), 0); let fg_i = fg_rgba * area[i]; rgba[i] = rgba[i] * (1.0 - fg_i.a) + fg_i; @@ -278,11 +301,16 @@ fn main( let xy_xformed = rad.matrx.xy * my_xy.x + rad.matrx.zw * my_xy.y + rad.xlat; let ba = dot(xy_xformed, rad.c1); let ca = rad.ra * dot(xy_xformed, xy_xformed); - let t = sqrt(ba * ba + ca) - ba - rad.roff; - let x = i32(round(clamp(t, 0.0, 1.0) * f32(GRADIENT_WIDTH - 1))); - let fg_rgba = textureLoad(gradients, vec2(x, i32(rad.index)), 0); - let fg_i = fg_rgba * area[i]; - rgba[i] = rgba[i] * (1.0 - fg_i.a) + fg_i; + let t0 = sqrt(ba * ba + ca) - ba; + // For radial gradients that generate a cone, reject pixels outside + // the region. + if t0 >= 0.0 { + let t = t0 - rad.roff; + let x = i32(round(extend_mode(t, rad.extend_mode) * f32(GRADIENT_WIDTH - 1))); + let fg_rgba = textureLoad(gradients, vec2(x, i32(rad.index)), 0); + let fg_i = fg_rgba * area[i]; + rgba[i] = rgba[i] * (1.0 - fg_i.a) + fg_i; + } } cmd_ix += 3u; } @@ -352,7 +380,7 @@ fn main( let fg = rgba[i]; // Max with a small epsilon to avoid NaNs let a_inv = 1.0 / max(fg.a, 1e-6); - let rgba_sep = vec4(fg.rgb * a_inv, fg.a); + let rgba_sep = vec4(fg.rgb * a_inv, fg.a); textureStore(output, vec2(coords), rgba_sep); } } diff --git a/shader/shared/blend.wgsl b/shader/shared/blend.wgsl index e101c70..e8ec3c7 100644 --- a/shader/shared/blend.wgsl +++ b/shader/shared/blend.wgsl @@ -306,7 +306,8 @@ fn blend_compose( let as_fa = as_ * fa; let ab_fb = ab * fb; let co = as_fa * cs + ab_fb * cb; - return vec4(co, as_fa + ab_fb); + // Modes like COMPOSE_PLUS can generate alpha > 1.0, so clamp. + return vec4(co, min(as_fa + ab_fb, 1.0)); } // Apply color mixing and composition. Both input and output colors are diff --git a/shader/shared/ptcl.wgsl b/shader/shared/ptcl.wgsl index 5d5e528..8137f32 100644 --- a/shader/shared/ptcl.wgsl +++ b/shader/shared/ptcl.wgsl @@ -44,6 +44,7 @@ struct CmdColor { struct CmdLinGrad { index: u32, + extend_mode: u32, line_x: f32, line_y: f32, line_c: f32, @@ -51,6 +52,7 @@ struct CmdLinGrad { struct CmdRadGrad { index: u32, + extend_mode: u32, matrx: vec4, xlat: vec2, c1: vec2, From c3ca624c1e5f8d1b83938ec2440033144d7269bf Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Tue, 2 May 2023 14:07:25 -0400 Subject: [PATCH 02/17] Add resolve function for path only pipeline Adds a new `resolve_simple` function that doesn't handle late bound resources (gradients, images and glyph runs). --- crates/encoding/src/lib.rs | 2 +- crates/encoding/src/resolve.rs | 59 ++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/crates/encoding/src/lib.rs b/crates/encoding/src/lib.rs index 38da4a6..529fdc6 100644 --- a/crates/encoding/src/lib.rs +++ b/crates/encoding/src/lib.rs @@ -35,4 +35,4 @@ pub use path::{ Cubic, Path, PathBbox, PathEncoder, PathMonoid, PathSegment, PathSegmentType, PathTag, Tile, }; pub use ramp_cache::Ramps; -pub use resolve::{Layout, Patch, Resolver}; +pub use resolve::{resolve_simple, Layout, Patch, Resolver}; diff --git a/crates/encoding/src/resolve.rs b/crates/encoding/src/resolve.rs index 0f72939..fa1a641 100644 --- a/crates/encoding/src/resolve.rs +++ b/crates/encoding/src/resolve.rs @@ -100,6 +100,65 @@ impl Layout { } } +/// Resolves and packs an encoding that doesn't contain late bound resources +/// (gradients, images and glyph runs). +pub fn resolve_simple(encoding: &Encoding, packed: &mut Vec) -> Layout { + assert!( + encoding.patches.is_empty(), + "this resolve function doesn't support late bound resources" + ); + let sizes = StreamOffsets::default(); + let data = packed; + data.clear(); + let mut layout = Layout { + n_paths: encoding.n_paths, + n_clips: encoding.n_clips, + ..Layout::default() + }; + // Compute size of data buffer + let n_path_tags = encoding.path_tags.len() + sizes.path_tags + encoding.n_open_clips as usize; + let path_tag_padded = align_up(n_path_tags, 4 * crate::config::PATH_REDUCE_WG); + let capacity = path_tag_padded + + slice_size_in_bytes(&encoding.path_data, sizes.path_data) + + slice_size_in_bytes( + &encoding.draw_tags, + sizes.draw_tags + encoding.n_open_clips as usize, + ) + + slice_size_in_bytes(&encoding.draw_data, sizes.draw_data) + + slice_size_in_bytes(&encoding.transforms, sizes.transforms) + + slice_size_in_bytes(&encoding.linewidths, sizes.linewidths); + data.reserve(capacity); + // Path tag stream + layout.path_tag_base = size_to_words(data.len()); + data.extend_from_slice(bytemuck::cast_slice(&encoding.path_tags)); + for _ in 0..encoding.n_open_clips { + data.extend_from_slice(bytemuck::bytes_of(&PathTag::PATH)); + } + // Path data stream + layout.path_data_base = size_to_words(data.len()); + data.extend_from_slice(bytemuck::cast_slice(&encoding.path_data)); + // Draw tag stream + layout.draw_tag_base = size_to_words(data.len()); + // Bin data follows draw info + layout.bin_data_start = encoding.draw_tags.iter().map(|tag| tag.info_size()).sum(); + data.extend_from_slice(bytemuck::cast_slice(&encoding.draw_tags)); + for _ in 0..encoding.n_open_clips { + data.extend_from_slice(bytemuck::bytes_of(&DrawTag::END_CLIP)); + } + // Draw data stream + layout.draw_data_base = size_to_words(data.len()); + data.extend_from_slice(bytemuck::cast_slice(&encoding.draw_data)); + // Transform stream + layout.transform_base = size_to_words(data.len()); + data.extend_from_slice(bytemuck::cast_slice(&encoding.transforms)); + // Linewidth stream + layout.linewidth_base = size_to_words(data.len()); + data.extend_from_slice(bytemuck::cast_slice(&encoding.linewidths)); + layout.n_draw_objects = layout.n_paths; + assert_eq!(capacity, data.len()); + layout +} + /// Resolver for late bound resources. #[derive(Default)] pub struct Resolver { From 46328c7a2c7ec449395b673d21cba7b27d5a80d6 Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Tue, 2 May 2023 16:42:50 -0400 Subject: [PATCH 03/17] cleanup for review * rename resolve_simple to resolve_solid_paths_only to better capture the semantics of the function * move duplicated buffer size computation code to separate function * change Resolver::resolve to call resolve_solid_paths_only when encoding.patches.is_empty() is true. This is likely to be slightly faster and will ensure that the "simple" code path is actually used. --- crates/encoding/src/image_cache.rs | 1 + crates/encoding/src/lib.rs | 2 +- crates/encoding/src/resolve.rs | 88 ++++++++++++++++++------------ 3 files changed, 56 insertions(+), 35 deletions(-) diff --git a/crates/encoding/src/image_cache.rs b/crates/encoding/src/image_cache.rs index 85b658e..78f5cf7 100644 --- a/crates/encoding/src/image_cache.rs +++ b/crates/encoding/src/image_cache.rs @@ -8,6 +8,7 @@ use std::collections::{hash_map::Entry, HashMap}; const DEFAULT_ATLAS_SIZE: i32 = 1024; const MAX_ATLAS_SIZE: i32 = 8192; +#[derive(Default)] pub struct Images<'a> { pub width: u32, pub height: u32, diff --git a/crates/encoding/src/lib.rs b/crates/encoding/src/lib.rs index 529fdc6..748354c 100644 --- a/crates/encoding/src/lib.rs +++ b/crates/encoding/src/lib.rs @@ -35,4 +35,4 @@ pub use path::{ Cubic, Path, PathBbox, PathEncoder, PathMonoid, PathSegment, PathSegmentType, PathTag, Tile, }; pub use ramp_cache::Ramps; -pub use resolve::{resolve_simple, Layout, Patch, Resolver}; +pub use resolve::{resolve_solid_paths_only, Layout, Patch, Resolver}; diff --git a/crates/encoding/src/resolve.rs b/crates/encoding/src/resolve.rs index fa1a641..1a5ba9e 100644 --- a/crates/encoding/src/resolve.rs +++ b/crates/encoding/src/resolve.rs @@ -100,14 +100,16 @@ impl Layout { } } -/// Resolves and packs an encoding that doesn't contain late bound resources -/// (gradients, images and glyph runs). -pub fn resolve_simple(encoding: &Encoding, packed: &mut Vec) -> Layout { +/// Resolves and packs an encoding that contains only paths with solid color +/// fills. +/// +/// Panics if the encoding contains any late bound resources (gradients, images +/// or glyph runs). +pub fn resolve_solid_paths_only(encoding: &Encoding, packed: &mut Vec) -> Layout { assert!( encoding.patches.is_empty(), "this resolve function doesn't support late bound resources" ); - let sizes = StreamOffsets::default(); let data = packed; data.clear(); let mut layout = Layout { @@ -115,25 +117,18 @@ pub fn resolve_simple(encoding: &Encoding, packed: &mut Vec) -> Layout { n_clips: encoding.n_clips, ..Layout::default() }; - // Compute size of data buffer - let n_path_tags = encoding.path_tags.len() + sizes.path_tags + encoding.n_open_clips as usize; - let path_tag_padded = align_up(n_path_tags, 4 * crate::config::PATH_REDUCE_WG); - let capacity = path_tag_padded - + slice_size_in_bytes(&encoding.path_data, sizes.path_data) - + slice_size_in_bytes( - &encoding.draw_tags, - sizes.draw_tags + encoding.n_open_clips as usize, - ) - + slice_size_in_bytes(&encoding.draw_data, sizes.draw_data) - + slice_size_in_bytes(&encoding.transforms, sizes.transforms) - + slice_size_in_bytes(&encoding.linewidths, sizes.linewidths); - data.reserve(capacity); + let SceneBufferSizes { + buffer_size, + path_tag_padded, + } = SceneBufferSizes::new(encoding, &StreamOffsets::default()); + data.reserve(buffer_size); // Path tag stream layout.path_tag_base = size_to_words(data.len()); data.extend_from_slice(bytemuck::cast_slice(&encoding.path_tags)); for _ in 0..encoding.n_open_clips { data.extend_from_slice(bytemuck::bytes_of(&PathTag::PATH)); } + data.resize(path_tag_padded, 0); // Path data stream layout.path_data_base = size_to_words(data.len()); data.extend_from_slice(bytemuck::cast_slice(&encoding.path_data)); @@ -155,7 +150,7 @@ pub fn resolve_simple(encoding: &Encoding, packed: &mut Vec) -> Layout { layout.linewidth_base = size_to_words(data.len()); data.extend_from_slice(bytemuck::cast_slice(&encoding.linewidths)); layout.n_draw_objects = layout.n_paths; - assert_eq!(capacity, data.len()); + assert_eq!(buffer_size, data.len()); layout } @@ -184,7 +179,11 @@ impl Resolver { encoding: &Encoding, packed: &mut Vec, ) -> (Layout, Ramps<'a>, Images<'a>) { - let sizes = self.resolve_patches(encoding); + if encoding.patches.is_empty() { + let layout = resolve_solid_paths_only(encoding, packed); + return (layout, Ramps::default(), Images::default()); + } + let patch_sizes = self.resolve_patches(encoding); self.resolve_pending_images(); let data = packed; data.clear(); @@ -193,20 +192,11 @@ impl Resolver { n_clips: encoding.n_clips, ..Layout::default() }; - // Compute size of data buffer - let n_path_tags = - encoding.path_tags.len() + sizes.path_tags + encoding.n_open_clips as usize; - let path_tag_padded = align_up(n_path_tags, 4 * crate::config::PATH_REDUCE_WG); - let capacity = path_tag_padded - + slice_size_in_bytes(&encoding.path_data, sizes.path_data) - + slice_size_in_bytes( - &encoding.draw_tags, - sizes.draw_tags + encoding.n_open_clips as usize, - ) - + slice_size_in_bytes(&encoding.draw_data, sizes.draw_data) - + slice_size_in_bytes(&encoding.transforms, sizes.transforms) - + slice_size_in_bytes(&encoding.linewidths, sizes.linewidths); - data.reserve(capacity); + let SceneBufferSizes { + buffer_size, + path_tag_padded, + } = SceneBufferSizes::new(encoding, &patch_sizes); + data.reserve(buffer_size); // Path tag stream layout.path_tag_base = size_to_words(data.len()); { @@ -382,7 +372,7 @@ impl Resolver { } } layout.n_draw_objects = layout.n_paths; - assert_eq!(capacity, data.len()); + assert_eq!(buffer_size, data.len()); (layout, self.ramp_cache.ramps(), self.image_cache.images()) } @@ -577,6 +567,36 @@ enum ResolvedPatch { }, } +struct SceneBufferSizes { + /// Full size of the scene buffer in bytes. + buffer_size: usize, + /// Padded length of the path tag stream in bytes. + path_tag_padded: usize, +} + +impl SceneBufferSizes { + /// Computes common scene buffer sizes for the given encoding and patch + /// stream sizes. + fn new(encoding: &Encoding, patch_sizes: &StreamOffsets) -> Self { + let n_path_tags = + encoding.path_tags.len() + patch_sizes.path_tags + encoding.n_open_clips as usize; + let path_tag_padded = align_up(n_path_tags, 4 * crate::config::PATH_REDUCE_WG); + let buffer_size = path_tag_padded + + slice_size_in_bytes(&encoding.path_data, patch_sizes.path_data) + + slice_size_in_bytes( + &encoding.draw_tags, + patch_sizes.draw_tags + encoding.n_open_clips as usize, + ) + + slice_size_in_bytes(&encoding.draw_data, patch_sizes.draw_data) + + slice_size_in_bytes(&encoding.transforms, patch_sizes.transforms) + + slice_size_in_bytes(&encoding.linewidths, patch_sizes.linewidths); + Self { + buffer_size, + path_tag_padded, + } + } +} + fn slice_size_in_bytes(slice: &[T], extra: usize) -> usize { (slice.len() + extra) * std::mem::size_of::() } From c68d011c7c27cb00ff3146ba11e37b120cf03c44 Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Wed, 3 May 2023 12:22:51 -0400 Subject: [PATCH 04/17] feature gate full pipeline encoding support Adds a new feature called "full" (on by default) that enables encoding support for the full pipeline. --- crates/encoding/Cargo.toml | 10 +- crates/encoding/src/config.rs | 1 + crates/encoding/src/encoding.rs | 189 ++++++++++++++++++++------------ crates/encoding/src/lib.rs | 16 ++- crates/encoding/src/math.rs | 1 + crates/encoding/src/path.rs | 1 + crates/encoding/src/resolve.rs | 59 ++++++---- src/scene.rs | 25 +++-- 8 files changed, 192 insertions(+), 110 deletions(-) diff --git a/crates/encoding/Cargo.toml b/crates/encoding/Cargo.toml index f11c4bc..538dc36 100644 --- a/crates/encoding/Cargo.toml +++ b/crates/encoding/Cargo.toml @@ -3,8 +3,14 @@ name = "vello_encoding" version = "0.1.0" edition = "2021" +[features] +default = ["full"] +# Enables support for the full pipeline including late-bound +# resources (gradients, images and glyph runs) +full = ["fello", "guillotiere"] + [dependencies] bytemuck = { workspace = true } -fello = { workspace = true } +fello = { workspace = true, optional = true } peniko = { workspace = true } -guillotiere = "0.6.2" +guillotiere = { version = "0.6.2", optional = true } diff --git a/crates/encoding/src/config.rs b/crates/encoding/src/config.rs index ddd7e2d..2d3dba4 100644 --- a/crates/encoding/src/config.rs +++ b/crates/encoding/src/config.rs @@ -196,6 +196,7 @@ impl BufferSize { } /// Returns the number of elements. + #[allow(clippy::len_without_is_empty)] pub const fn len(self) -> u32 { self.len } diff --git a/crates/encoding/src/encoding.rs b/crates/encoding/src/encoding.rs index 65dcbbb..9834e64 100644 --- a/crates/encoding/src/encoding.rs +++ b/crates/encoding/src/encoding.rs @@ -1,13 +1,16 @@ // Copyright 2022 The Vello authors // SPDX-License-Identifier: Apache-2.0 OR MIT -use super::{ - resolve::Patch, DrawColor, DrawImage, DrawLinearGradient, DrawRadialGradient, DrawTag, Glyph, - GlyphRun, PathEncoder, PathTag, Transform, -}; +use super::{DrawColor, DrawTag, PathEncoder, PathTag, Transform}; -use fello::NormalizedCoord; -use peniko::{kurbo::Shape, BlendMode, BrushRef, ColorStop, Extend, GradientKind, Image}; +use peniko::{kurbo::Shape, BlendMode, BrushRef}; + +#[cfg(feature = "full")] +use { + super::{DrawImage, DrawLinearGradient, DrawRadialGradient, Glyph, GlyphRun, Patch}, + fello::NormalizedCoord, + peniko::{ColorStop, Extend, GradientKind, Image}, +}; /// Encoded data streams for a scene. #[derive(Clone, Default)] @@ -20,20 +23,13 @@ pub struct Encoding { pub draw_tags: Vec, /// The draw data stream. pub draw_data: Vec, - /// Draw data patches for late bound resources. - pub patches: Vec, - /// Color stop collection for gradients. - pub color_stops: Vec, /// The transform stream. pub transforms: Vec, /// The line width stream. pub linewidths: Vec, - /// Positioned glyph buffer. - pub glyphs: Vec, - /// Sequences of glyphs. - pub glyph_runs: Vec, - /// Normalized coordinate buffer for variable fonts. - pub normalized_coords: Vec, + /// Late bound resource data. + #[cfg(feature = "full")] + pub resources: Resources, /// Number of encoded paths. pub n_paths: u32, /// Number of encoded path segments. @@ -63,15 +59,12 @@ impl Encoding { self.linewidths.clear(); self.draw_data.clear(); self.draw_tags.clear(); - self.glyphs.clear(); - self.glyph_runs.clear(); - self.normalized_coords.clear(); self.n_paths = 0; self.n_path_segments = 0; self.n_clips = 0; self.n_open_clips = 0; - self.patches.clear(); - self.color_stops.clear(); + #[cfg(feature = "full")] + self.resources.reset(); if !is_fragment { self.transforms.push(Transform::IDENTITY); self.linewidths.push(-1.0); @@ -80,62 +73,74 @@ impl Encoding { /// Appends another encoding to this one with an optional transform. pub fn append(&mut self, other: &Self, transform: &Option) { - let stops_base = self.color_stops.len(); - let glyph_runs_base = self.glyph_runs.len(); - let glyphs_base = self.glyphs.len(); - let coords_base = self.normalized_coords.len(); - let offsets = self.stream_offsets(); + #[cfg(feature = "full")] + let glyph_runs_base = { + let offsets = self.stream_offsets(); + let stops_base = self.resources.color_stops.len(); + let glyph_runs_base = self.resources.glyph_runs.len(); + let glyphs_base = self.resources.glyphs.len(); + let coords_base = self.resources.normalized_coords.len(); + self.resources + .glyphs + .extend_from_slice(&other.resources.glyphs); + self.resources + .normalized_coords + .extend_from_slice(&other.resources.normalized_coords); + self.resources + .glyph_runs + .extend(other.resources.glyph_runs.iter().cloned().map(|mut run| { + run.glyphs.start += glyphs_base; + run.normalized_coords.start += coords_base; + run.stream_offsets.path_tags += offsets.path_tags; + run.stream_offsets.path_data += offsets.path_data; + run.stream_offsets.draw_tags += offsets.draw_tags; + run.stream_offsets.draw_data += offsets.draw_data; + run.stream_offsets.transforms += offsets.transforms; + run.stream_offsets.linewidths += offsets.linewidths; + run + })); + self.resources + .patches + .extend(other.resources.patches.iter().map(|patch| match patch { + Patch::Ramp { + draw_data_offset: offset, + stops, + } => { + let stops = stops.start + stops_base..stops.end + stops_base; + Patch::Ramp { + draw_data_offset: offset + offsets.draw_data, + stops, + } + } + Patch::GlyphRun { index } => Patch::GlyphRun { + index: index + glyph_runs_base, + }, + Patch::Image { + image, + draw_data_offset, + } => Patch::Image { + image: image.clone(), + draw_data_offset: *draw_data_offset + offsets.draw_data, + }, + })); + self.resources + .color_stops + .extend_from_slice(&other.resources.color_stops); + glyph_runs_base + }; self.path_tags.extend_from_slice(&other.path_tags); self.path_data.extend_from_slice(&other.path_data); self.draw_tags.extend_from_slice(&other.draw_tags); self.draw_data.extend_from_slice(&other.draw_data); - self.glyphs.extend_from_slice(&other.glyphs); - self.normalized_coords - .extend_from_slice(&other.normalized_coords); - self.glyph_runs - .extend(other.glyph_runs.iter().cloned().map(|mut run| { - run.glyphs.start += glyphs_base; - run.normalized_coords.start += coords_base; - run.stream_offsets.path_tags += offsets.path_tags; - run.stream_offsets.path_data += offsets.path_data; - run.stream_offsets.draw_tags += offsets.draw_tags; - run.stream_offsets.draw_data += offsets.draw_data; - run.stream_offsets.transforms += offsets.transforms; - run.stream_offsets.linewidths += offsets.linewidths; - run - })); self.n_paths += other.n_paths; self.n_path_segments += other.n_path_segments; self.n_clips += other.n_clips; self.n_open_clips += other.n_open_clips; - self.patches - .extend(other.patches.iter().map(|patch| match patch { - Patch::Ramp { - draw_data_offset: offset, - stops, - } => { - let stops = stops.start + stops_base..stops.end + stops_base; - Patch::Ramp { - draw_data_offset: offset + offsets.draw_data, - stops, - } - } - Patch::GlyphRun { index } => Patch::GlyphRun { - index: index + glyph_runs_base, - }, - Patch::Image { - image, - draw_data_offset, - } => Patch::Image { - image: image.clone(), - draw_data_offset: *draw_data_offset + offsets.draw_data, - }, - })); - self.color_stops.extend_from_slice(&other.color_stops); if let Some(transform) = *transform { self.transforms .extend(other.transforms.iter().map(|x| transform * *x)); - for run in &mut self.glyph_runs[glyph_runs_base..] { + #[cfg(feature = "full")] + for run in &mut self.resources.glyph_runs[glyph_runs_base..] { run.transform = transform * run.transform; } } else { @@ -199,7 +204,9 @@ impl Encoding { } /// Encodes a brush with an optional alpha modifier. + #[allow(unused_variables)] pub fn encode_brush<'b>(&mut self, brush: impl Into>, alpha: f32) { + #[cfg(feature = "full")] use super::math::point_to_f32; match brush.into() { BrushRef::Solid(color) => { @@ -210,6 +217,7 @@ impl Encoding { }; self.encode_color(DrawColor::new(color)); } + #[cfg(feature = "full")] BrushRef::Gradient(gradient) => match gradient.kind { GradientKind::Linear { start, end } => { self.encode_linear_gradient( @@ -246,9 +254,13 @@ impl Encoding { todo!("sweep gradients aren't supported yet!") } }, + #[cfg(feature = "full")] BrushRef::Image(image) => { + #[cfg(feature = "full")] self.encode_image(image, alpha); } + #[cfg(not(feature = "full"))] + _ => panic!("brushes other than solid require the 'full' feature to be enabled"), } } @@ -259,6 +271,7 @@ impl Encoding { } /// Encodes a linear gradient brush. + #[cfg(feature = "full")] pub fn encode_linear_gradient( &mut self, gradient: DrawLinearGradient, @@ -273,6 +286,7 @@ impl Encoding { } /// Encodes a radial gradient brush. + #[cfg(feature = "full")] pub fn encode_radial_gradient( &mut self, gradient: DrawRadialGradient, @@ -287,10 +301,11 @@ impl Encoding { } /// Encodes an image brush. + #[cfg(feature = "full")] pub fn encode_image(&mut self, image: &Image, _alpha: f32) { // TODO: feed the alpha multiplier through the full pipeline for consistency // with other brushes? - self.patches.push(Patch::Image { + self.resources.patches.push(Patch::Image { image: image.clone(), draw_data_offset: self.draw_data.len(), }); @@ -331,22 +346,51 @@ impl Encoding { self.path_tags.swap(len - 1, len - 2); } + #[cfg(feature = "full")] fn add_ramp(&mut self, color_stops: impl Iterator, alpha: f32) { let offset = self.draw_data.len(); - let stops_start = self.color_stops.len(); + let stops_start = self.resources.color_stops.len(); if alpha != 1.0 { - self.color_stops + self.resources + .color_stops .extend(color_stops.map(|stop| stop.with_alpha_factor(alpha))); } else { - self.color_stops.extend(color_stops); + self.resources.color_stops.extend(color_stops); } - self.patches.push(Patch::Ramp { + self.resources.patches.push(Patch::Ramp { draw_data_offset: offset, - stops: stops_start..self.color_stops.len(), + stops: stops_start..self.resources.color_stops.len(), }); } } +/// Encoded data for late bound resources. +#[cfg(feature = "full")] +#[derive(Clone, Default)] +pub struct Resources { + /// Draw data patches for late bound resources. + pub patches: Vec, + /// Color stop collection for gradients. + pub color_stops: Vec, + /// Positioned glyph buffer. + pub glyphs: Vec, + /// Sequences of glyphs. + pub glyph_runs: Vec, + /// Normalized coordinate buffer for variable fonts. + pub normalized_coords: Vec, +} + +#[cfg(feature = "full")] +impl Resources { + fn reset(&mut self) { + self.patches.clear(); + self.color_stops.clear(); + self.glyphs.clear(); + self.glyph_runs.clear(); + self.normalized_coords.clear(); + } +} + /// Snapshot of offsets for encoded streams. #[derive(Copy, Clone, Default, Debug)] pub struct StreamOffsets { @@ -365,6 +409,7 @@ pub struct StreamOffsets { } impl StreamOffsets { + #[cfg(feature = "full")] pub(crate) fn add(&mut self, other: &Self) { self.path_tags += other.path_tags; self.path_data += other.path_data; diff --git a/crates/encoding/src/lib.rs b/crates/encoding/src/lib.rs index 748354c..62a7d6c 100644 --- a/crates/encoding/src/lib.rs +++ b/crates/encoding/src/lib.rs @@ -8,12 +8,16 @@ mod clip; mod config; mod draw; mod encoding; +#[cfg(feature = "full")] mod glyph; +#[cfg(feature = "full")] mod glyph_cache; +#[cfg(feature = "full")] mod image_cache; mod math; mod monoid; mod path; +#[cfg(feature = "full")] mod ramp_cache; mod resolve; @@ -28,11 +32,17 @@ pub use draw::{ DrawRadialGradient, DrawTag, }; pub use encoding::{Encoding, StreamOffsets}; -pub use glyph::{Glyph, GlyphRun}; pub use math::Transform; pub use monoid::Monoid; pub use path::{ Cubic, Path, PathBbox, PathEncoder, PathMonoid, PathSegment, PathSegmentType, PathTag, Tile, }; -pub use ramp_cache::Ramps; -pub use resolve::{resolve_solid_paths_only, Layout, Patch, Resolver}; +pub use resolve::{resolve_solid_paths_only, Layout}; + +#[cfg(feature = "full")] +pub use { + encoding::Resources, + glyph::{Glyph, GlyphRun}, + ramp_cache::Ramps, + resolve::{Patch, Resolver}, +}; diff --git a/crates/encoding/src/math.rs b/crates/encoding/src/math.rs index 9ab9935..5873fba 100644 --- a/crates/encoding/src/math.rs +++ b/crates/encoding/src/math.rs @@ -72,6 +72,7 @@ impl Mul for Transform { } } +#[allow(dead_code)] pub fn point_to_f32(point: kurbo::Point) -> [f32; 2] { [point.x as f32, point.y as f32] } diff --git a/crates/encoding/src/path.rs b/crates/encoding/src/path.rs index 6c3cc5f..10e7499 100644 --- a/crates/encoding/src/path.rs +++ b/crates/encoding/src/path.rs @@ -403,6 +403,7 @@ impl<'a> PathEncoder<'a> { } } +#[cfg(feature = "full")] impl fello::scale::Pen for PathEncoder<'_> { fn move_to(&mut self, x: f32, y: f32) { self.move_to(x, y) diff --git a/crates/encoding/src/resolve.rs b/crates/encoding/src/resolve.rs index 1a5ba9e..ee95628 100644 --- a/crates/encoding/src/resolve.rs +++ b/crates/encoding/src/resolve.rs @@ -1,16 +1,19 @@ // Copyright 2022 The Vello authors // SPDX-License-Identifier: Apache-2.0 OR MIT -use std::ops::Range; - use bytemuck::{Pod, Zeroable}; -use peniko::Image; -use super::{ - glyph_cache::{CachedRange, GlyphCache, GlyphKey}, - image_cache::{ImageCache, Images}, - ramp_cache::{RampCache, Ramps}, - DrawTag, Encoding, PathTag, StreamOffsets, Transform, +use super::{DrawTag, Encoding, PathTag, StreamOffsets, Transform}; + +#[cfg(feature = "full")] +use { + super::{ + glyph_cache::{CachedRange, GlyphCache, GlyphKey}, + image_cache::{ImageCache, Images}, + ramp_cache::{RampCache, Ramps}, + }, + peniko::Image, + std::ops::Range, }; /// Layout of a packed encoding. @@ -106,8 +109,9 @@ impl Layout { /// Panics if the encoding contains any late bound resources (gradients, images /// or glyph runs). pub fn resolve_solid_paths_only(encoding: &Encoding, packed: &mut Vec) -> Layout { + #[cfg(feature = "full")] assert!( - encoding.patches.is_empty(), + encoding.resources.patches.is_empty(), "this resolve function doesn't support late bound resources" ); let data = packed; @@ -155,6 +159,7 @@ pub fn resolve_solid_paths_only(encoding: &Encoding, packed: &mut Vec) -> La } /// Resolver for late bound resources. +#[cfg(feature = "full")] #[derive(Default)] pub struct Resolver { glyph_cache: GlyphCache, @@ -166,6 +171,7 @@ pub struct Resolver { patches: Vec, } +#[cfg(feature = "full")] impl Resolver { /// Creates a new resource cache. pub fn new() -> Self { @@ -179,7 +185,8 @@ impl Resolver { encoding: &Encoding, packed: &mut Vec, ) -> (Layout, Ramps<'a>, Images<'a>) { - if encoding.patches.is_empty() { + let resources = &encoding.resources; + if resources.patches.is_empty() { let layout = resolve_solid_paths_only(encoding, packed); return (layout, Ramps::default(), Images::default()); } @@ -205,7 +212,7 @@ impl Resolver { for patch in &self.patches { if let ResolvedPatch::GlyphRun { index, glyphs, .. } = patch { layout.n_paths += 1; - let stream_offset = encoding.glyph_runs[*index].stream_offsets.path_tags; + let stream_offset = resources.glyph_runs[*index].stream_offsets.path_tags; if pos < stream_offset { data.extend_from_slice(bytemuck::cast_slice(&stream[pos..stream_offset])); pos = stream_offset; @@ -234,7 +241,9 @@ impl Resolver { let stream = &encoding.path_data; for patch in &self.patches { if let ResolvedPatch::GlyphRun { index, glyphs, .. } = patch { - let stream_offset = encoding.glyph_runs[*index].stream_offsets.path_data; + let stream_offset = encoding.resources.glyph_runs[*index] + .stream_offsets + .path_data; if pos < stream_offset { data.extend_from_slice(bytemuck::cast_slice(&stream[pos..stream_offset])); pos = stream_offset; @@ -316,14 +325,14 @@ impl Resolver { transform, } = patch { - let run = &encoding.glyph_runs[*index]; - let stream_offset = encoding.glyph_runs[*index].stream_offsets.transforms; + let run = &resources.glyph_runs[*index]; + let stream_offset = run.stream_offsets.transforms; if pos < stream_offset { data.extend_from_slice(bytemuck::cast_slice(&stream[pos..stream_offset])); pos = stream_offset; } if let Some(glyph_transform) = run.glyph_transform { - for glyph in &encoding.glyphs[run.glyphs.clone()] { + for glyph in &resources.glyphs[run.glyphs.clone()] { let xform = *transform * Transform { matrix: [1.0, 0.0, 0.0, -1.0], @@ -333,7 +342,7 @@ impl Resolver { data.extend_from_slice(bytemuck::bytes_of(&xform)); } } else { - for glyph in &encoding.glyphs[run.glyphs.clone()] { + for glyph in &resources.glyphs[run.glyphs.clone()] { let xform = *transform * Transform { matrix: [1.0, 0.0, 0.0, -1.0], @@ -355,7 +364,7 @@ impl Resolver { let stream = &encoding.linewidths; for patch in &self.patches { if let ResolvedPatch::GlyphRun { index, glyphs, .. } = patch { - let stream_offset = encoding.glyph_runs[*index].stream_offsets.linewidths; + let stream_offset = resources.glyph_runs[*index].stream_offsets.linewidths; if pos < stream_offset { data.extend_from_slice(bytemuck::cast_slice(&stream[pos..stream_offset])); pos = stream_offset; @@ -384,13 +393,14 @@ impl Resolver { self.pending_images.clear(); self.patches.clear(); let mut sizes = StreamOffsets::default(); - for patch in &encoding.patches { + let resources = &encoding.resources; + for patch in &resources.patches { match patch { Patch::Ramp { draw_data_offset, stops, } => { - let ramp_id = self.ramp_cache.add(&encoding.color_stops[stops.clone()]); + let ramp_id = self.ramp_cache.add(&resources.color_stops[stops.clone()]); self.patches.push(ResolvedPatch::Ramp { draw_data_offset: *draw_data_offset + sizes.draw_data, ramp_id, @@ -398,7 +408,7 @@ impl Resolver { } Patch::GlyphRun { index } => { let mut run_sizes = StreamOffsets::default(); - let run = &encoding.glyph_runs[*index]; + let run = &resources.glyph_runs[*index]; let font_id = run.font.data.id(); let font_size_u32 = run.font_size.to_bits(); let Ok(font_file) = fello::raw::FileRef::new(run.font.data.as_ref()) else { continue }; @@ -409,8 +419,8 @@ impl Resolver { } }; let Some(font) = font else { continue }; - let glyphs = &encoding.glyphs[run.glyphs.clone()]; - let coords = &encoding.normalized_coords[run.normalized_coords.clone()]; + let glyphs = &resources.glyphs[run.glyphs.clone()]; + let coords = &resources.normalized_coords[run.normalized_coords.clone()]; let key = fello::FontKey { data_id: font_id, index: run.font.index, @@ -512,8 +522,9 @@ impl Resolver { } } -#[derive(Clone)] /// Patch for a late bound resource. +#[cfg(feature = "full")] +#[derive(Clone)] pub enum Patch { /// Gradient ramp resource. Ramp { @@ -537,12 +548,14 @@ pub enum Patch { } /// Image to be allocated in the atlas. +#[cfg(feature = "full")] #[derive(Clone, Debug)] struct PendingImage { image: Image, xy: Option<(u32, u32)>, } +#[cfg(feature = "full")] #[derive(Clone, Debug)] enum ResolvedPatch { Ramp { diff --git a/src/scene.rs b/src/scene.rs index 647bf89..02e4006 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -205,8 +205,8 @@ impl<'a> DrawGlyphs<'a> { /// Creates a new builder for encoding a glyph run for the specified /// encoding with the given font. pub fn new(encoding: &'a mut Encoding, font: &Font) -> Self { - let coords_start = encoding.normalized_coords.len(); - let glyphs_start = encoding.glyphs.len(); + let coords_start = encoding.resources.normalized_coords.len(); + let glyphs_start = encoding.resources.glyphs.len(); let stream_offsets = encoding.stream_offsets(); Self { encoding, @@ -264,10 +264,14 @@ impl<'a> DrawGlyphs<'a> { /// Sets the normalized design space coordinates for a variable font instance. pub fn normalized_coords(mut self, coords: &[NormalizedCoord]) -> Self { self.encoding + .resources .normalized_coords .truncate(self.run.normalized_coords.start); - self.encoding.normalized_coords.extend_from_slice(coords); - self.run.normalized_coords.end = self.encoding.normalized_coords.len(); + self.encoding + .resources + .normalized_coords + .extend_from_slice(coords); + self.run.normalized_coords.end = self.encoding.resources.normalized_coords.len(); self } @@ -292,18 +296,19 @@ impl<'a> DrawGlyphs<'a> { /// /// The `style` parameter accepts either `Fill` or `&Stroke` types. pub fn draw(mut self, style: impl Into>, glyphs: impl Iterator) { + let resources = &mut self.encoding.resources; self.run.style = style.into().to_owned(); - self.encoding.glyphs.extend(glyphs); - self.run.glyphs.end = self.encoding.glyphs.len(); + resources.glyphs.extend(glyphs); + self.run.glyphs.end = resources.glyphs.len(); if self.run.glyphs.is_empty() { - self.encoding + resources .normalized_coords .truncate(self.run.normalized_coords.start); return; } - let index = self.encoding.glyph_runs.len(); - self.encoding.glyph_runs.push(self.run); - self.encoding.patches.push(Patch::GlyphRun { index }); + let index = resources.glyph_runs.len(); + resources.glyph_runs.push(self.run); + resources.patches.push(Patch::GlyphRun { index }); self.encoding.encode_brush(self.brush, self.brush_alpha); } } From e9c2ce162358cfdf5b7b0d47f2592bdea1e72a92 Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Wed, 3 May 2023 14:53:36 -0400 Subject: [PATCH 05/17] resolve merge conflicts --- crates/encoding/src/encoding.rs | 10 +++++++++- crates/encoding/src/resolve.rs | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/encoding/src/encoding.rs b/crates/encoding/src/encoding.rs index 5232615..b94ac69 100644 --- a/crates/encoding/src/encoding.rs +++ b/crates/encoding/src/encoding.rs @@ -105,11 +105,13 @@ impl Encoding { Patch::Ramp { draw_data_offset: offset, stops, + extend, } => { let stops = stops.start + stops_base..stops.end + stops_base; Patch::Ramp { draw_data_offset: offset + offsets.draw_data, stops, + extend: *extend, } } Patch::GlyphRun { index } => Patch::GlyphRun { @@ -347,7 +349,12 @@ impl Encoding { } #[cfg(feature = "full")] - fn add_ramp(&mut self, color_stops: impl Iterator, alpha: f32) { + fn add_ramp( + &mut self, + color_stops: impl Iterator, + alpha: f32, + extend: Extend, + ) { let offset = self.draw_data.len(); let stops_start = self.resources.color_stops.len(); if alpha != 1.0 { @@ -360,6 +367,7 @@ impl Encoding { self.resources.patches.push(Patch::Ramp { draw_data_offset: offset, stops: stops_start..self.resources.color_stops.len(), + extend, }); } } diff --git a/crates/encoding/src/resolve.rs b/crates/encoding/src/resolve.rs index 37357ef..a1d3eed 100644 --- a/crates/encoding/src/resolve.rs +++ b/crates/encoding/src/resolve.rs @@ -12,7 +12,7 @@ use { image_cache::{ImageCache, Images}, ramp_cache::{RampCache, Ramps}, }, - peniko::Image, + peniko::{Extend, Image}, std::ops::Range, }; From 2db555145eb11acad73b6915bf96a26935c74ea7 Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Wed, 3 May 2023 15:29:45 -0400 Subject: [PATCH 06/17] clean up test scene code --- examples/scenes/src/test_scenes.rs | 54 ++++++++++-------------------- 1 file changed, 17 insertions(+), 37 deletions(-) diff --git a/examples/scenes/src/test_scenes.rs b/examples/scenes/src/test_scenes.rs index e5aff38..4da8959 100644 --- a/examples/scenes/src/test_scenes.rs +++ b/examples/scenes/src/test_scenes.rs @@ -251,16 +251,10 @@ fn brush_transform(sb: &mut SceneBuilder, params: &mut SceneParams) { } fn gradient_extend(sb: &mut SceneBuilder, params: &mut SceneParams) { - fn square( - sb: &mut SceneBuilder, - is_radial: bool, - transform: Affine, - brush_transform: Option, - extend: Extend, - ) { + fn square(sb: &mut SceneBuilder, is_radial: bool, transform: Affine, extend: Extend) { let colors = [Color::RED, Color::GREEN, Color::BLUE]; - let width = 400f64; - let height = 400f64; + let width = 300f64; + let height = 300f64; let gradient: Brush = if is_radial { Gradient::new_radial((width * 0.5, height * 0.5), (width * 0.25) as f32) .with_stops(colors) @@ -276,7 +270,7 @@ fn gradient_extend(sb: &mut SceneBuilder, params: &mut SceneParams) { Fill::NonZero, transform, &gradient, - brush_transform, + None, &Rect::new(0.0, 0.0, width, height), ); } @@ -285,35 +279,21 @@ fn gradient_extend(sb: &mut SceneBuilder, params: &mut SceneParams) { let extend = extend_modes[x]; for y in 0..2 { let is_radial = y & 1 != 0; - let transform = Affine::translate((x as f64 * 450.0 + 50.0, y as f64 * 450.0 + 100.0)); - square(sb, is_radial, transform, None, extend); + let transform = Affine::translate((x as f64 * 350.0 + 50.0, y as f64 * 350.0 + 100.0)); + square(sb, is_radial, transform, extend); } } - let white = Color::WHITE.into(); - params.text.add( - sb, - None, - 32.0, - Some(&white), - Affine::translate((50.0, 70.0)), - "Pad", - ); - params.text.add( - sb, - None, - 32.0, - Some(&white), - Affine::translate((500.0, 70.0)), - "Repeat", - ); - params.text.add( - sb, - None, - 32.0, - Some(&white), - Affine::translate((950.0, 70.0)), - "Reflect", - ); + for (i, label) in ["Pad", "Repeat", "Reflect"].iter().enumerate() { + let x = i as f64 * 350.0 + 50.0; + params.text.add( + sb, + None, + 32.0, + Some(&Color::WHITE.into()), + Affine::translate((x, 70.0)), + label, + ); + } } fn blend_grid(sb: &mut SceneBuilder, _: &mut SceneParams) { From ced6309a3b40798f8ed69c2aa23d2debff8a40ff Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Sat, 6 May 2023 03:27:53 -0400 Subject: [PATCH 07/17] support two point radial with r0 > 0.0 --- examples/scenes/src/test_scenes.rs | 13 +++++++++++++ shader/draw_leaf.wgsl | 14 +++++++++----- shader/fine.wgsl | 11 +++++------ shader/shared/ptcl.wgsl | 2 +- 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/examples/scenes/src/test_scenes.rs b/examples/scenes/src/test_scenes.rs index 4da8959..9c95b3c 100644 --- a/examples/scenes/src/test_scenes.rs +++ b/examples/scenes/src/test_scenes.rs @@ -294,6 +294,19 @@ fn gradient_extend(sb: &mut SceneBuilder, params: &mut SceneParams) { label, ); } + let t = (params.time * 0.5).sin() * 0.5 + 0.5; + let x_delta: f64 = t * 2000.0 - 1000.0; + let gradient = + Gradient::new_two_point_radial((400.0, 500.0), 100.0, (101.0 + x_delta, 500.0), 200.0) + .with_extend(Extend::Reflect) + .with_stops([Color::GREEN, Color::WHITE, Color::RED]); + sb.fill( + Fill::NonZero, + Affine::translate((400.0, 800.0)) * Affine::scale(0.2), + &gradient, + None, + &Rect::new(0.0, 0.0, 2000.0, 2000.0), + ); } fn blend_grid(sb: &mut SceneBuilder, _: &mut SceneParams) { diff --git a/shader/draw_leaf.wgsl b/shader/draw_leaf.wgsl index b149d27..beced9c 100644 --- a/shader/draw_leaf.wgsl +++ b/shader/draw_leaf.wgsl @@ -153,13 +153,17 @@ fn main( let r1 = bitcast(scene[dd + 6u]); let inv_det = 1.0 / (matrx.x * matrx.w - matrx.y * matrx.z); let inv_mat = inv_det * vec4(matrx.w, -matrx.y, -matrx.z, matrx.x); + let rr = r1 / (r1 - r0); + var rr1 = rr; + if r0 > 0.0 { + p0 = p0 + (p0 - p1) * rr * 0.5; + rr1 = 1.0; + } let inv_tr = mat2x2(inv_mat.xy, inv_mat.zw) * -translate - p0; let center1 = p1 - p0; - let rr = r1 / (r1 - r0); - let ra_inv = rr / (r1 * r1 - dot(center1, center1)); + let ra_inv = rr1 / (r1 * r1 - dot(center1, center1)); let c1 = center1 * ra_inv; - let ra = rr * ra_inv; - let roff = rr - 1.0; + let ra = rr1 * ra_inv; info[di + 1u] = bitcast(inv_mat.x); info[di + 2u] = bitcast(inv_mat.y); info[di + 3u] = bitcast(inv_mat.z); @@ -169,7 +173,7 @@ fn main( info[di + 7u] = bitcast(c1.x); info[di + 8u] = bitcast(c1.y); info[di + 9u] = bitcast(ra); - info[di + 10u] = bitcast(roff); + info[di + 10u] = bitcast(rr); } // DRAWTAG_FILL_IMAGE case 0x248u: { diff --git a/shader/fine.wgsl b/shader/fine.wgsl index 42970e5..85ed7a4 100644 --- a/shader/fine.wgsl +++ b/shader/fine.wgsl @@ -295,18 +295,17 @@ fn main( // CMD_RAD_GRAD case 7u: { let rad = read_rad_grad(cmd_ix); + let rr = rad.rr; + let roff = rr - 1.0; for (var i = 0u; i < PIXELS_PER_THREAD; i += 1u) { let my_xy = vec2(xy.x + f32(i), xy.y); // TODO: can hoist y, but for now stick to the GLSL version let xy_xformed = rad.matrx.xy * my_xy.x + rad.matrx.zw * my_xy.y + rad.xlat; let ba = dot(xy_xformed, rad.c1); let ca = rad.ra * dot(xy_xformed, xy_xformed); - let t0 = sqrt(ba * ba + ca) - ba; - // For radial gradients that generate a cone, reject pixels outside - // the region. - if t0 >= 0.0 { - let t = t0 - rad.roff; - let x = i32(round(extend_mode(t, rad.extend_mode) * f32(GRADIENT_WIDTH - 1))); + let t = sqrt(ba * ba + ca) - ba; + if t >= 0.0 { + let x = i32(round(extend_mode(t * rr - roff, rad.extend_mode) * f32(GRADIENT_WIDTH - 1))); let fg_rgba = textureLoad(gradients, vec2(x, i32(rad.index)), 0); let fg_i = fg_rgba * area[i]; rgba[i] = rgba[i] * (1.0 - fg_i.a) + fg_i; diff --git a/shader/shared/ptcl.wgsl b/shader/shared/ptcl.wgsl index 8137f32..8e80524 100644 --- a/shader/shared/ptcl.wgsl +++ b/shader/shared/ptcl.wgsl @@ -57,7 +57,7 @@ struct CmdRadGrad { xlat: vec2, c1: vec2, ra: f32, - roff: f32, + rr: f32, } struct CmdImage { From b103a5530178de9857b0e0706d5de936f0fa2b97 Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Tue, 9 May 2023 18:09:53 -0400 Subject: [PATCH 08/17] rework radial gradients Adds full support for COLRv1 radial gradients based on the two-point conical gradient algorithm at https://skia.org/docs/dev/design/conical/ Also adds robustness to degenerate cases in gradient encoding: * Radial where p0 == p1 && r0 == r1 renders transparent solid * Empty stops render as transparent solid * Single stop renders as solid --- .vscode/settings.json | 3 +- crates/encoding/src/draw.rs | 2 +- crates/encoding/src/encoding.rs | 63 +++++++++--- examples/scenes/src/test_scenes.rs | 44 ++++++++- shader/coarse.wgsl | 2 +- shader/draw_leaf.wgsl | 153 ++++++++++++++++++++--------- shader/fine.wgsl | 56 ++++++++--- shader/pathseg.wgsl | 11 +-- shader/shared/config.wgsl | 9 ++ shader/shared/drawtag.wgsl | 2 +- shader/shared/ptcl.wgsl | 7 +- shader/shared/transform.wgsl | 26 +++++ src/shaders.rs | 1 + 13 files changed, 284 insertions(+), 95 deletions(-) create mode 100644 shader/shared/transform.wgsl diff --git a/.vscode/settings.json b/.vscode/settings.json index 883465a..c7bfa32 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,7 +10,8 @@ "pathtag": "${workspaceFolder}/shader/shared/pathtag.wgsl", "ptcl": "${workspaceFolder}/shader/shared/ptcl.wgsl", "segment": "${workspaceFolder}/shader/shared/segment.wgsl", - "tile": "${workspaceFolder}/shader/shared/tile.wgsl" + "tile": "${workspaceFolder}/shader/shared/tile.wgsl", + "transform": "${workspaceFolder}/shader/shared/transform.wgsl" }, "wgsl-analyzer.diagnostics.nagaVersion": "main", "wgsl-analyzer.preprocessor.shaderDefs": [ diff --git a/crates/encoding/src/draw.rs b/crates/encoding/src/draw.rs index e26dacd..8e8b1ba 100644 --- a/crates/encoding/src/draw.rs +++ b/crates/encoding/src/draw.rs @@ -22,7 +22,7 @@ impl DrawTag { pub const LINEAR_GRADIENT: Self = Self(0x114); /// Radial gradient fill. - pub const RADIAL_GRADIENT: Self = Self(0x2dc); + pub const RADIAL_GRADIENT: Self = Self(0x29c); /// Image fill. pub const IMAGE: Self = Self(0x248); diff --git a/crates/encoding/src/encoding.rs b/crates/encoding/src/encoding.rs index b94ac69..0b2a13b 100644 --- a/crates/encoding/src/encoding.rs +++ b/crates/encoding/src/encoding.rs @@ -3,7 +3,7 @@ use super::{DrawColor, DrawTag, PathEncoder, PathTag, Transform}; -use peniko::{kurbo::Shape, BlendMode, BrushRef}; +use peniko::{kurbo::Shape, BlendMode, BrushRef, Color}; #[cfg(feature = "full")] use { @@ -281,10 +281,15 @@ impl Encoding { alpha: f32, extend: Extend, ) { - self.add_ramp(color_stops, alpha, extend); - self.draw_tags.push(DrawTag::LINEAR_GRADIENT); - self.draw_data - .extend_from_slice(bytemuck::bytes_of(&gradient)); + match self.add_ramp(color_stops, alpha, extend) { + RampStops::Empty => self.encode_color(DrawColor::new(Color::TRANSPARENT)), + RampStops::One(color) => self.encode_color(DrawColor::new(color)), + _ => { + self.draw_tags.push(DrawTag::LINEAR_GRADIENT); + self.draw_data + .extend_from_slice(bytemuck::bytes_of(&gradient)); + } + } } /// Encodes a radial gradient brush. @@ -296,10 +301,20 @@ impl Encoding { alpha: f32, extend: Extend, ) { - self.add_ramp(color_stops, alpha, extend); - self.draw_tags.push(DrawTag::RADIAL_GRADIENT); - self.draw_data - .extend_from_slice(bytemuck::bytes_of(&gradient)); + // Match Skia's epsilon for radii comparison + const SKIA_EPSILON: f32 = 1.0 / (1 << 12) as f32; + if gradient.p0 == gradient.p1 && (gradient.r0 - gradient.r1).abs() < SKIA_EPSILON { + self.encode_color(DrawColor::new(Color::TRANSPARENT)); + } + match self.add_ramp(color_stops, alpha, extend) { + RampStops::Empty => self.encode_color(DrawColor::new(Color::TRANSPARENT)), + RampStops::One(color) => self.encode_color(DrawColor::new(color)), + _ => { + self.draw_tags.push(DrawTag::RADIAL_GRADIENT); + self.draw_data + .extend_from_slice(bytemuck::bytes_of(&gradient)); + } + } } /// Encodes an image brush. @@ -354,7 +369,7 @@ impl Encoding { color_stops: impl Iterator, alpha: f32, extend: Extend, - ) { + ) -> RampStops { let offset = self.draw_data.len(); let stops_start = self.resources.color_stops.len(); if alpha != 1.0 { @@ -364,14 +379,32 @@ impl Encoding { } else { self.resources.color_stops.extend(color_stops); } - self.resources.patches.push(Patch::Ramp { - draw_data_offset: offset, - stops: stops_start..self.resources.color_stops.len(), - extend, - }); + let stops_end = self.resources.color_stops.len(); + match stops_end - stops_start { + 0 => RampStops::Empty, + 1 => RampStops::One(self.resources.color_stops.pop().unwrap().color), + _ => { + self.resources.patches.push(Patch::Ramp { + draw_data_offset: offset, + stops: stops_start..stops_end, + extend, + }); + RampStops::Many + } + } } } +/// Result for adding a sequence of color stops. +enum RampStops { + /// Color stop sequence was empty. + Empty, + /// Contained a single color stop. + One(Color), + /// More than one color stop. + Many, +} + /// Encoded data for late bound resources. #[cfg(feature = "full")] #[derive(Clone, Default)] diff --git a/examples/scenes/src/test_scenes.rs b/examples/scenes/src/test_scenes.rs index 9c95b3c..7c48364 100644 --- a/examples/scenes/src/test_scenes.rs +++ b/examples/scenes/src/test_scenes.rs @@ -31,6 +31,7 @@ pub fn test_scenes() -> SceneSet { scene!(cardioid_and_friends), scene!(animated_text: animated), scene!(gradient_extend), + scene!(two_point_radial), scene!(brush_transform: animated), scene!(blend_grid), scene!(conflation_artifacts), @@ -252,11 +253,13 @@ fn brush_transform(sb: &mut SceneBuilder, params: &mut SceneParams) { fn gradient_extend(sb: &mut SceneBuilder, params: &mut SceneParams) { fn square(sb: &mut SceneBuilder, is_radial: bool, transform: Affine, extend: Extend) { - let colors = [Color::RED, Color::GREEN, Color::BLUE]; + let colors = [Color::RED, Color::rgb8(0, 255, 0), Color::BLUE]; let width = 300f64; let height = 300f64; let gradient: Brush = if is_radial { - Gradient::new_radial((width * 0.5, height * 0.5), (width * 0.25) as f32) + let center = (width * 0.5, height * 0.5); + let radius = (width * 0.25) as f32; + Gradient::new_two_point_radial(center, radius * 0.25, center, radius) .with_stops(colors) .with_extend(extend) .into() @@ -294,10 +297,45 @@ fn gradient_extend(sb: &mut SceneBuilder, params: &mut SceneParams) { label, ); } +} + +fn two_point_radial(sb: &mut SceneBuilder, params: &mut SceneParams) { + let colors = [Color::RED, Color::rgb8(0, 255, 0), Color::BLUE]; + let extend = Extend::Reflect; + let gradient1 = Gradient::new_two_point_radial((150.0, 150.0), 50.0, (200.0, 150.0), 100.0) + .with_extend(extend) + .with_stops(colors); + let gradient2 = Gradient::new_two_point_radial((300.0, 150.0), 100.0, (150.0, 150.0), 50.0) + .with_extend(extend) + .with_stops(colors); + let gradient3 = Gradient::new_two_point_radial((300.0, 150.0), 50.0, (150.0, 150.0), 50.0) + .with_extend(extend) + .with_stops(colors); + sb.fill( + Fill::NonZero, + Affine::scale(1.5) * Affine::translate((50.0, 0.0)), + &gradient1, + None, + &Rect::new(0.0, 0.0, 400.0, 400.0), + ); + sb.fill( + Fill::NonZero, + Affine::scale(1.5) * Affine::translate((50.0, 300.0)), + &gradient2, + None, + &Rect::new(0.0, 0.0, 400.0, 400.0), + ); + sb.fill( + Fill::NonZero, + Affine::scale(1.5) * Affine::translate((50.0, 600.0)), + &gradient3, + None, + &Rect::new(0.0, 0.0, 400.0, 400.0), + ); let t = (params.time * 0.5).sin() * 0.5 + 0.5; let x_delta: f64 = t * 2000.0 - 1000.0; let gradient = - Gradient::new_two_point_radial((400.0, 500.0), 100.0, (101.0 + x_delta, 500.0), 200.0) + Gradient::new_two_point_radial((400.0, 500.0), 100.0, (101.0 + x_delta, 500.0), 0.0) .with_extend(Extend::Reflect) .with_stops([Color::GREEN, Color::WHITE, Color::RED]); sb.fill( diff --git a/shader/coarse.wgsl b/shader/coarse.wgsl index 58fe409..aeb85c2 100644 --- a/shader/coarse.wgsl +++ b/shader/coarse.wgsl @@ -376,7 +376,7 @@ fn main( } } // DRAWTAG_FILL_RAD_GRADIENT - case 0x2dcu: { + case 0x29cu: { let linewidth = bitcast(info_bin_data[di]); if write_path(tile, linewidth) { let index = scene[dd]; diff --git a/shader/draw_leaf.wgsl b/shader/draw_leaf.wgsl index beced9c..ab27619 100644 --- a/shader/draw_leaf.wgsl +++ b/shader/draw_leaf.wgsl @@ -6,6 +6,7 @@ #import clip #import drawtag #import bbox +#import transform @group(0) @binding(0) var config: Config; @@ -30,12 +31,6 @@ var clip_inp: array; let WG_SIZE = 256u; -// Possibly dedup? -struct Transform { - matrx: vec4, - translate: vec2, -} - fn read_transform(transform_base: u32, ix: u32) -> Transform { let base = transform_base + ix * 6u; let c0 = bitcast(scene[base]); @@ -110,18 +105,16 @@ fn main( // let y1 = f32(bbox.y1); // let bbox_f = vec4(x0, y0, x1, y1); let fill_mode = u32(bbox.linewidth >= 0.0); - var matrx: vec4; - var translate: vec2; + var transform = Transform(); var linewidth = bbox.linewidth; if linewidth >= 0.0 || tag_word == DRAWTAG_FILL_LIN_GRADIENT || tag_word == DRAWTAG_FILL_RAD_GRADIENT || tag_word == DRAWTAG_FILL_IMAGE { - let transform = read_transform(config.transform_base, bbox.trans_ix); - matrx = transform.matrx; - translate = transform.translate; + transform = read_transform(config.transform_base, bbox.trans_ix); } if linewidth >= 0.0 { // Note: doesn't deal with anisotropic case + let matrx = transform.matrx; linewidth *= sqrt(abs(matrx.x * matrx.w - matrx.y * matrx.z)); } switch tag_word { @@ -134,8 +127,8 @@ fn main( info[di] = bitcast(linewidth); var p0 = bitcast>(vec2(scene[dd + 1u], scene[dd + 2u])); var p1 = bitcast>(vec2(scene[dd + 3u], scene[dd + 4u])); - p0 = matrx.xy * p0.x + matrx.zw * p0.y + translate; - p1 = matrx.xy * p1.x + matrx.zw * p1.y + translate; + p0 = transform_apply(transform, p0); + p1 = transform_apply(transform, p1); let dxy = p1 - p0; let scale = 1.0 / dot(dxy, dxy); let line_xy = dxy * scale; @@ -145,48 +138,100 @@ fn main( info[di + 3u] = bitcast(line_c); } // DRAWTAG_FILL_RAD_GRADIENT - case 0x2dcu: { + case 0x29cu: { + // Two-point conical gradient implementation based + // on the algorithm at + // This epsilon matches what Skia uses + let GRADIENT_EPSILON = 1.0 / f32(1 << 12u); info[di] = bitcast(linewidth); var p0 = bitcast>(vec2(scene[dd + 1u], scene[dd + 2u])); var p1 = bitcast>(vec2(scene[dd + 3u], scene[dd + 4u])); - let r0 = bitcast(scene[dd + 5u]); - let r1 = bitcast(scene[dd + 6u]); - let inv_det = 1.0 / (matrx.x * matrx.w - matrx.y * matrx.z); - let inv_mat = inv_det * vec4(matrx.w, -matrx.y, -matrx.z, matrx.x); - let rr = r1 / (r1 - r0); - var rr1 = rr; - if r0 > 0.0 { - p0 = p0 + (p0 - p1) * rr * 0.5; - rr1 = 1.0; + var r0 = bitcast(scene[dd + 5u]); + var r1 = bitcast(scene[dd + 6u]); + let user_to_gradient = transform_inverse(transform); + // Output variables + var xform = Transform(); + var focal_x = 0.0; + var radius = 0.0; + var kind = 0u; + var flags = 0u; + if abs(r0 - r1) <= GRADIENT_EPSILON { + // When the radii are the same, emit a strip gradient + kind = RAD_GRAD_KIND_STRIP; + let scaled = r0 / distance(p0, p1); + xform = transform_mul( + two_point_to_unit_line(p0, p1), + user_to_gradient + ); + radius = scaled * scaled; + } else { + // Assume a two point conical gradient unless the centers + // are equal. + kind = RAD_GRAD_KIND_CONE; + if all(p0 == p1) { + kind = RAD_GRAD_KIND_CIRCULAR; + // Nudge p0 a bit to avoid denormals. + p0 += GRADIENT_EPSILON; + } + if r1 == 0.0 { + // If r1 == 0.0, swap the points and radii + flags |= RAD_GRAD_SWAPPED; + let tmp_p = p0; + p0 = p1; + p1 = tmp_p; + let tmp_r = r0; + r0 = r1; + r1 = tmp_r; + } + focal_x = r0 / (r0 - r1); + let one_minus_focal_x = 1.0 - focal_x; + let cf = one_minus_focal_x * p0 + focal_x * p1; + let abs_one_minus_focal_x = abs(one_minus_focal_x); + radius = r1 / (distance(cf, p1)); + let user_to_unit_line = transform_mul( + two_point_to_unit_line(cf, p1), + user_to_gradient + ); + var user_to_scaled = user_to_unit_line; + // When r == 1.0, focal point is on circle + if abs(radius - 1.0) <= GRADIENT_EPSILON { + kind = RAD_GRAD_KIND_FOCAL_ON_CIRCLE; + let scale = 0.5 * abs_one_minus_focal_x; + user_to_scaled = transform_mul( + Transform(vec4(scale, 0.0, 0.0, scale), vec2(0.0)), + user_to_unit_line + ); + } else { + let a = radius * radius - 1.0; + let scale_x = radius / a * abs_one_minus_focal_x; + let scale_y = sqrt(abs(a)) / a * abs_one_minus_focal_x; + user_to_scaled = transform_mul( + Transform(vec4(scale_x, 0.0, 0.0, scale_y), vec2(0.0)), + user_to_unit_line + ); + } + xform = user_to_scaled; } - let inv_tr = mat2x2(inv_mat.xy, inv_mat.zw) * -translate - p0; - let center1 = p1 - p0; - let ra_inv = rr1 / (r1 * r1 - dot(center1, center1)); - let c1 = center1 * ra_inv; - let ra = rr1 * ra_inv; - info[di + 1u] = bitcast(inv_mat.x); - info[di + 2u] = bitcast(inv_mat.y); - info[di + 3u] = bitcast(inv_mat.z); - info[di + 4u] = bitcast(inv_mat.w); - info[di + 5u] = bitcast(inv_tr.x); - info[di + 6u] = bitcast(inv_tr.y); - info[di + 7u] = bitcast(c1.x); - info[di + 8u] = bitcast(c1.y); - info[di + 9u] = bitcast(ra); - info[di + 10u] = bitcast(rr); + info[di + 1u] = bitcast(xform.matrx.x); + info[di + 2u] = bitcast(xform.matrx.y); + info[di + 3u] = bitcast(xform.matrx.z); + info[di + 4u] = bitcast(xform.matrx.w); + info[di + 5u] = bitcast(xform.translate.x); + info[di + 6u] = bitcast(xform.translate.y); + info[di + 7u] = bitcast(focal_x); + info[di + 8u] = bitcast(radius); + info[di + 9u] = bitcast((flags << 3u) | kind); } // DRAWTAG_FILL_IMAGE case 0x248u: { info[di] = bitcast(linewidth); - let inv_det = 1.0 / (matrx.x * matrx.w - matrx.y * matrx.z); - let inv_mat = inv_det * vec4(matrx.w, -matrx.y, -matrx.z, matrx.x); - let inv_tr = mat2x2(inv_mat.xy, inv_mat.zw) * -translate; - info[di + 1u] = bitcast(inv_mat.x); - info[di + 2u] = bitcast(inv_mat.y); - info[di + 3u] = bitcast(inv_mat.z); - info[di + 4u] = bitcast(inv_mat.w); - info[di + 5u] = bitcast(inv_tr.x); - info[di + 6u] = bitcast(inv_tr.y); + let inv = transform_inverse(transform); + info[di + 1u] = bitcast(inv.matrx.x); + info[di + 2u] = bitcast(inv.matrx.y); + info[di + 3u] = bitcast(inv.matrx.z); + info[di + 4u] = bitcast(inv.matrx.w); + info[di + 5u] = bitcast(inv.translate.x); + info[di + 6u] = bitcast(inv.translate.y); info[di + 7u] = scene[dd]; info[di + 8u] = scene[dd + 1u]; } @@ -201,3 +246,17 @@ fn main( clip_inp[m.clip_ix] = ClipInp(ix, i32(path_ix)); } } + +fn two_point_to_unit_line(p0: vec2, p1: vec2) -> Transform { + let tmp1 = from_poly2(p0, p1); + let inv = transform_inverse(tmp1); + let tmp2 = from_poly2(vec2(0.0), vec2(1.0, 0.0)); + return transform_mul(tmp2, inv); +} + +fn from_poly2(p0: vec2, p1: vec2) -> Transform { + return Transform( + vec4(p1.y - p0.y, p0.x - p1.x, p1.x - p0.x, p1.y - p0.y), + vec2(p0.x, p0.y) + ); +} diff --git a/shader/fine.wgsl b/shader/fine.wgsl index 85ed7a4..4047ebd 100644 --- a/shader/fine.wgsl +++ b/shader/fine.wgsl @@ -82,10 +82,12 @@ fn read_rad_grad(cmd_ix: u32) -> CmdRadGrad { let m3 = bitcast(info[info_offset + 3u]); let matrx = vec4(m0, m1, m2, m3); let xlat = vec2(bitcast(info[info_offset + 4u]), bitcast(info[info_offset + 5u])); - let c1 = vec2(bitcast(info[info_offset + 6u]), bitcast(info[info_offset + 7u])); - let ra = bitcast(info[info_offset + 8u]); - let roff = bitcast(info[info_offset + 9u]); - return CmdRadGrad(index, extend_mode, matrx, xlat, c1, ra, roff); + let focal_x = bitcast(info[info_offset + 6u]); + let radius = bitcast(info[info_offset + 7u]); + let flags_kind = info[info_offset + 8u]; + let flags = flags_kind >> 3u; + let kind = flags_kind & 0x7u; + return CmdRadGrad(index, extend_mode, matrx, xlat, focal_x, radius, kind, flags); } fn read_image(cmd_ix: u32) -> CmdImage { @@ -295,17 +297,45 @@ fn main( // CMD_RAD_GRAD case 7u: { let rad = read_rad_grad(cmd_ix); - let rr = rad.rr; - let roff = rr - 1.0; + let focal_x = rad.focal_x; + let one_minus_focal_x = 1.0 - focal_x; + let radius = rad.radius; + let is_strip = rad.kind == RAD_GRAD_KIND_STRIP; + let is_circular = rad.kind == RAD_GRAD_KIND_CIRCULAR; + let is_focal_on_circle = rad.kind == RAD_GRAD_KIND_FOCAL_ON_CIRCLE; + let is_swapped = (rad.flags & RAD_GRAD_SWAPPED) != 0u; + let inv_r1 = select(1.0 / radius, 0.0, is_circular); + let root_f = select(1.0, -1.0, is_swapped || one_minus_focal_x < 0.0); + let t_base_scale = select(vec2(0.0, -1.0), vec2(1.0, 1.0), is_swapped); + let t_sign = sign(one_minus_focal_x); for (var i = 0u; i < PIXELS_PER_THREAD; i += 1u) { let my_xy = vec2(xy.x + f32(i), xy.y); - // TODO: can hoist y, but for now stick to the GLSL version - let xy_xformed = rad.matrx.xy * my_xy.x + rad.matrx.zw * my_xy.y + rad.xlat; - let ba = dot(xy_xformed, rad.c1); - let ca = rad.ra * dot(xy_xformed, xy_xformed); - let t = sqrt(ba * ba + ca) - ba; - if t >= 0.0 { - let x = i32(round(extend_mode(t * rr - roff, rad.extend_mode) * f32(GRADIENT_WIDTH - 1))); + let local_xy = rad.matrx.xy * my_xy.x + rad.matrx.zw * my_xy.y + rad.xlat; + let x = local_xy.x; + let y = local_xy.y; + let xx = x * x; + let yy = y * y; + let x_inv_r1 = x * inv_r1; + var t = 0.0; + var valid = true; + if is_strip { + let a = radius - yy; + t = sqrt(a) + x; + valid = a >= 0.0; + } else if is_focal_on_circle { + t = (xx + yy) / x; + valid = t >= 0.0; + } else if radius > 1.0 { + t = sqrt(xx + yy) - x_inv_r1; + } else { + let a = xx - yy; + t = root_f * sqrt(a) - x_inv_r1; + valid = a >= 0.0 && t >= 0.0; + } + if valid { + t = extend_mode(focal_x + t_sign * t, rad.extend_mode); + t = (t_base_scale.x - t) * t_base_scale.y; + let x = i32(round(t * f32(GRADIENT_WIDTH - 1))); let fg_rgba = textureLoad(gradients, vec2(x, i32(rad.index)), 0); let fg_i = fg_rgba * area[i]; rgba[i] = rgba[i] * (1.0 - fg_i.a) + fg_i; diff --git a/shader/pathseg.wgsl b/shader/pathseg.wgsl index ec059ab..bf02419 100644 --- a/shader/pathseg.wgsl +++ b/shader/pathseg.wgsl @@ -14,6 +14,7 @@ #import config #import pathtag #import cubic +#import transform @group(0) @binding(0) var config: Config; @@ -36,7 +37,6 @@ struct AtomicPathBbox { @group(0) @binding(3) var path_bboxes: array; - @group(0) @binding(4) var cubics: array; @@ -85,11 +85,6 @@ fn read_i16_point(ix: u32) -> vec2 { return vec2(x, y); } -struct Transform { - matrx: vec4, - translate: vec2, -} - fn read_transform(transform_base: u32, ix: u32) -> Transform { let base = transform_base + ix * 6u; let c0 = bitcast(scene[base]); @@ -103,10 +98,6 @@ fn read_transform(transform_base: u32, ix: u32) -> Transform { return Transform(matrx, translate); } -fn transform_apply(transform: Transform, p: vec2) -> vec2 { - return transform.matrx.xy * p.x + transform.matrx.zw * p.y + transform.translate; -} - fn round_down(x: f32) -> i32 { return i32(floor(x)); } diff --git a/shader/shared/config.wgsl b/shader/shared/config.wgsl index 1063134..9b1cd2e 100644 --- a/shader/shared/config.wgsl +++ b/shader/shared/config.wgsl @@ -49,3 +49,12 @@ let N_TILE_Y = 16u; let N_TILE = 256u; let BLEND_STACK_SPLIT = 4u; + +// Radial gradient kinds +let RAD_GRAD_KIND_CIRCULAR = 1u; +let RAD_GRAD_KIND_STRIP = 2u; +let RAD_GRAD_KIND_FOCAL_ON_CIRCLE = 3u; +let RAD_GRAD_KIND_CONE = 4u; + +// Radial gradient flags +let RAD_GRAD_SWAPPED = 1u; diff --git a/shader/shared/drawtag.wgsl b/shader/shared/drawtag.wgsl index 0d532d1..0b8ae41 100644 --- a/shader/shared/drawtag.wgsl +++ b/shader/shared/drawtag.wgsl @@ -18,7 +18,7 @@ struct DrawMonoid { let DRAWTAG_NOP = 0u; let DRAWTAG_FILL_COLOR = 0x44u; let DRAWTAG_FILL_LIN_GRADIENT = 0x114u; -let DRAWTAG_FILL_RAD_GRADIENT = 0x2dcu; +let DRAWTAG_FILL_RAD_GRADIENT = 0x29cu; let DRAWTAG_FILL_IMAGE = 0x248u; let DRAWTAG_BEGIN_CLIP = 0x9u; let DRAWTAG_END_CLIP = 0x21u; diff --git a/shader/shared/ptcl.wgsl b/shader/shared/ptcl.wgsl index 8e80524..d5fc5d4 100644 --- a/shader/shared/ptcl.wgsl +++ b/shader/shared/ptcl.wgsl @@ -55,9 +55,10 @@ struct CmdRadGrad { extend_mode: u32, matrx: vec4, xlat: vec2, - c1: vec2, - ra: f32, - rr: f32, + focal_x: f32, + radius: f32, + kind: u32, + flags: u32, } struct CmdImage { diff --git a/shader/shared/transform.wgsl b/shader/shared/transform.wgsl new file mode 100644 index 0000000..00d7279 --- /dev/null +++ b/shader/shared/transform.wgsl @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT OR Unlicense + +// Helpers for working with transforms. + +struct Transform { + matrx: vec4, + translate: vec2, +} + +fn transform_apply(transform: Transform, p: vec2) -> vec2 { + return transform.matrx.xy * p.x + transform.matrx.zw * p.y + transform.translate; +} + +fn transform_inverse(transform: Transform) -> Transform { + let inv_det = 1.0 / (transform.matrx.x * transform.matrx.w - transform.matrx.y * transform.matrx.z); + let inv_mat = inv_det * vec4(transform.matrx.w, -transform.matrx.y, -transform.matrx.z, transform.matrx.x); + let inv_tr = mat2x2(inv_mat.xy, inv_mat.zw) * -transform.translate; + return Transform(inv_mat, inv_tr); +} + +fn transform_mul(a: Transform, b: Transform) -> Transform { + return Transform( + a.matrx.xyxy * b.matrx.xxzz + a.matrx.zwzw * b.matrx.yyww, + a.matrx.xy * b.translate.x + a.matrx.zw * b.translate.y + a.translate + ); +} diff --git a/src/shaders.rs b/src/shaders.rs index d1cc0dc..8da1807 100644 --- a/src/shaders.rs +++ b/src/shaders.rs @@ -318,4 +318,5 @@ const SHARED_SHADERS: &[(&str, &str)] = &[ shared_shader!("ptcl"), shared_shader!("segment"), shared_shader!("tile"), + shared_shader!("transform"), ]; From 570a0f3ce431cd39a34dba4b867f27343f7c7204 Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Wed, 10 May 2023 13:54:42 -0400 Subject: [PATCH 09/17] Re-export font API Just exports fello from the glyph module so we can construct FontRefs in xilem. --- src/glyph.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/glyph.rs b/src/glyph.rs index b388227..75fbce5 100644 --- a/src/glyph.rs +++ b/src/glyph.rs @@ -29,6 +29,7 @@ use { vello_encoding::Encoding, }; +pub use fello; pub use vello_encoding::Glyph; /// General context for creating scene fragments for glyph outlines. From b4ffb884946101466681a8dd0eb81c14788bc285 Mon Sep 17 00:00:00 2001 From: Arman Uguray Date: Wed, 10 May 2023 14:03:50 -0700 Subject: [PATCH 10/17] [shaders] Revise access to backend-agnostic metadata Previously the generated shader data structures were rooted in backend-specific top-level mods (`mod wgsl`, `mod msl`, etc). This made access to per-shader information that is common to all backends (e.g. workgroup sizes, shader name etc) awkward to access from backend agnostic code, especially when feature-gated conditional compilation is used on the client side. The data structures have been rearranged such that there is a top-level `ComputeShader` declaration for each stage under a `gen` mod. The `ComputeShader` struct declares feature-gated fields for backend shader sources, such that backend specific data is now a leaf node in the structure rather than the root. This has some additional benefits: 1. Common data doesn't have to be redeclared, saving on code size when multiple backends are enabled. 2. The backend specific source code was previously encoded as a `[u8]`. We can now use types that more closely match the expected format, for example `&str` for WGSL and MSL, `[u32]` for SPIR-V, etc. 3. If we ever need to expose additional backend-specific metadata in the future, we can bundle them alongside the source code in a backend-specific data structure at this level of the tree. --- crates/shaders/build.rs | 33 ++++++++++++--------------------- crates/shaders/src/lib.rs | 9 ++++++++- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/crates/shaders/build.rs b/crates/shaders/build.rs index 7e5a952..8c948c1 100644 --- a/crates/shaders/build.rs +++ b/crates/shaders/build.rs @@ -21,18 +21,7 @@ fn main() { shaders.sort_by(|x, y| x.0.cmp(&y.0)); let mut buf = String::default(); write_types(&mut buf, &shaders).unwrap(); - if cfg!(feature = "wgsl") { - write_shaders(&mut buf, "wgsl", &shaders, |info| { - info.source.as_bytes().to_owned() - }) - .unwrap(); - } - if cfg!(feature = "msl") { - write_shaders(&mut buf, "msl", &shaders, |info| { - compile::msl::translate(info).unwrap().as_bytes().to_owned() - }) - .unwrap(); - } + write_shaders(&mut buf, &shaders).unwrap(); std::fs::write(&dest_path, &buf).unwrap(); println!("cargo:rerun-if-changed=../shader"); } @@ -65,11 +54,9 @@ fn write_types(buf: &mut String, shaders: &[(String, ShaderInfo)]) -> Result<(), fn write_shaders( buf: &mut String, - mod_name: &str, shaders: &[(String, ShaderInfo)], - translate: impl Fn(&ShaderInfo) -> Vec, ) -> Result<(), std::fmt::Error> { - writeln!(buf, "pub mod {mod_name} {{")?; + writeln!(buf, "mod gen {{")?; writeln!(buf, " use super::*;")?; writeln!(buf, " use BindType::*;")?; writeln!(buf, " pub const SHADERS: Shaders<'static> = Shaders {{")?; @@ -80,14 +67,8 @@ fn write_shaders( .map(|binding| binding.ty) .collect::>(); let wg_bufs = &info.workgroup_buffers; - let source = translate(info); writeln!(buf, " {name}: ComputeShader {{")?; writeln!(buf, " name: Cow::Borrowed({:?}),", name)?; - writeln!( - buf, - " code: Cow::Borrowed(&{:?}),", - source.as_slice() - )?; writeln!( buf, " workgroup_size: {:?},", @@ -99,6 +80,16 @@ fn write_shaders( " workgroup_buffers: Cow::Borrowed(&{:?}),", wg_bufs )?; + if cfg!(feature = "wgsl") { + writeln!(buf, " wgsl: Cow::Borrowed(&{:?}),", info.source)?; + } + if cfg!(feature = "msl") { + writeln!( + buf, + " msl: Cow::Borrowed(&{:?}),", + compile::msl::translate(info).unwrap() + )?; + } writeln!(buf, " }},")?; } writeln!(buf, " }};")?; diff --git a/crates/shaders/src/lib.rs b/crates/shaders/src/lib.rs index 66ac937..73b621d 100644 --- a/crates/shaders/src/lib.rs +++ b/crates/shaders/src/lib.rs @@ -13,10 +13,15 @@ use std::borrow::Cow; #[derive(Clone, Debug)] pub struct ComputeShader<'a> { pub name: Cow<'a, str>, - pub code: Cow<'a, [u8]>, pub workgroup_size: [u32; 3], pub bindings: Cow<'a, [BindType]>, pub workgroup_buffers: Cow<'a, [WorkgroupBufferInfo]>, + + #[cfg(feature = "wgsl")] + pub wgsl: Cow<'a, str>, + + #[cfg(feature = "msl")] + pub msl: Cow<'a, str>, } pub trait PipelineHost { @@ -32,3 +37,5 @@ pub trait PipelineHost { } include!(concat!(env!("OUT_DIR"), "/shaders.rs")); + +pub use gen::SHADERS; From 5e1188f9687173d8039598b8043e0986b1f027d9 Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Thu, 11 May 2023 12:37:36 -0400 Subject: [PATCH 11/17] replace branches with chained selects This exchanges the per-pixel branching with additional ALU + selects. My expectation is that this will be faster, but that may be hardware/driver dependent and likely requires profiling and examination of generated code. The original code is kept in a comment with notes to explain the more obfuscated select version. --- shader/fine.wgsl | 125 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 89 insertions(+), 36 deletions(-) diff --git a/shader/fine.wgsl b/shader/fine.wgsl index 4047ebd..bab1b63 100644 --- a/shader/fine.wgsl +++ b/shader/fine.wgsl @@ -115,22 +115,33 @@ fn read_end_clip(cmd_ix: u32) -> CmdEndClip { } fn extend_mode(t: f32, mode: u32) -> f32 { - // This can be replaced with two selects, exchanging the cost - // of a branch for additional ALU - switch mode { - // PAD - case 0u: { - return clamp(t, 0.0, 1.0); - } - // REPEAT - case 1u: { - return fract(t); - } - // REFLECT (2) - default: { - return abs(t - 2.0 * round(0.5 * t)); - } - } + let EXTEND_PAD = 0u; + let EXTEND_REPEAT = 1u; + let EXTEND_REFLECT = 2u; + // Branching version of the code below: + // + // switch mode { + // // EXTEND_PAD + // case 0u: { + // return clamp(t, 0.0, 1.0); + // } + // // EXTEND_REPEAT + // case 1u: { + // return fract(t); + // } + // // EXTEND_REFLECT + // default: { + // return abs(t - 2.0 * round(0.5 * t)); + // } + // } + let pad = clamp(t, 0.0, 1.0); + let repeat = fract(t); + let reflect = abs(t - 2.0 * round(0.5 * t)); + return select( + select(pad, repeat, mode == EXTEND_REPEAT), + reflect, + mode == EXTEND_REFLECT + ); } #else @@ -304,9 +315,9 @@ fn main( let is_circular = rad.kind == RAD_GRAD_KIND_CIRCULAR; let is_focal_on_circle = rad.kind == RAD_GRAD_KIND_FOCAL_ON_CIRCLE; let is_swapped = (rad.flags & RAD_GRAD_SWAPPED) != 0u; + let is_greater = radius > 1.0; let inv_r1 = select(1.0 / radius, 0.0, is_circular); - let root_f = select(1.0, -1.0, is_swapped || one_minus_focal_x < 0.0); - let t_base_scale = select(vec2(0.0, -1.0), vec2(1.0, 1.0), is_swapped); + let less_scale = select(1.0, -1.0, is_swapped || one_minus_focal_x < 0.0); let t_sign = sign(one_minus_focal_x); for (var i = 0u; i < PIXELS_PER_THREAD; i += 1u) { let my_xy = vec2(xy.x + f32(i), xy.y); @@ -316,25 +327,67 @@ fn main( let xx = x * x; let yy = y * y; let x_inv_r1 = x * inv_r1; - var t = 0.0; - var valid = true; - if is_strip { - let a = radius - yy; - t = sqrt(a) + x; - valid = a >= 0.0; - } else if is_focal_on_circle { - t = (xx + yy) / x; - valid = t >= 0.0; - } else if radius > 1.0 { - t = sqrt(xx + yy) - x_inv_r1; - } else { - let a = xx - yy; - t = root_f * sqrt(a) - x_inv_r1; - valid = a >= 0.0 && t >= 0.0; - } - if valid { + // This is the branching version of the code implemented + // by the chained selects below: + // + // var t = 0.0; + // var is_valid = true; + // if is_strip { + // let a = radius - yy; + // t = sqrt(a) + x; + // is_valid = a >= 0.0; + // } else if is_focal_on_circle { + // t = (xx + yy) / x; + // is_valid = t >= 0.0; + // } else if radius > 1.0 { + // t = sqrt(xx + yy) - x_inv_r1; + // } else { + // let a = xx - yy; + // t = root_f * sqrt(a) - x_inv_r1; + // is_valid = a >= 0.0 && t >= 0.0; + // } + // + // The pattern is that these can all be computed with + // the expression: a * sqrt(b) + c + // + // The parameters to the expression are computed up front + // and chosen with chained selects based on their + // respective conditions. The same process is done + // for determining the validity of the resulting value. + var strip_params = vec3(1.0, radius - yy, x); + var foc_params = vec3(1.0, 0.0, (xx + yy) / x); + var greater_params = vec3(1.0, xx + yy, -x_inv_r1); + var less_params = vec3(less_scale, xx - yy, -x_inv_r1); + var params = select( + select( + select( + less_params, + greater_params, + is_greater, + ), + foc_params, + is_focal_on_circle, + ), + strip_params, + is_strip, + ); + var t = params.x * sqrt(params.y) + params.z; + let is_valid = select( + select( + select( + params.y >= 0.0 && t >= 0.0, + true, + is_greater + ), + t >= 0.0 && x != 0.0, + is_focal_on_circle, + ), + params.y >= 0.0, + is_strip, + ); + if is_valid { t = extend_mode(focal_x + t_sign * t, rad.extend_mode); - t = (t_base_scale.x - t) * t_base_scale.y; + t = select(t, 1.0 - t, is_swapped); let x = i32(round(t * f32(GRADIENT_WIDTH - 1))); let fg_rgba = textureLoad(gradients, vec2(x, i32(rad.index)), 0); let fg_i = fg_rgba * area[i]; From 58c7df469dba9fbd102706984a2f38c477dfac51 Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Mon, 15 May 2023 14:45:38 -0400 Subject: [PATCH 12/17] Address review feedback * replace one_minus_focal_x and abs_one_minus_focal_x variables with the actual expressions * replace division by r^2-1 with multiplication by reciprocal * revert chain selects to branchy code for clarity. Branching is dynamically uniform so shouldn't affect performance * add suggested comment describing gradient kind/flags constants --- shader/draw_leaf.wgsl | 11 ++-- shader/fine.wgsl | 121 +++++++++++--------------------------- shader/shared/config.wgsl | 4 ++ 3 files changed, 42 insertions(+), 94 deletions(-) diff --git a/shader/draw_leaf.wgsl b/shader/draw_leaf.wgsl index ab27619..9845fdd 100644 --- a/shader/draw_leaf.wgsl +++ b/shader/draw_leaf.wgsl @@ -184,9 +184,7 @@ fn main( r1 = tmp_r; } focal_x = r0 / (r0 - r1); - let one_minus_focal_x = 1.0 - focal_x; - let cf = one_minus_focal_x * p0 + focal_x * p1; - let abs_one_minus_focal_x = abs(one_minus_focal_x); + let cf = (1.0 - focal_x) * p0 + focal_x * p1; radius = r1 / (distance(cf, p1)); let user_to_unit_line = transform_mul( two_point_to_unit_line(cf, p1), @@ -196,15 +194,16 @@ fn main( // When r == 1.0, focal point is on circle if abs(radius - 1.0) <= GRADIENT_EPSILON { kind = RAD_GRAD_KIND_FOCAL_ON_CIRCLE; - let scale = 0.5 * abs_one_minus_focal_x; + let scale = 0.5 * abs(1.0 - focal_x); user_to_scaled = transform_mul( Transform(vec4(scale, 0.0, 0.0, scale), vec2(0.0)), user_to_unit_line ); } else { let a = radius * radius - 1.0; - let scale_x = radius / a * abs_one_minus_focal_x; - let scale_y = sqrt(abs(a)) / a * abs_one_minus_focal_x; + let a_recip = 1.0 / a; + let scale_x = radius * a_recip * abs(1.0 - focal_x); + let scale_y = sqrt(abs(a)) * a_recip * abs(1.0 - focal_x); user_to_scaled = transform_mul( Transform(vec4(scale_x, 0.0, 0.0, scale_y), vec2(0.0)), user_to_unit_line diff --git a/shader/fine.wgsl b/shader/fine.wgsl index bab1b63..e6ddb2c 100644 --- a/shader/fine.wgsl +++ b/shader/fine.wgsl @@ -118,30 +118,20 @@ fn extend_mode(t: f32, mode: u32) -> f32 { let EXTEND_PAD = 0u; let EXTEND_REPEAT = 1u; let EXTEND_REFLECT = 2u; - // Branching version of the code below: - // - // switch mode { - // // EXTEND_PAD - // case 0u: { - // return clamp(t, 0.0, 1.0); - // } - // // EXTEND_REPEAT - // case 1u: { - // return fract(t); - // } - // // EXTEND_REFLECT - // default: { - // return abs(t - 2.0 * round(0.5 * t)); - // } - // } - let pad = clamp(t, 0.0, 1.0); - let repeat = fract(t); - let reflect = abs(t - 2.0 * round(0.5 * t)); - return select( - select(pad, repeat, mode == EXTEND_REPEAT), - reflect, - mode == EXTEND_REFLECT - ); + switch mode { + // EXTEND_PAD + case 0u: { + return clamp(t, 0.0, 1.0); + } + // EXTEND_REPEAT + case 1u: { + return fract(t); + } + // EXTEND_REFLECT + default: { + return abs(t - 2.0 * round(0.5 * t)); + } + } } #else @@ -309,16 +299,14 @@ fn main( case 7u: { let rad = read_rad_grad(cmd_ix); let focal_x = rad.focal_x; - let one_minus_focal_x = 1.0 - focal_x; let radius = rad.radius; let is_strip = rad.kind == RAD_GRAD_KIND_STRIP; let is_circular = rad.kind == RAD_GRAD_KIND_CIRCULAR; let is_focal_on_circle = rad.kind == RAD_GRAD_KIND_FOCAL_ON_CIRCLE; let is_swapped = (rad.flags & RAD_GRAD_SWAPPED) != 0u; - let is_greater = radius > 1.0; - let inv_r1 = select(1.0 / radius, 0.0, is_circular); - let less_scale = select(1.0, -1.0, is_swapped || one_minus_focal_x < 0.0); - let t_sign = sign(one_minus_focal_x); + let r1_recip = select(1.0 / radius, 0.0, is_circular); + let less_scale = select(1.0, -1.0, is_swapped || (1.0 - focal_x) < 0.0); + let t_sign = sign(1.0 - focal_x); for (var i = 0u; i < PIXELS_PER_THREAD; i += 1u) { let my_xy = vec2(xy.x + f32(i), xy.y); let local_xy = rad.matrx.xy * my_xy.x + rad.matrx.zw * my_xy.y + rad.xlat; @@ -326,65 +314,22 @@ fn main( let y = local_xy.y; let xx = x * x; let yy = y * y; - let x_inv_r1 = x * inv_r1; - // This is the branching version of the code implemented - // by the chained selects below: - // - // var t = 0.0; - // var is_valid = true; - // if is_strip { - // let a = radius - yy; - // t = sqrt(a) + x; - // is_valid = a >= 0.0; - // } else if is_focal_on_circle { - // t = (xx + yy) / x; - // is_valid = t >= 0.0; - // } else if radius > 1.0 { - // t = sqrt(xx + yy) - x_inv_r1; - // } else { - // let a = xx - yy; - // t = root_f * sqrt(a) - x_inv_r1; - // is_valid = a >= 0.0 && t >= 0.0; - // } - // - // The pattern is that these can all be computed with - // the expression: a * sqrt(b) + c - // - // The parameters to the expression are computed up front - // and chosen with chained selects based on their - // respective conditions. The same process is done - // for determining the validity of the resulting value. - var strip_params = vec3(1.0, radius - yy, x); - var foc_params = vec3(1.0, 0.0, (xx + yy) / x); - var greater_params = vec3(1.0, xx + yy, -x_inv_r1); - var less_params = vec3(less_scale, xx - yy, -x_inv_r1); - var params = select( - select( - select( - less_params, - greater_params, - is_greater, - ), - foc_params, - is_focal_on_circle, - ), - strip_params, - is_strip, - ); - var t = params.x * sqrt(params.y) + params.z; - let is_valid = select( - select( - select( - params.y >= 0.0 && t >= 0.0, - true, - is_greater - ), - t >= 0.0 && x != 0.0, - is_focal_on_circle, - ), - params.y >= 0.0, - is_strip, - ); + var t = 0.0; + var is_valid = true; + if is_strip { + let a = radius - yy; + t = sqrt(a) + x; + is_valid = a >= 0.0; + } else if is_focal_on_circle { + t = (xx + yy) / x; + is_valid = t >= 0.0 && x != 0.0; + } else if radius > 1.0 { + t = sqrt(xx + yy) - x * r1_recip; + } else { // radius < 1.0 + let a = xx - yy; + t = less_scale * sqrt(a) - x * r1_recip; + is_valid = a >= 0.0 && t >= 0.0; + } if is_valid { t = extend_mode(focal_x + t_sign * t, rad.extend_mode); t = select(t, 1.0 - t, is_swapped); diff --git a/shader/shared/config.wgsl b/shader/shared/config.wgsl index 9b1cd2e..4ac718c 100644 --- a/shader/shared/config.wgsl +++ b/shader/shared/config.wgsl @@ -50,6 +50,10 @@ let N_TILE = 256u; let BLEND_STACK_SPLIT = 4u; +// The following are computed in draw_leaf from the generic gradient parameters +// encoded in the scene, and stored in the gradient's info struct, for +// consumption during fine rasterization. + // Radial gradient kinds let RAD_GRAD_KIND_CIRCULAR = 1u; let RAD_GRAD_KIND_STRIP = 2u; From 7b68630d6a8ce672aa38dbd2c3a3c677d0748458 Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Mon, 15 May 2023 14:54:44 -0400 Subject: [PATCH 13/17] refactor common scale ratio code --- shader/draw_leaf.wgsl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shader/draw_leaf.wgsl b/shader/draw_leaf.wgsl index 9845fdd..59e9e97 100644 --- a/shader/draw_leaf.wgsl +++ b/shader/draw_leaf.wgsl @@ -201,9 +201,9 @@ fn main( ); } else { let a = radius * radius - 1.0; - let a_recip = 1.0 / a; - let scale_x = radius * a_recip * abs(1.0 - focal_x); - let scale_y = sqrt(abs(a)) * a_recip * abs(1.0 - focal_x); + let scale_ratio = abs(1.0 - focal_x) / a; + let scale_x = radius * scale_ratio; + let scale_y = sqrt(abs(a)) * scale_ratio; user_to_scaled = transform_mul( Transform(vec4(scale_x, 0.0, 0.0, scale_y), vec2(0.0)), user_to_unit_line From 780cff09dbc103b6a850a979858d03d6ef17753f Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Tue, 16 May 2023 02:35:20 -0400 Subject: [PATCH 14/17] add examples from COLRv1 spec --- examples/scenes/src/test_scenes.rs | 208 ++++++++++++++++++++++------- 1 file changed, 162 insertions(+), 46 deletions(-) diff --git a/examples/scenes/src/test_scenes.rs b/examples/scenes/src/test_scenes.rs index 7c48364..6696f1c 100644 --- a/examples/scenes/src/test_scenes.rs +++ b/examples/scenes/src/test_scenes.rs @@ -299,52 +299,168 @@ fn gradient_extend(sb: &mut SceneBuilder, params: &mut SceneParams) { } } -fn two_point_radial(sb: &mut SceneBuilder, params: &mut SceneParams) { - let colors = [Color::RED, Color::rgb8(0, 255, 0), Color::BLUE]; - let extend = Extend::Reflect; - let gradient1 = Gradient::new_two_point_radial((150.0, 150.0), 50.0, (200.0, 150.0), 100.0) - .with_extend(extend) - .with_stops(colors); - let gradient2 = Gradient::new_two_point_radial((300.0, 150.0), 100.0, (150.0, 150.0), 50.0) - .with_extend(extend) - .with_stops(colors); - let gradient3 = Gradient::new_two_point_radial((300.0, 150.0), 50.0, (150.0, 150.0), 50.0) - .with_extend(extend) - .with_stops(colors); - sb.fill( - Fill::NonZero, - Affine::scale(1.5) * Affine::translate((50.0, 0.0)), - &gradient1, - None, - &Rect::new(0.0, 0.0, 400.0, 400.0), - ); - sb.fill( - Fill::NonZero, - Affine::scale(1.5) * Affine::translate((50.0, 300.0)), - &gradient2, - None, - &Rect::new(0.0, 0.0, 400.0, 400.0), - ); - sb.fill( - Fill::NonZero, - Affine::scale(1.5) * Affine::translate((50.0, 600.0)), - &gradient3, - None, - &Rect::new(0.0, 0.0, 400.0, 400.0), - ); - let t = (params.time * 0.5).sin() * 0.5 + 0.5; - let x_delta: f64 = t * 2000.0 - 1000.0; - let gradient = - Gradient::new_two_point_radial((400.0, 500.0), 100.0, (101.0 + x_delta, 500.0), 0.0) - .with_extend(Extend::Reflect) - .with_stops([Color::GREEN, Color::WHITE, Color::RED]); - sb.fill( - Fill::NonZero, - Affine::translate((400.0, 800.0)) * Affine::scale(0.2), - &gradient, - None, - &Rect::new(0.0, 0.0, 2000.0, 2000.0), - ); +fn two_point_radial(sb: &mut SceneBuilder, _params: &mut SceneParams) { + fn make( + sb: &mut SceneBuilder, + x0: f64, + y0: f64, + r0: f32, + x1: f64, + y1: f64, + r1: f32, + transform: Affine, + extend: Extend, + ) { + let colors = [Color::RED, Color::YELLOW, Color::rgb8(6, 85, 186)]; + let width = 400f64; + let height = 200f64; + let rect = Rect::new(0.0, 0.0, width, height); + sb.fill(Fill::NonZero, transform, Color::WHITE, None, &rect); + sb.fill( + Fill::NonZero, + transform, + &Gradient::new_two_point_radial((x0, y0), r0, (x1, y1), r1) + .with_stops(colors) + .with_extend(extend), + None, + &Rect::new(0.0, 0.0, width, height), + ); + let r0 = r0 as f64 - 1.0; + let r1 = r1 as f64 - 1.0; + let stroke_width = 1.0; + sb.stroke( + &Stroke::new(stroke_width), + transform, + Color::BLACK, + None, + &Ellipse::new((x0, y0), (r0, r0), 0.0), + ); + sb.stroke( + &Stroke::new(stroke_width), + transform, + Color::BLACK, + None, + &Ellipse::new((x1, y1), (r1, r1), 0.0), + ); + } + + // These demonstrate radial gradient patterns similar to the examples shown + // at + + for (i, mode) in [Extend::Pad, Extend::Repeat, Extend::Reflect] + .iter() + .enumerate() + { + let y = 100.0; + let x0 = 140.0; + let x1 = x0 + 140.0; + let r0 = 20.0; + let r1 = 50.0; + make( + sb, + x0, + y, + r0, + x1, + y, + r1, + Affine::translate((i as f64 * 420.0 + 20.0, 20.0)), + *mode, + ); + } + + for (i, mode) in [Extend::Pad, Extend::Repeat, Extend::Reflect] + .iter() + .enumerate() + { + let y = 100.0; + let x0 = 140.0; + let x1 = x0 + 140.0; + let r0 = 20.0; + let r1 = 50.0; + make( + sb, + x1, + y, + r1, + x0, + y, + r0, + Affine::translate((i as f64 * 420.0 + 20.0, 240.0)), + *mode, + ); + } + + for (i, mode) in [Extend::Pad, Extend::Repeat, Extend::Reflect] + .iter() + .enumerate() + { + let y = 100.0; + let x0 = 140.0; + let x1 = x0 + 140.0; + let r0 = 50.0; + let r1 = 50.0; + make( + sb, + x0, + y, + r0, + x1, + y, + r1, + Affine::translate((i as f64 * 420.0 + 20.0, 460.0)), + *mode, + ); + } + + for (i, mode) in [Extend::Pad, Extend::Repeat, Extend::Reflect] + .iter() + .enumerate() + { + let x0 = 140.0; + let y0 = 125.0; + let r0 = 20.0; + let x1 = 190.0; + let y1 = 100.0; + let r1 = 95.0; + make( + sb, + x0, + y0, + r0, + x1, + y1, + r1, + Affine::translate((i as f64 * 420.0 + 20.0, 680.0)), + *mode, + ); + } + + for (i, mode) in [Extend::Pad, Extend::Repeat, Extend::Reflect] + .iter() + .enumerate() + { + let x0 = 140.0; + let y0 = 125.0; + let r0 = 20.0; + let x1 = 190.0; + let y1 = 100.0; + let r1 = 96.0; + // Shift p0 so the outer edges of both circles touch + let p0 = Point::new(x1, y1) + + ((Point::new(x0, y0) - Point::new(x1, y1)).normalize() * (r1 - r0)); + make( + sb, + p0.x, + p0.y, + r0 as f32, + x1, + y1, + r1 as f32, + Affine::translate((i as f64 * 420.0 + 20.0, 900.0)), + *mode, + ); + } } fn blend_grid(sb: &mut SceneBuilder, _: &mut SceneParams) { From d82bd409ffafd694215d0e0df031fbff21dadbef Mon Sep 17 00:00:00 2001 From: Arman Uguray Date: Tue, 16 May 2023 15:02:34 -0700 Subject: [PATCH 15/17] [shaders] Shader path look up fixes for hermetic builds * Bazel builds seem to fail to open relative paths to parent directories due to hermetic sandboxing of third-party repositories. This adds a WORKSPACE_MANIFEST_FILE environment variable that allows the caller to optionally provide an absolute path to the workspace root manifest file. * The existing code processed a shader file only if `FileType::is_file` returns true for it. This is not the case when sources are accessed via symbolic links, which is possible in a Bazel sandbox. The code now filters for the ".wgsl" file extension instead of the file type which should generally be safe. --- crates/shaders/build.rs | 16 ++++++++-- crates/shaders/src/compile/mod.rs | 37 ++++++++++++------------ crates/shaders/src/compile/preprocess.rs | 21 +++++++------- 3 files changed, 44 insertions(+), 30 deletions(-) diff --git a/crates/shaders/build.rs b/crates/shaders/build.rs index 8c948c1..614bacb 100644 --- a/crates/shaders/build.rs +++ b/crates/shaders/build.rs @@ -8,14 +8,26 @@ mod types; use std::env; use std::fmt::Write; -use std::path::Path; +use std::path::{Path, PathBuf}; use compile::ShaderInfo; fn main() { let out_dir = env::var_os("OUT_DIR").unwrap(); let dest_path = Path::new(&out_dir).join("shaders.rs"); - let mut shaders = compile::ShaderInfo::from_dir("../../shader"); + + // The shaders are defined under the workspace root and not in this crate so we need to locate + // them somehow. Cargo doesn't define an environment variable that points at the root workspace + // directory. In hermetic build environments that don't support relative paths (such as Bazel) + // we support supplying a `WORKSPACE_MANIFEST_FILE` that is expected to be an absolute path to + // the Cargo.toml file at the workspace root. If that's not present, we use a relative path. + let workspace_dir = env::var("WORKSPACE_MANIFEST_FILE") + .ok() + .and_then(|p| Path::new(&p).parent().map(|p| p.to_owned())) + .unwrap_or(PathBuf::from("../../")); + let shader_dir = Path::new(&workspace_dir).join("shader"); + let mut shaders = compile::ShaderInfo::from_dir(&shader_dir); + // Drop the HashMap and sort by name so that we get deterministic order. let mut shaders = shaders.drain().collect::>(); shaders.sort_by(|x, y| x.0.cmp(&y.0)); diff --git a/crates/shaders/src/compile/mod.rs b/crates/shaders/src/compile/mod.rs index 74cafda..98d116d 100644 --- a/crates/shaders/src/compile/mod.rs +++ b/crates/shaders/src/compile/mod.rs @@ -166,28 +166,29 @@ impl ShaderInfo { for entry in shader_dir .read_dir() .expect("Can read shader import directory") + .filter_map(move |e| { + e.ok() + .filter(|e| e.path().extension().map(|e| e == "wgsl").unwrap_or(false)) + }) { - let entry = entry.expect("Can continue reading shader import directory"); - if entry.file_type().unwrap().is_file() { - let file_name = entry.file_name(); - if let Some(name) = file_name.to_str() { - let suffix = ".wgsl"; - if let Some(shader_name) = name.strip_suffix(suffix) { - let contents = fs::read_to_string(shader_dir.join(&file_name)) - .expect("Could read shader {shader_name} contents"); - if let Some(permutations) = permutation_map.get(shader_name) { - for permutation in permutations { - let mut defines = defines.clone(); - defines.extend(permutation.defines.iter().cloned()); - let source = preprocess::preprocess(&contents, &defines, &imports); - let shader_info = Self::new(source.clone(), "main").unwrap(); - info.insert(permutation.name.clone(), shader_info); - } - } else { + let file_name = entry.file_name(); + if let Some(name) = file_name.to_str() { + let suffix = ".wgsl"; + if let Some(shader_name) = name.strip_suffix(suffix) { + let contents = fs::read_to_string(shader_dir.join(&file_name)) + .expect("Could read shader {shader_name} contents"); + if let Some(permutations) = permutation_map.get(shader_name) { + for permutation in permutations { + let mut defines = defines.clone(); + defines.extend(permutation.defines.iter().cloned()); let source = preprocess::preprocess(&contents, &defines, &imports); let shader_info = Self::new(source.clone(), "main").unwrap(); - info.insert(shader_name.to_string(), shader_info); + info.insert(permutation.name.clone(), shader_info); } + } else { + let source = preprocess::preprocess(&contents, &defines, &imports); + let shader_info = Self::new(source.clone(), "main").unwrap(); + info.insert(shader_name.to_string(), shader_info); } } } diff --git a/crates/shaders/src/compile/preprocess.rs b/crates/shaders/src/compile/preprocess.rs index 917f83f..199ea40 100644 --- a/crates/shaders/src/compile/preprocess.rs +++ b/crates/shaders/src/compile/preprocess.rs @@ -14,17 +14,18 @@ pub fn get_imports(shader_dir: &Path) -> HashMap { for entry in imports_dir .read_dir() .expect("Can read shader import directory") + .filter_map(move |e| { + e.ok() + .filter(|e| e.path().extension().map(|e| e == "wgsl").unwrap_or(false)) + }) { - let entry = entry.expect("Can continue reading shader import directory"); - if entry.file_type().unwrap().is_file() { - let file_name = entry.file_name(); - if let Some(name) = file_name.to_str() { - let suffix = ".wgsl"; - if let Some(import_name) = name.strip_suffix(suffix) { - let contents = fs::read_to_string(imports_dir.join(&file_name)) - .expect("Could read shader {import_name} contents"); - imports.insert(import_name.to_owned(), contents); - } + let file_name = entry.file_name(); + if let Some(name) = file_name.to_str() { + let suffix = ".wgsl"; + if let Some(import_name) = name.strip_suffix(suffix) { + let contents = fs::read_to_string(imports_dir.join(&file_name)) + .expect("Could read shader {import_name} contents"); + imports.insert(import_name.to_owned(), contents); } } } From 2c394aa265ca2bed677d9152e3941cfe56819051 Mon Sep 17 00:00:00 2001 From: Daniel McNab <36049421+DJMcNab@users.noreply.github.com> Date: Thu, 18 May 2023 16:14:46 +0100 Subject: [PATCH 16/17] Add a CI check that everything compiles (#321) --- .github/workflows/ci.yml | 43 ++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0996920..087c986 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,6 @@ on: push: branches: - main - - dev pull_request: jobs: @@ -11,17 +10,35 @@ jobs: name: cargo fmt steps: - uses: actions/checkout@v2 - - - name: install stable toolchain - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@stable + - run: cargo fmt --all --check + compiles: + runs-on: ubuntu-latest + name: Check workspace compile + steps: + - uses: actions/checkout@v2 + - uses: dtolnay/rust-toolchain@stable + - name: Install native dependencies + run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev + - run: cargo check --workspace + # --exclude with_bevy # for when bevy has an outdated wgpu version + # -Dwarnings # for when we have fixed unused code warnings + + wasm: + runs-on: ubuntu-latest + name: Ensure with_winit compiles on WASM + steps: + - uses: actions/checkout@v2 + - uses: dtolnay/rust-toolchain@stable with: - toolchain: stable - profile: minimal - components: rustfmt - override: true + targets: wasm32-unknown-unknown + # cargo-run-wasm does not provide a way to determine that it failed programmatically. + # Ideally, fix this and use: + # - run: cargo run_wasm -- -p with_winit --bin with_winit_bin --build-only + - name: Allow using WebGPU in web_sys + run: | + echo "RUSTFLAGS=--cfg=web_sys_unstable_apis" >> "$GITHUB_ENV" + - run: cargo check -p with_winit --target wasm32-unknown-unknown + + - - name: cargo fmt - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check From abfe9fbb56b97f14dffe2259db2a573e09f56222 Mon Sep 17 00:00:00 2001 From: Daniel McNab <36049421+DJMcNab@users.noreply.github.com> Date: Thu, 18 May 2023 18:50:57 +0100 Subject: [PATCH 17/17] Add a stats display for scene complexity (#322) --- examples/with_winit/README.md | 1 + examples/with_winit/src/lib.rs | 10 ++++++++-- examples/with_winit/src/stats.rs | 15 +++++++++++++-- src/lib.rs | 25 ++++++++++++++++++------- 4 files changed, 40 insertions(+), 11 deletions(-) diff --git a/examples/with_winit/README.md b/examples/with_winit/README.md index f1c0910..abcc8f3 100644 --- a/examples/with_winit/README.md +++ b/examples/with_winit/README.md @@ -20,5 +20,6 @@ $ cargo run -p with_winit --release -- [SVG FILES] - Space resets the position and zoom of the image. - S toggles the frame statistics layer - C resets the min/max frame time tracked by statistics +- D toggles displaying the required number of each kind of dynamically allocated element (default: off) - V toggles VSync on/off (default: on) - Escape exits the program. diff --git a/examples/with_winit/src/lib.rs b/examples/with_winit/src/lib.rs index e08add2..5bbf5fa 100644 --- a/examples/with_winit/src/lib.rs +++ b/examples/with_winit/src/lib.rs @@ -27,7 +27,7 @@ use vello::{ util::RenderContext, Renderer, Scene, SceneBuilder, }; -use vello::{RendererOptions, SceneFragment}; +use vello::{BumpAllocators, RendererOptions, SceneFragment}; use winit::{ event_loop::{EventLoop, EventLoopBuilder}, @@ -100,6 +100,8 @@ fn run( let mut images = ImageCache::new(); let mut stats = stats::Stats::new(); let mut stats_shown = true; + let mut scene_complexity: Option = None; + let mut complexity_shown = false; let mut vsync_on = true; let mut frame_start_time = Instant::now(); let start = Instant::now(); @@ -150,6 +152,9 @@ fn run( Some(VirtualKeyCode::S) => { stats_shown = !stats_shown; } + Some(VirtualKeyCode::D) => { + complexity_shown = !complexity_shown; + } Some(VirtualKeyCode::C) => { stats.clear_min_and_max(); } @@ -326,6 +331,7 @@ fn run( width as f64, height as f64, stats.samples(), + complexity_shown.then_some(scene_complexity).flatten(), vsync_on, ); } @@ -336,7 +342,7 @@ fn run( .expect("failed to get surface texture"); #[cfg(not(target_arch = "wasm32"))] { - vello::block_on_wgpu( + scene_complexity = vello::block_on_wgpu( &device_handle.device, renderers[render_state.surface.dev_id] .as_mut() diff --git a/examples/with_winit/src/stats.rs b/examples/with_winit/src/stats.rs index a6531e6..81dda86 100644 --- a/examples/with_winit/src/stats.rs +++ b/examples/with_winit/src/stats.rs @@ -19,7 +19,7 @@ use std::collections::VecDeque; use vello::{ kurbo::{Affine, PathEl, Rect}, peniko::{Brush, Color, Fill, Stroke}, - SceneBuilder, + BumpAllocators, SceneBuilder, }; const SLIDING_WINDOW_SIZE: usize = 100; @@ -40,6 +40,7 @@ impl Snapshot { viewport_width: f64, viewport_height: f64, samples: T, + bump: Option, vsync: bool, ) where T: Iterator, @@ -59,13 +60,23 @@ impl Snapshot { &Rect::new(0., 0., width, height), ); - let labels = [ + let mut labels = vec![ format!("Frame Time: {:.2} ms", self.frame_time_ms), format!("Frame Time (min): {:.2} ms", self.frame_time_min_ms), format!("Frame Time (max): {:.2} ms", self.frame_time_max_ms), format!("VSync: {}", if vsync { "on" } else { "off" }), format!("Resolution: {viewport_width}x{viewport_height}"), ]; + if let Some(bump) = &bump { + if bump.failed >= 1 { + labels.push(format!("Allocation Failed!")); + } + labels.push(format!("binning: {}", bump.binning)); + labels.push(format!("ptcl: {}", bump.ptcl)); + labels.push(format!("tile: {}", bump.tile)); + labels.push(format!("segments: {}", bump.segments)); + labels.push(format!("blend: {}", bump.blend)); + } // height / 2 is dedicated to the text labels and the rest is filled by the bar graph. let text_height = height * 0.5 / (1 + labels.len()) as f64; diff --git a/src/lib.rs b/src/lib.rs index b0f35a6..a1e3b03 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,6 +37,8 @@ pub use util::block_on_wgpu; use engine::{Engine, ExternalResource, Recording}; use shaders::FullShaders; +/// Temporary export, used in with_winit for stats +pub use vello_encoding::BumpAllocators; use wgpu::{Device, Queue, SurfaceTexture, TextureFormat, TextureView}; /// Catch-all error type. @@ -196,6 +198,12 @@ impl Renderer { /// The texture is assumed to be of the specified dimensions and have been created with /// the [wgpu::TextureFormat::Rgba8Unorm] format and the [wgpu::TextureUsages::STORAGE_BINDING] /// flag set. + /// + /// The return value is the value of the `BumpAllocators` in this rendering, which is currently used + /// for debug output. + /// + /// This return type is not stable, and will likely be changed when a more principled way to access + /// relevant statistics is implemented pub async fn render_to_texture_async( &mut self, device: &Device, @@ -203,13 +211,15 @@ impl Renderer { scene: &Scene, texture: &TextureView, params: &RenderParams, - ) -> Result<()> { + ) -> Result> { let mut render = Render::new(); let encoding = scene.data(); let recording = render.render_encoding_coarse(encoding, &self.shaders, params, true); let target = render.out_image(); let bump_buf = render.bump_buf(); self.engine.run_recording(device, queue, &recording, &[])?; + + let mut bump: Option = None; if let Some(bump_buf) = self.engine.get_download(bump_buf) { let buf_slice = bump_buf.slice(..); let (sender, receiver) = futures_intrusive::channel::shared::oneshot_channel(); @@ -219,8 +229,8 @@ impl Renderer { } else { return Err("channel was closed".into()); } - let _mapped = buf_slice.get_mapped_range(); - // println!("{:?}", bytemuck::cast_slice::<_, u32>(&mapped)); + let mapped = buf_slice.get_mapped_range(); + bump = Some(bytemuck::pod_read_unaligned(&*mapped)); } // TODO: apply logic to determine whether we need to rerun coarse, and also // allocate the blend stack as needed. @@ -231,7 +241,7 @@ impl Renderer { let external_resources = [ExternalResource::Image(target, texture)]; self.engine .run_recording(device, queue, &recording, &external_resources)?; - Ok(()) + Ok(bump) } /// See [Self::render_to_surface] @@ -242,7 +252,7 @@ impl Renderer { scene: &Scene, surface: &SurfaceTexture, params: &RenderParams, - ) -> Result<()> { + ) -> Result> { let width = params.width; let height = params.height; let mut target = self @@ -254,7 +264,8 @@ impl Renderer { if target.width != width || target.height != height { target = TargetTexture::new(device, width, height); } - self.render_to_texture_async(device, queue, scene, &target.view, params) + let bump = self + .render_to_texture_async(device, queue, scene, &target.view, params) .await?; let blit = self .blit @@ -292,7 +303,7 @@ impl Renderer { } queue.submit(Some(encoder.finish())); self.target = Some(target); - Ok(()) + Ok(bump) } }