use crate::{ platform::AtlasTextureList, AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas, Point, Size, }; use anyhow::Result; use blade_graphics as gpu; use blade_util::{BufferBelt, BufferBeltDescriptor}; use collections::FxHashMap; use etagere::BucketedAtlasAllocator; use parking_lot::Mutex; use std::{borrow::Cow, ops, sync::Arc}; pub(crate) const PATH_TEXTURE_FORMAT: gpu::TextureFormat = gpu::TextureFormat::R16Float; pub(crate) struct BladeAtlas(Mutex); struct PendingUpload { id: AtlasTextureId, bounds: Bounds, data: gpu::BufferPiece, } struct BladeAtlasState { gpu: Arc, upload_belt: BufferBelt, storage: BladeAtlasStorage, tiles_by_key: FxHashMap, initializations: Vec, uploads: Vec, path_sample_count: u32, } #[cfg(gles)] unsafe impl Send for BladeAtlasState {} impl BladeAtlasState { fn destroy(&mut self) { self.storage.destroy(&self.gpu); self.upload_belt.destroy(&self.gpu); } } pub struct BladeTextureInfo { pub size: gpu::Extent, pub raw_view: gpu::TextureView, pub msaa_view: Option, } impl BladeAtlas { pub(crate) fn new(gpu: &Arc, path_sample_count: u32) -> Self { BladeAtlas(Mutex::new(BladeAtlasState { gpu: Arc::clone(gpu), upload_belt: BufferBelt::new(BufferBeltDescriptor { memory: gpu::Memory::Upload, min_chunk_size: 0x10000, alignment: 64, // Vulkan `optimalBufferCopyOffsetAlignment` on Intel XE }), storage: BladeAtlasStorage::default(), tiles_by_key: Default::default(), initializations: Vec::new(), uploads: Vec::new(), path_sample_count, })) } pub(crate) fn destroy(&self) { self.0.lock().destroy(); } pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) { let mut lock = self.0.lock(); let textures = &mut lock.storage[texture_kind]; for texture in textures.iter_mut() { texture.clear(); } } /// Allocate a rectangle and make it available for rendering immediately (without waiting for `before_frame`) pub fn allocate_for_rendering( &self, size: Size, texture_kind: AtlasTextureKind, gpu_encoder: &mut gpu::CommandEncoder, ) -> AtlasTile { let mut lock = self.0.lock(); let tile = lock.allocate(size, texture_kind); lock.flush_initializations(gpu_encoder); tile } pub fn before_frame(&self, gpu_encoder: &mut gpu::CommandEncoder) { let mut lock = self.0.lock(); lock.flush(gpu_encoder); } pub fn after_frame(&self, sync_point: &gpu::SyncPoint) { let mut lock = self.0.lock(); lock.upload_belt.flush(sync_point); } pub fn get_texture_info(&self, id: AtlasTextureId) -> BladeTextureInfo { let lock = self.0.lock(); let texture = &lock.storage[id]; let size = texture.allocator.size(); BladeTextureInfo { size: gpu::Extent { width: size.width as u32, height: size.height as u32, depth: 1, }, raw_view: texture.raw_view, msaa_view: texture.msaa_view, } } } impl PlatformAtlas for BladeAtlas { fn get_or_insert_with<'a>( &self, key: &AtlasKey, build: &mut dyn FnMut() -> Result, Cow<'a, [u8]>)>>, ) -> Result> { let mut lock = self.0.lock(); if let Some(tile) = lock.tiles_by_key.get(key) { Ok(Some(tile.clone())) } else { profiling::scope!("new tile"); let Some((size, bytes)) = build()? else { return Ok(None); }; let tile = lock.allocate(size, key.texture_kind()); lock.upload_texture(tile.texture_id, tile.bounds, &bytes); lock.tiles_by_key.insert(key.clone(), tile.clone()); Ok(Some(tile)) } } fn remove(&self, key: &AtlasKey) { let mut lock = self.0.lock(); let Some(id) = lock.tiles_by_key.remove(key).map(|tile| tile.texture_id) else { return; }; let Some(texture_slot) = lock.storage[id.kind].textures.get_mut(id.index as usize) else { return; }; if let Some(mut texture) = texture_slot.take() { texture.decrement_ref_count(); if texture.is_unreferenced() { lock.storage[id.kind] .free_list .push(texture.id.index as usize); texture.destroy(&lock.gpu); } else { *texture_slot = Some(texture); } } } } impl BladeAtlasState { fn allocate(&mut self, size: Size, texture_kind: AtlasTextureKind) -> AtlasTile { { let textures = &mut self.storage[texture_kind]; if let Some(tile) = textures .iter_mut() .rev() .find_map(|texture| texture.allocate(size)) { return tile; } } let texture = self.push_texture(size, texture_kind); texture.allocate(size).unwrap() } fn push_texture( &mut self, min_size: Size, kind: AtlasTextureKind, ) -> &mut BladeAtlasTexture { const DEFAULT_ATLAS_SIZE: Size = Size { width: DevicePixels(1024), height: DevicePixels(1024), }; let size = min_size.max(&DEFAULT_ATLAS_SIZE); let format; let usage; match kind { AtlasTextureKind::Monochrome => { format = gpu::TextureFormat::R8Unorm; usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE; } AtlasTextureKind::Polychrome => { format = gpu::TextureFormat::Bgra8UnormSrgb; usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE; } AtlasTextureKind::Path => { format = PATH_TEXTURE_FORMAT; usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE | gpu::TextureUsage::TARGET; } } // We currently only enable MSAA for path textures. let (msaa, msaa_view) = if self.path_sample_count > 1 && kind == AtlasTextureKind::Path { let msaa = self.gpu.create_texture(gpu::TextureDesc { name: "msaa path texture", format, size: gpu::Extent { width: size.width.into(), height: size.height.into(), depth: 1, }, array_layer_count: 1, mip_level_count: 1, sample_count: self.path_sample_count, dimension: gpu::TextureDimension::D2, usage: gpu::TextureUsage::TARGET, }); ( Some(msaa), Some(self.gpu.create_texture_view( msaa, gpu::TextureViewDesc { name: "msaa texture view", format, dimension: gpu::ViewDimension::D2, subresources: &Default::default(), }, )), ) } else { (None, None) }; let raw = self.gpu.create_texture(gpu::TextureDesc { name: "atlas", format, size: gpu::Extent { width: size.width.into(), height: size.height.into(), depth: 1, }, array_layer_count: 1, mip_level_count: 1, sample_count: 1, dimension: gpu::TextureDimension::D2, usage, }); let raw_view = self.gpu.create_texture_view( raw, gpu::TextureViewDesc { name: "", format, dimension: gpu::ViewDimension::D2, subresources: &Default::default(), }, ); let texture_list = &mut self.storage[kind]; let index = texture_list.free_list.pop(); let atlas_texture = BladeAtlasTexture { id: AtlasTextureId { index: index.unwrap_or(texture_list.textures.len()) as u32, kind, }, allocator: etagere::BucketedAtlasAllocator::new(size.into()), format, raw, raw_view, msaa, msaa_view, live_atlas_keys: 0, }; self.initializations.push(atlas_texture.id); if let Some(ix) = index { texture_list.textures[ix] = Some(atlas_texture); texture_list.textures.get_mut(ix).unwrap().as_mut().unwrap() } else { texture_list.textures.push(Some(atlas_texture)); texture_list.textures.last_mut().unwrap().as_mut().unwrap() } } fn upload_texture(&mut self, id: AtlasTextureId, bounds: Bounds, bytes: &[u8]) { let data = self.upload_belt.alloc_bytes(bytes, &self.gpu); self.uploads.push(PendingUpload { id, bounds, data }); } fn flush_initializations(&mut self, encoder: &mut gpu::CommandEncoder) { for id in self.initializations.drain(..) { let texture = &self.storage[id]; encoder.init_texture(texture.raw); } } fn flush(&mut self, encoder: &mut gpu::CommandEncoder) { self.flush_initializations(encoder); let mut transfers = encoder.transfer("atlas"); for upload in self.uploads.drain(..) { let texture = &self.storage[upload.id]; transfers.copy_buffer_to_texture( upload.data, upload.bounds.size.width.to_bytes(texture.bytes_per_pixel()), gpu::TexturePiece { texture: texture.raw, mip_level: 0, array_layer: 0, origin: [ upload.bounds.origin.x.into(), upload.bounds.origin.y.into(), 0, ], }, gpu::Extent { width: upload.bounds.size.width.into(), height: upload.bounds.size.height.into(), depth: 1, }, ); } } } #[derive(Default)] struct BladeAtlasStorage { monochrome_textures: AtlasTextureList, polychrome_textures: AtlasTextureList, path_textures: AtlasTextureList, } impl ops::Index for BladeAtlasStorage { type Output = AtlasTextureList; fn index(&self, kind: AtlasTextureKind) -> &Self::Output { match kind { crate::AtlasTextureKind::Monochrome => &self.monochrome_textures, crate::AtlasTextureKind::Polychrome => &self.polychrome_textures, crate::AtlasTextureKind::Path => &self.path_textures, } } } impl ops::IndexMut for BladeAtlasStorage { fn index_mut(&mut self, kind: AtlasTextureKind) -> &mut Self::Output { match kind { crate::AtlasTextureKind::Monochrome => &mut self.monochrome_textures, crate::AtlasTextureKind::Polychrome => &mut self.polychrome_textures, crate::AtlasTextureKind::Path => &mut self.path_textures, } } } impl ops::Index for BladeAtlasStorage { type Output = BladeAtlasTexture; fn index(&self, id: AtlasTextureId) -> &Self::Output { let textures = match id.kind { crate::AtlasTextureKind::Monochrome => &self.monochrome_textures, crate::AtlasTextureKind::Polychrome => &self.polychrome_textures, crate::AtlasTextureKind::Path => &self.path_textures, }; textures[id.index as usize].as_ref().unwrap() } } impl BladeAtlasStorage { fn destroy(&mut self, gpu: &gpu::Context) { for mut texture in self.monochrome_textures.drain().flatten() { texture.destroy(gpu); } for mut texture in self.polychrome_textures.drain().flatten() { texture.destroy(gpu); } for mut texture in self.path_textures.drain().flatten() { texture.destroy(gpu); } } } struct BladeAtlasTexture { id: AtlasTextureId, allocator: BucketedAtlasAllocator, raw: gpu::Texture, raw_view: gpu::TextureView, msaa: Option, msaa_view: Option, format: gpu::TextureFormat, live_atlas_keys: u32, } impl BladeAtlasTexture { fn clear(&mut self) { self.allocator.clear(); } fn allocate(&mut self, size: Size) -> Option { let allocation = self.allocator.allocate(size.into())?; let tile = AtlasTile { texture_id: self.id, tile_id: allocation.id.into(), padding: 0, bounds: Bounds { origin: allocation.rectangle.min.into(), size, }, }; self.live_atlas_keys += 1; Some(tile) } fn destroy(&mut self, gpu: &gpu::Context) { gpu.destroy_texture(self.raw); gpu.destroy_texture_view(self.raw_view); if let Some(msaa) = self.msaa { gpu.destroy_texture(msaa); } if let Some(msaa_view) = self.msaa_view { gpu.destroy_texture_view(msaa_view); } } fn bytes_per_pixel(&self) -> u8 { self.format.block_info().size } fn decrement_ref_count(&mut self) { self.live_atlas_keys -= 1; } fn is_unreferenced(&mut self) -> bool { self.live_atlas_keys == 0 } } impl From> for etagere::Size { fn from(size: Size) -> Self { etagere::Size::new(size.width.into(), size.height.into()) } } impl From for Point { fn from(value: etagere::Point) -> Self { Point { x: DevicePixels::from(value.x), y: DevicePixels::from(value.y), } } } impl From for Size { fn from(size: etagere::Size) -> Self { Size { width: DevicePixels::from(size.width), height: DevicePixels::from(size.height), } } } impl From for Bounds { fn from(rectangle: etagere::Rectangle) -> Self { Bounds { origin: rectangle.min.into(), size: rectangle.size().into(), } } }