.
diff --git a/sparse_strips/vello_common/src/encode.rs b/sparse_strips/vello_common/src/encode.rs index 02f244a..113b70a 100644 --- a/sparse_strips/vello_common/src/encode.rs +++ b/sparse_strips/vello_common/src/encode.rs
@@ -614,7 +614,7 @@ pub sampler: ImageSampler, /// Whether the sampled content may contain non-opaque pixels. pub may_have_transparency: bool, - /// Inverse destination transform, mapping scene coordinates to local source-rect space. + /// Inverse paint transform, mapping scene coordinates to local source-region space. pub transform: Affine, /// Optional tint applied to the sampled color. pub tint: Option<Tint>,
diff --git a/sparse_strips/vello_example_scenes/src/lib.rs b/sparse_strips/vello_example_scenes/src/lib.rs index 0003d52..83d0ab8 100644 --- a/sparse_strips/vello_example_scenes/src/lib.rs +++ b/sparse_strips/vello_example_scenes/src/lib.rs
@@ -30,8 +30,8 @@ pub use vello_common::peniko::{BlendMode, Fill, FontData, ImageQuality}; #[cfg(feature = "cpu")] use vello_cpu::{RenderContext, Resources as CpuResources}; +pub use vello_hybrid::{ExternalTextureRect, TextureId}; use vello_hybrid::{Resources as HybridResources, Scene}; -pub use vello_hybrid::{SampleRect, TextureId}; /// Renderer capability flags controlling which scenes are listed by [`get_example_scenes`]. /// @@ -40,7 +40,7 @@ #[derive(Default, Clone, Copy, Debug)] pub struct Capabilities { /// Whether the renderer supports externally bound textures and - /// [`RenderingContext::draw_texture_rects`]. + /// [`RenderingContext::draw_texture_rect`]. pub external_textures: bool, } @@ -105,14 +105,8 @@ fn pop_layer(&mut self); /// Pop the last clip path. fn pop_clip_path(&mut self); - /// Sample rectangular regions from an externally bound texture and draw them with the - /// corresponding transforms. - fn draw_texture_rects( - &mut self, - texture_id: TextureId, - quality: ImageQuality, - rects: impl IntoIterator<Item = SampleRect>, - ); + /// Sample a rectangular region from an externally bound texture and draw it. + fn draw_texture_rect(&mut self, rect: ExternalTextureRect); } #[cfg(feature = "cpu")] @@ -211,12 +205,7 @@ Self::pop_clip_path(self); } - fn draw_texture_rects( - &mut self, - _texture_id: TextureId, - _quality: ImageQuality, - _rects: impl IntoIterator<Item = SampleRect>, - ) { + fn draw_texture_rect(&mut self, _rect: ExternalTextureRect) { unimplemented!("vello_cpu does not yet support external textures"); } } @@ -316,13 +305,8 @@ Self::pop_clip_path(self); } - fn draw_texture_rects( - &mut self, - texture_id: TextureId, - quality: ImageQuality, - rects: impl IntoIterator<Item = SampleRect>, - ) { - self.draw_texture_rects(texture_id, quality, rects); + fn draw_texture_rect(&mut self, rect: ExternalTextureRect) { + self.draw_texture_rect(rect); } }
diff --git a/sparse_strips/vello_example_scenes/src/spritesheet.rs b/sparse_strips/vello_example_scenes/src/spritesheet.rs index 083c627..70974e5 100644 --- a/sparse_strips/vello_example_scenes/src/spritesheet.rs +++ b/sparse_strips/vello_example_scenes/src/spritesheet.rs
@@ -6,10 +6,10 @@ use std::io::Cursor; use vello_common::geometry::RectU16; -use vello_common::kurbo::Affine; -use vello_common::peniko::ImageQuality; +use vello_common::kurbo::{Affine, Rect}; +use vello_common::peniko::{Extend, ImageQuality, ImageSampler}; use vello_common::pixmap::Pixmap; -use vello_hybrid::{SampleRect, TextureId}; +use vello_hybrid::{ExternalTextureRect, TextureId}; use crate::{ExampleScene, RenderingContext}; @@ -30,9 +30,8 @@ /// The spritesheet scene. /// -/// Draws many rectangular regions of one externally bound texture in a single -/// [`RenderingContext::draw_texture_rects`] call. The host is responsible for uploading the -/// spritesheet (see [`SpritesheetScene::read_spritesheet`]) and binding it under +/// Draws many rectangular regions of one externally bound texture. The host is responsible for +/// uploading the spritesheet (see [`SpritesheetScene::read_spritesheet`]) and binding it under /// [`SPRITESHEET_TEXTURE_ID`] at render time. #[derive(Debug, Default)] pub struct SpritesheetScene {} @@ -79,9 +78,6 @@ const CELL_W: f64 = 80.; const CELL_H: f64 = 80.; - ctx.set_transform(root_transform); - - let mut rects = Vec::with_capacity(COLS * ROWS); for row in 0..ROWS { for col in 0..COLS { let i = row * COLS + col; @@ -96,18 +92,32 @@ let half_w = f64::from(sprite.width()) * 0.5; let half_h = f64::from(sprite.height()) * 0.5; - // This per-rect transform maps the local source region (with origin (0,0) at - // the sampled region's top-left corner) into the destination. let transform = Affine::translate((cx, cy)) * Affine::rotate(rotation) * Affine::skew(skew_x, skew_y) * Affine::scale(scale) * Affine::translate((-half_w, -half_h)); - rects.push(SampleRect::new(sprite, transform)); + ctx.set_transform(root_transform * transform); + ctx.set_paint_transform(Affine::IDENTITY); + ctx.draw_texture_rect(ExternalTextureRect { + texture_id: SPRITESHEET_TEXTURE_ID, + source_region: sprite, + destination_rect: Rect::new( + 0.0, + 0.0, + f64::from(sprite.width()), + f64::from(sprite.height()), + ), + sampler: ImageSampler { + x_extend: Extend::Pad, + y_extend: Extend::Pad, + quality: ImageQuality::Medium, + alpha: 1.0, + }, + may_have_transparency: true, + }); } } - - ctx.draw_texture_rects(SPRITESHEET_TEXTURE_ID, ImageQuality::Medium, rects); } }
diff --git a/sparse_strips/vello_hybrid/src/draw.rs b/sparse_strips/vello_hybrid/src/draw.rs index 7f09e0f..730a24a 100644 --- a/sparse_strips/vello_hybrid/src/draw.rs +++ b/sparse_strips/vello_hybrid/src/draw.rs
@@ -691,6 +691,20 @@ } #[test] + fn texture_runs_coalesce_distinct_paints_for_same_texture() { + let texture = TextureId(10); + let encoded = [external(texture), external(texture)]; + let resolver = PaintResolver::new(&encoded, &[0, 3]); + let mut case = DrawCase::new(RootTarget::UserSurface, RectU16::new(0, 0, 8, 8)); + let mut draw = Draw::default(); + + case.rect(&mut draw, rect(0.0), indexed(0), resolver); + case.rect(&mut draw, rect(4.0), indexed(1), resolver); + + assert_eq!(run_starts(&draw.external_texture_runs), [(texture, 0)]); + } + + #[test] fn texture_runs_collapse_across_atlas_images() { let texture = TextureId(10); let encoded = [external(texture), atlas_image()];
diff --git a/sparse_strips/vello_hybrid/src/lib.rs b/sparse_strips/vello_hybrid/src/lib.rs index b1ed16c..2163d6c 100644 --- a/sparse_strips/vello_hybrid/src/lib.rs +++ b/sparse_strips/vello_hybrid/src/lib.rs
@@ -101,7 +101,7 @@ #[cfg(all(feature = "webgl", feature = "probe"))] pub use render::{WebGlPendingProbe, WebGlProbeError, WebGlProbeStatus}; pub use resources::Resources; -pub use sampling::SampleRect; +pub use sampling::ExternalTextureRect; pub use scene::{LayersConfig, MemorySettings, RenderSettings, Scene}; #[cfg(feature = "text")] pub use text::{GlyphRunBuilder, HybridGlyphRunBackend};
diff --git a/sparse_strips/vello_hybrid/src/sampling.rs b/sparse_strips/vello_hybrid/src/sampling.rs index 812eb84..c2edb4b 100644 --- a/sparse_strips/vello_hybrid/src/sampling.rs +++ b/sparse_strips/vello_hybrid/src/sampling.rs
@@ -3,16 +3,29 @@ //! Sampling helpers for image drawing. +use vello_common::TextureId; use vello_common::geometry::RectU16; -use vello_common::kurbo::Affine; +use vello_common::kurbo::Rect; +use vello_common::peniko::ImageSampler; -/// A rectangular source region sampled from an image input (e.g., [`crate::TextureId`]), paired -/// with a transform of the rectangle into the destination. +/// A rectangular region sampled from an externally bound texture and drawn into a destination +/// rectangle. #[derive(Debug, Clone, Copy)] -pub struct SampleRect { +pub struct ExternalTextureRect { + /// The external texture to sample. + pub texture_id: TextureId, + /// Source region in texel coordinates. pub source_region: RectU16, + /// Destination rectangle in local scene coordinates. + pub destination_rect: Rect, + + /// Sampling parameters. + /// + /// A sampler alpha other than `1.0` is not currently supported. + pub sampler: ImageSampler, + /// Whether the sampled source region may contain non-opaque pixels. /// /// Only set this to `false` if every pixel is guaranteed to be opaque (e.g. in a texture @@ -21,28 +34,4 @@ /// /// If unsure, always set this to `true`. pub may_have_transparency: bool, - - /// Transform mapping the local source region to the destination. - /// - /// This maps from the *local* rectangle into the destination, ignoring the origin of - /// [`Self::source_region`]. - pub transform: Affine, -} - -impl SampleRect { - /// Create a new [`SampleRect`]. - pub fn new(source_region: RectU16, transform: Affine) -> Self { - Self { - source_region, - may_have_transparency: true, - transform, - } - } - - /// Indicate that the sample rect only contains opaque pixels. - #[must_use] - pub fn with_opaque_hint(mut self) -> Self { - self.may_have_transparency = false; - self - } }
diff --git a/sparse_strips/vello_hybrid/src/scene.rs b/sparse_strips/vello_hybrid/src/scene.rs index d6a8504..041cab0 100644 --- a/sparse_strips/vello_hybrid/src/scene.rs +++ b/sparse_strips/vello_hybrid/src/scene.rs
@@ -5,7 +5,7 @@ #[cfg(feature = "text")] use crate::Resources; -use crate::sampling::SampleRect; +use crate::sampling::ExternalTextureRect; #[cfg(feature = "text")] use crate::text::GlyphRunBuilder; use alloc::vec; @@ -27,7 +27,7 @@ #[cfg(feature = "text")] use vello_common::peniko::FontData; use vello_common::peniko::color::palette::css::BLACK; -use vello_common::peniko::{BlendMode, Extend, Fill, ImageQuality, ImageSampler}; +use vello_common::peniko::{BlendMode, Fill, ImageSampler}; use vello_common::record::{CommandRecorder, Drawable, LayerClip, LayerProps, PoppedLayer}; use vello_common::render_state::RenderState; use vello_common::strip::Strip; @@ -307,19 +307,16 @@ &mut self, texture_id: TextureId, source_region: RectU16, - quality: ImageQuality, - x_extend: Extend, - y_extend: Extend, + sampler: ImageSampler, transform: Affine, may_have_transparency: bool, ) -> Paint { + assert_eq!( + sampler.alpha, 1.0, + "external textures currently only support alpha of 1" + ); + let idx = self.encoded_paints.len(); - let sampler = ImageSampler { - x_extend, - y_extend, - quality, - alpha: 1.0, - }; let has_opacity = self .render_state .tint @@ -493,92 +490,36 @@ }); } - /// Sample rectangular regions from an externally bound texture and draw them with the - /// corresponding transforms. + /// Draw a region from an externally bound texture. /// - /// The per-rect transforms are composed with the current - /// [scene transform][`Self::set_transform`]. This transform is relative to the local region - /// defined by each [`SampleRect`]: i.e., the origin of each [`SampleRect`] is used only to - /// determine the region to sample in the source [`TextureId`], and is ignored for determining - /// the destination. Note that the [`paint transform`](Self::set_paint_transform) has no impact - /// on this method. - /// - /// A texture with the given [`TextureId`] must be supplied at render time. The given - /// [source regions][`SampleRect::source_region`] must be within bounds of that texture. The - /// texture is treated as premultiplied alpha in the render target's color space. See the - /// backend's binding type for more information on texture requirements. - pub fn draw_texture_rects( - &mut self, - texture_id: TextureId, - quality: ImageQuality, - rects: impl IntoIterator<Item = SampleRect>, - ) { - // This API currently doesn't take extend mode parameters: as of writing, the - // `render.wesl` shader does not use extend modes to sample across boundaries, i.e., - // sampling near a boundary doesn't take extend modes into account when determining where - // the sample should be taken. - // - // Because in this API the destination drawn is always the transformed input rect, this - // means extend modes don't currently materially impact rendering. In general drawing with - // an external texture brush, extend modes would matter, so we still encode them. - let x_extend = Extend::Pad; - let y_extend = Extend::Pad; + /// See the documentation of [`ExternalTextureRect`] for more information. + pub fn draw_texture_rect(&mut self, rect: ExternalTextureRect) { + if rect.source_region.is_empty() || rect.destination_rect.is_zero_area() { + return; + } self.with_optional_filter_or_blend_layer(|ctx| { - let use_fast_rect = ctx.can_emit_fast_strips(); + let paint = ctx.encode_external_texture_paint( + rect.texture_id, + rect.source_region, + rect.sampler, + ctx.effective_paint_transform(), + rect.may_have_transparency, + ); - for rect in rects { - if rect.source_region.is_empty() { - continue; - } - - let w = f64::from(rect.source_region.width()); - let h = f64::from(rect.source_region.height()); - let transform = ctx.effective_path_transform() * rect.transform; - - if use_fast_rect && is_axis_aligned(&transform) { - let dst_rect = Rect::new(0., 0., w, h); - let transformed_rect = transform - .transform_rect_bbox(dst_rect) - .intersect(ctx.active_rect()); - - // Skip mirrored or zero-sized rectangles. - if transformed_rect.is_zero_area() { - continue; - } - - let paint = ctx.encode_external_texture_paint( - texture_id, - rect.source_region, - quality, - x_extend, - y_extend, - transform, - rect.may_have_transparency, - ); - - ctx.recorder - .push_draw(RecordedDraw::new_rect(transformed_rect, paint), &[]); - } else { - let paint = ctx.encode_external_texture_paint( - texture_id, - rect.source_region, - quality, - x_extend, - y_extend, - transform, - rect.may_have_transparency, - ); - let dst_rect = Rect::new(0., 0., w, h); - ctx.fill_path_with( - &dst_rect.to_path(DEFAULT_TOLERANCE), - transform, - ctx.render_state.fill_rule, - paint, - ctx.aliasing_threshold, - ); - } + if let Some(bounds) = ctx.fast_rect_bounds(&rect.destination_rect) { + ctx.recorder + .push_draw(RecordedDraw::new_rect(bounds, paint), &[]); + return; } + + ctx.fill_path_with( + &rect.destination_rect.to_path(DEFAULT_TOLERANCE), + ctx.effective_path_transform(), + ctx.render_state.fill_rule, + paint, + ctx.aliasing_threshold, + ); }); } @@ -1015,8 +956,8 @@ use alloc::sync::Arc; #[cfg(feature = "text")] use glifo::Glyph; - use vello_common::kurbo::BezPath; - use vello_common::kurbo::Rect; + use vello_common::geometry::RectU16; + use vello_common::kurbo::{BezPath, Rect}; use vello_common::paint::{Paint, PremulColor}; use vello_common::peniko::color::palette::css::{BLUE, TRANSPARENT}; #[cfg(feature = "text")] @@ -1030,10 +971,7 @@ Paint::Solid(PremulColor::from_alpha_color(BLUE)), ); - assert_eq!( - draw.bbox(&[]), - Some(vello_common::geometry::RectU16::new(0, 0, 8, 8)) - ); + assert_eq!(draw.bbox(&[]), Some(RectU16::new(0, 0, 8, 8))); } #[test]
diff --git a/sparse_strips/vello_sparse_tests/snapshots/external_texture_repeat.png b/sparse_strips/vello_sparse_tests/snapshots/external_texture_repeat.png new file mode 100644 index 0000000..f1d6213 --- /dev/null +++ b/sparse_strips/vello_sparse_tests/snapshots/external_texture_repeat.png
@@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b46560a6660134c2f6c58de742ae3534b78cd0609991075b06cdf6e54b38210 +size 232
diff --git a/sparse_strips/vello_sparse_tests/snapshots/external_texture_with_cropped_source.png b/sparse_strips/vello_sparse_tests/snapshots/external_texture_with_cropped_source.png new file mode 100644 index 0000000..09e2e4f --- /dev/null +++ b/sparse_strips/vello_sparse_tests/snapshots/external_texture_with_cropped_source.png
@@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d48c74f7a240f82024027429fc1adb7924fb61ebc53342e511837db9a7b57b6e +size 1343
diff --git a/sparse_strips/vello_sparse_tests/snapshots/external_texture_with_paint_transform.png b/sparse_strips/vello_sparse_tests/snapshots/external_texture_with_paint_transform.png new file mode 100644 index 0000000..e230b64 --- /dev/null +++ b/sparse_strips/vello_sparse_tests/snapshots/external_texture_with_paint_transform.png
@@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d913f32e0b85273b0a8861464006fc2af4932f382da7742387c94f085544dd43 +size 10591
diff --git a/sparse_strips/vello_sparse_tests/snapshots/external_texture_with_scene_transform_2.png b/sparse_strips/vello_sparse_tests/snapshots/external_texture_with_scene_transform_2.png new file mode 100644 index 0000000..4ca670b --- /dev/null +++ b/sparse_strips/vello_sparse_tests/snapshots/external_texture_with_scene_transform_2.png
@@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f835051b66e43aeca0579ce31145e4507d3e95ce5ba1ade542be44b8d2d1fcb3 +size 1207
diff --git a/sparse_strips/vello_sparse_tests/tests/assets/color_grid_16x16.png b/sparse_strips/vello_sparse_tests/tests/assets/color_grid_16x16.png new file mode 100644 index 0000000..e7ea562 --- /dev/null +++ b/sparse_strips/vello_sparse_tests/tests/assets/color_grid_16x16.png Binary files differ
diff --git a/sparse_strips/vello_sparse_tests/tests/external_texture.rs b/sparse_strips/vello_sparse_tests/tests/external_texture.rs index 8a1bd2f..8483854 100644 --- a/sparse_strips/vello_sparse_tests/tests/external_texture.rs +++ b/sparse_strips/vello_sparse_tests/tests/external_texture.rs
@@ -13,7 +13,7 @@ use vello_common::peniko::{Color, Extend, ImageQuality, ImageSampler}; use vello_common::pixmap::Pixmap; use vello_dev_macros::vello_test; - use vello_hybrid::SampleRect; + use vello_hybrid::{ExternalTextureRect, TextureId}; use crate::load_image; use crate::renderer::Renderer; @@ -37,26 +37,55 @@ )) } + fn pad_sampler(quality: ImageQuality) -> ImageSampler { + ImageSampler { + x_extend: Extend::Pad, + y_extend: Extend::Pad, + quality, + alpha: 1.0, + } + } + fn texture_rect( + texture_id: TextureId, x: f64, y: f64, width: f64, height: f64, may_have_transparency: bool, - ) -> SampleRect { - let rect = SampleRect::new( - RectU16::new(0, 0, 1, 1), - Affine::translate((x, y)) * Affine::scale_non_uniform(width, height), - ); - if may_have_transparency { - rect - } else { - rect.with_opaque_hint() + ) -> ExternalTextureRect { + ExternalTextureRect { + texture_id, + source_region: RectU16::new(0, 0, 1, 1), + destination_rect: Rect::new(x, y, x + width, y + height), + sampler: pad_sampler(ImageQuality::Low), + may_have_transparency, } } - fn texture_rect_at(x: f64, y: f64) -> [SampleRect; 1] { - [texture_rect(x, y, 40., 40., true)] + fn texture_rect_at(texture_id: TextureId, x: f64, y: f64) -> ExternalTextureRect { + texture_rect(texture_id, x, y, 40., 40., true) + } + + fn sprite_rect( + texture_id: TextureId, + source_region: RectU16, + x: f64, + y: f64, + quality: ImageQuality, + ) -> ExternalTextureRect { + ExternalTextureRect { + texture_id, + source_region, + destination_rect: Rect::new( + x, + y, + x + f64::from(source_region.width()), + y + f64::from(source_region.height()), + ), + sampler: pad_sampler(quality), + may_have_transparency: true, + } } fn draw_atlas_rect(ctx: &mut impl Renderer, image: ImageSource, rect: Rect) { @@ -73,24 +102,18 @@ ctx.fill_rect(&rect); } - fn texture_circle( - ctx: &mut impl Renderer, - texture_id: vello_hybrid::TextureId, - circle: Circle, - ) { + fn texture_circle(ctx: &mut impl Renderer, texture_id: TextureId, circle: Circle) { let clip = circle.to_path(0.1); let rect = circle.bounding_box(); ctx.push_clip_path(&clip); - ctx.draw_texture_rects( + ctx.draw_texture_rect(ExternalTextureRect { texture_id, - ImageQuality::Medium, - [SampleRect::new( - RectU16::new(0, 0, 1, 1), - Affine::translate((rect.x0, rect.y0)) - * Affine::scale_non_uniform(rect.width(), rect.height()), - )], - ); + source_region: RectU16::new(0, 0, 1, 1), + destination_rect: rect, + sampler: pad_sampler(ImageQuality::Medium), + may_have_transparency: true, + }); ctx.pop_clip_path(); } @@ -102,18 +125,99 @@ #[vello_test(width = 96, height = 96, hybrid_only)] fn external_texture_composite(ctx: &mut impl Renderer) { let texture_id = ctx.register_external_texture(load_image!("glyphs_colr_noto")); - ctx.draw_texture_rects( + ctx.set_paint_transform(Affine::translate((12., 15.))); + ctx.draw_texture_rect(sprite_rect( texture_id, + SPRITES[0], + 12., + 15., ImageQuality::Low, - [SampleRect::new(SPRITES[0], Affine::translate((12., 15.)))], - ); + )); ctx.set_paint(color::palette::css::PALE_GOLDENROD.with_alpha(0.7)); ctx.fill_rect(&Rect::new(10., 7., 65., 55.)); - ctx.draw_texture_rects( + ctx.set_paint_transform(Affine::translate((25., 25.))); + ctx.draw_texture_rect(sprite_rect( texture_id, + SPRITES[3], + 25., + 25., ImageQuality::Low, - [SampleRect::new(SPRITES[3], Affine::translate((25., 25.)))], - ); + )); + } + + #[vello_test(hybrid_only)] + fn external_texture_repeat(ctx: &mut impl Renderer) { + let texture_id = ctx.register_external_texture(load_image!("color_grid_16x16")); + + ctx.draw_texture_rect(ExternalTextureRect { + texture_id, + source_region: RectU16::new(0, 0, 16, 16), + destination_rect: Rect::new(5., 5., 95., 95.), + sampler: ImageSampler { + x_extend: Extend::Repeat, + y_extend: Extend::Repeat, + quality: ImageQuality::Medium, + alpha: 1.0, + }, + may_have_transparency: false, + }); + } + + #[vello_test(hybrid_only)] + fn external_texture_with_paint_transform(ctx: &mut impl Renderer) { + let texture_id = ctx.register_external_texture(load_image!("color_grid_16x16")); + + ctx.set_paint_transform(Affine::rotate(0.35) * Affine::scale(2.)); + ctx.draw_texture_rect(ExternalTextureRect { + texture_id, + source_region: RectU16::new(2, 2, 14, 14), + destination_rect: Rect::new(5., 5., 95., 95.), + sampler: ImageSampler { + x_extend: Extend::Reflect, + y_extend: Extend::Reflect, + quality: ImageQuality::Medium, + alpha: 1.0, + }, + may_have_transparency: false, + }); + } + + #[vello_test(hybrid_only)] + fn external_texture_with_scene_transform_2(ctx: &mut impl Renderer) { + let texture_id = ctx.register_external_texture(load_image!("color_grid_16x16")); + + ctx.set_transform(Affine::translate((5., 5.)) * Affine::scale(5.625)); + ctx.draw_texture_rect(ExternalTextureRect { + texture_id, + source_region: RectU16::new(0, 0, 16, 16), + destination_rect: Rect::new(0., 0., 16., 16.), + sampler: ImageSampler { + x_extend: Extend::Repeat, + y_extend: Extend::Repeat, + quality: ImageQuality::Medium, + alpha: 1.0, + }, + may_have_transparency: false, + }); + } + + #[vello_test(hybrid_only)] + fn external_texture_with_cropped_source(ctx: &mut impl Renderer) { + let texture_id = ctx.register_external_texture(load_image!("color_grid_16x16")); + + ctx.set_paint_transform(Affine::translate((14.0, 14.0)) * Affine::scale(6.0)); + ctx.draw_texture_rect(ExternalTextureRect { + texture_id, + source_region: RectU16::new(2, 2, 14, 14), + destination_rect: Rect::new(5., 5., 95., 95.), + sampler: ImageSampler { + x_extend: Extend::Reflect, + y_extend: Extend::Reflect, + quality: ImageQuality::Medium, + alpha: 1.0, + }, + may_have_transparency: false, + }); } #[vello_test(width = 96, height = 96, hybrid_only, hybrid_no_depth)] @@ -123,7 +227,7 @@ ctx.set_paint(AlphaColor::from_rgba8(0, 0, 255, 255)); ctx.fill_rect(&Rect::new(8., 8., 64., 64.)); - ctx.draw_texture_rects(texture_id, ImageQuality::Low, texture_rect_at(28., 28.)); + ctx.draw_texture_rect(texture_rect_at(texture_id, 28., 28.)); ctx.set_paint(AlphaColor::from_rgba8(0, 255, 0, 255)); ctx.fill_rect(&Rect::new(48., 16., 88., 56.)); @@ -137,13 +241,13 @@ ctx.set_paint(AlphaColor::from_rgba8(32, 32, 32, 255)); ctx.fill_rect(&Rect::new(4., 4., 92., 92.)); - ctx.draw_texture_rects(red_texture, ImageQuality::Low, texture_rect_at(8., 8.)); + ctx.draw_texture_rect(texture_rect_at(red_texture, 8., 8.)); ctx.set_paint(AlphaColor::from_rgba8(255, 255, 255, 128)); ctx.fill_rect(&Rect::new(24., 24., 72., 72.)); - ctx.draw_texture_rects(green_texture, ImageQuality::Low, texture_rect_at(28., 28.)); - ctx.draw_texture_rects(red_texture, ImageQuality::Low, texture_rect_at(48., 48.)); + ctx.draw_texture_rect(texture_rect_at(green_texture, 28., 28.)); + ctx.draw_texture_rect(texture_rect_at(red_texture, 48., 48.)); } #[vello_test(hybrid_only, hybrid_no_depth)] @@ -154,55 +258,21 @@ let gold = ctx.register_external_texture(solid_pixmap(153, 102, 61, 160)); let tint_source = ctx.register_external_texture(solid_pixmap(255, 255, 255, 255)); - ctx.draw_texture_rects( - coral, - ImageQuality::Low, - [texture_rect(6., 6., 88., 88., false)], - ); - ctx.draw_texture_rects( - gold, - ImageQuality::Low, - [ - texture_rect(11.5, 33.5, 77., 33., true), - texture_rect(33.5, 11.5, 33., 77., true), - ], - ); - ctx.draw_texture_rects( - teal, - ImageQuality::Low, - [texture_rect(21.5, 21.5, 57., 57., false)], - ); + ctx.draw_texture_rect(texture_rect(coral, 6., 6., 88., 88., false)); + ctx.draw_texture_rect(texture_rect(gold, 11.5, 33.5, 77., 33., true)); + ctx.draw_texture_rect(texture_rect(gold, 33.5, 11.5, 33., 77., true)); + ctx.draw_texture_rect(texture_rect(teal, 21.5, 21.5, 57., 57., false)); ctx.set_tint(Some(Tint { color: Color::from_rgba8(128, 102, 204, 160), mode: TintMode::Multiply, })); - ctx.draw_texture_rects( - tint_source, - ImageQuality::Low, - [ - texture_rect(16., 38., 68., 24., false), - texture_rect(38., 16., 24., 68., false), - ], - ); + ctx.draw_texture_rect(texture_rect(tint_source, 16., 38., 68., 24., false)); + ctx.draw_texture_rect(texture_rect(tint_source, 38., 16., 24., 68., false)); ctx.set_tint(None); - ctx.draw_texture_rects( - navy, - ImageQuality::Low, - [texture_rect(33.5, 33.5, 33., 33., false)], - ); - ctx.draw_texture_rects( - gold, - ImageQuality::Low, - [ - texture_rect(28., 44., 44., 12., true), - texture_rect(44., 28., 12., 44., true), - ], - ); - ctx.draw_texture_rects( - coral, - ImageQuality::Low, - [texture_rect(44., 44., 12., 12., false)], - ); + ctx.draw_texture_rect(texture_rect(navy, 33.5, 33.5, 33., 33., false)); + ctx.draw_texture_rect(texture_rect(gold, 28., 44., 44., 12., true)); + ctx.draw_texture_rect(texture_rect(gold, 44., 28., 12., 44., true)); + ctx.draw_texture_rect(texture_rect(coral, 44., 44., 12., 12., false)); } #[vello_test(hybrid_only)] @@ -214,23 +284,9 @@ let atlas_magenta = ctx.get_image_source(solid_pixmap(254, 0, 254, 254)); draw_atlas_rect(ctx, atlas_red, Rect::new(10., 10., 34., 34.)); - ctx.draw_texture_rects( - external_green, - ImageQuality::Low, - [SampleRect::new( - RectU16::new(0, 0, 1, 1), - Affine::translate((66., 10.)) * Affine::scale(24.), - )], - ); + ctx.draw_texture_rect(texture_rect(external_green, 66., 10., 24., 24., true)); draw_atlas_rect(ctx, atlas_blue, Rect::new(38., 38., 62., 62.)); - ctx.draw_texture_rects( - external_yellow, - ImageQuality::Low, - [SampleRect::new( - RectU16::new(0, 0, 1, 1), - Affine::translate((10., 66.)) * Affine::scale(24.), - )], - ); + ctx.draw_texture_rect(texture_rect(external_yellow, 10., 66., 24., 24., true)); draw_atlas_rect(ctx, atlas_magenta, Rect::new(66., 66., 90., 90.)); } @@ -244,7 +300,7 @@ ctx.pop_layer(); ctx.push_layer(None, None, None, None, None); - ctx.draw_texture_rects(texture_id, ImageQuality::Medium, texture_rect_at(40., 40.)); + ctx.draw_texture_rect(texture_rect_at(texture_id, 40., 40.)); ctx.pop_layer(); } @@ -253,7 +309,7 @@ let texture_id = ctx.register_external_texture(solid_pixmap(0, 255, 0, 255)); ctx.push_layer(None, None, None, None, None); - ctx.draw_texture_rects(texture_id, ImageQuality::Medium, texture_rect_at(20., 20.)); + ctx.draw_texture_rect(texture_rect_at(texture_id, 20., 20.)); ctx.pop_layer(); ctx.push_layer(None, None, None, None, None); @@ -280,7 +336,7 @@ #[derive(Clone, Copy)] enum CircleOp { - Texture(CirclePos, vello_hybrid::TextureId), + Texture(CirclePos, TextureId), Paint(CirclePos, AlphaColor<Srgb>), } @@ -342,14 +398,21 @@ #[vello_test(width = 96, height = 96, hybrid_only)] fn external_texture_skewed(ctx: &mut impl Renderer) { let texture_id = ctx.register_external_texture(load_image!("glyphs_colr_noto")); - ctx.draw_texture_rects( + let source_region = SPRITES[0]; + ctx.set_transform(Affine::translate((15., 15.)) * Affine::skew(0.2, 0.1)); + ctx.set_paint_transform(Affine::IDENTITY); + ctx.draw_texture_rect(ExternalTextureRect { texture_id, - ImageQuality::High, - [SampleRect::new( - SPRITES[0], - Affine::translate((15., 15.)) * Affine::skew(0.2, 0.1), - )], - ); + source_region, + destination_rect: Rect::new( + 0., + 0., + f64::from(source_region.width()), + f64::from(source_region.height()), + ), + sampler: pad_sampler(ImageQuality::High), + may_have_transparency: true, + }); } #[vello_test(width = 96, height = 96, hybrid_only)] @@ -358,14 +421,22 @@ let clip = Circle::new((48., 48.), 24.).to_path(0.1); ctx.push_clip_layer(&clip); - ctx.draw_texture_rects( + ctx.set_paint_transform(Affine::translate((18., 18.))); + ctx.draw_texture_rect(sprite_rect( texture_id, + SPRITES[1], + 18., + 18., ImageQuality::Medium, - [ - SampleRect::new(SPRITES[1], Affine::translate((18., 18.))), - SampleRect::new(SPRITES[3], Affine::translate((34., 34.))), - ], - ); + )); + ctx.set_paint_transform(Affine::translate((34., 34.))); + ctx.draw_texture_rect(sprite_rect( + texture_id, + SPRITES[3], + 34., + 34., + ImageQuality::Medium, + )); ctx.pop_layer(); } @@ -378,11 +449,14 @@ }); ctx.push_filter_layer(blur); - ctx.draw_texture_rects( + ctx.set_paint_transform(Affine::translate((20., 20.))); + ctx.draw_texture_rect(sprite_rect( texture_id, + SPRITES[2], + 20., + 20., ImageQuality::Low, - [SampleRect::new(SPRITES[2], Affine::translate((20., 20.)))], - ); + )); ctx.pop_layer(); } @@ -404,35 +478,48 @@ (SPRITES[1], 134., 67.), ]; - ctx.draw_texture_rects( - texture_id, - ImageQuality::Low, - placements.map(|(source_region, x, y)| { - SampleRect::new(source_region, Affine::translate((x, y))) - }), - ); + for (source_region, x, y) in placements { + ctx.set_paint_transform(Affine::translate((x, y))); + ctx.draw_texture_rect(sprite_rect( + texture_id, + source_region, + x, + y, + ImageQuality::Low, + )); + } } #[vello_test(width = 96, height = 96, hybrid_only, hybrid_tolerance = 2)] fn external_texture_with_scene_transform(ctx: &mut impl Renderer) { let texture_id = ctx.register_external_texture(load_image!("glyphs_colr_noto")); - ctx.set_transform( - Affine::translate((20., 0.)) - * Affine::rotate(0.35) - * Affine::scale_non_uniform(0.85, 1.1), - ); - ctx.draw_texture_rects( - texture_id, - ImageQuality::Medium, - [ - SampleRect::new(SPRITES[0], Affine::translate((6., 8.))), - SampleRect::new( - SPRITES[3], - Affine::translate((28., 5.)) * Affine::skew(0.18, -0.08), + let scene_transform = Affine::translate((20., 0.)) + * Affine::rotate(0.35) + * Affine::scale_non_uniform(0.85, 1.1); + + for (source_region, local_transform) in [ + (SPRITES[0], Affine::translate((6., 8.))), + ( + SPRITES[3], + Affine::translate((28., 5.)) * Affine::skew(0.18, -0.08), + ), + (SPRITES[2], Affine::translate((48., 6.))), + ] { + ctx.set_transform(scene_transform * local_transform); + ctx.set_paint_transform(Affine::IDENTITY); + ctx.draw_texture_rect(ExternalTextureRect { + texture_id, + source_region, + destination_rect: Rect::new( + 0., + 0., + f64::from(source_region.width()), + f64::from(source_region.height()), ), - SampleRect::new(SPRITES[2], Affine::translate((48., 6.))), - ], - ); + sampler: pad_sampler(ImageQuality::Medium), + may_have_transparency: true, + }); + } } }
diff --git a/sparse_strips/vello_sparse_tests/tests/renderer.rs b/sparse_strips/vello_sparse_tests/tests/renderer.rs index 4043a98..dd66772 100644 --- a/sparse_strips/vello_sparse_tests/tests/renderer.rs +++ b/sparse_strips/vello_sparse_tests/tests/renderer.rs
@@ -10,12 +10,12 @@ use vello_common::kurbo::{Affine, BezPath, Rect, Stroke}; use vello_common::mask::Mask; use vello_common::paint::{ImageId, ImageSource, PaintType, Tint}; -use vello_common::peniko::{BlendMode, Fill, FontData, ImageQuality}; +use vello_common::peniko::{BlendMode, Fill, FontData}; use vello_common::pixmap::Pixmap; use vello_cpu::{Level, RasterizerSettings, RenderContext, RenderMode, RenderSettings, Resources}; use vello_hybrid::{ - RenderSettings as HybridRenderSettings, Resources as HybridResources, SampleRect, Scene, - TextureId, + ExternalTextureRect, RenderSettings as HybridRenderSettings, Resources as HybridResources, + Scene, TextureId, }; #[cfg(all(target_arch = "wasm32", feature = "webgl"))] use web_sys::WebGl2RenderingContext; @@ -88,12 +88,7 @@ fn width(&self) -> u16; fn height(&self) -> u16; fn register_external_texture(&mut self, pixmap: Arc<Pixmap>) -> TextureId; - fn draw_texture_rects( - &mut self, - texture_id: TextureId, - quality: ImageQuality, - rects: impl IntoIterator<Item = SampleRect>, - ); + fn draw_texture_rect(&mut self, rect: ExternalTextureRect); fn get_image_source(&mut self, pixmap: Arc<Pixmap>) -> ImageSource; fn register_image(&mut self, pixmap: Arc<Pixmap>) -> ImageId; } @@ -269,12 +264,7 @@ unimplemented!("external textures are only supported by hybrid renderer tests") } - fn draw_texture_rects( - &mut self, - _: TextureId, - _: ImageQuality, - _: impl IntoIterator<Item = SampleRect>, - ) { + fn draw_texture_rect(&mut self, _: ExternalTextureRect) { unimplemented!("external textures are only supported by hybrid renderer tests") } @@ -720,13 +710,8 @@ texture_id } - fn draw_texture_rects( - &mut self, - texture_id: TextureId, - quality: ImageQuality, - rects: impl IntoIterator<Item = SampleRect>, - ) { - self.scene.draw_texture_rects(texture_id, quality, rects); + fn draw_texture_rect(&mut self, rect: ExternalTextureRect) { + self.scene.draw_texture_rect(rect); } fn get_image_source(&mut self, pixmap: Arc<Pixmap>) -> ImageSource { @@ -1046,13 +1031,8 @@ texture_id } - fn draw_texture_rects( - &mut self, - texture_id: TextureId, - quality: ImageQuality, - rects: impl IntoIterator<Item = SampleRect>, - ) { - self.scene.draw_texture_rects(texture_id, quality, rects); + fn draw_texture_rect(&mut self, rect: ExternalTextureRect) { + self.scene.draw_texture_rect(rect); } fn get_image_source(&mut self, pixmap: Arc<Pixmap>) -> ImageSource {