1use crate::core::{self, Size};
2use crate::graphics::Shell;
3use crate::image::atlas::{self, Atlas};
4
5#[cfg(all(feature = "image", not(target_arch = "wasm32")))]
6use worker::Worker;
7
8#[cfg(feature = "image")]
9use std::collections::HashMap;
10
11use std::sync::Arc;
12
13pub struct Cache {
14 atlas: Atlas,
15 #[cfg(feature = "image")]
16 raster: Raster,
17 #[cfg(feature = "svg")]
18 vector: crate::image::vector::Cache,
19 #[cfg(all(feature = "image", not(target_arch = "wasm32")))]
20 worker: Worker,
21}
22
23impl Cache {
24 pub fn new(
25 device: &wgpu::Device,
26 _queue: &wgpu::Queue,
27 backend: wgpu::Backend,
28 layout: wgpu::BindGroupLayout,
29 _shell: &Shell,
30 ) -> Self {
31 #[cfg(all(feature = "image", not(target_arch = "wasm32")))]
32 let worker = Worker::new(device, _queue, backend, layout.clone(), _shell);
33
34 Self {
35 atlas: Atlas::new(device, backend, layout),
36 #[cfg(feature = "image")]
37 raster: Raster {
38 cache: crate::image::raster::Cache::default(),
39 pending: HashMap::new(),
40 belt: wgpu::util::StagingBelt::new(2 * 1024 * 1024),
41 },
42 #[cfg(feature = "svg")]
43 vector: crate::image::vector::Cache::default(),
44 #[cfg(all(feature = "image", not(target_arch = "wasm32")))]
45 worker,
46 }
47 }
48
49 #[cfg(feature = "image")]
50 pub fn allocate_image(
51 &mut self,
52 handle: &core::image::Handle,
53 callback: impl FnOnce(Result<core::image::Allocation, core::image::Error>) + Send + 'static,
54 ) {
55 use crate::image::raster::Memory;
56
57 let callback = Box::new(callback);
58
59 if let Some(callbacks) = self.raster.pending.get_mut(&handle.id()) {
60 callbacks.push(callback);
61 return;
62 }
63
64 if let Some(Memory::Device {
65 allocation, entry, ..
66 }) = self.raster.cache.get_mut(handle)
67 {
68 if let Some(allocation) = allocation
69 .as_ref()
70 .and_then(core::image::Allocation::upgrade)
71 {
72 callback(Ok(allocation));
73 return;
74 }
75
76 #[allow(unsafe_code)]
77 let new = unsafe { core::image::allocate(handle, entry.size()) };
78 *allocation = Some(new.downgrade());
79 callback(Ok(new));
80
81 return;
82 }
83
84 let _ = self.raster.pending.insert(handle.id(), vec![callback]);
85
86 #[cfg(not(target_arch = "wasm32"))]
87 self.worker.load(handle);
88 }
89
90 #[cfg(feature = "image")]
91 pub fn load_image(
92 &mut self,
93 device: &wgpu::Device,
94 queue: &wgpu::Queue,
95 handle: &core::image::Handle,
96 ) -> Result<core::image::Allocation, core::image::Error> {
97 use crate::image::raster::Memory;
98
99 if !self.raster.cache.contains(handle) {
100 self.raster.cache.insert(handle, Memory::load(handle));
101 }
102
103 match self.raster.cache.get_mut(handle).unwrap() {
104 Memory::Host(image) => {
105 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
106 label: Some("raster image upload"),
107 });
108
109 let entry = self.atlas.upload(
110 device,
111 &mut encoder,
112 &mut self.raster.belt,
113 image.width(),
114 image.height(),
115 image,
116 );
117
118 self.raster.belt.finish();
119 let submission = queue.submit([encoder.finish()]);
120 self.raster.belt.recall();
121
122 let Some(entry) = entry else {
123 return Err(core::image::Error::OutOfMemory);
124 };
125
126 let _ = device.poll(wgpu::PollType::Wait {
127 submission_index: Some(submission),
128 timeout: None,
129 });
130
131 #[allow(unsafe_code)]
132 let allocation = unsafe {
133 core::image::allocate(handle, Size::new(image.width(), image.height()))
134 };
135
136 self.raster.cache.insert(
137 handle,
138 Memory::Device {
139 entry,
140 bind_group: None,
141 allocation: Some(allocation.downgrade()),
142 },
143 );
144
145 Ok(allocation)
146 }
147 Memory::Device {
148 entry, allocation, ..
149 } => {
150 if let Some(allocation) = allocation
151 .as_ref()
152 .and_then(core::image::Allocation::upgrade)
153 {
154 return Ok(allocation);
155 }
156
157 #[allow(unsafe_code)]
158 let new = unsafe { core::image::allocate(handle, entry.size()) };
159
160 *allocation = Some(new.downgrade());
161
162 Ok(new)
163 }
164 Memory::Error(error) => Err(error.clone()),
165 }
166 }
167
168 #[cfg(feature = "image")]
169 pub fn measure_image(&mut self, handle: &core::image::Handle) -> Option<Size<u32>> {
170 self.receive();
171
172 let image = load_image(
173 &mut self.raster.cache,
174 &mut self.raster.pending,
175 #[cfg(not(target_arch = "wasm32"))]
176 &self.worker,
177 handle,
178 None,
179 )?;
180
181 Some(image.dimensions())
182 }
183
184 #[cfg(feature = "svg")]
185 pub fn measure_svg(&mut self, handle: &core::svg::Handle) -> Size<u32> {
186 self.vector.load(handle).viewport_dimensions()
188 }
189
190 #[cfg(feature = "image")]
191 pub fn upload_raster(
192 &mut self,
193 device: &wgpu::Device,
194 encoder: &mut wgpu::CommandEncoder,
195 belt: &mut wgpu::util::StagingBelt,
196 handle: &core::image::Handle,
197 ) -> Option<(&atlas::Entry, &Arc<wgpu::BindGroup>)> {
198 use crate::image::raster::Memory;
199
200 self.receive();
201
202 let memory = load_image(
203 &mut self.raster.cache,
204 &mut self.raster.pending,
205 #[cfg(not(target_arch = "wasm32"))]
206 &self.worker,
207 handle,
208 None,
209 )?;
210
211 if let Memory::Device {
212 entry, bind_group, ..
213 } = memory
214 {
215 return Some((
216 entry,
217 bind_group.as_ref().unwrap_or(self.atlas.bind_group()),
218 ));
219 }
220
221 let image = memory.host()?;
222
223 const MAX_SYNC_SIZE: usize = 2 * 1024 * 1024;
224
225 if image.len() < MAX_SYNC_SIZE || cfg!(target_arch = "wasm32") {
227 let entry =
228 self.atlas
229 .upload(device, encoder, belt, image.width(), image.height(), &image)?;
230
231 *memory = Memory::Device {
232 entry,
233 bind_group: None,
234 allocation: None,
235 };
236
237 if let Memory::Device { entry, .. } = memory {
238 return Some((entry, self.atlas.bind_group()));
239 }
240 }
241
242 if !self.raster.pending.contains_key(&handle.id()) {
243 let _ = self.raster.pending.insert(handle.id(), Vec::new());
244
245 #[cfg(not(target_arch = "wasm32"))]
246 self.worker.upload(handle, image);
247 }
248
249 None
250 }
251
252 #[cfg(feature = "svg")]
253 pub fn upload_vector(
254 &mut self,
255 device: &wgpu::Device,
256 encoder: &mut wgpu::CommandEncoder,
257 belt: &mut wgpu::util::StagingBelt,
258 handle: &core::svg::Handle,
259 color: Option<core::Color>,
260 size: Size,
261 scale: f32,
262 ) -> Option<(&atlas::Entry, &Arc<wgpu::BindGroup>)> {
263 self.vector
265 .upload(
266 device,
267 encoder,
268 belt,
269 handle,
270 color,
271 size,
272 scale,
273 &mut self.atlas,
274 )
275 .map(|entry| (entry, self.atlas.bind_group()))
276 }
277
278 pub fn trim(&mut self) {
279 #[cfg(feature = "image")]
280 {
281 self.receive();
282 self.raster.cache.trim(&mut self.atlas, |_bind_group| {
283 #[cfg(not(target_arch = "wasm32"))]
284 self.worker.drop(_bind_group);
285 });
286 }
287
288 #[cfg(feature = "svg")]
289 self.vector.trim(&mut self.atlas); }
291
292 #[cfg(feature = "image")]
293 fn receive(&mut self) {
294 #[cfg(not(target_arch = "wasm32"))]
295 while let Ok(work) = self.worker.try_recv() {
296 use crate::image::raster::Memory;
297
298 match work {
299 worker::Work::Upload {
300 handle,
301 entry,
302 bind_group,
303 } => {
304 let callbacks = self.raster.pending.remove(&handle.id());
305
306 let allocation = if let Some(callbacks) = callbacks {
307 #[allow(unsafe_code)]
308 let allocation = unsafe { core::image::allocate(&handle, entry.size()) };
309
310 let reference = allocation.downgrade();
311
312 for callback in callbacks {
313 callback(Ok(allocation.clone()));
314 }
315
316 Some(reference)
317 } else {
318 None
319 };
320
321 self.raster.cache.insert(
322 &handle,
323 Memory::Device {
324 entry,
325 bind_group: Some(bind_group),
326 allocation,
327 },
328 );
329 }
330 worker::Work::Error { handle, error } => {
331 let callbacks = self.raster.pending.remove(&handle.id());
332
333 if let Some(callbacks) = callbacks {
334 for callback in callbacks {
335 callback(Err(error.clone()));
336 }
337 }
338
339 self.raster.cache.insert(&handle, Memory::Error(error));
340 }
341 }
342 }
343 }
344}
345
346#[cfg(all(feature = "image", not(target_arch = "wasm32")))]
347impl Drop for Cache {
348 fn drop(&mut self) {
349 self.worker.quit();
350 }
351}
352
353#[cfg(feature = "image")]
354struct Raster {
355 cache: crate::image::raster::Cache,
356 pending: HashMap<core::image::Id, Vec<Callback>>,
357 belt: wgpu::util::StagingBelt,
358}
359
360#[cfg(feature = "image")]
361type Callback = Box<dyn FnOnce(Result<core::image::Allocation, core::image::Error>) + Send>;
362
363#[cfg(feature = "image")]
364fn load_image<'a>(
365 cache: &'a mut crate::image::raster::Cache,
366 pending: &mut HashMap<core::image::Id, Vec<Callback>>,
367 #[cfg(not(target_arch = "wasm32"))] worker: &Worker,
368 handle: &core::image::Handle,
369 callback: Option<Callback>,
370) -> Option<&'a mut crate::image::raster::Memory> {
371 use crate::image::raster::Memory;
372
373 if !cache.contains(handle) {
374 if cfg!(target_arch = "wasm32") {
375 cache.insert(handle, Memory::load(handle));
377 } else if let core::image::Handle::Rgba { .. } = handle {
378 cache.insert(handle, Memory::load(handle));
380 } else if !pending.contains_key(&handle.id()) {
381 let _ = pending.insert(handle.id(), Vec::from_iter(callback));
382
383 #[cfg(not(target_arch = "wasm32"))]
384 worker.load(handle);
385 }
386 }
387
388 cache.get_mut(handle)
389}
390
391#[cfg(all(feature = "image", not(target_arch = "wasm32")))]
392mod worker {
393 use crate::core::Bytes;
394 use crate::core::image;
395 use crate::graphics::Shell;
396 use crate::image::atlas::{self, Atlas};
397 use crate::image::raster;
398
399 use std::sync::Arc;
400 use std::sync::mpsc;
401 use std::thread;
402
403 pub struct Worker {
404 jobs: mpsc::SyncSender<Job>,
405 quit: mpsc::SyncSender<()>,
406 work: mpsc::Receiver<Work>,
407 handle: Option<std::thread::JoinHandle<()>>,
408 }
409
410 impl Worker {
411 pub fn new(
412 device: &wgpu::Device,
413 queue: &wgpu::Queue,
414 backend: wgpu::Backend,
415 texture_layout: wgpu::BindGroupLayout,
416 shell: &Shell,
417 ) -> Self {
418 let (jobs_sender, jobs_receiver) = mpsc::sync_channel(1_000);
419 let (quit_sender, quit_receiver) = mpsc::sync_channel(1);
420 let (work_sender, work_receiver) = mpsc::sync_channel(1_000);
421
422 let instance = Instance {
423 device: device.clone(),
424 queue: queue.clone(),
425 backend,
426 texture_layout,
427 shell: shell.clone(),
428 belt: wgpu::util::StagingBelt::new(4 * 1024 * 1024),
429 jobs: jobs_receiver,
430 output: work_sender,
431 quit: quit_receiver,
432 };
433
434 let handle = thread::spawn(move || instance.run());
435
436 Self {
437 jobs: jobs_sender,
438 quit: quit_sender,
439 work: work_receiver,
440 handle: Some(handle),
441 }
442 }
443
444 pub fn load(&self, handle: &image::Handle) {
445 let _ = self.jobs.send(Job::Load(handle.clone()));
446 }
447
448 pub fn upload(&self, handle: &image::Handle, image: raster::Image) {
449 let _ = self.jobs.send(Job::Upload {
450 handle: handle.clone(),
451 width: image.width(),
452 height: image.height(),
453 rgba: image.into_raw(),
454 });
455 }
456
457 pub fn drop(&self, bind_group: Arc<wgpu::BindGroup>) {
458 let _ = self.jobs.send(Job::Drop(bind_group));
459 }
460
461 pub fn try_recv(&self) -> Result<Work, mpsc::TryRecvError> {
462 self.work.try_recv()
463 }
464
465 pub fn quit(&mut self) {
466 let _ = self.quit.try_send(());
467 let _ = self.jobs.send(Job::Quit);
468 let _ = self.handle.take().map(thread::JoinHandle::join);
469 }
470 }
471
472 pub struct Instance {
473 device: wgpu::Device,
474 queue: wgpu::Queue,
475 backend: wgpu::Backend,
476 texture_layout: wgpu::BindGroupLayout,
477 shell: Shell,
478 belt: wgpu::util::StagingBelt,
479 jobs: mpsc::Receiver<Job>,
480 output: mpsc::SyncSender<Work>,
481 quit: mpsc::Receiver<()>,
482 }
483
484 #[derive(Debug)]
485 enum Job {
486 Load(image::Handle),
487 Upload {
488 handle: image::Handle,
489 rgba: Bytes,
490 width: u32,
491 height: u32,
492 },
493 Drop(Arc<wgpu::BindGroup>),
494 Quit,
495 }
496
497 pub enum Work {
498 Upload {
499 handle: image::Handle,
500 entry: atlas::Entry,
501 bind_group: Arc<wgpu::BindGroup>,
502 },
503 Error {
504 handle: image::Handle,
505 error: image::Error,
506 },
507 }
508
509 impl Instance {
510 fn run(mut self) {
511 loop {
512 if self.quit.try_recv().is_ok() {
513 return;
514 }
515
516 let Ok(job) = self.jobs.recv() else {
517 return;
518 };
519
520 match job {
521 Job::Load(handle) => match crate::graphics::image::load(&handle) {
522 Ok(image) => self.upload(
523 handle,
524 image.width(),
525 image.height(),
526 image.into_raw(),
527 Shell::invalidate_layout,
528 ),
529 Err(error) => {
530 let _ = self.output.send(Work::Error { handle, error });
531 }
532 },
533 Job::Upload {
534 handle,
535 rgba,
536 width,
537 height,
538 } => {
539 self.upload(handle, width, height, rgba, Shell::request_redraw);
540 }
541 Job::Drop(bind_group) => {
542 drop(bind_group);
543 }
544 Job::Quit => return,
545 }
546 }
547 }
548
549 fn upload(
550 &mut self,
551 handle: image::Handle,
552 width: u32,
553 height: u32,
554 rgba: Bytes,
555 callback: fn(&Shell),
556 ) {
557 let mut encoder = self
558 .device
559 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
560 label: Some("raster image upload"),
561 });
562
563 let mut atlas = Atlas::with_size(
564 &self.device,
565 self.backend,
566 self.texture_layout.clone(),
567 width.max(height),
568 );
569
570 let Some(entry) = atlas.upload(
571 &self.device,
572 &mut encoder,
573 &mut self.belt,
574 width,
575 height,
576 &rgba,
577 ) else {
578 return;
579 };
580
581 let output = self.output.clone();
582 let shell = self.shell.clone();
583
584 self.belt.finish();
585 let submission = self.queue.submit([encoder.finish()]);
586 self.belt.recall();
587
588 let bind_group = atlas.bind_group().clone();
589
590 self.queue.on_submitted_work_done(move || {
591 let _ = output.send(Work::Upload {
592 handle,
593 entry,
594 bind_group,
595 });
596
597 callback(&shell);
598 });
599
600 let _ = self.device.poll(wgpu::PollType::Wait {
601 submission_index: Some(submission),
602 timeout: None,
603 });
604 }
605 }
606}