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