Skip to content

Commit a608e69

Browse files
committed
add support for sceneimage in hybrid
1 parent 8edd911 commit a608e69

5 files changed

Lines changed: 176 additions & 25 deletions

File tree

imaging/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,9 @@
137137
//! - `imaging_skia` supports scene-backed image brushes natively.
138138
//! - `imaging_tiny_skia` and `imaging_vello_cpu` support them by rasterizing the source scene and
139139
//! then sampling the realized image.
140-
//! - `imaging_vello` and `imaging_vello_hybrid` intentionally reject them.
140+
//! - `imaging_vello_hybrid` supports them by rasterizing the source scene, then uploading the
141+
//! realized image into its hybrid atlas cache.
142+
//! - `imaging_vello` intentionally rejects them.
141143
//!
142144
//! The API is intentionally small and experimental; expect breaking changes while we iterate.
143145

imaging_vello_hybrid/README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ This backend supports both headless image rendering and host-owned `wgpu` textur
1010
themselves; test code in this repository uses local helper functions rather than a public
1111
bootstrap API.
1212
- Recorded `imaging::record::Scene` values can use inline image brushes; the renderer uploads and
13-
caches them behind the scenes. Direct native-scene recording can use image brushes too via
14-
`VelloHybridSceneSink::with_renderer`; the plain `VelloHybridSceneSink::new` constructor stays
15-
limited to non-image brushes.
13+
caches them behind the scenes. This includes `imaging::SceneImage`, which is rasterized once
14+
per retained-image identity and then reused through the same hybrid cache. Direct native-scene
15+
recording can use image brushes too via `VelloHybridSceneSink::with_renderer`; the plain
16+
`VelloHybridSceneSink::new` constructor stays limited to non-image brushes.
1617
- Use `VelloHybridSceneSink::new` for solid/gradient-only native scene recording.
1718
- Use `VelloHybridSceneSink::with_renderer` for native scene recording that needs image brushes.
1819
- Group-level filters are currently not supported by `vello_hybrid`; `imaging_vello_hybrid`

imaging_vello_hybrid/src/image_registry.rs

Lines changed: 116 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
33

44
use crate::Error;
5-
use imaging::{ImageBrush, ImageRef};
6-
use peniko::ImageData;
5+
use imaging::{ImageBrush, ImageRef, SceneImage, SceneImageWeak};
6+
use peniko::{Blob, ImageAlphaType, ImageData, ImageFormat};
77
use std::collections::VecDeque;
88
use std::hash::{DefaultHasher, Hash, Hasher};
9+
use std::sync::Arc;
910
use vello_common::paint::{Image as VelloImage, ImageId, ImageSource};
1011

