hUI/hui/src/draw/atlas.rs

178 lines
6 KiB
Rust
Raw Normal View History

2024-02-21 16:56:55 -06:00
use glam::{uvec2, UVec2};
2024-02-21 13:13:58 -06:00
use hashbrown::HashMap;
use nohash_hasher::BuildNoHashHasher;
use rect_packer::DensePacker;
2024-02-24 16:32:09 -06:00
use crate::IfModified;
2024-02-21 13:13:58 -06:00
const CHANNEL_COUNT: u32 = 4;
2024-02-24 16:32:09 -06:00
//TODO: make this work
const ALLOW_ROTATION: bool = false;
2024-02-21 13:13:58 -06:00
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TextureHandle {
//TODO automatic cleanup when handle is dropped
//man: Weak<RefCell<TextureAtlasManager>>,
pub(crate) index: u32
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct TextureAllocation {
/// Index of the texture allocation
pub index: u32,
/// Position in the texture atlas
pub position: UVec2,
/// Requested texture size
pub size: UVec2,
/// True if the texture was rotated by 90 degrees
pub rotated: bool,
}
2024-02-24 16:32:09 -06:00
/// Manages a texture atlas and the allocation of space within it\
/// The atlas is alllowed to grow and resize dynamically, as needed
2024-02-21 13:13:58 -06:00
pub(crate) struct TextureAtlasManager {
packer: DensePacker,
count: u32,
size: UVec2,
data: Vec<u8>,
allocations: HashMap<u32, TextureAllocation, BuildNoHashHasher<u32>>,
2024-02-24 16:32:09 -06:00
modified: bool,
2024-02-21 13:13:58 -06:00
}
impl TextureAtlasManager {
2024-02-24 16:32:09 -06:00
/// Create a new texture atlas with the specified size\
/// 512x512 is a good default size for most applications, and the texture atlas can grow dynamically as needed
2024-02-21 13:13:58 -06:00
pub fn new(size: UVec2) -> Self {
Self {
packer: DensePacker::new(size.x as i32, size.y as i32),
count: 0,
size: UVec2::new(0, 0),
2024-02-21 16:56:55 -06:00
data: vec![0; (size.x * size.y * CHANNEL_COUNT) as usize],
2024-02-21 13:13:58 -06:00
allocations: HashMap::default(),
2024-02-24 16:32:09 -06:00
modified: true,
2024-02-21 13:13:58 -06:00
}
}
2024-02-24 16:32:09 -06:00
/// Resize the texture atlas to the new size in-place, preserving the existing data
2024-02-21 13:13:58 -06:00
pub fn resize(&mut self, new_size: UVec2) {
if new_size.x > self.size.x && new_size.y > self.size.y{
self.packer.resize(new_size.x as i32, new_size.y as i32);
//Resize the data array in-place
self.data.resize((new_size.x * new_size.y * CHANNEL_COUNT) as usize, 0);
for y in (1..self.size.y).rev() {
for x in (0..self.size.x).rev() {
2024-02-21 16:56:55 -06:00
let idx = ((y * self.size.x + x) * CHANNEL_COUNT) as usize;
let new_idx = ((y * new_size.x + x) * CHANNEL_COUNT) as usize;
for c in 0..(CHANNEL_COUNT as usize) {
self.data[new_idx + c] = self.data[idx + c];
}
2024-02-21 13:13:58 -06:00
}
}
} else {
//If scaling down, just recreate the atlas from scratch (since we need to re-pack everything anyway)
todo!("Atlas downscaling is not implemented yet");
}
self.size = new_size;
2024-02-24 16:32:09 -06:00
self.modified = true;
2024-02-21 13:13:58 -06:00
}
2024-02-21 16:56:55 -06:00
/// Allocate a new texture region in the atlas and return a handle to it\
/// Returns None if the texture could not be allocated due to lack of space\
2024-02-24 16:32:09 -06:00
/// Use `allocate` to allocate a texture and resize the atlas if necessary\
/// Does not modify the texture data
fn try_allocate(&mut self, size: UVec2) -> Option<TextureHandle> {
let result = self.packer.pack(size.x as i32, size.y as i32, ALLOW_ROTATION)?;
2024-02-21 13:13:58 -06:00
let index = self.count;
self.count += 1;
let allocation = TextureAllocation {
index,
position: UVec2::new(result.x as u32, result.y as u32),
size,
//If the size does not match the requested size, the texture was rotated
2024-02-24 16:32:09 -06:00
rotated: ALLOW_ROTATION && (result.width != size.x as i32),
2024-02-21 13:13:58 -06:00
};
self.allocations.insert_unique_unchecked(index, allocation);
Some(TextureHandle { index })
}
2024-02-21 16:56:55 -06:00
/// Allocate a new texture region in the atlas and resize the atlas if necessary\
2024-02-24 16:32:09 -06:00
/// This function should never fail under normal circumstances.\
/// May modify the texture data if the atlas is resized
pub fn allocate(&mut self, size: UVec2) -> TextureHandle {
2024-02-21 16:56:55 -06:00
let mut new_size = self.size;
while !self.packer.can_pack(size.x as i32, size.y as i32, true) {
new_size *= 2;
self.packer.resize(new_size.x as i32, new_size.y as i32);
}
self.resize(new_size);
2024-02-24 16:32:09 -06:00
self.try_allocate(size).unwrap()
2024-02-21 16:56:55 -06:00
}
2024-02-24 16:32:09 -06:00
/// Allocate a new texture region in the atlas and copy the data into it\
/// This function may resize the atlas as needed, and should never fail under normal circumstances.
2024-02-21 16:56:55 -06:00
pub fn add(&mut self, width: usize, data: &[u8]) -> TextureHandle {
let size = uvec2(width as u32, (data.len() / (width * CHANNEL_COUNT as usize)) as u32);
2024-02-24 16:32:09 -06:00
let handle = self.allocate(size);
2024-02-21 16:56:55 -06:00
let allocation = self.allocations.get_mut(&handle.index).unwrap();
assert!(!allocation.rotated, "Rotated textures are not implemented yet");
for y in 0..size.y {
for x in 0..size.x {
let src_idx = (y * size.x + x) * CHANNEL_COUNT;
let dst_idx = ((allocation.position.y + y) * self.size.x + allocation.position.x + x) * CHANNEL_COUNT;
for c in 0..CHANNEL_COUNT as usize {
self.data[dst_idx as usize + c] = data[src_idx as usize + c];
}
}
}
handle
2024-02-21 13:13:58 -06:00
}
pub fn modify(&mut self, handle: TextureHandle) {
todo!()
}
pub fn remove(&mut self, handle: TextureHandle) {
todo!()
}
2024-02-21 16:56:55 -06:00
pub fn atlas_size(&self) -> UVec2 {
self.size
}
2024-02-21 13:13:58 -06:00
pub fn get(&self, handle: TextureHandle) -> Option<&TextureAllocation> {
self.allocations.get(&handle.index)
}
2024-02-24 16:32:09 -06:00
/// Reset the `is_modified` flag
pub fn reset_modified(&mut self) {
self.modified = false;
}
/// Returns true if the atlas has been modified since the last call to `reset_modified`\
/// If this function returns true, the texture atlas should be re-uploaded to the GPU\
/// This function is mostly useful for developers of graphics backends
pub fn is_modified(&self) -> bool {
self.modified
}
2024-02-21 13:13:58 -06:00
}
impl Default for TextureAtlasManager {
2024-02-24 16:32:09 -06:00
/// Create a new texture atlas with a default size of 512x512
2024-02-21 13:13:58 -06:00
fn default() -> Self {
Self::new(UVec2::new(512, 512))
}
}
2024-02-24 16:32:09 -06:00
#[allow(deprecated)]
impl<'a> IfModified<TextureAtlasManager> for TextureAtlasManager {
fn if_modified(&self) -> Option<&Self> {
match self.modified {
true => Some(self),
false => None,
}
}
}