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