1112
#[derive(Debug)]
1213
pub(crate) struct HybridImageRegistry {
1314
live: VecDeque<RegisteredImage>,
15+
scene_images: VecDeque<CachedSceneImage>,
1416
bytes_used: usize,
1517
max_bytes: usize,
1618
}
@@ -25,6 +27,7 @@ impl HybridImageRegistry {
2527
pub(crate) fn new(max_bytes: usize) -> Self {
2628
Self {
2729
live: VecDeque::new(),
30+
scene_images: VecDeque::new(),
2831
bytes_used: 0,
2932
max_bytes,
3033
}
@@ -35,6 +38,7 @@ impl HybridImageRegistry {
3538
renderer: &'a mut vello_hybrid::Renderer,
3639
device: &'a wgpu::Device,
3740
queue: &'a wgpu::Queue,
41+
tolerance: f64,
3842
mut encoder: wgpu::CommandEncoder,
3943
) -> HybridImageUploadSession<'a> {
4044
// We evict excess images at the start of the session,
@@ -50,6 +54,7 @@ impl HybridImageRegistry {
5054
renderer,
5155
device,
5256
queue,
57+
tolerance,
5358
encoder: Some(encoder),
5459
pending: Vec::new(),
5560
}
@@ -102,22 +107,78 @@ pub(crate) struct HybridImageUploadSession<'a> {
102107
renderer: &'a mut vello_hybrid::Renderer,
103108
device: &'a wgpu::Device,
104109
queue: &'a wgpu::Queue,
110+
tolerance: f64,
105111
encoder: Option<wgpu::CommandEncoder>,
106112
pending: Vec<RegisteredImage>,
107113
}
108114

109115
impl HybridImageUploadSession<'_> {
116+
pub(crate) fn realize_scene_image(
117+
&mut self,
118+
scene_image: &SceneImage,
119+
) -> Result<ImageData, Error> {
120+
self.registry
121+
.scene_images
122+
.retain(|entry| entry.scene_image.upgrade().is_some());
123+
124+
if let Some(entry) = self.registry.scene_images.iter().find(|entry| {
125+
entry.scene_image_id == scene_image.id()
126+
&& entry.width == scene_image.width()
127+
&& entry.height == scene_image.height()
128+
&& entry.tolerance == self.tolerance
129+
}) {
130+
return Ok(entry.image.clone());
131+
}
132+
133+
let (width, height) = crate::VelloHybridRendererState::checked_size(
134+
scene_image.width(),
135+
scene_image.height(),
136+
)?;
137+
let mut renderer = crate::VelloHybridRenderer::new(self.device.clone(), self.queue.clone());
138+
renderer.set_tolerance(self.tolerance);
139+
let native = renderer.encode_scene(scene_image.scene(), width, height)?;
140+
let image = renderer.render(&native, width, height)?;
141+
let image = ImageData {
142+
data: Blob::new(Arc::new(image.data)),
143+
format: ImageFormat::Rgba8,
144+
alpha_type: ImageAlphaType::Alpha,
145+
width: scene_image.width(),
146+
height: scene_image.height(),
147+
};
148+
self.registry.scene_images.push_back(CachedSceneImage {
149+
scene_image: scene_image.downgrade(),
150+
scene_image_id: scene_image.id(),
151+
width: scene_image.width(),
152+
height: scene_image.height(),
153+
tolerance: self.tolerance,
154+
image: image.clone(),
155+
});
156+
Ok(image)
157+
}
110158
pub(crate) fn resolve_image_brush(&mut self, brush: &ImageBrush) -> Result<VelloImage, Error> {
111-
let ImageRef::Raster(image) = brush.image.as_ref() else {
112-
return Err(Error::UnsupportedImageBrush);
159+
let key = match brush.image.as_ref() {
160+
ImageRef::Raster(image) => ImageKey::Raster(ImageDataKey::derive(image)),
161+
ImageRef::Scene(scene_image) => ImageKey::Scene(SceneImageKey {
162+
scene_image_id: scene_image.id(),
163+
width: scene_image.width(),
164+
height: scene_image.height(),
165+
tolerance: self.tolerance.to_bits(),
166+
}),
113167
};
114-
let key = ImageKey::derive(image);
115168
let image = if let Some(image) = self.pending.iter().find(|ri| ri.key == key).copied() {
116169
image
117170
} else if let Some(index) = self.registry.live.iter().position(|ri| ri.key == key) {
118171
let index = self.registry.touch(index);
119172
self.registry.live.get(index).copied().unwrap()
120173
} else {
174+
let realized_image;
175+
let image = match brush.image.as_ref() {
176+
ImageRef::Raster(image) => image,
177+
ImageRef::Scene(scene_image) => {
178+
realized_image = self.realize_scene_image(scene_image)?;
179+
&realized_image
180+
}
181+
};
121182
let image_source = ImageSource::from_peniko_image_data(image);
122183
let ImageSource::Pixmap(pixmap) = image_source else {
123184
return Err(Error::Internal(
@@ -185,15 +246,21 @@ struct RegisteredImage {
185246
}
186247

187248
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
188-
struct ImageKey {
189-
format: core::mem::Discriminant<peniko::ImageFormat>,
190-
alpha_type: core::mem::Discriminant<peniko::ImageAlphaType>,
249+
enum ImageKey {
250+
Raster(ImageDataKey),
251+
Scene(SceneImageKey),
252+
}
253+
254+
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
255+
struct ImageDataKey {
256+
format: core::mem::Discriminant<ImageFormat>,
257+
alpha_type: core::mem::Discriminant<ImageAlphaType>,
191258
width: u32,
192259
height: u32,
193260
data_hash: u64,
194261
}
195262

196-
impl ImageKey {
263+
impl ImageDataKey {
197264
fn derive(image: &ImageData) -> Self {
198265
let mut hasher = DefaultHasher::new();
199266
image.data.data().hash(&mut hasher);
@@ -207,11 +274,29 @@ impl ImageKey {
207274
}
208275
}
209276

277+
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
278+
struct SceneImageKey {
279+
scene_image_id: u64,
280+
width: u32,
281+
height: u32,
282+
tolerance: u64,
283+
}
284+
285+
#[derive(Clone, Debug)]
286+
struct CachedSceneImage {
287+
scene_image: SceneImageWeak,
288+
scene_image_id: u64,
289+
width: u32,
290+
height: u32,
291+
tolerance: f64,
292+
image: ImageData,
293+
}
294+
210295
#[cfg(test)]
211296
mod tests {
212297
use crate::image_registry::HybridImageRegistry;
213298

214-
use super::{ImageKey, RegisteredImage};
299+
use super::{ImageDataKey, ImageKey, RegisteredImage, SceneImageKey};
215300
use peniko::{Blob, ImageAlphaType, ImageData, ImageFormat};
216301
use std::collections::VecDeque;
217302
use std::sync::Arc;
@@ -231,18 +316,33 @@ mod tests {
231316
fn image_key_dedupes_equivalent_image_contents() {
232317
let a = image([1, 2, 3, 4, 9, 8, 7, 6, 5, 4, 3, 2, 10, 11, 12, 13]);
233318
let b = image([1, 2, 3, 4, 9, 8, 7, 6, 5, 4, 3, 2, 10, 11, 12, 13]);
234-
assert_eq!(ImageKey::derive(&a), ImageKey::derive(&b));
319+
assert_eq!(ImageDataKey::derive(&a), ImageDataKey::derive(&b));
235320
}
236321

237322
#[test]
238323
fn image_key_distinguishes_metadata_changes() {
239324
let mut a = image([1, 2, 3, 4, 9, 8, 7, 6, 5, 4, 3, 2, 10, 11, 12, 13]);
240325
let mut b = a.clone();
241326
b.alpha_type = ImageAlphaType::AlphaPremultiplied;
242-
assert_ne!(ImageKey::derive(&a), ImageKey::derive(&b));
327+
assert_ne!(ImageDataKey::derive(&a), ImageDataKey::derive(&b));
243328

244329
a.format = ImageFormat::Bgra8;
245-
assert_ne!(ImageKey::derive(&a), ImageKey::derive(&b));
330+
assert_ne!(ImageDataKey::derive(&a), ImageDataKey::derive(&b));
331+
}
332+
333+
#[test]
334+
fn scene_image_key_distinguishes_scene_identity() {
335+
let a = SceneImageKey {
336+
scene_image_id: 1,
337+
width: 2,
338+
height: 3,
339+
tolerance: 0.1_f64.to_bits(),
340+
};
341+
let b = SceneImageKey {
342+
scene_image_id: 2,
343+
..a
344+
};
345+
assert_ne!(a, b);
246346
}
247347

248348
#[test]
@@ -252,8 +352,8 @@ mod tests {
252352

253353
let bytes_used = a.data.len() + b.data.len();
254354

255-
let a_key = ImageKey::derive(&a);
256-
let b_key = ImageKey::derive(&b);
355+
let a_key = ImageKey::Raster(ImageDataKey::derive(&a));
356+
let b_key = ImageKey::Raster(ImageDataKey::derive(&b));
257357

258358
let a_ri = RegisteredImage {
259359
key: a_key,
@@ -274,6 +374,7 @@ mod tests {
274374

275375
let mut registry = HybridImageRegistry {
276376
live,
377+
scene_images: VecDeque::new(),
277378
max_bytes: 1000 * 1000 * 1000,
278379
bytes_used,
279380
};

imaging_vello_hybrid/src/lib.rs

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@
1313
//! In UI integrations, the host application should usually own the `wgpu` device, queue, and
1414
//! presentation targets, then pass those handles into [`VelloHybridRenderer`].
1515
//!
16-
//! Scene-backed [`imaging::SceneImage`] brushes are intentionally unsupported here. Hybrid scenes
17-
//! can upload raster images into the backend image atlas, but this backend does not treat retained
18-
//! scenes as brush-samplable image sources.
16+
//! Scene-backed [`imaging::SceneImage`] brushes are supported by rasterizing the retained scene to
17+
//! a cached image, then uploading that image into the hybrid atlas.
1918
//!
2019
//! Recorded scenes with inline image brushes are uploaded through a renderer-scoped image registry
2120
//! and translated to backend-managed opaque image ids. Use [`VelloHybridSceneSink::with_renderer`]
@@ -268,6 +267,7 @@ impl VelloHybridRendererState {
268267
&mut self.renderer,
269268
&self.device,
270269
&self.queue,
270+
self.tolerance,
271271
encoder,
272272
)
273273
}
@@ -644,7 +644,10 @@ fn map_readback_image_error(error: ReadbackError) -> ImageRendererError {
644644
#[cfg(test)]
645645
mod tests {
646646
use super::*;
647-
use imaging::{Painter, record::Scene, render::ImageTargetError};
647+
use imaging::{
648+
Brush as ImagingBrush, ImageBrush as ImagingImageBrush, Painter, SceneImage, ScenePicture,
649+
record::Scene, render::ImageTargetError,
650+
};
648651
use kurbo::Rect;
649652
use peniko::{Blob, Brush, Color, ImageAlphaType, ImageBrush, ImageData, ImageFormat};
650653
use pollster::block_on;
@@ -925,4 +928,49 @@ mod tests {
925928
assert_eq!(image.width, 20);
926929
assert_eq!(image.height, 20);
927930
}
931+
932+
#[test]
933+
fn scene_image_brush_renders() {
934+
let Ok((device, queue)) = try_init_device_and_queue() else {
935+
return;
936+
};
937+
let mut renderer = VelloHybridRenderer::new(device, queue);
938+
939+
let source = solid_scene(Color::from_rgb8(0x12, 0x34, 0x56), 2.0, 2.0);
940+
let brush = ImagingBrush::Image(ImagingImageBrush::from(SceneImage::new(source, 2, 2)));
941+
942+
let mut scene = Scene::new();
943+
{
944+
let mut painter = Painter::new(&mut scene);
945+
painter.fill(Rect::new(0.0, 0.0, 20.0, 20.0), &brush).draw();
946+
}
947+
948+
let native = renderer.encode_scene(&scene, 20, 20).unwrap();
949+
let image = renderer.render(&native, 20, 20).unwrap();
950+
assert_eq!(image.width, 20);
951+
assert_eq!(image.height, 20);
952+
}
953+
954+
#[test]
955+
fn scene_picture_draw_renders() {
956+
let Ok((device, queue)) = try_init_device_and_queue() else {
957+
return;
958+
};
959+
let mut renderer = VelloHybridRenderer::new(device, queue);
960+
961+
let picture = ScenePicture::new(
962+
solid_scene(Color::from_rgb8(0xaa, 0x44, 0x22), 8.0, 8.0),
963+
Rect::new(0.0, 0.0, 8.0, 8.0),
964+
);
965+
let mut scene = Scene::new();
966+
{
967+
let mut painter = Painter::new(&mut scene);
968+
painter.draw_scene_picture(&picture, kurbo::Affine::IDENTITY);
969+
}
970+
971+
let native = renderer.encode_scene(&scene, 8, 8).unwrap();
972+
let image = renderer.render(&native, 8, 8).unwrap();
973+
assert_eq!(image.width, 8);
974+
assert_eq!(image.height, 8);
975+
}
928976
}

imaging_vello_hybrid/src/scene_sink.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -370,12 +370,11 @@ impl PaintSink for VelloHybridSceneSink<'_> {
370370
self.draw_blurred_rounded_rect(draw);
371371
}
372372
}
373-
374373
#[cfg(test)]
375374
mod tests {
376375
use super::*;
377376
use imaging::{Filter, MaskMode, MaskRef, record};
378-
use peniko::{Blob, ImageAlphaType, ImageData, ImageFormat};
377+
use peniko::{Blob, Brush, ImageAlphaType, ImageBrush, ImageData, ImageFormat};
379378
use std::sync::Arc;
380379

381380
#[test]

0 commit comments

Comments
 (0)