use etagere::{AllocId, BucketedAtlasAllocator, size2};
#[derive(Clone, Copy, Debug)]
pub struct AtlasTile {
pub uv: [f32; 4],
pub width: u32,
pub height: u32,
pub(crate) allocation: AllocId,
}
enum Storage {
Gpu {
texture: wgpu::Texture,
view: wgpu::TextureView,
},
Cpu(Vec<u8>),
}
pub struct TextureAtlas {
storage: Storage,
allocator: BucketedAtlasAllocator,
format: wgpu::TextureFormat,
size: u32,
overflowed: bool,
}
const TILE_PADDING: u32 = 1;
impl TextureAtlas {
pub fn monochrome(device: &wgpu::Device) -> Self {
Self::new(device, wgpu::TextureFormat::R8Unorm, 1024, "aura_atlas_mono")
}
pub fn polychrome(device: &wgpu::Device) -> Self {
Self::new(device, wgpu::TextureFormat::Rgba8Unorm, 2048, "aura_atlas_color")
}
fn new(device: &wgpu::Device, format: wgpu::TextureFormat, size: u32, label: &str) -> Self {
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some(label),
size: wgpu::Extent3d { width: size, height: size, depth_or_array_layers: 1 },
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
Self::with_storage(Storage::Gpu { texture, view }, format, size)
}
pub fn cpu_monochrome() -> Self {
Self::with_storage(Storage::Cpu(vec![0; 1024 * 1024]), wgpu::TextureFormat::R8Unorm, 1024)
}
pub fn cpu_polychrome() -> Self {
Self::with_storage(Storage::Cpu(vec![0; 2048 * 2048 * 4]), wgpu::TextureFormat::Rgba8Unorm, 2048)
}
fn with_storage(storage: Storage, format: wgpu::TextureFormat, size: u32) -> Self {
let allocator = BucketedAtlasAllocator::new(size2(size as i32, size as i32));
Self { storage, allocator, format, size, overflowed: false }
}
pub fn view(&self) -> &wgpu::TextureView {
match &self.storage {
Storage::Gpu { view, .. } => view,
Storage::Cpu(_) => panic!("un atlas en memoria no tiene vista de GPU"),
}
}
pub fn cpu_pixels(&self) -> Option<(&[u8], u32, u32)> {
match &self.storage {
Storage::Cpu(pixels) => Some((pixels, self.size, self.bytes_per_pixel())),
Storage::Gpu { .. } => None,
}
}
fn bytes_per_pixel(&self) -> u32 {
match self.format {
wgpu::TextureFormat::R8Unorm => 1,
_ => 4,
}
}
pub fn insert(&mut self, queue: Option<&wgpu::Queue>, width: u32, height: u32, pixels: &[u8]) -> Option<AtlasTile> {
let padded = size2((width + TILE_PADDING * 2) as i32, (height + TILE_PADDING * 2) as i32);
let Some(allocation) = self.allocator.allocate(padded) else {
self.overflowed = true;
return None;
};
let x = allocation.rectangle.min.x as u32 + TILE_PADDING;
let y = allocation.rectangle.min.y as u32 + TILE_PADDING;
let bytes = self.bytes_per_pixel();
match (&mut self.storage, queue) {
(Storage::Gpu { texture, .. }, Some(queue)) => queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture,
mip_level: 0,
origin: wgpu::Origin3d { x, y, z: 0 },
aspect: wgpu::TextureAspect::All,
},
pixels,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(width * bytes),
rows_per_image: Some(height),
},
wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
),
(Storage::Cpu(storage), _) => {
let row = (width * bytes) as usize;
for (line, source) in pixels.chunks(row).take(height as usize).enumerate() {
let start = (((y as usize + line) * self.size as usize) + x as usize) * bytes as usize;
storage[start..start + source.len()].copy_from_slice(source);
}
}
(Storage::Gpu { .. }, None) => return None,
}
let scale = 1.0 / self.size as f32;
Some(AtlasTile {
uv: [x as f32 * scale, y as f32 * scale, (x + width) as f32 * scale, (y + height) as f32 * scale],
width,
height,
allocation: allocation.id,
})
}
pub fn take_overflow(&mut self) -> bool {
std::mem::take(&mut self.overflowed)
}
pub fn remove(&mut self, tile: &AtlasTile) {
self.allocator.deallocate(tile.allocation);
}
pub fn clear(&mut self) {
self.allocator.clear();
}
}