diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 76a575724f6405b59ed77c17068dc64598aa3dc7..8228d44bb4f495b9451aa45096f4a26b2268f874 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -46,6 +46,7 @@ use smallvec::SmallVec; use std::borrow::Cow; use std::hash::{Hash, Hasher}; use std::io::Cursor; +use std::ops; use std::time::{Duration, Instant}; use std::{ fmt::{self, Debug}, @@ -561,6 +562,42 @@ pub(crate) trait PlatformAtlas: Send + Sync { key: &AtlasKey, build: &mut dyn FnMut() -> Result, Cow<'a, [u8]>)>>, ) -> Result>; + fn remove(&self, key: &AtlasKey); +} + +struct AtlasTextureList { + textures: Vec>, + free_list: Vec, +} + +impl Default for AtlasTextureList { + fn default() -> Self { + Self { + textures: Vec::default(), + free_list: Vec::default(), + } + } +} + +impl ops::Index for AtlasTextureList { + type Output = Option; + + fn index(&self, index: usize) -> &Self::Output { + &self.textures[index] + } +} + +impl AtlasTextureList { + #[allow(unused)] + fn drain(&mut self) -> std::vec::Drain> { + self.free_list.clear(); + self.textures.drain(..) + } + + #[allow(dead_code)] + fn iter_mut(&mut self) -> impl DoubleEndedIterator { + self.textures.iter_mut().flatten() + } } #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/crates/gpui/src/platform/blade/blade_atlas.rs b/crates/gpui/src/platform/blade/blade_atlas.rs index e6d5dc8ee9092732ce3d65867a1247cc2f014059..b876d5bb9be9e5efe24059f92622198bd3cce9f6 100644 --- a/crates/gpui/src/platform/blade/blade_atlas.rs +++ b/crates/gpui/src/platform/blade/blade_atlas.rs @@ -1,6 +1,6 @@ use crate::{ - AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas, - Point, Size, + platform::AtlasTextureList, AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, + DevicePixels, PlatformAtlas, Point, Size, }; use anyhow::Result; use blade_graphics as gpu; @@ -67,7 +67,7 @@ impl BladeAtlas { 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 { + for texture in textures.iter_mut() { texture.clear(); } } @@ -130,19 +130,48 @@ impl PlatformAtlas for BladeAtlas { 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]; - textures - .iter_mut() - .rev() - .find_map(|texture| texture.allocate(size)) - .unwrap_or_else(|| { - let texture = self.push_texture(size, texture_kind); - texture.allocate(size).unwrap() - }) + { + 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( @@ -198,21 +227,30 @@ impl BladeAtlasState { }, ); - let textures = &mut self.storage[kind]; + let texture_list = &mut self.storage[kind]; + let index = texture_list.free_list.pop(); + let atlas_texture = BladeAtlasTexture { id: AtlasTextureId { - index: textures.len() as u32, + index: index.unwrap_or(texture_list.textures.len()) as u32, kind, }, allocator: etagere::BucketedAtlasAllocator::new(size.into()), format, raw, raw_view, + live_atlas_keys: 0, }; self.initializations.push(atlas_texture.id); - textures.push(atlas_texture); - textures.last_mut().unwrap() + + 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]) { @@ -258,13 +296,13 @@ impl BladeAtlasState { #[derive(Default)] struct BladeAtlasStorage { - monochrome_textures: Vec, - polychrome_textures: Vec, - path_textures: Vec, + monochrome_textures: AtlasTextureList, + polychrome_textures: AtlasTextureList, + path_textures: AtlasTextureList, } impl ops::Index for BladeAtlasStorage { - type Output = Vec; + type Output = AtlasTextureList; fn index(&self, kind: AtlasTextureKind) -> &Self::Output { match kind { crate::AtlasTextureKind::Monochrome => &self.monochrome_textures, @@ -292,19 +330,19 @@ impl ops::Index for BladeAtlasStorage { crate::AtlasTextureKind::Polychrome => &self.polychrome_textures, crate::AtlasTextureKind::Path => &self.path_textures, }; - &textures[id.index as usize] + 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(..) { + for mut texture in self.monochrome_textures.drain().flatten() { texture.destroy(gpu); } - for mut texture in self.polychrome_textures.drain(..) { + for mut texture in self.polychrome_textures.drain().flatten() { texture.destroy(gpu); } - for mut texture in self.path_textures.drain(..) { + for mut texture in self.path_textures.drain().flatten() { texture.destroy(gpu); } } @@ -316,6 +354,7 @@ struct BladeAtlasTexture { raw: gpu::Texture, raw_view: gpu::TextureView, format: gpu::TextureFormat, + live_atlas_keys: u32, } impl BladeAtlasTexture { @@ -334,6 +373,7 @@ impl BladeAtlasTexture { size, }, }; + self.live_atlas_keys += 1; Some(tile) } @@ -345,6 +385,14 @@ impl BladeAtlasTexture { 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 { diff --git a/crates/gpui/src/platform/mac/metal_atlas.rs b/crates/gpui/src/platform/mac/metal_atlas.rs index 89a69877521cb339dd3596b0ba26b9cc9f0280ff..ca595c5ce3475356e47ac886f90704ceb6fdecfd 100644 --- a/crates/gpui/src/platform/mac/metal_atlas.rs +++ b/crates/gpui/src/platform/mac/metal_atlas.rs @@ -1,6 +1,6 @@ use crate::{ - AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas, - Point, Size, + platform::AtlasTextureList, AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, + DevicePixels, PlatformAtlas, Point, Size, }; use anyhow::{anyhow, Result}; use collections::FxHashMap; @@ -42,7 +42,7 @@ impl MetalAtlas { AtlasTextureKind::Polychrome => &mut lock.polychrome_textures, AtlasTextureKind::Path => &mut lock.path_textures, }; - for texture in textures { + for texture in textures.iter_mut() { texture.clear(); } } @@ -50,9 +50,9 @@ impl MetalAtlas { struct MetalAtlasState { device: AssertSend, - monochrome_textures: Vec, - polychrome_textures: Vec, - path_textures: Vec, + monochrome_textures: AtlasTextureList, + polychrome_textures: AtlasTextureList, + path_textures: AtlasTextureList, tiles_by_key: FxHashMap, } @@ -78,6 +78,38 @@ impl PlatformAtlas for MetalAtlas { Ok(Some(tile)) } } + + fn remove(&self, key: &AtlasKey) { + let mut lock = self.0.lock(); + let Some(id) = lock.tiles_by_key.get(key).map(|v| v.texture_id) else { + return; + }; + + let textures = match id.kind { + AtlasTextureKind::Monochrome => &mut lock.monochrome_textures, + AtlasTextureKind::Polychrome => &mut lock.polychrome_textures, + AtlasTextureKind::Path => &mut lock.polychrome_textures, + }; + + let Some(texture_slot) = textures + .textures + .iter_mut() + .find(|texture| texture.as_ref().is_some_and(|v| v.id == id)) + else { + return; + }; + + if let Some(mut texture) = texture_slot.take() { + texture.decrement_ref_count(); + + if texture.is_unreferenced() { + textures.free_list.push(id.index as usize); + lock.tiles_by_key.remove(key); + } else { + *texture_slot = Some(texture); + } + } + } } impl MetalAtlasState { @@ -86,20 +118,24 @@ impl MetalAtlasState { size: Size, texture_kind: AtlasTextureKind, ) -> Option { - let textures = match texture_kind { - AtlasTextureKind::Monochrome => &mut self.monochrome_textures, - AtlasTextureKind::Polychrome => &mut self.polychrome_textures, - AtlasTextureKind::Path => &mut self.path_textures, - }; + { + let textures = match texture_kind { + AtlasTextureKind::Monochrome => &mut self.monochrome_textures, + AtlasTextureKind::Polychrome => &mut self.polychrome_textures, + AtlasTextureKind::Path => &mut self.path_textures, + }; - textures - .iter_mut() - .rev() - .find_map(|texture| texture.allocate(size)) - .or_else(|| { - let texture = self.push_texture(size, texture_kind); - texture.allocate(size) - }) + 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( @@ -140,21 +176,31 @@ impl MetalAtlasState { texture_descriptor.set_usage(usage); let metal_texture = self.device.new_texture(&texture_descriptor); - let textures = match kind { + let texture_list = match kind { AtlasTextureKind::Monochrome => &mut self.monochrome_textures, AtlasTextureKind::Polychrome => &mut self.polychrome_textures, AtlasTextureKind::Path => &mut self.path_textures, }; + + let index = texture_list.free_list.pop(); + let atlas_texture = MetalAtlasTexture { id: AtlasTextureId { - index: textures.len() as u32, + index: index.unwrap_or(texture_list.textures.len()) as u32, kind, }, allocator: etagere::BucketedAtlasAllocator::new(size.into()), metal_texture: AssertSend(metal_texture), + live_atlas_keys: 0, }; - textures.push(atlas_texture); - textures.last_mut().unwrap() + + 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) -> &MetalAtlasTexture { @@ -163,7 +209,7 @@ impl MetalAtlasState { crate::AtlasTextureKind::Polychrome => &self.polychrome_textures, crate::AtlasTextureKind::Path => &self.path_textures, }; - &textures[id.index as usize] + textures[id.index as usize].as_ref().unwrap() } } @@ -171,6 +217,7 @@ struct MetalAtlasTexture { id: AtlasTextureId, allocator: BucketedAtlasAllocator, metal_texture: AssertSend, + live_atlas_keys: u32, } impl MetalAtlasTexture { @@ -189,6 +236,7 @@ impl MetalAtlasTexture { }, padding: 0, }; + self.live_atlas_keys += 1; Some(tile) } @@ -215,6 +263,14 @@ impl MetalAtlasTexture { _ => unimplemented!(), } } + + 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 { diff --git a/crates/gpui/src/platform/test/window.rs b/crates/gpui/src/platform/test/window.rs index d8ec6a718b0d21b6e9c72d18b7f770ae0f798786..9c94aeaf2f26ca8ffcbe3e574df48082dfc352c0 100644 --- a/crates/gpui/src/platform/test/window.rs +++ b/crates/gpui/src/platform/test/window.rs @@ -339,4 +339,9 @@ impl PlatformAtlas for TestAtlas { Ok(Some(state.tiles[key].clone())) } + + fn remove(&self, key: &AtlasKey) { + let mut state = self.0.lock(); + state.tiles.remove(key); + } } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index e4fa74f981ed5ff102b3a97d6f575fbf9d23c3e3..2b6f1d4a99fa0e6c30a80601d1e976d1b3e72829 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -2685,6 +2685,20 @@ impl<'a> WindowContext<'a> { }); } + /// Removes an image from the sprite atlas. + pub fn drop_image(&mut self, data: Arc) -> Result<()> { + for frame_index in 0..data.frame_count() { + let params = RenderImageParams { + image_id: data.id, + frame_index, + }; + + self.window.sprite_atlas.remove(¶ms.clone().into()); + } + + Ok(()) + } + #[must_use] /// Add a node to the layout tree for the current frame. Takes the `Style` of the element for which /// layout is being requested, along with the layout ids of any children. This method is called during