1use crate::{ImageData, ImageDelta, TextureId};
2use ahash::{HashMap, HashSet};
3use smallvec::{SmallVec, smallvec};
4use std::mem;
5
6#[derive(Default)]
12pub struct TextureManager {
13 next_id: u64,
15
16 metas: ahash::HashMap<TextureId, TextureMeta>,
18
19 delta: TexturesDelta,
20}
21
22impl TextureManager {
23 pub fn alloc(&mut self, name: String, image: ImageData, options: TextureOptions) -> TextureId {
35 let id = TextureId::Managed(self.next_id);
36 self.next_id += 1;
37
38 self.metas.entry(id).or_insert_with(|| TextureMeta {
39 name,
40 size: image.size(),
41 bytes_per_pixel: image.bytes_per_pixel(),
42 retain_count: 1,
43 options,
44 });
45
46 self.delta.push(id, ImageDelta::full(image, options));
47 id
48 }
49
50 pub fn set(&mut self, id: TextureId, delta: ImageDelta) {
53 if let Some(meta) = self.metas.get_mut(&id) {
54 if let Some(pos) = delta.pos {
55 debug_assert!(
56 pos[0] + delta.image.width() <= meta.size[0]
57 && pos[1] + delta.image.height() <= meta.size[1],
58 "Partial texture update is outside the bounds of texture {id:?}",
59 );
60 } else {
61 meta.size = delta.image.size();
63 meta.bytes_per_pixel = delta.image.bytes_per_pixel();
64 }
65 self.delta.push(id, delta);
66 } else {
67 debug_assert!(false, "Tried setting texture {id:?} which is not allocated");
68 }
69 }
70
71 pub fn free(&mut self, id: TextureId) {
73 if let std::collections::hash_map::Entry::Occupied(mut entry) = self.metas.entry(id) {
74 let meta = entry.get_mut();
75 meta.retain_count -= 1;
76 if meta.retain_count == 0 {
77 entry.remove();
78 self.delta.free(id);
79 }
80 } else {
81 debug_assert!(false, "Tried freeing texture {id:?} which is not allocated");
82 }
83 }
84
85 pub fn retain(&mut self, id: TextureId) {
89 if let Some(meta) = self.metas.get_mut(&id) {
90 meta.retain_count += 1;
91 } else {
92 debug_assert!(
93 false,
94 "Tried retaining texture {id:?} which is not allocated",
95 );
96 }
97 }
98
99 pub fn take_delta(&mut self) -> TexturesDelta {
103 std::mem::take(&mut self.delta)
104 }
105
106 pub fn meta(&self, id: TextureId) -> Option<&TextureMeta> {
108 self.metas.get(&id)
109 }
110
111 pub fn allocated(&self) -> impl ExactSizeIterator<Item = (&TextureId, &TextureMeta)> {
113 self.metas.iter()
114 }
115
116 pub fn num_allocated(&self) -> usize {
118 self.metas.len()
119 }
120}
121
122impl Drop for TextureManager {
123 fn drop(&mut self) {
124 self.delta.clear(); }
126}
127
128#[derive(Clone, Debug, PartialEq, Eq)]
130pub struct TextureMeta {
131 pub name: String,
133
134 pub size: [usize; 2],
136
137 pub bytes_per_pixel: usize,
139
140 pub retain_count: usize,
142
143 pub options: TextureOptions,
145}
146
147impl TextureMeta {
148 pub fn bytes_used(&self) -> usize {
151 self.size[0] * self.size[1] * self.bytes_per_pixel
152 }
153}
154
155#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
159#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
160pub struct TextureOptions {
161 pub magnification: TextureFilter,
163
164 pub minification: TextureFilter,
166
167 pub wrap_mode: TextureWrapMode,
169
170 pub mipmap_mode: Option<TextureFilter>,
179}
180
181impl TextureOptions {
182 pub const LINEAR: Self = Self {
184 magnification: TextureFilter::Linear,
185 minification: TextureFilter::Linear,
186 wrap_mode: TextureWrapMode::ClampToEdge,
187 mipmap_mode: None,
188 };
189
190 pub const NEAREST: Self = Self {
192 magnification: TextureFilter::Nearest,
193 minification: TextureFilter::Nearest,
194 wrap_mode: TextureWrapMode::ClampToEdge,
195 mipmap_mode: None,
196 };
197
198 pub const LINEAR_REPEAT: Self = Self {
200 magnification: TextureFilter::Linear,
201 minification: TextureFilter::Linear,
202 wrap_mode: TextureWrapMode::Repeat,
203 mipmap_mode: None,
204 };
205
206 pub const LINEAR_MIRRORED_REPEAT: Self = Self {
208 magnification: TextureFilter::Linear,
209 minification: TextureFilter::Linear,
210 wrap_mode: TextureWrapMode::MirroredRepeat,
211 mipmap_mode: None,
212 };
213
214 pub const NEAREST_REPEAT: Self = Self {
216 magnification: TextureFilter::Nearest,
217 minification: TextureFilter::Nearest,
218 wrap_mode: TextureWrapMode::Repeat,
219 mipmap_mode: None,
220 };
221
222 pub const NEAREST_MIRRORED_REPEAT: Self = Self {
224 magnification: TextureFilter::Nearest,
225 minification: TextureFilter::Nearest,
226 wrap_mode: TextureWrapMode::MirroredRepeat,
227 mipmap_mode: None,
228 };
229
230 pub const fn with_mipmap_mode(self, mipmap_mode: Option<TextureFilter>) -> Self {
231 Self {
232 mipmap_mode,
233 ..self
234 }
235 }
236}
237
238impl Default for TextureOptions {
239 fn default() -> Self {
241 Self::LINEAR
242 }
243}
244
245#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
247#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
248pub enum TextureFilter {
249 Nearest,
254
255 Linear,
257}
258
259#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
261#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
262pub enum TextureWrapMode {
263 #[default]
267 ClampToEdge,
268
269 Repeat,
271
272 MirroredRepeat,
274}
275
276#[derive(Clone, Default, PartialEq, Eq)]
282#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
283#[must_use = "The painter must take care of this"]
284pub struct TexturesDelta {
285 pub set: HashMap<TextureId, SmallVec<[ImageDelta; 1]>>,
287
288 pub free: HashSet<TextureId>,
290}
291
292impl TexturesDelta {
293 pub fn is_empty(&self) -> bool {
294 self.set.is_empty() && self.free.is_empty()
295 }
296
297 pub fn push(&mut self, id: TextureId, delta: ImageDelta) {
302 if delta.is_whole() {
303 self.set.insert(id, smallvec![delta]);
305 } else {
306 self.set.entry(id).or_default().push(delta);
307 }
308 }
309
310 pub fn free(&mut self, id: TextureId) {
311 self.free.insert(id);
312 }
313
314 #[expect(clippy::iter_over_hash_type)]
315 pub fn append(&mut self, mut newer: Self) {
316 for id in &newer.free {
319 self.set.remove(id);
320 }
321 for (id, deltas) in newer.set.drain() {
322 for delta in deltas {
323 self.push(id, delta);
324 }
325 }
326 self.free.extend(mem::take(&mut newer.free));
327 }
328
329 pub fn clear(&mut self) {
330 self.set.clear();
331 self.free.clear();
332 }
333}
334
335impl Drop for TexturesDelta {
336 fn drop(&mut self) {
337 debug_assert!(
338 self.is_empty(),
339 "Dropped TexturesDelta with {} unapplied deltas. Deltas need to be handled. \
340 If you want to drop this intentionally call `clear` before dropping.",
341 self.free.len() + self.set.len()
342 );
343 }
344}
345
346impl std::fmt::Debug for TexturesDelta {
347 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
348 use std::fmt::Write as _;
349
350 let mut debug_struct = f.debug_struct("TexturesDelta");
351 if !self.set.is_empty() {
352 let mut string = String::new();
353 #[expect(clippy::iter_over_hash_type)]
354 for (tex_id, deltas) in &self.set {
355 for delta in deltas {
356 let size = delta.image.size();
357 if let Some(pos) = delta.pos {
358 write!(
359 string,
360 "{:?} partial ([{} {}] - [{} {}]), ",
361 tex_id,
362 pos[0],
363 pos[1],
364 pos[0] + size[0],
365 pos[1] + size[1]
366 )
367 .ok();
368 } else {
369 write!(string, "{:?} full {}x{}, ", tex_id, size[0], size[1]).ok();
370 }
371 }
372 }
373 debug_struct.field("set", &string);
374 }
375 if !self.free.is_empty() {
376 debug_struct.field("free", &self.free);
377 }
378 debug_struct.finish()
379 }
380}