Implement atlas padding in image pipeline
This commit is contained in:
parent
704144728f
commit
ce126f5ea3
4 changed files with 159 additions and 63 deletions
|
|
@ -10,7 +10,7 @@ pub use layer::Layer;
|
||||||
|
|
||||||
use allocator::Allocator;
|
use allocator::Allocator;
|
||||||
|
|
||||||
pub const DEFAULT_SIZE: u32 = 512;
|
pub const DEFAULT_SIZE: u32 = 2048;
|
||||||
pub const MAX_SIZE: u32 = 2048;
|
pub const MAX_SIZE: u32 = 2048;
|
||||||
|
|
||||||
use crate::core::Size;
|
use crate::core::Size;
|
||||||
|
|
@ -114,7 +114,7 @@ impl Atlas {
|
||||||
belt: &mut wgpu::util::StagingBelt,
|
belt: &mut wgpu::util::StagingBelt,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
data: &[u8],
|
pixels: &[u8],
|
||||||
) -> Option<Entry> {
|
) -> Option<Entry> {
|
||||||
let entry = {
|
let entry = {
|
||||||
let current_size = self.layers.len();
|
let current_size = self.layers.len();
|
||||||
|
|
@ -129,56 +129,25 @@ impl Atlas {
|
||||||
|
|
||||||
log::debug!("Allocated atlas entry: {entry:?}");
|
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 {
|
match &entry {
|
||||||
Entry::Contiguous(allocation) => {
|
Entry::Contiguous(allocation) => {
|
||||||
self.upload_allocation(
|
self.upload_allocation(
|
||||||
buffer_slice.buffer(),
|
pixels, width, 0, allocation, device, encoder, belt,
|
||||||
width,
|
|
||||||
height,
|
|
||||||
padding,
|
|
||||||
buffer_slice.offset() as usize,
|
|
||||||
allocation,
|
|
||||||
encoder,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Entry::Fragmented { fragments, .. } => {
|
Entry::Fragmented { fragments, .. } => {
|
||||||
for fragment in fragments {
|
for fragment in fragments {
|
||||||
let (x, y) = fragment.position;
|
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(
|
self.upload_allocation(
|
||||||
buffer_slice.buffer(),
|
pixels,
|
||||||
width,
|
width,
|
||||||
height,
|
offset,
|
||||||
padding,
|
|
||||||
offset + buffer_slice.offset() as usize,
|
|
||||||
&fragment.allocation,
|
&fragment.allocation,
|
||||||
|
device,
|
||||||
encoder,
|
encoder,
|
||||||
|
belt,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -339,44 +308,129 @@ impl Atlas {
|
||||||
|
|
||||||
fn upload_allocation(
|
fn upload_allocation(
|
||||||
&self,
|
&self,
|
||||||
buffer: &wgpu::Buffer,
|
pixels: &[u8],
|
||||||
image_width: u32,
|
image_width: u32,
|
||||||
image_height: u32,
|
|
||||||
padding: u32,
|
|
||||||
offset: usize,
|
offset: usize,
|
||||||
allocation: &Allocation,
|
allocation: &Allocation,
|
||||||
|
device: &wgpu::Device,
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
belt: &mut wgpu::util::StagingBelt,
|
||||||
) {
|
) {
|
||||||
let (x, y) = allocation.position();
|
let (x, y) = allocation.position();
|
||||||
let Size { width, height } = allocation.size();
|
let Size { width, height } = allocation.size();
|
||||||
let layer = allocation.layer();
|
let layer = allocation.layer();
|
||||||
|
let padding = allocation.padding();
|
||||||
|
|
||||||
let extent = wgpu::Extent3d {
|
// It is a webgpu requirement that:
|
||||||
width,
|
// BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0
|
||||||
height,
|
// So we calculate padded_width by rounding width up to the next
|
||||||
depth_or_array_layers: 1,
|
// 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(
|
encoder.copy_buffer_to_texture(
|
||||||
wgpu::TexelCopyBufferInfo {
|
wgpu::TexelCopyBufferInfo {
|
||||||
buffer,
|
buffer: buffer_slice.buffer(),
|
||||||
layout: wgpu::TexelCopyBufferLayout {
|
layout: wgpu::TexelCopyBufferLayout {
|
||||||
offset: offset as u64,
|
offset: buffer_slice.offset(),
|
||||||
bytes_per_row: Some(4 * image_width + padding),
|
bytes_per_row: Some(bytes_per_row as u32),
|
||||||
rows_per_image: Some(image_height),
|
rows_per_image: Some(height + padding.height * 2),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wgpu::TexelCopyTextureInfo {
|
wgpu::TexelCopyTextureInfo {
|
||||||
texture: &self.texture,
|
texture: &self.texture,
|
||||||
mip_level: 0,
|
mip_level: 0,
|
||||||
origin: wgpu::Origin3d {
|
origin: wgpu::Origin3d {
|
||||||
x,
|
x: x - padding.width,
|
||||||
y,
|
y: y - padding.height,
|
||||||
z: layer as u32,
|
z: layer as u32,
|
||||||
},
|
},
|
||||||
aspect: wgpu::TextureAspect::default(),
|
aspect: wgpu::TextureAspect::default(),
|
||||||
},
|
},
|
||||||
extent,
|
wgpu::Extent3d {
|
||||||
|
width: width + padding.width * 2,
|
||||||
|
height: height + padding.height * 2,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,13 @@ impl Allocation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn padding(&self) -> Size<u32> {
|
||||||
|
match self {
|
||||||
|
Allocation::Partial { region, .. } => region.padding(),
|
||||||
|
Allocation::Full { .. } => Size::new(0, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn layer(&self) -> usize {
|
pub fn layer(&self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
Allocation::Partial { layer, .. } => *layer,
|
Allocation::Partial { layer, .. } => *layer,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use crate::core;
|
||||||
|
|
||||||
use guillotiere::{AtlasAllocator, Size};
|
use guillotiere::{AtlasAllocator, Size};
|
||||||
|
|
||||||
pub struct Allocator {
|
pub struct Allocator {
|
||||||
|
|
@ -6,6 +8,8 @@ pub struct Allocator {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Allocator {
|
impl Allocator {
|
||||||
|
const PADDING: u32 = 1;
|
||||||
|
|
||||||
pub fn new(size: u32) -> Allocator {
|
pub fn new(size: u32) -> Allocator {
|
||||||
let raw = AtlasAllocator::new(Size::new(size as i32, size as i32));
|
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<Region> {
|
pub fn allocate(&mut self, width: u32, height: u32) -> Option<Region> {
|
||||||
let allocation =
|
let size = self.raw.size();
|
||||||
self.raw.allocate(Size::new(width as i32, height as i32))?;
|
|
||||||
|
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;
|
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) {
|
pub fn deallocate(&mut self, region: &Region) {
|
||||||
|
|
@ -41,6 +71,7 @@ impl Allocator {
|
||||||
|
|
||||||
pub struct Region {
|
pub struct Region {
|
||||||
allocation: guillotiere::Allocation,
|
allocation: guillotiere::Allocation,
|
||||||
|
padding: core::Size<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Region {
|
impl Region {
|
||||||
|
|
@ -50,10 +81,14 @@ impl Region {
|
||||||
(rectangle.min.x as u32, rectangle.min.y as u32)
|
(rectangle.min.x as u32, rectangle.min.y as u32)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn size(&self) -> crate::core::Size<u32> {
|
pub fn size(&self) -> core::Size<u32> {
|
||||||
let size = self.allocation.rectangle.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<u32> {
|
||||||
|
self.padding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -735,12 +735,12 @@ fn add_instance(
|
||||||
_rotation: rotation,
|
_rotation: rotation,
|
||||||
_opacity: opacity,
|
_opacity: opacity,
|
||||||
_position_in_atlas: [
|
_position_in_atlas: [
|
||||||
(x as f32 + 0.5) / atlas_size as f32,
|
x as f32 / atlas_size as f32,
|
||||||
(y as f32 + 0.5) / atlas_size as f32,
|
y as f32 / atlas_size as f32,
|
||||||
],
|
],
|
||||||
_size_in_atlas: [
|
_size_in_atlas: [
|
||||||
(width as f32 - 1.0) / atlas_size as f32,
|
width as f32 / atlas_size as f32,
|
||||||
(height as f32 - 1.0) / atlas_size as f32,
|
height as f32 / atlas_size as f32,
|
||||||
],
|
],
|
||||||
_layer: layer as u32,
|
_layer: layer as u32,
|
||||||
_snap: snap as u32,
|
_snap: snap as u32,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue