diff options
author | 2020-02-28 14:38:42 +0100 | |
---|---|---|
committer | 2020-02-28 14:38:42 +0100 | |
commit | 4e7159c22c6be90f61aa715d5eb6811f805cb597 (patch) | |
tree | 77faf86bc65142d3c224b240a0c5922b7f4f9025 /wgpu/src/image | |
parent | fc55e3a3df9e08d361985b01528d2a6095d98aba (diff) | |
download | iced-4e7159c22c6be90f61aa715d5eb6811f805cb597.tar.gz iced-4e7159c22c6be90f61aa715d5eb6811f805cb597.tar.bz2 iced-4e7159c22c6be90f61aa715d5eb6811f805cb597.zip |
Stop creating image pipeline when unnecessary
Diffstat (limited to 'wgpu/src/image')
-rw-r--r-- | wgpu/src/image/atlas.rs | 361 | ||||
-rw-r--r-- | wgpu/src/image/atlas/allocation.rs | 35 | ||||
-rw-r--r-- | wgpu/src/image/atlas/allocator.rs | 69 | ||||
-rw-r--r-- | wgpu/src/image/atlas/entry.rs | 26 | ||||
-rw-r--r-- | wgpu/src/image/atlas/layer.rs | 17 | ||||
-rw-r--r-- | wgpu/src/image/raster.rs | 2 | ||||
-rw-r--r-- | wgpu/src/image/vector.rs | 2 |
7 files changed, 510 insertions, 2 deletions
diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs new file mode 100644 index 00000000..86a5ff49 --- /dev/null +++ b/wgpu/src/image/atlas.rs @@ -0,0 +1,361 @@ +pub mod entry; + +mod allocation; +mod allocator; +mod layer; + +pub use allocation::Allocation; +pub use entry::Entry; +pub use layer::Layer; + +use allocator::Allocator; + +pub const SIZE: u32 = 2048; + +#[derive(Debug)] +pub struct Atlas { + texture: wgpu::Texture, + texture_view: wgpu::TextureView, + layers: Vec<Layer>, +} + +impl Atlas { + pub fn new(device: &wgpu::Device) -> Self { + let extent = wgpu::Extent3d { + width: SIZE, + height: SIZE, + depth: 1, + }; + + let texture = device.create_texture(&wgpu::TextureDescriptor { + size: extent, + array_layer_count: 2, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + usage: wgpu::TextureUsage::COPY_DST + | wgpu::TextureUsage::COPY_SRC + | wgpu::TextureUsage::SAMPLED, + }); + + let texture_view = texture.create_default_view(); + + Atlas { + texture, + texture_view, + layers: vec![Layer::Empty, Layer::Empty], + } + } + + pub fn view(&self) -> &wgpu::TextureView { + &self.texture_view + } + + pub fn layer_count(&self) -> usize { + self.layers.len() + } + + pub fn upload<C>( + &mut self, + width: u32, + height: u32, + data: &[C], + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + ) -> Option<Entry> + where + C: Copy + 'static, + { + let entry = { + let current_size = self.layers.len(); + let entry = self.allocate(width, height)?; + + // We grow the internal texture after allocating if necessary + let new_layers = self.layers.len() - current_size; + self.grow(new_layers, device, encoder); + + entry + }; + + log::info!("Allocated atlas entry: {:?}", entry); + + let buffer = device + .create_buffer_mapped(data.len(), wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(data); + + match &entry { + Entry::Contiguous(allocation) => { + self.upload_allocation( + &buffer, + width, + height, + 0, + &allocation, + encoder, + ); + } + Entry::Fragmented { fragments, .. } => { + for fragment in fragments { + let (x, y) = fragment.position; + let offset = (y * width + x) as usize * 4; + + self.upload_allocation( + &buffer, + width, + height, + offset, + &fragment.allocation, + encoder, + ); + } + } + } + + log::info!("Current atlas: {:?}", self); + + Some(entry) + } + + pub fn remove(&mut self, entry: &Entry) { + log::info!("Removing atlas entry: {:?}", entry); + + match entry { + Entry::Contiguous(allocation) => { + self.deallocate(allocation); + } + Entry::Fragmented { fragments, .. } => { + for fragment in fragments { + self.deallocate(&fragment.allocation); + } + } + } + } + + fn allocate(&mut self, width: u32, height: u32) -> Option<Entry> { + // Allocate one layer if texture fits perfectly + if width == SIZE && height == SIZE { + let mut empty_layers = self + .layers + .iter_mut() + .enumerate() + .filter(|(_, layer)| layer.is_empty()); + + if let Some((i, layer)) = empty_layers.next() { + *layer = Layer::Full; + + return Some(Entry::Contiguous(Allocation::Full { layer: i })); + } + + self.layers.push(Layer::Full); + + return Some(Entry::Contiguous(Allocation::Full { + layer: self.layers.len() - 1, + })); + } + + // Split big textures across multiple layers + if width > SIZE || height > SIZE { + let mut fragments = Vec::new(); + let mut y = 0; + + while y < height { + let height = std::cmp::min(height - y, SIZE); + let mut x = 0; + + while x < width { + let width = std::cmp::min(width - x, SIZE); + + let allocation = self.allocate(width, height)?; + + if let Entry::Contiguous(allocation) = allocation { + fragments.push(entry::Fragment { + position: (x, y), + allocation, + }); + } + + x += width; + } + + y += height; + } + + return Some(Entry::Fragmented { + size: (width, height), + fragments, + }); + } + + // Try allocating on an existing layer + for (i, layer) in self.layers.iter_mut().enumerate() { + match layer { + Layer::Empty => { + let mut allocator = Allocator::new(SIZE); + + if let Some(region) = allocator.allocate(width, height) { + *layer = Layer::Busy(allocator); + + return Some(Entry::Contiguous(Allocation::Partial { + region, + layer: i, + })); + } + } + Layer::Busy(allocator) => { + if let Some(region) = allocator.allocate(width, height) { + return Some(Entry::Contiguous(Allocation::Partial { + region, + layer: i, + })); + } + } + _ => {} + } + } + + // Create new layer with atlas allocator + let mut allocator = Allocator::new(SIZE); + + if let Some(region) = allocator.allocate(width, height) { + self.layers.push(Layer::Busy(allocator)); + + return Some(Entry::Contiguous(Allocation::Partial { + region, + layer: self.layers.len() - 1, + })); + } + + // We ran out of memory (?) + None + } + + fn deallocate(&mut self, allocation: &Allocation) { + log::info!("Deallocating atlas: {:?}", allocation); + + match allocation { + Allocation::Full { layer } => { + self.layers[*layer] = Layer::Empty; + } + Allocation::Partial { layer, region } => { + let layer = &mut self.layers[*layer]; + + if let Layer::Busy(allocator) = layer { + allocator.deallocate(region); + + if allocator.is_empty() { + *layer = Layer::Empty; + } + } + } + } + } + + fn upload_allocation( + &mut self, + buffer: &wgpu::Buffer, + image_width: u32, + image_height: u32, + offset: usize, + allocation: &Allocation, + encoder: &mut wgpu::CommandEncoder, + ) { + let (x, y) = allocation.position(); + let (width, height) = allocation.size(); + let layer = allocation.layer(); + + let extent = wgpu::Extent3d { + width, + height, + depth: 1, + }; + + encoder.copy_buffer_to_texture( + wgpu::BufferCopyView { + buffer, + offset: offset as u64, + row_pitch: 4 * image_width, + image_height, + }, + wgpu::TextureCopyView { + texture: &self.texture, + array_layer: layer as u32, + mip_level: 0, + origin: wgpu::Origin3d { + x: x as f32, + y: y as f32, + z: 0.0, + }, + }, + extent, + ); + } + + fn grow( + &mut self, + amount: usize, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + ) { + if amount == 0 { + return; + } + + let new_texture = device.create_texture(&wgpu::TextureDescriptor { + size: wgpu::Extent3d { + width: SIZE, + height: SIZE, + depth: 1, + }, + array_layer_count: self.layers.len() as u32, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + usage: wgpu::TextureUsage::COPY_DST + | wgpu::TextureUsage::COPY_SRC + | wgpu::TextureUsage::SAMPLED, + }); + + let amount_to_copy = self.layers.len() - amount; + + for (i, layer) in + self.layers.iter_mut().take(amount_to_copy).enumerate() + { + if layer.is_empty() { + continue; + } + + encoder.copy_texture_to_texture( + wgpu::TextureCopyView { + texture: &self.texture, + array_layer: i as u32, + mip_level: 0, + origin: wgpu::Origin3d { + x: 0.0, + y: 0.0, + z: 0.0, + }, + }, + wgpu::TextureCopyView { + texture: &new_texture, + array_layer: i as u32, + mip_level: 0, + origin: wgpu::Origin3d { + x: 0.0, + y: 0.0, + z: 0.0, + }, + }, + wgpu::Extent3d { + width: SIZE, + height: SIZE, + depth: 1, + }, + ); + } + + self.texture = new_texture; + self.texture_view = self.texture.create_default_view(); + } +} diff --git a/wgpu/src/image/atlas/allocation.rs b/wgpu/src/image/atlas/allocation.rs new file mode 100644 index 00000000..59b7239f --- /dev/null +++ b/wgpu/src/image/atlas/allocation.rs @@ -0,0 +1,35 @@ +use crate::image::atlas::{self, allocator}; + +#[derive(Debug)] +pub enum Allocation { + Partial { + layer: usize, + region: allocator::Region, + }, + Full { + layer: usize, + }, +} + +impl Allocation { + pub fn position(&self) -> (u32, u32) { + match self { + Allocation::Partial { region, .. } => region.position(), + Allocation::Full { .. } => (0, 0), + } + } + + pub fn size(&self) -> (u32, u32) { + match self { + Allocation::Partial { region, .. } => region.size(), + Allocation::Full { .. } => (atlas::SIZE, atlas::SIZE), + } + } + + pub fn layer(&self) -> usize { + match self { + Allocation::Partial { layer, .. } => *layer, + Allocation::Full { layer } => *layer, + } + } +} diff --git a/wgpu/src/image/atlas/allocator.rs b/wgpu/src/image/atlas/allocator.rs new file mode 100644 index 00000000..7a4ff5b1 --- /dev/null +++ b/wgpu/src/image/atlas/allocator.rs @@ -0,0 +1,69 @@ +use guillotiere::{AtlasAllocator, Size}; + +pub struct Allocator { + raw: AtlasAllocator, + allocations: usize, +} + +impl Allocator { + pub fn new(size: u32) -> Allocator { + let raw = AtlasAllocator::new(Size::new(size as i32, size as i32)); + + Allocator { + raw, + allocations: 0, + } + } + + pub fn allocate(&mut self, width: u32, height: u32) -> Option<Region> { + let allocation = + self.raw.allocate(Size::new(width as i32, height as i32))?; + + self.allocations += 1; + + Some(Region { allocation }) + } + + pub fn deallocate(&mut self, region: &Region) { + self.raw.deallocate(region.allocation.id); + + self.allocations = self.allocations.saturating_sub(1); + } + + pub fn is_empty(&self) -> bool { + self.allocations == 0 + } +} + +pub struct Region { + allocation: guillotiere::Allocation, +} + +impl Region { + pub fn position(&self) -> (u32, u32) { + let rectangle = &self.allocation.rectangle; + + (rectangle.min.x as u32, rectangle.min.y as u32) + } + + pub fn size(&self) -> (u32, u32) { + let size = self.allocation.rectangle.size(); + + (size.width as u32, size.height as u32) + } +} + +impl std::fmt::Debug for Allocator { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Allocator") + } +} + +impl std::fmt::Debug for Region { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Region") + .field("id", &self.allocation.id) + .field("rectangle", &self.allocation.rectangle) + .finish() + } +} diff --git a/wgpu/src/image/atlas/entry.rs b/wgpu/src/image/atlas/entry.rs new file mode 100644 index 00000000..0310fc54 --- /dev/null +++ b/wgpu/src/image/atlas/entry.rs @@ -0,0 +1,26 @@ +use crate::image::atlas; + +#[derive(Debug)] +pub enum Entry { + Contiguous(atlas::Allocation), + Fragmented { + size: (u32, u32), + fragments: Vec<Fragment>, + }, +} + +impl Entry { + #[cfg(feature = "image")] + pub fn size(&self) -> (u32, u32) { + match self { + Entry::Contiguous(allocation) => allocation.size(), + Entry::Fragmented { size, .. } => *size, + } + } +} + +#[derive(Debug)] +pub struct Fragment { + pub position: (u32, u32), + pub allocation: atlas::Allocation, +} diff --git a/wgpu/src/image/atlas/layer.rs b/wgpu/src/image/atlas/layer.rs new file mode 100644 index 00000000..b1084ed9 --- /dev/null +++ b/wgpu/src/image/atlas/layer.rs @@ -0,0 +1,17 @@ +use crate::image::atlas::Allocator; + +#[derive(Debug)] +pub enum Layer { + Empty, + Busy(Allocator), + Full, +} + +impl Layer { + pub fn is_empty(&self) -> bool { + match self { + Layer::Empty => true, + _ => false, + } + } +} diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index 883c32f7..3edec57e 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -1,4 +1,4 @@ -use crate::texture::atlas::{self, Atlas}; +use crate::image::atlas::{self, Atlas}; use iced_native::image; use std::collections::{HashMap, HashSet}; diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index 6b12df54..bae0f82f 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -1,4 +1,4 @@ -use crate::texture::atlas::{self, Atlas}; +use crate::image::atlas::{self, Atlas}; use iced_native::svg; use std::collections::{HashMap, HashSet}; |