blade_atlas.rs

  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}