.
diff --git a/sparse_strips/vello_common/src/paint.rs b/sparse_strips/vello_common/src/paint.rs index 04e1083..682f2c8 100644 --- a/sparse_strips/vello_common/src/paint.rs +++ b/sparse_strips/vello_common/src/paint.rs
@@ -145,9 +145,12 @@ /// image is actually needed during rasterization, enabling patterns like /// dynamic sprite atlases where the image data may be updated between /// encoding and rendering. -pub trait ImageResolver { +pub trait ImageResolver: Send + Sync { /// Resolve an `ImageId` to its pixmap data. /// + /// In `vello_cpu`, this is called during fine rasterization for each wide tile + /// command that references an `ImageSource::OpaqueId`. + /// /// Returns `None` if the image ID is not found in the registry. fn resolve(&self, id: ImageId) -> Option<Arc<Pixmap>>; }
diff --git a/sparse_strips/vello_cpu/src/dispatch/multi_threaded.rs b/sparse_strips/vello_cpu/src/dispatch/multi_threaded.rs index 5826368..d64bb31 100644 --- a/sparse_strips/vello_cpu/src/dispatch/multi_threaded.rs +++ b/sparse_strips/vello_cpu/src/dispatch/multi_threaded.rs
@@ -27,7 +27,7 @@ use vello_common::fearless_simd::{Level, Simd, dispatch}; use vello_common::filter_effects::Filter; use vello_common::mask::Mask; -use vello_common::paint::{ImageResolver, NoOpImageResolver, Paint}; +use vello_common::paint::{ImageResolver, Paint}; use vello_common::render_graph::RenderGraph; use vello_common::strip::Strip; use vello_common::strip_generator::{StripGenerator, StripStorage}; @@ -381,12 +381,8 @@ width: u16, height: u16, encoded_paints: &[EncodedPaint], - _image_resolver: &dyn ImageResolver, + image_resolver: &dyn ImageResolver, ) { - // Note: Multi-threaded dispatcher does not support ImageSource::OpaqueId. - // Images with OpaqueId will panic at rasterization time. - let noop_resolver = NoOpImageResolver; - let mut buffer = Regions::new(width, height, buffer); let fines = ThreadLocal::new(); let wide = &self.wide; @@ -415,7 +411,7 @@ let alphas = thread_idx .map(|i| alpha_slots[i as usize].as_slice()) .unwrap_or(&[]); - fine.run_cmd(cmd, alphas, encoded_paints, &noop_resolver, &wide.attrs); + fine.run_cmd(cmd, alphas, encoded_paints, image_resolver, &wide.attrs); } fine.pack(region);
diff --git a/sparse_strips/vello_cpu/src/render.rs b/sparse_strips/vello_cpu/src/render.rs index 72d82c8..7912801 100644 --- a/sparse_strips/vello_cpu/src/render.rs +++ b/sparse_strips/vello_cpu/src/render.rs
@@ -76,13 +76,8 @@ dispatcher: Box<dyn Dispatcher>, #[cfg(feature = "text")] pub(crate) glyph_caches: Option<GlyphCaches>, - /// Image registry for resolving `ImageSource::OpaqueId` to pixmap data. - /// - /// This allows decoupling render commands from image data, enabling - /// patterns like spritesheet rendering. - image_registry: HashMap<u32, Arc<Pixmap>>, - /// Counter for generating unique image IDs. - next_image_id: u32, + /// Registry for resolving `ImageSource::OpaqueId` to pixmap data. + image_registry: ImageRegistry, } /// Settings to apply to the render context. @@ -178,8 +173,7 @@ filter: None, #[cfg(feature = "text")] glyph_caches: Some(GlyphCaches::default()), - image_registry: HashMap::new(), - next_image_id: 0, + image_registry: ImageRegistry::new(), } } @@ -599,7 +593,7 @@ width, height, &self.encoded_paints, - self, + &self.image_registry, ); } @@ -645,7 +639,7 @@ dst_buffer_height, self.render_settings.render_mode, &self.encoded_paints, - self, + &self.image_registry, ); } @@ -703,41 +697,22 @@ impl RenderContext { /// Register a pixmap in the image registry and return its [`ImageId`]. pub fn register_image(&mut self, pixmap: Arc<Pixmap>) -> ImageId { - let id = self.next_image_id; - self.next_image_id += 1; - self.image_registry.insert(id, pixmap); - ImageId::new(id) - } - - /// Update an existing image in the registry with new pixmap data. - pub fn update_image(&mut self, id: ImageId, pixmap: Arc<Pixmap>) { - debug_assert!( - self.image_registry.contains_key(&id.as_u32()), - "Cannot update unregistered image {id:?}" - ); - self.image_registry.insert(id.as_u32(), pixmap); + self.image_registry.register(pixmap) } /// Remove an image from the registry. pub fn destroy_image(&mut self, id: ImageId) -> bool { - self.image_registry.remove(&id.as_u32()).is_some() + self.image_registry.destroy(id) } /// Resolve an `ImageId` to its pixmap data. pub fn resolve_image(&self, id: ImageId) -> Option<Arc<Pixmap>> { - self.image_registry.get(&id.as_u32()).cloned() + self.image_registry.resolve(id) } /// Clear the image registry. pub fn clear_images(&mut self) { self.image_registry.clear(); - self.next_image_id = 0; - } -} - -impl ImageResolver for RenderContext { - fn resolve(&self, id: ImageId) -> Option<Arc<Pixmap>> { - self.image_registry.get(&id.as_u32()).cloned() } } @@ -1036,7 +1011,51 @@ } } -/// Saved state for recording operations. +/// Registry that maps opaque [`ImageId`]s to [`Pixmap`] data. +/// +/// Used by [`RenderContext`] to resolve `ImageSource::OpaqueId` at rasterization time. +#[derive(Debug)] +struct ImageRegistry { + images: HashMap<u32, Arc<Pixmap>>, + next_id: u32, +} + +impl ImageRegistry { + fn new() -> Self { + Self { + images: HashMap::new(), + next_id: 0, + } + } + + fn register(&mut self, pixmap: Arc<Pixmap>) -> ImageId { + let id = self.next_id; + self.next_id += 1; + self.images.insert(id, pixmap); + ImageId::new(id) + } + + fn destroy(&mut self, id: ImageId) -> bool { + self.images.remove(&id.as_u32()).is_some() + } + + fn resolve(&self, id: ImageId) -> Option<Arc<Pixmap>> { + self.images.get(&id.as_u32()).cloned() + } + + fn clear(&mut self) { + self.images.clear(); + self.next_id = 0; + } +} + +impl ImageResolver for ImageRegistry { + fn resolve(&self, id: ImageId) -> Option<Arc<Pixmap>> { + self.images.get(&id.as_u32()).cloned() + } +} + +/// Saved state for recording operations.¬ #[derive(Debug)] pub struct RenderState { transform: Affine,
diff --git a/sparse_strips/vello_sparse_tests/tests/image.rs b/sparse_strips/vello_sparse_tests/tests/image.rs index d538dcc..eb81bfe 100644 --- a/sparse_strips/vello_sparse_tests/tests/image.rs +++ b/sparse_strips/vello_sparse_tests/tests/image.rs
@@ -601,7 +601,7 @@ /// Test rendering "hello" from a glyph atlas (spritesheet-style). /// Uses `ImageSource::OpaqueId` to demonstrate the image registry pattern. -#[vello_test(width = 60, height = 30, skip_hybrid, skip_multithreaded)] +#[vello_test(width = 60, height = 30, skip_hybrid)] fn image_spritesheet(ctx: &mut impl Renderer) { let atlas_id = ctx.register_image(load_image!("glyph_atlas")); let atlas_src = ImageSource::OpaqueId(atlas_id);