diff --git a/hui/src/draw.rs b/hui/src/draw.rs index 0198a15..1e421bd 100644 --- a/hui/src/draw.rs +++ b/hui/src/draw.rs @@ -6,10 +6,13 @@ use crate::{ IfModified }; -mod atlas; -mod corner_radius; +pub(crate) mod atlas; +//pub(crate) use atlas::{TextureAllocation, TextureAtlasManager}; +//pub use atlas::TextureHandle; +mod corner_radius; pub use corner_radius::RoundedCorners; + use std::borrow::Cow; use fontdue::layout::{Layout, CoordinateSystem, TextStyle}; use glam::{Vec2, Vec4, vec2}; @@ -296,6 +299,7 @@ impl UiDrawCall { } } +#[allow(deprecated)] impl IfModified for (bool, &UiDrawCall) { fn if_modified(&self) -> Option<&UiDrawCall> { match self.0 { diff --git a/hui/src/draw/atlas.rs b/hui/src/draw/atlas.rs index c630cac..a9cfbd4 100644 --- a/hui/src/draw/atlas.rs +++ b/hui/src/draw/atlas.rs @@ -3,7 +3,11 @@ use hashbrown::HashMap; use nohash_hasher::BuildNoHashHasher; use rect_packer::DensePacker; +use crate::IfModified; + const CHANNEL_COUNT: u32 = 4; +//TODO: make this work +const ALLOW_ROTATION: bool = false; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct TextureHandle { @@ -27,15 +31,20 @@ pub(crate) struct TextureAllocation { pub rotated: bool, } +/// Manages a texture atlas and the allocation of space within it\ +/// The atlas is alllowed to grow and resize dynamically, as needed pub(crate) struct TextureAtlasManager { packer: DensePacker, count: u32, size: UVec2, data: Vec, allocations: HashMap>, + modified: bool, } impl TextureAtlasManager { + /// 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 pub fn new(size: UVec2) -> Self { Self { packer: DensePacker::new(size.x as i32, size.y as i32), @@ -43,9 +52,11 @@ impl TextureAtlasManager { size: UVec2::new(0, 0), data: vec![0; (size.x * size.y * CHANNEL_COUNT) as usize], allocations: HashMap::default(), + modified: true, } } + /// Resize the texture atlas to the new size in-place, preserving the existing data 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); @@ -65,13 +76,15 @@ impl TextureAtlasManager { todo!("Atlas downscaling is not implemented yet"); } self.size = new_size; + self.modified = true; } /// 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\ - /// Use `allocate_resize` to allocate a texture and resize the atlas if necessary - fn allocate(&mut self, size: UVec2) -> Option { - let result = self.packer.pack(size.x as i32, size.y as i32, true)?; + /// 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 { + let result = self.packer.pack(size.x as i32, size.y as i32, ALLOW_ROTATION)?; let index = self.count; self.count += 1; let allocation = TextureAllocation { @@ -79,28 +92,30 @@ impl TextureAtlasManager { 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 - rotated: result.width != size.x as i32, + rotated: ALLOW_ROTATION && (result.width != size.x as i32), }; self.allocations.insert_unique_unchecked(index, allocation); Some(TextureHandle { index }) } /// Allocate a new texture region in the atlas and resize the atlas if necessary\ - /// This function should never fail under normal circumstances. - fn allocate_resize(&mut self, size: UVec2) -> TextureHandle { + /// 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 { 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); - self.allocate(size).unwrap() + self.try_allocate(size).unwrap() } - /// Allocate a new texture region in the atlas and copy the data into it + /// 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. 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); - let handle = self.allocate_resize(size); + let handle = self.allocate(size); let allocation = self.allocations.get_mut(&handle.index).unwrap(); assert!(!allocation.rotated, "Rotated textures are not implemented yet"); for y in 0..size.y { @@ -130,10 +145,33 @@ impl TextureAtlasManager { pub fn get(&self, handle: TextureHandle) -> Option<&TextureAllocation> { self.allocations.get(&handle.index) } + + /// 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 + } } impl Default for TextureAtlasManager { + /// Create a new texture atlas with a default size of 512x512 fn default() -> Self { Self::new(UVec2::new(512, 512)) } } + +#[allow(deprecated)] +impl<'a> IfModified for TextureAtlasManager { + fn if_modified(&self) -> Option<&Self> { + match self.modified { + true => Some(self), + false => None, + } + } +} diff --git a/hui/src/lib.rs b/hui/src/lib.rs index 0bcd95a..b826d8f 100644 --- a/hui/src/lib.rs +++ b/hui/src/lib.rs @@ -23,26 +23,8 @@ pub mod text; pub use instance::UiInstance; +#[deprecated(since = "0.1.0-alpha.4", note = "will be removed in the next release")] pub trait IfModified { + #[deprecated(since = "0.1.0-alpha.4", note = "will be removed in the next release")] fn if_modified(&self) -> Option<&T>; } - -#[allow(deprecated)] -#[deprecated(since = "0.1.0-alpha.3", note = "will be removed in the next release")] -pub struct ElementList(Vec>); - -#[allow(deprecated)] -#[deprecated(since = "0.1.0-alpha.3", note = "will be removed in the next release")] -impl ElementList { - pub fn add(&mut self, element: impl element::UiElement + 'static) { - self.0.push(Box::new(element)); - } -} - -#[allow(deprecated)] -#[deprecated(since = "0.1.0-alpha.3", note = "will be removed in the next release")] -pub fn elements(f: impl FnOnce(&mut ElementList)) -> Vec> { - let mut elements = ElementList(Vec::new()); - f(&mut elements); - elements.0 -} diff --git a/hui/src/text.rs b/hui/src/text.rs index d69570b..ff83dcf 100644 --- a/hui/src/text.rs +++ b/hui/src/text.rs @@ -12,6 +12,8 @@ use fontdue::{Font, FontSettings}; use ftm::FontTextureManager; pub use ftm::{FontTextureInfo, GlyphCacheEntry}; +use crate::draw::atlas::TextureAtlasManager; + pub struct TextRenderer { fm: FontManager, ftm: FontTextureManager, @@ -29,16 +31,8 @@ impl TextRenderer { self.fm.add_font(Font::from_bytes(font, FontSettings::default()).unwrap()) } - pub fn reset_frame(&mut self) { - self.ftm.reset_modified(); - } - - pub fn font_texture(&self) -> FontTextureInfo { - self.ftm.info() - } - - pub fn glyph(&mut self, font_handle: FontHandle, character: char, size: u8) -> Arc { - self.ftm.glyph(&self.fm, font_handle, character, size) + pub fn glyph(&mut self, atlas: &mut TextureAtlasManager, font_handle: FontHandle, character: char, size: u8) -> Arc { + self.ftm.glyph(&self.fm, atlas, font_handle, character, size) } pub(crate) fn internal_font(&self, handle: FontHandle) -> &Font { diff --git a/hui/src/text/ftm.rs b/hui/src/text/ftm.rs index 2882b34..5104c2f 100644 --- a/hui/src/text/ftm.rs +++ b/hui/src/text/ftm.rs @@ -1,10 +1,11 @@ use std::sync::Arc; use fontdue::Metrics; -use glam::{IVec2, UVec2, uvec2, ivec2}; +use glam::UVec2; use hashbrown::HashMap; -use rect_packer::DensePacker; - -use crate::IfModified; +use crate::{ + draw::atlas::{TextureAtlasManager, TextureHandle}, + IfModified +}; use super::font::{FontHandle, FontManager}; @@ -16,70 +17,30 @@ struct GlyphCacheKey { } pub struct GlyphCacheEntry { - pub data: Vec, pub metrics: Metrics, - pub position: IVec2, - pub size: UVec2, + pub texture: TextureHandle, } -#[derive(Clone, Copy, Debug)] -pub struct FontTextureInfo<'a> { - pub modified: bool, - pub data: &'a [u8], - pub size: UVec2, -} - -impl<'a> IfModified> for FontTextureInfo<'a> { - fn if_modified(&self) -> Option<&Self> { - match self.modified { - true => Some(self), - false => None, - } - } -} - -// impl<'a> FontTextureInfo<'a> { -// fn if_modified(&self) -> Option { -// match self.modified { -// true => Some(*self), -// false => None, -// } -// } -// } - pub struct FontTextureManager { - glyph_cache: HashMap>, - packer: DensePacker, - font_texture: Vec, - font_texture_size: UVec2, - modified: bool, + glyph_cache: HashMap> } impl FontTextureManager { - pub fn new(size: UVec2) -> Self { + pub fn new() -> Self { FontTextureManager { glyph_cache: HashMap::new(), - packer: DensePacker::new(size.x as i32, size.y as i32), - font_texture: vec![0; (size.x * size.y * 4) as usize], - font_texture_size: size, - modified: false, - } - } - - pub fn reset_modified(&mut self) { - self.modified = false; - } - - pub fn info(&self) -> FontTextureInfo { - FontTextureInfo { - modified: self.modified, - data: &self.font_texture, - size: self.font_texture_size, } } /// Either looks up the glyph in the cache or renders it and adds it to the cache. - pub fn glyph(&mut self, font_manager: &FontManager, font_handle: FontHandle, character: char, size: u8) -> Arc { + pub fn glyph( + &mut self, + atlas: &mut TextureAtlasManager, + font_manager: &FontManager, + font_handle: FontHandle, + character: char, + size: u8 + ) -> Arc { let key = GlyphCacheKey { font_index: font_handle.0, character, @@ -91,37 +52,15 @@ impl FontTextureManager { let font = font_manager.get(font_handle).unwrap(); let (metrics, bitmap) = font.rasterize(character, size as f32); log::debug!("rasterized glyph: {}, {:?}, {:?}", character, metrics, bitmap); - let texture_position = self.packer.pack(metrics.width as i32, metrics.height as i32, false).unwrap(); - let texture_size = uvec2(metrics.width as u32, metrics.height as u32); + let texture = atlas.add(metrics.width, &bitmap); let entry = Arc::new(GlyphCacheEntry { - data: bitmap, metrics, - position: ivec2(texture_position.x, texture_position.y), - size: texture_size, + texture }); self.glyph_cache.insert_unique_unchecked(key, Arc::clone(&entry)); - self.glyph_place(&entry); - self.modified = true; entry } - /// Place glyph onto the font texture. - fn glyph_place(&mut self, entry: &GlyphCacheEntry) { - let tex_size = self.font_texture_size; - let GlyphCacheEntry { size, position, data, .. } = entry; - //println!("{size:?} {position:?}"); - for y in 0..size.y { - for x in 0..size.x { - let src = (size.x * y + x) as usize; - let dst = (tex_size.x * (y + position.y as u32) + (x + position.x as u32)) as usize * 4; - self.font_texture[dst..=(dst + 3)].copy_from_slice(&[255, 255, 255, data[src]]); - //print!("{} ", if data[src] > 0 {'#'} else {'.'}); - //print!("{src} {dst} / "); - } - //println!(); - } - } - // pub fn glyph(&mut self, font_manager: &FontManager, font_handle: FontHandle, character: char, size: u8) -> Arc { // let (is_new, glyph) = self.glyph_allocate(font_manager, font_handle, character, size); // if is_new { @@ -133,7 +72,5 @@ impl FontTextureManager { } impl Default for FontTextureManager { - fn default() -> Self { - Self::new(uvec2(1024, 1024)) - } + fn default() -> Self { Self::new() } }