1use super::{BladeBelt, BladeBeltDescriptor};
2use crate::{
3 AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
4 Point, Size,
5};
6use anyhow::Result;
7use blade_graphics as gpu;
8use collections::FxHashMap;
9use etagere::BucketedAtlasAllocator;
10use parking_lot::Mutex;
11use std::{borrow::Cow, sync::Arc};
12
13pub(crate) const PATH_TEXTURE_FORMAT: gpu::TextureFormat = gpu::TextureFormat::R16Float;
14
15pub(crate) struct BladeAtlas(Mutex<BladeAtlasState>);
16
17struct PendingUpload {
18 id: AtlasTextureId,
19 bounds: Bounds<DevicePixels>,
20 data: gpu::BufferPiece,
21}
22
23struct BladeAtlasState {
24 gpu: Arc<gpu::Context>,
25 upload_belt: BladeBelt,
26 monochrome_textures: Vec<BladeAtlasTexture>,
27 polychrome_textures: Vec<BladeAtlasTexture>,
28 path_textures: Vec<BladeAtlasTexture>,
29 tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
30 uploads: Vec<PendingUpload>,
31}
32
33impl BladeAtlasState {
34 fn destroy(&mut self) {
35 for texture in self.monochrome_textures.drain(..) {
36 self.gpu.destroy_texture(texture.raw);
37 }
38 for texture in self.polychrome_textures.drain(..) {
39 self.gpu.destroy_texture(texture.raw);
40 }
41 for texture in self.path_textures.drain(..) {
42 self.gpu.destroy_texture(texture.raw);
43 self.gpu.destroy_texture_view(texture.raw_view.unwrap());
44 }
45 self.upload_belt.destroy(&self.gpu);
46 }
47}
48
49pub struct BladeTextureInfo {
50 pub size: gpu::Extent,
51 pub raw_view: Option<gpu::TextureView>,
52}
53
54impl BladeAtlas {
55 pub(crate) fn new(gpu: &Arc<gpu::Context>) -> Self {
56 BladeAtlas(Mutex::new(BladeAtlasState {
57 gpu: Arc::clone(gpu),
58 upload_belt: BladeBelt::new(BladeBeltDescriptor {
59 memory: gpu::Memory::Upload,
60 min_chunk_size: 0x10000,
61 }),
62 monochrome_textures: Default::default(),
63 polychrome_textures: Default::default(),
64 path_textures: Default::default(),
65 tiles_by_key: Default::default(),
66 uploads: Vec::new(),
67 }))
68 }
69
70 pub(crate) fn destroy(&self) {
71 self.0.lock().destroy();
72 }
73
74 pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) {
75 let mut lock = self.0.lock();
76 let textures = match texture_kind {
77 AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
78 AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
79 AtlasTextureKind::Path => &mut lock.path_textures,
80 };
81 for texture in textures {
82 texture.clear();
83 }
84 }
85
86 pub fn allocate(&self, size: Size<DevicePixels>, texture_kind: AtlasTextureKind) -> AtlasTile {
87 let mut lock = self.0.lock();
88 lock.allocate(size, texture_kind)
89 }
90
91 pub fn before_frame(&self, gpu_encoder: &mut gpu::CommandEncoder) {
92 let mut lock = self.0.lock();
93 lock.flush(gpu_encoder.transfer());
94 }
95
96 pub fn after_frame(&self, sync_point: &gpu::SyncPoint) {
97 let mut lock = self.0.lock();
98 lock.upload_belt.flush(sync_point);
99 }
100
101 pub fn get_texture_info(&self, id: AtlasTextureId) -> BladeTextureInfo {
102 let lock = self.0.lock();
103 let textures = match id.kind {
104 crate::AtlasTextureKind::Monochrome => &lock.monochrome_textures,
105 crate::AtlasTextureKind::Polychrome => &lock.polychrome_textures,
106 crate::AtlasTextureKind::Path => &lock.path_textures,
107 };
108 let texture = &textures[id.index as usize];
109 let size = texture.allocator.size();
110 BladeTextureInfo {
111 size: gpu::Extent {
112 width: size.width as u32,
113 height: size.height as u32,
114 depth: 1,
115 },
116 raw_view: texture.raw_view,
117 }
118 }
119}
120
121impl PlatformAtlas for BladeAtlas {
122 fn get_or_insert_with<'a>(
123 &self,
124 key: &AtlasKey,
125 build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
126 ) -> Result<AtlasTile> {
127 let mut lock = self.0.lock();
128 if let Some(tile) = lock.tiles_by_key.get(key) {
129 Ok(tile.clone())
130 } else {
131 let (size, bytes) = build()?;
132 let tile = lock.allocate(size, key.texture_kind());
133 lock.upload_texture(tile.texture_id, tile.bounds, &bytes);
134 lock.tiles_by_key.insert(key.clone(), tile.clone());
135 Ok(tile)
136 }
137 }
138}
139
140impl BladeAtlasState {
141 fn allocate(&mut self, size: Size<DevicePixels>, texture_kind: AtlasTextureKind) -> AtlasTile {
142 let textures = match texture_kind {
143 AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
144 AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
145 AtlasTextureKind::Path => &mut self.path_textures,
146 };
147 textures
148 .iter_mut()
149 .rev()
150 .find_map(|texture| texture.allocate(size))
151 .unwrap_or_else(|| {
152 let texture = self.push_texture(size, texture_kind);
153 texture.allocate(size).unwrap()
154 })
155 }
156
157 fn push_texture(
158 &mut self,
159 min_size: Size<DevicePixels>,
160 kind: AtlasTextureKind,
161 ) -> &mut BladeAtlasTexture {
162 const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {
163 width: DevicePixels(1024),
164 height: DevicePixels(1024),
165 };
166
167 let size = min_size.max(&DEFAULT_ATLAS_SIZE);
168 let format;
169 let usage;
170 match kind {
171 AtlasTextureKind::Monochrome => {
172 format = gpu::TextureFormat::R8Unorm;
173 usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE;
174 }
175 AtlasTextureKind::Polychrome => {
176 format = gpu::TextureFormat::Bgra8Unorm;
177 usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE;
178 }
179 AtlasTextureKind::Path => {
180 format = PATH_TEXTURE_FORMAT;
181 usage = gpu::TextureUsage::COPY
182 | gpu::TextureUsage::RESOURCE
183 | gpu::TextureUsage::TARGET;
184 }
185 }
186
187 let raw = self.gpu.create_texture(gpu::TextureDesc {
188 name: "atlas",
189 format,
190 size: gpu::Extent {
191 width: size.width.into(),
192 height: size.height.into(),
193 depth: 1,
194 },
195 array_layer_count: 1,
196 mip_level_count: 1,
197 dimension: gpu::TextureDimension::D2,
198 usage,
199 });
200 let raw_view = if usage.contains(gpu::TextureUsage::TARGET) {
201 Some(self.gpu.create_texture_view(gpu::TextureViewDesc {
202 name: "",
203 texture: raw,
204 format,
205 dimension: gpu::ViewDimension::D2,
206 subresources: &Default::default(),
207 }))
208 } else {
209 None
210 };
211
212 let textures = match kind {
213 AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
214 AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
215 AtlasTextureKind::Path => &mut self.path_textures,
216 };
217 let atlas_texture = BladeAtlasTexture {
218 id: AtlasTextureId {
219 index: textures.len() as u32,
220 kind,
221 },
222 allocator: etagere::BucketedAtlasAllocator::new(size.into()),
223 format,
224 raw,
225 raw_view,
226 };
227 textures.push(atlas_texture);
228 textures.last_mut().unwrap()
229 }
230
231 fn upload_texture(&mut self, id: AtlasTextureId, bounds: Bounds<DevicePixels>, bytes: &[u8]) {
232 let data = self.upload_belt.alloc_data(bytes, &self.gpu);
233 self.uploads.push(PendingUpload { id, bounds, data });
234 }
235
236 fn flush(&mut self, mut transfers: gpu::TransferCommandEncoder) {
237 for upload in self.uploads.drain(..) {
238 let textures = match upload.id.kind {
239 crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
240 crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
241 crate::AtlasTextureKind::Path => &self.path_textures,
242 };
243 let texture = &textures[upload.id.index as usize];
244
245 transfers.copy_buffer_to_texture(
246 upload.data,
247 upload.bounds.size.width.to_bytes(texture.bytes_per_pixel()),
248 gpu::TexturePiece {
249 texture: texture.raw,
250 mip_level: 0,
251 array_layer: 0,
252 origin: [
253 upload.bounds.origin.x.into(),
254 upload.bounds.origin.y.into(),
255 0,
256 ],
257 },
258 gpu::Extent {
259 width: upload.bounds.size.width.into(),
260 height: upload.bounds.size.height.into(),
261 depth: 1,
262 },
263 );
264 }
265 }
266}
267
268struct BladeAtlasTexture {
269 id: AtlasTextureId,
270 allocator: BucketedAtlasAllocator,
271 raw: gpu::Texture,
272 raw_view: Option<gpu::TextureView>,
273 format: gpu::TextureFormat,
274}
275
276impl BladeAtlasTexture {
277 fn clear(&mut self) {
278 self.allocator.clear();
279 }
280
281 fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
282 let allocation = self.allocator.allocate(size.into())?;
283 let tile = AtlasTile {
284 texture_id: self.id,
285 tile_id: allocation.id.into(),
286 padding: 0,
287 bounds: Bounds {
288 origin: allocation.rectangle.min.into(),
289 size,
290 },
291 };
292 Some(tile)
293 }
294
295 fn bytes_per_pixel(&self) -> u8 {
296 self.format.block_info().size
297 }
298}
299
300impl From<Size<DevicePixels>> for etagere::Size {
301 fn from(size: Size<DevicePixels>) -> Self {
302 etagere::Size::new(size.width.into(), size.height.into())
303 }
304}
305
306impl From<etagere::Point> for Point<DevicePixels> {
307 fn from(value: etagere::Point) -> Self {
308 Point {
309 x: DevicePixels::from(value.x),
310 y: DevicePixels::from(value.y),
311 }
312 }
313}
314
315impl From<etagere::Size> for Size<DevicePixels> {
316 fn from(size: etagere::Size) -> Self {
317 Size {
318 width: DevicePixels::from(size.width),
319 height: DevicePixels::from(size.height),
320 }
321 }
322}
323
324impl From<etagere::Rectangle> for Bounds<DevicePixels> {
325 fn from(rectangle: etagere::Rectangle) -> Self {
326 Bounds {
327 origin: rectangle.min.into(),
328 size: rectangle.size().into(),
329 }
330 }
331}