metal_atlas.rs

  1use crate::{
  2    AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
  3    Point, Size,
  4};
  5use anyhow::{anyhow, Result};
  6use collections::FxHashMap;
  7use derive_more::{Deref, DerefMut};
  8use etagere::BucketedAtlasAllocator;
  9use metal::Device;
 10use parking_lot::Mutex;
 11use std::borrow::Cow;
 12
 13pub(crate) struct MetalAtlas(Mutex<MetalAtlasState>);
 14
 15impl MetalAtlas {
 16    pub(crate) fn new(device: Device) -> Self {
 17        MetalAtlas(Mutex::new(MetalAtlasState {
 18            device: AssertSend(device),
 19            monochrome_textures: Default::default(),
 20            polychrome_textures: Default::default(),
 21            path_textures: Default::default(),
 22            tiles_by_key: Default::default(),
 23        }))
 24    }
 25
 26    pub(crate) fn metal_texture(&self, id: AtlasTextureId) -> metal::Texture {
 27        self.0.lock().texture(id).metal_texture.clone()
 28    }
 29
 30    pub(crate) fn allocate(
 31        &self,
 32        size: Size<DevicePixels>,
 33        texture_kind: AtlasTextureKind,
 34    ) -> Option<AtlasTile> {
 35        self.0.lock().allocate(size, texture_kind)
 36    }
 37
 38    pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) {
 39        let mut lock = self.0.lock();
 40        let textures = match texture_kind {
 41            AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
 42            AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
 43            AtlasTextureKind::Path => &mut lock.path_textures,
 44        };
 45        for texture in textures {
 46            texture.clear();
 47        }
 48    }
 49}
 50
 51struct MetalAtlasState {
 52    device: AssertSend<Device>,
 53    monochrome_textures: Vec<MetalAtlasTexture>,
 54    polychrome_textures: Vec<MetalAtlasTexture>,
 55    path_textures: Vec<MetalAtlasTexture>,
 56    tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
 57}
 58
 59impl PlatformAtlas for MetalAtlas {
 60    fn get_or_insert_with<'a>(
 61        &self,
 62        key: &AtlasKey,
 63        build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
 64    ) -> Result<Option<AtlasTile>> {
 65        let mut lock = self.0.lock();
 66        if let Some(tile) = lock.tiles_by_key.get(key) {
 67            Ok(Some(tile.clone()))
 68        } else {
 69            let Some((size, bytes)) = build()? else {
 70                return Ok(None);
 71            };
 72            let tile = lock
 73                .allocate(size, key.texture_kind())
 74                .ok_or_else(|| anyhow!("failed to allocate"))?;
 75            let texture = lock.texture(tile.texture_id);
 76            texture.upload(tile.bounds, &bytes);
 77            lock.tiles_by_key.insert(key.clone(), tile.clone());
 78            Ok(Some(tile))
 79        }
 80    }
 81}
 82
 83impl MetalAtlasState {
 84    fn allocate(
 85        &mut self,
 86        size: Size<DevicePixels>,
 87        texture_kind: AtlasTextureKind,
 88    ) -> Option<AtlasTile> {
 89        let textures = match texture_kind {
 90            AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
 91            AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
 92            AtlasTextureKind::Path => &mut self.path_textures,
 93        };
 94
 95        textures
 96            .iter_mut()
 97            .rev()
 98            .find_map(|texture| texture.allocate(size))
 99            .or_else(|| {
100                let texture = self.push_texture(size, texture_kind);
101                texture.allocate(size)
102            })
103    }
104
105    fn push_texture(
106        &mut self,
107        min_size: Size<DevicePixels>,
108        kind: AtlasTextureKind,
109    ) -> &mut MetalAtlasTexture {
110        const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {
111            width: DevicePixels(1024),
112            height: DevicePixels(1024),
113        };
114        // Max texture size on all modern Apple GPUs. Anything bigger than that crashes in validateWithDevice.
115        const MAX_ATLAS_SIZE: Size<DevicePixels> = Size {
116            width: DevicePixels(16384),
117            height: DevicePixels(16384),
118        };
119        let size = min_size.min(&MAX_ATLAS_SIZE).max(&DEFAULT_ATLAS_SIZE);
120        let texture_descriptor = metal::TextureDescriptor::new();
121        texture_descriptor.set_width(size.width.into());
122        texture_descriptor.set_height(size.height.into());
123        let pixel_format;
124        let usage;
125        match kind {
126            AtlasTextureKind::Monochrome => {
127                pixel_format = metal::MTLPixelFormat::A8Unorm;
128                usage = metal::MTLTextureUsage::ShaderRead;
129            }
130            AtlasTextureKind::Polychrome => {
131                pixel_format = metal::MTLPixelFormat::BGRA8Unorm;
132                usage = metal::MTLTextureUsage::ShaderRead;
133            }
134            AtlasTextureKind::Path => {
135                pixel_format = metal::MTLPixelFormat::R16Float;
136                usage = metal::MTLTextureUsage::RenderTarget | metal::MTLTextureUsage::ShaderRead;
137            }
138        }
139        texture_descriptor.set_pixel_format(pixel_format);
140        texture_descriptor.set_usage(usage);
141        let metal_texture = self.device.new_texture(&texture_descriptor);
142
143        let textures = match kind {
144            AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
145            AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
146            AtlasTextureKind::Path => &mut self.path_textures,
147        };
148        let atlas_texture = MetalAtlasTexture {
149            id: AtlasTextureId {
150                index: textures.len() as u32,
151                kind,
152            },
153            allocator: etagere::BucketedAtlasAllocator::new(size.into()),
154            metal_texture: AssertSend(metal_texture),
155        };
156        textures.push(atlas_texture);
157        textures.last_mut().unwrap()
158    }
159
160    fn texture(&self, id: AtlasTextureId) -> &MetalAtlasTexture {
161        let textures = match id.kind {
162            crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
163            crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
164            crate::AtlasTextureKind::Path => &self.path_textures,
165        };
166        &textures[id.index as usize]
167    }
168}
169
170struct MetalAtlasTexture {
171    id: AtlasTextureId,
172    allocator: BucketedAtlasAllocator,
173    metal_texture: AssertSend<metal::Texture>,
174}
175
176impl MetalAtlasTexture {
177    fn clear(&mut self) {
178        self.allocator.clear();
179    }
180
181    fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
182        let allocation = self.allocator.allocate(size.into())?;
183        let tile = AtlasTile {
184            texture_id: self.id,
185            tile_id: allocation.id.into(),
186            bounds: Bounds {
187                origin: allocation.rectangle.min.into(),
188                size,
189            },
190            padding: 0,
191        };
192        Some(tile)
193    }
194
195    fn upload(&self, bounds: Bounds<DevicePixels>, bytes: &[u8]) {
196        let region = metal::MTLRegion::new_2d(
197            bounds.origin.x.into(),
198            bounds.origin.y.into(),
199            bounds.size.width.into(),
200            bounds.size.height.into(),
201        );
202        self.metal_texture.replace_region(
203            region,
204            0,
205            bytes.as_ptr() as *const _,
206            bounds.size.width.to_bytes(self.bytes_per_pixel()) as u64,
207        );
208    }
209
210    fn bytes_per_pixel(&self) -> u8 {
211        use metal::MTLPixelFormat::*;
212        match self.metal_texture.pixel_format() {
213            A8Unorm | R8Unorm => 1,
214            RGBA8Unorm | BGRA8Unorm => 4,
215            _ => unimplemented!(),
216        }
217    }
218}
219
220impl From<Size<DevicePixels>> for etagere::Size {
221    fn from(size: Size<DevicePixels>) -> Self {
222        etagere::Size::new(size.width.into(), size.height.into())
223    }
224}
225
226impl From<etagere::Point> for Point<DevicePixels> {
227    fn from(value: etagere::Point) -> Self {
228        Point {
229            x: DevicePixels::from(value.x),
230            y: DevicePixels::from(value.y),
231        }
232    }
233}
234
235impl From<etagere::Size> for Size<DevicePixels> {
236    fn from(size: etagere::Size) -> Self {
237        Size {
238            width: DevicePixels::from(size.width),
239            height: DevicePixels::from(size.height),
240        }
241    }
242}
243
244impl From<etagere::Rectangle> for Bounds<DevicePixels> {
245    fn from(rectangle: etagere::Rectangle) -> Self {
246        Bounds {
247            origin: rectangle.min.into(),
248            size: rectangle.size().into(),
249        }
250    }
251}
252
253#[derive(Deref, DerefMut)]
254struct AssertSend<T>(T);
255
256unsafe impl<T> Send for AssertSend<T> {}