egui/load/
texture_loader.rs1use 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
16type Bucket = HashMap<Option<SizeHint>, Entry>;
18
19struct Entry {
20 last_used: AtomicU64,
21
22 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 Some(size_hint)
53 } else {
54 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 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}