diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs index 5f7265ae..38328619 100644 --- a/wgpu/src/image/atlas.rs +++ b/wgpu/src/image/atlas.rs @@ -10,7 +10,7 @@ pub use layer::Layer; use allocator::Allocator; -pub const DEFAULT_SIZE: u32 = 512; +pub const DEFAULT_SIZE: u32 = 2048; pub const MAX_SIZE: u32 = 2048; use crate::core::Size; @@ -114,7 +114,7 @@ impl Atlas { belt: &mut wgpu::util::StagingBelt, width: u32, height: u32, - data: &[u8], + pixels: &[u8], ) -> Option { let entry = { let current_size = self.layers.len(); @@ -129,56 +129,25 @@ impl Atlas { log::debug!("Allocated atlas entry: {entry:?}"); - // It is a webgpu requirement that: - // BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0 - // So we calculate padded_width by rounding width up to the next - // multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT. - let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT; - let padding = (align - (4 * width) % align) % align; - let padded_width = (4 * width + padding) as usize; - let padded_data_size = padded_width * height as usize; - - let buffer_slice = belt.allocate( - wgpu::BufferSize::new(padded_data_size as u64).unwrap(), - wgpu::BufferSize::new(8 * 4).unwrap(), - device, - ); - - let mut padded_data = buffer_slice.get_mapped_range_mut(); - - for row in 0..height as usize { - let offset = row * padded_width; - - padded_data[offset..offset + 4 * width as usize].copy_from_slice( - &data[row * 4 * width as usize..(row + 1) * 4 * width as usize], - ); - } - match &entry { Entry::Contiguous(allocation) => { self.upload_allocation( - buffer_slice.buffer(), - width, - height, - padding, - buffer_slice.offset() as usize, - allocation, - encoder, + pixels, width, 0, allocation, device, encoder, belt, ); } Entry::Fragmented { fragments, .. } => { for fragment in fragments { let (x, y) = fragment.position; - let offset = (y * padded_width as u32 + 4 * x) as usize; + let offset = (y * width + 4 * x) as usize; self.upload_allocation( - buffer_slice.buffer(), + pixels, width, - height, - padding, - offset + buffer_slice.offset() as usize, + offset, &fragment.allocation, + device, encoder, + belt, ); } } @@ -339,44 +308,129 @@ impl Atlas { fn upload_allocation( &self, - buffer: &wgpu::Buffer, + pixels: &[u8], image_width: u32, - image_height: u32, - padding: u32, offset: usize, allocation: &Allocation, + device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, ) { let (x, y) = allocation.position(); let Size { width, height } = allocation.size(); let layer = allocation.layer(); + let padding = allocation.padding(); - let extent = wgpu::Extent3d { - width, - height, - depth_or_array_layers: 1, - }; + // It is a webgpu requirement that: + // BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0 + // So we calculate padded_width by rounding width up to the next + // multiple of wgpu::COPY_BYTES_PER_ROW_ALIGNMENT. + let bytes_per_row = (4 * (width + padding.width * 2)) + .next_multiple_of(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT) + as usize; + let total_bytes = + bytes_per_row * (height + padding.height * 2) as usize; + let buffer_slice = belt.allocate( + wgpu::BufferSize::new(total_bytes as u64).unwrap(), + wgpu::BufferSize::new(8 * 4).unwrap(), + device, + ); + + let mut padded_data = buffer_slice.get_mapped_range_mut(); + let padding_width = padding.width as usize; + let padding_height = padding.height as usize; + + // Copy image rows + for row in 0..height as usize { + let offset = offset + row * 4 * image_width as usize; + let start = (row + padding_height) * bytes_per_row; + let stride = 4 * width as usize; + + padded_data + [start + 4 * padding_width..start + 4 * padding_width + stride] + .copy_from_slice(&pixels[offset..offset + stride]); + + // Add padding to the sides, if needed + for i in 0..padding_width { + padded_data[start + 4 * i..start + 4 * (i + 1)] + .copy_from_slice(&pixels[offset..offset + 4]); + + padded_data[start + stride + 4 * (padding_width + i) + ..start + stride + 4 * (padding_width + i + 1)] + .copy_from_slice( + &pixels[offset + stride - 4..offset + stride], + ); + } + } + + // Add padding on top and bottom + for row in 0..padding_height { + let start = row * bytes_per_row; + let end = (padding_height + height as usize + row) * bytes_per_row; + let end_offset = + offset + height as usize * 4 * image_width as usize; + + // Top + padded_data[start + 4 * padding_width + ..start + 4 * (padding_width + width as usize)] + .copy_from_slice(&pixels[offset..offset + 4 * width as usize]); + + // Bottom + padded_data[end + 4 * padding_width + ..end + 4 * (padding_width + width as usize)] + .copy_from_slice( + &pixels[end_offset - 4 * width as usize..end_offset], + ); + + // Corners + for i in 0..padding_width { + padded_data[start + 4 * i..start + 4 * (i + 1)] + .copy_from_slice(&pixels[offset..4]); + + padded_data[start + 4 * (width as usize + padding_width + i) + ..start + 4 * (width as usize + padding_width + i + 1)] + .copy_from_slice( + &pixels[offset + 4 * (width - 1) as usize + ..offset + 4 * width as usize], + ); + + padded_data[end + 4 * i..end + 4 * (i + 1)].copy_from_slice( + &pixels[end_offset - 4 * width as usize + ..end_offset - 4 * (width as usize - 1)], + ); + + padded_data[end + 4 * (width as usize + padding_width + i) + ..end + 4 * (width as usize + padding_width + i + 1)] + .copy_from_slice(&pixels[end_offset - 4..end_offset]); + } + } + + // Copy actual image encoder.copy_buffer_to_texture( wgpu::TexelCopyBufferInfo { - buffer, + buffer: buffer_slice.buffer(), layout: wgpu::TexelCopyBufferLayout { - offset: offset as u64, - bytes_per_row: Some(4 * image_width + padding), - rows_per_image: Some(image_height), + offset: buffer_slice.offset(), + bytes_per_row: Some(bytes_per_row as u32), + rows_per_image: Some(height + padding.height * 2), }, }, wgpu::TexelCopyTextureInfo { texture: &self.texture, mip_level: 0, origin: wgpu::Origin3d { - x, - y, + x: x - padding.width, + y: y - padding.height, z: layer as u32, }, aspect: wgpu::TextureAspect::default(), }, - extent, + wgpu::Extent3d { + width: width + padding.width * 2, + height: height + padding.height * 2, + depth_or_array_layers: 1, + }, ); } diff --git a/wgpu/src/image/atlas/allocation.rs b/wgpu/src/image/atlas/allocation.rs index 2e8a7b14..295a7a13 100644 --- a/wgpu/src/image/atlas/allocation.rs +++ b/wgpu/src/image/atlas/allocation.rs @@ -29,6 +29,13 @@ impl Allocation { } } + pub fn padding(&self) -> Size { + match self { + Allocation::Partial { region, .. } => region.padding(), + Allocation::Full { .. } => Size::new(0, 0), + } + } + pub fn layer(&self) -> usize { match self { Allocation::Partial { layer, .. } => *layer, diff --git a/wgpu/src/image/atlas/allocator.rs b/wgpu/src/image/atlas/allocator.rs index a51ac1f5..263619f6 100644 --- a/wgpu/src/image/atlas/allocator.rs +++ b/wgpu/src/image/atlas/allocator.rs @@ -1,3 +1,5 @@ +use crate::core; + use guillotiere::{AtlasAllocator, Size}; pub struct Allocator { @@ -6,6 +8,8 @@ pub struct Allocator { } impl Allocator { + const PADDING: u32 = 1; + pub fn new(size: u32) -> Allocator { let raw = AtlasAllocator::new(Size::new(size as i32, size as i32)); @@ -16,12 +20,38 @@ impl Allocator { } pub fn allocate(&mut self, width: u32, height: u32) -> Option { - let allocation = - self.raw.allocate(Size::new(width as i32, height as i32))?; + let size = self.raw.size(); + + let padded_width = width + Self::PADDING * 2; + let padded_height = height + Self::PADDING * 2; + + let pad_width = padded_width as i32 <= size.width; + let pad_height = padded_height as i32 <= size.height; + + let mut allocation = self.raw.allocate(Size::new( + if pad_width { padded_width } else { width } as i32, + if pad_height { padded_height } else { height } as i32, + ))?; + + if pad_width { + allocation.rectangle.min.x += Self::PADDING as i32; + allocation.rectangle.max.x -= Self::PADDING as i32; + } + + if pad_height { + allocation.rectangle.min.y += Self::PADDING as i32; + allocation.rectangle.max.y -= Self::PADDING as i32; + } self.allocations += 1; - Some(Region { allocation }) + Some(Region { + allocation, + padding: core::Size::new( + if pad_width { Self::PADDING } else { 0 }, + if pad_height { Self::PADDING } else { 0 }, + ), + }) } pub fn deallocate(&mut self, region: &Region) { @@ -41,6 +71,7 @@ impl Allocator { pub struct Region { allocation: guillotiere::Allocation, + padding: core::Size, } impl Region { @@ -50,10 +81,14 @@ impl Region { (rectangle.min.x as u32, rectangle.min.y as u32) } - pub fn size(&self) -> crate::core::Size { + pub fn size(&self) -> core::Size { let size = self.allocation.rectangle.size(); - crate::core::Size::new(size.width as u32, size.height as u32) + core::Size::new(size.width as u32, size.height as u32) + } + + pub fn padding(&self) -> crate::core::Size { + self.padding } } diff --git a/wgpu/src/image/mod.rs b/wgpu/src/image/mod.rs index d1e39c92..21164741 100644 --- a/wgpu/src/image/mod.rs +++ b/wgpu/src/image/mod.rs @@ -735,12 +735,12 @@ fn add_instance( _rotation: rotation, _opacity: opacity, _position_in_atlas: [ - (x as f32 + 0.5) / atlas_size as f32, - (y as f32 + 0.5) / atlas_size as f32, + x as f32 / atlas_size as f32, + y as f32 / 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, + width as f32 / atlas_size as f32, + height as f32 / atlas_size as f32, ], _layer: layer as u32, _snap: snap as u32,