diff options
Diffstat (limited to 'wgpu/src/image.rs')
-rw-r--r-- | wgpu/src/image.rs | 480 |
1 files changed, 283 insertions, 197 deletions
diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 5dc972ac..d3603676 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -1,11 +1,30 @@ +mod atlas; + +#[cfg(feature = "image")] +mod raster; + +#[cfg(feature = "svg")] +mod vector; + use crate::Transformation; +use atlas::Atlas; + use iced_native::Rectangle; +use std::cell::RefCell; +use std::mem; + +#[cfg(feature = "image")] +use iced_native::image; -use std::{cell::RefCell, collections::HashMap, mem, rc::Rc}; +#[cfg(feature = "svg")] +use iced_native::svg; #[derive(Debug)] pub struct Pipeline { - cache: RefCell<HashMap<String, Memory>>, + #[cfg(feature = "image")] + raster_cache: RefCell<raster::Cache>, + #[cfg(feature = "svg")] + vector_cache: RefCell<vector::Cache>, pipeline: wgpu::RenderPipeline, uniforms: wgpu::Buffer, @@ -13,11 +32,14 @@ pub struct Pipeline { indices: wgpu::Buffer, instances: wgpu::Buffer, constants: wgpu::BindGroup, + texture: wgpu::BindGroup, + texture_version: usize, texture_layout: wgpu::BindGroupLayout, + texture_atlas: Atlas, } impl Pipeline { - pub fn new(device: &wgpu::Device) -> Self { + pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self { let sampler = device.create_sampler(&wgpu::SamplerDescriptor { address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_v: wgpu::AddressMode::ClampToEdge, @@ -124,7 +146,7 @@ impl Pipeline { }), primitive_topology: wgpu::PrimitiveTopology::TriangleList, color_states: &[wgpu::ColorStateDescriptor { - format: wgpu::TextureFormat::Bgra8UnormSrgb, + format, color_blend: wgpu::BlendDescriptor { src_factor: wgpu::BlendFactor::SrcAlpha, dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, @@ -163,6 +185,21 @@ impl Pipeline { format: wgpu::VertexFormat::Float2, offset: 4 * 2, }, + wgpu::VertexAttributeDescriptor { + shader_location: 3, + format: wgpu::VertexFormat::Float2, + offset: 4 * 4, + }, + wgpu::VertexAttributeDescriptor { + shader_location: 4, + format: wgpu::VertexFormat::Float2, + offset: 4 * 6, + }, + wgpu::VertexAttributeDescriptor { + shader_location: 5, + format: wgpu::VertexFormat::Uint, + offset: 4 * 8, + }, ], }, ], @@ -180,12 +217,28 @@ impl Pipeline { .fill_from_slice(&QUAD_INDICES); let instances = device.create_buffer(&wgpu::BufferDescriptor { - size: mem::size_of::<Instance>() as u64, + size: mem::size_of::<Instance>() as u64 * Instance::MAX as u64, usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST, }); + let texture_atlas = Atlas::new(device); + + let texture = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &texture_layout, + bindings: &[wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::TextureView( + &texture_atlas.view(), + ), + }], + }); + Pipeline { - cache: RefCell::new(HashMap::new()), + #[cfg(feature = "image")] + raster_cache: RefCell::new(raster::Cache::new()), + + #[cfg(feature = "svg")] + vector_cache: RefCell::new(vector::Cache::new()), pipeline, uniforms: uniforms_buffer, @@ -193,39 +246,99 @@ impl Pipeline { indices, instances, constants: constant_bind_group, + texture, + texture_version: texture_atlas.layer_count(), texture_layout, + texture_atlas, } } - pub fn dimensions(&self, path: &str) -> (u32, u32) { - self.load(path); + #[cfg(feature = "image")] + pub fn dimensions(&self, handle: &image::Handle) -> (u32, u32) { + let mut cache = self.raster_cache.borrow_mut(); + let memory = cache.load(&handle); - self.cache.borrow().get(path).unwrap().dimensions() + memory.dimensions() } - fn load(&self, path: &str) { - if !self.cache.borrow().contains_key(path) { - let memory = if let Ok(image) = image::open(path) { - Memory::Host { - image: image.to_bgra(), - } - } else { - Memory::NotFound - }; + #[cfg(feature = "svg")] + pub fn viewport_dimensions(&self, handle: &svg::Handle) -> (u32, u32) { + let mut cache = self.vector_cache.borrow_mut(); + let svg = cache.load(&handle); - let _ = self.cache.borrow_mut().insert(path.to_string(), memory); - } + svg.viewport_dimensions() } pub fn draw( &mut self, device: &mut wgpu::Device, encoder: &mut wgpu::CommandEncoder, - instances: &[Image], + images: &[Image], transformation: Transformation, bounds: Rectangle<u32>, target: &wgpu::TextureView, + _scale: f32, ) { + let instances: &mut Vec<Instance> = &mut Vec::new(); + + #[cfg(feature = "image")] + let mut raster_cache = self.raster_cache.borrow_mut(); + + #[cfg(feature = "svg")] + let mut vector_cache = self.vector_cache.borrow_mut(); + + for image in images { + match &image.handle { + #[cfg(feature = "image")] + Handle::Raster(handle) => { + if let Some(atlas_entry) = raster_cache.upload( + handle, + device, + encoder, + &mut self.texture_atlas, + ) { + add_instances(image, atlas_entry, instances); + } + } + #[cfg(feature = "svg")] + Handle::Vector(handle) => { + if let Some(atlas_entry) = vector_cache.upload( + handle, + image.size, + _scale, + device, + encoder, + &mut self.texture_atlas, + ) { + add_instances(image, atlas_entry, instances); + } + } + } + } + + if instances.is_empty() { + return; + } + + let texture_version = self.texture_atlas.layer_count(); + + if self.texture_version != texture_version { + log::info!("Atlas has grown. Recreating bind group..."); + + self.texture = + device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &self.texture_layout, + bindings: &[wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::TextureView( + &self.texture_atlas.view(), + ), + }], + }); + + self.texture_version = texture_version; + } + let uniforms_buffer = device .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) .fill_from_slice(&[Uniforms { @@ -240,193 +353,94 @@ impl Pipeline { std::mem::size_of::<Uniforms>() as u64, ); - // TODO: Batch draw calls using a texture atlas - // Guillotière[1] by @nical can help us a lot here. - // - // [1]: https://github.com/nical/guillotiere - for image in instances { - self.load(&image.path); - - if let Some(texture) = self - .cache - .borrow_mut() - .get_mut(&image.path) - .unwrap() - .upload(device, encoder, &self.texture_layout) - { - let instance_buffer = device - .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) - .fill_from_slice(&[Instance { - _position: image.position, - _scale: image.scale, - }]); - - encoder.copy_buffer_to_buffer( - &instance_buffer, - 0, - &self.instances, - 0, - mem::size_of::<Instance>() as u64, - ); - - { - let mut render_pass = encoder.begin_render_pass( - &wgpu::RenderPassDescriptor { - color_attachments: &[ - wgpu::RenderPassColorAttachmentDescriptor { - attachment: target, - resolve_target: None, - load_op: wgpu::LoadOp::Load, - store_op: wgpu::StoreOp::Store, - clear_color: wgpu::Color { - r: 0.0, - g: 0.0, - b: 0.0, - a: 0.0, - }, - }, - ], - depth_stencil_attachment: None, + let instances_buffer = device + .create_buffer_mapped(instances.len(), wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(&instances); + + let mut i = 0; + let total = instances.len(); + + while i < total { + let end = (i + Instance::MAX).min(total); + let amount = end - i; + + encoder.copy_buffer_to_buffer( + &instances_buffer, + (i * std::mem::size_of::<Instance>()) as u64, + &self.instances, + 0, + (amount * std::mem::size_of::<Instance>()) as u64, + ); + + let mut render_pass = + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[ + wgpu::RenderPassColorAttachmentDescriptor { + attachment: target, + resolve_target: None, + load_op: wgpu::LoadOp::Load, + store_op: wgpu::StoreOp::Store, + clear_color: wgpu::Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 0.0, + }, }, - ); - - render_pass.set_pipeline(&self.pipeline); - render_pass.set_bind_group(0, &self.constants, &[]); - render_pass.set_bind_group(1, &texture, &[]); - render_pass.set_index_buffer(&self.indices, 0); - render_pass.set_vertex_buffers( - 0, - &[(&self.vertices, 0), (&self.instances, 0)], - ); - render_pass.set_scissor_rect( - bounds.x, - bounds.y, - bounds.width, - bounds.height, - ); - - render_pass.draw_indexed( - 0..QUAD_INDICES.len() as u32, - 0, - 0..1 as u32, - ); - } - } - } - } -} - -#[derive(Debug)] -enum Memory { - Host { - image: image::ImageBuffer<image::Bgra<u8>, Vec<u8>>, - }, - Device { - bind_group: Rc<wgpu::BindGroup>, - width: u32, - height: u32, - }, - NotFound, -} + ], + depth_stencil_attachment: None, + }); -impl Memory { - fn dimensions(&self) -> (u32, u32) { - match self { - Memory::Host { image } => image.dimensions(), - Memory::Device { width, height, .. } => (*width, *height), - Memory::NotFound => (1, 1), + render_pass.set_pipeline(&self.pipeline); + render_pass.set_bind_group(0, &self.constants, &[]); + render_pass.set_bind_group(1, &self.texture, &[]); + render_pass.set_index_buffer(&self.indices, 0); + render_pass.set_vertex_buffers( + 0, + &[(&self.vertices, 0), (&self.instances, 0)], + ); + + render_pass.set_scissor_rect( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ); + + render_pass.draw_indexed( + 0..QUAD_INDICES.len() as u32, + 0, + 0..amount as u32, + ); + + i += Instance::MAX; } } - 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 slice = image.clone().into_raw(); - - let temp_buf = 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, - }; + pub fn trim_cache(&mut self) { + #[cfg(feature = "image")] + self.raster_cache.borrow_mut().trim(&mut self.texture_atlas); - Some(bind_group) - } - Memory::Device { bind_group, .. } => Some(bind_group.clone()), - Memory::NotFound => None, - } + #[cfg(feature = "svg")] + self.vector_cache.borrow_mut().trim(&mut self.texture_atlas); } } pub struct Image { - pub path: String, + pub handle: Handle, pub position: [f32; 2], - pub scale: [f32; 2], + pub size: [f32; 2], +} + +pub enum Handle { + #[cfg(feature = "image")] + Raster(image::Handle), + + #[cfg(feature = "svg")] + Vector(svg::Handle), } +#[repr(C)] #[derive(Clone, Copy)] pub struct Vertex { _position: [f32; 2], @@ -449,10 +463,18 @@ const QUAD_VERTS: [Vertex; 4] = [ }, ]; -#[derive(Clone, Copy)] +#[repr(C)] +#[derive(Debug, Clone, Copy)] struct Instance { _position: [f32; 2], - _scale: [f32; 2], + _size: [f32; 2], + _position_in_atlas: [f32; 2], + _size_in_atlas: [f32; 2], + _layer: u32, +} + +impl Instance { + pub const MAX: usize = 1_000; } #[repr(C)] @@ -460,3 +482,67 @@ struct Instance { struct Uniforms { transform: [f32; 16], } + +fn add_instances( + image: &Image, + entry: &atlas::Entry, + instances: &mut Vec<Instance>, +) { + match entry { + atlas::Entry::Contiguous(allocation) => { + add_instance(image.position, image.size, allocation, instances); + } + atlas::Entry::Fragmented { fragments, size } => { + let scaling_x = image.size[0] / size.0 as f32; + let scaling_y = image.size[1] / size.1 as f32; + + for fragment in fragments { + let allocation = &fragment.allocation; + + let [x, y] = image.position; + let (fragment_x, fragment_y) = fragment.position; + let (fragment_width, fragment_height) = allocation.size(); + + let position = [ + x + fragment_x as f32 * scaling_x, + y + fragment_y as f32 * scaling_y, + ]; + + let size = [ + fragment_width as f32 * scaling_x, + fragment_height as f32 * scaling_y, + ]; + + add_instance(position, size, allocation, instances); + } + } + } +} + +#[inline] +fn add_instance( + position: [f32; 2], + size: [f32; 2], + allocation: &atlas::Allocation, + instances: &mut Vec<Instance>, +) { + let (x, y) = allocation.position(); + let (width, height) = allocation.size(); + let layer = allocation.layer(); + + let instance = Instance { + _position: position, + _size: size, + _position_in_atlas: [ + (x as f32 + 0.5) / atlas::SIZE as f32, + (y as f32 + 0.5) / atlas::SIZE as f32, + ], + _size_in_atlas: [ + (width as f32 - 1.0) / atlas::SIZE as f32, + (height as f32 - 1.0) / atlas::SIZE as f32, + ], + _layer: layer as u32, + }; + + instances.push(instance); +} |