diff options
author | 2019-12-15 06:19:07 +0100 | |
---|---|---|
committer | 2019-12-15 06:19:07 +0100 | |
commit | 09707f29fcf7fbd71570a43db214921043427c3f (patch) | |
tree | b3e045a64e81f58910c976b0478e6f7f91780ad0 /wgpu/src/image | |
parent | 27717bc70c3947f553a8b75da9789fe967994a31 (diff) | |
download | iced-09707f29fcf7fbd71570a43db214921043427c3f.tar.gz iced-09707f29fcf7fbd71570a43db214921043427c3f.tar.bz2 iced-09707f29fcf7fbd71570a43db214921043427c3f.zip |
Rerasterize SVGs when resized and refactor a bit
Diffstat (limited to 'wgpu/src/image')
-rw-r--r-- | wgpu/src/image/raster.rs | 176 | ||||
-rw-r--r-- | wgpu/src/image/vector.rs | 187 |
2 files changed, 363 insertions, 0 deletions
diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs new file mode 100644 index 00000000..fa107879 --- /dev/null +++ b/wgpu/src/image/raster.rs @@ -0,0 +1,176 @@ +use iced_native::image; +use std::{ + collections::{HashMap, HashSet}, + rc::Rc, +}; + +#[derive(Debug)] +pub enum Memory { + Host(::image::ImageBuffer<::image::Bgra<u8>, Vec<u8>>), + Device { + bind_group: Rc<wgpu::BindGroup>, + width: u32, + height: u32, + }, + NotFound, + Invalid, +} + +impl Memory { + pub fn dimensions(&self) -> (u32, u32) { + match self { + Memory::Host(image) => image.dimensions(), + Memory::Device { width, height, .. } => (*width, *height), + Memory::NotFound => (1, 1), + Memory::Invalid => (1, 1), + } + } + + pub fn upload( + &mut self, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + texture_layout: &wgpu::BindGroupLayout, + ) -> Option<Rc<wgpu::BindGroup>> { + match self { + Memory::Host(image) => { + let (width, height) = image.dimensions(); + + let extent = wgpu::Extent3d { + width, + height, + depth: 1, + }; + + let texture = device.create_texture(&wgpu::TextureDescriptor { + size: extent, + array_layer_count: 1, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + usage: wgpu::TextureUsage::COPY_DST + | wgpu::TextureUsage::SAMPLED, + }); + + let temp_buf = { + let flat_samples = image.as_flat_samples(); + let slice = flat_samples.as_slice(); + + device + .create_buffer_mapped( + slice.len(), + wgpu::BufferUsage::COPY_SRC, + ) + .fill_from_slice(slice) + }; + + encoder.copy_buffer_to_texture( + wgpu::BufferCopyView { + buffer: &temp_buf, + offset: 0, + row_pitch: 4 * width as u32, + image_height: height as u32, + }, + wgpu::TextureCopyView { + texture: &texture, + array_layer: 0, + mip_level: 0, + origin: wgpu::Origin3d { + x: 0.0, + y: 0.0, + z: 0.0, + }, + }, + extent, + ); + + let bind_group = + device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: texture_layout, + bindings: &[wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::TextureView( + &texture.create_default_view(), + ), + }], + }); + + let bind_group = Rc::new(bind_group); + + *self = Memory::Device { + bind_group: bind_group.clone(), + width, + height, + }; + + Some(bind_group) + } + Memory::Device { bind_group, .. } => Some(bind_group.clone()), + Memory::NotFound => None, + Memory::Invalid => None, + } + } +} + +#[derive(Debug)] +pub struct Cache { + map: HashMap<u64, Memory>, + hits: HashSet<u64>, +} + +impl Cache { + pub fn new() -> Self { + Self { + map: HashMap::new(), + hits: HashSet::new(), + } + } + + pub fn load(&mut self, handle: &image::Handle) -> &mut Memory { + if self.contains(handle) { + return self.get(handle).unwrap(); + } + + let memory = match handle.data() { + image::Data::Path(path) => { + if let Ok(image) = ::image::open(path) { + Memory::Host(image.to_bgra()) + } else { + Memory::NotFound + } + } + image::Data::Bytes(bytes) => { + if let Ok(image) = ::image::load_from_memory(&bytes) { + Memory::Host(image.to_bgra()) + } else { + Memory::Invalid + } + } + }; + + self.insert(handle, memory); + self.get(handle).unwrap() + } + + pub fn trim(&mut self) { + let hits = &self.hits; + + self.map.retain(|k, _| hits.contains(k)); + self.hits.clear(); + } + + fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory> { + let _ = self.hits.insert(handle.id()); + + self.map.get_mut(&handle.id()) + } + + fn insert(&mut self, handle: &image::Handle, memory: Memory) { + let _ = self.map.insert(handle.id(), memory); + } + + fn contains(&self, handle: &image::Handle) -> bool { + self.map.contains_key(&handle.id()) + } +} diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs new file mode 100644 index 00000000..0894c6bc --- /dev/null +++ b/wgpu/src/image/vector.rs @@ -0,0 +1,187 @@ +use iced_native::svg; +use std::{ + collections::{HashMap, HashSet}, + rc::Rc, +}; + +pub struct Svg { + tree: resvg::usvg::Tree, +} + +impl Svg { + pub fn viewport_dimensions(&self) -> (u32, u32) { + let size = self.tree.svg_node().size; + + (size.width() as u32, size.height() as u32) + } +} + +impl std::fmt::Debug for Svg { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Svg") + } +} + +#[derive(Debug)] +pub struct Cache { + svgs: HashMap<u64, Svg>, + rasterized: HashMap<(u64, u32, u32), Rc<wgpu::BindGroup>>, + svg_hits: HashSet<u64>, + rasterized_hits: HashSet<(u64, u32, u32)>, +} + +impl Cache { + pub fn new() -> Self { + Self { + svgs: HashMap::new(), + rasterized: HashMap::new(), + svg_hits: HashSet::new(), + rasterized_hits: HashSet::new(), + } + } + + pub fn load(&mut self, handle: &svg::Handle) -> Option<&Svg> { + if self.svgs.contains_key(&handle.id()) { + return self.svgs.get(&handle.id()); + } + + let opt = resvg::Options::default(); + + match resvg::usvg::Tree::from_file(handle.path(), &opt.usvg) { + Ok(tree) => { + let _ = self.svgs.insert(handle.id(), Svg { tree }); + } + Err(_) => {} + }; + + self.svgs.get(&handle.id()) + } + + pub fn upload( + &mut self, + handle: &svg::Handle, + [width, height]: [f32; 2], + scale: f32, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + texture_layout: &wgpu::BindGroupLayout, + ) -> Option<Rc<wgpu::BindGroup>> { + let id = handle.id(); + + let (width, height) = ( + (scale * width).round() as u32, + (scale * height).round() as u32, + ); + + // TODO: Optimize! + // We currently rerasterize the SVG when its size changes. This is slow + // as heck. A GPU rasterizer like `pathfinder` may perform better. + // It would be cool to be able to smooth resize the `tiger` example. + if let Some(bind_group) = self.rasterized.get(&(id, width, height)) { + let _ = self.svg_hits.insert(id); + let _ = self.rasterized_hits.insert((id, width, height)); + + return Some(bind_group.clone()); + } + + match self.load(handle) { + Some(svg) => { + let extent = wgpu::Extent3d { + width, + height, + depth: 1, + }; + + let texture = device.create_texture(&wgpu::TextureDescriptor { + size: extent, + array_layer_count: 1, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + usage: wgpu::TextureUsage::COPY_DST + | wgpu::TextureUsage::SAMPLED, + }); + + let temp_buf = { + let screen_size = + resvg::ScreenSize::new(width, height).unwrap(); + + let mut canvas = resvg::raqote::DrawTarget::new( + width as i32, + height as i32, + ); + + resvg::backend_raqote::render_to_canvas( + &svg.tree, + &resvg::Options::default(), + screen_size, + &mut canvas, + ); + + let slice = canvas.get_data(); + + device + .create_buffer_mapped( + slice.len(), + wgpu::BufferUsage::COPY_SRC, + ) + .fill_from_slice(slice) + }; + + encoder.copy_buffer_to_texture( + wgpu::BufferCopyView { + buffer: &temp_buf, + offset: 0, + row_pitch: 4 * width as u32, + image_height: height as u32, + }, + wgpu::TextureCopyView { + texture: &texture, + array_layer: 0, + mip_level: 0, + origin: wgpu::Origin3d { + x: 0.0, + y: 0.0, + z: 0.0, + }, + }, + extent, + ); + + let bind_group = + device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: texture_layout, + bindings: &[wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::TextureView( + &texture.create_default_view(), + ), + }], + }); + + let bind_group = Rc::new(bind_group); + + let _ = self + .rasterized + .insert((id, width, height), bind_group.clone()); + + let _ = self.svg_hits.insert(id); + let _ = self.rasterized_hits.insert((id, width, height)); + + Some(bind_group) + } + None => None, + } + } + + pub fn trim(&mut self) { + let svg_hits = &self.svg_hits; + let rasterized_hits = &self.rasterized_hits; + + self.svgs.retain(|k, _| svg_hits.contains(k)); + self.rasterized.retain(|k, _| rasterized_hits.contains(k)); + self.svg_hits.clear(); + self.rasterized_hits.clear(); + } +} |