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
164impl TextureOptions {
165    /// Linear magnification and minification.
166    pub const LINEAR: Self = Self {
167        magnification: TextureFilter::Linear,
168        minification: TextureFilter::Linear,
169        wrap_mode: TextureWrapMode::ClampToEdge,
170    };
171
172    /// Nearest magnification and minification.
173    pub const NEAREST: Self = Self {
174        magnification: TextureFilter::Nearest,
175        minification: TextureFilter::Nearest,
176        wrap_mode: TextureWrapMode::ClampToEdge,
177    };
178
179    /// Linear magnification and minification, but with the texture repeated.
180    pub const LINEAR_REPEAT: Self = Self {
181        magnification: TextureFilter::Linear,
182        minification: TextureFilter::Linear,
183        wrap_mode: TextureWrapMode::Repeat,
184    };
185
186    /// Linear magnification and minification, but with the texture mirrored and repeated.
187    pub const LINEAR_MIRRORED_REPEAT: Self = Self {
188        magnification: TextureFilter::Linear,
189        minification: TextureFilter::Linear,
190        wrap_mode: TextureWrapMode::MirroredRepeat,
191    };
192
193    /// Nearest magnification and minification, but with the texture repeated.
194    pub const NEAREST_REPEAT: Self = Self {
195        magnification: TextureFilter::Nearest,
196        minification: TextureFilter::Nearest,
197        wrap_mode: TextureWrapMode::Repeat,
198    };
199
200    /// Nearest magnification and minification, but with the texture mirrored and repeated.
201    pub const NEAREST_MIRRORED_REPEAT: Self = Self {
202        magnification: TextureFilter::Nearest,
203        minification: TextureFilter::Nearest,
204        wrap_mode: TextureWrapMode::MirroredRepeat,
205    };
206}
207
208impl Default for TextureOptions {
209    /// The default is linear for both magnification and minification.
210    fn default() -> Self {
211        Self::LINEAR
212    }
213}
214
215/// How the texture texels are filtered.
216#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
217#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
218pub enum TextureFilter {
219    /// Show the nearest pixel value.
220    ///
221    /// When zooming in you will get sharp, square pixels/texels.
222    /// When zooming out you will get a very crisp (and aliased) look.
223    Nearest,
224
225    /// Linearly interpolate the nearest neighbors, creating a smoother look when zooming in and out.
226    Linear,
227}
228
229/// Defines how textures are wrapped around objects when texture coordinates fall outside the [0, 1] range.
230#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
231#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
232pub enum TextureWrapMode {
233    /// Stretches the edge pixels to fill beyond the texture's bounds.
234    ///
235    /// This is what you want to use for a normal image in a GUI.
236    #[default]
237    ClampToEdge,
238
239    /// Tiles the texture across the surface, repeating it horizontally and vertically.
240    Repeat,
241
242    /// Mirrors the texture with each repetition, creating symmetrical tiling.
243    MirroredRepeat,
244}
245
246// ----------------------------------------------------------------------------
247
248/// What has been allocated and freed during the last period.
249///
250/// These are commands given to the integration painter.
251#[derive(Clone, Default, PartialEq)]
252#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
253#[must_use = "The painter must take care of this"]
254pub struct TexturesDelta {
255    /// New or changed textures. Apply before painting.
256    pub set: Vec<(TextureId, ImageDelta)>,
257
258    /// Textures to free after painting.
259    pub free: Vec<TextureId>,
260}
261
262impl TexturesDelta {
263    pub fn is_empty(&self) -> bool {
264        self.set.is_empty() && self.free.is_empty()
265    }
266
267    pub fn append(&mut self, mut newer: Self) {
268        self.set.extend(newer.set);
269        self.free.append(&mut newer.free);
270    }
271
272    pub fn clear(&mut self) {
273        self.set.clear();
274        self.free.clear();
275    }
276}
277
278impl std::fmt::Debug for TexturesDelta {
279    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
280        use std::fmt::Write as _;
281
282        let mut debug_struct = f.debug_struct("TexturesDelta");
283        if !self.set.is_empty() {
284            let mut string = String::new();
285            for (tex_id, delta) in &self.set {
286                let size = delta.image.size();
287                if let Some(pos) = delta.pos {
288                    write!(
289                        string,
290                        "{:?} partial ([{} {}] - [{} {}]), ",
291                        tex_id,
292                        pos[0],
293                        pos[1],
294                        pos[0] + size[0],
295                        pos[1] + size[1]
296                    )
297                    .ok();
298                } else {
299                    write!(string, "{:?} full {}x{}, ", tex_id, size[0], size[1]).ok();
300                }
301            }
302            debug_struct.field("set", &string);
303        }
304        if !self.free.is_empty() {
305            debug_struct.field("free", &self.free);
306        }
307        debug_struct.finish()
308    }
309}