1use crate::extension::AsWgpu;
29use crate::types::GpuTexture;
30use crate::GraphicsContext;
31use ahash::HashMap;
32use std::sync::Arc;
33
34#[derive(Debug, Clone, Copy, PartialEq)]
40pub struct AtlasRect {
41 pub x: f32,
42 pub y: f32,
43 pub width: f32,
44 pub height: f32,
45}
46
47impl AtlasRect {
48 pub const fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
50 Self { x, y, width, height }
51 }
52}
53
54type Rect = AtlasRect;
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
59pub struct AtlasKey(u64);
60
61impl AtlasKey {
62 pub fn new(s: &str) -> Self {
64 use std::collections::hash_map::DefaultHasher;
65 use std::hash::{Hash, Hasher};
66
67 let mut hasher = DefaultHasher::new();
68 s.hash(&mut hasher);
69 Self(hasher.finish())
70 }
71
72 pub fn from_u64(id: u64) -> Self {
74 Self(id)
75 }
76
77 pub fn as_u64(&self) -> u64 {
79 self.0
80 }
81}
82
83#[derive(Debug, Clone, Copy)]
85pub struct AtlasEntry {
86 pub rect: Rect,
88 pub uv_rect: Rect,
90}
91
92impl AtlasEntry {
93 pub fn new(rect: Rect, atlas_size: f32) -> Self {
95 let uv_rect = Rect {
96 x: rect.x / atlas_size,
97 y: rect.y / atlas_size,
98 width: rect.width / atlas_size,
99 height: rect.height / atlas_size,
100 };
101
102 Self { rect, uv_rect }
103 }
104}
105
106#[derive(Debug, Clone)]
108enum PackerNode {
109 Empty {
111 rect: Rect,
112 },
113 Filled {
115 rect: Rect,
116 key: AtlasKey,
117 },
118 Split {
120 rect: Rect,
121 left: Box<PackerNode>,
122 right: Box<PackerNode>,
123 },
124}
125
126impl PackerNode {
127 fn new(rect: Rect) -> Self {
129 Self::Empty { rect }
130 }
131
132 fn insert(&mut self, key: AtlasKey, width: f32, height: f32) -> Option<Rect> {
134 match self {
135 PackerNode::Empty { rect } => {
136 if width > rect.width || height > rect.height {
138 return None;
139 }
140
141 if width == rect.width && height == rect.height {
143 let result = *rect;
144 *self = PackerNode::Filled { rect: *rect, key };
145 return Some(result);
146 }
147
148 let rect_copy = *rect;
150
151 let horizontal_waste = rect.width - width;
153 let vertical_waste = rect.height - height;
154
155 let (left_rect, right_rect) = if horizontal_waste > vertical_waste {
156 (
158 Rect {
159 x: rect.x,
160 y: rect.y,
161 width,
162 height: rect.height,
163 },
164 Rect {
165 x: rect.x + width,
166 y: rect.y,
167 width: rect.width - width,
168 height: rect.height,
169 },
170 )
171 } else {
172 (
174 Rect {
175 x: rect.x,
176 y: rect.y,
177 width: rect.width,
178 height,
179 },
180 Rect {
181 x: rect.x,
182 y: rect.y + height,
183 width: rect.width,
184 height: rect.height - height,
185 },
186 )
187 };
188
189 let mut left = Box::new(PackerNode::new(left_rect));
190 let right = Box::new(PackerNode::new(right_rect));
191
192 let result = left.insert(key, width, height);
194
195 *self = PackerNode::Split {
196 rect: rect_copy,
197 left,
198 right,
199 };
200
201 result
202 }
203 PackerNode::Filled { .. } => None,
204 PackerNode::Split { left, right, .. } => {
205 left.insert(key, width, height)
207 .or_else(|| right.insert(key, width, height))
208 }
209 }
210 }
211}
212
213pub struct TextureAtlas {
215 texture: GpuTexture,
217 entries: HashMap<AtlasKey, AtlasEntry>,
218 packer: PackerNode,
219 context: Arc<GraphicsContext>,
220 pending_uploads: Vec<(AtlasKey, Vec<u8>, Rect)>,
222 dirty: bool,
223}
224
225impl TextureAtlas {
226 pub fn new(context: Arc<GraphicsContext>, size: u32, format: wgpu::TextureFormat) -> Self {
234 let texture = GpuTexture::new_2d(
235 &context.device,
236 Some("TextureAtlas"),
237 size,
238 size,
239 format,
240 wgpu::TextureUsages::TEXTURE_BINDING
241 | wgpu::TextureUsages::COPY_DST
242 | wgpu::TextureUsages::RENDER_ATTACHMENT,
243 );
244
245 let packer = PackerNode::new(Rect {
246 x: 0.0,
247 y: 0.0,
248 width: size as f32,
249 height: size as f32,
250 });
251
252 Self {
253 texture,
254 entries: HashMap::default(),
255 packer,
256 context,
257 pending_uploads: Vec::new(),
258 dirty: false,
259 }
260 }
261
262 pub fn insert(
273 &mut self,
274 key: AtlasKey,
275 image_data: &[u8],
276 width: u32,
277 height: u32,
278 ) -> Option<AtlasEntry> {
279 if let Some(entry) = self.entries.get(&key) {
281 return Some(*entry);
282 }
283
284 let rect = self.packer.insert(key, width as f32, height as f32)?;
286
287 let entry = AtlasEntry::new(rect, self.texture.width() as f32);
289 self.entries.insert(key, entry);
290
291 self.pending_uploads
293 .push((key, image_data.to_vec(), rect));
294 self.dirty = true;
295
296 Some(entry)
297 }
298
299 pub fn get(&self, key: &AtlasKey) -> Option<&AtlasEntry> {
301 self.entries.get(key)
302 }
303
304 pub fn contains(&self, key: &AtlasKey) -> bool {
306 self.entries.contains_key(key)
307 }
308
309 pub fn upload(&mut self) {
311 if !self.dirty {
312 return;
313 }
314
315 let format = self.texture.format();
316 for (_, data, rect) in &self.pending_uploads {
317 let bytes_per_pixel = match format {
318 wgpu::TextureFormat::Rgba8UnormSrgb | wgpu::TextureFormat::Rgba8Unorm => 4,
319 wgpu::TextureFormat::Bgra8UnormSrgb | wgpu::TextureFormat::Bgra8Unorm => 4,
320 wgpu::TextureFormat::R8Unorm => 1,
321 _ => 4, };
323
324 self.context.queue.write_texture(
325 wgpu::TexelCopyTextureInfo {
326 texture: self.texture.as_wgpu(),
327 mip_level: 0,
328 origin: wgpu::Origin3d {
329 x: rect.x as u32,
330 y: rect.y as u32,
331 z: 0,
332 },
333 aspect: wgpu::TextureAspect::All,
334 },
335 data,
336 wgpu::TexelCopyBufferLayout {
337 offset: 0,
338 bytes_per_row: Some(rect.width as u32 * bytes_per_pixel),
339 rows_per_image: Some(rect.height as u32),
340 },
341 wgpu::Extent3d {
342 width: rect.width as u32,
343 height: rect.height as u32,
344 depth_or_array_layers: 1,
345 },
346 );
347 }
348
349 self.pending_uploads.clear();
350 self.dirty = false;
351 }
352
353 pub fn texture_view(&self) -> &wgpu::TextureView {
355 self.texture.view()
356 }
357
358 pub fn texture(&self) -> &wgpu::Texture {
360 self.texture.as_wgpu()
361 }
362
363 pub fn size(&self) -> u32 {
365 self.texture.width()
366 }
367
368 pub fn format(&self) -> wgpu::TextureFormat {
370 self.texture.format()
371 }
372
373 pub fn len(&self) -> usize {
375 self.entries.len()
376 }
377
378 pub fn is_empty(&self) -> bool {
380 self.entries.is_empty()
381 }
382
383 pub fn clear(&mut self) {
385 self.entries.clear();
386 self.pending_uploads.clear();
387 let size = self.texture.width();
388 self.packer = PackerNode::new(Rect {
389 x: 0.0,
390 y: 0.0,
391 width: size as f32,
392 height: size as f32,
393 });
394 self.dirty = false;
395 }
396}
397
398#[cfg(test)]
399mod tests {
400 use super::*;
401
402 #[test]
403 fn test_atlas_key() {
404 let key1 = AtlasKey::new("test");
405 let key2 = AtlasKey::new("test");
406 let key3 = AtlasKey::new("other");
407
408 assert_eq!(key1, key2);
409 assert_ne!(key1, key3);
410 }
411
412 #[test]
413 fn test_atlas_entry_uv() {
414 let rect = Rect {
415 x: 0.0,
416 y: 0.0,
417 width: 64.0,
418 height: 64.0,
419 };
420 let entry = AtlasEntry::new(rect, 256.0);
421
422 assert_eq!(entry.uv_rect.x, 0.0);
423 assert_eq!(entry.uv_rect.y, 0.0);
424 assert_eq!(entry.uv_rect.width, 0.25);
425 assert_eq!(entry.uv_rect.height, 0.25);
426 }
427
428 #[test]
429 fn test_packer_insertion() {
430 let mut packer = PackerNode::new(Rect {
431 x: 0.0,
432 y: 0.0,
433 width: 256.0,
434 height: 256.0,
435 });
436
437 let key1 = AtlasKey::new("rect1");
438 let rect1 = packer.insert(key1, 64.0, 64.0);
439 assert!(rect1.is_some());
440
441 let key2 = AtlasKey::new("rect2");
442 let rect2 = packer.insert(key2, 32.0, 32.0);
443 assert!(rect2.is_some());
444
445 let key3 = AtlasKey::new("rect3");
447 let rect3 = packer.insert(key3, 512.0, 512.0);
448 assert!(rect3.is_none());
449 }
450
451 #[test]
452 fn test_atlas_basic() {
453 let context = GraphicsContext::new_owned_sync_or_panic();
454 let mut atlas = TextureAtlas::new(context, 256, wgpu::TextureFormat::Rgba8UnormSrgb);
455
456 assert_eq!(atlas.size(), 256);
457 assert_eq!(atlas.len(), 0);
458 assert!(atlas.is_empty());
459
460 let mut image_data = vec![0u8; 32 * 32 * 4];
462 for i in 0..(32 * 32) {
463 image_data[i * 4] = 255; image_data[i * 4 + 1] = 0; image_data[i * 4 + 2] = 0; image_data[i * 4 + 3] = 255; }
468
469 let key = AtlasKey::new("red_square");
470 let entry = atlas.insert(key, &image_data, 32, 32);
471 assert!(entry.is_some());
472 assert_eq!(atlas.len(), 1);
473
474 let retrieved = atlas.get(&key);
476 assert!(retrieved.is_some());
477
478 atlas.upload();
480 }
481
482 #[test]
483 fn test_atlas_multiple_inserts() {
484 let context = GraphicsContext::new_owned_sync_or_panic();
485 let mut atlas = TextureAtlas::new(context, 256, wgpu::TextureFormat::Rgba8UnormSrgb);
486
487 for i in 0..10 {
489 let image_data = vec![0u8; 16 * 16 * 4];
490 let key = AtlasKey::new(&format!("image_{}", i));
491 let entry = atlas.insert(key, &image_data, 16, 16);
492 assert!(entry.is_some());
493 }
494
495 assert_eq!(atlas.len(), 10);
496 atlas.upload();
497 }
498
499 #[test]
500 fn test_atlas_duplicate_key() {
501 let context = GraphicsContext::new_owned_sync_or_panic();
502 let mut atlas = TextureAtlas::new(context, 256, wgpu::TextureFormat::Rgba8UnormSrgb);
503
504 let image_data = vec![0u8; 32 * 32 * 4];
505 let key = AtlasKey::new("duplicate");
506
507 let entry1 = atlas.insert(key, &image_data, 32, 32);
508 assert!(entry1.is_some());
509
510 let entry2 = atlas.insert(key, &image_data, 32, 32);
511 assert!(entry2.is_some());
512
513 assert_eq!(atlas.len(), 1);
515 }
516
517 #[test]
518 fn test_atlas_clear() {
519 let context = GraphicsContext::new_owned_sync_or_panic();
520 let mut atlas = TextureAtlas::new(context, 256, wgpu::TextureFormat::Rgba8UnormSrgb);
521
522 let image_data = vec![0u8; 32 * 32 * 4];
523 let key = AtlasKey::new("test");
524 atlas.insert(key, &image_data, 32, 32);
525
526 assert_eq!(atlas.len(), 1);
527
528 atlas.clear();
529
530 assert_eq!(atlas.len(), 0);
531 assert!(atlas.is_empty());
532 }
533}