use std::{num::NonZeroU32, time::Instant};
use glam::{U8Vec2, UVec2, UVec4, Vec2, Vec4, uvec2};
use rustc_hash::FxHashMap;
use swash::{GlyphId, zeno::Placement};
use crate::RRect;
use super::{DrawCommand, RawImage};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub struct Land {
pub pos: UVec2,
pub size: UVec2,
}
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct LandUv {
pub pos: Vec2,
pub size: Vec2,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct QuadNodeId(NonZeroU32);
#[derive(Debug, Clone, Copy)]
enum QuadSlot {
Free {
prev_free: Option<QuadNodeId>,
next_free: Option<QuadNodeId>,
},
Branch {
children: [[QuadNodeId; 2]; 2],
},
Glyph {
key: GlyphKey,
size: UVec2,
},
}
#[derive(Debug, Clone, Copy)]
struct QuadNode {
parent_or_next_free: Option<QuadNodeId>,
pos: UVec2,
size: UVec2,
slot: QuadSlot,
prev_priority: Option<QuadNodeId>,
next_priority: Option<QuadNodeId>,
last_frame: u64,
}
#[derive(Debug, Clone, Copy)]
pub enum InsertGlyphError {
EmptyImage,
TooBig,
NoSpaceLeft,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct GlyphKey {
pub id: GlyphId,
pub quantization: U8Vec2,
pub style_hash: u32,
}
#[derive(Debug, Clone, Copy)]
pub struct GlyphCacheEntry {
quad_node_id: QuadNodeId,
pub placement: Placement,
pub is_colored: bool,
pub level: u32,
pub created: Instant,
}
pub struct Quadlas {
size: u32,
max_texture_size: u32,
quad_tree: Vec<QuadNode>,
first_unoccupied_node: Option<QuadNodeId>,
first_free_slot_per_level: Box<[Option<QuadNodeId>; 16]>,
first_priority_slot_per_level: Box<[Option<QuadNodeId>; 16]>,
last_priority_slot_per_level: Box<[Option<QuadNodeId>; 16]>,
glyph_cache: FxHashMap<GlyphKey, GlyphCacheEntry>,
}
impl Quadlas {
pub fn new(max_texture_size: u32, size: u32) -> Self {
let mut quadlas = Self {
size,
max_texture_size,
quad_tree: Vec::new(),
first_unoccupied_node: None,
first_free_slot_per_level: Box::default(),
first_priority_slot_per_level: Box::default(),
last_priority_slot_per_level: Box::default(),
glyph_cache: FxHashMap::default(),
};
quadlas.clear_with_size(size);
quadlas
}
pub fn size(&self) -> u32 {
self.size
}
fn subdivide_quad_node(&mut self, id: QuadNodeId, level: u32) -> [[QuadNodeId; 2]; 2] {
fn push_quad_node(
quadlas: &mut Quadlas,
parent: QuadNodeId,
parent_level: u32,
pos: UVec2,
size: u32,
) -> QuadNodeId {
let new_node = QuadNode {
parent_or_next_free: Some(parent),
pos,
size: UVec2::splat(size),
slot: QuadSlot::Free {
prev_free: None,
next_free: None,
},
prev_priority: None,
next_priority: None,
last_frame: 0,
};
let id = if let Some(first_unoccupied_node_id) = quadlas.first_unoccupied_node {
let unoccupied = quadlas.quad_node_mut(first_unoccupied_node_id);
let next_unoccupied = unoccupied.parent_or_next_free;
*unoccupied = new_node;
quadlas.first_unoccupied_node = next_unoccupied;
first_unoccupied_node_id
} else {
quadlas.quad_tree.push(new_node);
QuadNodeId(NonZeroU32::new(quadlas.quad_tree.len() as u32).unwrap())
};
quadlas.push_free_slot(id, parent_level + 1);
id
}
let size = self.size / 2_u32.pow(level + 1);
let pos = self.quad_node(id).pos;
let qnbr = push_quad_node(self, id, level, pos + size * uvec2(1, 1), size); let qnbl = push_quad_node(self, id, level, pos + size * uvec2(0, 1), size); let qntr = push_quad_node(self, id, level, pos + size * uvec2(1, 0), size); let qntl = push_quad_node(self, id, level, pos + size * uvec2(0, 0), size);
let children = [[qntl, qntr], [qnbl, qnbr]];
self.quad_node_mut(id).slot = QuadSlot::Branch { children };
children
}
fn erase_node(&mut self, id: QuadNodeId, level: u32) {
match self.quad_node_mut(id).slot {
QuadSlot::Branch { children } => {
for child_row in children {
for child_id in child_row {
self.erase_node(child_id, level + 1);
self.quad_node_mut(child_id).parent_or_next_free = self.first_unoccupied_node;
self.first_unoccupied_node = Some(child_id);
self.remove_free_slot(child_id, level + 1);
}
}
}
QuadSlot::Free { .. } => {
return;
}
QuadSlot::Glyph { key, .. } => {
self.glyph_cache.remove(&key);
}
}
self.remove_priority_slot(id, level);
let next_free = self.first_free_slot_per_level[level as usize];
self.quad_node_mut(id).slot = QuadSlot::Free {
prev_free: None,
next_free,
};
self.first_free_slot_per_level[level as usize] = Some(id);
}
pub fn to_land_uv(&self, land: Land) -> LandUv {
LandUv {
pos: land.pos.as_vec2() / self.size as f32,
size: land.size.as_vec2() / self.size as f32,
}
}
fn quad_node(&self, id: QuadNodeId) -> &QuadNode {
&self.quad_tree[id.0.get() as usize - 1]
}
fn quad_node_mut(&mut self, id: QuadNodeId) -> &mut QuadNode {
&mut self.quad_tree[id.0.get() as usize - 1]
}
}
impl Quadlas {
pub fn get_cached_glyph(&mut self, glyph_key: GlyphKey, current_frame: u64) -> Option<(Land, GlyphCacheEntry)> {
if let Some(&gce) = self.glyph_cache.get(&glyph_key) {
let quad_node = self.quad_node_mut(gce.quad_node_id);
let QuadSlot::Glyph { size, .. } = quad_node.slot else {
panic!("non-glyph was inserted into the glyph cache");
};
let pos = quad_node.pos;
self.update_priority(gce.quad_node_id, gce.level, current_frame);
Some((Land { pos, size }, gce))
} else {
None
}
}
pub fn insert_glyph_unless_cached(
&mut self,
glyph_key: GlyphKey,
placement: Placement,
is_colored: bool,
image: RawImage,
current_frame: u64,
) -> Result<(Land, GlyphCacheEntry), InsertGlyphError> {
if let Some(glyph_data) = self.get_cached_glyph(glyph_key, current_frame) {
return Ok(glyph_data);
}
let image_size = image.width.max(image.height);
if image_size == 0 {
return Err(InsertGlyphError::EmptyImage);
}
if image_size > self.size {
return Err(InsertGlyphError::TooBig);
}
let image_level = {
let mut curr_size = self.size;
let mut curr_level = 0;
while curr_size / 2 >= image_size {
curr_size /= 2;
curr_level += 1;
}
curr_level
};
let glyph_node_id = match self.pop_free_slot_le_level(image_level) {
Some(glyph_node_id) => glyph_node_id,
None => {
self.free_last_priority_slot(image_level, current_frame);
match self.pop_free_slot_le_level(image_level) {
Some(glyph_node_id) => glyph_node_id,
None => return Err(InsertGlyphError::NoSpaceLeft),
}
}
};
let size = uvec2(image.width, image.height);
let node = self.quad_node_mut(glyph_node_id);
node.slot = QuadSlot::Glyph { key: glyph_key, size };
let pos = node.pos;
self.update_priority(glyph_node_id, image_level, current_frame);
let glyph_cache_entry = GlyphCacheEntry {
quad_node_id: glyph_node_id,
placement,
is_colored,
level: image_level,
created: Instant::now(),
};
self.glyph_cache.insert(glyph_key, glyph_cache_entry);
Ok((Land { pos, size }, glyph_cache_entry))
}
pub fn grow(&mut self) {
self.clear_with_size(self.size * 2);
}
pub fn clear_with_size(&mut self, size: u32) {
self.quad_tree.clear();
self.first_unoccupied_node = None;
self.first_free_slot_per_level.iter_mut().for_each(|s| *s = None);
self.first_priority_slot_per_level.iter_mut().for_each(|s| *s = None);
self.last_priority_slot_per_level.iter_mut().for_each(|s| *s = None);
self.glyph_cache.clear();
self.size = size.min(self.max_texture_size);
self.quad_tree.push(QuadNode {
parent_or_next_free: None,
pos: UVec2::ZERO,
size: UVec2::splat(self.size),
slot: QuadSlot::Free {
prev_free: None,
next_free: None,
},
prev_priority: None,
next_priority: None,
last_frame: 0,
});
self.first_free_slot_per_level[0] = Some(QuadNodeId(NonZeroU32::MIN));
}
}
impl Quadlas {
fn push_priority_slot(&mut self, id: QuadNodeId, level: u32, current_frame: u64) {
let mut next_priority_slot = None;
if let Some(first_priority_slot) = (self.first_priority_slot_per_level)[level as usize] {
next_priority_slot = Some(first_priority_slot);
self.quad_node_mut(first_priority_slot).prev_priority = Some(id);
}
{
let node = self.quad_node_mut(id);
node.next_priority = next_priority_slot;
node.last_frame = current_frame;
}
if self.first_priority_slot_per_level[level as usize].replace(id).is_none() {
self.last_priority_slot_per_level[level as usize] = Some(id);
}
}
fn remove_priority_slot(&mut self, priority_slot: QuadNodeId, level: u32) {
let (prev_priority, next_priority) = {
let node = self.quad_node_mut(priority_slot);
(node.prev_priority.take(), node.next_priority.take())
};
if let Some(next_priority) = next_priority {
self.quad_node_mut(next_priority).prev_priority = prev_priority;
}
if let Some(prev_priority) = prev_priority {
self.quad_node_mut(prev_priority).next_priority = next_priority;
}
if (self.first_priority_slot_per_level)[level as usize] == Some(priority_slot) {
if let Some(next_priority) = next_priority {
self.first_priority_slot_per_level[level as usize] = Some(next_priority);
} else {
self.first_priority_slot_per_level[level as usize] = None;
};
}
if (self.last_priority_slot_per_level)[level as usize] == Some(priority_slot) {
if let Some(prev_priority) = prev_priority {
self.last_priority_slot_per_level[level as usize] = Some(prev_priority);
} else {
self.last_priority_slot_per_level[level as usize] = None;
};
}
}
fn update_priority(&mut self, priority_slot: QuadNodeId, level: u32, current_frame: u64) {
let mut parent_id = Some(priority_slot);
let mut level = level + 1;
while let Some(node_id) = parent_id {
level -= 1;
self.remove_priority_slot(node_id, level);
self.push_priority_slot(node_id, level, current_frame);
parent_id = self.quad_node_mut(node_id).parent_or_next_free;
}
}
fn free_last_priority_slot(&mut self, level: u32, current_frame: u64) {
let mut avail_level = level;
let avail_slot_id = loop {
if let Some(last_slot_id) = self.last_priority_slot_per_level[avail_level as usize] {
let last_slot = self.quad_node(last_slot_id);
if last_slot.last_frame < current_frame {
break last_slot_id;
}
}
if avail_level == 0 {
return;
}
avail_level -= 1;
};
self.erase_node(avail_slot_id, avail_level);
}
}
impl Quadlas {
fn push_free_slot(&mut self, id: QuadNodeId, level: u32) {
let mut next_free_slot = None;
if let Some(free_slot) = self.first_free_slot_per_level[level as usize] {
next_free_slot = Some(free_slot);
match &mut self.quad_node_mut(free_slot).slot {
QuadSlot::Free { prev_free, .. } => *prev_free = Some(id),
_ => panic!("Supposed free slot is not of variant QuadSlot::Free"),
}
}
match &mut self.quad_node_mut(id).slot {
QuadSlot::Free { next_free, .. } => *next_free = next_free_slot,
_ => panic!("Supposed free slot is not of variant QuadSlot::Free"),
};
self.first_free_slot_per_level[level as usize] = Some(id);
}
fn remove_free_slot(&mut self, free_slot: QuadNodeId, level: u32) {
let (curr_prev_free, curr_next_free) = match &mut self.quad_node_mut(free_slot).slot {
QuadSlot::Free { prev_free, next_free } => (prev_free.take(), next_free.take()),
_ => panic!("Supposed free slot is not of variant QuadSlot::Free"),
};
if let Some(curr_next_free) = curr_next_free {
match &mut self.quad_node_mut(curr_next_free).slot {
QuadSlot::Free { prev_free, .. } => *prev_free = curr_prev_free,
_ => panic!("Supposed free slot is not of variant QuadSlot::Free"),
};
}
if let Some(curr_prev_free) = curr_prev_free {
match &mut self.quad_node_mut(curr_prev_free).slot {
QuadSlot::Free { next_free, .. } => *next_free = curr_next_free,
_ => panic!("Supposed free slot is not of variant QuadSlot::Free"),
};
}
if self.first_free_slot_per_level[level as usize] == Some(free_slot) {
self.first_free_slot_per_level[level as usize] = curr_next_free;
}
}
fn pop_free_slot_le_level(&mut self, level: u32) -> Option<QuadNodeId> {
let mut curr_level = level;
loop {
if let Some(mut free_slot) = self.first_free_slot_per_level[curr_level as usize] {
while curr_level < level {
self.remove_free_slot(free_slot, curr_level);
free_slot = self.subdivide_quad_node(free_slot, curr_level)[0][0];
curr_level += 1;
}
self.remove_free_slot(free_slot, level);
return Some(free_slot);
}
if curr_level == 0 {
return None;
}
curr_level -= 1;
}
}
}
#[allow(unused)]
impl Quadlas {
#[allow(unused)]
fn debug_free_list(&self, level: u32) {
let mut s = format!("Free list (L{}): ", level);
if let Some(free_slot) = self.first_free_slot_per_level[level as usize] {
let mut i_free_slot = Some(free_slot);
while let Some(free_slot) = i_free_slot {
s += match &self.quad_node(free_slot).slot {
QuadSlot::Free { next_free, .. } => {
i_free_slot = *next_free;
"F"
}
QuadSlot::Branch { .. } => {
i_free_slot = None;
"B??"
}
QuadSlot::Glyph { .. } => {
i_free_slot = None;
"G??"
}
};
s += " > ";
}
}
s += "()";
#[cfg(feature = "logging")]
tracing::info!("{}", s);
#[cfg(not(feature = "logging"))]
eprintln!("{}", s);
}
pub fn debug_view(&self, draw_commands: &mut Vec<DrawCommand>, pos_offset: Vec2, quad_size: Vec2) {
let factor = quad_size / self.size as f32;
self.debug_node_view(draw_commands, QuadNodeId(NonZeroU32::MIN), pos_offset, factor);
}
fn debug_node_view(&self, draw_commands: &mut Vec<DrawCommand>, id: QuadNodeId, pos_offset: Vec2, factor: Vec2) {
let QuadNode { pos, size, slot, .. } = *self.quad_node(id);
match slot {
QuadSlot::Free { .. } => {
}
QuadSlot::Branch { children } => {
draw_commands.push(DrawCommand::RRect(RRect {
pos: pos_offset + pos.as_vec2() * factor,
size: size.as_vec2() * factor,
border_radius: Vec4::splat(0.0),
border_width: Vec4::splat(1.0),
fill_color: 0x00000000,
stroke_color: 0xffffff20,
}));
for rows in children {
for child in rows {
self.debug_node_view(draw_commands, child, pos_offset, factor);
}
}
}
QuadSlot::Glyph { size: glyph_size, .. } => {
draw_commands.push(DrawCommand::RRect(RRect {
pos: pos_offset + pos.as_vec2() * factor,
size: size.as_vec2() * factor,
border_radius: Vec4::splat(0.0),
border_width: Vec4::splat(1.0),
fill_color: 0x00000000,
stroke_color: 0xff323280,
}));
draw_commands.push(DrawCommand::RRect(RRect {
pos: pos_offset + pos.as_vec2() * factor,
size: glyph_size.as_vec2() * factor,
border_radius: Vec4::splat(0.0),
border_width: Vec4::splat(1.0),
fill_color: 0x00000000,
stroke_color: 0xffaa32ff,
}));
}
}
}
pub fn debug_glyph_priorities(
&self,
draw_commands: &mut Vec<DrawCommand>,
current_frame: u64,
pos_offset: Vec2,
quad_size: Vec2,
) {
let now = Instant::now();
let factor = quad_size / self.size as f32;
self.debug_glyph_priorities_rec(
draw_commands,
now,
current_frame,
QuadNodeId(NonZeroU32::MIN),
pos_offset,
factor,
);
}
fn debug_glyph_priorities_rec(
&self,
draw_commands: &mut Vec<DrawCommand>,
now: Instant,
current_frame: u64,
id: QuadNodeId,
pos_offset: Vec2,
factor: Vec2,
) {
let QuadNode {
pos,
size,
slot,
last_frame,
..
} = *self.quad_node(id);
match slot {
QuadSlot::Free { .. } => {
}
QuadSlot::Branch { children } => {
for rows in children {
for child in rows {
self.debug_glyph_priorities_rec(draw_commands, now, current_frame, child, pos_offset, factor);
}
}
}
QuadSlot::Glyph { ref key, .. } => {
fn color_to_vec4(c: u32) -> Vec4 {
UVec4::new((c >> 24) & 0xff, (c >> 16) & 0xff, (c >> 8) & 0xff, c & 0xff).as_vec4() / 255.0
}
fn vec4_to_color(c: Vec4) -> u32 {
(((c.x * 255.0) as u32).min(255) << 24)
| (((c.y * 255.0) as u32).min(255) << 16)
| (((c.z * 255.0) as u32).min(255) << 8)
| (((c.w * 255.0) as u32).min(255))
}
fn lerp_vec4(start: Vec4, end: Vec4, t: f32) -> Vec4 {
start + (end - start) * t
}
let sep = current_frame - last_frame;
let base_color = match () {
_ if sep == 0 => color_to_vec4(0x60ff8032),
_ if sep < 1024 => color_to_vec4(0xffff6432),
_ if sep < 2048 => color_to_vec4(0xffaa6432),
_ => color_to_vec4(0xff646432),
};
let fill_color = match self.glyph_cache.get(key) {
Some(entry) => {
let t = (now.duration_since(entry.created).as_secs_f32() / 0.500).min(1.0);
let cubic_t = 1.0 - (1.0 - t).powi(3);
let flash_color: Vec4 = color_to_vec4(0xffffffaa);
lerp_vec4(flash_color, base_color, cubic_t)
}
None => base_color,
};
draw_commands.push(DrawCommand::RRect(RRect {
pos: pos_offset + pos.as_vec2() * factor,
size: size.as_vec2() * factor,
border_radius: Vec4::splat(0.0),
border_width: Vec4::splat(0.0),
fill_color: vec4_to_color(fill_color),
stroke_color: 0x00000000,
}));
}
}
}
fn debug_ffspl(&self) -> String {
let mut s = String::new();
for &slot in self.first_free_slot_per_level.as_ref() {
let Some(slot) = slot else {
s.push('_');
continue;
};
let c = match &self.quad_node(slot).slot {
QuadSlot::Free { next_free, .. } => {
let mut count = 1;
let mut counter_next_free = *next_free;
while let Some(next_slot) = counter_next_free {
counter_next_free = match self.quad_node(next_slot).slot {
QuadSlot::Free { next_free, .. } => next_free,
_ => None,
};
if count == u8::MAX {
break;
}
count += 1;
}
if count > 9 {
s += "[>";
let mut count = 1;
let mut counter_next_free = *next_free;
while let Some(next_slot) = counter_next_free {
counter_next_free = match self.quad_node(next_slot).slot {
QuadSlot::Free { next_free, .. } => {
s += &format!(" {}", next_slot.0);
next_free
}
QuadSlot::Branch { .. } => {
s.push('B');
None
}
QuadSlot::Glyph { .. } => {
s.push('G');
None
}
};
if count == u8::MAX {
s += "...";
break;
}
count += 1;
}
s += "]";
'+'
} else {
(b'0' + count) as char
}
}
QuadSlot::Branch { .. } => 'B',
QuadSlot::Glyph { .. } => 'G',
};
s.push(c);
}
s
}
}
#[cfg(test)]
mod tests {
#[test]
fn atlas_size() {
let size = 2048;
let size_expectations = &[
(5, 8, 8),
(9, 16, 7),
(22, 32, 6),
(48, 64, 5),
(128, 128, 4),
(234, 256, 3),
(400, 512, 2),
(599, 1024, 1),
(727, 1024, 1),
(1024, 1024, 1),
(2000, 2048, 0),
];
for &(image_size, expected_size, expected_level) in size_expectations {
let mut curr_size = size;
let mut curr_level = 0;
while curr_size / 2 >= image_size {
curr_size /= 2;
curr_level += 1;
}
assert_eq!(curr_size, expected_size);
assert_eq!(curr_level, expected_level);
}
}
}