diff options
Diffstat (limited to 'wgpu/src/image.rs')
-rw-r--r-- | wgpu/src/image.rs | 352 |
1 files changed, 246 insertions, 106 deletions
diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index f7ed67c3..d3603676 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -1,15 +1,23 @@ +mod atlas; + #[cfg(feature = "image")] mod raster; + #[cfg(feature = "svg")] mod vector; use crate::Transformation; -use iced_native::{image, svg, Rectangle}; +use atlas::Atlas; +use iced_native::Rectangle; +use std::cell::RefCell; use std::mem; -#[cfg(any(feature = "image", feature = "svg"))] -use std::cell::RefCell; +#[cfg(feature = "image")] +use iced_native::image; + +#[cfg(feature = "svg")] +use iced_native::svg; #[derive(Debug)] pub struct Pipeline { @@ -24,7 +32,10 @@ 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 { @@ -174,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, + }, ], }, ], @@ -191,13 +217,26 @@ 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 { #[cfg(feature = "image")] raster_cache: RefCell::new(raster::Cache::new()), + #[cfg(feature = "svg")] vector_cache: RefCell::new(vector::Cache::new()), @@ -207,7 +246,10 @@ impl Pipeline { indices, instances, constants: constant_bind_group, + texture, + texture_version: texture_atlas.layer_count(), texture_layout, + texture_atlas, } } @@ -231,12 +273,72 @@ impl Pipeline { &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 { @@ -251,123 +353,90 @@ 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 { - let uploaded_texture = match &image.handle { - Handle::Raster(_handle) => { - #[cfg(feature = "image")] - { - let mut cache = self.raster_cache.borrow_mut(); - let memory = cache.load(&_handle); - - memory.upload(device, encoder, &self.texture_layout) - } - - #[cfg(not(feature = "image"))] - None - } - Handle::Vector(_handle) => { - #[cfg(feature = "svg")] - { - let mut cache = self.vector_cache.borrow_mut(); - - cache.upload( - _handle, - image.scale, - _scale, - device, - encoder, - &self.texture_layout, - ) - } - - #[cfg(not(feature = "svg"))] - None - } - }; - - if let Some(texture) = uploaded_texture { - 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, - ); - } - } + ], + depth_stencil_attachment: None, + }); + + 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; } } pub fn trim_cache(&mut self) { #[cfg(feature = "image")] - self.raster_cache.borrow_mut().trim(); + self.raster_cache.borrow_mut().trim(&mut self.texture_atlas); #[cfg(feature = "svg")] - self.vector_cache.borrow_mut().trim(); + self.vector_cache.borrow_mut().trim(&mut self.texture_atlas); } } pub struct Image { 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), } @@ -395,10 +464,17 @@ const QUAD_VERTS: [Vertex; 4] = [ ]; #[repr(C)] -#[derive(Clone, Copy)] +#[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)] @@ -406,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); +} |