Skip to main content

egui/load/
texture_loader.rs

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