blade_atlas.rs

  1use super::{BladeBelt, BladeBeltDescriptor};
  2use crate::{
  3    AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
  4    Point, Size,
  5};
  6use anyhow::Result;
  7use blade_graphics as gpu;
  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: BladeBelt,
 26    storage: BladeAtlasStorage,
 27    tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
 28    initializations: Vec<AtlasTextureId>,
 29    uploads: Vec<PendingUpload>,
 30}
 31
 32impl BladeAtlasState {
 33    fn destroy(&mut self) {
 34        self.storage.destroy(&self.gpu);
 35        self.upload_belt.destroy(&self.gpu);
 36    }
 37}
 38
 39pub struct BladeTextureInfo {
 40    pub size: gpu::Extent,
 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: BladeBelt::new(BladeBeltDescriptor {
 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(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) {
 65        let mut lock = self.0.lock();
 66        let textures = &mut lock.storage[texture_kind];
 67        for texture in textures {
 68            texture.clear();
 69        }
 70    }
 71
 72    pub fn allocate(&self, size: Size<DevicePixels>, texture_kind: AtlasTextureKind) -> AtlasTile {
 73        let mut lock = self.0.lock();
 74        lock.allocate(size, texture_kind)
 75    }
 76
 77    pub fn before_frame(&self, gpu_encoder: &mut gpu::CommandEncoder) {
 78        let mut lock = self.0.lock();
 79        lock.flush(gpu_encoder);
 80    }
 81
 82    pub fn after_frame(&self, sync_point: &gpu::SyncPoint) {
 83        let mut lock = self.0.lock();
 84        lock.upload_belt.flush(sync_point);
 85    }
 86
 87    pub fn get_texture_info(&self, id: AtlasTextureId) -> BladeTextureInfo {
 88        let lock = self.0.lock();
 89        let texture = &lock.storage[id];
 90        let size = texture.allocator.size();
 91        BladeTextureInfo {
 92            size: gpu::Extent {
 93                width: size.width as u32,
 94                height: size.height as u32,
 95                depth: 1,
 96            },
 97            raw_view: texture.raw_view,
 98        }
 99    }
100}
101
102impl PlatformAtlas for BladeAtlas {
103    fn get_or_insert_with<'a>(
104        &self,
105        key: &AtlasKey,
106        build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
107    ) -> Result<AtlasTile> {
108        let mut lock = self.0.lock();
109        if let Some(tile) = lock.tiles_by_key.get(key) {
110            Ok(tile.clone())
111        } else {
112            let (size, bytes) = build()?;
113            let tile = lock.allocate(size, key.texture_kind());
114            lock.upload_texture(tile.texture_id, tile.bounds, &bytes);
115            lock.tiles_by_key.insert(key.clone(), tile.clone());
116            Ok(tile)
117        }
118    }
119}
120
121impl BladeAtlasState {
122    fn allocate(&mut self, size: Size<DevicePixels>, texture_kind: AtlasTextureKind) -> AtlasTile {
123        let textures = &mut self.storage[texture_kind];
124        textures
125            .iter_mut()
126            .rev()
127            .find_map(|texture| texture.allocate(size))
128            .unwrap_or_else(|| {
129                let texture = self.push_texture(size, texture_kind);
130                texture.allocate(size).unwrap()
131            })
132    }
133
134    fn push_texture(
135        &mut self,
136        min_size: Size<DevicePixels>,
137        kind: AtlasTextureKind,
138    ) -> &mut BladeAtlasTexture {
139        const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {
140            width: DevicePixels(1024),
141            height: DevicePixels(1024),
142        };
143
144        let size = min_size.max(&DEFAULT_ATLAS_SIZE);
145        let format;
146        let usage;
147        match kind {
148            AtlasTextureKind::Monochrome => {
149                format = gpu::TextureFormat::R8Unorm;
150                usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE;
151            }
152            AtlasTextureKind::Polychrome => {
153                format = gpu::TextureFormat::Bgra8Unorm;
154                usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE;
155            }
156            AtlasTextureKind::Path => {
157                format = PATH_TEXTURE_FORMAT;
158                usage = gpu::TextureUsage::COPY
159                    | gpu::TextureUsage::RESOURCE
160                    | gpu::TextureUsage::TARGET;
161            }
162        }
163
164        let raw = self.gpu.create_texture(gpu::TextureDesc {
165            name: "atlas",
166            format,
167            size: gpu::Extent {
168                width: size.width.into(),
169                height: size.height.into(),
170                depth: 1,
171            },
172            array_layer_count: 1,
173            mip_level_count: 1,
174            dimension: gpu::TextureDimension::D2,
175            usage,
176        });
177        let raw_view = self.gpu.create_texture_view(gpu::TextureViewDesc {
178            name: "",
179            texture: raw,
180            format,
181            dimension: gpu::ViewDimension::D2,
182            subresources: &Default::default(),
183        });
184
185        let textures = &mut self.storage[kind];
186        let atlas_texture = BladeAtlasTexture {
187            id: AtlasTextureId {
188                index: textures.len() as u32,
189                kind,
190            },
191            allocator: etagere::BucketedAtlasAllocator::new(size.into()),
192            format,
193            raw,
194            raw_view,
195        };
196
197        self.initializations.push(atlas_texture.id);
198        textures.push(atlas_texture);
199        textures.last_mut().unwrap()
200    }
201
202    fn upload_texture(&mut self, id: AtlasTextureId, bounds: Bounds<DevicePixels>, bytes: &[u8]) {
203        let data = self.upload_belt.alloc_data(bytes, &self.gpu);
204        self.uploads.push(PendingUpload { id, bounds, data });
205    }
206
207    fn flush(&mut self, encoder: &mut gpu::CommandEncoder) {
208        for id in self.initializations.drain(..) {
209            let texture = &self.storage[id];
210            encoder.init_texture(texture.raw);
211        }
212
213        let mut transfers = encoder.transfer();
214        for upload in self.uploads.drain(..) {
215            let texture = &self.storage[upload.id];
216            transfers.copy_buffer_to_texture(
217                upload.data,
218                upload.bounds.size.width.to_bytes(texture.bytes_per_pixel()),
219                gpu::TexturePiece {
220                    texture: texture.raw,
221                    mip_level: 0,
222                    array_layer: 0,
223                    origin: [
224                        upload.bounds.origin.x.into(),
225                        upload.bounds.origin.y.into(),
226                        0,
227                    ],
228                },
229                gpu::Extent {
230                    width: upload.bounds.size.width.into(),
231                    height: upload.bounds.size.height.into(),
232                    depth: 1,
233                },
234            );
235        }
236    }
237}
238
239#[derive(Default)]
240struct BladeAtlasStorage {
241    monochrome_textures: Vec<BladeAtlasTexture>,
242    polychrome_textures: Vec<BladeAtlasTexture>,
243    path_textures: Vec<BladeAtlasTexture>,
244}
245
246impl ops::Index<AtlasTextureKind> for BladeAtlasStorage {
247    type Output = Vec<BladeAtlasTexture>;
248    fn index(&self, kind: AtlasTextureKind) -> &Self::Output {
249        match kind {
250            crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
251            crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
252            crate::AtlasTextureKind::Path => &self.path_textures,
253        }
254    }
255}
256
257impl ops::IndexMut<AtlasTextureKind> for BladeAtlasStorage {
258    fn index_mut(&mut self, kind: AtlasTextureKind) -> &mut Self::Output {
259        match kind {
260            crate::AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
261            crate::AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
262            crate::AtlasTextureKind::Path => &mut self.path_textures,
263        }
264    }
265}
266
267impl ops::Index<AtlasTextureId> for BladeAtlasStorage {
268    type Output = BladeAtlasTexture;
269    fn index(&self, id: AtlasTextureId) -> &Self::Output {
270        let textures = match id.kind {
271            crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
272            crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
273            crate::AtlasTextureKind::Path => &self.path_textures,
274        };
275        &textures[id.index as usize]
276    }
277}
278
279impl BladeAtlasStorage {
280    fn destroy(&mut self, gpu: &gpu::Context) {
281        for mut texture in self.monochrome_textures.drain(..) {
282            texture.destroy(gpu);
283        }
284        for mut texture in self.polychrome_textures.drain(..) {
285            texture.destroy(gpu);
286        }
287        for mut texture in self.path_textures.drain(..) {
288            texture.destroy(gpu);
289        }
290    }
291}
292
293struct BladeAtlasTexture {
294    id: AtlasTextureId,
295    allocator: BucketedAtlasAllocator,
296    raw: gpu::Texture,
297    raw_view: gpu::TextureView,
298    format: gpu::TextureFormat,
299}
300
301impl BladeAtlasTexture {
302    fn clear(&mut self) {
303        self.allocator.clear();
304    }
305
306    fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
307        let allocation = self.allocator.allocate(size.into())?;
308        let tile = AtlasTile {
309            texture_id: self.id,
310            tile_id: allocation.id.into(),
311            padding: 0,
312            bounds: Bounds {
313                origin: allocation.rectangle.min.into(),
314                size,
315            },
316        };
317        Some(tile)
318    }
319
320    fn destroy(&mut self, gpu: &gpu::Context) {
321        gpu.destroy_texture(self.raw);
322        gpu.destroy_texture_view(self.raw_view);
323    }
324
325    fn bytes_per_pixel(&self) -> u8 {
326        self.format.block_info().size
327    }
328}
329
330impl From<Size<DevicePixels>> for etagere::Size {
331    fn from(size: Size<DevicePixels>) -> Self {
332        etagere::Size::new(size.width.into(), size.height.into())
333    }
334}
335
336impl From<etagere::Point> for Point<DevicePixels> {
337    fn from(value: etagere::Point) -> Self {
338        Point {
339            x: DevicePixels::from(value.x),
340            y: DevicePixels::from(value.y),
341        }
342    }
343}
344
345impl From<etagere::Size> for Size<DevicePixels> {
346    fn from(size: etagere::Size) -> Self {
347        Size {
348            width: DevicePixels::from(size.width),
349            height: DevicePixels::from(size.height),
350        }
351    }
352}
353
354impl From<etagere::Rectangle> for Bounds<DevicePixels> {
355    fn from(rectangle: etagere::Rectangle) -> Self {
356        Bounds {
357            origin: rectangle.min.into(),
358            size: rectangle.size().into(),
359        }
360    }
361}