use std::collections::HashMap;
use wgpu;
pub struct TextureAtlas {
pub texture: wgpu::Texture,
pub view: wgpu::TextureView,
pub sampler: wgpu::Sampler,
max_size: u32,
allocator: AtlasAllocator,
regions: HashMap<u32, AtlasRegion>,
next_id: u32,
}
#[derive(Debug, Clone, Copy)]
pub struct AtlasRegion {
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
pub layer: u32,
}
impl AtlasRegion {
pub fn uv(&self, atlas_size: u32) -> (f32, f32, f32, f32) {
let x1 = self.x as f32 / atlas_size as f32;
let y1 = self.y as f32 / atlas_size as f32;
let x2 = (self.x + self.width) as f32 / atlas_size as f32;
let y2 = (self.y + self.height) as f32 / atlas_size as f32;
(x1, y1, x2, y2)
}
}
struct AtlasAllocator {
row_y: u32,
row_x: u32,
row_height: u32,
size: u32,
}
impl AtlasAllocator {
fn new(size: u32) -> Self {
Self {
row_y: 0,
row_x: 0,
row_height: 0,
size,
}
}
fn allocate(&mut self, width: u32, height: u32) -> Option<(u32, u32)> {
if self.row_x + width <= self.size {
let pos = (self.row_x, self.row_y);
self.row_x += width;
self.row_height = self.row_height.max(height);
return Some(pos);
}
self.row_y += self.row_height;
self.row_x = 0;
self.row_height = 0;
if self.row_y + height > self.size {
return None; }
let pos = (self.row_x, self.row_y);
self.row_x = width;
self.row_height = height;
Some(pos)
}
fn reset(&mut self) {
self.row_y = 0;
self.row_x = 0;
self.row_height = 0;
}
}
impl TextureAtlas {
pub fn new(device: &wgpu::Device, max_size: u32) -> Self {
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Texture Atlas"),
size: wgpu::Extent3d {
width: max_size,
height: max_size,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("Atlas Sampler"),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
Self {
texture,
view,
sampler,
max_size,
allocator: AtlasAllocator::new(max_size),
regions: HashMap::new(),
next_id: 1,
}
}
pub fn upload(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
width: u32,
height: u32,
data: &[u8],
) -> u32 {
let (x, y) = match self.allocator.allocate(width, height) {
Some(pos) => pos,
None => {
log::warn!("Texture atlas full, clearing...");
self.clear(device);
self.allocator.allocate(width, height).expect("Texture too large for atlas")
}
};
queue.write_texture(
wgpu::ImageCopyTexture {
texture: &self.texture,
mip_level: 0,
origin: wgpu::Origin3d { x, y, z: 0 },
aspect: wgpu::TextureAspect::All,
},
data,
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(width * 4),
rows_per_image: Some(height),
},
wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
);
let id = self.next_id;
self.next_id += 1;
self.regions.insert(id, AtlasRegion {
x,
y,
width,
height,
layer: 0,
});
id
}
pub fn get(&self, id: u32) -> Option<&AtlasRegion> {
self.regions.get(&id)
}
pub fn get_uv(&self, id: u32) -> Option<(f32, f32, f32, f32)> {
self.regions.get(&id).map(|r| r.uv(self.max_size))
}
pub fn clear(&mut self, _device: &wgpu::Device) {
self.allocator.reset();
self.regions.clear();
}
pub fn size(&self) -> u32 {
self.max_size
}
pub fn texture_count(&self) -> usize {
self.regions.len()
}
}
pub struct GlyphCache {
glyphs: HashMap<GlyphKey, GlyphInfo>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct GlyphKey {
pub font_id: u32,
pub glyph_id: u32,
pub size_px: u32,
}
#[derive(Debug, Clone, Copy)]
pub struct GlyphInfo {
pub atlas_id: u32,
pub width: f32,
pub height: f32,
pub bearing_x: f32,
pub bearing_y: f32,
pub advance: f32,
}
impl GlyphCache {
pub fn new() -> Self {
Self {
glyphs: HashMap::new(),
}
}
pub fn get(&self, key: GlyphKey) -> Option<&GlyphInfo> {
self.glyphs.get(&key)
}
pub fn insert(&mut self, key: GlyphKey, info: GlyphInfo) {
self.glyphs.insert(key, info);
}
pub fn clear(&mut self) {
self.glyphs.clear();
}
}
impl Default for GlyphCache {
fn default() -> Self {
Self::new()
}
}