1use crate::{
2 AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
3 Point, Size,
4};
5use anyhow::Result;
6use collections::HashMap;
7use derive_more::{Deref, DerefMut};
8use etagere::BucketedAtlasAllocator;
9use metal::Device;
10use parking_lot::Mutex;
11use std::borrow::Cow;
12
13pub struct MetalAtlas(Mutex<MetalAtlasState>);
14
15impl MetalAtlas {
16 pub 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 ) -> 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: HashMap<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 return Ok(tile.clone());
68 } else {
69 let (size, bytes) = build()?;
70 let tile = lock.allocate(size, key.texture_kind());
71 let texture = lock.texture(tile.texture_id);
72 texture.upload(tile.bounds, &bytes);
73 lock.tiles_by_key.insert(key.clone(), tile.clone());
74 Ok(tile)
75 }
76 }
77
78 fn clear(&self) {
79 let mut lock = self.0.lock();
80 lock.tiles_by_key.clear();
81 for texture in &mut lock.monochrome_textures {
82 texture.clear();
83 }
84 for texture in &mut lock.polychrome_textures {
85 texture.clear();
86 }
87 for texture in &mut lock.path_textures {
88 texture.clear();
89 }
90 }
91}
92
93impl MetalAtlasState {
94 fn allocate(&mut self, size: Size<DevicePixels>, texture_kind: AtlasTextureKind) -> AtlasTile {
95 let textures = match texture_kind {
96 AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
97 AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
98 AtlasTextureKind::Path => &mut self.path_textures,
99 };
100 textures
101 .iter_mut()
102 .rev()
103 .find_map(|texture| texture.allocate(size))
104 .unwrap_or_else(|| {
105 let texture = self.push_texture(size, texture_kind);
106 texture.allocate(size).unwrap()
107 })
108 }
109
110 fn push_texture(
111 &mut self,
112 min_size: Size<DevicePixels>,
113 kind: AtlasTextureKind,
114 ) -> &mut MetalAtlasTexture {
115 const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {
116 width: DevicePixels(1024),
117 height: DevicePixels(1024),
118 };
119
120 let size = min_size.max(&DEFAULT_ATLAS_SIZE);
121 let texture_descriptor = metal::TextureDescriptor::new();
122 texture_descriptor.set_width(size.width.into());
123 texture_descriptor.set_height(size.height.into());
124 let pixel_format;
125 let usage;
126 match kind {
127 AtlasTextureKind::Monochrome => {
128 pixel_format = metal::MTLPixelFormat::A8Unorm;
129 usage = metal::MTLTextureUsage::ShaderRead;
130 }
131 AtlasTextureKind::Polychrome => {
132 pixel_format = metal::MTLPixelFormat::BGRA8Unorm;
133 usage = metal::MTLTextureUsage::ShaderRead;
134 }
135 AtlasTextureKind::Path => {
136 pixel_format = metal::MTLPixelFormat::R16Float;
137 usage = metal::MTLTextureUsage::RenderTarget | metal::MTLTextureUsage::ShaderRead;
138 }
139 }
140 texture_descriptor.set_pixel_format(pixel_format);
141 texture_descriptor.set_usage(usage);
142 let metal_texture = self.device.new_texture(&texture_descriptor);
143
144 let textures = match kind {
145 AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
146 AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
147 AtlasTextureKind::Path => &mut self.path_textures,
148 };
149 let atlas_texture = MetalAtlasTexture {
150 id: AtlasTextureId {
151 index: textures.len() as u32,
152 kind,
153 },
154 allocator: etagere::BucketedAtlasAllocator::new(size.into()),
155 metal_texture: AssertSend(metal_texture),
156 };
157 textures.push(atlas_texture);
158 textures.last_mut().unwrap()
159 }
160
161 fn texture(&self, id: AtlasTextureId) -> &MetalAtlasTexture {
162 let textures = match id.kind {
163 crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
164 crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
165 crate::AtlasTextureKind::Path => &self.path_textures,
166 };
167 &textures[id.index as usize]
168 }
169}
170
171struct MetalAtlasTexture {
172 id: AtlasTextureId,
173 allocator: BucketedAtlasAllocator,
174 metal_texture: AssertSend<metal::Texture>,
175}
176
177impl MetalAtlasTexture {
178 fn clear(&mut self) {
179 self.allocator.clear();
180 }
181
182 fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
183 let allocation = self.allocator.allocate(size.into())?;
184 let tile = AtlasTile {
185 texture_id: self.id,
186 tile_id: allocation.id.into(),
187 bounds: Bounds {
188 origin: allocation.rectangle.min.into(),
189 size,
190 },
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 u32::from(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> {}