epaint/
textures.rs

1use crate::{ImageData, ImageDelta, TextureId};
2
3// ----------------------------------------------------------------------------
4
5/// Low-level manager for allocating textures.
6///
7/// Communicates with the painting subsystem using [`Self::take_delta`].
8#[derive(Default)]
9pub struct TextureManager {
10    /// We allocate texture id:s linearly.
11    next_id: u64,
12
13    /// Information about currently allocated textures.
14    metas: ahash::HashMap<TextureId, TextureMeta>,
15
16    delta: TexturesDelta,
17}
18
19impl TextureManager {
20    /// Allocate a new texture.
21    ///
22    /// The given name can be useful for later debugging.
23    ///
24    /// The returned [`TextureId`] will be [`TextureId::Managed`], with an index
25    /// starting from zero and increasing with each call to [`Self::alloc`].
26    ///
27    /// The first texture you allocate will be `TextureId::Managed(0) == TextureId::default()` and
28    /// MUST have a white pixel at (0,0) ([`crate::WHITE_UV`]).
29    ///
30    /// The texture is given a retain-count of `1`, requiring one call to [`Self::free`] to free it.
31    pub fn alloc(&mut self, name: String, image: ImageData, options: TextureOptions) -> TextureId {
32        let id = TextureId::Managed(self.next_id);
33        self.next_id += 1;
34
35        self.metas.entry(id).or_insert_with(|| TextureMeta {
36            name,
37            size: image.size(),
38            bytes_per_pixel: image.bytes_per_pixel(),
39            retain_count: 1,
40            options,
41        });
42
43        self.delta.set.push((id, ImageDelta::full(image, options)));
44        id
45    }
46
47    /// Assign a new image to an existing texture,
48    /// or update a region of it.
49    pub fn set(&mut self, id: TextureId, delta: ImageDelta) {
50        if let Some(meta) = self.metas.get_mut(&id) {
51            if let Some(pos) = delta.pos {
52                debug_assert!(
53                    pos[0] + delta.image.width() <= meta.size[0]
54                        && pos[1] + delta.image.height() <= meta.size[1],
55                    "Partial texture update is outside the bounds of texture {id:?}",
56                );
57            } else {
58                // whole update
59                meta.size = delta.image.size();
60                meta.bytes_per_pixel = delta.image.bytes_per_pixel();
61                // since we update the whole image, we can discard all old enqueued deltas
62                self.delta.set.retain(|(x, _)| x != &id);
63            }
64            self.delta.set.push((id, delta));
65        } else {
66            debug_assert!(false, "Tried setting texture {id:?} which is not allocated");
67        }
68    }
69
70    /// Free an existing texture.
71    pub fn free(&mut self, id: TextureId) {
72        if let std::collections::hash_map::Entry::Occupied(mut entry) = self.metas.entry(id) {
73            let meta = entry.get_mut();
74            meta.retain_count -= 1;
75            if meta.retain_count == 0 {
76                entry.remove();
77                self.delta.free.push(id);
78            }
79        } else {
80            debug_assert!(false, "Tried freeing texture {id:?} which is not allocated");
81        }
82    }
83
84    /// Increase the retain-count of the given texture.
85    ///
86    /// For each time you call [`Self::retain`] you must call [`Self::free`] on additional time.
87    pub fn retain(&mut self, id: TextureId) {
88        if let Some(meta) = self.metas.get_mut(&id) {
89            meta.retain_count += 1;
90        } else {
91            debug_assert!(
92                false,
93                "Tried retaining texture {id:?} which is not allocated",
94            );
95        }
96    }
97
98    /// Take and reset changes since last frame.
99    ///
100    /// These should be applied to the painting subsystem each frame.
101    pub fn take_delta(&mut self) -> TexturesDelta {
102        std::mem::take(&mut self.delta)
103    }
104
105    /// Get meta-data about a specific texture.
106    pub fn meta(&self, id: TextureId) -> Option<&TextureMeta> {
107        self.metas.get(&id)
108    }
109
110    /// Get meta-data about all allocated textures in some arbitrary order.
111    pub fn allocated(&self) -> impl ExactSizeIterator<Item = (&TextureId, &TextureMeta)> {
112        self.metas.iter()
113    }
114
115    /// Total number of allocated textures.
116    pub fn num_allocated(&self) -> usize {
117        self.metas.len()
118    }
119}
120
121/// Meta-data about an allocated texture.
122#[derive(Clone, Debug, PartialEq, Eq)]
123pub struct TextureMeta {
124    /// A human-readable name useful for debugging.
125    pub name: String,
126
127    /// width x height
128    pub size: [usize; 2],
129
130    /// 4 or 1
131    pub bytes_per_pixel: usize,
132
133    /// Free when this reaches zero.
134    pub retain_count: usize,
135
136    /// The texture filtering mode to use when rendering.
137    pub options: TextureOptions,
138}
139
140impl TextureMeta {
141    /// Size in bytes.
142    /// width x height x [`Self::bytes_per_pixel`].
143    pub fn bytes_used(&self) -> usize {
144        self.size[0] * self.size[1] * self.bytes_per_pixel
145    }
146}
147
148// ----------------------------------------------------------------------------
149
150/// How the texture texels are filtered.
151#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
152#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
153pub struct TextureOptions {
154    /// How to filter when magnifying (when texels are larger than pixels).
155    pub magnification: TextureFilter,
156
157    /// How to filter when minifying (when texels are smaller than pixels).
158    pub minification: TextureFilter,
159
160    /// How to wrap the texture when the texture coordinates are outside the [0, 1] range.
161    pub wrap_mode: TextureWrapMode,
162
163    /// How to filter between texture mipmaps.
164    ///
165    /// Mipmaps ensures textures look smooth even when the texture is very small and pixels are much
166    /// larger than individual texels.
167    ///
168    /// # Notes
169    ///
170    /// - This may not be available on all backends (currently only `egui_glow`).
171    pub mipmap_mode: Option<TextureFilter>,
172}
173
174impl TextureOptions {
175    /// Linear magnification and minification.
176    pub const LINEAR: Self = Self {
177        magnification: TextureFilter::Linear,
178        minification: TextureFilter::Linear,
179        wrap_mode: TextureWrapMode::ClampToEdge,
180        mipmap_mode: None,
181    };
182
183    /// Nearest magnification and minification.
184    pub const NEAREST: Self = Self {
185        magnification: TextureFilter::Nearest,
186        minification: TextureFilter::Nearest,
187        wrap_mode: TextureWrapMode::ClampToEdge,
188        mipmap_mode: None,
189    };
190
191    /// Linear magnification and minification, but with the texture repeated.
192    pub const LINEAR_REPEAT: Self = Self {
193        magnification: TextureFilter::Linear,
194        minification: TextureFilter::Linear,
195        wrap_mode: TextureWrapMode::Repeat,
196        mipmap_mode: None,
197    };
198
199    /// Linear magnification and minification, but with the texture mirrored and repeated.
200    pub const LINEAR_MIRRORED_REPEAT: Self = Self {
201        magnification: TextureFilter::Linear,
202        minification: TextureFilter::Linear,
203        wrap_mode: TextureWrapMode::MirroredRepeat,
204        mipmap_mode: None,
205    };
206
207    /// Nearest magnification and minification, but with the texture repeated.
208    pub const NEAREST_REPEAT: Self = Self {
209        magnification: TextureFilter::Nearest,
210        minification: TextureFilter::Nearest,
211        wrap_mode: TextureWrapMode::Repeat,
212        mipmap_mode: None,
213    };
214
215    /// Nearest magnification and minification, but with the texture mirrored and repeated.
216    pub const NEAREST_MIRRORED_REPEAT: Self = Self {
217        magnification: TextureFilter::Nearest,
218        minification: TextureFilter::Nearest,
219        wrap_mode: TextureWrapMode::MirroredRepeat,
220        mipmap_mode: None,
221    };
222
223    pub const fn with_mipmap_mode(self, mipmap_mode: Option<TextureFilter>) -> Self {
224        Self {
225            mipmap_mode,
226            ..self
227        }
228    }
229}
230
231impl Default for TextureOptions {
232    /// The default is linear for both magnification and minification.
233    fn default() -> Self {
234        Self::LINEAR
235    }
236}
237
238/// How the texture texels are filtered.
239#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
240#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
241pub enum TextureFilter {
242    /// Show the nearest pixel value.
243    ///
244    /// When zooming in you will get sharp, square pixels/texels.
245    /// When zooming out you will get a very crisp (and aliased) look.
246    Nearest,
247
248    /// Linearly interpolate the nearest neighbors, creating a smoother look when zooming in and out.
249    Linear,
250}
251
252/// Defines how textures are wrapped around objects when texture coordinates fall outside the [0, 1] range.
253#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
254#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
255pub enum TextureWrapMode {
256    /// Stretches the edge pixels to fill beyond the texture's bounds.
257    ///
258    /// This is what you want to use for a normal image in a GUI.
259    #[default]
260    ClampToEdge,
261
262    /// Tiles the texture across the surface, repeating it horizontally and vertically.
263    Repeat,
264
265    /// Mirrors the texture with each repetition, creating symmetrical tiling.
266    MirroredRepeat,
267}
268
269// ----------------------------------------------------------------------------
270
271/// What has been allocated and freed during the last period.
272///
273/// These are commands given to the integration painter.
274#[derive(Clone, Default, PartialEq)]
275#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
276#[must_use = "The painter must take care of this"]
277pub struct TexturesDelta {
278    /// New or changed textures. Apply before painting.
279    pub set: Vec<(TextureId, ImageDelta)>,
280
281    /// Textures to free after painting.
282    pub free: Vec<TextureId>,
283}
284
285impl TexturesDelta {
286    pub fn is_empty(&self) -> bool {
287        self.set.is_empty() && self.free.is_empty()
288    }
289
290    pub fn append(&mut self, mut newer: Self) {
291        self.set.extend(newer.set);
292        self.free.append(&mut newer.free);
293    }
294
295    pub fn clear(&mut self) {
296        self.set.clear();
297        self.free.clear();
298    }
299}
300
301impl std::fmt::Debug for TexturesDelta {
302    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
303        use std::fmt::Write as _;
304
305        let mut debug_struct = f.debug_struct("TexturesDelta");
306        if !self.set.is_empty() {
307            let mut string = String::new();
308            for (tex_id, delta) in &self.set {
309                let size = delta.image.size();
310                if let Some(pos) = delta.pos {
311                    write!(
312                        string,
313                        "{:?} partial ([{} {}] - [{} {}]), ",
314                        tex_id,
315                        pos[0],
316                        pos[1],
317                        pos[0] + size[0],
318                        pos[1] + size[1]
319                    )
320                    .ok();
321                } else {
322                    write!(string, "{:?} full {}x{}, ", tex_id, size[0], size[1]).ok();
323                }
324            }
325            debug_struct.field("set", &string);
326        }
327        if !self.free.is_empty() {
328            debug_struct.field("free", &self.free);
329        }
330        debug_struct.finish()
331    }
332}