From 0ce6f5689b281b51ac986934538e49e9f546657e Mon Sep 17 00:00:00 2001 From: Laurenz Stampfl Date: Thu, 8 Oct 2026 11:15:27 +0200 Subject: [PATCH 1/8] vello_gpu: Replace `image_bicubic` with a `extended_images` feature --- vello_gpu/Cargo.toml | 4 +- vello_gpu/README.md | 7 ++- vello_gpu/src/lib.rs | 7 ++- vello_gpu/src/paint.rs | 20 +++++++- vello_gpu/src/render/common.rs | 6 +-- vello_gpu/src/render/webgl/mod.rs | 5 ++ vello_gpu/src/render/wgpu/mod.rs | 5 ++ vello_gpu/src/scene.rs | 17 ++++--- vello_gpu/src/text.rs | 5 ++ vello_gpu_shaders/Cargo.toml | 4 +- vello_gpu_shaders/README.md | 2 +- vello_gpu_shaders/build.rs | 2 +- .../shaders/helpers/external_texture.wesl | 12 +++-- vello_gpu_shaders/shaders/helpers/image.wesl | 17 +++++-- vello_gpu_shaders/shaders/render.wesl | 10 ++-- vello_gpu_shaders/src/lib.rs | 2 +- vello_tests/feature_tests/Cargo.toml | 2 +- vello_tests/feature_tests/build.rs | 2 +- vello_tests/feature_tests/run.sh | 2 +- .../feature_tests/tests/support/mod.rs | 2 +- .../feature_tests/tests/support/webgl.rs | 48 +++++++++++++++++-- .../feature_tests/tests/support/wgpu.rs | 46 ++++++++++++++++-- ...> feature_tests_probe_extended_images.png} | 0 23 files changed, 183 insertions(+), 44 deletions(-) rename vello_tests/snapshots/{feature_tests_probe_image_bicubic.png => feature_tests_probe_extended_images.png} (100%) diff --git a/vello_gpu/Cargo.toml b/vello_gpu/Cargo.toml index 09e50d66ee..3971239c65 100644 --- a/vello_gpu/Cargo.toml +++ b/vello_gpu/Cargo.toml @@ -51,9 +51,9 @@ roxmltree = "0.20.0" [features] default = ["wgpu", "wgpu_default", "text", "all_shader_features"] # Enable all optional shader features. -all_shader_features = ["blurred_rounded_rect", "image_bicubic", "gradient_sweep"] +all_shader_features = ["blurred_rounded_rect", "extended_images", "gradient_sweep"] blurred_rounded_rect = ["vello_gpu_shaders?/blurred_rounded_rect"] -image_bicubic = ["vello_gpu_shaders?/image_bicubic"] +extended_images = ["vello_gpu_shaders?/extended_images"] gradient_sweep = ["vello_gpu_shaders?/gradient_sweep"] std = ["vello_common/std", "glifo?/std"] libm = ["vello_common/libm", "glifo?/libm"] diff --git a/vello_gpu/README.md b/vello_gpu/README.md index 9b0dd27e86..9cda87dfb9 100644 --- a/vello_gpu/README.md +++ b/vello_gpu/README.md @@ -50,8 +50,11 @@ preprocessing and GPU rendering: features. - `blurred_rounded_rect` (enabled by default): Enables rendering of blurred rounded rectangles. -- `image_bicubic` (enabled by default): Enables rendering of images with - `ImageQuality::High` +- `extended_images` (enabled by default): If this feature is disabled, only + external textures using `TextureRegion::Full` with nearest or bilinear + sampling and opacity are supported. Enabling this feature also adds support + for uploaded images, glyph atlas caching, bicubic sampling, cropped external + textures, and colored or alpha-mask tints. - `gradient_sweep` (enabled by default): Enables rendering of sweep gradients - `webgl`: Enables the WebGL rendering backend for browser support, using GLSL shaders for compatibility. diff --git a/vello_gpu/src/lib.rs b/vello_gpu/src/lib.rs index 85f74b64f7..bfe9e7510c 100644 --- a/vello_gpu/src/lib.rs +++ b/vello_gpu/src/lib.rs @@ -31,8 +31,11 @@ //! features. //! - `blurred_rounded_rect` (enabled by default): Enables rendering of blurred //! rounded rectangles. -//! - `image_bicubic` (enabled by default): Enables rendering of images with -//! `ImageQuality::High` +//! - `extended_images` (enabled by default): If this feature is disabled, only +//! external textures using `TextureRegion::Full` with nearest or bilinear +//! sampling and opacity are supported. Enabling this feature also adds support +//! for uploaded images, glyph atlas caching, bicubic sampling, cropped external +//! textures, and colored or alpha-mask tints. //! - `gradient_sweep` (enabled by default): Enables rendering of sweep gradients //! - `webgl`: Enables the WebGL rendering backend for browser support, using GLSL //! shaders for compatibility. diff --git a/vello_gpu/src/paint.rs b/vello_gpu/src/paint.rs index 4351d2adb8..9e4e8fd54d 100644 --- a/vello_gpu/src/paint.rs +++ b/vello_gpu/src/paint.rs @@ -9,7 +9,7 @@ use vello_common::TextureId; use vello_common::encode::{EncodedKind, EncodedPaint}; use vello_common::image_cache::ImageCache; use vello_common::multi_atlas::AtlasId; -use vello_common::paint::{ImageSource, Paint}; +use vello_common::paint::{ImageSource, Paint, TextureRegion, Tint, TintMode}; use vello_common::peniko::{Extend, ImageQuality, ImageSampler}; const COLOR_SOURCE_PAYLOAD: u32 = 0; @@ -29,6 +29,24 @@ const PAINT_TYPE_SHIFT: u32 = 26; pub(crate) const EXTERNAL_TEXTURE_SLOT_SHIFT: u32 = 24; const PAINT_TEXTURE_INDEX_MASK: u32 = (1 << EXTERNAL_TEXTURE_SLOT_SHIFT) - 1; +/// Whether an image can use hardware sampling without the manual image shader. +pub(crate) fn supports_native_image( + source: &ImageSource, + quality: ImageQuality, + tint: Option, +) -> bool { + matches!( + source, + ImageSource::ExternalTexture { + source_region: TextureRegion::Full, + .. + } + ) && quality != ImageQuality::High + && tint.is_none_or(|tint| { + tint.mode == TintMode::Multiply && tint.color.components[..3] == [1.0; 3] + }) +} + /// Texture sampled by an image paint. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub(crate) enum TextureSourceId { diff --git a/vello_gpu/src/render/common.rs b/vello_gpu/src/render/common.rs index 4febdd031a..e58164c566 100644 --- a/vello_gpu/src/render/common.rs +++ b/vello_gpu/src/render/common.rs @@ -20,7 +20,6 @@ use vello_common::color::{AlphaColor, Srgb}; use vello_common::encode::EncodedImage; use vello_common::geometry::{RectU16, SizeU16}; use vello_common::paint::{ImageSource, TextureRegion, TintMode}; -use vello_common::peniko::ImageQuality; use vello_common::record::CommandRecorder; // TODO: If we want to use native bilinear sampling for uploaded images, @@ -400,10 +399,7 @@ pub(crate) fn encode_native_image_paint( return None; } - if image.tint.is_some_and(|tint| { - tint.mode != TintMode::Multiply || tint.color.components[..3] != [1.0; 3] - }) || matches!(image.sampler.quality, ImageQuality::High) - { + if !crate::paint::supports_native_image(&image.source, image.sampler.quality, image.tint) { return None; } diff --git a/vello_gpu/src/render/webgl/mod.rs b/vello_gpu/src/render/webgl/mod.rs index e6bb956f8c..7c4bfc1393 100644 --- a/vello_gpu/src/render/webgl/mod.rs +++ b/vello_gpu/src/render/webgl/mod.rs @@ -773,11 +773,16 @@ impl WebGlRenderer { /// /// This is the WebGL analogue of the wgpu Renderer's `upload_image` method. /// It allocates space in the image cache and uploads the image data to the atlas texture. + #[track_caller] pub fn upload_image( &mut self, resources: &mut Resources, writer: &T, ) -> Result { + if !cfg!(feature = "extended_images") { + panic!("uploaded images require the `extended_images` feature"); + } + self.upload_image_with(&mut resources.image_cache, writer, IMAGE_PADDING) } diff --git a/vello_gpu/src/render/wgpu/mod.rs b/vello_gpu/src/render/wgpu/mod.rs index 74c24b5535..7a193ef28c 100644 --- a/vello_gpu/src/render/wgpu/mod.rs +++ b/vello_gpu/src/render/wgpu/mod.rs @@ -520,6 +520,7 @@ impl Renderer { /// 1. Reserves space in the image cache /// 2. Writes the image data directly to the atlas /// 3. Returns the `ImageId` for use in rendering + #[track_caller] pub fn upload_image( &mut self, resources: &mut Resources, @@ -528,6 +529,10 @@ impl Renderer { encoder: &mut CommandEncoder, writer: &T, ) -> vello_common::paint::ImageId { + if !cfg!(feature = "extended_images") { + panic!("uploaded images require the `extended_images` feature"); + } + self.upload_image_with( &mut resources.image_cache, device, diff --git a/vello_gpu/src/scene.rs b/vello_gpu/src/scene.rs index 613a62618f..ae1a1bd1b8 100644 --- a/vello_gpu/src/scene.rs +++ b/vello_gpu/src/scene.rs @@ -25,7 +25,7 @@ use vello_common::paint::{Paint, PaintType, PremulColor, Tint}; #[cfg(feature = "text")] use vello_common::peniko::FontData; use vello_common::peniko::color::palette::css::BLACK; -use vello_common::peniko::{BlendMode, Fill, GradientKind, ImageQuality}; +use vello_common::peniko::{BlendMode, Fill, GradientKind}; use vello_common::record::{CommandRecorder, Drawable, LayerClip, LayerProps, PoppedLayer}; use vello_common::render_state::RenderState; use vello_common::strip::Strip; @@ -815,12 +815,16 @@ impl Scene { #[track_caller] pub fn set_paint(&mut self, paint: impl Into) { let paint = paint.into(); - assert_paint_supported(&paint); + assert_paint_supported(&paint, self.render_state.tint); + self.render_state.paint = paint; } /// Set the tint for subsequent image paint operations. + #[track_caller] pub fn set_tint(&mut self, tint: Option) { + assert_paint_supported(&self.render_state.paint, tint); + self.render_state.tint = tint; } @@ -937,7 +941,7 @@ impl Scene { /// Restore rendering state. #[track_caller] pub fn restore_state(&mut self, state: RenderState) { - assert_paint_supported(&state.paint); + assert_paint_supported(&state.paint, state.tint); self.render_state = state; } @@ -987,7 +991,7 @@ impl vello_common::probe::ProbeRenderer for Scene { } #[track_caller] -fn assert_paint_supported(paint: &PaintType) { +fn assert_paint_supported(paint: &PaintType, tint: Option) { match paint { PaintType::Solid(_) => {} PaintType::Gradient(gradient) => assert!( @@ -995,8 +999,9 @@ fn assert_paint_supported(paint: &PaintType) { "sweep gradients require the `gradient_sweep` feature" ), PaintType::Image(image) => assert!( - cfg!(feature = "image_bicubic") || image.sampler.quality != ImageQuality::High, - "bicubic image sampling requires the `image_bicubic` feature" + cfg!(feature = "extended_images") + || crate::paint::supports_native_image(&image.image, image.sampler.quality, tint), + "extended image sampling requires the `extended_images` feature" ), } } diff --git a/vello_gpu/src/text.rs b/vello_gpu/src/text.rs index de6f1d1d28..139ade98d5 100644 --- a/vello_gpu/src/text.rs +++ b/vello_gpu/src/text.rs @@ -258,6 +258,11 @@ impl<'a> GpuGlyphRunBackend<'a> { impl<'a> GlyphRunBackend<'a> for GpuGlyphRunBackend<'a> { fn atlas_cache(mut self, enabled: bool) -> Self { + assert!( + !enabled || cfg!(feature = "extended_images"), + "glyph atlas caching requires the `extended_images` feature" + ); + self.atlas_cache_enabled = enabled; self } diff --git a/vello_gpu_shaders/Cargo.toml b/vello_gpu_shaders/Cargo.toml index ce72b657f6..53aeeadff5 100644 --- a/vello_gpu_shaders/Cargo.toml +++ b/vello_gpu_shaders/Cargo.toml @@ -34,9 +34,9 @@ naga = { workspace = true, features = ["wgsl-in", "wgsl-out", "glsl-out"] } wesl = "=0.4.0" [features] -default = ["blurred_rounded_rect", "image_bicubic", "gradient_sweep"] +default = ["blurred_rounded_rect", "extended_images", "gradient_sweep"] blurred_rounded_rect = [] -image_bicubic = [] +extended_images = [] gradient_sweep = [] glsl = ["dep:naga"] unminified = [] diff --git a/vello_gpu_shaders/README.md b/vello_gpu_shaders/README.md index 70c0c6b306..aedac8665a 100644 --- a/vello_gpu_shaders/README.md +++ b/vello_gpu_shaders/README.md @@ -18,7 +18,7 @@ generated GLSL shader programs, used by the Vello GPU renderer. [naga](https://github.com/gfx-rs/wgpu/tree/trunk/naga) to minify the resulting WGSL. - Optional generation of minified GLSL and reflection metadata for WebGL. -- The `blurred_rounded_rect`, `image_bicubic`, and `gradient_sweep` features are +- The `blurred_rounded_rect`, `extended_images`, and `gradient_sweep` features are enabled by default and include their respective shader paths. ## Usage diff --git a/vello_gpu_shaders/build.rs b/vello_gpu_shaders/build.rs index 4e10937748..d0736a902d 100644 --- a/vello_gpu_shaders/build.rs +++ b/vello_gpu_shaders/build.rs @@ -76,7 +76,7 @@ fn load_shader_infos(shader_dir: &Path) -> Vec { "blurred_rounded_rect", cfg!(feature = "blurred_rounded_rect"), ); - compiler.set_feature("image_bicubic", cfg!(feature = "image_bicubic")); + compiler.set_feature("extended_images", cfg!(feature = "extended_images")); compiler.set_feature("gradient_sweep", cfg!(feature = "gradient_sweep")); shader_names diff --git a/vello_gpu_shaders/shaders/helpers/external_texture.wesl b/vello_gpu_shaders/shaders/helpers/external_texture.wesl index c51061c33e..e0eb603256 100644 --- a/vello_gpu_shaders/shaders/helpers/external_texture.wesl +++ b/vello_gpu_shaders/shaders/helpers/external_texture.wesl @@ -3,7 +3,9 @@ //! Sampling helpers for externally bound image textures. +@if(extended_images) import package::helpers::extend::{EXTEND_PAD, extend_mode}; +@if(extended_images) import package::helpers::image::{ IMAGE_QUALITY_HIGH, IMAGE_QUALITY_MEDIUM, @@ -13,8 +15,9 @@ import package::helpers::image::{ get_image_quality, get_image_size, }; -@if(image_bicubic) +@if(extended_images) import package::helpers::image::cubic_weights; +@if(extended_images) import package::helpers::paint::{PAINT_TEXTURE_INDEX_MASK, load_encoded_paint_texel}; fn sample_native_image_paint( @@ -27,6 +30,7 @@ fn sample_native_image_paint( return image_opacity * textureSampleLevel(external_texture, external_sampler, sample_xy, 0.0); } +@if(extended_images) fn sample_image_paint( encoded_paints_texture: texture_2d, tex_width_bits: u32, @@ -64,11 +68,13 @@ fn sample_image_paint( ); } +@if(extended_images) fn external_axis_index(coord: f32, extend_mode_id: u32, size: f32) -> i32 { return i32(min(extend_mode(coord, extend_mode_id, size), size - 1.0)); } // Bilinear sampling with each tap extended within the source region. +@if(extended_images) fn external_bilinear_sample( external_texture: texture_2d, coords: vec2, @@ -110,7 +116,7 @@ fn external_bilinear_sample( } // Bicubic sampling with each tap extended within the source region. -@if(image_bicubic) +@if(extended_images) fn external_bicubic_sample( external_texture: texture_2d, coords: vec2, @@ -183,6 +189,7 @@ fn external_bicubic_sample( return vec4(clamp(result.rgb, vec3(0.0), vec3(a)), a); } +@if(extended_images) fn sample_external_image( external_texture: texture_2d, quality: u32, @@ -191,7 +198,6 @@ fn sample_external_image( image_size: vec2, image_extend_modes: vec2, ) -> vec4 { - @if(image_bicubic) if quality == IMAGE_QUALITY_HIGH { return external_bicubic_sample( external_texture, diff --git a/vello_gpu_shaders/shaders/helpers/image.wesl b/vello_gpu_shaders/shaders/helpers/image.wesl index d6e216556b..7c4a17ff60 100644 --- a/vello_gpu_shaders/shaders/helpers/image.wesl +++ b/vello_gpu_shaders/shaders/helpers/image.wesl @@ -4,12 +4,17 @@ //! Shared encoded image accessors and cubic filter weights. /// Image rendering quality. +@if(extended_images) const IMAGE_QUALITY_LOW: u32 = 0u; +@if(extended_images) const IMAGE_QUALITY_MEDIUM: u32 = 1u; +@if(extended_images) const IMAGE_QUALITY_HIGH: u32 = 2u; /// Tint mode constants. +@if(extended_images) const TINT_MODE_ALPHA_MASK: u32 = 0u; +@if(extended_images) const TINT_MODE_MULTIPLY: u32 = 1u; // Encoded image layout. Must match `GpuEncodedImage` in `vello_gpu/src/render/common.rs`. @@ -33,24 +38,29 @@ const TINT_MODE_MULTIPLY: u32 = 1u; // texel1.w: padding /// The rendering quality of the image. +@if(extended_images) fn get_image_quality(texel0: vec4) -> u32 { return texel0.x & 0x3u; } /// The extend modes in the horizontal and vertical direction. +@if(extended_images) fn get_image_extend_modes(texel0: vec4) -> vec2 { return vec2((texel0.x >> 2u) & 0x3u, (texel0.x >> 4u) & 0x3u); } /// The size of the image in pixels. +@if(extended_images) fn get_image_size(texel0: vec4) -> vec2 { return vec2(f32(texel0.y >> 16u), f32(texel0.y & 0xFFFFu)); } /// The offset of the image in pixels. +@if(extended_images) fn get_image_offset(texel0: vec4) -> vec2 { return vec2(f32(texel0.z >> 16u), f32(texel0.z & 0xFFFFu)); } /// 2x2 linear part of the affine transform (columns [a,b] and [c,d]). +@if(extended_images) fn get_image_transform(texel0: vec4, texel1: vec4) -> mat2x2 { return mat2x2( vec2(bitcast(texel0.w), bitcast(texel1.x)), @@ -59,6 +69,7 @@ fn get_image_transform(texel0: vec4, texel1: vec4) -> mat2x2 { } /// Translation part of the affine transform [tx, ty]. +@if(extended_images) fn get_image_translate(texel1: vec4, texel2: vec4) -> vec2 { return vec2(bitcast(texel1.w), bitcast(texel2.x)); } @@ -83,7 +94,7 @@ fn get_native_image_opacity(texel1: vec4) -> u32 { // Cubic resampler logic borrowed from Skia (same as CPU cubic_resampler function) // Mitchell-Netravali cubic filter coefficients with parameters B=1/3 and C=1/3 -@if(image_bicubic) +@if(extended_images) const MF: array, 4> = array, 4>( vec4( (1.0 / 6.0) / 3.0, @@ -112,7 +123,7 @@ const MF: array, 4> = array, 4>( ); // Calculate the weights for a single fractional value (same as CPU weights function) -@if(image_bicubic) +@if(extended_images) fn cubic_weights(fract: f32) -> vec4 { return vec4( single_weight(fract, MF[0][0], MF[0][1], MF[0][2], MF[0][3]), @@ -124,7 +135,7 @@ fn cubic_weights(fract: f32) -> vec4 { // Calculate a weight based on the fractional value t and the cubic coefficients // This matches the CPU implementation exactly -@if(image_bicubic) +@if(extended_images) fn single_weight(t: f32, a: f32, b: f32, c: f32, d: f32) -> f32 { return t * (t * (t * d + c) + b) + a; } diff --git a/vello_gpu_shaders/shaders/render.wesl b/vello_gpu_shaders/shaders/render.wesl index a90d80c5a0..07352e4d4b 100644 --- a/vello_gpu_shaders/shaders/render.wesl +++ b/vello_gpu_shaders/shaders/render.wesl @@ -29,16 +29,18 @@ import package::helpers::blurred_rounded_rect::{ get_blurred_rounded_rect_sample_xy, sample_blurred_rounded_rect, }; -import package::helpers::external_texture::{sample_image_paint, sample_native_image_paint}; +@if(extended_images) +import package::helpers::external_texture::sample_image_paint; +import package::helpers::external_texture::sample_native_image_paint; import package::helpers::gradient::get_gradient_sample_xy; import package::helpers::gradient::sample_linear_gradient; import package::helpers::gradient::sample_radial_gradient; @if(gradient_sweep) import package::helpers::gradient::sample_sweep_gradient; import package::helpers::geometry::{pixel_to_ndc, quad_corner, rect_coverage}; +@if(extended_images) +import package::helpers::image::{get_image_transform, get_image_translate}; import package::helpers::image::{ - get_image_transform, - get_image_translate, get_native_image_opacity, get_native_image_transform, get_native_image_translate @@ -286,6 +288,7 @@ fn vs_main( + get_native_image_transform(image_texel0) * pos; out.payload = get_native_image_opacity(image_texel1); } + @if(extended_images) case PAINT_TYPE_IMAGE: { let paint_tex_idx = instance.paint_and_rect_flag & PAINT_TEXTURE_INDEX_MASK; let image_texel0 = load_encoded_paint_texel( @@ -381,6 +384,7 @@ fn fs_main( external_texture_0, external_sampler_0, payload, sample_xy, ); } + @if(extended_images) case PAINT_TYPE_IMAGE: { final_color = sample_image_paint( encoded_paints_texture, config.encoded_paints_tex_width_bits, diff --git a/vello_gpu_shaders/src/lib.rs b/vello_gpu_shaders/src/lib.rs index 571b5131f3..3998b02a8e 100644 --- a/vello_gpu_shaders/src/lib.rs +++ b/vello_gpu_shaders/src/lib.rs @@ -22,7 +22,7 @@ mod feature_tests { #[test] fn shader_feature_combinations_compile() { - let features = ["blurred_rounded_rect", "image_bicubic", "gradient_sweep"]; + let features = ["blurred_rounded_rect", "extended_images", "gradient_sweep"]; for mask in [0b000, 0b001, 0b010, 0b100, 0b111] { let mut compiler = Wesl::new("shaders"); compiler.use_stripping(true); diff --git a/vello_tests/feature_tests/Cargo.toml b/vello_tests/feature_tests/Cargo.toml index 8d43bd2a23..230b0fe5f1 100644 --- a/vello_tests/feature_tests/Cargo.toml +++ b/vello_tests/feature_tests/Cargo.toml @@ -42,7 +42,7 @@ default = [] wgpu = ["dep:wgpu", "dep:pollster", "vello_gpu/wgpu_default"] webgl = ["vello_gpu/webgl"] blurred_rounded_rect = ["vello_gpu/blurred_rounded_rect"] -image_bicubic = ["vello_gpu/image_bicubic"] +extended_images = ["vello_gpu/extended_images"] gradient_sweep = ["vello_gpu/gradient_sweep"] [lints] diff --git a/vello_tests/feature_tests/build.rs b/vello_tests/feature_tests/build.rs index 94b5602e55..ed62bdc2d7 100644 --- a/vello_tests/feature_tests/build.rs +++ b/vello_tests/feature_tests/build.rs @@ -4,7 +4,7 @@ //! Select the reference image for the enabled shader features. fn main() { - let features = ["blurred_rounded_rect", "image_bicubic", "gradient_sweep"]; + let features = ["blurred_rounded_rect", "extended_images", "gradient_sweep"]; let enabled = features .iter() .filter(|feature| { diff --git a/vello_tests/feature_tests/run.sh b/vello_tests/feature_tests/run.sh index e8eac81295..986e1c3927 100644 --- a/vello_tests/feature_tests/run.sh +++ b/vello_tests/feature_tests/run.sh @@ -50,7 +50,7 @@ case $backend in *) usage; exit 2 ;; esac -features=(blurred_rounded_rect image_bicubic gradient_sweep) +features=(blurred_rounded_rect extended_images gradient_sweep) all_features=$(IFS=,; echo "${features[*]}") for combination in "" "${features[@]}" "$all_features"; do echo "Testing $backend: ${combination:-no shader features}" diff --git a/vello_tests/feature_tests/tests/support/mod.rs b/vello_tests/feature_tests/tests/support/mod.rs index 2e2bbcd8dc..0e0e8c7838 100644 --- a/vello_tests/feature_tests/tests/support/mod.rs +++ b/vello_tests/feature_tests/tests/support/mod.rs @@ -23,7 +23,7 @@ pub(crate) fn elements() -> Vec { for &feature in probe::ALL_PROBE_ELEMENTS { let enabled = match feature { ProbeFeature::BlurredRoundedRect => Some(cfg!(feature = "blurred_rounded_rect")), - ProbeFeature::ImageBicubic => Some(cfg!(feature = "image_bicubic")), + ProbeFeature::ImageBicubic => Some(cfg!(feature = "extended_images")), ProbeFeature::SweepGradient => Some(cfg!(feature = "gradient_sweep")), _ => None, }; diff --git a/vello_tests/feature_tests/tests/support/webgl.rs b/vello_tests/feature_tests/tests/support/webgl.rs index d7b683343a..915786820f 100644 --- a/vello_tests/feature_tests/tests/support/webgl.rs +++ b/vello_tests/feature_tests/tests/support/webgl.rs @@ -3,7 +3,11 @@ use super::{Pixmap, ProbeFeature, probe}; use vello_common::{ - color::palette::css, paint::ImageSource, peniko::ImageAlphaType, pixmap::PixelMetadata, + TextureId, + color::palette::css, + paint::{ImageSource, TextureRegion}, + peniko::ImageAlphaType, + pixmap::PixelMetadata, }; use vello_gpu::{RenderSettings, RenderSize, Scene, WebGlRenderer, WebGlTextureBindings}; use wasm_bindgen::JsCast; @@ -27,12 +31,46 @@ pub(crate) fn render(elements: &[ProbeFeature]) -> Pixmap { WebGlRenderer::new_with(&canvas, RenderSettings::default(), true).unwrap(); let image = probe::probe_image_pixmap(); - let image_id = renderer.upload_image(&mut resources, &image).unwrap(); + let gl = renderer.gl_context(); + let image_texture = gl.create_texture().unwrap(); + gl.active_texture(WebGl2RenderingContext::TEXTURE0); + gl.bind_texture(WebGl2RenderingContext::TEXTURE_2D, Some(&image_texture)); + gl.tex_storage_2d( + WebGl2RenderingContext::TEXTURE_2D, + 1, + WebGl2RenderingContext::RGBA8, + image.width().into(), + image.height().into(), + ); + gl.tex_sub_image_2d_with_i32_and_i32_and_u32_and_type_and_opt_u8_array( + WebGl2RenderingContext::TEXTURE_2D, + 0, + 0, + 0, + image.width().into(), + image.height().into(), + WebGl2RenderingContext::RGBA, + WebGl2RenderingContext::UNSIGNED_BYTE, + Some(image.data_as_u8_slice()), + ) + .unwrap(); + let texture_id = TextureId(0); + let mut texture_bindings = WebGlTextureBindings::new(); + texture_bindings.insert( + texture_id, + image_texture.clone(), + image.width(), + image.height(), + ); let mut scene = Scene::new(width, height); probe::draw_scene( &mut scene, - ImageSource::opaque_id_with_transparency_hint(image_id, image.may_have_transparency()), + ImageSource::external_texture( + texture_id, + TextureRegion::Full, + image.may_have_transparency(), + ), elements, ); @@ -41,7 +79,7 @@ pub(crate) fn render(elements: &[ProbeFeature]) -> Pixmap { &scene, &mut resources, &RenderSize { width, height }, - &WebGlTextureBindings::new(), + &texture_bindings, css::WHITE, ) .unwrap(); @@ -67,6 +105,8 @@ pub(crate) fn render(elements: &[ProbeFeature]) -> Pixmap { top[y * row_bytes..(y + 1) * row_bytes].swap_with_slice(&mut bottom[..row_bytes]); } + gl.delete_texture(Some(&image_texture)); + Pixmap::from_parts( pixels, width, diff --git a/vello_tests/feature_tests/tests/support/wgpu.rs b/vello_tests/feature_tests/tests/support/wgpu.rs index 30b59a7110..66c595e8c1 100644 --- a/vello_tests/feature_tests/tests/support/wgpu.rs +++ b/vello_tests/feature_tests/tests/support/wgpu.rs @@ -2,7 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT use super::{Pixmap, ProbeFeature, probe}; -use vello_common::{color::palette::css, paint::ImageSource, pixmap::PixelMetadata}; +use vello_common::{ + TextureId, + color::palette::css, + paint::{ImageSource, TextureRegion}, + pixmap::PixelMetadata, +}; use vello_gpu::{ ClearSettings, RenderSize, RenderTargetConfig, Renderer, Scene, TargetInit, TextureBindings, }; @@ -24,12 +29,45 @@ pub(crate) fn render(elements: &[ProbeFeature]) -> Pixmap { let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); let image = probe::probe_image_pixmap(); - let image_id = renderer.upload_image(&mut resources, &device, &queue, &mut encoder, &image); + let image_texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some("Feature test image"), + size: wgpu::Extent3d { + width: image.width().into(), + height: image.height().into(), + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + view_formats: &[], + }); + queue.write_texture( + image_texture.as_image_copy(), + image.data_as_u8_slice(), + wgpu::TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some(u32::from(image.width()) * 4), + rows_per_image: None, + }, + image_texture.size(), + ); + let texture_id = TextureId(0); + let mut texture_bindings = TextureBindings::new(); + texture_bindings.insert( + texture_id, + image_texture.create_view(&wgpu::TextureViewDescriptor::default()), + ); let mut scene = Scene::new(width, height); probe::draw_scene( &mut scene, - ImageSource::opaque_id_with_transparency_hint(image_id, image.may_have_transparency()), + ImageSource::external_texture( + texture_id, + TextureRegion::Full, + image.may_have_transparency(), + ), elements, ); @@ -62,7 +100,7 @@ pub(crate) fn render(elements: &[ProbeFeature]) -> Pixmap { &size, &texture.create_view(&wgpu::TextureViewDescriptor::default()), Some(&depth), - &TextureBindings::new(), + &texture_bindings, TargetInit::Clear(ClearSettings::Viewport { color: css::WHITE }), ) .unwrap(); diff --git a/vello_tests/snapshots/feature_tests_probe_image_bicubic.png b/vello_tests/snapshots/feature_tests_probe_extended_images.png similarity index 100% rename from vello_tests/snapshots/feature_tests_probe_image_bicubic.png rename to vello_tests/snapshots/feature_tests_probe_extended_images.png From 514c4df88e500cba0d5a1ab36d3829848fbaf677 Mon Sep 17 00:00:00 2001 From: Laurenz Stampfl Date: Thu, 8 Oct 2026 12:26:32 +0200 Subject: [PATCH 2/8] vello_gpu: Gate glyph atlas test on extended_images --- vello_gpu/src/render/webgl/mod.rs | 9 +++++---- vello_gpu/src/render/wgpu/mod.rs | 9 +++++---- vello_gpu/src/scene.rs | 10 +++++----- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/vello_gpu/src/render/webgl/mod.rs b/vello_gpu/src/render/webgl/mod.rs index 7c4bfc1393..2a025afda1 100644 --- a/vello_gpu/src/render/webgl/mod.rs +++ b/vello_gpu/src/render/webgl/mod.rs @@ -779,19 +779,20 @@ impl WebGlRenderer { resources: &mut Resources, writer: &T, ) -> Result { - if !cfg!(feature = "extended_images") { - panic!("uploaded images require the `extended_images` feature"); - } - self.upload_image_with(&mut resources.image_cache, writer, IMAGE_PADDING) } + #[track_caller] pub(crate) fn upload_image_with( &mut self, image_cache: &mut ImageCache, writer: &T, padding: u16, ) -> Result { + if !cfg!(feature = "extended_images") { + panic!("uploaded images require the `extended_images` feature"); + } + let width = writer.width(); let height = writer.height(); let image_id = image_cache diff --git a/vello_gpu/src/render/wgpu/mod.rs b/vello_gpu/src/render/wgpu/mod.rs index 7a193ef28c..f5cc6644f9 100644 --- a/vello_gpu/src/render/wgpu/mod.rs +++ b/vello_gpu/src/render/wgpu/mod.rs @@ -529,10 +529,6 @@ impl Renderer { encoder: &mut CommandEncoder, writer: &T, ) -> vello_common::paint::ImageId { - if !cfg!(feature = "extended_images") { - panic!("uploaded images require the `extended_images` feature"); - } - self.upload_image_with( &mut resources.image_cache, device, @@ -543,6 +539,7 @@ impl Renderer { ) } + #[track_caller] pub(crate) fn upload_image_with( &mut self, image_cache: &mut ImageCache, @@ -552,6 +549,10 @@ impl Renderer { writer: &T, padding: u16, ) -> vello_common::paint::ImageId { + if !cfg!(feature = "extended_images") { + panic!("uploaded images require the `extended_images` feature"); + } + let width = writer.width(); let height = writer.height(); let image_id = image_cache.allocate(width, height, padding).unwrap(); diff --git a/vello_gpu/src/scene.rs b/vello_gpu/src/scene.rs index ae1a1bd1b8..64b7f07325 100644 --- a/vello_gpu/src/scene.rs +++ b/vello_gpu/src/scene.rs @@ -1009,11 +1009,11 @@ fn assert_paint_supported(paint: &PaintType, tint: Option) { #[cfg(test)] mod tests { use super::{RecordedDraw, Scene}; - #[cfg(feature = "text")] + #[cfg(all(feature = "text", feature = "extended_images"))] use crate::resources::Resources; - #[cfg(feature = "text")] + #[cfg(all(feature = "text", feature = "extended_images"))] use alloc::sync::Arc; - #[cfg(feature = "text")] + #[cfg(all(feature = "text", feature = "extended_images"))] use glifo::Glyph; use vello_common::TextureId; use vello_common::geometry::RectU16; @@ -1021,7 +1021,7 @@ mod tests { use vello_common::paint::{Image, ImageSource, Paint, PremulColor, TextureRegion}; use vello_common::peniko::ImageSampler; use vello_common::peniko::color::palette::css::BLUE; - #[cfg(feature = "text")] + #[cfg(all(feature = "text", feature = "extended_images"))] use vello_common::peniko::{Blob, FontData}; use vello_common::record::Drawable; @@ -1106,7 +1106,7 @@ mod tests { assert!(scene.recorder.nodes.is_empty()); } - #[cfg(feature = "text")] + #[cfg(all(feature = "text", feature = "extended_images"))] #[test] fn glyph_atlas_resources_are_lazy() { const ROBOTO_FONT: &[u8] = include_bytes!("../../assets/roboto/Roboto-Regular.ttf"); From 667f0440099b7d247dae163ade40699a72cb552d Mon Sep 17 00:00:00 2001 From: Laurenz Stampfl Date: Thu, 15 Oct 2026 09:07:30 +0200 Subject: [PATCH 3/8] vello_gpu: Rename extended_images to full_image_support --- vello_gpu/Cargo.toml | 4 +-- vello_gpu/README.md | 2 +- vello_gpu/src/lib.rs | 2 +- vello_gpu/src/render/webgl/mod.rs | 4 +-- vello_gpu/src/render/wgpu/mod.rs | 4 +-- vello_gpu/src/scene.rs | 14 +++++----- vello_gpu/src/text.rs | 4 +-- vello_gpu_shaders/Cargo.toml | 4 +-- vello_gpu_shaders/README.md | 2 +- vello_gpu_shaders/build.rs | 2 +- .../shaders/helpers/external_texture.wesl | 18 ++++++------ vello_gpu_shaders/shaders/helpers/image.wesl | 28 +++++++++---------- vello_gpu_shaders/shaders/render.wesl | 8 +++--- vello_gpu_shaders/src/lib.rs | 6 +++- vello_tests/feature_tests/Cargo.toml | 2 +- vello_tests/feature_tests/build.rs | 6 +++- vello_tests/feature_tests/run.sh | 2 +- .../feature_tests/tests/support/mod.rs | 2 +- ...eature_tests_probe_full_image_support.png} | 0 19 files changed, 61 insertions(+), 53 deletions(-) rename vello_tests/snapshots/{feature_tests_probe_extended_images.png => feature_tests_probe_full_image_support.png} (100%) diff --git a/vello_gpu/Cargo.toml b/vello_gpu/Cargo.toml index 3971239c65..421df1f2a5 100644 --- a/vello_gpu/Cargo.toml +++ b/vello_gpu/Cargo.toml @@ -51,9 +51,9 @@ roxmltree = "0.20.0" [features] default = ["wgpu", "wgpu_default", "text", "all_shader_features"] # Enable all optional shader features. -all_shader_features = ["blurred_rounded_rect", "extended_images", "gradient_sweep"] +all_shader_features = ["blurred_rounded_rect", "full_image_support", "gradient_sweep"] blurred_rounded_rect = ["vello_gpu_shaders?/blurred_rounded_rect"] -extended_images = ["vello_gpu_shaders?/extended_images"] +full_image_support = ["vello_gpu_shaders?/full_image_support"] gradient_sweep = ["vello_gpu_shaders?/gradient_sweep"] std = ["vello_common/std", "glifo?/std"] libm = ["vello_common/libm", "glifo?/libm"] diff --git a/vello_gpu/README.md b/vello_gpu/README.md index 9cda87dfb9..a1bfd0712b 100644 --- a/vello_gpu/README.md +++ b/vello_gpu/README.md @@ -50,7 +50,7 @@ preprocessing and GPU rendering: features. - `blurred_rounded_rect` (enabled by default): Enables rendering of blurred rounded rectangles. -- `extended_images` (enabled by default): If this feature is disabled, only +- `full_image_support` (enabled by default): If this feature is disabled, only external textures using `TextureRegion::Full` with nearest or bilinear sampling and opacity are supported. Enabling this feature also adds support for uploaded images, glyph atlas caching, bicubic sampling, cropped external diff --git a/vello_gpu/src/lib.rs b/vello_gpu/src/lib.rs index bfe9e7510c..22602d4415 100644 --- a/vello_gpu/src/lib.rs +++ b/vello_gpu/src/lib.rs @@ -31,7 +31,7 @@ //! features. //! - `blurred_rounded_rect` (enabled by default): Enables rendering of blurred //! rounded rectangles. -//! - `extended_images` (enabled by default): If this feature is disabled, only +//! - `full_image_support` (enabled by default): If this feature is disabled, only //! external textures using `TextureRegion::Full` with nearest or bilinear //! sampling and opacity are supported. Enabling this feature also adds support //! for uploaded images, glyph atlas caching, bicubic sampling, cropped external diff --git a/vello_gpu/src/render/webgl/mod.rs b/vello_gpu/src/render/webgl/mod.rs index 2a025afda1..796dbc171c 100644 --- a/vello_gpu/src/render/webgl/mod.rs +++ b/vello_gpu/src/render/webgl/mod.rs @@ -789,8 +789,8 @@ impl WebGlRenderer { writer: &T, padding: u16, ) -> Result { - if !cfg!(feature = "extended_images") { - panic!("uploaded images require the `extended_images` feature"); + if !cfg!(feature = "full_image_support") { + panic!("uploaded images require the `full_image_support` feature"); } let width = writer.width(); diff --git a/vello_gpu/src/render/wgpu/mod.rs b/vello_gpu/src/render/wgpu/mod.rs index f5cc6644f9..ee0227fc58 100644 --- a/vello_gpu/src/render/wgpu/mod.rs +++ b/vello_gpu/src/render/wgpu/mod.rs @@ -549,8 +549,8 @@ impl Renderer { writer: &T, padding: u16, ) -> vello_common::paint::ImageId { - if !cfg!(feature = "extended_images") { - panic!("uploaded images require the `extended_images` feature"); + if !cfg!(feature = "full_image_support") { + panic!("uploaded images require the `full_image_support` feature"); } let width = writer.width(); diff --git a/vello_gpu/src/scene.rs b/vello_gpu/src/scene.rs index 64b7f07325..c0914d8d99 100644 --- a/vello_gpu/src/scene.rs +++ b/vello_gpu/src/scene.rs @@ -999,9 +999,9 @@ fn assert_paint_supported(paint: &PaintType, tint: Option) { "sweep gradients require the `gradient_sweep` feature" ), PaintType::Image(image) => assert!( - cfg!(feature = "extended_images") + cfg!(feature = "full_image_support") || crate::paint::supports_native_image(&image.image, image.sampler.quality, tint), - "extended image sampling requires the `extended_images` feature" + "extended image sampling requires the `full_image_support` feature" ), } } @@ -1009,11 +1009,11 @@ fn assert_paint_supported(paint: &PaintType, tint: Option) { #[cfg(test)] mod tests { use super::{RecordedDraw, Scene}; - #[cfg(all(feature = "text", feature = "extended_images"))] + #[cfg(all(feature = "text", feature = "full_image_support"))] use crate::resources::Resources; - #[cfg(all(feature = "text", feature = "extended_images"))] + #[cfg(all(feature = "text", feature = "full_image_support"))] use alloc::sync::Arc; - #[cfg(all(feature = "text", feature = "extended_images"))] + #[cfg(all(feature = "text", feature = "full_image_support"))] use glifo::Glyph; use vello_common::TextureId; use vello_common::geometry::RectU16; @@ -1021,7 +1021,7 @@ mod tests { use vello_common::paint::{Image, ImageSource, Paint, PremulColor, TextureRegion}; use vello_common::peniko::ImageSampler; use vello_common::peniko::color::palette::css::BLUE; - #[cfg(all(feature = "text", feature = "extended_images"))] + #[cfg(all(feature = "text", feature = "full_image_support"))] use vello_common::peniko::{Blob, FontData}; use vello_common::record::Drawable; @@ -1106,7 +1106,7 @@ mod tests { assert!(scene.recorder.nodes.is_empty()); } - #[cfg(all(feature = "text", feature = "extended_images"))] + #[cfg(all(feature = "text", feature = "full_image_support"))] #[test] fn glyph_atlas_resources_are_lazy() { const ROBOTO_FONT: &[u8] = include_bytes!("../../assets/roboto/Roboto-Regular.ttf"); diff --git a/vello_gpu/src/text.rs b/vello_gpu/src/text.rs index 139ade98d5..f2ece83df2 100644 --- a/vello_gpu/src/text.rs +++ b/vello_gpu/src/text.rs @@ -259,8 +259,8 @@ impl<'a> GpuGlyphRunBackend<'a> { impl<'a> GlyphRunBackend<'a> for GpuGlyphRunBackend<'a> { fn atlas_cache(mut self, enabled: bool) -> Self { assert!( - !enabled || cfg!(feature = "extended_images"), - "glyph atlas caching requires the `extended_images` feature" + !enabled || cfg!(feature = "full_image_support"), + "glyph atlas caching requires the `full_image_support` feature" ); self.atlas_cache_enabled = enabled; diff --git a/vello_gpu_shaders/Cargo.toml b/vello_gpu_shaders/Cargo.toml index 53aeeadff5..b64041d5a8 100644 --- a/vello_gpu_shaders/Cargo.toml +++ b/vello_gpu_shaders/Cargo.toml @@ -34,9 +34,9 @@ naga = { workspace = true, features = ["wgsl-in", "wgsl-out", "glsl-out"] } wesl = "=0.4.0" [features] -default = ["blurred_rounded_rect", "extended_images", "gradient_sweep"] +default = ["blurred_rounded_rect", "full_image_support", "gradient_sweep"] blurred_rounded_rect = [] -extended_images = [] +full_image_support = [] gradient_sweep = [] glsl = ["dep:naga"] unminified = [] diff --git a/vello_gpu_shaders/README.md b/vello_gpu_shaders/README.md index aedac8665a..714a3eecfc 100644 --- a/vello_gpu_shaders/README.md +++ b/vello_gpu_shaders/README.md @@ -18,7 +18,7 @@ generated GLSL shader programs, used by the Vello GPU renderer. [naga](https://github.com/gfx-rs/wgpu/tree/trunk/naga) to minify the resulting WGSL. - Optional generation of minified GLSL and reflection metadata for WebGL. -- The `blurred_rounded_rect`, `extended_images`, and `gradient_sweep` features are +- The `blurred_rounded_rect`, `full_image_support`, and `gradient_sweep` features are enabled by default and include their respective shader paths. ## Usage diff --git a/vello_gpu_shaders/build.rs b/vello_gpu_shaders/build.rs index d0736a902d..dc2bf1975e 100644 --- a/vello_gpu_shaders/build.rs +++ b/vello_gpu_shaders/build.rs @@ -76,7 +76,7 @@ fn load_shader_infos(shader_dir: &Path) -> Vec { "blurred_rounded_rect", cfg!(feature = "blurred_rounded_rect"), ); - compiler.set_feature("extended_images", cfg!(feature = "extended_images")); + compiler.set_feature("full_image_support", cfg!(feature = "full_image_support")); compiler.set_feature("gradient_sweep", cfg!(feature = "gradient_sweep")); shader_names diff --git a/vello_gpu_shaders/shaders/helpers/external_texture.wesl b/vello_gpu_shaders/shaders/helpers/external_texture.wesl index e0eb603256..a71cb6d6d0 100644 --- a/vello_gpu_shaders/shaders/helpers/external_texture.wesl +++ b/vello_gpu_shaders/shaders/helpers/external_texture.wesl @@ -3,9 +3,9 @@ //! Sampling helpers for externally bound image textures. -@if(extended_images) +@if(full_image_support) import package::helpers::extend::{EXTEND_PAD, extend_mode}; -@if(extended_images) +@if(full_image_support) import package::helpers::image::{ IMAGE_QUALITY_HIGH, IMAGE_QUALITY_MEDIUM, @@ -15,9 +15,9 @@ import package::helpers::image::{ get_image_quality, get_image_size, }; -@if(extended_images) +@if(full_image_support) import package::helpers::image::cubic_weights; -@if(extended_images) +@if(full_image_support) import package::helpers::paint::{PAINT_TEXTURE_INDEX_MASK, load_encoded_paint_texel}; fn sample_native_image_paint( @@ -30,7 +30,7 @@ fn sample_native_image_paint( return image_opacity * textureSampleLevel(external_texture, external_sampler, sample_xy, 0.0); } -@if(extended_images) +@if(full_image_support) fn sample_image_paint( encoded_paints_texture: texture_2d, tex_width_bits: u32, @@ -68,13 +68,13 @@ fn sample_image_paint( ); } -@if(extended_images) +@if(full_image_support) fn external_axis_index(coord: f32, extend_mode_id: u32, size: f32) -> i32 { return i32(min(extend_mode(coord, extend_mode_id, size), size - 1.0)); } // Bilinear sampling with each tap extended within the source region. -@if(extended_images) +@if(full_image_support) fn external_bilinear_sample( external_texture: texture_2d, coords: vec2, @@ -116,7 +116,7 @@ fn external_bilinear_sample( } // Bicubic sampling with each tap extended within the source region. -@if(extended_images) +@if(full_image_support) fn external_bicubic_sample( external_texture: texture_2d, coords: vec2, @@ -189,7 +189,7 @@ fn external_bicubic_sample( return vec4(clamp(result.rgb, vec3(0.0), vec3(a)), a); } -@if(extended_images) +@if(full_image_support) fn sample_external_image( external_texture: texture_2d, quality: u32, diff --git a/vello_gpu_shaders/shaders/helpers/image.wesl b/vello_gpu_shaders/shaders/helpers/image.wesl index 7c4a17ff60..9b05b28404 100644 --- a/vello_gpu_shaders/shaders/helpers/image.wesl +++ b/vello_gpu_shaders/shaders/helpers/image.wesl @@ -4,17 +4,17 @@ //! Shared encoded image accessors and cubic filter weights. /// Image rendering quality. -@if(extended_images) +@if(full_image_support) const IMAGE_QUALITY_LOW: u32 = 0u; -@if(extended_images) +@if(full_image_support) const IMAGE_QUALITY_MEDIUM: u32 = 1u; -@if(extended_images) +@if(full_image_support) const IMAGE_QUALITY_HIGH: u32 = 2u; /// Tint mode constants. -@if(extended_images) +@if(full_image_support) const TINT_MODE_ALPHA_MASK: u32 = 0u; -@if(extended_images) +@if(full_image_support) const TINT_MODE_MULTIPLY: u32 = 1u; // Encoded image layout. Must match `GpuEncodedImage` in `vello_gpu/src/render/common.rs`. @@ -38,29 +38,29 @@ const TINT_MODE_MULTIPLY: u32 = 1u; // texel1.w: padding /// The rendering quality of the image. -@if(extended_images) +@if(full_image_support) fn get_image_quality(texel0: vec4) -> u32 { return texel0.x & 0x3u; } /// The extend modes in the horizontal and vertical direction. -@if(extended_images) +@if(full_image_support) fn get_image_extend_modes(texel0: vec4) -> vec2 { return vec2((texel0.x >> 2u) & 0x3u, (texel0.x >> 4u) & 0x3u); } /// The size of the image in pixels. -@if(extended_images) +@if(full_image_support) fn get_image_size(texel0: vec4) -> vec2 { return vec2(f32(texel0.y >> 16u), f32(texel0.y & 0xFFFFu)); } /// The offset of the image in pixels. -@if(extended_images) +@if(full_image_support) fn get_image_offset(texel0: vec4) -> vec2 { return vec2(f32(texel0.z >> 16u), f32(texel0.z & 0xFFFFu)); } /// 2x2 linear part of the affine transform (columns [a,b] and [c,d]). -@if(extended_images) +@if(full_image_support) fn get_image_transform(texel0: vec4, texel1: vec4) -> mat2x2 { return mat2x2( vec2(bitcast(texel0.w), bitcast(texel1.x)), @@ -69,7 +69,7 @@ fn get_image_transform(texel0: vec4, texel1: vec4) -> mat2x2 { } /// Translation part of the affine transform [tx, ty]. -@if(extended_images) +@if(full_image_support) fn get_image_translate(texel1: vec4, texel2: vec4) -> vec2 { return vec2(bitcast(texel1.w), bitcast(texel2.x)); } @@ -94,7 +94,7 @@ fn get_native_image_opacity(texel1: vec4) -> u32 { // Cubic resampler logic borrowed from Skia (same as CPU cubic_resampler function) // Mitchell-Netravali cubic filter coefficients with parameters B=1/3 and C=1/3 -@if(extended_images) +@if(full_image_support) const MF: array, 4> = array, 4>( vec4( (1.0 / 6.0) / 3.0, @@ -123,7 +123,7 @@ const MF: array, 4> = array, 4>( ); // Calculate the weights for a single fractional value (same as CPU weights function) -@if(extended_images) +@if(full_image_support) fn cubic_weights(fract: f32) -> vec4 { return vec4( single_weight(fract, MF[0][0], MF[0][1], MF[0][2], MF[0][3]), @@ -135,7 +135,7 @@ fn cubic_weights(fract: f32) -> vec4 { // Calculate a weight based on the fractional value t and the cubic coefficients // This matches the CPU implementation exactly -@if(extended_images) +@if(full_image_support) fn single_weight(t: f32, a: f32, b: f32, c: f32, d: f32) -> f32 { return t * (t * (t * d + c) + b) + a; } diff --git a/vello_gpu_shaders/shaders/render.wesl b/vello_gpu_shaders/shaders/render.wesl index 07352e4d4b..cc40fefd07 100644 --- a/vello_gpu_shaders/shaders/render.wesl +++ b/vello_gpu_shaders/shaders/render.wesl @@ -29,7 +29,7 @@ import package::helpers::blurred_rounded_rect::{ get_blurred_rounded_rect_sample_xy, sample_blurred_rounded_rect, }; -@if(extended_images) +@if(full_image_support) import package::helpers::external_texture::sample_image_paint; import package::helpers::external_texture::sample_native_image_paint; import package::helpers::gradient::get_gradient_sample_xy; @@ -38,7 +38,7 @@ import package::helpers::gradient::sample_radial_gradient; @if(gradient_sweep) import package::helpers::gradient::sample_sweep_gradient; import package::helpers::geometry::{pixel_to_ndc, quad_corner, rect_coverage}; -@if(extended_images) +@if(full_image_support) import package::helpers::image::{get_image_transform, get_image_translate}; import package::helpers::image::{ get_native_image_opacity, @@ -288,7 +288,7 @@ fn vs_main( + get_native_image_transform(image_texel0) * pos; out.payload = get_native_image_opacity(image_texel1); } - @if(extended_images) + @if(full_image_support) case PAINT_TYPE_IMAGE: { let paint_tex_idx = instance.paint_and_rect_flag & PAINT_TEXTURE_INDEX_MASK; let image_texel0 = load_encoded_paint_texel( @@ -384,7 +384,7 @@ fn fs_main( external_texture_0, external_sampler_0, payload, sample_xy, ); } - @if(extended_images) + @if(full_image_support) case PAINT_TYPE_IMAGE: { final_color = sample_image_paint( encoded_paints_texture, config.encoded_paints_tex_width_bits, diff --git a/vello_gpu_shaders/src/lib.rs b/vello_gpu_shaders/src/lib.rs index 3998b02a8e..1202e153f4 100644 --- a/vello_gpu_shaders/src/lib.rs +++ b/vello_gpu_shaders/src/lib.rs @@ -22,7 +22,11 @@ mod feature_tests { #[test] fn shader_feature_combinations_compile() { - let features = ["blurred_rounded_rect", "extended_images", "gradient_sweep"]; + let features = [ + "blurred_rounded_rect", + "full_image_support", + "gradient_sweep", + ]; for mask in [0b000, 0b001, 0b010, 0b100, 0b111] { let mut compiler = Wesl::new("shaders"); compiler.use_stripping(true); diff --git a/vello_tests/feature_tests/Cargo.toml b/vello_tests/feature_tests/Cargo.toml index 230b0fe5f1..b3b9e4a61c 100644 --- a/vello_tests/feature_tests/Cargo.toml +++ b/vello_tests/feature_tests/Cargo.toml @@ -42,7 +42,7 @@ default = [] wgpu = ["dep:wgpu", "dep:pollster", "vello_gpu/wgpu_default"] webgl = ["vello_gpu/webgl"] blurred_rounded_rect = ["vello_gpu/blurred_rounded_rect"] -extended_images = ["vello_gpu/extended_images"] +full_image_support = ["vello_gpu/full_image_support"] gradient_sweep = ["vello_gpu/gradient_sweep"] [lints] diff --git a/vello_tests/feature_tests/build.rs b/vello_tests/feature_tests/build.rs index ed62bdc2d7..fc39d51780 100644 --- a/vello_tests/feature_tests/build.rs +++ b/vello_tests/feature_tests/build.rs @@ -4,7 +4,11 @@ //! Select the reference image for the enabled shader features. fn main() { - let features = ["blurred_rounded_rect", "extended_images", "gradient_sweep"]; + let features = [ + "blurred_rounded_rect", + "full_image_support", + "gradient_sweep", + ]; let enabled = features .iter() .filter(|feature| { diff --git a/vello_tests/feature_tests/run.sh b/vello_tests/feature_tests/run.sh index 986e1c3927..01df24e51e 100644 --- a/vello_tests/feature_tests/run.sh +++ b/vello_tests/feature_tests/run.sh @@ -50,7 +50,7 @@ case $backend in *) usage; exit 2 ;; esac -features=(blurred_rounded_rect extended_images gradient_sweep) +features=(blurred_rounded_rect full_image_support gradient_sweep) all_features=$(IFS=,; echo "${features[*]}") for combination in "" "${features[@]}" "$all_features"; do echo "Testing $backend: ${combination:-no shader features}" diff --git a/vello_tests/feature_tests/tests/support/mod.rs b/vello_tests/feature_tests/tests/support/mod.rs index 0e0e8c7838..24c0a8e80c 100644 --- a/vello_tests/feature_tests/tests/support/mod.rs +++ b/vello_tests/feature_tests/tests/support/mod.rs @@ -23,7 +23,7 @@ pub(crate) fn elements() -> Vec { for &feature in probe::ALL_PROBE_ELEMENTS { let enabled = match feature { ProbeFeature::BlurredRoundedRect => Some(cfg!(feature = "blurred_rounded_rect")), - ProbeFeature::ImageBicubic => Some(cfg!(feature = "extended_images")), + ProbeFeature::ImageBicubic => Some(cfg!(feature = "full_image_support")), ProbeFeature::SweepGradient => Some(cfg!(feature = "gradient_sweep")), _ => None, }; diff --git a/vello_tests/snapshots/feature_tests_probe_extended_images.png b/vello_tests/snapshots/feature_tests_probe_full_image_support.png similarity index 100% rename from vello_tests/snapshots/feature_tests_probe_extended_images.png rename to vello_tests/snapshots/feature_tests_probe_full_image_support.png From db7098b6c3740d06dc8246d34398f05e4641dc82 Mon Sep 17 00:00:00 2001 From: Laurenz Stampfl Date: Thu, 15 Oct 2026 09:15:25 +0200 Subject: [PATCH 4/8] vello_gpu: Return an error for unsupported WebGL image uploads --- vello_gpu/src/render/webgl/error.rs | 3 +++ vello_gpu/src/render/webgl/mod.rs | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/vello_gpu/src/render/webgl/error.rs b/vello_gpu/src/render/webgl/error.rs index 5f4620a5a2..222fd6fcf2 100644 --- a/vello_gpu/src/render/webgl/error.rs +++ b/vello_gpu/src/render/webgl/error.rs @@ -15,6 +15,9 @@ pub enum WebGlError { /// Rendering failed for a certain reason. #[error(transparent)] Render(#[from] RenderError), + /// An image upload was attempted without the `full_image_support` feature. + #[error("uploaded images require the `full_image_support` feature")] + FullImageSupportDisabled, /// A WebGL2 context could not be created for the canvas. #[error("WebGL2 context unavailable")] ContextUnavailable, diff --git a/vello_gpu/src/render/webgl/mod.rs b/vello_gpu/src/render/webgl/mod.rs index 796dbc171c..b0abb33b0d 100644 --- a/vello_gpu/src/render/webgl/mod.rs +++ b/vello_gpu/src/render/webgl/mod.rs @@ -773,6 +773,10 @@ impl WebGlRenderer { /// /// This is the WebGL analogue of the wgpu Renderer's `upload_image` method. /// It allocates space in the image cache and uploads the image data to the atlas texture. + /// + /// # Errors + /// Returns [`WebGlError::FullImageSupportDisabled`] if the `full_image_support` feature + /// is disabled, or an error if allocating or uploading the image fails. #[track_caller] pub fn upload_image( &mut self, @@ -790,7 +794,7 @@ impl WebGlRenderer { padding: u16, ) -> Result { if !cfg!(feature = "full_image_support") { - panic!("uploaded images require the `full_image_support` feature"); + return Err(WebGlError::FullImageSupportDisabled); } let width = writer.width(); From 9bf82167104fb94f5c526969fffef4f38333f9b8 Mon Sep 17 00:00:00 2001 From: Laurenz Stampfl Date: Thu, 15 Oct 2026 09:16:44 +0200 Subject: [PATCH 5/8] vello_gpu: Note paint validation setter order concern --- vello_gpu/src/scene.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vello_gpu/src/scene.rs b/vello_gpu/src/scene.rs index c0914d8d99..96732ff912 100644 --- a/vello_gpu/src/scene.rs +++ b/vello_gpu/src/scene.rs @@ -990,6 +990,9 @@ impl vello_common::probe::ProbeRenderer for Scene { } } +// TODO: Consider validating paint/tint support at draw time so otherwise valid state updates +// do not depend on setter order. See: +// https://github.com/linebender/vello/pull/1994#discussion_r4278390451 #[track_caller] fn assert_paint_supported(paint: &PaintType, tint: Option) { match paint { From 977fe1f97df726f63d9ce2300e664f4a7d7e991a Mon Sep 17 00:00:00 2001 From: Laurenz Stampfl Date: Thu, 15 Oct 2026 09:24:37 +0200 Subject: [PATCH 6/8] vello_gpu: Remove redundant native image source check --- vello_gpu/src/render/common.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/vello_gpu/src/render/common.rs b/vello_gpu/src/render/common.rs index e58164c566..7c9544f4dd 100644 --- a/vello_gpu/src/render/common.rs +++ b/vello_gpu/src/render/common.rs @@ -19,7 +19,7 @@ use core::mem::size_of; use vello_common::color::{AlphaColor, Srgb}; use vello_common::encode::EncodedImage; use vello_common::geometry::{RectU16, SizeU16}; -use vello_common::paint::{ImageSource, TextureRegion, TintMode}; +use vello_common::paint::TintMode; use vello_common::record::CommandRecorder; // TODO: If we want to use native bilinear sampling for uploaded images, @@ -389,16 +389,6 @@ pub(crate) fn encode_native_image_paint( texture_width: u16, texture_height: u16, ) -> Option { - if !matches!( - image.source, - ImageSource::ExternalTexture { - source_region: TextureRegion::Full, - .. - } - ) { - return None; - } - if !crate::paint::supports_native_image(&image.source, image.sampler.quality, image.tint) { return None; } From 57348e739e9aa01d8f1786e85fc42cdabb8f982e Mon Sep 17 00:00:00 2001 From: Laurenz Stampfl Date: Thu, 15 Oct 2026 09:32:17 +0200 Subject: [PATCH 7/8] Allow intentional full_image_support feature naming --- Cargo.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e66416ea51..7f3e381d63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,7 +99,9 @@ clippy.use_self = "warn" clippy.cargo_common_metadata = "warn" clippy.negative_feature_names = "warn" -clippy.redundant_feature_names = "warn" +# `full_image_support` distinguishes complete image support from native texture sampling. +# This lint checks feature names across the workspace, so allow it here. +clippy.redundant_feature_names = "allow" clippy.wildcard_dependencies = "warn" # END LINEBENDER LINT SET From 499762a9d1e4a2b8c818966039c4df5be90dfc70 Mon Sep 17 00:00:00 2001 From: Laurenz Stampfl Date: Thu, 15 Oct 2026 09:32:56 +0200 Subject: [PATCH 8/8] clippy --- Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7f3e381d63..c1f2dd14d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,8 +99,7 @@ clippy.use_self = "warn" clippy.cargo_common_metadata = "warn" clippy.negative_feature_names = "warn" -# `full_image_support` distinguishes complete image support from native texture sampling. -# This lint checks feature names across the workspace, so allow it here. +# To support the `full_image_support` feature name. clippy.redundant_feature_names = "allow" clippy.wildcard_dependencies = "warn" # END LINEBENDER LINT SET