From 4e7159c22c6be90f61aa715d5eb6811f805cb597 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 28 Feb 2020 14:38:42 +0100 Subject: Stop creating image pipeline when unnecessary --- wgpu/src/image.rs | 74 +++---- wgpu/src/image/atlas.rs | 361 +++++++++++++++++++++++++++++++++++ wgpu/src/image/atlas/allocation.rs | 35 ++++ wgpu/src/image/atlas/allocator.rs | 69 +++++++ wgpu/src/image/atlas/entry.rs | 26 +++ wgpu/src/image/atlas/layer.rs | 17 ++ wgpu/src/image/raster.rs | 2 +- wgpu/src/image/vector.rs | 2 +- wgpu/src/lib.rs | 9 +- wgpu/src/renderer.rs | 103 ++++++---- wgpu/src/texture.rs | 3 - wgpu/src/texture/atlas.rs | 361 ----------------------------------- wgpu/src/texture/atlas/allocation.rs | 35 ---- wgpu/src/texture/atlas/allocator.rs | 69 ------- wgpu/src/texture/atlas/entry.rs | 25 --- wgpu/src/texture/atlas/layer.rs | 17 -- 16 files changed, 618 insertions(+), 590 deletions(-) create mode 100644 wgpu/src/image/atlas.rs create mode 100644 wgpu/src/image/atlas/allocation.rs create mode 100644 wgpu/src/image/atlas/allocator.rs create mode 100644 wgpu/src/image/atlas/entry.rs create mode 100644 wgpu/src/image/atlas/layer.rs delete mode 100644 wgpu/src/texture.rs delete mode 100644 wgpu/src/texture/atlas.rs delete mode 100644 wgpu/src/texture/atlas/allocation.rs delete mode 100644 wgpu/src/texture/atlas/allocator.rs delete mode 100644 wgpu/src/texture/atlas/entry.rs delete mode 100644 wgpu/src/texture/atlas/layer.rs (limited to 'wgpu/src') diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index dc19cfbf..d3603676 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -1,18 +1,23 @@ +mod atlas; + #[cfg(feature = "image")] mod raster; + #[cfg(feature = "svg")] mod vector; -use crate::{texture, Transformation}; +use crate::Transformation; +use atlas::Atlas; -use iced_native::{image, svg, Rectangle}; +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(any(feature = "image", feature = "svg"))] -use crate::texture::atlas; +#[cfg(feature = "svg")] +use iced_native::svg; #[derive(Debug)] pub struct Pipeline { @@ -30,7 +35,7 @@ pub struct Pipeline { texture: wgpu::BindGroup, texture_version: usize, texture_layout: wgpu::BindGroupLayout, - texture_atlas: texture::Atlas, + texture_atlas: Atlas, } impl Pipeline { @@ -216,7 +221,7 @@ impl Pipeline { usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST, }); - let texture_atlas = texture::Atlas::new(device); + let texture_atlas = Atlas::new(device); let texture = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &texture_layout, @@ -284,33 +289,29 @@ impl Pipeline { for image in images { match &image.handle { - Handle::Raster(_handle) => { - #[cfg(feature = "image")] - { - if let Some(atlas_entry) = raster_cache.upload( - _handle, - device, - encoder, - &mut self.texture_atlas, - ) { - add_instances(image, atlas_entry, instances); - } - }; + #[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); + } } - Handle::Vector(_handle) => { - #[cfg(feature = "svg")] - { - if let Some(atlas_entry) = vector_cache.upload( - _handle, - image.size, - _scale, - 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); + } } } } @@ -432,7 +433,10 @@ pub struct Image { } pub enum Handle { + #[cfg(feature = "image")] Raster(image::Handle), + + #[cfg(feature = "svg")] Vector(svg::Handle), } @@ -479,7 +483,6 @@ struct Uniforms { transform: [f32; 16], } -#[cfg(any(feature = "image", feature = "svg"))] fn add_instances( image: &Image, entry: &atlas::Entry, @@ -516,7 +519,6 @@ fn add_instances( } } -#[cfg(any(feature = "image", feature = "svg"))] #[inline] fn add_instance( position: [f32; 2], 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, +} + +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( + &mut self, + width: u32, + height: u32, + data: &[C], + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + ) -> Option + 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 { + // 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 { + 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, + }, +} + +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}; diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index a807b44d..1d63abbf 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -30,13 +30,11 @@ pub mod triangle; pub mod widget; pub mod window; -mod image; mod primitive; mod quad; mod renderer; mod target; mod text; -mod texture; mod transformation; mod viewport; @@ -52,6 +50,11 @@ pub use viewport::Viewport; #[doc(no_inline)] pub use widget::*; -pub(crate) use self::image::Image; pub(crate) use quad::Quad; pub(crate) use transformation::Transformation; + +#[cfg(any(feature = "image", feature = "svg"))] +mod image; + +#[cfg(any(feature = "image", feature = "svg"))] +pub(crate) use self::image::Image; diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index b5dce480..d9ef9fc4 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -1,7 +1,11 @@ use crate::{ - image, quad, text, triangle, Defaults, Image, Primitive, Quad, Settings, - Target, Transformation, + quad, text, triangle, Defaults, Primitive, Quad, Settings, Target, + Transformation, }; + +#[cfg(any(feature = "image", feature = "svg"))] +use crate::{image, Image}; + use iced_native::{ layout, Background, Color, Layout, MouseCursor, Point, Rectangle, Vector, Widget, @@ -16,18 +20,22 @@ mod widget; #[derive(Debug)] pub struct Renderer { quad_pipeline: quad::Pipeline, - image_pipeline: image::Pipeline, text_pipeline: text::Pipeline, - triangle_pipeline: crate::triangle::Pipeline, + triangle_pipeline: triangle::Pipeline, + + #[cfg(any(feature = "image", feature = "svg"))] + image_pipeline: image::Pipeline, } struct Layer<'a> { bounds: Rectangle, offset: Vector, quads: Vec, - images: Vec, meshes: Vec<(Point, Arc)>, text: Vec>, + + #[cfg(any(feature = "image", feature = "svg"))] + images: Vec, } impl<'a> Layer<'a> { @@ -36,9 +44,11 @@ impl<'a> Layer<'a> { bounds, offset, quads: Vec::new(), - images: Vec::new(), text: Vec::new(), meshes: Vec::new(), + + #[cfg(any(feature = "image", feature = "svg"))] + images: Vec::new(), } } } @@ -51,19 +61,22 @@ impl Renderer { let text_pipeline = text::Pipeline::new(device, settings.format, settings.default_font); let quad_pipeline = quad::Pipeline::new(device, settings.format); - let image_pipeline = - crate::image::Pipeline::new(device, settings.format); let triangle_pipeline = triangle::Pipeline::new( device, settings.format, settings.antialiasing, ); + #[cfg(any(feature = "image", feature = "svg"))] + let image_pipeline = image::Pipeline::new(device, settings.format); + Self { quad_pipeline, - image_pipeline, text_pipeline, triangle_pipeline, + + #[cfg(any(feature = "image", feature = "svg"))] + image_pipeline, } } @@ -116,6 +129,7 @@ impl Renderer { ); } + #[cfg(any(feature = "image", feature = "svg"))] self.image_pipeline.trim_cache(); *mouse_cursor @@ -223,20 +237,6 @@ impl Renderer { border_color: border_color.into_linear(), }); } - Primitive::Image { handle, bounds } => { - layer.images.push(Image { - handle: image::Handle::Raster(handle.clone()), - position: [bounds.x, bounds.y], - size: [bounds.width, bounds.height], - }); - } - Primitive::Svg { handle, bounds } => { - layer.images.push(Image { - handle: image::Handle::Vector(handle.clone()), - position: [bounds.x, bounds.y], - size: [bounds.width, bounds.height], - }); - } Primitive::Mesh2D { origin, buffers } => { layer.meshes.push((*origin, buffers.clone())); } @@ -264,6 +264,28 @@ impl Renderer { layers.push(new_layer); } } + + #[cfg(feature = "image")] + Primitive::Image { handle, bounds } => { + layer.images.push(Image { + handle: image::Handle::Raster(handle.clone()), + position: [bounds.x, bounds.y], + size: [bounds.width, bounds.height], + }); + } + #[cfg(not(feature = "image"))] + Primitive::Image { .. } => {} + + #[cfg(feature = "svg")] + Primitive::Svg { handle, bounds } => { + layer.images.push(Image { + handle: image::Handle::Vector(handle.clone()), + position: [bounds.x, bounds.y], + size: [bounds.width, bounds.height], + }); + } + #[cfg(not(feature = "svg"))] + Primitive::Svg { .. } => {} } } @@ -346,23 +368,26 @@ impl Renderer { ); } - if layer.images.len() > 0 { - let translated_and_scaled = transformation - * Transformation::scale(scale_factor, scale_factor) - * Transformation::translate( - -(layer.offset.x as f32), - -(layer.offset.y as f32), + #[cfg(any(feature = "image", feature = "svg"))] + { + if layer.images.len() > 0 { + let translated_and_scaled = transformation + * Transformation::scale(scale_factor, scale_factor) + * Transformation::translate( + -(layer.offset.x as f32), + -(layer.offset.y as f32), + ); + + self.image_pipeline.draw( + device, + encoder, + &layer.images, + translated_and_scaled, + bounds, + target, + scale_factor, ); - - self.image_pipeline.draw( - device, - encoder, - &layer.images, - translated_and_scaled, - bounds, - target, - scale_factor, - ); + } } if layer.text.len() > 0 { diff --git a/wgpu/src/texture.rs b/wgpu/src/texture.rs deleted file mode 100644 index 00b60bfa..00000000 --- a/wgpu/src/texture.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod atlas; - -pub use atlas::Atlas; diff --git a/wgpu/src/texture/atlas.rs b/wgpu/src/texture/atlas.rs deleted file mode 100644 index 86a5ff49..00000000 --- a/wgpu/src/texture/atlas.rs +++ /dev/null @@ -1,361 +0,0 @@ -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, -} - -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( - &mut self, - width: u32, - height: u32, - data: &[C], - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - ) -> Option - 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 { - // 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/texture/atlas/allocation.rs b/wgpu/src/texture/atlas/allocation.rs deleted file mode 100644 index e17b3f8c..00000000 --- a/wgpu/src/texture/atlas/allocation.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::texture::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/texture/atlas/allocator.rs b/wgpu/src/texture/atlas/allocator.rs deleted file mode 100644 index 7a4ff5b1..00000000 --- a/wgpu/src/texture/atlas/allocator.rs +++ /dev/null @@ -1,69 +0,0 @@ -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 { - 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/texture/atlas/entry.rs b/wgpu/src/texture/atlas/entry.rs deleted file mode 100644 index 2c064665..00000000 --- a/wgpu/src/texture/atlas/entry.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::texture::atlas; - -#[derive(Debug)] -pub enum Entry { - Contiguous(atlas::Allocation), - Fragmented { - size: (u32, u32), - fragments: Vec, - }, -} - -impl Entry { - 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/texture/atlas/layer.rs b/wgpu/src/texture/atlas/layer.rs deleted file mode 100644 index b025d8a1..00000000 --- a/wgpu/src/texture/atlas/layer.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::texture::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, - } - } -} -- cgit