1#![allow(dead_code)]
5
6pub enum TextureFormat {
10 Rgba8,
11 Rgba16Float,
12 R8,
13 Rg8,
14 Bc1,
15 Bc3,
16 Bc7,
17}
18
19pub enum TextureFilter {
21 Nearest,
22 Linear,
23 Anisotropic(u8),
24}
25
26pub enum TextureWrap {
28 Clamp,
29 Repeat,
30 Mirror,
31}
32
33pub struct TextureDescriptor {
37 pub label: String,
38 pub width: u32,
39 pub height: u32,
40 pub mip_levels: u32,
41 pub format: TextureFormat,
42 pub filter: TextureFilter,
43 pub wrap_u: TextureWrap,
44 pub wrap_v: TextureWrap,
45}
46
47pub struct TextureEntry {
49 pub id: u32,
50 pub descriptor: TextureDescriptor,
51 pub data: Option<Vec<u8>>,
53 pub loaded: bool,
54}
55
56pub struct TextureCache {
60 entries: Vec<TextureEntry>,
61 next_id: u32,
62}
63
64impl TextureCache {
65 pub fn new() -> Self {
67 Self {
68 entries: Vec::new(),
69 next_id: 0,
70 }
71 }
72
73 pub fn insert(&mut self, desc: TextureDescriptor, data: Option<Vec<u8>>) -> u32 {
75 let id = self.next_id;
76 self.next_id += 1;
77 let loaded = data.is_some();
78 self.entries.push(TextureEntry {
79 id,
80 descriptor: desc,
81 data,
82 loaded,
83 });
84 id
85 }
86
87 pub fn get(&self, id: u32) -> Option<&TextureEntry> {
89 self.entries.iter().find(|e| e.id == id)
90 }
91
92 pub fn remove(&mut self, id: u32) -> bool {
94 if let Some(pos) = self.entries.iter().position(|e| e.id == id) {
95 self.entries.remove(pos);
96 true
97 } else {
98 false
99 }
100 }
101
102 pub fn count(&self) -> usize {
104 self.entries.len()
105 }
106
107 pub fn loaded_count(&self) -> usize {
109 self.entries.iter().filter(|e| e.loaded).count()
110 }
111
112 pub fn mark_loaded(&mut self, id: u32, data: Vec<u8>) -> bool {
115 if let Some(entry) = self.entries.iter_mut().find(|e| e.id == id) {
116 entry.data = Some(data);
117 entry.loaded = true;
118 true
119 } else {
120 false
121 }
122 }
123
124 pub fn evict_all(&mut self) {
126 for entry in &mut self.entries {
127 entry.data = None;
128 entry.loaded = false;
129 }
130 }
131
132 pub fn total_memory_bytes(&self) -> usize {
134 self.entries
135 .iter()
136 .filter(|e| e.loaded)
137 .map(|e| texture_memory_bytes(&e.descriptor))
138 .sum()
139 }
140}
141
142impl Default for TextureCache {
143 fn default() -> Self {
144 Self::new()
145 }
146}
147
148pub fn texture_format_bytes(fmt: &TextureFormat) -> u32 {
152 match fmt {
153 TextureFormat::R8 => 1,
154 TextureFormat::Rg8 => 2,
155 TextureFormat::Rgba8 => 4,
156 TextureFormat::Rgba16Float => 8,
157 TextureFormat::Bc1 => 0, TextureFormat::Bc3 => 1,
161 TextureFormat::Bc7 => 1,
162 }
163}
164
165pub fn texture_memory_bytes(desc: &TextureDescriptor) -> usize {
167 let pixels = desc.width as usize * desc.height as usize;
168 match &desc.format {
169 TextureFormat::Bc1 => {
170 pixels / 2
172 }
173 other => pixels * texture_format_bytes(other) as usize,
174 }
175}
176
177pub fn default_placeholder_texture() -> TextureEntry {
179 let desc = TextureDescriptor {
180 label: "placeholder".to_string(),
181 width: 1,
182 height: 1,
183 mip_levels: 1,
184 format: TextureFormat::Rgba8,
185 filter: TextureFilter::Nearest,
186 wrap_u: TextureWrap::Clamp,
187 wrap_v: TextureWrap::Clamp,
188 };
189 let data = vec![255u8, 105, 180, 255];
191 TextureEntry {
192 id: u32::MAX,
193 descriptor: desc,
194 data: Some(data),
195 loaded: true,
196 }
197}
198
199#[cfg(test)]
202mod tests {
203 use super::*;
204
205 fn rgba8_desc(label: &str, w: u32, h: u32) -> TextureDescriptor {
206 TextureDescriptor {
207 label: label.to_string(),
208 width: w,
209 height: h,
210 mip_levels: 1,
211 format: TextureFormat::Rgba8,
212 filter: TextureFilter::Linear,
213 wrap_u: TextureWrap::Repeat,
214 wrap_v: TextureWrap::Repeat,
215 }
216 }
217
218 #[test]
219 fn new_cache_is_empty() {
220 let cache = TextureCache::new();
221 assert_eq!(cache.count(), 0);
222 assert_eq!(cache.loaded_count(), 0);
223 }
224
225 #[test]
226 fn insert_returns_incrementing_ids() {
227 let mut cache = TextureCache::new();
228 let id0 = cache.insert(rgba8_desc("a", 4, 4), None);
229 let id1 = cache.insert(rgba8_desc("b", 4, 4), None);
230 assert_eq!(id0, 0);
231 assert_eq!(id1, 1);
232 }
233
234 #[test]
235 fn get_found() {
236 let mut cache = TextureCache::new();
237 let id = cache.insert(rgba8_desc("tex", 8, 8), None);
238 let entry = cache.get(id);
239 assert!(entry.is_some());
240 assert_eq!(entry.expect("should succeed").id, id);
241 }
242
243 #[test]
244 fn get_not_found() {
245 let cache = TextureCache::new();
246 assert!(cache.get(999).is_none());
247 }
248
249 #[test]
250 fn remove_true_when_present() {
251 let mut cache = TextureCache::new();
252 let id = cache.insert(rgba8_desc("x", 2, 2), None);
253 assert!(cache.remove(id));
254 assert_eq!(cache.count(), 0);
255 }
256
257 #[test]
258 fn remove_false_when_absent() {
259 let mut cache = TextureCache::new();
260 assert!(!cache.remove(42));
261 }
262
263 #[test]
264 fn count_and_loaded_count() {
265 let mut cache = TextureCache::new();
266 cache.insert(rgba8_desc("a", 4, 4), None);
267 cache.insert(rgba8_desc("b", 4, 4), Some(vec![0u8; 64]));
268 assert_eq!(cache.count(), 2);
269 assert_eq!(cache.loaded_count(), 1);
270 }
271
272 #[test]
273 fn mark_loaded_sets_data_and_flag() {
274 let mut cache = TextureCache::new();
275 let id = cache.insert(rgba8_desc("stream", 4, 4), None);
276 assert_eq!(cache.loaded_count(), 0);
277 let ok = cache.mark_loaded(id, vec![0u8; 64]);
278 assert!(ok);
279 assert_eq!(cache.loaded_count(), 1);
280 assert!(cache.get(id).expect("should succeed").data.is_some());
281 }
282
283 #[test]
284 fn mark_loaded_returns_false_for_missing_id() {
285 let mut cache = TextureCache::new();
286 assert!(!cache.mark_loaded(999, vec![0u8; 4]));
287 }
288
289 #[test]
290 fn evict_all_clears_data_and_loaded_flag() {
291 let mut cache = TextureCache::new();
292 cache.insert(rgba8_desc("t", 2, 2), Some(vec![0u8; 16]));
293 cache.insert(rgba8_desc("u", 2, 2), Some(vec![0u8; 16]));
294 assert_eq!(cache.loaded_count(), 2);
295 cache.evict_all();
296 assert_eq!(cache.loaded_count(), 0);
297 assert_eq!(cache.count(), 2); for entry in &cache.entries {
299 assert!(entry.data.is_none());
300 }
301 }
302
303 #[test]
304 fn texture_format_bytes_rgba8_is_4() {
305 assert_eq!(texture_format_bytes(&TextureFormat::Rgba8), 4);
306 }
307
308 #[test]
309 fn texture_format_bytes_r8_is_1() {
310 assert_eq!(texture_format_bytes(&TextureFormat::R8), 1);
311 }
312
313 #[test]
314 fn texture_format_bytes_rgba16float_is_8() {
315 assert_eq!(texture_format_bytes(&TextureFormat::Rgba16Float), 8);
316 }
317
318 #[test]
319 fn texture_memory_bytes_rgba8_formula() {
320 let desc = rgba8_desc("t", 4, 4);
321 assert_eq!(texture_memory_bytes(&desc), 64);
323 }
324
325 #[test]
326 fn texture_memory_bytes_bc1_half_pixel() {
327 let desc = TextureDescriptor {
328 label: "bc".to_string(),
329 width: 4,
330 height: 4,
331 mip_levels: 1,
332 format: TextureFormat::Bc1,
333 filter: TextureFilter::Linear,
334 wrap_u: TextureWrap::Clamp,
335 wrap_v: TextureWrap::Clamp,
336 };
337 assert_eq!(texture_memory_bytes(&desc), 8);
339 }
340
341 #[test]
342 fn total_memory_bytes_sum_of_loaded() {
343 let mut cache = TextureCache::new();
344 cache.insert(rgba8_desc("a", 4, 4), Some(vec![0u8; 64]));
346 cache.insert(rgba8_desc("b", 4, 4), Some(vec![0u8; 64]));
347 cache.insert(rgba8_desc("c", 4, 4), None);
349 assert_eq!(cache.total_memory_bytes(), 128);
350 }
351
352 #[test]
353 fn default_placeholder_texture_non_null() {
354 let entry = default_placeholder_texture();
355 assert!(entry.data.is_some());
356 let data = entry.data.expect("should succeed");
357 assert_eq!(data.len(), 4);
358 assert_eq!(data[0], 255);
360 assert_eq!(data[2], 180);
361 }
362}