1#![allow(dead_code)]
8
9use std::collections::HashMap;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
14pub enum FilterMode {
15 Nearest,
17 #[default]
19 Linear,
20 NearestMipmapNearest,
22 LinearMipmapLinear,
24}
25
26impl FilterMode {
27 #[must_use]
29 pub const fn uses_mipmaps(self) -> bool {
30 matches!(self, Self::NearestMipmapNearest | Self::LinearMipmapLinear)
31 }
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
36pub enum WrapMode {
37 Repeat,
39 MirrorRepeat,
41 #[default]
43 ClampToEdge,
44 ClampToBorder,
46}
47
48pub type LodBias = f32;
52
53#[derive(Debug, Clone, PartialEq)]
58pub struct SamplerConfig {
59 pub min_filter: FilterMode,
61 pub mag_filter: FilterMode,
63 pub wrap_u: WrapMode,
65 pub wrap_v: WrapMode,
67 pub lod_bias: LodBias,
69 pub max_anisotropy: u8,
71}
72
73impl SamplerConfig {
74 #[must_use]
76 pub fn linear_clamp() -> Self {
77 Self {
78 min_filter: FilterMode::Linear,
79 mag_filter: FilterMode::Linear,
80 wrap_u: WrapMode::ClampToEdge,
81 wrap_v: WrapMode::ClampToEdge,
82 lod_bias: 0.0,
83 max_anisotropy: 1,
84 }
85 }
86
87 #[must_use]
89 pub fn nearest_repeat() -> Self {
90 Self {
91 min_filter: FilterMode::Nearest,
92 mag_filter: FilterMode::Nearest,
93 wrap_u: WrapMode::Repeat,
94 wrap_v: WrapMode::Repeat,
95 lod_bias: 0.0,
96 max_anisotropy: 1,
97 }
98 }
99
100 #[must_use]
102 pub fn trilinear_anisotropic() -> Self {
103 Self {
104 min_filter: FilterMode::LinearMipmapLinear,
105 mag_filter: FilterMode::Linear,
106 wrap_u: WrapMode::Repeat,
107 wrap_v: WrapMode::Repeat,
108 lod_bias: 0.0,
109 max_anisotropy: 16,
110 }
111 }
112
113 #[must_use]
115 pub fn needs_mipmaps(&self) -> bool {
116 self.min_filter.uses_mipmaps()
117 }
118
119 pub fn clamp_anisotropy(&mut self, hardware_max: u8) {
121 self.max_anisotropy = self.max_anisotropy.min(hardware_max);
122 }
123}
124
125impl Default for SamplerConfig {
126 fn default() -> Self {
127 Self::linear_clamp()
128 }
129}
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
135pub struct SamplerHandle(u64);
136
137impl SamplerHandle {
138 #[must_use]
140 pub fn id(self) -> u64 {
141 self.0
142 }
143}
144
145#[derive(Debug, Default)]
162pub struct SamplerCache {
163 entries: HashMap<u64, SamplerHandle>,
165 next_id: u64,
167}
168
169impl SamplerCache {
170 #[must_use]
172 pub fn new() -> Self {
173 Self::default()
174 }
175
176 fn config_key(cfg: &SamplerConfig) -> u64 {
178 use std::collections::hash_map::DefaultHasher;
179 use std::hash::{Hash, Hasher};
180 let mut h = DefaultHasher::new();
181 cfg.min_filter.hash(&mut h);
182 cfg.mag_filter.hash(&mut h);
183 cfg.wrap_u.hash(&mut h);
184 cfg.wrap_v.hash(&mut h);
185 cfg.lod_bias.to_bits().hash(&mut h);
187 cfg.max_anisotropy.hash(&mut h);
188 h.finish()
189 }
190
191 pub fn get_or_insert(&mut self, config: SamplerConfig) -> SamplerHandle {
194 let key = Self::config_key(&config);
195 if let Some(&handle) = self.entries.get(&key) {
196 return handle;
197 }
198 let handle = SamplerHandle(self.next_id);
199 self.next_id += 1;
200 self.entries.insert(key, handle);
201 handle
202 }
203
204 #[must_use]
206 pub fn len(&self) -> usize {
207 self.entries.len()
208 }
209
210 #[must_use]
212 pub fn is_empty(&self) -> bool {
213 self.entries.is_empty()
214 }
215
216 pub fn clear(&mut self) {
218 self.entries.clear();
219 self.next_id = 0;
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 #[test]
228 fn filter_mode_linear_no_mipmap() {
229 assert!(!FilterMode::Linear.uses_mipmaps());
230 }
231
232 #[test]
233 fn filter_mode_nearest_no_mipmap() {
234 assert!(!FilterMode::Nearest.uses_mipmaps());
235 }
236
237 #[test]
238 fn filter_mode_mipmap_variants_use_mipmaps() {
239 assert!(FilterMode::NearestMipmapNearest.uses_mipmaps());
240 assert!(FilterMode::LinearMipmapLinear.uses_mipmaps());
241 }
242
243 #[test]
244 fn filter_mode_default_is_linear() {
245 assert_eq!(FilterMode::default(), FilterMode::Linear);
246 }
247
248 #[test]
249 fn wrap_mode_default_is_clamp_to_edge() {
250 assert_eq!(WrapMode::default(), WrapMode::ClampToEdge);
251 }
252
253 #[test]
254 fn sampler_config_linear_clamp_no_mipmaps() {
255 let cfg = SamplerConfig::linear_clamp();
256 assert!(!cfg.needs_mipmaps());
257 }
258
259 #[test]
260 fn sampler_config_trilinear_needs_mipmaps() {
261 let cfg = SamplerConfig::trilinear_anisotropic();
262 assert!(cfg.needs_mipmaps());
263 }
264
265 #[test]
266 fn sampler_config_clamp_anisotropy() {
267 let mut cfg = SamplerConfig::trilinear_anisotropic();
268 cfg.clamp_anisotropy(4);
269 assert_eq!(cfg.max_anisotropy, 4);
270 }
271
272 #[test]
273 fn sampler_config_clamp_anisotropy_no_increase() {
274 let mut cfg = SamplerConfig::linear_clamp();
275 cfg.clamp_anisotropy(32);
276 assert_eq!(cfg.max_anisotropy, 1); }
278
279 #[test]
280 fn sampler_cache_deduplicate() {
281 let mut cache = SamplerCache::new();
282 let h1 = cache.get_or_insert(SamplerConfig::linear_clamp());
283 let h2 = cache.get_or_insert(SamplerConfig::linear_clamp());
284 assert_eq!(h1, h2);
285 assert_eq!(cache.len(), 1);
286 }
287
288 #[test]
289 fn sampler_cache_different_configs_different_handles() {
290 let mut cache = SamplerCache::new();
291 let h1 = cache.get_or_insert(SamplerConfig::linear_clamp());
292 let h2 = cache.get_or_insert(SamplerConfig::nearest_repeat());
293 assert_ne!(h1, h2);
294 assert_eq!(cache.len(), 2);
295 }
296
297 #[test]
298 fn sampler_cache_is_empty_initially() {
299 let cache = SamplerCache::new();
300 assert!(cache.is_empty());
301 }
302
303 #[test]
304 fn sampler_cache_clear_resets() {
305 let mut cache = SamplerCache::new();
306 cache.get_or_insert(SamplerConfig::linear_clamp());
307 cache.clear();
308 assert!(cache.is_empty());
309 assert_eq!(cache.next_id, 0);
310 }
311
312 #[test]
313 fn sampler_handle_id() {
314 let mut cache = SamplerCache::new();
315 let h = cache.get_or_insert(SamplerConfig::linear_clamp());
316 assert_eq!(h.id(), 0);
317 }
318
319 #[test]
320 fn sampler_config_default_is_linear_clamp() {
321 let a = SamplerConfig::default();
322 let b = SamplerConfig::linear_clamp();
323 assert_eq!(a, b);
324 }
325
326 #[test]
327 fn nearest_repeat_config_values() {
328 let cfg = SamplerConfig::nearest_repeat();
329 assert_eq!(cfg.min_filter, FilterMode::Nearest);
330 assert_eq!(cfg.wrap_u, WrapMode::Repeat);
331 }
332}