332 lines · 10.8 KB · Rust
RawDownload
use crate::{    platform::AtlasTextureList, AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds,    DevicePixels, PlatformAtlas, Point, Size,};use anyhow::{anyhow, Result};use collections::FxHashMap;use derive_more::{Deref, DerefMut};use etagere::BucketedAtlasAllocator;use metal::Device;use parking_lot::Mutex;use std::borrow::Cow; pub(crate) struct MetalAtlas(Mutex<MetalAtlasState>); impl MetalAtlas {    pub(crate) fn new(device: Device, path_sample_count: u32) -> Self {        MetalAtlas(Mutex::new(MetalAtlasState {            device: AssertSend(device),            monochrome_textures: Default::default(),            polychrome_textures: Default::default(),            path_textures: Default::default(),            tiles_by_key: Default::default(),            path_sample_count,        }))    }     pub(crate) fn metal_texture(&self, id: AtlasTextureId) -> metal::Texture {        self.0.lock().texture(id).metal_texture.clone()    }     pub(crate) fn msaa_texture(&self, id: AtlasTextureId) -> Option<metal::Texture> {        self.0.lock().texture(id).msaa_texture.clone()    }     pub(crate) fn allocate(        &self,        size: Size<DevicePixels>,        texture_kind: AtlasTextureKind,    ) -> Option<AtlasTile> {        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,            AtlasTextureKind::Path => &mut lock.path_textures,        };        for texture in textures.iter_mut() {            texture.clear();        }    }} struct MetalAtlasState {    device: AssertSend<Device>,    monochrome_textures: AtlasTextureList<MetalAtlasTexture>,    polychrome_textures: AtlasTextureList<MetalAtlasTexture>,    path_textures: AtlasTextureList<MetalAtlasTexture>,    tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,    path_sample_count: u32,} impl PlatformAtlas for MetalAtlas {    fn get_or_insert_with<'a>(        &self,        key: &AtlasKey,        build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,    ) -> Result<Option<AtlasTile>> {        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!("failed to allocate"))?;            let texture = lock.texture(tile.texture_id);            texture.upload(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.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 {    fn allocate(        &mut self,        size: Size<DevicePixels>,        texture_kind: AtlasTextureKind,    ) -> Option<AtlasTile> {        {            let textures = match texture_kind {                AtlasTextureKind::Monochrome => &mut self.monochrome_textures,                AtlasTextureKind::Polychrome => &mut self.polychrome_textures,                AtlasTextureKind::Path => &mut self.path_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<DevicePixels>,        kind: AtlasTextureKind,    ) -> &mut MetalAtlasTexture {        const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {            width: DevicePixels(1024),            height: DevicePixels(1024),        };        // Max texture size on all modern Apple GPUs. Anything bigger than that crashes in validateWithDevice.        const MAX_ATLAS_SIZE: Size<DevicePixels> = Size {            width: DevicePixels(16384),            height: DevicePixels(16384),        };        let size = min_size.min(&MAX_ATLAS_SIZE).max(&DEFAULT_ATLAS_SIZE);        let texture_descriptor = metal::TextureDescriptor::new();        texture_descriptor.set_width(size.width.into());        texture_descriptor.set_height(size.height.into());        let pixel_format;        let usage;        match kind {            AtlasTextureKind::Monochrome => {                pixel_format = metal::MTLPixelFormat::A8Unorm;                usage = metal::MTLTextureUsage::ShaderRead;            }            AtlasTextureKind::Polychrome => {                pixel_format = metal::MTLPixelFormat::BGRA8Unorm;                usage = metal::MTLTextureUsage::ShaderRead;            }            AtlasTextureKind::Path => {                pixel_format = metal::MTLPixelFormat::R16Float;                usage = metal::MTLTextureUsage::RenderTarget | metal::MTLTextureUsage::ShaderRead;            }        }        texture_descriptor.set_pixel_format(pixel_format);        texture_descriptor.set_usage(usage);        let metal_texture = self.device.new_texture(&texture_descriptor);         // We currently only enable MSAA for path textures.        let msaa_texture = if self.path_sample_count > 1 && kind == AtlasTextureKind::Path {            let mut descriptor = texture_descriptor.clone();            descriptor.set_texture_type(metal::MTLTextureType::D2Multisample);            descriptor.set_storage_mode(metal::MTLStorageMode::Private);            descriptor.set_sample_count(self.path_sample_count as _);            let msaa_texture = self.device.new_texture(&descriptor);            Some(msaa_texture)        } else {            None        };         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: index.unwrap_or(texture_list.textures.len()) as u32,                kind,            },            allocator: etagere::BucketedAtlasAllocator::new(size.into()),            metal_texture: AssertSend(metal_texture),            msaa_texture: AssertSend(msaa_texture),            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) -> &MetalAtlasTexture {        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()    }} struct MetalAtlasTexture {    id: AtlasTextureId,    allocator: BucketedAtlasAllocator,    metal_texture: AssertSend<metal::Texture>,    msaa_texture: AssertSend<Option<metal::Texture>>,    live_atlas_keys: u32,} impl MetalAtlasTexture {    fn clear(&mut self) {        self.allocator.clear();    }     fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {        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, bounds: Bounds<DevicePixels>, bytes: &[u8]) {        let region = metal::MTLRegion::new_2d(            bounds.origin.x.into(),            bounds.origin.y.into(),            bounds.size.width.into(),            bounds.size.height.into(),        );        self.metal_texture.replace_region(            region,            0,            bytes.as_ptr() as *const _,            bounds.size.width.to_bytes(self.bytes_per_pixel()) as u64,        );    }     fn bytes_per_pixel(&self) -> u8 {        use metal::MTLPixelFormat::*;        match self.metal_texture.pixel_format() {            A8Unorm | R8Unorm => 1,            RGBA8Unorm | BGRA8Unorm => 4,            _ => 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<Size<DevicePixels>> for etagere::Size {    fn from(size: Size<DevicePixels>) -> Self {        etagere::Size::new(size.width.into(), size.height.into())    }} impl From<etagere::Point> for Point<DevicePixels> {    fn from(value: etagere::Point) -> Self {        Point {            x: DevicePixels::from(value.x),            y: DevicePixels::from(value.y),        }    }} impl From<etagere::Size> for Size<DevicePixels> {    fn from(size: etagere::Size) -> Self {        Size {            width: DevicePixels::from(size.width),            height: DevicePixels::from(size.height),        }    }} impl From<etagere::Rectangle> for Bounds<DevicePixels> {    fn from(rectangle: etagere::Rectangle) -> Self {        Bounds {            origin: rectangle.min.into(),            size: rectangle.size().into(),        }    }} #[derive(Deref, DerefMut)]struct AssertSend<T>(T); unsafe impl<T> Send for AssertSend<T> {}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t