iced_wgpu/image/
cache.rs

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        // TODO: Concurrency
187        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        // TODO: Concurrent Wasm support
226        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        // TODO: Concurrency
264        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); // TODO: Concurrency
290    }
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            // TODO: Concurrent support for Wasm
376            cache.insert(handle, Memory::load(handle));
377        } else if let core::image::Handle::Rgba { .. } = handle {
378            // Load RGBA handles synchronously, since it's very cheap
379            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}