mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
gpui_macos: Fix stale atlas key causing crash on None texture lookup (#51996)
MetalAtlas::remove() used tiles_by_key.get(key) to look up the texture ID but only called tiles_by_key.remove(key) when the texture became fully unreferenced. When a tile was removed while other tiles remained on the same texture, the key stayed in tiles_by_key as a stale entry. Once subsequent removals deleted the texture, get_or_insert_with could return the stale tile referencing a now-deleted texture slot, causing an unwrap panic in MetalAtlasState::texture(). Fix: change .get(key) to .remove(key) unconditionally, matching the WGPU and DirectX atlas implementations which already do this correctly. Closes ZED-5KV Release Notes: - N/A or Added/Fixed/Improved ...
This commit is contained in:
parent
95852d4c71
commit
34807eb146
2 changed files with 79 additions and 4 deletions
|
|
@ -61,7 +61,7 @@ impl PlatformAtlas for MetalAtlas {
|
|||
|
||||
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 {
|
||||
let Some(id) = lock.tiles_by_key.remove(key).map(|v| v.texture_id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
|
@ -81,10 +81,8 @@ impl PlatformAtlas for MetalAtlas {
|
|||
|
||||
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);
|
||||
}
|
||||
|
|
@ -271,3 +269,81 @@ fn point_from_etagere(value: etagere::Point) -> Point<DevicePixels> {
|
|||
struct AssertSend<T>(T);
|
||||
|
||||
unsafe impl<T> Send for AssertSend<T> {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use gpui::PlatformAtlas;
|
||||
use std::borrow::Cow;
|
||||
|
||||
fn create_atlas() -> Option<MetalAtlas> {
|
||||
let device = metal::Device::system_default()?;
|
||||
Some(MetalAtlas::new(device, true))
|
||||
}
|
||||
|
||||
fn make_image_key(image_id: usize, frame_index: usize) -> AtlasKey {
|
||||
AtlasKey::Image(gpui::RenderImageParams {
|
||||
image_id: gpui::ImageId(image_id),
|
||||
frame_index,
|
||||
})
|
||||
}
|
||||
|
||||
fn insert_tile(atlas: &MetalAtlas, key: &AtlasKey, size: Size<DevicePixels>) -> AtlasTile {
|
||||
atlas
|
||||
.get_or_insert_with(key, &mut || {
|
||||
let byte_count = (size.width.0 as usize) * (size.height.0 as usize) * 4;
|
||||
Ok(Some((size, Cow::Owned(vec![0u8; byte_count]))))
|
||||
})
|
||||
.expect("allocation should succeed")
|
||||
.expect("callback returns Some")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_clears_stale_keys_from_tiles_by_key() {
|
||||
let Some(atlas) = create_atlas() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let small = Size {
|
||||
width: DevicePixels(64),
|
||||
height: DevicePixels(64),
|
||||
};
|
||||
|
||||
let key_a = make_image_key(1, 0);
|
||||
let key_b = make_image_key(2, 0);
|
||||
let key_c = make_image_key(3, 0);
|
||||
|
||||
let tile_a = insert_tile(&atlas, &key_a, small);
|
||||
let tile_b = insert_tile(&atlas, &key_b, small);
|
||||
let tile_c = insert_tile(&atlas, &key_c, small);
|
||||
|
||||
assert_eq!(tile_a.texture_id, tile_b.texture_id);
|
||||
assert_eq!(tile_b.texture_id, tile_c.texture_id);
|
||||
|
||||
// Remove A: texture still has B and C, so it stays.
|
||||
// The key for A must be removed from tiles_by_key.
|
||||
atlas.remove(&key_a);
|
||||
|
||||
// Remove B: texture still has C.
|
||||
atlas.remove(&key_b);
|
||||
|
||||
// Remove C: texture becomes unreferenced and is deleted.
|
||||
atlas.remove(&key_c);
|
||||
|
||||
// Re-inserting A must allocate a fresh tile on a new texture,
|
||||
// NOT return a stale tile referencing the deleted texture.
|
||||
let tile_a2 = insert_tile(&atlas, &key_a, small);
|
||||
|
||||
// The texture must actually exist — this would panic before the fix.
|
||||
let _texture = atlas.metal_texture(tile_a2.texture_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_nonexistent_key_is_noop() {
|
||||
let Some(atlas) = create_atlas() else {
|
||||
return;
|
||||
};
|
||||
let key = make_image_key(999, 0);
|
||||
atlas.remove(&key);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,7 +116,6 @@ impl PlatformAtlas for DirectXAtlas {
|
|||
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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue