use collections::FxHashMap; use etagere::BucketedAtlasAllocator; use parking_lot::Mutex; use windows::Win32::Graphics::{ Direct3D11::{ D3D11_BIND_SHADER_RESOURCE, D3D11_BOX, D3D11_CPU_ACCESS_WRITE, D3D11_TEXTURE2D_DESC, D3D11_USAGE_DEFAULT, ID3D11Device, ID3D11DeviceContext, ID3D11ShaderResourceView, ID3D11Texture2D, }, Dxgi::Common::{DXGI_FORMAT_A8_UNORM, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_SAMPLE_DESC}, }; use crate::{ AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas, Size, platform::AtlasTextureList, }; pub(crate) struct DirectXAtlas(Mutex); struct DirectXAtlasState { device: ID3D11Device, device_context: ID3D11DeviceContext, monochrome_textures: AtlasTextureList, polychrome_textures: AtlasTextureList, tiles_by_key: FxHashMap, } struct DirectXAtlasTexture { id: AtlasTextureId, bytes_per_pixel: u32, allocator: BucketedAtlasAllocator, texture: ID3D11Texture2D, view: [Option; 1], live_atlas_keys: u32, } impl DirectXAtlas { pub(crate) fn new(device: ID3D11Device, device_context: ID3D11DeviceContext) -> Self { DirectXAtlas(Mutex::new(DirectXAtlasState { device, device_context, monochrome_textures: Default::default(), polychrome_textures: Default::default(), tiles_by_key: Default::default(), })) } pub(crate) fn get_texture_view( &self, id: AtlasTextureId, ) -> [Option; 1] { let lock = self.0.lock(); let tex = lock.texture(id); tex.view.clone() } pub(crate) fn allocate( &self, size: Size, texture_kind: AtlasTextureKind, ) -> Option { self.0.lock().allocate(size, texture_kind) } pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) { let mut lock = self.0.lock(); let textures = match texture_kind { AtlasTextureKind::Monochrome => &mut lock.monochrome_textures, AtlasTextureKind::Polychrome => &mut lock.polychrome_textures, }; for texture in textures.iter_mut() { texture.clear(); } } } impl PlatformAtlas for DirectXAtlas { fn get_or_insert_with<'a>( &self, key: &AtlasKey, build: &mut dyn FnMut() -> anyhow::Result< Option<(Size, std::borrow::Cow<'a, [u8]>)>, >, ) -> anyhow::Result> { let mut lock = self.0.lock(); if let Some(tile) = lock.tiles_by_key.get(key) { Ok(Some(tile.clone())) } else { let Some((size, bytes)) = build()? else { return Ok(None); }; let tile = lock .allocate(size, key.texture_kind()) .ok_or_else(|| anyhow::anyhow!("failed to allocate"))?; let texture = lock.texture(tile.texture_id); texture.upload(&lock.device_context, 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 textures = match id.kind { AtlasTextureKind::Monochrome => &mut lock.monochrome_textures, AtlasTextureKind::Polychrome => &mut lock.polychrome_textures, }; let Some(texture_slot) = textures.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() { textures.free_list.push(texture.id.index as usize); lock.tiles_by_key.remove(key); } else { *texture_slot = Some(texture); } } } } impl DirectXAtlasState { fn allocate( &mut self, size: Size, texture_kind: AtlasTextureKind, ) -> Option { { let textures = match texture_kind { AtlasTextureKind::Monochrome => &mut self.monochrome_textures, AtlasTextureKind::Polychrome => &mut self.polychrome_textures, }; if let Some(tile) = textures .iter_mut() .rev() .find_map(|texture| texture.allocate(size)) { return Some(tile); } } let texture = self.push_texture(size, texture_kind); texture.allocate(size) } fn push_texture( &mut self, min_size: Size, kind: AtlasTextureKind, ) -> &mut DirectXAtlasTexture { const DEFAULT_ATLAS_SIZE: Size = Size { width: DevicePixels(1024), height: DevicePixels(1024), }; // Max texture size for DirectX. See: // https://learn.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-resources-limits const MAX_ATLAS_SIZE: Size = Size { width: DevicePixels(16384), height: DevicePixels(16384), }; let size = min_size.min(&MAX_ATLAS_SIZE).max(&DEFAULT_ATLAS_SIZE); let pixel_format; let bind_flag; let bytes_per_pixel; match kind { AtlasTextureKind::Monochrome => { pixel_format = DXGI_FORMAT_A8_UNORM; bind_flag = D3D11_BIND_SHADER_RESOURCE; bytes_per_pixel = 1; } AtlasTextureKind::Polychrome => { pixel_format = DXGI_FORMAT_B8G8R8A8_UNORM; bind_flag = D3D11_BIND_SHADER_RESOURCE; bytes_per_pixel = 4; } } let texture_desc = D3D11_TEXTURE2D_DESC { Width: size.width.0 as u32, Height: size.height.0 as u32, MipLevels: 1, ArraySize: 1, Format: pixel_format, SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0, }, Usage: D3D11_USAGE_DEFAULT, BindFlags: bind_flag.0 as u32, CPUAccessFlags: D3D11_CPU_ACCESS_WRITE.0 as u32, MiscFlags: 0, }; let mut texture: Option = None; unsafe { self.device .CreateTexture2D(&texture_desc, None, Some(&mut texture)) .unwrap(); } let texture = texture.unwrap(); let texture_list = match kind { AtlasTextureKind::Monochrome => &mut self.monochrome_textures, AtlasTextureKind::Polychrome => &mut self.polychrome_textures, }; let index = texture_list.free_list.pop(); let view = unsafe { let mut view = None; self.device .CreateShaderResourceView(&texture, None, Some(&mut view)) .unwrap(); [view] }; let atlas_texture = DirectXAtlasTexture { id: AtlasTextureId { index: index.unwrap_or(texture_list.textures.len()) as u32, kind, }, bytes_per_pixel, allocator: etagere::BucketedAtlasAllocator::new(size.into()), texture, view, live_atlas_keys: 0, }; 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 texture(&self, id: AtlasTextureId) -> &DirectXAtlasTexture { 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 DirectXAtlasTexture { 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(), bounds: Bounds { origin: allocation.rectangle.min.into(), size, }, padding: 0, }; self.live_atlas_keys += 1; Some(tile) } fn upload( &self, device_context: &ID3D11DeviceContext, bounds: Bounds, bytes: &[u8], ) { unsafe { device_context.UpdateSubresource( &self.texture, 0, Some(&D3D11_BOX { left: bounds.left().0 as u32, top: bounds.top().0 as u32, front: 0, right: bounds.right().0 as u32, bottom: bounds.bottom().0 as u32, back: 1, }), bytes.as_ptr() as _, bounds.size.width.to_bytes(self.bytes_per_pixel as u8), 0, ); } } fn decrement_ref_count(&mut self) { self.live_atlas_keys -= 1; } fn is_unreferenced(&mut self) -> bool { self.live_atlas_keys == 0 } }