Implement faster rect rendering
diff --git a/sparse_strips/vello_hybrid/src/render/common.rs b/sparse_strips/vello_hybrid/src/render/common.rs index 71b3b36..2f48a9d 100644 --- a/sparse_strips/vello_hybrid/src/render/common.rs +++ b/sparse_strips/vello_hybrid/src/render/common.rs
@@ -47,7 +47,7 @@ pub _padding: [u32; 3], } -/// Represents a GPU strip for rendering. +/// A GPU strip instance for rendering. /// /// This struct corresponds to the `StripInstance` struct in the shader. /// See the `StripInstance` documentation in `render_strips.wgsl` for detailed field descriptions. @@ -58,16 +58,16 @@ pub x: u16, /// See `StripInstance::xy` documentation in `render_strips.wgsl`. pub y: u16, - /// See `StripInstance::widths` documentation in `render_strips.wgsl`. + /// See `StripInstance::dense_width_or_rect_height` documentation in `render_strips.wgsl`. pub width: u16, - /// See `StripInstance::widths` documentation in `render_strips.wgsl`. - pub dense_width: u16, - /// See `StripInstance::col_idx` documentation in `render_strips.wgsl`. - pub col_idx: u32, + /// See `StripInstance::dense_width_or_rect_height` documentation in `render_strips.wgsl`. + pub dense_width_or_rect_height: u16, + /// See `StripInstance::col_idx_or_rect_frac` documentation in `render_strips.wgsl`. + pub col_idx_or_rect_frac: u32, /// See `StripInstance::payload` documentation in `render_strips.wgsl`. pub payload: u32, - /// See `StripInstance::paint` documentation in `render_strips.wgsl`. - pub paint: u32, + /// See `StripInstance::paint_and_rect_flag` documentation in `render_strips.wgsl`. + pub paint_and_rect_flag: u32, } /// Different types of GPU encoded paints.
diff --git a/sparse_strips/vello_hybrid/src/scene.rs b/sparse_strips/vello_hybrid/src/scene.rs index 988d3a1..160d7a8 100644 --- a/sparse_strips/vello_hybrid/src/scene.rs +++ b/sparse_strips/vello_hybrid/src/scene.rs
@@ -27,7 +27,6 @@ use vello_common::render_graph::RenderGraph; use vello_common::strip::Strip; use vello_common::strip_generator::{GenerationMode, StripGenerator, StripStorage}; -use vello_common::util::{is_integer_rect, is_integer_translation}; /// Default tolerance for curve flattening pub(crate) const DEFAULT_TOLERANCE: f64 = 0.1; @@ -72,6 +71,25 @@ pub(crate) paint: Paint, } +/// A rectangle stored in the fast-path buffer. +#[derive(Debug)] +pub(crate) struct FastPathRect { + pub(crate) x0: f32, + pub(crate) y0: f32, + pub(crate) x1: f32, + pub(crate) y1: f32, + pub(crate) paint: Paint, +} + +/// A command in the fast strips buffer. +#[derive(Debug)] +pub(crate) enum FastStripCommand { + /// A path rendered via the normal strip pipeline. + Path(FastStripsPath), + /// A rectangle. + Rect(FastPathRect), +} + /// A buffer that collects strips from paths that are rendered directly to the surface, /// bypassing coarse rasterization. /// @@ -79,14 +97,14 @@ /// for one path within that storage. #[derive(Debug, Default)] pub(crate) struct FastStripsBuffer { - /// All paths in the buffer. - pub(crate) paths: Vec<FastStripsPath>, + /// All commands in the buffer. + pub(crate) commands: Vec<FastStripCommand>, } impl FastStripsBuffer { #[inline(always)] fn clear(&mut self) { - self.paths.clear(); + self.commands.clear(); } } @@ -222,10 +240,13 @@ macro_rules! submit_strips { ($self:ident, $strip_storage:expr, $strip_start:expr, $paint:expr) => { if $self.strip_path_mode != StripPathMode::CoarseOnly && !$self.wide.has_layers() { - $self.fast_strips_buffer.paths.push(FastStripsPath { - strips: $strip_start..$strip_storage.strips.len(), - paint: $paint, - }); + $self + .fast_strips_buffer + .commands + .push(FastStripCommand::Path(FastStripsPath { + strips: $strip_start..$strip_storage.strips.len(), + paint: $paint, + })); } else { // In `ReplaceAfter(n)` mode the fast path prefix lives at `[0..n]` // and must not be fed into the coarse rasterizer. @@ -434,40 +455,56 @@ return; } - // Fast path: use optimized rect filling when transforms are integer translations - // AND rect coordinates are integers. This bypasses path processing by generating - // strips directly for the rectangle. - // - Requires integer translation to ensure pixel-aligned rect boundaries. - // - Requires integer rect coordinates because the optimized path doesn't handle - // anti-aliasing for fractional edges. - // - Also requires simple paint transform to avoid precision differences with complex paints. - if is_integer_translation(&self.render_state.transform) - && is_integer_translation(&self.render_state.paint_transform) - && is_integer_rect(rect) - { - self.fill_rect_fast(rect); - } else { - self.fill_path(&rect.to_path(DEFAULT_TOLERANCE)); + if self.try_fast_rect(rect) { + return; } + + self.fill_path(&rect.to_path(DEFAULT_TOLERANCE)); } - /// Fast path for filling a pixel-aligned rectangle. - /// - /// Bypasses path processing by generating strips directly for the rectangle. - /// The caller must ensure the transform is an integer translation and the rect - /// has integer coordinates. - fn fill_rect_fast(&mut self, rect: &Rect) { + #[expect( + clippy::cast_possible_truncation, + reason = "f64→f32 truncation is acceptable for pixel coordinates" + )] + fn try_fast_rect(&mut self, rect: &Rect) -> bool { + if self.strip_path_mode == StripPathMode::CoarseOnly || self.wide.has_layers() { + return false; + } + + if self.clip_context.get().is_some() { + return false; + } + + // We can't handle skewed rectangles. + let coeffs = self.render_state.transform.as_coeffs(); + if coeffs[1].abs() > 1e-5 || coeffs[2].abs() > 1e-5 { + return false; + } + let paint = self.encode_current_paint(); let transformed_rect = self.render_state.transform.transform_rect_bbox(*rect); - let strip_storage = &mut self.strip_storage.borrow_mut(); - let strip_start = strip_storage.strips.len(); - self.strip_generator.generate_filled_rect_fast( - &transformed_rect, - strip_storage, - self.clip_context.get(), - ); - submit_strips!(self, strip_storage, strip_start, paint); + let x0 = transformed_rect.x0.max(0.0).min(f64::from(self.width)); + let y0 = transformed_rect.y0.max(0.0).min(f64::from(self.height)); + let x1 = transformed_rect.x1.max(0.0).min(f64::from(self.width)); + let y1 = transformed_rect.y1.max(0.0).min(f64::from(self.height)); + + // Can't handle mirrored or zero-sized rectangles. + if x1 <= x0 || y1 <= y0 { + return false; + } + + self.fast_strips_buffer + .commands + .push(FastStripCommand::Rect(FastPathRect { + x0: x0 as f32, + y0: y0 as f32, + x1: x1 as f32, + y1: y1 as f32, + paint, + })); + + true } /// Stroke a rectangle with the current paint and stroke settings. @@ -493,15 +530,38 @@ } let mut strip_storage = self.strip_storage.borrow_mut(); - for path in self.fast_strips_buffer.paths.drain(..) { - self.wide.generate( - &strip_storage.strips[path.strips], - path.paint, - BlendMode::default(), - 0, - None, - &self.encoded_paints, - ); + for cmd in self.fast_strips_buffer.commands.drain(..) { + match cmd { + FastStripCommand::Path(path) => { + self.wide.generate( + &strip_storage.strips[path.strips], + path.paint, + BlendMode::default(), + 0, + None, + &self.encoded_paints, + ); + } + FastStripCommand::Rect(r) => { + let rect = Rect::new( + f64::from(r.x0), + f64::from(r.y0), + f64::from(r.x1), + f64::from(r.y1), + ); + let strip_start = strip_storage.strips.len(); + self.strip_generator + .generate_filled_rect_fast(&rect, &mut strip_storage, None); + self.wide.generate( + &strip_storage.strips[strip_start..], + r.paint, + BlendMode::default(), + 0, + None, + &self.encoded_paints, + ); + } + } } strip_storage.set_generation_mode(GenerationMode::Replace); @@ -529,7 +589,7 @@ // split point so the scheduler knows to process one coarse batch after // processing fast path strips up to this point. if !self.wide.has_layers() { - let split = self.fast_strips_buffer.paths.len(); + let split = self.fast_strips_buffer.commands.len(); self.coarse_batch_splits.push(split); } let mut strip_storage = self.strip_storage.borrow_mut(); @@ -1050,10 +1110,12 @@ strip_storage .strips .extend_from_slice(&adjusted_strips[start..end]); - self.fast_strips_buffer.paths.push(FastStripsPath { - strips: strip_start..strip_storage.strips.len(), - paint, - }); + self.fast_strips_buffer + .commands + .push(FastStripCommand::Path(FastStripsPath { + strips: strip_start..strip_storage.strips.len(), + paint, + })); } else { self.wide.generate( &adjusted_strips[start..end],
diff --git a/sparse_strips/vello_hybrid/src/schedule.rs b/sparse_strips/vello_hybrid/src/schedule.rs index e61315a..40a561e 100644 --- a/sparse_strips/vello_hybrid/src/schedule.rs +++ b/sparse_strips/vello_hybrid/src/schedule.rs
@@ -176,7 +176,7 @@ only break in edge cases, and some of them are also only related to conversions from f64 to f32." )] -use crate::scene::{FastStripsPath, StripPathMode}; +use crate::scene::{FastStripCommand, FastStripsPath, StripPathMode}; use crate::{GpuStrip, RenderError, Scene}; use alloc::collections::VecDeque; use alloc::vec::Vec; @@ -202,6 +202,10 @@ const PAINT_TYPE_RADIAL_GRADIENT: u32 = 3; const PAINT_TYPE_SWEEP_GRADIENT: u32 = 4; +/// Bit 31 of [`GpuStrip::paint_and_rect_flag`] signals that the strip +/// represents a full rectangle. +const RECT_STRIP_FLAG: u32 = 1 << 31; + // The sentinel tile index representing the surface. const SENTINEL_SLOT_IDX: usize = usize::MAX; @@ -485,7 +489,11 @@ match scene.strip_path_mode { StripPathMode::FastOnly => { // We only have strips. - self.push_direct_strips(scene, 0..scene.fast_strips_buffer.paths.len(), paint_idxs); + self.push_direct_strips( + scene, + 0..scene.fast_strips_buffer.commands.len(), + paint_idxs, + ); } StripPathMode::CoarseOnly => { // We only have coarse-rasterized paths. @@ -527,7 +535,7 @@ // Handle the last batch of fast strips, which isn't explicitly delimited in the // scene. - let tail_end = scene.fast_strips_buffer.paths.len(); + let tail_end = scene.fast_strips_buffer.commands.len(); if prev_split < tail_end { self.push_direct_strips(scene, prev_split..tail_end, paint_idxs); } @@ -555,7 +563,7 @@ Ok(()) } - /// Generate `GpuStrips` for a range of direct paths and append them + /// Generate `GpuStrips` for a range of direct commands and append them /// directly into the current round's surface draw array. fn push_direct_strips(&mut self, scene: &Scene, range: Range<usize>, paint_idxs: &[u32]) { let strip_storage = scene.strip_storage.borrow(); @@ -563,8 +571,51 @@ // rendered to the final surface. let draw = self.draw_mut(self.round, 2); - for path in &scene.fast_strips_buffer.paths[range] { - generate_gpu_strips_for_path(path, &strip_storage, scene, paint_idxs, &mut draw.0); + for cmd in &scene.fast_strips_buffer.commands[range] { + match cmd { + FastStripCommand::Path(path) => { + generate_gpu_strips_for_fast_path( + path, + &strip_storage, + scene, + paint_idxs, + &mut draw.0, + ); + } + FastStripCommand::Rect(r) => { + let sx0 = r.x0.floor(); + let sy0 = r.y0.floor(); + let sx1 = r.x1.ceil(); + let sy1 = r.y1.ceil(); + + let x = sx0 as u16; + let y = sy0 as u16; + // Are guaranteed to be > 0 since we rejected negative rectangles. + let width = (sx1 - sx0) as u16; + let height = (sy1 - sy0) as u16; + + let (payload, paint_packed) = + Self::process_paint(&r.paint, scene, (x, y), paint_idxs); + + // Determine the fractional offsets for anti-aliasing and quantize so it + // fits into u8. + let q = |f: f32| -> u8 { (f * 255.0 + 0.5) as u8 }; + let frac = u32::from(q(r.x0 - sx0)) + | (u32::from(q(r.y0 - sy0)) << 8) + | (u32::from(q(sx1 - r.x1)) << 16) + | (u32::from(q(sy1 - r.y1)) << 24); + + draw.0.push(GpuStrip { + x, + y, + width, + dense_width_or_rect_height: height, + col_idx_or_rect_frac: frac, + payload, + paint_and_rect_flag: paint_packed | RECT_STRIP_FLAG, + }); + } + } } } @@ -1207,7 +1258,7 @@ has_non_zero_alpha(rgba), "Color fields with 0 alpha are reserved for clipping" ); - let paint_packed = (COLOR_SOURCE_PAYLOAD << 30) | (PAINT_TYPE_SOLID << 27); + let paint_packed = (COLOR_SOURCE_PAYLOAD << 29) | (PAINT_TYPE_SOLID << 26); (rgba, paint_packed) } Paint::Indexed(indexed_paint) => { @@ -1217,9 +1268,9 @@ match scene.encoded_paints.get(paint_id) { Some(EncodedPaint::Image(encoded_image)) => match &encoded_image.source { ImageSource::OpaqueId { .. } => { - let paint_packed = (COLOR_SOURCE_PAYLOAD << 30) - | (PAINT_TYPE_IMAGE << 27) - | (paint_idx & 0x07FFFFFF); + let paint_packed = (COLOR_SOURCE_PAYLOAD << 29) + | (PAINT_TYPE_IMAGE << 26) + | (paint_idx & 0x03FF_FFFF); let scene_strip_xy = ((scene_strip_y as u32) << 16) | (scene_strip_x as u32); (scene_strip_xy, paint_packed) @@ -1233,9 +1284,9 @@ EncodedKind::Radial(_) => PAINT_TYPE_RADIAL_GRADIENT, EncodedKind::Sweep(_) => PAINT_TYPE_SWEEP_GRADIENT, }; - let paint_packed = (COLOR_SOURCE_PAYLOAD << 30) - | (gradient_paint_type << 27) - | (paint_idx & 0x07FFFFFF); + let paint_packed = (COLOR_SOURCE_PAYLOAD << 29) + | (gradient_paint_type << 26) + | (paint_idx & 0x03FF_FFFF); let scene_strip_xy = ((scene_strip_y as u32) << 16) | (scene_strip_x as u32); (scene_strip_xy, paint_packed) @@ -1253,8 +1304,8 @@ x: u16, y: u16, width: u16, - dense_width: u16, - col_idx: u32, + dense_width_or_rect_height: u16, + col_idx_or_rect_frac: u32, } impl GpuStripBuilder { @@ -1264,8 +1315,8 @@ x, y, width, - dense_width: 0, - col_idx: 0, + dense_width_or_rect_height: 0, + col_idx_or_rect_frac: 0, } } @@ -1275,15 +1326,15 @@ x: x_offset, y: u16::try_from(slot_idx).unwrap() * Tile::HEIGHT, width, - dense_width: 0, - col_idx: 0, + dense_width_or_rect_height: 0, + col_idx_or_rect_frac: 0, } } /// Add sparse strip parameters. fn with_sparse(mut self, dense_width: u16, col_idx: u32) -> Self { - self.dense_width = dense_width; - self.col_idx = col_idx; + self.dense_width_or_rect_height = dense_width; + self.col_idx_or_rect_frac = col_idx; self } @@ -1293,10 +1344,10 @@ x: self.x, y: self.y, width: self.width, - dense_width: self.dense_width, - col_idx: self.col_idx, + dense_width_or_rect_height: self.dense_width_or_rect_height, + col_idx_or_rect_frac: self.col_idx_or_rect_frac, payload, - paint, + paint_and_rect_flag: paint, } } @@ -1306,10 +1357,10 @@ x: self.x, y: self.y, width: self.width, - dense_width: self.dense_width, - col_idx: self.col_idx, + dense_width_or_rect_height: self.dense_width_or_rect_height, + col_idx_or_rect_frac: self.col_idx_or_rect_frac, payload: u32::try_from(from_slot).unwrap(), - paint: (COLOR_SOURCE_SLOT << 30) | (opacity as u32), + paint_and_rect_flag: (COLOR_SOURCE_SLOT << 29) | (opacity as u32), } } @@ -1326,11 +1377,11 @@ x: self.x, y: self.y, width: self.width, - dense_width: self.dense_width, - col_idx: self.col_idx, + dense_width_or_rect_height: self.dense_width_or_rect_height, + col_idx_or_rect_frac: self.col_idx_or_rect_frac, payload: (u32::try_from(src_slot).unwrap()) | ((u32::try_from(dest_slot).unwrap()) << 16), - paint: (COLOR_SOURCE_BLEND << 30) + paint_and_rect_flag: (COLOR_SOURCE_BLEND << 29) | ((opacity as u32) << 16) | ((mix_mode as u32) << 8) | (compose_mode as u32), @@ -1343,7 +1394,7 @@ rgba >= 0x1_00_00_00 } -fn generate_gpu_strips_for_path( +fn generate_gpu_strips_for_fast_path( path: &FastStripsPath, strip_storage: &StripStorage, scene: &Scene,
diff --git a/sparse_strips/vello_sparse_shaders/shaders/render_strips.wgsl b/sparse_strips/vello_sparse_shaders/shaders/render_strips.wgsl index a54638d..d02eef5 100644 --- a/sparse_strips/vello_sparse_shaders/shaders/render_strips.wgsl +++ b/sparse_strips/vello_sparse_shaders/shaders/render_strips.wgsl
@@ -38,8 +38,10 @@ const PAINT_TYPE_RADIAL_GRADIENT: u32 = 3u; const PAINT_TYPE_SWEEP_GRADIENT: u32 = 4u; -// Paint texture index mask (extracts lower 27 bits from paint field). -const PAINT_TEXTURE_INDEX_MASK: u32 = 0x07FFFFFFu; +// Paint texture index mask (extracts lower 26 bits from paint field). +const PAINT_TEXTURE_INDEX_MASK: u32 = 0x03FFFFFFu; + +const RECT_STRIP_FLAG: u32 = 0x80000000u; // Image quality const IMAGE_QUALITY_LOW = 0u; @@ -119,22 +121,38 @@ _padding2: u32, } -// `paint` bit layout: -// - Bits 30-31: `color_source` 0 = use payload, 1 = use slot texture, 2 = blend mode -// - Bits 0-29: Usage depends on color_source: +// A `StripInstance` can represent either a **normal strip** (representing a sparse fill or alpha fill of height +// Tile::HEIGHT) or a **rect strip** (an entire rectangle rendered as a single quad, with anti-aliasing support). +// The two modes are distinguished by RECT_STRIP_FLAG (bit 31 of `paint_and_rect_flag`). +// +// Depending on the active mode, the fields are interpreted as follows: +// +// Field | Normal strip | Rect strip +// ----------------------+-----------------------------------+----------------------------------- +// xy | Strip position | Rect top-left (snapped outward) +// widths_or_rect_height | [width, dense_width] | [width, height] (both snapped) +// col_idx_or_rect_frac | Alpha column index | Packed AA edge fractions (4 × u8) +// payload | Color / scene coords / slot idx | Color / scene coords +// paint_and_rect_flag | Paint encoding | Paint encoding | RECT_STRIP_FLAG +// +// +// `paint_and_rect_flag` bit layout: +// - Bit 31: `RECT_STRIP_FLAG` 0 = normal strip, 1 = rect strip +// - Bits 29-30: `color_source` 0 = use payload, 1 = use slot texture, 2 = blend mode +// - Bits 0-28: Usage depends on color_source: // // When color_source = 0 (COLOR_SOURCE_PAYLOAD): -// - Bits 27-29: `paint_type` (0 = solid, 1 = image, 2 = linear_gradient, 3 = radial_gradient, 4 = sweep_gradient) -// - Bits 0-26: +// - Bits 26-28: `paint_type` (0 = solid, 1 = image, 2 = linear_gradient, 3 = radial_gradient, 4 = sweep_gradient) +// - Bits 0-25: // - If paint_type = 0: unused // - If paint_type >= 1: `paint_texture_idx` // // When color_source = 1 (COLOR_SOURCE_SLOT): // - Bits 0-7: opacity (0-255) -// - Bits 8-29: unused +// - Bits 8-28: unused // // When color_source = 2 (COLOR_SOURCE_BLEND): -// - Bits 16-29: `dest_slot` (14 bits) +// - Bits 16-28: `dest_slot` (14 bits) // - Bits 8-15: `mix_mode` (8 bits) // - Bits 0-7: `compose_mode` (8 bits) // @@ -151,7 +169,7 @@ // ├── paint_type = 3 (PAINT_TYPE_RADIAL_GRADIENT) - Radial gradient (with kind discriminator) // └── paint_type = 4 (PAINT_TYPE_SWEEP_GRADIENT) - Sweep gradient rendering // ├── payload = [x, y] scene coordinates (packed as u16s) -// └── bits 0-27 = paint_texture_idx +// └── bits 0-25 = paint_texture_idx // // color_source = 1 (COLOR_SOURCE_SLOT) - Use slot texture // ├── payload = slot_index (u32) @@ -167,37 +185,44 @@ // └── bits 0-7 = compose_mode (compositing operation) struct StripInstance { // [x, y] packed as u16's - // x, y — coordinates of the strip + // x, y — coordinates of the strip or rect @location(0) xy: u32, // [width, dense_width] packed as u16's - // width — width of the strip + // width — width of the strip or rect // dense_width — width of the portion where alpha blending should be applied // Note that currently, if the strip instance represents an actual strip (i.e. an anti-aliased region), // width = dense_width. If the StripInstance represents a sparse fill region, then dense_width = 0. + // For rect strips, dense_width is repurposed to hold the rectangle height. // TODO: In the future, this could be optimized such that `width` always represents the width and a simple // 1-bit flag is used to distinguish between sparse fill region and strip. This frees up 15 other bits. // Otherwise, it might also be possible to merge a strip and sparse fill command into a single strip instance. - @location(1) widths: u32, - // Alpha texture column index where this strip's alpha values begin + @location(1) widths_or_rect_height: u32, + // For normal strips: alpha texture column index where this strip's alpha values begin. // There are [`Config::strip_height`] alpha values per column. - @location(2) col_idx: u32, + // For rect strips: packed fractional edge offsets for AA. + @location(2) col_idx_or_rect_frac: u32, // See StripInstance documentation above. @location(3) payload: u32, // See StripInstance documentation above. - @location(4) paint: u32, + @location(4) paint_and_rect_flag: u32, } struct VertexOutput { // Render type for the strip - @location(0) @interpolate(flat) paint: u32, + @location(0) @interpolate(flat) paint_and_rect_flag: u32, // Texture coordinates for the current fragment @location(1) tex_coord: vec2<f32>, // UV coordinates for the current fragment, used for image sampling @location(2) sample_xy: vec2<f32>, - // Ending x-position of the dense (alpha) region + // For normal strips: ending x-position of the dense (alpha) region. + // For rect strips: packed dimensions (width | height << 16). @location(3) @interpolate(flat) dense_end: u32, // Color value or slot index when alpha is 0 @location(4) @interpolate(flat) payload: u32, + // Packed fractional edge offsets for rectangles. + // Bits 0-7: x0, 8-15: y0, 16-23: x1, 24-31: y1. + // Zero for normal strips. + @location(5) @interpolate(flat) rect_frac: u32, // Normalized device coordinates (NDC) for the current vertex @builtin(position) position: vec4<f32>, }; @@ -228,29 +253,36 @@ // Unpack the x and y coordinates from the packed u32 instance.xy let x0 = instance.xy & 0xffffu; let y0 = instance.xy >> 16u; - // Unpack the total width and dense (alpha) width from the packed u32 instance.widths - let width = instance.widths & 0xffffu; - let dense_width = instance.widths >> 16u; - // Calculate the ending x-position of the dense (alpha) region - // This boundary is used in the fragment shader to determine if alpha sampling is needed - out.dense_end = instance.col_idx + dense_width; + let width = instance.widths_or_rect_height & 0xffffu; + let dense_width = instance.widths_or_rect_height >> 16u; + + let is_rect = (instance.paint_and_rect_flag & RECT_STRIP_FLAG) != 0u; + var height = config.strip_height; + if is_rect { + height = dense_width; + out.dense_end = width | (dense_width << 16u); + out.rect_frac = instance.col_idx_or_rect_frac; + } else { + out.dense_end = instance.col_idx_or_rect_frac + dense_width; + out.rect_frac = 0u; + } // Calculate the pixel coordinates of the current vertex within the strip let pix_x = f32(x0) + x * f32(width); - let pix_y = f32(y0) + y * f32(config.strip_height); + let pix_y = f32(y0) + y * f32(height); // Convert pixel coordinates to normalized device coordinates (NDC) // NDC ranges from -1 to 1, with (0,0) at the center of the viewport let ndc_x = pix_x * 2.0 / f32(config.width) - 1.0; let ndc_y = 1.0 - pix_y * 2.0 / f32(config.height); - let color_source = (instance.paint >> 30u) & 0x3u; + let color_source = (instance.paint_and_rect_flag >> 29u) & 0x3u; if color_source == COLOR_SOURCE_PAYLOAD { - let paint_type = (instance.paint >> 27u) & 0x7u; + let paint_type = (instance.paint_and_rect_flag >> 26u) & 0x7u; // Unpack view coordinates for image sampling and gradient calculations let scene_strip_x = instance.payload & 0xffffu; let scene_strip_y = instance.payload >> 16u; if paint_type == PAINT_TYPE_IMAGE { - let paint_tex_idx = instance.paint & PAINT_TEXTURE_INDEX_MASK; + let paint_tex_idx = instance.paint_and_rect_flag & PAINT_TEXTURE_INDEX_MASK; let encoded_image = unpack_encoded_image(paint_tex_idx); // Use view coordinates for image sampling (always in global view space) out.sample_xy = encoded_image.translate @@ -258,21 +290,24 @@ + encoded_image.transform.xy * f32(scene_strip_x) + encoded_image.transform.zw * f32(scene_strip_y) + encoded_image.transform.xy * x * f32(width) - + encoded_image.transform.zw * y * f32(config.strip_height); + + encoded_image.transform.zw * y * f32(height); } else if paint_type == PAINT_TYPE_LINEAR_GRADIENT || paint_type == PAINT_TYPE_RADIAL_GRADIENT || paint_type == PAINT_TYPE_SWEEP_GRADIENT { // Use view coordinates for gradient transform (always in global view space) out.sample_xy = vec2<f32>( f32(scene_strip_x) + x * f32(width), - f32(scene_strip_y) + y * f32(config.strip_height) + f32(scene_strip_y) + y * f32(height) ); } } - // Regular texture coordinates for other render types - out.tex_coord = vec2<f32>(f32(instance.col_idx) + x * f32(width), y * f32(config.strip_height)); + if is_rect { + out.tex_coord = vec2<f32>(x * f32(width), y * f32(height)); + } else { + out.tex_coord = vec2<f32>(f32(instance.col_idx_or_rect_frac) + x * f32(width), y * f32(height)); + } out.position = vec4<f32>(ndc_x, ndc_y, 0.0, 1.0); out.payload = instance.payload; - out.paint = instance.paint; + out.paint_and_rect_flag = instance.paint_and_rect_flag; return out; } @@ -285,13 +320,24 @@ @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> { - let x = u32(floor(in.tex_coord.x)); var alpha = 1.0; - // This if condition essentially checks whether the current pixel lies within a strip or a sparse - // fill region. In the former case, `dense_end` will be bigger than 0 since `dense_width` != 0. In the latter - // case, `dense_end` will always be zero since for sparse regions `col_idx` and `dense_width` are both set to - // zero. - if in.dense_end != 0 { + let is_rect = (in.paint_and_rect_flag & RECT_STRIP_FLAG) != 0u; + if is_rect && in.rect_frac != 0u { + let frac = unpack4x8unorm(in.rect_frac); + // Calculate how much of the pixel is actually covered by the rect. + // We do this by simply calculating the fractions in the x and y direction, and + // then multiplying them. + // For (maybe?) better performance, we calculate the x and y dimension in a single + // pass by packing everything into a vec2. + let rect_size = vec2<f32>(f32(in.dense_end & 0xFFFFu), f32(in.dense_end >> 16u)); + let tc = in.tex_coord; + // + 0.5 and -0.5 since the fragment shader positions the coordinates in the center of the pixel. + let bottom_and_right = min(tc + 0.5, rect_size - frac.zw); + let top_and_left = max(tc - 0.5, frac.xy); + let a = clamp(bottom_and_right - top_and_left, vec2(0.0), vec2(1.0)); + alpha = a.x * a.y; + } else if !is_rect && in.dense_end != 0u { + let x = u32(floor(in.tex_coord.x)); let y = u32(floor(in.tex_coord.y)); // Retrieve alpha value from the texture. We store 16 1-byte alpha // values per texel, with each color channel packing 4 alpha values. @@ -318,17 +364,17 @@ alpha = f32((alphas_u32 >> (y * 8u)) & 0xffu) * (1.0 / 255.0); } // Apply the alpha value to the unpacked RGBA color or slot index - let color_source = (in.paint >> 30u) & 0x3u; + let color_source = (in.paint_and_rect_flag >> 29u) & 0x3u; var final_color: vec4<f32>; if color_source == COLOR_SOURCE_PAYLOAD { - let paint_type = (in.paint >> 27u) & 0x7u; + let paint_type = (in.paint_and_rect_flag >> 26u) & 0x7u; // in.payload encodes a color for PAINT_TYPE_SOLID or sample_xy for PAINT_TYPE_IMAGE if paint_type == PAINT_TYPE_SOLID { final_color = alpha * unpack4x8unorm(in.payload); } else if paint_type == PAINT_TYPE_IMAGE { - let paint_tex_idx = in.paint & PAINT_TEXTURE_INDEX_MASK; + let paint_tex_idx = in.paint_and_rect_flag & PAINT_TEXTURE_INDEX_MASK; let encoded_image = unpack_encoded_image(paint_tex_idx); let image_offset = encoded_image.image_offset; let image_size = encoded_image.image_size; @@ -380,7 +426,7 @@ is_multiply ); } else if paint_type == PAINT_TYPE_LINEAR_GRADIENT { - let paint_tex_idx = in.paint & PAINT_TEXTURE_INDEX_MASK; + let paint_tex_idx = in.paint_and_rect_flag & PAINT_TEXTURE_INDEX_MASK; let linear_gradient = unpack_linear_gradient(paint_tex_idx); // Calculate fragment position and apply transform @@ -405,7 +451,7 @@ ); final_color = alpha * gradient_color; } else if paint_type == PAINT_TYPE_RADIAL_GRADIENT { - let paint_tex_idx = in.paint & PAINT_TEXTURE_INDEX_MASK; + let paint_tex_idx = in.paint_and_rect_flag & PAINT_TEXTURE_INDEX_MASK; let radial_gradient = unpack_radial_gradient(paint_tex_idx); // Calculate fragment position and apply transform @@ -430,7 +476,7 @@ ); final_color = alpha * gradient_color; } else if paint_type == PAINT_TYPE_SWEEP_GRADIENT { - let paint_tex_idx = in.paint & PAINT_TEXTURE_INDEX_MASK; + let paint_tex_idx = in.paint_and_rect_flag & PAINT_TEXTURE_INDEX_MASK; let sweep_gradient = unpack_sweep_gradient(paint_tex_idx); // Calculate fragment position and apply transform @@ -476,13 +522,13 @@ let clip_in_color = textureLoad(clip_input_texture, vec2(clip_x, clip_y), 0); // Extract opacity from first 8 bits (quantized from [0, 255]) - let opacity = f32(in.paint & 0xFFu) * (1.0 / 255.0); + let opacity = f32(in.paint_and_rect_flag & 0xFFu) * (1.0 / 255.0); final_color = alpha * opacity * clip_in_color; } else if color_source == COLOR_SOURCE_BLEND { - let opacity = f32((in.paint >> 16u) & 0xFFu) * (1.0 / 255.0); - let mix_mode = (in.paint >> 8u) & 0xFFu; - let compose_mode = in.paint & 0xFFu; + let opacity = f32((in.paint_and_rect_flag >> 16u) & 0xFFu) * (1.0 / 255.0); + let mix_mode = (in.paint_and_rect_flag >> 8u) & 0xFFu; + let compose_mode = in.paint_and_rect_flag & 0xFFu; // Read source color from slot let src_slot = in.payload & 0xFFFFu;