egui/load/
texture_loader.rs

1use std::sync::atomic::{AtomicU64, Ordering::Relaxed};
2
3use emath::Vec2;
4
5use super::{
6    BytesLoader as _, Context, HashMap, ImagePoll, Mutex, SizeHint, SizedTexture, TextureHandle,
7    TextureLoadResult, TextureLoader, TextureOptions, TexturePoll,
8};
9
10#[derive(Clone, Debug, PartialEq, Eq, Hash)]
11struct PrimaryKey {
12    uri: String,
13    texture_options: TextureOptions,
14}
15
16/// SVG:s might have several different sizes loaded
17type Bucket = HashMap<Option<SizeHint>, Entry>;
18
19struct Entry {
20    last_used: AtomicU64,
21
22    /// Size of the original SVG, if any, or the texel size of the image if not an SVG.
23    source_size: Vec2,
24
25    handle: TextureHandle,
26}
27
28#[derive(Default)]
29pub struct DefaultTextureLoader {
30    pass_index: AtomicU64,
31    cache: Mutex<HashMap<PrimaryKey, Bucket>>,
32}
33
34impl TextureLoader for DefaultTextureLoader {
35    fn id(&self) -> &'static str {
36        crate::generate_loader_id!(DefaultTextureLoader)
37    }
38
39    fn load(
40        &self,
41        ctx: &Context,
42        uri: &str,
43        texture_options: TextureOptions,
44        size_hint: SizeHint,
45    ) -> TextureLoadResult {
46        let svg_size_hint = if is_svg(uri) {
47            // For SVGs it's important that we render at the desired size,
48            // or we might get a blurry image when we scale it up.
49            // So we make the size hint a part of the cache key.
50            // This might lead to a lot of extra entries for the same SVG file,
51            // which is potentially wasteful of RAM, but better that than blurry images.
52            Some(size_hint)
53        } else {
54            // For other images we just use one cache value, no matter what the size we render at.
55            None
56        };
57
58        let mut cache = self.cache.lock();
59        let bucket = cache
60            .entry(PrimaryKey {
61                uri: uri.to_owned(),
62                texture_options,
63            })
64            .or_default();
65
66        if let Some(texture) = bucket.get(&svg_size_hint) {
67            texture
68                .last_used
69                .store(self.pass_index.load(Relaxed), Relaxed);
70            let texture = SizedTexture::new(texture.handle.id(), texture.source_size);
71            Ok(TexturePoll::Ready { texture })
72        } else {
73            match ctx.try_load_image(uri, size_hint)? {
74                ImagePoll::Pending { size } => Ok(TexturePoll::Pending { size }),
75                ImagePoll::Ready { image } => {
76                    let source_size = image.source_size;
77                    let handle = ctx.load_texture(uri, image, texture_options);
78                    let texture = SizedTexture::new(handle.id(), source_size);
79                    bucket.insert(
80                        svg_size_hint,
81                        Entry {
82                            last_used: AtomicU64::new(self.pass_index.load(Relaxed)),
83                            source_size,
84                            handle,
85                        },
86                    );
87                    let reduce_texture_memory = ctx.options(|o| o.reduce_texture_memory);
88                    if reduce_texture_memory {
89                        let loaders = ctx.loaders();
90                        loaders.include.forget(uri);
91                        for loader in loaders.bytes.lock().iter().rev() {
92                            loader.forget(uri);
93                        }
94                        for loader in loaders.image.lock().iter().rev() {
95                            loader.forget(uri);
96                        }
97                    }
98                    Ok(TexturePoll::Ready { texture })
99                }
100            }
101        }
102    }
103
104    fn forget(&self, uri: &str) {
105        #[cfg(feature = "log")]
106        log::trace!("forget {uri:?}");
107
108        self.cache.lock().retain(|key, _value| key.uri != uri);
109    }
110
111    fn forget_all(&self) {
112        #[cfg(feature = "log")]
113        log::trace!("forget all");
114
115        self.cache.lock().clear();
116    }
117
118    fn end_pass(&self, pass_index: u64) {
119        self.pass_index.store(pass_index, Relaxed);
120        let mut cache = self.cache.lock();
121        cache.retain(|_key, bucket| {
122            if 2 <= bucket.len() {
123                // There are multiple textures of the same URI (e.g. SVGs of different scales).
124                // This could be because someone has an SVG in a resizable container,
125                // and so we get a lot of different sizes of it.
126                // This could wast VRAM, so we remove the ones that are not used in this frame.
127                bucket.retain(|_, texture| pass_index <= texture.last_used.load(Relaxed) + 1);
128            }
129            !bucket.is_empty()
130        });
131    }
132
133    fn byte_size(&self) -> usize {
134        self.cache
135            .lock()
136            .values()
137            .map(|bucket| {
138                bucket
139                    .values()
140                    .map(|texture| texture.handle.byte_size())
141                    .sum::<usize>()
142            })
143            .sum()
144    }
145}
146
147fn is_svg(uri: &str) -> bool {
148    uri.ends_with(".svg")
149}