1use crate::{
2 AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
3 Point, Size, platform::AtlasTextureList,
4};
5use anyhow::Result;
6use blade_graphics as gpu;
7use blade_util::{BufferBelt, BufferBeltDescriptor};
8use collections::FxHashMap;
9use etagere::BucketedAtlasAllocator;
10use parking_lot::Mutex;
11use std::{borrow::Cow, ops, sync::Arc};
12
13pub(crate) struct BladeAtlas(Mutex<BladeAtlasState>);
14
15struct PendingUpload {
16 id: AtlasTextureId,
17 bounds: Bounds<DevicePixels>,
18 data: gpu::BufferPiece,
19}
20
21struct BladeAtlasState {
22 gpu: Arc<gpu::Context>,
23 upload_belt: BufferBelt,
24 storage: BladeAtlasStorage,
25 tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
26 initializations: Vec<AtlasTextureId>,
27 uploads: Vec<PendingUpload>,
28}
29
30#[cfg(gles)]
31unsafe impl Send for BladeAtlasState {}
32
33impl BladeAtlasState {
34 fn destroy(&mut self) {
35 self.storage.destroy(&self.gpu);
36 self.upload_belt.destroy(&self.gpu);
37 }
38}
39
40pub struct BladeTextureInfo {
41 #[allow(dead_code)]
42 pub size: gpu::Extent,
43 pub raw_view: gpu::TextureView,
44}
45
46impl BladeAtlas {
47 pub(crate) fn new(gpu: &Arc<gpu::Context>) -> Self {
48 BladeAtlas(Mutex::new(BladeAtlasState {
49 gpu: Arc::clone(gpu),
50 upload_belt: BufferBelt::new(BufferBeltDescriptor {
51 memory: gpu::Memory::Upload,
52 min_chunk_size: 0x10000,
53 alignment: 64, // Vulkan `optimalBufferCopyOffsetAlignment` on Intel XE
54 }),
55 storage: BladeAtlasStorage::default(),
56 tiles_by_key: Default::default(),
57 initializations: Vec::new(),
58 uploads: Vec::new(),
59 }))
60 }
61
62 pub(crate) fn destroy(&self) {
63 self.0.lock().destroy();
64 }
65
66 #[allow(dead_code)]
67 pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) {
68 let mut lock = self.0.lock();
69 let textures = &mut lock.storage[texture_kind];
70 for texture in textures.iter_mut() {
71 texture.clear();
72 }
73 }
74
75 pub fn before_frame(&self, gpu_encoder: &mut gpu::CommandEncoder) {
76 let mut lock = self.0.lock();
77 lock.flush(gpu_encoder);
78 }
79
80 pub fn after_frame(&self, sync_point: &gpu::SyncPoint) {
81 let mut lock = self.0.lock();
82 lock.upload_belt.flush(sync_point);
83 }
84
85 pub fn get_texture_info(&self, id: AtlasTextureId) -> BladeTextureInfo {
86 let lock = self.0.lock();
87 let texture = &lock.storage[id];
88 let size = texture.allocator.size();
89 BladeTextureInfo {
90 size: gpu::Extent {
91 width: size.width as u32,
92 height: size.height as u32,
93 depth: 1,
94 },
95 raw_view: texture.raw_view,
96 }
97 }
98}
99
100impl PlatformAtlas for BladeAtlas {
101 fn get_or_insert_with<'a>(
102 &self,
103 key: &AtlasKey,
104 build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
105 ) -> Result<Option<AtlasTile>> {
106 let mut lock = self.0.lock();
107 if let Some(tile) = lock.tiles_by_key.get(key) {
108 Ok(Some(tile.clone()))
109 } else {
110 profiling::scope!("new tile");
111 let Some((size, bytes)) = build()? else {
112 return Ok(None);
113 };
114 let tile = lock.allocate(size, key.texture_kind());
115 lock.upload_texture(tile.texture_id, tile.bounds, &bytes);
116 lock.tiles_by_key.insert(key.clone(), tile.clone());
117 Ok(Some(tile))
118 }
119 }
120
121 fn remove(&self, key: &AtlasKey) {
122 let mut lock = self.0.lock();
123
124 let Some(id) = lock.tiles_by_key.remove(key).map(|tile| tile.texture_id) else {
125 return;
126 };
127
128 let Some(texture_slot) = lock.storage[id.kind].textures.get_mut(id.index as usize) else {
129 return;
130 };
131
132 if let Some(mut texture) = texture_slot.take() {
133 texture.decrement_ref_count();
134 if texture.is_unreferenced() {
135 lock.storage[id.kind]
136 .free_list
137 .push(texture.id.index as usize);
138 texture.destroy(&lock.gpu);
139 } else {
140 *texture_slot = Some(texture);
141 }
142 }
143 }
144}
145
146impl BladeAtlasState {
147 fn allocate(&mut self, size: Size<DevicePixels>, texture_kind: AtlasTextureKind) -> AtlasTile {
148 {
149 let textures = &mut self.storage[texture_kind];
150
151 if let Some(tile) = textures
152 .iter_mut()
153 .rev()
154 .find_map(|texture| texture.allocate(size))
155 {
156 return tile;
157 }
158 }
159
160 let texture = self.push_texture(size, texture_kind);
161 texture.allocate(size).unwrap()
162 }
163
164 fn push_texture(
165 &mut self,
166 min_size: Size<DevicePixels>,
167 kind: AtlasTextureKind,
168 ) -> &mut BladeAtlasTexture {
169 const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {
170 width: DevicePixels(1024),
171 height: DevicePixels(1024),
172 };
173
174 let size = min_size.max(&DEFAULT_ATLAS_SIZE);
175 let format;
176 let usage;
177 match kind {
178 AtlasTextureKind::Monochrome => {
179 format = gpu::TextureFormat::R8Unorm;
180 usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE;
181 }
182 AtlasTextureKind::Polychrome => {
183 format = gpu::TextureFormat::Bgra8UnormSrgb;
184 usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE;
185 }
186 }
187
188 let raw = self.gpu.create_texture(gpu::TextureDesc {
189 name: "atlas",
190 format,
191 size: gpu::Extent {
192 width: size.width.into(),
193 height: size.height.into(),
194 depth: 1,
195 },
196 array_layer_count: 1,
197 mip_level_count: 1,
198 sample_count: 1,
199 dimension: gpu::TextureDimension::D2,
200 usage,
201 external: None,
202 });
203 let raw_view = self.gpu.create_texture_view(
204 raw,
205 gpu::TextureViewDesc {
206 name: "",
207 format,
208 dimension: gpu::ViewDimension::D2,
209 subresources: &Default::default(),
210 },
211 );
212
213 let texture_list = &mut self.storage[kind];
214 let index = texture_list.free_list.pop();
215
216 let atlas_texture = BladeAtlasTexture {
217 id: AtlasTextureId {
218 index: index.unwrap_or(texture_list.textures.len()) as u32,
219 kind,
220 },
221 allocator: etagere::BucketedAtlasAllocator::new(size.into()),
222 format,
223 raw,
224 raw_view,
225 live_atlas_keys: 0,
226 };
227
228 self.initializations.push(atlas_texture.id);
229
230 if let Some(ix) = index {
231 texture_list.textures[ix] = Some(atlas_texture);
232 texture_list.textures.get_mut(ix).unwrap().as_mut().unwrap()
233 } else {
234 texture_list.textures.push(Some(atlas_texture));
235 texture_list.textures.last_mut().unwrap().as_mut().unwrap()
236 }
237 }
238
239 fn upload_texture(&mut self, id: AtlasTextureId, bounds: Bounds<DevicePixels>, bytes: &[u8]) {
240 let data = self.upload_belt.alloc_bytes(bytes, &self.gpu);
241 self.uploads.push(PendingUpload { id, bounds, data });
242 }
243
244 fn flush_initializations(&mut self, encoder: &mut gpu::CommandEncoder) {
245 for id in self.initializations.drain(..) {
246 let texture = &self.storage[id];
247 encoder.init_texture(texture.raw);
248 }
249 }
250
251 fn flush(&mut self, encoder: &mut gpu::CommandEncoder) {
252 self.flush_initializations(encoder);
253
254 let mut transfers = encoder.transfer("atlas");
255 for upload in self.uploads.drain(..) {
256 let texture = &self.storage[upload.id];
257 transfers.copy_buffer_to_texture(
258 upload.data,
259 upload.bounds.size.width.to_bytes(texture.bytes_per_pixel()),
260 gpu::TexturePiece {
261 texture: texture.raw,
262 mip_level: 0,
263 array_layer: 0,
264 origin: [
265 upload.bounds.origin.x.into(),
266 upload.bounds.origin.y.into(),
267 0,
268 ],
269 },
270 gpu::Extent {
271 width: upload.bounds.size.width.into(),
272 height: upload.bounds.size.height.into(),
273 depth: 1,
274 },
275 );
276 }
277 }
278}
279
280#[derive(Default)]
281struct BladeAtlasStorage {
282 monochrome_textures: AtlasTextureList<BladeAtlasTexture>,
283 polychrome_textures: AtlasTextureList<BladeAtlasTexture>,
284}
285
286impl ops::Index<AtlasTextureKind> for BladeAtlasStorage {
287 type Output = AtlasTextureList<BladeAtlasTexture>;
288 fn index(&self, kind: AtlasTextureKind) -> &Self::Output {
289 match kind {
290 crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
291 crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
292 }
293 }
294}
295
296impl ops::IndexMut<AtlasTextureKind> for BladeAtlasStorage {
297 fn index_mut(&mut self, kind: AtlasTextureKind) -> &mut Self::Output {
298 match kind {
299 crate::AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
300 crate::AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
301 }
302 }
303}
304
305impl ops::Index<AtlasTextureId> for BladeAtlasStorage {
306 type Output = BladeAtlasTexture;
307 fn index(&self, id: AtlasTextureId) -> &Self::Output {
308 let textures = match id.kind {
309 crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
310 crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
311 };
312 textures[id.index as usize].as_ref().unwrap()
313 }
314}
315
316impl BladeAtlasStorage {
317 fn destroy(&mut self, gpu: &gpu::Context) {
318 for mut texture in self.monochrome_textures.drain().flatten() {
319 texture.destroy(gpu);
320 }
321 for mut texture in self.polychrome_textures.drain().flatten() {
322 texture.destroy(gpu);
323 }
324 }
325}
326
327struct BladeAtlasTexture {
328 id: AtlasTextureId,
329 allocator: BucketedAtlasAllocator,
330 raw: gpu::Texture,
331 raw_view: gpu::TextureView,
332 format: gpu::TextureFormat,
333 live_atlas_keys: u32,
334}
335
336impl BladeAtlasTexture {
337 fn clear(&mut self) {
338 self.allocator.clear();
339 }
340
341 fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
342 let allocation = self.allocator.allocate(size.into())?;
343 let tile = AtlasTile {
344 texture_id: self.id,
345 tile_id: allocation.id.into(),
346 padding: 0,
347 bounds: Bounds {
348 origin: allocation.rectangle.min.into(),
349 size,
350 },
351 };
352 self.live_atlas_keys += 1;
353 Some(tile)
354 }
355
356 fn destroy(&mut self, gpu: &gpu::Context) {
357 gpu.destroy_texture(self.raw);
358 gpu.destroy_texture_view(self.raw_view);
359 }
360
361 fn bytes_per_pixel(&self) -> u8 {
362 self.format.block_info().size
363 }
364
365 fn decrement_ref_count(&mut self) {
366 self.live_atlas_keys -= 1;
367 }
368
369 fn is_unreferenced(&mut self) -> bool {
370 self.live_atlas_keys == 0
371 }
372}
373
374impl From<Size<DevicePixels>> for etagere::Size {
375 fn from(size: Size<DevicePixels>) -> Self {
376 etagere::Size::new(size.width.into(), size.height.into())
377 }
378}
379
380impl From<etagere::Point> for Point<DevicePixels> {
381 fn from(value: etagere::Point) -> Self {
382 Point {
383 x: DevicePixels::from(value.x),
384 y: DevicePixels::from(value.y),
385 }
386 }
387}
388
389impl From<etagere::Size> for Size<DevicePixels> {
390 fn from(size: etagere::Size) -> Self {
391 Size {
392 width: DevicePixels::from(size.width),
393 height: DevicePixels::from(size.height),
394 }
395 }
396}
397
398impl From<etagere::Rectangle> for Bounds<DevicePixels> {
399 fn from(rectangle: etagere::Rectangle) -> Self {
400 Bounds {
401 origin: rectangle.min.into(),
402 size: rectangle.size().into(),
403 }
404 }
405}