[vello_hybrid]: add example for image destruction
diff --git a/sparse_strips/vello_hybrid/examples/native_webgl/src/lib.rs b/sparse_strips/vello_hybrid/examples/native_webgl/src/lib.rs index d36bf7e..23e1784 100644 --- a/sparse_strips/vello_hybrid/examples/native_webgl/src/lib.rs +++ b/sparse_strips/vello_hybrid/examples/native_webgl/src/lib.rs
@@ -11,8 +11,11 @@ use std::cell::RefCell; use std::rc::Rc; -use vello_common::kurbo::{Affine, Vec2}; -use vello_hybrid_scenes::AnyScene; +use vello_common::{ + kurbo::{Affine, Vec2}, + paint::ImageId, +}; +use vello_hybrid_scenes::{AnyScene, SceneResources}; use wasm_bindgen::prelude::*; use web_sys::{Event, HtmlCanvasElement, KeyboardEvent, MouseEvent, WheelEvent}; @@ -41,6 +44,8 @@ renderer_wrapper: RendererWrapper, need_render: bool, canvas: HtmlCanvasElement, + scene_resources: SceneResources, + uploaded_images: Vec<ImageId>, } impl AppState { @@ -62,6 +67,8 @@ renderer_wrapper, need_render: true, canvas, + scene_resources: SceneResources::new(), + uploaded_images: Vec::new(), }; app_state.upload_images_to_atlas(); @@ -77,7 +84,11 @@ self.scene.reset(); // Render the current scene with transform - self.scenes[self.current_scene].render(&mut self.scene, self.transform); + self.scenes[self.current_scene].render( + &mut self.scene, + self.transform, + &self.scene_resources, + ); let render_size = vello_hybrid::RenderSize { width: self.width, @@ -118,8 +129,9 @@ self.need_render = true; } - fn reset_transform(&mut self) { + fn reset_scene(&mut self) { self.transform = Affine::IDENTITY; + self.upload_images_to_atlas(); self.need_render = true; } @@ -180,14 +192,27 @@ fn upload_images_to_atlas(&mut self) { use vello_hybrid_scenes::image::ImageScene; + self.scene_resources.images.clear(); + self.uploaded_images.clear(); + // 1st example — uploading pixmap directly to WebGL atlas let pixmap1 = ImageScene::read_flower_image(); - self.renderer_wrapper.renderer.upload_image(&pixmap1); + let image_id1 = self.renderer_wrapper.renderer.upload_image(&pixmap1); // 2nd example — uploading from a WebGL texture let pixmap2 = ImageScene::read_cowboy_image(); let texture2 = self.pixmap_to_webgl_texture(&pixmap2); - self.renderer_wrapper.renderer.upload_image(&texture2); + let image_id2 = self.renderer_wrapper.renderer.upload_image(&texture2); + + self.scene_resources.images = vec![image_id1, image_id2]; + self.uploaded_images = vec![image_id2, image_id1]; + } + + fn handle_destroy_image(&mut self) { + if let Some(image_id) = self.uploaded_images.pop() { + self.renderer_wrapper.renderer.destroy_image(image_id); + } + self.need_render = true; } /// Convert a pixmap to WebGL texture @@ -382,7 +407,8 @@ Box::new(move |event: KeyboardEvent| match event.key().as_str() { "ArrowRight" => app_state.borrow_mut().next_scene(), "ArrowLeft" => app_state.borrow_mut().prev_scene(), - " " => app_state.borrow_mut().reset_transform(), + "d" => app_state.borrow_mut().handle_destroy_image(), + " " => app_state.borrow_mut().reset_scene(), _ => {} }) as Box<dyn FnMut(_)>, ); @@ -396,7 +422,7 @@ let document = web_sys::window().unwrap().document().unwrap(); let instructions = document.create_element("div").unwrap(); instructions.set_inner_html( - "Left/Right Arrow: Change scene | Space: Reset view | Mouse Drag: Pan | Mouse Wheel: Zoom", + "Left/Right Arrow: Change scene | Space: Reset scene | D: Destroy image | Mouse Drag: Pan | Mouse Wheel: Zoom", ); let style = instructions .dyn_ref::<web_sys::HtmlElement>()
diff --git a/sparse_strips/vello_hybrid/examples/scenes/src/clip.rs b/sparse_strips/vello_hybrid/examples/scenes/src/clip.rs index 009402c..63979c5 100644 --- a/sparse_strips/vello_hybrid/examples/scenes/src/clip.rs +++ b/sparse_strips/vello_hybrid/examples/scenes/src/clip.rs
@@ -22,7 +22,7 @@ pub struct ClipScene {} impl ExampleScene for ClipScene { - fn render(&mut self, ctx: &mut Scene, root_transform: Affine) { + fn render(&mut self, ctx: &mut Scene, root_transform: Affine, _resources: &crate::SceneResources) { render(ctx, root_transform); } }
diff --git a/sparse_strips/vello_hybrid/examples/scenes/src/image.rs b/sparse_strips/vello_hybrid/examples/scenes/src/image.rs index 0e7ca3f..0659c43 100644 --- a/sparse_strips/vello_hybrid/examples/scenes/src/image.rs +++ b/sparse_strips/vello_hybrid/examples/scenes/src/image.rs
@@ -10,28 +10,28 @@ use vello_common::pixmap::Pixmap; use vello_common::{ kurbo::{Affine, Rect}, - paint::{Image, ImageId, ImageSource}, + paint::{Image, ImageSource}, peniko::{Extend, ImageQuality}, }; use vello_hybrid::Scene; -use crate::ExampleScene; +use crate::{ExampleScene, SceneResources}; -/// Image scene state +/// Image scene shows how to use images in the scene. #[derive(Debug, Default)] -pub struct ImageScene {} +pub struct ImageScene; impl ImageScene { /// Create a new image scene pub fn new() -> Self { - Self {} + Self } } impl ExampleScene for ImageScene { - fn render(&mut self, scene: &mut Scene, root_transform: Affine) { - let splash_flower_id = ImageId::new(0); - let cowboy_id = ImageId::new(1); + fn render(&mut self, scene: &mut Scene, root_transform: Affine, resources: &SceneResources) { + let splash_flower_id = resources.images.first().copied().unwrap(); + let cowboy_id = resources.images.get(1).copied().unwrap(); scene.set_transform( root_transform @@ -80,7 +80,7 @@ ); scene.set_paint_transform(Affine::scale(0.25)); scene.set_paint(Image { - source: ImageSource::OpaqueId(ImageId::new(0)), + source: ImageSource::OpaqueId(splash_flower_id), x_extend: Extend::Repeat, y_extend: Extend::Repeat, quality: ImageQuality::Low,
diff --git a/sparse_strips/vello_hybrid/examples/scenes/src/lib.rs b/sparse_strips/vello_hybrid/examples/scenes/src/lib.rs index f9f5394..9290424 100644 --- a/sparse_strips/vello_hybrid/examples/scenes/src/lib.rs +++ b/sparse_strips/vello_hybrid/examples/scenes/src/lib.rs
@@ -10,12 +10,30 @@ pub mod text; use vello_common::kurbo::Affine; +use vello_common::paint::ImageId; use vello_hybrid::Scene; -/// Example scene that can maintain state between renders +/// Resources that scenes may need each frame. +#[derive(Debug, Clone, Default)] +pub struct SceneResources { + /// Image ids that have been uploaded to the GPU. + pub images: Vec<ImageId>, +} + +impl SceneResources { + /// Create a new instance + pub fn new() -> Self { + Self { images: Vec::new() } + } +} + +/// Example scene that can maintain state between renders. +/// +/// The `resources` parameter contains data such a uploaded image ids. +/// Scenes that do not require external resources can ignore this parameter. pub trait ExampleScene { - /// Render the scene using the current state - fn render(&mut self, scene: &mut Scene, root_transform: Affine); + /// Render the scene using the current state. + fn render(&mut self, scene: &mut Scene, root_transform: Affine, resources: &SceneResources); } /// A type-erased example scene @@ -25,7 +43,7 @@ } /// A type-erased render function -type RenderFn = Box<dyn FnMut(&mut Scene, Affine)>; +type RenderFn = Box<dyn FnMut(&mut Scene, Affine, &SceneResources)>; impl std::fmt::Debug for AnyScene { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -37,13 +55,18 @@ /// Create a new `AnyScene` from any type that implements `ExampleScene` pub fn new<T: ExampleScene + 'static>(mut scene: T) -> Self { Self { - render_fn: Box::new(move |s, transform| scene.render(s, transform)), + render_fn: Box::new(move |s, transform, res| scene.render(s, transform, res)), } } /// Render the scene - pub fn render(&mut self, scene: &mut Scene, root_transform: Affine) { - (self.render_fn)(scene, root_transform); + pub fn render( + &mut self, + scene: &mut Scene, + root_transform: Affine, + resources: &SceneResources, + ) { + (self.render_fn)(scene, root_transform, resources); } } @@ -80,7 +103,7 @@ AnyScene::new(text::TextScene::new("Hello, Vello!")), AnyScene::new(simple::SimpleScene::new()), AnyScene::new(clip::ClipScene::new()), - AnyScene::new(image::ImageScene {}), + AnyScene::new(image::ImageScene::new()), ] .into_boxed_slice() }
diff --git a/sparse_strips/vello_hybrid/examples/scenes/src/simple.rs b/sparse_strips/vello_hybrid/examples/scenes/src/simple.rs index b8da87b..7c491d6 100644 --- a/sparse_strips/vello_hybrid/examples/scenes/src/simple.rs +++ b/sparse_strips/vello_hybrid/examples/scenes/src/simple.rs
@@ -14,7 +14,7 @@ pub struct SimpleScene {} impl ExampleScene for SimpleScene { - fn render(&mut self, ctx: &mut Scene, root_transform: Affine) { + fn render(&mut self, ctx: &mut Scene, root_transform: Affine, _resources: &crate::SceneResources) { render(ctx, root_transform); } }
diff --git a/sparse_strips/vello_hybrid/examples/scenes/src/svg.rs b/sparse_strips/vello_hybrid/examples/scenes/src/svg.rs index 11c4317..53b9e77 100644 --- a/sparse_strips/vello_hybrid/examples/scenes/src/svg.rs +++ b/sparse_strips/vello_hybrid/examples/scenes/src/svg.rs
@@ -26,7 +26,7 @@ } impl ExampleScene for SvgScene { - fn render(&mut self, scene: &mut Scene, root_transform: Affine) { + fn render(&mut self, scene: &mut Scene, root_transform: Affine, _resources: &crate::SceneResources) { render_svg(scene, &self.svg.items, root_transform * self.transform); } }
diff --git a/sparse_strips/vello_hybrid/examples/scenes/src/text.rs b/sparse_strips/vello_hybrid/examples/scenes/src/text.rs index bdc5b70..fc8fc16 100644 --- a/sparse_strips/vello_hybrid/examples/scenes/src/text.rs +++ b/sparse_strips/vello_hybrid/examples/scenes/src/text.rs
@@ -45,7 +45,7 @@ } impl ExampleScene for TextScene { - fn render(&mut self, scene: &mut Scene, root_transform: Affine) { + fn render(&mut self, scene: &mut Scene, root_transform: Affine, _resources: &crate::SceneResources) { scene.set_transform(root_transform); render_text(self, scene); }
diff --git a/sparse_strips/vello_hybrid/examples/wgpu_webgl/src/lib.rs b/sparse_strips/vello_hybrid/examples/wgpu_webgl/src/lib.rs index 4ed8269..21be7a8 100644 --- a/sparse_strips/vello_hybrid/examples/wgpu_webgl/src/lib.rs +++ b/sparse_strips/vello_hybrid/examples/wgpu_webgl/src/lib.rs
@@ -11,9 +11,12 @@ use std::cell::RefCell; use std::rc::Rc; -use vello_common::kurbo::{Affine, Point}; +use vello_common::{ + kurbo::{Affine, Point}, + paint::ImageId, +}; use vello_hybrid::Pixmap; -use vello_hybrid_scenes::AnyScene; +use vello_hybrid_scenes::{AnyScene, SceneResources}; use wasm_bindgen::prelude::*; use web_sys::{Event, HtmlCanvasElement, KeyboardEvent, MouseEvent, WheelEvent}; @@ -123,6 +126,8 @@ renderer_wrapper: RendererWrapper, need_render: bool, canvas: HtmlCanvasElement, + scene_resources: SceneResources, + uploaded_images: Vec<ImageId>, } impl AppState { @@ -144,6 +149,8 @@ renderer_wrapper, need_render: true, canvas, + scene_resources: SceneResources::new(), + uploaded_images: Vec::new(), }; // Upload images to the WebGL atlas @@ -160,7 +167,11 @@ self.scene.reset(); // Render the current scene with transform - self.scenes[self.current_scene].render(&mut self.scene, self.transform); + self.scenes[self.current_scene].render( + &mut self.scene, + self.transform, + &self.scene_resources, + ); let render_size = vello_hybrid::RenderSize { width: self.width, @@ -223,8 +234,9 @@ self.need_render = true; } - fn reset_transform(&mut self) { + fn reset_scene(&mut self) { self.transform = Affine::IDENTITY; + self.upload_images_to_atlas(); self.need_render = true; } @@ -267,9 +279,19 @@ self.need_render = true; } + fn handle_destroy_image(&mut self) { + if let Some(image_id) = self.uploaded_images.pop() { + self.destroy_image(image_id); + } + self.need_render = true; + } + fn upload_images_to_atlas(&mut self) { use vello_hybrid_scenes::image::ImageScene; + self.scene_resources.images.clear(); + self.uploaded_images.clear(); + let mut encoder = self.renderer_wrapper .device @@ -279,7 +301,7 @@ // 1st example — uploading pixmap directly to WebGL atlas let pixmap1 = ImageScene::read_flower_image(); - self.renderer_wrapper.renderer.upload_image( + let image_id1 = self.renderer_wrapper.renderer.upload_image( &self.renderer_wrapper.device, &self.renderer_wrapper.queue, &mut encoder, @@ -293,13 +315,16 @@ &self.renderer_wrapper.queue, &pixmap2, ); - self.renderer_wrapper.renderer.upload_image( + let image_id2 = self.renderer_wrapper.renderer.upload_image( &self.renderer_wrapper.device, &self.renderer_wrapper.queue, &mut encoder, &texture2, ); + self.scene_resources.images = vec![image_id1, image_id2]; + self.uploaded_images = vec![image_id2, image_id1]; + self.renderer_wrapper.queue.submit([encoder.finish()]); } @@ -352,6 +377,22 @@ texture } + + fn destroy_image(&mut self, image_id: ImageId) { + let mut encoder = + self.renderer_wrapper + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Destroy Image pass"), + }); + self.renderer_wrapper.renderer.destroy_image( + &self.renderer_wrapper.device, + &self.renderer_wrapper.queue, + &mut encoder, + image_id, + ); + self.renderer_wrapper.queue.submit([encoder.finish()]); + } } #[wasm_bindgen] @@ -494,7 +535,8 @@ Box::new(move |event: KeyboardEvent| match event.key().as_str() { "ArrowRight" => app_state.borrow_mut().next_scene(), "ArrowLeft" => app_state.borrow_mut().prev_scene(), - " " => app_state.borrow_mut().reset_transform(), + "d" => app_state.borrow_mut().handle_destroy_image(), + " " => app_state.borrow_mut().reset_scene(), _ => {} }) as Box<dyn FnMut(_)>, ); @@ -508,7 +550,7 @@ let document = web_sys::window().unwrap().document().unwrap(); let instructions = document.create_element("div").unwrap(); instructions.set_inner_html( - "Left/Right Arrow: Change scene | Space: Reset view | Mouse Drag: Pan | Mouse Wheel: Zoom", + "Left/Right Arrow: Change scene | Space: Reset scene | D: Destroy image | Mouse Drag: Pan | Mouse Wheel: Zoom", ); let style = instructions .dyn_ref::<web_sys::HtmlElement>()
diff --git a/sparse_strips/vello_hybrid/examples/winit/src/main.rs b/sparse_strips/vello_hybrid/examples/winit/src/main.rs index d56b682..da308ed 100644 --- a/sparse_strips/vello_hybrid/examples/winit/src/main.rs +++ b/sparse_strips/vello_hybrid/examples/winit/src/main.rs
@@ -11,9 +11,10 @@ use vello_common::color::palette::css::WHITE; use vello_common::color::{AlphaColor, Srgb}; use vello_common::kurbo::{Affine, Point}; +use vello_common::paint::ImageId; use vello_hybrid::{Pixmap, RenderSize, Renderer, Scene}; use vello_hybrid_scenes::image::ImageScene; -use vello_hybrid_scenes::{AnyScene, get_example_scenes}; +use vello_hybrid_scenes::{AnyScene, SceneResources, get_example_scenes}; use winit::{ application::ApplicationHandler, event::{ElementState, KeyEvent, MouseButton, MouseScrollDelta, WindowEvent}, @@ -45,6 +46,8 @@ transform: Affine, mouse_down: bool, last_cursor_position: Option<Point>, + scene_resources: SceneResources, + uploaded_images: Vec<ImageId>, } fn main() { @@ -87,6 +90,8 @@ transform: Affine::IDENTITY, mouse_down: false, last_cursor_position: None, + scene_resources: SceneResources::new(), + uploaded_images: vec![], }; let event_loop = EventLoop::new().unwrap(); @@ -195,13 +200,27 @@ window.request_redraw(); } Key::Named(NamedKey::Space) => { + let dev_id = surface.dev_id; + let window = window.clone(); // Reset transform on spacebar self.transform = Affine::IDENTITY; + self.upload_images_to_atlas(dev_id); window.request_redraw(); } Key::Named(NamedKey::Escape) => { event_loop.exit(); } + Key::Character(str) => { + if str == "d" && self.current_scene == 4 { + let dev_id = surface.dev_id; + let window = window.clone(); + + if let Some(image_id) = self.uploaded_images.pop() { + self.destroy_image(dev_id, image_id); + } + window.request_redraw(); + } + } _ => {} }, WindowEvent::MouseInput { state, button, .. } => { @@ -264,7 +283,11 @@ self.scene.reset(); self.scene.set_transform(self.transform); - self.scenes[self.current_scene].render(&mut self.scene, self.transform); + self.scenes[self.current_scene].render( + &mut self.scene, + self.transform, + &self.scene_resources, + ); let device_handle = &self.context.devices[surface.dev_id]; let render_size = RenderSize { @@ -312,6 +335,9 @@ impl App<'_> { fn upload_images_to_atlas(&mut self, device_id: usize) { + self.scene_resources.images.clear(); + self.uploaded_images.clear(); + let device_handle = &self.context.devices[device_id]; let mut encoder = device_handle @@ -322,7 +348,7 @@ // 1st example — uploading pixmap directly let pixmap1 = ImageScene::read_flower_image(); - self.renderers[device_id].as_mut().unwrap().upload_image( + let image_id1 = self.renderers[device_id].as_mut().unwrap().upload_image( &device_handle.device, &device_handle.queue, &mut encoder, @@ -333,13 +359,16 @@ let pixmap2 = ImageScene::read_cowboy_image(); let texture2 = self.upload_image_to_texture(&device_handle.device, &device_handle.queue, &pixmap2); - self.renderers[device_id].as_mut().unwrap().upload_image( + let image_id2 = self.renderers[device_id].as_mut().unwrap().upload_image( &device_handle.device, &device_handle.queue, &mut encoder, &texture2, ); + self.scene_resources.images = vec![image_id1, image_id2]; + self.uploaded_images = vec![image_id2, image_id1]; + device_handle.queue.submit([encoder.finish()]); } @@ -392,4 +421,21 @@ texture } + + fn destroy_image(&mut self, device_id: usize, image_id: ImageId) { + let device_handle = &self.context.devices[device_id]; + let mut encoder = + device_handle + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Destroy Image pass"), + }); + self.renderers[device_id].as_mut().unwrap().destroy_image( + &device_handle.device, + &device_handle.queue, + &mut encoder, + image_id, + ); + device_handle.queue.submit([encoder.finish()]); + } }
diff --git a/sparse_strips/vello_hybrid/src/image_cache.rs b/sparse_strips/vello_hybrid/src/image_cache.rs index 15b6d64..f201352 100644 --- a/sparse_strips/vello_hybrid/src/image_cache.rs +++ b/sparse_strips/vello_hybrid/src/image_cache.rs
@@ -80,6 +80,14 @@ self.slots.get(id.as_u32() as usize)?.as_ref() } + /// Check if an image is in the cache + pub(crate) fn contains(&self, id: ImageId) -> bool { + self.slots + .get(id.as_u32() as usize) + .and_then(|slot| slot.as_ref()) + .is_some() + } + /// Allocate an image in the cache #[expect( clippy::cast_possible_truncation, @@ -89,7 +97,7 @@ let alloc = self .atlas .allocate(size2(width as i32, height as i32)) - .expect("Failed to allocate texture"); + .expect("Failed to allocate image in atlas"); let slot_idx = self.free_idxs.pop().unwrap_or_else(|| { // No free slots, append to vector @@ -248,4 +256,13 @@ assert!(new_id2.as_u32() == 1); assert_ne!(new_id1.as_u32(), new_id2.as_u32()); } + + #[test] + fn test_contains() { + let mut cache = ImageCache::default(); + let id = cache.allocate(100, 100); + assert!(cache.contains(id)); + cache.deallocate(id); + assert!(!cache.contains(id)); + } }
diff --git a/sparse_strips/vello_hybrid/src/render/webgl.rs b/sparse_strips/vello_hybrid/src/render/webgl.rs index ecbb1a1..dd8276b 100644 --- a/sparse_strips/vello_hybrid/src/render/webgl.rs +++ b/sparse_strips/vello_hybrid/src/render/webgl.rs
@@ -22,7 +22,7 @@ use crate::{ GpuStrip, RenderError, RenderSize, - image_cache::{ImageCache, ImageResource}, + image_cache::ImageCache, render::Config, scene::Scene, schedule::{LoadOp, RendererBackend, Scheduler}, @@ -116,7 +116,8 @@ programs: &mut self.programs, gl: &self.gl, }; - self.scheduler.do_scene(&mut ctx, scene)?; + self.scheduler + .do_scene(&mut ctx, scene, &self.image_cache)?; // Blit the view framebuffer to the default framebuffer (canvas element), reflecting the // image along the Y axis to complete the WebGPU to WebGL2 coordinate transform. @@ -257,32 +258,40 @@ } fn prepare_gpu_encoded_paints(&self, encoded_paints: &[EncodedPaint]) -> Vec<GpuEncodedImage> { - let mut bytes: Vec<GpuEncodedImage> = Vec::new(); + let mut gpu_encoded_images: Vec<GpuEncodedImage> = Vec::with_capacity(encoded_paints.len()); for paint in encoded_paints { match paint { EncodedPaint::Image(img) => { if let ImageSource::OpaqueId(image_id) = img.source { - let image_resource: Option<&ImageResource> = self.image_cache.get(image_id); - if let Some(image_resource) = image_resource { - let transform = img.transform * Affine::translate((-0.5, -0.5)); - // pack two u16 as u32 - let image_size = ((image_resource.width as u32) << 16) - | image_resource.height as u32; - let image_offset = ((image_resource.offset[0] as u32) << 16) - | image_resource.offset[1] as u32; - let quality_and_extend_modes = ((img.extends.1 as u32) << 4) - | ((img.extends.0 as u32) << 2) - | img.quality as u32; + let (width, height, offset) = + if let Some(image_resource) = self.image_cache.get(image_id) { + ( + image_resource.width, + image_resource.height, + image_resource.offset, + ) + } else { + (0, 0, [0, 0]) + }; + let transform = img.transform * Affine::translate((-0.5, -0.5)); + // Pack two u16 as u32 (width in high 16 bits, height in low 16 bits) + let image_size = ((width as u32) << 16) | height as u32; + // Pack offset similarly (x in high 16 bits, y in low 16 bits) + let image_offset = ((offset[0] as u32) << 16) | offset[1] as u32; + // Pack quality and extend modes: + // first 2 bits for quality, next 4 bits for extend mode + let quality_and_extend_modes = ((img.extends.1 as u32) << 4) + | ((img.extends.0 as u32) << 2) + | img.quality as u32; - bytes.push(GpuEncodedImage { - quality_and_extend_modes, - image_size, - image_offset, - _padding1: 0, - transform: transform.as_coeffs().map(|x| x as f32), - _padding2: [0, 0], - }); - } + gpu_encoded_images.push(GpuEncodedImage { + quality_and_extend_modes, + image_size, + image_offset, + _padding1: 0, + transform: transform.as_coeffs().map(|x| x as f32), + _padding2: [0, 0], + }); } } _ => { @@ -290,7 +299,7 @@ } } } - bytes + gpu_encoded_images } }
diff --git a/sparse_strips/vello_hybrid/src/render/wgpu.rs b/sparse_strips/vello_hybrid/src/render/wgpu.rs index 6968e22..31d2f2c 100644 --- a/sparse_strips/vello_hybrid/src/render/wgpu.rs +++ b/sparse_strips/vello_hybrid/src/render/wgpu.rs
@@ -35,7 +35,7 @@ use crate::{ GpuStrip, RenderError, RenderSize, - image_cache::{ImageCache, ImageResource}, + image_cache::ImageCache, render::{Config, common::GpuEncodedImage}, scene::Scene, schedule::{LoadOp, RendererBackend, Scheduler}, @@ -103,7 +103,7 @@ view, }; - self.scheduler.do_scene(&mut junk, scene) + self.scheduler.do_scene(&mut junk, scene, &self.image_cache) } /// Upload image to cache and atlas in one step. Returns the `ImageId`. @@ -207,32 +207,40 @@ } fn prepare_gpu_encoded_paints(&self, encoded_paints: &[EncodedPaint]) -> Vec<GpuEncodedImage> { - let mut bytes: Vec<GpuEncodedImage> = Vec::new(); + let mut gpu_encoded_images: Vec<GpuEncodedImage> = Vec::with_capacity(encoded_paints.len()); for paint in encoded_paints { match paint { EncodedPaint::Image(img) => { if let ImageSource::OpaqueId(image_id) = img.source { - let image_resource: Option<&ImageResource> = self.image_cache.get(image_id); - if let Some(image_resource) = image_resource { - let transform = img.transform * Affine::translate((-0.5, -0.5)); - // pack two u16 as u32 - let image_size = ((image_resource.width as u32) << 16) - | image_resource.height as u32; - let image_offset = ((image_resource.offset[0] as u32) << 16) - | image_resource.offset[1] as u32; - let quality_and_extend_modes = ((img.extends.1 as u32) << 4) - | ((img.extends.0 as u32) << 2) - | img.quality as u32; + let (width, height, offset) = + if let Some(image_resource) = self.image_cache.get(image_id) { + ( + image_resource.width, + image_resource.height, + image_resource.offset, + ) + } else { + (0, 0, [0, 0]) + }; + let transform = img.transform * Affine::translate((-0.5, -0.5)); + // Pack two u16 as u32 (width in high 16 bits, height in low 16 bits) + let image_size = ((width as u32) << 16) | height as u32; + // Pack offset similarly (x in high 16 bits, y in low 16 bits) + let image_offset = ((offset[0] as u32) << 16) | offset[1] as u32; + // Pack quality and extend modes: + // first 2 bits for quality, next 4 bits for extend mode + let quality_and_extend_modes = ((img.extends.1 as u32) << 4) + | ((img.extends.0 as u32) << 2) + | img.quality as u32; - bytes.push(GpuEncodedImage { - quality_and_extend_modes, - image_size, - image_offset, - _padding1: 0, - transform: transform.as_coeffs().map(|x| x as f32), - _padding2: [0, 0], - }); - } + gpu_encoded_images.push(GpuEncodedImage { + quality_and_extend_modes, + image_size, + image_offset, + _padding1: 0, + transform: transform.as_coeffs().map(|x| x as f32), + _padding2: [0, 0], + }); } } _ => { @@ -240,7 +248,7 @@ } } } - bytes + gpu_encoded_images } }
diff --git a/sparse_strips/vello_hybrid/src/schedule.rs b/sparse_strips/vello_hybrid/src/schedule.rs index a5fedde..e3851a8 100644 --- a/sparse_strips/vello_hybrid/src/schedule.rs +++ b/sparse_strips/vello_hybrid/src/schedule.rs
@@ -176,6 +176,7 @@ only break in edge cases, and some of them are also only related to conversions from f64 to f32." )] +use crate::image_cache::ImageCache; use crate::render::common::GpuEncodedImage; use crate::{GpuStrip, RenderError, Scene}; use alloc::collections::VecDeque; @@ -269,6 +270,7 @@ &mut self, renderer: &mut R, scene: &Scene, + image_cache: &ImageCache, ) -> Result<(), RenderError> { let mut tile_state = mem::take(&mut self.tile_state); let wide_tiles_per_row = scene.wide.width_tiles(); @@ -287,6 +289,7 @@ wide_tile_y, wide_tile, &mut tile_state, + image_cache, )?; } } @@ -370,6 +373,7 @@ wide_tile_y: u16, tile: &WideTile, state: &mut TileState, + image_cache: &ImageCache, ) -> Result<(), RenderError> { state.stack.clear(); // Sentinel `TileEl` to indicate the end of the stack where we draw all @@ -402,7 +406,8 @@ let el = state.stack.last().unwrap(); let draw = self.draw_mut(el.round, clip_depth); - let (col_idx, rgba_or_slot, paint) = Self::process_paint(&fill.paint, scene, 0); + let (col_idx, rgba_or_slot, paint) = + Self::process_paint(&fill.paint, scene, 0, image_cache); let (x, y) = if clip_depth == 1 { (wide_tile_x + fill.x, wide_tile_y) @@ -429,7 +434,7 @@ .expect("Sparse strips are bound to u32 range"); let (col_idx, rgba_or_slot, paint) = - Self::process_paint(&alpha_fill.paint, scene, alpha_col); + Self::process_paint(&alpha_fill.paint, scene, alpha_col, image_cache); let (x, y) = if clip_depth == 1 { (wide_tile_x + alpha_fill.x, wide_tile_y) @@ -528,8 +533,13 @@ Ok(()) } - /// Process a paint and return (`col_idx`, `rgba_or_slot`, `paint_type`) - fn process_paint(paint: &Paint, scene: &Scene, alpha_col: u32) -> (u32, u32, u32) { + /// Process a paint and return (`col_idx`, `rgba_or_slot`, `paint`) + fn process_paint( + paint: &Paint, + scene: &Scene, + alpha_col: u32, + image_cache: &ImageCache, + ) -> (u32, u32, u32) { match paint { Paint::Solid(color) => { let rgba = color.as_premul_rgba8().to_u32(); @@ -546,9 +556,12 @@ match scene.encoded_paints.get(paint_id) { Some(EncodedPaint::Image(encoded_image)) => match &encoded_image.source { - ImageSource::OpaqueId(_) => { + ImageSource::OpaqueId(image_id) => { let paint_type = 2_u32; - let paint_packed = (paint_type << 30) | (paint_tex_id & 0x3FFFFFFF); + let skip_paint = !image_cache.contains(*image_id) as u32; + let paint_packed = (paint_type << 30) + | (skip_paint << 29) + | (paint_tex_id & 0x1FFFFFFF); (alpha_col, 0, paint_packed) } _ => unimplemented!("unsupported image source"),
diff --git a/sparse_strips/vello_sparse_shaders/shaders/render_strips.wgsl b/sparse_strips/vello_sparse_shaders/shaders/render_strips.wgsl index 7b8bc1b..3d33ebc 100644 --- a/sparse_strips/vello_sparse_shaders/shaders/render_strips.wgsl +++ b/sparse_strips/vello_sparse_shaders/shaders/render_strips.wgsl
@@ -112,8 +112,7 @@ let paint_type = instance.paint >> 30u; if paint_type == PAINT_TYPE_IMAGE { - let paint_tex_id = instance.paint & 0x3FFFFFFF; - + let paint_tex_id = instance.paint & 0x1FFFFFFFu; let encoded_image = unpack_encoded_image(paint_tex_id); // Vertex position within the texture out.sample_xy = encoded_image.translate @@ -179,7 +178,12 @@ let paint_type = in.paint >> 30u; if paint_type == PAINT_TYPE_IMAGE { - let paint_tex_id = in.paint & 0x3FFFFFFF; + let paint_tex_id = in.paint & 0x1FFFFFFFu; + let skip_paint = in.paint >> 29u & 1u; + if skip_paint == 1u { + discard; + } + let encoded_image = unpack_encoded_image(paint_tex_id); let image_offset = encoded_image.image_offset; let image_size = encoded_image.image_size;