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