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) 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}