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(); } }