1use crate::geometry::{
2 rect::RectI,
3 vector::{vec2i, Vector2I},
4};
5use anyhow::anyhow;
6use etagere::BucketedAtlasAllocator;
7use foreign_types::ForeignType;
8use metal::{Device, TextureDescriptor};
9use objc::{msg_send, sel, sel_impl};
10
11pub struct AtlasAllocator {
12 device: Device,
13 texture_descriptor: TextureDescriptor,
14 atlases: Vec<Atlas>,
15 free_atlases: Vec<Atlas>,
16}
17
18#[derive(Copy, Clone)]
19pub struct AllocId {
20 pub atlas_id: usize,
21 alloc_id: etagere::AllocId,
22}
23
24impl AtlasAllocator {
25 pub fn new(device: Device, texture_descriptor: TextureDescriptor) -> Self {
26 let mut me = Self {
27 device,
28 texture_descriptor,
29 atlases: Vec::new(),
30 free_atlases: Vec::new(),
31 };
32 let atlas = me.new_atlas(Vector2I::zero());
33 me.atlases.push(atlas);
34 me
35 }
36
37 pub fn default_atlas_size(&self) -> Vector2I {
38 vec2i(
39 self.texture_descriptor.width() as i32,
40 self.texture_descriptor.height() as i32,
41 )
42 }
43
44 pub fn allocate(&mut self, requested_size: Vector2I) -> (AllocId, Vector2I) {
45 let (alloc_id, origin) = self
46 .atlases
47 .last_mut()
48 .unwrap()
49 .allocate(requested_size)
50 .unwrap_or_else(|| {
51 let mut atlas = self.new_atlas(requested_size);
52 let (id, origin) = atlas
53 .allocate(requested_size)
54 .ok_or_else(|| {
55 anyhow!("could not allocate requested size {:?}", requested_size)
56 })
57 .unwrap();
58 self.atlases.push(atlas);
59 (id, origin)
60 });
61
62 let id = AllocId {
63 atlas_id: self.atlases.len() - 1,
64 alloc_id,
65 };
66 (id, origin)
67 }
68
69 pub fn upload(&mut self, size: Vector2I, bytes: &[u8]) -> (AllocId, RectI) {
70 let (alloc_id, origin) = self.allocate(size);
71 let bounds = RectI::new(origin, size);
72 self.atlases[alloc_id.atlas_id].upload(bounds, bytes);
73 (alloc_id, bounds)
74 }
75
76 pub fn deallocate(&mut self, id: AllocId) {
77 if let Some(atlas) = self.atlases.get_mut(id.atlas_id) {
78 atlas.deallocate(id.alloc_id);
79 if atlas.is_empty() {
80 self.free_atlases.push(self.atlases.remove(id.atlas_id));
81 }
82 }
83 }
84
85 pub fn clear(&mut self) {
86 for atlas in &mut self.atlases {
87 atlas.clear();
88 }
89 self.free_atlases.extend(self.atlases.drain(1..));
90 }
91
92 pub fn texture(&self, atlas_id: usize) -> Option<&metal::TextureRef> {
93 self.atlases.get(atlas_id).map(|a| a.texture.as_ref())
94 }
95
96 fn new_atlas(&mut self, required_size: Vector2I) -> Atlas {
97 if let Some(i) = self.free_atlases.iter().rposition(|atlas| {
98 atlas.size().x() >= required_size.x() && atlas.size().y() >= required_size.y()
99 }) {
100 self.free_atlases.remove(i)
101 } else {
102 let size = self.default_atlas_size().max(required_size);
103 let texture = if size.x() as u64 > self.texture_descriptor.width()
104 || size.y() as u64 > self.texture_descriptor.height()
105 {
106 let descriptor = unsafe {
107 let descriptor_ptr: *mut metal::MTLTextureDescriptor =
108 msg_send![self.texture_descriptor, copy];
109 metal::TextureDescriptor::from_ptr(descriptor_ptr)
110 };
111 descriptor.set_width(size.x() as u64);
112 descriptor.set_height(size.y() as u64);
113 self.device.new_texture(&descriptor)
114 } else {
115 self.device.new_texture(&self.texture_descriptor)
116 };
117 Atlas::new(size, texture)
118 }
119 }
120}
121
122struct Atlas {
123 allocator: BucketedAtlasAllocator,
124 texture: metal::Texture,
125}
126
127impl Atlas {
128 fn new(size: Vector2I, texture: metal::Texture) -> Self {
129 Self {
130 allocator: BucketedAtlasAllocator::new(etagere::Size::new(size.x(), size.y())),
131 texture,
132 }
133 }
134
135 fn size(&self) -> Vector2I {
136 let size = self.allocator.size();
137 vec2i(size.width, size.height)
138 }
139
140 fn allocate(&mut self, size: Vector2I) -> Option<(etagere::AllocId, Vector2I)> {
141 let alloc = self
142 .allocator
143 .allocate(etagere::Size::new(size.x(), size.y()))?;
144 let origin = alloc.rectangle.min;
145 Some((alloc.id, vec2i(origin.x, origin.y)))
146 }
147
148 fn upload(&mut self, bounds: RectI, bytes: &[u8]) {
149 let region = metal::MTLRegion::new_2d(
150 bounds.origin().x() as u64,
151 bounds.origin().y() as u64,
152 bounds.size().x() as u64,
153 bounds.size().y() as u64,
154 );
155 self.texture.replace_region(
156 region,
157 0,
158 bytes.as_ptr() as *const _,
159 (bounds.size().x() * self.bytes_per_pixel() as i32) as u64,
160 );
161 }
162
163 fn bytes_per_pixel(&self) -> u8 {
164 use metal::MTLPixelFormat::*;
165 match self.texture.pixel_format() {
166 A8Unorm | R8Unorm => 1,
167 RGBA8Unorm | BGRA8Unorm => 4,
168 _ => unimplemented!(),
169 }
170 }
171
172 fn deallocate(&mut self, id: etagere::AllocId) {
173 self.allocator.deallocate(id);
174 }
175
176 fn is_empty(&self) -> bool {
177 self.allocator.is_empty()
178 }
179
180 fn clear(&mut self) {
181 self.allocator.clear();
182 }
183}