diff --git a/sparse_strips/vello_bench/src/fine/rounded_blurred_rect.rs b/sparse_strips/vello_bench/src/fine/rounded_blurred_rect.rs index e5ac1d945..682de92cc 100644 --- a/sparse_strips/vello_bench/src/fine/rounded_blurred_rect.rs +++ b/sparse_strips/vello_bench/src/fine/rounded_blurred_rect.rs @@ -35,7 +35,7 @@ fn base>(b: &mut Bencher<'_>, fine: &mut Fine, t let rect = BlurredRoundedRectangle { rect: Rect::new(0.0, 0.0, BENCH_WIDTH as f64, Tile::HEIGHT as f64), color: GREEN, - radius: 30.0, + radii: 30.0.into(), std_dev: 10.0, invert: false, }; diff --git a/sparse_strips/vello_common/src/blurred_rounded_rect.rs b/sparse_strips/vello_common/src/blurred_rounded_rect.rs index c2ed31521..1d1b6bf29 100644 --- a/sparse_strips/vello_common/src/blurred_rounded_rect.rs +++ b/sparse_strips/vello_common/src/blurred_rounded_rect.rs @@ -3,7 +3,7 @@ //! Blurred, rounded rectangles. use crate::color::{AlphaColor, Srgb}; -use crate::kurbo::Rect; +use crate::kurbo::{Rect, RoundedRectRadii}; /// A blurred, rounded rectangle. #[derive(Debug)] @@ -12,8 +12,11 @@ pub struct BlurredRoundedRectangle { pub rect: Rect, /// The color of the blurred rectangle. pub color: AlphaColor, - /// The radius of the rounded rectangle's corners. - pub radius: f32, + /// The radii of the rounded rectangle's corners. + /// + /// Each corner may have a different radius. Note that only scalar (circular) corner + /// radii are supported; elliptical radii are not. + pub radii: RoundedRectRadii, /// The standard deviation of the blur effect. pub std_dev: f32, /// Whether to paint the inverse (`1 - alpha`) of the blur coverage. diff --git a/sparse_strips/vello_common/src/encode.rs b/sparse_strips/vello_common/src/encode.rs index 02f244a19..866306cae 100644 --- a/sparse_strips/vello_common/src/encode.rs +++ b/sparse_strips/vello_common/src/encode.rs @@ -856,12 +856,14 @@ pub struct GradientRange { } /// An encoded blurred, rounded rectangle. +/// +/// Per-corner values are stored in the order `[top-left, top-right, bottom-left, bottom-right]`. #[derive(Debug)] pub struct EncodedBlurredRoundedRectangle { - /// An component for computing the blur effect. - pub exponent: f32, - /// An component for computing the blur effect. - pub recip_exponent: f32, + /// A per-corner component for computing the blur effect. + pub exponent: [f32; 4], + /// A per-corner component for computing the blur effect. + pub recip_exponent: [f32; 4], /// An component for computing the blur effect. pub scale: f32, /// An component for computing the blur effect. @@ -876,8 +878,8 @@ pub struct EncodedBlurredRoundedRectangle { pub width: f32, /// An component for computing the blur effect. pub height: f32, - /// An component for computing the blur effect. - pub r1: f32, + /// A per-corner component for computing the blur effect. + pub r1: [f32; 4], /// Whether to paint the inverse (`1 - alpha`) of the blur coverage. /// /// When `true`, the paint is fully opaque outside the blurred rectangle and fades to @@ -923,17 +925,30 @@ impl EncodeExt for BlurredRoundedRectangle { let width = rect.width() as f32; let height = rect.height() as f32; - let radius = self.radius.min(0.5 * width.min(height)); // To avoid divide by 0; potentially should be a bigger number for antialiasing. let std_dev = self.std_dev.max(1e-6); let min_edge = width.min(height); let rmax = 0.5 * min_edge; - let r0 = radius.hypot(std_dev * 1.15).min(rmax); - let r1 = radius.hypot(std_dev * 2.0).min(rmax); - let exponent = 2.0 * r1 / r0; + // Per-corner radii, in the order [top-left, top-right, bottom-left, bottom-right]. + let radii = [ + self.radii.top_left, + self.radii.top_right, + self.radii.bottom_left, + self.radii.bottom_right, + ] + .map(|r| (r as f32).clamp(0.0, rmax)); + + let r1 = radii.map(|radius| radius.hypot(std_dev * 2.0).min(rmax)); + let mut exponent = [0.0; 4]; + let mut recip_exponent = [0.0; 4]; + for i in 0..4 { + let r0 = radii[i].hypot(std_dev * 1.15).min(rmax); + exponent[i] = 2.0 * r1[i] / r0; + recip_exponent[i] = exponent[i].recip(); + } let std_dev_inv = std_dev.recip(); @@ -945,8 +960,8 @@ impl EncodeExt for BlurredRoundedRectangle { let w = width + delta.min(0.0); let h = height - delta.max(0.0); - let recip_exponent = exponent.recip(); - let scale = 0.5 * compute_erf7(std_dev_inv * 0.5 * (w.max(h) - 0.5 * radius)); + let mean_radius = 0.25 * (radii[0] + radii[1] + radii[2] + radii[3]); + let scale = 0.5 * compute_erf7(std_dev_inv * 0.5 * (w.max(h) - 0.5 * mean_radius)); let encoded = EncodedBlurredRoundedRectangle { exponent, diff --git a/sparse_strips/vello_cpu/CHANGELOG.md b/sparse_strips/vello_cpu/CHANGELOG.md index 31f956d26..07f009428 100644 --- a/sparse_strips/vello_cpu/CHANGELOG.md +++ b/sparse_strips/vello_cpu/CHANGELOG.md @@ -12,6 +12,10 @@ Subheadings to categorize changes are `added, changed, deprecated, removed, fixe This release has an [MSRV][] of 1.88. +### Changed + +- Breaking change: `RenderContext::fill_blurred_rounded_rect` now takes a `RoundedRectRadii`, allowing a different radius per corner. A uniform radius can be passed as `radius.into()`. + ## [0.1.0][] - 2026-07-29 This release has an [MSRV][] of 1.88. diff --git a/sparse_strips/vello_cpu/src/fine/common/rounded_blurred_rect.rs b/sparse_strips/vello_cpu/src/fine/common/rounded_blurred_rect.rs index 9089d1d88..7f887cb15 100644 --- a/sparse_strips/vello_cpu/src/fine/common/rounded_blurred_rect.rs +++ b/sparse_strips/vello_cpu/src/fine/common/rounded_blurred_rect.rs @@ -169,17 +169,34 @@ impl Iterator for AlphaCalculator { let r = &self.r; let y = j - r.v1 * r.height; - // Equivalent to r.r1 + y.abs() - (r.h * r.v1) - let y0 = r.r1 - r.h.mul_sub(r.v1, y.abs()); + let x = i - r.v1 * r.width; + + // Select the parameters of the corner in whose quadrant the pixel lies. + // Per-corner values are stored as [top-left, top-right, bottom-left, bottom-right]. + let simd = self.simd; + let right = simd.simd_ge_f32x8(x, r.v0); + let bottom = simd.simd_ge_f32x8(y, r.v0); + let select_corner = |v: &[f32x8; 4]| { + simd.select_f32x8( + bottom, + simd.select_f32x8(right, v[3], v[2]), + simd.select_f32x8(right, v[1], v[0]), + ) + }; + let r1 = select_corner(&r.r1); + let exponent = select_corner(&r.exponent); + let recip_exponent = select_corner(&r.recip_exponent); + + // Equivalent to r1 + y.abs() - (r.h * r.v1) + let y0 = r1 - r.h.mul_sub(r.v1, y.abs()); let y1 = y0.max(r.v0); - let x = i - r.v1 * r.width; - // Equivalent to r.r1 + x.abs() - (r.w * r.v1) - let x0 = r.r1 - r.w.mul_sub(r.v1, x.abs()); + // Equivalent to r1 + x.abs() - (r.w * r.v1) + let x0 = r1 - r.w.mul_sub(r.v1, x.abs()); let x1 = x0.max(r.v0); - let d_pos = (x1.powf(r.exponent) + y1.powf(r.exponent)).powf(r.recip_exponent); + let d_pos = (x1.powf(exponent) + y1.powf(exponent)).powf(recip_exponent); let d_neg = x0.max(y0).min(r.v0); - let d = d_pos + d_neg - r.r1; + let d = d_pos + d_neg - r1; let z = r.scale * (f32x8::compute_erf7(self.simd, r.std_dev_inv * (r.min_edge + d)) - f32x8::compute_erf7(self.simd, r.std_dev_inv * d)); @@ -192,8 +209,8 @@ impl Iterator for AlphaCalculator { #[derive(Debug)] struct SimdRoundedBlurredRect { - pub exponent: f32, - pub recip_exponent: f32, + pub exponent: [f32x8; 4], + pub recip_exponent: [f32x8; 4], pub scale: f32x8, pub std_dev_inv: f32x8, pub min_edge: f32x8, @@ -201,7 +218,7 @@ struct SimdRoundedBlurredRect { pub h: f32x8, pub width: f32x8, pub height: f32x8, - pub r1: f32x8, + pub r1: [f32x8; 4], pub v0: f32x8, pub v1: f32x8, } @@ -215,9 +232,9 @@ impl SimdRoundedBlurredRect { let w = f32x8::splat(s, encoded.w); let width = f32x8::splat(s, encoded.width); let height = f32x8::splat(s, encoded.height); - let r1 = f32x8::splat(s, encoded.r1); - let exponent = encoded.exponent; - let recip_exponent = encoded.recip_exponent; + let r1 = encoded.r1.map(|v| f32x8::splat(s, v)); + let exponent = encoded.exponent.map(|v| f32x8::splat(s, v)); + let recip_exponent = encoded.recip_exponent.map(|v| f32x8::splat(s, v)); let scale = f32x8::splat(s, encoded.scale); let min_edge = f32x8::splat(s, encoded.min_edge); let std_dev_inv = f32x8::splat(s, encoded.std_dev_inv); @@ -248,7 +265,7 @@ trait FloatExt { // explanation of this approximation to the erf function. /// Approximate the erf function. fn compute_erf7(simd: S, x: Self) -> Self; - fn powf(self, x: f32) -> Self; + fn powf(self, x: Self) -> Self; } impl FloatExt for f32x8 { @@ -269,16 +286,16 @@ impl FloatExt for f32x8 { } #[inline] - fn powf(mut self, x: f32) -> Self { + fn powf(mut self, x: Self) -> Self { // TODO: SIMD - self[0] = self[0].powf(x); - self[1] = self[1].powf(x); - self[2] = self[2].powf(x); - self[3] = self[3].powf(x); - self[4] = self[4].powf(x); - self[5] = self[5].powf(x); - self[6] = self[6].powf(x); - self[7] = self[7].powf(x); + self[0] = self[0].powf(x[0]); + self[1] = self[1].powf(x[1]); + self[2] = self[2].powf(x[2]); + self[3] = self[3].powf(x[3]); + self[4] = self[4].powf(x[4]); + self[5] = self[5].powf(x[5]); + self[6] = self[6].powf(x[6]); + self[7] = self[7].powf(x[7]); self } diff --git a/sparse_strips/vello_cpu/src/render.rs b/sparse_strips/vello_cpu/src/render.rs index f2d89e54f..524399d02 100644 --- a/sparse_strips/vello_cpu/src/render.rs +++ b/sparse_strips/vello_cpu/src/render.rs @@ -24,7 +24,7 @@ use vello_common::encode::{EncodeExt, EncodedPaint}; use vello_common::fearless_simd::Level; use vello_common::filter::FilterData; use vello_common::filter_effects::Filter; -use vello_common::kurbo::{Affine, BezPath, Rect, Stroke}; +use vello_common::kurbo::{Affine, BezPath, Rect, RoundedRectRadii, Stroke}; use vello_common::mask::Mask; use vello_common::paint::{ImageId, ImageResolver, Paint, PaintType, Tint}; use vello_common::peniko::color::palette::css::BLACK; @@ -383,7 +383,10 @@ impl RenderContext { self.temp_path.push(PathEl::ClosePath); } - /// Fill a blurred rectangle with the given corner radius and standard deviation. + /// Fill a blurred rectangle with the given corner radii and standard deviation. + /// + /// Each corner may have a different (scalar) radius; elliptical corner radii are not + /// supported. /// /// When `invert` is `true`, the inverse (`1 - alpha`) of the blur coverage is painted: the /// paint is fully opaque outside the blurred rectangle and fades to transparent inside it. This @@ -394,7 +397,7 @@ impl RenderContext { pub fn fill_blurred_rounded_rect( &mut self, rect: &Rect, - radius: f32, + radii: RoundedRectRadii, std_dev: f32, invert: bool, ) { @@ -408,7 +411,7 @@ impl RenderContext { let blurred_rect = BlurredRoundedRectangle { rect, color, - radius, + radii, std_dev, invert, }; diff --git a/sparse_strips/vello_example_scenes/src/blurred_rounded_rect.rs b/sparse_strips/vello_example_scenes/src/blurred_rounded_rect.rs index 937079470..efcf41499 100644 --- a/sparse_strips/vello_example_scenes/src/blurred_rounded_rect.rs +++ b/sparse_strips/vello_example_scenes/src/blurred_rounded_rect.rs @@ -3,7 +3,7 @@ //! Scene demonstrating blurred rounded rectangle rendering. -use vello_common::kurbo::{Affine, Rect}; +use vello_common::kurbo::{Affine, Rect, RoundedRectRadii}; use vello_common::peniko::color::palette; use vello_common::peniko::color::{AlphaColor, Srgb}; @@ -53,7 +53,7 @@ pub fn render(ctx: &mut impl RenderingContext, root_transform: Affine) { scene_transform * Affine::translate((300.0, 300.0)), rect, palette::css::BLUE, - 50.0, + 50.0.into(), 45.0, ); @@ -64,7 +64,7 @@ pub fn render(ctx: &mut impl RenderingContext, root_transform: Affine) { * Affine::skew(20_f64.to_radians().tan(), 0.0), rect, palette::css::BLACK, - 50.0, + 50.0.into(), 45.0, ); @@ -73,7 +73,7 @@ pub fn render(ctx: &mut impl RenderingContext, root_transform: Affine) { scene_transform, Rect::new(100.0, 800.0, 400.0, 1100.0), palette::css::BLACK, - 150.0, + 150.0.into(), 45.0, ); @@ -82,7 +82,17 @@ pub fn render(ctx: &mut impl RenderingContext, root_transform: Affine) { scene_transform, Rect::new(600.0, 800.0, 900.0, 900.0), palette::css::BLACK, - 150.0, + 150.0.into(), + 45.0, + ); + + // Non-uniform corner radii. + draw_blurred_rect( + ctx, + scene_transform * Affine::translate((900.0, 1000.0)), + rect, + palette::css::BLACK, + RoundedRectRadii::new(120.0, 0.0, 60.0, 20.0), 45.0, ); @@ -91,7 +101,7 @@ pub fn render(ctx: &mut impl RenderingContext, root_transform: Affine) { scene_transform * Affine::translate((600.0, 600.0)) * Affine::scale_non_uniform(2.2, 0.9), rect, palette::css::BLACK, - 50.0, + 50.0.into(), 30.0, ); } @@ -101,10 +111,10 @@ fn draw_blurred_rect( transform: Affine, rect: Rect, color: AlphaColor, - radius: f32, + radii: RoundedRectRadii, std_dev: f32, ) { ctx.set_transform(transform); ctx.set_paint(color); - ctx.fill_blurred_rounded_rect(&rect, radius, std_dev); + ctx.fill_blurred_rounded_rect(&rect, radii, std_dev); } diff --git a/sparse_strips/vello_example_scenes/src/lib.rs b/sparse_strips/vello_example_scenes/src/lib.rs index 0003d5204..f10f60812 100644 --- a/sparse_strips/vello_example_scenes/src/lib.rs +++ b/sparse_strips/vello_example_scenes/src/lib.rs @@ -23,7 +23,7 @@ pub mod text; use glifo::GlyphRunBackend; use vello_common::filter_effects::Filter; use vello_common::kurbo::Affine; -pub use vello_common::kurbo::{BezPath, Rect, Shape, Stroke}; +pub use vello_common::kurbo::{BezPath, Rect, RoundedRectRadii, Shape, Stroke}; pub use vello_common::mask::Mask; use vello_common::paint::ImageSource; pub use vello_common::paint::{Paint, PaintType}; @@ -81,7 +81,7 @@ pub trait RenderingContext: Sized { /// Fill a rectangle with the current paint. fn fill_rect(&mut self, rect: &Rect); /// Fill a blurred rounded rectangle with the current solid paint. - fn fill_blurred_rounded_rect(&mut self, rect: &Rect, radius: f32, std_dev: f32); + fn fill_blurred_rounded_rect(&mut self, rect: &Rect, radii: RoundedRectRadii, std_dev: f32); /// Create a glyph run builder for text rendering. fn glyph_run<'a>( &'a mut self, @@ -172,8 +172,8 @@ impl RenderingContext for RenderContext { self.fill_rect(rect); } - fn fill_blurred_rounded_rect(&mut self, rect: &Rect, radius: f32, std_dev: f32) { - self.fill_blurred_rounded_rect(rect, radius, std_dev, false); + fn fill_blurred_rounded_rect(&mut self, rect: &Rect, radii: RoundedRectRadii, std_dev: f32) { + self.fill_blurred_rounded_rect(rect, radii, std_dev, false); } fn glyph_run<'a>( @@ -277,8 +277,8 @@ impl RenderingContext for Scene { self.fill_rect(rect); } - fn fill_blurred_rounded_rect(&mut self, rect: &Rect, radius: f32, std_dev: f32) { - self.fill_blurred_rounded_rect(rect, radius, std_dev, false); + fn fill_blurred_rounded_rect(&mut self, rect: &Rect, radii: RoundedRectRadii, std_dev: f32) { + self.fill_blurred_rounded_rect(rect, radii, std_dev, false); } fn glyph_run<'a>( diff --git a/sparse_strips/vello_hybrid/CHANGELOG.md b/sparse_strips/vello_hybrid/CHANGELOG.md index 128db909d..52bb121b7 100644 --- a/sparse_strips/vello_hybrid/CHANGELOG.md +++ b/sparse_strips/vello_hybrid/CHANGELOG.md @@ -19,6 +19,7 @@ This release has an [MSRV][] of 1.88. ### Changed - Breaking change: `WebGlRenderer::render` and `WebGlRenderer::render_to_atlas` now take a `WebGlTextureBindings`, providing the external textures referenced by the scene. Pass `&WebGlTextureBindings::new()` if the scene does not use any. +- Breaking change: `Scene::fill_blurred_rounded_rect` now takes a `RoundedRectRadii`, allowing a different radius per corner. A uniform radius can be passed as `radius.into()`. ## [0.1.0][] - 2026-07-29 diff --git a/sparse_strips/vello_hybrid/src/render/common.rs b/sparse_strips/vello_hybrid/src/render/common.rs index 372382e3b..721da78d5 100644 --- a/sparse_strips/vello_hybrid/src/render/common.rs +++ b/sparse_strips/vello_hybrid/src/render/common.rs @@ -462,14 +462,14 @@ pub(crate) struct GpuBlurredRoundedRect { pub color: u32, /// Whether to paint the inverse (`1 - alpha`) of the blur coverage pub invert: u32, - /// Blur parameters: exponent, reciprocal exponent, scale, and inverse standard deviation. + /// Blur parameters: scale, inverse standard deviation, and minimum edge length (one unused). pub params0: [f32; 4], - /// Blur parameters: minimum edge length, adjusted width, adjusted height, and outer radius. + /// Blur parameters: adjusted width, adjusted height, width, and height. pub params1: [f32; 4], - /// Blur parameters [width, height]. - pub size: [f32; 2], - /// Padding for 16-byte alignment. - pub _padding1: [u32; 2], + /// Per-corner outer radii, in the order [top-left, top-right, bottom-left, bottom-right]. + pub r1: [f32; 4], + /// Per-corner exponents, in the order [top-left, top-right, bottom-left, bottom-right]. + pub exponent: [f32; 4], } /// GPU encoded linear gradient data. diff --git a/sparse_strips/vello_hybrid/src/render/webgl.rs b/sparse_strips/vello_hybrid/src/render/webgl.rs index fee372c3c..c3c1b9b11 100644 --- a/sparse_strips/vello_hybrid/src/render/webgl.rs +++ b/sparse_strips/vello_hybrid/src/render/webgl.rs @@ -896,15 +896,10 @@ impl WebGlRenderer { transform: rect.transform.as_coeffs().map(|x| x as f32), color: rect.color.as_premul_rgba8().to_u32(), invert: u32::from(rect.invert), - params0: [ - rect.exponent, - rect.recip_exponent, - rect.scale, - rect.std_dev_inv, - ], - params1: [rect.min_edge, rect.w, rect.h, rect.r1], - size: [rect.width, rect.height], - _padding1: [0, 0], + params0: [rect.scale, rect.std_dev_inv, rect.min_edge, 0.0], + params1: [rect.w, rect.h, rect.width, rect.height], + r1: rect.r1, + exponent: rect.exponent, }) } } diff --git a/sparse_strips/vello_hybrid/src/render/wgpu.rs b/sparse_strips/vello_hybrid/src/render/wgpu.rs index ea9ba9aa5..1acc5a0f2 100644 --- a/sparse_strips/vello_hybrid/src/render/wgpu.rs +++ b/sparse_strips/vello_hybrid/src/render/wgpu.rs @@ -859,15 +859,10 @@ impl Renderer { transform: rect.transform.as_coeffs().map(|x| x as f32), color: rect.color.as_premul_rgba8().to_u32(), invert: u32::from(rect.invert), - params0: [ - rect.exponent, - rect.recip_exponent, - rect.scale, - rect.std_dev_inv, - ], - params1: [rect.min_edge, rect.w, rect.h, rect.r1], - size: [rect.width, rect.height], - _padding1: [0, 0], + params0: [rect.scale, rect.std_dev_inv, rect.min_edge, 0.0], + params1: [rect.w, rect.h, rect.width, rect.height], + r1: rect.r1, + exponent: rect.exponent, }) } } diff --git a/sparse_strips/vello_hybrid/src/scene.rs b/sparse_strips/vello_hybrid/src/scene.rs index addf7f734..6cb2b5614 100644 --- a/sparse_strips/vello_hybrid/src/scene.rs +++ b/sparse_strips/vello_hybrid/src/scene.rs @@ -20,7 +20,7 @@ use vello_common::fearless_simd::Level; use vello_common::filter::FilterData; use vello_common::filter_effects::Filter; use vello_common::geometry::{RectU16, SizeU16}; -use vello_common::kurbo::{Affine, BezPath, Rect, Shape, Stroke}; +use vello_common::kurbo::{Affine, BezPath, Rect, RoundedRectRadii, Shape, Stroke}; use vello_common::mask::Mask; use vello_common::multi_atlas::AtlasConfig; use vello_common::paint::{Paint, PaintType, Tint}; @@ -630,7 +630,10 @@ impl Scene { self.stroke_path(&rect.to_path(DEFAULT_TOLERANCE)); } - /// Fill a blurred rectangle with the given corner radius and standard deviation. + /// Fill a blurred rectangle with the given corner radii and standard deviation. + /// + /// Each corner may have a different (scalar) radius; elliptical corner radii are not + /// supported. /// /// When `invert` is `true`, the inverse (`1 - alpha`) of the blur coverage is painted: the /// paint is fully opaque outside the blurred rectangle and fades to transparent inside it. This @@ -641,7 +644,7 @@ impl Scene { pub fn fill_blurred_rounded_rect( &mut self, rect: &Rect, - radius: f32, + radii: RoundedRectRadii, std_dev: f32, invert: bool, ) { @@ -658,7 +661,7 @@ impl Scene { let blurred_rect = BlurredRoundedRectangle { rect, color, - radius, + radii, std_dev, invert, }; diff --git a/sparse_strips/vello_sparse_shaders/shaders/render.wesl b/sparse_strips/vello_sparse_shaders/shaders/render.wesl index bc3879956..0b06709d7 100644 --- a/sparse_strips/vello_sparse_shaders/shaders/render.wesl +++ b/sparse_strips/vello_sparse_shaders/shaders/render.wesl @@ -520,6 +520,7 @@ fn fs_main( let blurred_texel2 = load_encoded_paint_texel(paint_tex_idx, 2u); let blurred_texel3 = load_encoded_paint_texel(paint_tex_idx, 3u); let blurred_texel4 = load_encoded_paint_texel(paint_tex_idx, 4u); + let blurred_texel5 = load_encoded_paint_texel(paint_tex_idx, 5u); final_color = alpha * calculate_blurred_rounded_rect( sample_xy, blurred_texel0, @@ -527,6 +528,7 @@ fn fs_main( blurred_texel2, blurred_texel3, blurred_texel4, + blurred_texel5, ); } } else if color_source == COLOR_SOURCE_LAYER { @@ -982,25 +984,25 @@ fn get_blurred_rounded_rect_color(texel1: vec4) -> vec4 { return unpac /// Whether to paint the inverse (`1 - alpha`) of the blur coverage. fn get_blurred_rounded_rect_invert(texel1: vec4) -> u32 { return texel1.w; } -fn get_blurred_rounded_rect_exponent(texel2: vec4) -> f32 { return bitcast(texel2.x); } +fn get_blurred_rounded_rect_scale(texel2: vec4) -> f32 { return bitcast(texel2.x); } -fn get_blurred_rounded_rect_recip_exponent(texel2: vec4) -> f32 { return bitcast(texel2.y); } +fn get_blurred_rounded_rect_std_dev_inv(texel2: vec4) -> f32 { return bitcast(texel2.y); } -fn get_blurred_rounded_rect_scale(texel2: vec4) -> f32 { return bitcast(texel2.z); } +fn get_blurred_rounded_rect_min_edge(texel2: vec4) -> f32 { return bitcast(texel2.z); } -fn get_blurred_rounded_rect_std_dev_inv(texel2: vec4) -> f32 { return bitcast(texel2.w); } +fn get_blurred_rounded_rect_w(texel3: vec4) -> f32 { return bitcast(texel3.x); } -fn get_blurred_rounded_rect_min_edge(texel3: vec4) -> f32 { return bitcast(texel3.x); } +fn get_blurred_rounded_rect_h(texel3: vec4) -> f32 { return bitcast(texel3.y); } -fn get_blurred_rounded_rect_w(texel3: vec4) -> f32 { return bitcast(texel3.y); } +fn get_blurred_rounded_rect_width(texel3: vec4) -> f32 { return bitcast(texel3.z); } -fn get_blurred_rounded_rect_h(texel3: vec4) -> f32 { return bitcast(texel3.z); } +fn get_blurred_rounded_rect_height(texel3: vec4) -> f32 { return bitcast(texel3.w); } -fn get_blurred_rounded_rect_r1(texel3: vec4) -> f32 { return bitcast(texel3.w); } +/// Per-corner outer radii, in the order [top-left, top-right, bottom-left, bottom-right]. +fn get_blurred_rounded_rect_r1(texel4: vec4) -> vec4 { return bitcast>(texel4); } -fn get_blurred_rounded_rect_width(texel4: vec4) -> f32 { return bitcast(texel4.x); } - -fn get_blurred_rounded_rect_height(texel4: vec4) -> f32 { return bitcast(texel4.y); } +/// Per-corner exponents, in the order [top-left, top-right, bottom-left, bottom-right]. +fn get_blurred_rounded_rect_exponent(texel5: vec4) -> vec4 { return bitcast>(texel5); } // Calculate a radial gradient; matches vello_cpu implementation. fn calculate_radial_gradient( @@ -1113,29 +1115,37 @@ fn calculate_blurred_rounded_rect( texel2: vec4, texel3: vec4, texel4: vec4, + texel5: vec4, ) -> vec4 { let transform = get_blurred_rounded_rect_transform(texel0); let translate = get_blurred_rounded_rect_translate(texel1); let color = get_blurred_rounded_rect_color(texel1); let invert = get_blurred_rounded_rect_invert(texel1); - let exponent = get_blurred_rounded_rect_exponent(texel2); - let recip_exponent = get_blurred_rounded_rect_recip_exponent(texel2); let scale = get_blurred_rounded_rect_scale(texel2); let std_dev_inv = get_blurred_rounded_rect_std_dev_inv(texel2); - let min_edge = get_blurred_rounded_rect_min_edge(texel3); + let min_edge = get_blurred_rounded_rect_min_edge(texel2); let w = get_blurred_rounded_rect_w(texel3); let h = get_blurred_rounded_rect_h(texel3); - let r1 = get_blurred_rounded_rect_r1(texel3); - let width = get_blurred_rounded_rect_width(texel4); - let height = get_blurred_rounded_rect_height(texel4); + let width = get_blurred_rounded_rect_width(texel3); + let height = get_blurred_rounded_rect_height(texel3); + let corner_r1 = get_blurred_rounded_rect_r1(texel4); + let corner_exponent = get_blurred_rounded_rect_exponent(texel5); let local_xy = transform * fragment_pos + translate; // The 0.5 and 0.0 constants correspond to vello_cpu's v1 and v0 respectively. let y = local_xy.y - 0.5 * height; + let x = local_xy.x - 0.5 * width; + + // Select the parameters of the corner in whose quadrant the fragment lies. + // Per-corner values are stored as [top-left, top-right, bottom-left, bottom-right]. + let corner = select(0u, 1u, x >= 0.0) + select(0u, 2u, y >= 0.0); + let r1 = corner_r1[corner]; + let exponent = corner_exponent[corner]; + let recip_exponent = 1.0 / exponent; + let y0 = r1 + abs(y) - 0.5 * h; let y1 = max(y0, 0.0); - let x = local_xy.x - 0.5 * width; let x0 = r1 + abs(x) - 0.5 * w; let x1 = max(x0, 0.0); diff --git a/sparse_strips/vello_sparse_tests/snapshots/blurred_rounded_rect_non_uniform_radii.png b/sparse_strips/vello_sparse_tests/snapshots/blurred_rounded_rect_non_uniform_radii.png new file mode 100644 index 000000000..bd8dac750 --- /dev/null +++ b/sparse_strips/vello_sparse_tests/snapshots/blurred_rounded_rect_non_uniform_radii.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28d9adb3f5e11ff5cd123dad09e180ff0b3b2770ddaee0178e80c61d11abf6b4 +size 1454 diff --git a/sparse_strips/vello_sparse_tests/snapshots/blurred_rounded_rect_non_uniform_radii_large_std_dev.png b/sparse_strips/vello_sparse_tests/snapshots/blurred_rounded_rect_non_uniform_radii_large_std_dev.png new file mode 100644 index 000000000..121f2ceea --- /dev/null +++ b/sparse_strips/vello_sparse_tests/snapshots/blurred_rounded_rect_non_uniform_radii_large_std_dev.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9b23634dcf5d64eaed57ff8caf3d86c76c6dec74952228b4f1f5195f49f9828 +size 3717 diff --git a/sparse_strips/vello_sparse_tests/snapshots/blurred_rounded_rect_non_uniform_radii_with_transform.png b/sparse_strips/vello_sparse_tests/snapshots/blurred_rounded_rect_non_uniform_radii_with_transform.png new file mode 100644 index 000000000..a520ae5e2 --- /dev/null +++ b/sparse_strips/vello_sparse_tests/snapshots/blurred_rounded_rect_non_uniform_radii_with_transform.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a44240a9cd63886490556751f4ea522e28cceadcc8852631438a6750dc07ad42 +size 1744 diff --git a/sparse_strips/vello_sparse_tests/snapshots/inverse_blurred_rounded_rect_non_uniform_radii.png b/sparse_strips/vello_sparse_tests/snapshots/inverse_blurred_rounded_rect_non_uniform_radii.png new file mode 100644 index 000000000..5b43060a6 --- /dev/null +++ b/sparse_strips/vello_sparse_tests/snapshots/inverse_blurred_rounded_rect_non_uniform_radii.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c7ad664a6657db22a415b000430f8981a8865d24a3a997dfa91633360e206694 +size 1502 diff --git a/sparse_strips/vello_sparse_tests/tests/basic.rs b/sparse_strips/vello_sparse_tests/tests/basic.rs index f6a8f37cb..e57cd3127 100644 --- a/sparse_strips/vello_sparse_tests/tests/basic.rs +++ b/sparse_strips/vello_sparse_tests/tests/basic.rs @@ -354,7 +354,7 @@ fn blurred_rounded_rect_inverted(ctx: &mut impl Renderer) { let rect = Rect::new(80.0, 80.0, 20.0, 20.0); ctx.set_paint(REBECCA_PURPLE); - ctx.fill_blurred_rounded_rect(&rect, 10.0, 2.0, false); + ctx.fill_blurred_rounded_rect(&rect, 10.0.into(), 2.0, false); } #[vello_test(width = 30, height = 30)] diff --git a/sparse_strips/vello_sparse_tests/tests/blurred_rounded_rect.rs b/sparse_strips/vello_sparse_tests/tests/blurred_rounded_rect.rs index 68b08b2bc..d28491b85 100644 --- a/sparse_strips/vello_sparse_tests/tests/blurred_rounded_rect.rs +++ b/sparse_strips/vello_sparse_tests/tests/blurred_rounded_rect.rs @@ -3,14 +3,18 @@ use crate::renderer::Renderer; use vello_common::color::palette::css::REBECCA_PURPLE; -use vello_common::kurbo::{Affine, Point, Rect}; +use vello_common::kurbo::{Affine, Point, Rect, RoundedRectRadii}; use vello_dev_macros::vello_test; fn rect_with(ctx: &mut impl Renderer, radius: f32, std_dev: f32, affine: Affine) { + rect_with_radii(ctx, f64::from(radius).into(), std_dev, affine); +} + +fn rect_with_radii(ctx: &mut impl Renderer, radii: RoundedRectRadii, std_dev: f32, affine: Affine) { let rect = Rect::new(20.0, 20.0, 80.0, 80.0); ctx.set_paint(REBECCA_PURPLE); ctx.set_transform(affine); - ctx.fill_blurred_rounded_rect(&rect, radius, std_dev, false); + ctx.fill_blurred_rounded_rect(&rect, radii, std_dev, false); } #[vello_test] @@ -63,11 +67,41 @@ fn blurred_rounded_rect_with_transform(ctx: &mut impl Renderer) { ); } +#[vello_test] +fn blurred_rounded_rect_non_uniform_radii(ctx: &mut impl Renderer) { + rect_with_radii( + ctx, + RoundedRectRadii::new(20.0, 0.0, 10.0, 5.0), + 3.0, + Affine::IDENTITY, + ); +} + +#[vello_test] +fn blurred_rounded_rect_non_uniform_radii_large_std_dev(ctx: &mut impl Renderer) { + rect_with_radii( + ctx, + RoundedRectRadii::new(30.0, 0.0, 15.0, 5.0), + 10.0, + Affine::IDENTITY, + ); +} + +#[vello_test] +fn blurred_rounded_rect_non_uniform_radii_with_transform(ctx: &mut impl Renderer) { + rect_with_radii( + ctx, + RoundedRectRadii::new(20.0, 0.0, 10.0, 5.0), + 3.0, + Affine::rotate_about(45.0_f64.to_radians(), Point::new(50.0, 50.0)), + ); +} + fn inverse_rect_with(ctx: &mut impl Renderer, radius: f32, std_dev: f32, affine: Affine) { let rect = Rect::new(20.0, 20.0, 80.0, 80.0); ctx.set_paint(REBECCA_PURPLE); ctx.set_transform(affine); - ctx.fill_blurred_rounded_rect(&rect, radius, std_dev, true); + ctx.fill_blurred_rounded_rect(&rect, f64::from(radius).into(), std_dev, true); } #[vello_test] @@ -104,3 +138,16 @@ fn inverse_blurred_rounded_rect_with_transform(ctx: &mut impl Renderer) { Affine::rotate_about(45.0_f64.to_radians(), Point::new(50.0, 50.0)), ); } + +#[vello_test] +fn inverse_blurred_rounded_rect_non_uniform_radii(ctx: &mut impl Renderer) { + let rect = Rect::new(20.0, 20.0, 80.0, 80.0); + ctx.set_paint(REBECCA_PURPLE); + ctx.set_transform(Affine::IDENTITY); + ctx.fill_blurred_rounded_rect( + &rect, + RoundedRectRadii::new(20.0, 0.0, 10.0, 5.0), + 3.0, + true, + ); +} diff --git a/sparse_strips/vello_sparse_tests/tests/renderer.rs b/sparse_strips/vello_sparse_tests/tests/renderer.rs index f2f44ca91..c2fbb4350 100644 --- a/sparse_strips/vello_sparse_tests/tests/renderer.rs +++ b/sparse_strips/vello_sparse_tests/tests/renderer.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use glifo::GlyphRunBackend; use vello_common::filter_effects::Filter; -use vello_common::kurbo::{Affine, BezPath, Rect, Stroke}; +use vello_common::kurbo::{Affine, BezPath, Rect, RoundedRectRadii, Stroke}; use vello_common::mask::Mask; use vello_common::paint::{ImageId, ImageSource, PaintType, Tint}; use vello_common::peniko::{BlendMode, Fill, FontData, ImageQuality}; @@ -35,7 +35,13 @@ pub(crate) trait Renderer: Sized { fn fill_path(&mut self, path: &BezPath); fn stroke_path(&mut self, path: &BezPath); fn fill_rect(&mut self, rect: &Rect); - fn fill_blurred_rounded_rect(&mut self, rect: &Rect, radius: f32, std_dev: f32, invert: bool); + fn fill_blurred_rounded_rect( + &mut self, + rect: &Rect, + radii: RoundedRectRadii, + std_dev: f32, + invert: bool, + ); fn stroke_rect(&mut self, rect: &Rect); fn glyph_run( &mut self, @@ -120,9 +126,15 @@ impl Renderer for CpuRenderer { self.ctx.fill_rect(rect); } - fn fill_blurred_rounded_rect(&mut self, rect: &Rect, radius: f32, std_dev: f32, invert: bool) { + fn fill_blurred_rounded_rect( + &mut self, + rect: &Rect, + radii: RoundedRectRadii, + std_dev: f32, + invert: bool, + ) { self.ctx - .fill_blurred_rounded_rect(rect, radius, std_dev, invert); + .fill_blurred_rounded_rect(rect, radii, std_dev, invert); } fn stroke_rect(&mut self, rect: &Rect) { @@ -400,9 +412,15 @@ impl Renderer for HybridRenderer { self.scene.fill_rect(rect); } - fn fill_blurred_rounded_rect(&mut self, rect: &Rect, radius: f32, std_dev: f32, invert: bool) { + fn fill_blurred_rounded_rect( + &mut self, + rect: &Rect, + radii: RoundedRectRadii, + std_dev: f32, + invert: bool, + ) { self.scene - .fill_blurred_rounded_rect(rect, radius, std_dev, invert); + .fill_blurred_rounded_rect(rect, radii, std_dev, invert); } fn stroke_rect(&mut self, rect: &Rect) { @@ -773,9 +791,15 @@ impl Renderer for HybridRenderer { self.scene.fill_rect(rect); } - fn fill_blurred_rounded_rect(&mut self, rect: &Rect, radius: f32, std_dev: f32, invert: bool) { + fn fill_blurred_rounded_rect( + &mut self, + rect: &Rect, + radii: RoundedRectRadii, + std_dev: f32, + invert: bool, + ) { self.scene - .fill_blurred_rounded_rect(rect, radius, std_dev, invert); + .fill_blurred_rounded_rect(rect, radii, std_dev, invert); } fn stroke_rect(&mut self, rect: &Rect) {