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) struct BladeAtlas(Mutex<BladeAtlasState>);
14
15struct BladeAtlasState {
16 gpu: Arc<gpu::Context>,
17 gpu_encoder: gpu::CommandEncoder,
18 upload_belt: BladeBelt,
19 monochrome_textures: Vec<BladeAtlasTexture>,
20 polychrome_textures: Vec<BladeAtlasTexture>,
21 path_textures: Vec<BladeAtlasTexture>,
22 tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
23}
24
25impl BladeAtlasState {
26 fn destroy(&mut self) {
27 for texture in self.monochrome_textures.drain(..) {
28 self.gpu.destroy_texture(texture.raw);
29 }
30 for texture in self.polychrome_textures.drain(..) {
31 self.gpu.destroy_texture(texture.raw);
32 }
33 for texture in self.path_textures.drain(..) {
34 self.gpu.destroy_texture(texture.raw);
35 }
36 self.gpu.destroy_command_encoder(&mut self.gpu_encoder);
37 self.upload_belt.destroy(&self.gpu);
38 }
39}
40
41impl BladeAtlas {
42 pub(crate) fn new(gpu: &Arc<gpu::Context>) -> Self {
43 BladeAtlas(Mutex::new(BladeAtlasState {
44 gpu: Arc::clone(gpu),
45 gpu_encoder: gpu.create_command_encoder(gpu::CommandEncoderDesc {
46 name: "atlas",
47 buffer_count: 3,
48 }),
49 upload_belt: BladeBelt::new(BladeBeltDescriptor {
50 memory: gpu::Memory::Upload,
51 min_chunk_size: 0x10000,
52 }),
53 monochrome_textures: Default::default(),
54 polychrome_textures: Default::default(),
55 path_textures: Default::default(),
56 tiles_by_key: Default::default(),
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 = match texture_kind {
67 AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
68 AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
69 AtlasTextureKind::Path => &mut lock.path_textures,
70 };
71 for texture in textures {
72 texture.clear();
73 }
74 }
75
76 pub fn start_frame(&self) {
77 let mut lock = self.0.lock();
78 lock.gpu_encoder.start();
79 }
80
81 pub fn finish_frame(&self) -> gpu::SyncPoint {
82 let mut lock = self.0.lock();
83 let gpu = lock.gpu.clone();
84 let sync_point = gpu.submit(&mut lock.gpu_encoder);
85 lock.upload_belt.flush(&sync_point);
86 sync_point
87 }
88}
89
90impl PlatformAtlas for BladeAtlas {
91 fn get_or_insert_with<'a>(
92 &self,
93 key: &AtlasKey,
94 build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
95 ) -> Result<AtlasTile> {
96 let mut lock = self.0.lock();
97 if let Some(tile) = lock.tiles_by_key.get(key) {
98 Ok(tile.clone())
99 } else {
100 let (size, bytes) = build()?;
101 let tile = lock.allocate(size, key.texture_kind());
102 lock.upload_texture(tile.texture_id, tile.bounds, &bytes);
103 lock.tiles_by_key.insert(key.clone(), tile.clone());
104 Ok(tile)
105 }
106 }
107}
108
109impl BladeAtlasState {
110 fn allocate(&mut self, size: Size<DevicePixels>, texture_kind: AtlasTextureKind) -> AtlasTile {
111 let textures = match texture_kind {
112 AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
113 AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
114 AtlasTextureKind::Path => &mut self.path_textures,
115 };
116 textures
117 .iter_mut()
118 .rev()
119 .find_map(|texture| texture.allocate(size))
120 .unwrap_or_else(|| {
121 let texture = self.push_texture(size, texture_kind);
122 texture.allocate(size).unwrap()
123 })
124 }
125
126 fn push_texture(
127 &mut self,
128 min_size: Size<DevicePixels>,
129 kind: AtlasTextureKind,
130 ) -> &mut BladeAtlasTexture {
131 const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {
132 width: DevicePixels(1024),
133 height: DevicePixels(1024),
134 };
135
136 let size = min_size.max(&DEFAULT_ATLAS_SIZE);
137 let format;
138 let usage;
139 match kind {
140 AtlasTextureKind::Monochrome => {
141 format = gpu::TextureFormat::R8Unorm;
142 usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE;
143 }
144 AtlasTextureKind::Polychrome => {
145 format = gpu::TextureFormat::Bgra8Unorm;
146 usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE;
147 }
148 AtlasTextureKind::Path => {
149 format = gpu::TextureFormat::R16Float;
150 usage = gpu::TextureUsage::COPY
151 | gpu::TextureUsage::RESOURCE
152 | gpu::TextureUsage::TARGET;
153 }
154 }
155
156 let raw = self.gpu.create_texture(gpu::TextureDesc {
157 name: "",
158 format,
159 size: gpu::Extent {
160 width: size.width.into(),
161 height: size.height.into(),
162 depth: 1,
163 },
164 array_layer_count: 1,
165 mip_level_count: 1,
166 dimension: gpu::TextureDimension::D2,
167 usage,
168 });
169
170 let textures = match kind {
171 AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
172 AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
173 AtlasTextureKind::Path => &mut self.path_textures,
174 };
175 let atlas_texture = BladeAtlasTexture {
176 id: AtlasTextureId {
177 index: textures.len() as u32,
178 kind,
179 },
180 allocator: etagere::BucketedAtlasAllocator::new(size.into()),
181 format,
182 raw,
183 };
184 textures.push(atlas_texture);
185 textures.last_mut().unwrap()
186 }
187
188 fn upload_texture(&mut self, id: AtlasTextureId, bounds: Bounds<DevicePixels>, bytes: &[u8]) {
189 let textures = match id.kind {
190 crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
191 crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
192 crate::AtlasTextureKind::Path => &self.path_textures,
193 };
194 let texture = &textures[id.index as usize];
195
196 let src_data = self.upload_belt.alloc_data(bytes, &self.gpu);
197
198 let mut transfers = self.gpu_encoder.transfer();
199 transfers.copy_buffer_to_texture(
200 src_data,
201 bounds.size.width.to_bytes(texture.bytes_per_pixel()),
202 gpu::TexturePiece {
203 texture: texture.raw,
204 mip_level: 0,
205 array_layer: 0,
206 origin: [bounds.origin.x.into(), bounds.origin.y.into(), 0],
207 },
208 gpu::Extent {
209 width: bounds.size.width.into(),
210 height: bounds.size.height.into(),
211 depth: 1,
212 },
213 );
214 }
215}
216
217struct BladeAtlasTexture {
218 id: AtlasTextureId,
219 allocator: BucketedAtlasAllocator,
220 raw: gpu::Texture,
221 format: gpu::TextureFormat,
222}
223
224impl BladeAtlasTexture {
225 fn clear(&mut self) {
226 self.allocator.clear();
227 }
228
229 fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
230 let allocation = self.allocator.allocate(size.into())?;
231 let tile = AtlasTile {
232 texture_id: self.id,
233 tile_id: allocation.id.into(),
234 bounds: Bounds {
235 origin: allocation.rectangle.min.into(),
236 size,
237 },
238 };
239 Some(tile)
240 }
241
242 fn bytes_per_pixel(&self) -> u8 {
243 self.format.block_info().size
244 }
245}
246
247impl From<Size<DevicePixels>> for etagere::Size {
248 fn from(size: Size<DevicePixels>) -> Self {
249 etagere::Size::new(size.width.into(), size.height.into())
250 }
251}
252
253impl From<etagere::Point> for Point<DevicePixels> {
254 fn from(value: etagere::Point) -> Self {
255 Point {
256 x: DevicePixels::from(value.x),
257 y: DevicePixels::from(value.y),
258 }
259 }
260}
261
262impl From<etagere::Size> for Size<DevicePixels> {
263 fn from(size: etagere::Size) -> Self {
264 Size {
265 width: DevicePixels::from(size.width),
266 height: DevicePixels::from(size.height),
267 }
268 }
269}
270
271impl From<etagere::Rectangle> for Bounds<DevicePixels> {
272 fn from(rectangle: etagere::Rectangle) -> Self {
273 Bounds {
274 origin: rectangle.min.into(),
275 size: rectangle.size().into(),
276 }
277 }
278}