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    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::Polychrome => {
166                format = gpu::TextureFormat::Bgra8Unorm;
167                usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE;
168            }
169        }
170
171        let raw = self.gpu.create_texture(gpu::TextureDesc {
172            name: "atlas",
173            format,
174            size: gpu::Extent {
175                width: size.width.into(),
176                height: size.height.into(),
177                depth: 1,
178            },
179            array_layer_count: 1,
180            mip_level_count: 1,
181            sample_count: 1,
182            dimension: gpu::TextureDimension::D2,
183            usage,
184            external: None,
185        });
186        let raw_view = self.gpu.create_texture_view(
187            raw,
188            gpu::TextureViewDesc {
189                name: "",
190                format,
191                dimension: gpu::ViewDimension::D2,
192                subresources: &Default::default(),
193            },
194        );
195
196        let texture_list = &mut self.storage[kind];
197        let index = texture_list.free_list.pop();
198
199        let atlas_texture = BladeAtlasTexture {
200            id: AtlasTextureId {
201                index: index.unwrap_or(texture_list.textures.len()) as u32,
202                kind,
203            },
204            allocator: etagere::BucketedAtlasAllocator::new(size.into()),
205            format,
206            raw,
207            raw_view,
208            live_atlas_keys: 0,
209        };
210
211        self.initializations.push(atlas_texture.id);
212
213        if let Some(ix) = index {
214            texture_list.textures[ix] = Some(atlas_texture);
215            texture_list.textures.get_mut(ix).unwrap().as_mut().unwrap()
216        } else {
217            texture_list.textures.push(Some(atlas_texture));
218            texture_list.textures.last_mut().unwrap().as_mut().unwrap()
219        }
220    }
221
222    fn upload_texture(&mut self, id: AtlasTextureId, bounds: Bounds<DevicePixels>, bytes: &[u8]) {
223        let data = self.upload_belt.alloc_bytes(bytes, &self.gpu);
224        self.uploads.push(PendingUpload { id, bounds, data });
225    }
226
227    fn flush_initializations(&mut self, encoder: &mut gpu::CommandEncoder) {
228        for id in self.initializations.drain(..) {
229            let texture = &self.storage[id];
230            encoder.init_texture(texture.raw);
231        }
232    }
233
234    fn flush(&mut self, encoder: &mut gpu::CommandEncoder) {
235        self.flush_initializations(encoder);
236
237        let mut transfers = encoder.transfer("atlas");
238        for upload in self.uploads.drain(..) {
239            let texture = &self.storage[upload.id];
240            transfers.copy_buffer_to_texture(
241                upload.data,
242                upload.bounds.size.width.to_bytes(texture.bytes_per_pixel()),
243                gpu::TexturePiece {
244                    texture: texture.raw,
245                    mip_level: 0,
246                    array_layer: 0,
247                    origin: [
248                        upload.bounds.origin.x.into(),
249                        upload.bounds.origin.y.into(),
250                        0,
251                    ],
252                },
253                gpu::Extent {
254                    width: upload.bounds.size.width.into(),
255                    height: upload.bounds.size.height.into(),
256                    depth: 1,
257                },
258            );
259        }
260    }
261}
262
263#[derive(Default)]
264struct BladeAtlasStorage {
265    monochrome_textures: AtlasTextureList<BladeAtlasTexture>,
266    polychrome_textures: AtlasTextureList<BladeAtlasTexture>,
267}
268
269impl ops::Index<AtlasTextureKind> for BladeAtlasStorage {
270    type Output = AtlasTextureList<BladeAtlasTexture>;
271    fn index(&self, kind: AtlasTextureKind) -> &Self::Output {
272        match kind {
273            crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
274            crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
275        }
276    }
277}
278
279impl ops::IndexMut<AtlasTextureKind> for BladeAtlasStorage {
280    fn index_mut(&mut self, kind: AtlasTextureKind) -> &mut Self::Output {
281        match kind {
282            crate::AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
283            crate::AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
284        }
285    }
286}
287
288impl ops::Index<AtlasTextureId> for BladeAtlasStorage {
289    type Output = BladeAtlasTexture;
290    fn index(&self, id: AtlasTextureId) -> &Self::Output {
291        let textures = match id.kind {
292            crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
293            crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
294        };
295        textures[id.index as usize].as_ref().unwrap()
296    }
297}
298
299impl BladeAtlasStorage {
300    fn destroy(&mut self, gpu: &gpu::Context) {
301        for mut texture in self.monochrome_textures.drain().flatten() {
302            texture.destroy(gpu);
303        }
304        for mut texture in self.polychrome_textures.drain().flatten() {
305            texture.destroy(gpu);
306        }
307    }
308}
309
310struct BladeAtlasTexture {
311    id: AtlasTextureId,
312    allocator: BucketedAtlasAllocator,
313    raw: gpu::Texture,
314    raw_view: gpu::TextureView,
315    format: gpu::TextureFormat,
316    live_atlas_keys: u32,
317}
318
319impl BladeAtlasTexture {
320    fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
321        let allocation = self.allocator.allocate(size.into())?;
322        let tile = AtlasTile {
323            texture_id: self.id,
324            tile_id: allocation.id.into(),
325            padding: 0,
326            bounds: Bounds {
327                origin: allocation.rectangle.min.into(),
328                size,
329            },
330        };
331        self.live_atlas_keys += 1;
332        Some(tile)
333    }
334
335    fn destroy(&mut self, gpu: &gpu::Context) {
336        gpu.destroy_texture(self.raw);
337        gpu.destroy_texture_view(self.raw_view);
338    }
339
340    fn bytes_per_pixel(&self) -> u8 {
341        self.format.block_info().size
342    }
343
344    fn decrement_ref_count(&mut self) {
345        self.live_atlas_keys -= 1;
346    }
347
348    fn is_unreferenced(&mut self) -> bool {
349        self.live_atlas_keys == 0
350    }
351}
352
353impl From<Size<DevicePixels>> for etagere::Size {
354    fn from(size: Size<DevicePixels>) -> Self {
355        etagere::Size::new(size.width.into(), size.height.into())
356    }
357}
358
359impl From<etagere::Point> for Point<DevicePixels> {
360    fn from(value: etagere::Point) -> Self {
361        Point {
362            x: DevicePixels::from(value.x),
363            y: DevicePixels::from(value.y),
364        }
365    }
366}
367
368impl From<etagere::Size> for Size<DevicePixels> {
369    fn from(size: etagere::Size) -> Self {
370        Size {
371            width: DevicePixels::from(size.width),
372            height: DevicePixels::from(size.height),
373        }
374    }
375}
376
377impl From<etagere::Rectangle> for Bounds<DevicePixels> {
378    fn from(rectangle: etagere::Rectangle) -> Self {
379        Bounds {
380            origin: rectangle.min.into(),
381            size: rectangle.size().into(),
382        }
383    }
384}