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