mirror of
https://github.com/griffi-gh/hUI.git
synced 2024-12-22 04:18:21 -06:00
atlas stuff
This commit is contained in:
parent
e0d370844a
commit
8202e99c8f
|
@ -1,6 +1,19 @@
|
|||
pub mod paint;
|
||||
pub mod texture;
|
||||
|
||||
pub struct Painter {
|
||||
use texture::TextureAtlas;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Painter {
|
||||
atlas: TextureAtlas,
|
||||
}
|
||||
|
||||
impl Painter {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn atlas(&self) -> &TextureAtlas {
|
||||
&self.atlas
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
pub(crate) mod atlas;
|
||||
pub use atlas::TextureHandle;
|
||||
mod atlas;
|
||||
pub use atlas::*;
|
||||
|
|
|
@ -4,10 +4,16 @@ use hashbrown::HashMap;
|
|||
use nohash_hasher::BuildNoHashHasher;
|
||||
|
||||
//TODO support rotation
|
||||
const ALLOW_ROTATION: bool = false;
|
||||
const DEFAULT_ATLAS_SIZE: UVec2 = uvec2(128, 128);
|
||||
// const ALLOW_ROTATION: bool = false;
|
||||
|
||||
// Destination format is always RGBA
|
||||
const RGBA_BYTES_PER_PIXEL: usize = 4;
|
||||
|
||||
/// Assert that the passed texture size is valid, panicking if it's not.
|
||||
///
|
||||
/// - The size must be greater than 0.
|
||||
/// - The size must be less than `i32::MAX`.
|
||||
fn assert_size(size: UVec2) {
|
||||
assert!(
|
||||
size.x > 0 &&
|
||||
|
@ -21,58 +27,124 @@ fn assert_size(size: UVec2) {
|
|||
);
|
||||
}
|
||||
|
||||
/// The format of the source texture data to use when updating a texture in the atlas.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub enum SourceTextureFormat {
|
||||
/// RGBA, 8-bit per channel\
|
||||
/// (Default and preferred format)
|
||||
/// RGBA, 8-bit per channel
|
||||
#[default]
|
||||
RGBA8,
|
||||
|
||||
/// RGB, 8-bit per channel
|
||||
/// (Alpha channel is assumed to be 255)
|
||||
//TODO native-endian RGBA32 format
|
||||
|
||||
/// ARGB, 8-bit per channel
|
||||
ARGB8,
|
||||
|
||||
/// BGRA, 8-bit per channel
|
||||
BGRA8,
|
||||
|
||||
/// ABGR, 8-bit per channel
|
||||
ABGR8,
|
||||
|
||||
/// RGB, 8-bit per channel (Alpha = 255)
|
||||
RGB8,
|
||||
|
||||
/// Alpha only, 8-bit per channel
|
||||
/// (All other channels are assumed to be 255 (white))
|
||||
A8,
|
||||
/// BGR, 8-bit per channel (Alpha = 255)
|
||||
BGR8,
|
||||
|
||||
//TODO ARGB, BGRA, etc.
|
||||
/// Alpha only, 8-bit per channel (RGB = #ffffff)
|
||||
A8,
|
||||
}
|
||||
|
||||
impl SourceTextureFormat {
|
||||
pub const fn bytes_per_pixel(&self) -> usize {
|
||||
match self {
|
||||
SourceTextureFormat::RGBA8 => 4,
|
||||
SourceTextureFormat::RGB8 => 3,
|
||||
SourceTextureFormat::RGBA8 |
|
||||
SourceTextureFormat::ARGB8 |
|
||||
SourceTextureFormat::BGRA8 |
|
||||
SourceTextureFormat::ABGR8 => 4,
|
||||
SourceTextureFormat::RGB8 |
|
||||
SourceTextureFormat::BGR8 => 3,
|
||||
SourceTextureFormat::A8 => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type TextureId = u32;
|
||||
type TextureId = u32;
|
||||
|
||||
/// A handle to a texture in the texture atlas.
|
||||
///
|
||||
/// Can be cheaply copied and passed around.\
|
||||
/// The handle is only valid for the texture atlas it was created from.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct TextureHandle {
|
||||
pub(crate) id: TextureId,
|
||||
pub(crate) size: UVec2,
|
||||
}
|
||||
|
||||
struct TextureAllocation {
|
||||
handle: TextureHandle,
|
||||
offset: UVec2,
|
||||
size: UVec2,
|
||||
impl TextureHandle {
|
||||
pub fn size(&self) -> UVec2 {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TextureAtlas {
|
||||
/// Represents an area allocated to a specific texture handle in the texture atlas.
|
||||
struct TextureAllocation {
|
||||
/// Corresponding copyable texture handle
|
||||
handle: TextureHandle,
|
||||
|
||||
/// The offset of the allocation in the atlas, in pixels
|
||||
offset: UVec2,
|
||||
|
||||
/// The requested size of the allocation, in pixels
|
||||
size: UVec2,
|
||||
|
||||
/// The maximum size of the allocation, used for reusing deallocated allocations
|
||||
///
|
||||
/// Usually equal to `size`, but may be larger than the requested size
|
||||
/// if the allocation was reused by a smaller texture at some point
|
||||
max_size: UVec2,
|
||||
}
|
||||
|
||||
impl TextureAllocation {
|
||||
/// Create a new texture allocation with the specified parameters.
|
||||
///
|
||||
/// The `max_size` parameter will be set equal to `size`.
|
||||
pub fn new(handle: TextureHandle, offset: UVec2, size: UVec2) -> Self {
|
||||
Self {
|
||||
handle,
|
||||
offset,
|
||||
size,
|
||||
max_size: size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A texture atlas that can be used to pack multiple textures into a single texture.
|
||||
pub struct TextureAtlas {
|
||||
/// The size of the atlas, in pixels
|
||||
size: UVec2,
|
||||
|
||||
/// The texture data of the atlas, ALWAYS in RGBA8 format
|
||||
data: Vec<u8>,
|
||||
|
||||
/// The packer used to allocate space for textures in the atlas
|
||||
packer: DensePacker,
|
||||
|
||||
/// The next id to be used for a texture handle\
|
||||
/// Gets incremented every time a new texture is allocated
|
||||
next_id: TextureId,
|
||||
|
||||
/// Active allocated textures, indexed by id of their handle
|
||||
allocations: HashMap<TextureId, TextureAllocation, BuildNoHashHasher<TextureId>>,
|
||||
|
||||
/// Deallocated allocations that can be reused, sorted by size
|
||||
//TODO: use binary heap or btreeset for reuse_allocations instead, but this works for now
|
||||
reuse_allocations: Vec<TextureAllocation>,
|
||||
}
|
||||
|
||||
impl TextureAtlas {
|
||||
pub fn new(size: UVec2) -> Self {
|
||||
/// Create a new texture atlas with the specified size.
|
||||
pub(crate) fn new(size: UVec2) -> Self {
|
||||
assert_size(size);
|
||||
let data_bytes = (size.x * size.y) as usize * RGBA_BYTES_PER_PIXEL;
|
||||
Self {
|
||||
|
@ -84,9 +156,16 @@ impl TextureAtlas {
|
|||
),
|
||||
next_id: 0,
|
||||
allocations: HashMap::default(),
|
||||
reuse_allocations: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the next handle
|
||||
///
|
||||
/// Does not allocate a texture associated with it
|
||||
/// This handle will be invalid until it's associated with a texture.
|
||||
///
|
||||
/// Used internally in `allocate` and `allocate_with_data`.
|
||||
fn next_handle(&mut self, size: UVec2) -> TextureHandle {
|
||||
let handle = TextureHandle {
|
||||
id: self.next_id,
|
||||
|
@ -96,7 +175,6 @@ impl TextureAtlas {
|
|||
handle
|
||||
}
|
||||
|
||||
|
||||
/// Allocate a texture in the atlas, returning a handle to it.\
|
||||
/// The data present in the texture is undefined, and may include garbage data.
|
||||
///
|
||||
|
@ -105,11 +183,28 @@ impl TextureAtlas {
|
|||
pub fn allocate(&mut self, size: UVec2) -> TextureHandle {
|
||||
assert_size(size);
|
||||
|
||||
// Check if any deallocated allocations can be reused
|
||||
// Find the smallest allocation that fits the requested size
|
||||
// (The list is already sorted by size)
|
||||
for (idx, allocation) in self.reuse_allocations.iter().enumerate() {
|
||||
if allocation.max_size.x >= size.x && allocation.max_size.y >= size.y {
|
||||
let allocation = self.reuse_allocations.remove(idx);
|
||||
let handle = self.next_handle(size);
|
||||
self.allocations.insert_unique_unchecked(handle.id, TextureAllocation {
|
||||
handle,
|
||||
offset: allocation.offset,
|
||||
size,
|
||||
max_size: allocation.max_size,
|
||||
});
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
||||
// Pack the texture
|
||||
let pack = self.packer.pack(
|
||||
size.x as i32,
|
||||
size.y as i32,
|
||||
ALLOW_ROTATION
|
||||
false
|
||||
);
|
||||
|
||||
//TODO: handle pack failure by either resizing the atlas or returning an error
|
||||
|
@ -118,12 +213,97 @@ impl TextureAtlas {
|
|||
|
||||
// Allocate the texture
|
||||
let handle = self.next_handle(size);
|
||||
let allocation = TextureAllocation { handle, offset, size };
|
||||
let allocation = TextureAllocation::new(handle, offset, size);
|
||||
self.allocations.insert_unique_unchecked(handle.id, allocation);
|
||||
|
||||
handle
|
||||
}
|
||||
|
||||
/// Deallocate a texture in the atlas, allowing its space to be reused by future allocations.
|
||||
///
|
||||
/// # Panics
|
||||
/// - If the texture handle is invalid for this atlas.
|
||||
pub fn deallocate(&mut self, handle: TextureHandle) {
|
||||
// Remove the allocation from the active allocations
|
||||
let allocation = self.allocations
|
||||
.remove(&handle.id)
|
||||
.expect("invalid texture handle");
|
||||
|
||||
// TODO: this is not the most efficient way to do this:
|
||||
// And put it in the reuse allocations queue
|
||||
self.reuse_allocations.push(allocation);
|
||||
self.reuse_allocations.sort_unstable_by_key(|a| a.size.x * a.size.y);
|
||||
}
|
||||
|
||||
/// Update the data of a texture in the atlas.\
|
||||
/// The texture must have been previously allocated with `allocate` or `allocate_with_data`.
|
||||
///
|
||||
/// The source data must be in the format specified by the `format` parameter.\
|
||||
/// (Please note that the internal format of the texture is always RGBA8, regardless of the source format.)
|
||||
///
|
||||
/// The function will silently ignore any data that doesn't fit in the texture.
|
||||
///
|
||||
/// # Panics
|
||||
/// - If the texture handle is invalid for this atlas.
|
||||
/// - The length of the data array is less than the size of the texture.
|
||||
pub fn update(&mut self, handle: TextureHandle, format: SourceTextureFormat, data: &[u8]) {
|
||||
assert!(
|
||||
data.len() >= handle.size.x as usize * handle.size.y as usize * format.bytes_per_pixel(),
|
||||
"data length must be at least the size of the texture"
|
||||
);
|
||||
|
||||
let bpp = format.bytes_per_pixel();
|
||||
|
||||
let TextureAllocation { size, offset, ..} = self.allocations
|
||||
.get(&handle.id)
|
||||
.expect("invalid texture handle");
|
||||
|
||||
for y in 0..size.y {
|
||||
for x in 0..size.x {
|
||||
let src_idx = (y * size.x + x) as usize * bpp;
|
||||
let dst_idx: usize = (
|
||||
(offset.y + y) * size.x +
|
||||
(offset.x + x)
|
||||
) as usize * RGBA_BYTES_PER_PIXEL;
|
||||
|
||||
let src = &data[src_idx..src_idx + bpp];
|
||||
let dst = &mut self.data[dst_idx..dst_idx + RGBA_BYTES_PER_PIXEL];
|
||||
|
||||
match format {
|
||||
SourceTextureFormat::RGBA8 => {
|
||||
dst.copy_from_slice(src);
|
||||
},
|
||||
SourceTextureFormat::ARGB8 => {
|
||||
dst[..3].copy_from_slice(&src[1..]);
|
||||
dst[3] = src[0];
|
||||
},
|
||||
SourceTextureFormat::BGRA8 => {
|
||||
dst.copy_from_slice(src);
|
||||
dst.rotate_right(1);
|
||||
dst.reverse();
|
||||
},
|
||||
SourceTextureFormat::ABGR8 => {
|
||||
dst.copy_from_slice(src);
|
||||
dst.reverse();
|
||||
},
|
||||
SourceTextureFormat::RGB8 => {
|
||||
dst[..3].copy_from_slice(src);
|
||||
dst[3] = 0xff;
|
||||
},
|
||||
SourceTextureFormat::BGR8 => {
|
||||
dst[..3].copy_from_slice(src);
|
||||
dst[..3].reverse();
|
||||
dst[3] = 0xff;
|
||||
},
|
||||
SourceTextureFormat::A8 => {
|
||||
dst[..3].fill(0xff);
|
||||
dst[3] = src[0];
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocate a texture in the atlas, returning a handle to it.\
|
||||
/// The texture is initialized with the provided data.
|
||||
///
|
||||
|
@ -156,37 +336,16 @@ impl TextureAtlas {
|
|||
|
||||
// Allocate the texture
|
||||
let handle = self.allocate(size);
|
||||
let allocation = self.allocations.get(&handle.id).unwrap();
|
||||
|
||||
for y in 0..size.y {
|
||||
for x in 0..size.x {
|
||||
let src_idx = (y * size.x + x) as usize * bytes_per_pixel;
|
||||
let dst_idx: usize = (
|
||||
(allocation.offset.y + y) * size.x +
|
||||
(allocation.offset.x + x)
|
||||
) as usize * RGBA_BYTES_PER_PIXEL;
|
||||
|
||||
let src = &data[src_idx..src_idx + bytes_per_pixel];
|
||||
let dst = &mut self.data[dst_idx..dst_idx + RGBA_BYTES_PER_PIXEL];
|
||||
|
||||
match format {
|
||||
SourceTextureFormat::RGBA8 => {
|
||||
dst.copy_from_slice(src);
|
||||
}
|
||||
SourceTextureFormat::RGB8 => {
|
||||
dst[..3].copy_from_slice(src);
|
||||
dst[3] = 255;
|
||||
}
|
||||
SourceTextureFormat::A8 => {
|
||||
dst[0] = src[0];
|
||||
dst[1] = src[0];
|
||||
dst[2] = src[0];
|
||||
dst[3] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Write the data to the texture
|
||||
self.update(handle, format, data);
|
||||
|
||||
handle
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TextureAtlas {
|
||||
fn default() -> Self {
|
||||
Self::new(DEFAULT_ATLAS_SIZE)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ pub mod draw;
|
|||
pub mod measure;
|
||||
pub mod state;
|
||||
pub mod text;
|
||||
pub mod color;
|
||||
pub mod signal;
|
||||
pub mod frame;
|
||||
|
||||
|
|
Loading…
Reference in a new issue