Skip to main content

myth_resources/
shader_defines.rs

1//! Shader Macro Definition System
2//!
3//! Provides a unified, high-performance shader macro management system.
4//! Uses string interning and hash caching for O(1) macro set comparison.
5//!
6//! # Architecture
7//!
8//! The system uses [`Symbol`] (interned strings) for both keys and values,
9//! providing:
10//!
11//! - **Memory efficiency**: Duplicate strings share storage
12//! - **Fast comparison**: Symbol comparison is just integer comparison
13//! - **Consistent hashing**: Same macro sets always produce same hashes
14//!
15//! # Usage
16//!
17//! ```rust,ignore
18//! use myth::resources::ShaderDefines;
19//!
20//! let mut defines = ShaderDefines::new();
21//! defines.set("HAS_NORMAL_MAP", "1");
22//! defines.set("MAX_LIGHTS", "8");
23//!
24//! // Fast hash for pipeline cache lookup
25//! let hash = defines.compilation_hash();
26//! ```
27//!
28//! # Integration with Materials
29//!
30//! Materials implement [`crate::RenderableMaterialTrait::shader_defines`] to declare
31//! their required shader macros based on current state (e.g., enabled textures).
32
33use std::collections::BTreeMap;
34use std::hash::{Hash, Hasher};
35
36use myth_core::utils::interner::{self, Symbol};
37
38/// A collection of shader macro definitions.
39///
40/// Internally uses an ordered `Vec<(Symbol, Symbol)>` to store definitions,
41/// ensuring that identical macro sets produce identical hash values.
42///
43/// # Performance
44///
45/// - Insertion/lookup: O(log n) due to binary search
46/// - Hash computation: O(n) but cached
47/// - Comparison: O(1) when using cached hashes
48#[derive(Debug, Clone, Default)]
49pub struct ShaderDefines {
50    defines: Vec<(Symbol, Symbol)>,
51}
52
53impl ShaderDefines {
54    /// Create empty shader defines collection
55    #[inline]
56    #[must_use]
57    pub fn new() -> Self {
58        Self {
59            defines: Vec::new(),
60        }
61    }
62
63    /// Create shader defines collection with pre-allocated capacity
64    #[inline]
65    #[must_use]
66    pub fn with_capacity(capacity: usize) -> Self {
67        Self {
68            defines: Vec::with_capacity(capacity),
69        }
70    }
71
72    /// Set shader define (maintains sorted order)
73    ///
74    /// If key exists, updates its value; otherwise inserts new entry.
75    pub fn set(&mut self, key: &str, value: &str) {
76        let key_sym = interner::intern(key);
77        let value_sym = interner::intern(value);
78        self.set_symbol(key_sym, value_sym);
79    }
80
81    /// Set shader define using Symbol (internal method, more efficient)
82    #[inline]
83    pub fn set_symbol(&mut self, key: Symbol, value: Symbol) {
84        match self.defines.binary_search_by_key(&key, |&(k, _)| k) {
85            Ok(idx) => {
86                self.defines[idx].1 = value;
87            }
88            Err(idx) => {
89                self.defines.insert(idx, (key, value));
90            }
91        }
92    }
93
94    /// Remove shader define
95    pub fn remove(&mut self, key: &str) -> bool {
96        if let Some(key_sym) = interner::get(key) {
97            self.remove_symbol(key_sym)
98        } else {
99            false
100        }
101    }
102
103    /// Remove shader define using Symbol
104    #[inline]
105    pub fn remove_symbol(&mut self, key: Symbol) -> bool {
106        if let Ok(idx) = self.defines.binary_search_by_key(&key, |&(k, _)| k) {
107            self.defines.remove(idx);
108            true
109        } else {
110            false
111        }
112    }
113
114    /// Check if contains a shader define
115    #[must_use]
116    pub fn contains(&self, key: &str) -> bool {
117        interner::get(key).is_some_and(|key_sym| self.contains_symbol(key_sym))
118    }
119
120    /// Check if contains a shader define using Symbol
121    #[inline]
122    #[must_use]
123    pub fn contains_symbol(&self, key: Symbol) -> bool {
124        self.defines.binary_search_by_key(&key, |&(k, _)| k).is_ok()
125    }
126
127    /// Get shader define value
128    #[must_use]
129    pub fn get(&self, key: &str) -> Option<String> {
130        interner::get(key).and_then(|key_sym| self.get_symbol(key_sym).map(|s| s.to_string()))
131    }
132
133    /// Get shader define value using Symbol
134    #[inline]
135    #[must_use]
136    pub fn get_symbol(&self, key: Symbol) -> Option<std::borrow::Cow<'static, str>> {
137        self.defines
138            .binary_search_by_key(&key, |&(k, _)| k)
139            .ok()
140            .map(|idx| interner::resolve(self.defines[idx].1))
141    }
142
143    /// Clear all shader defines
144    #[inline]
145    pub fn clear(&mut self) {
146        self.defines.clear();
147    }
148
149    /// Get shader defines count
150    #[inline]
151    #[must_use]
152    pub fn len(&self) -> usize {
153        self.defines.len()
154    }
155
156    /// Check if empty
157    #[inline]
158    #[must_use]
159    pub fn is_empty(&self) -> bool {
160        self.defines.is_empty()
161    }
162
163    /// Iterate all shader defines (as Symbols)
164    #[inline]
165    pub fn iter(&self) -> impl Iterator<Item = &(Symbol, Symbol)> {
166        self.defines.iter()
167    }
168
169    /// Iterate all shader defines (as strings)
170    #[inline]
171    pub fn iter_strings(&self) -> impl Iterator<Item = (String, String)> + '_ {
172        self.defines.iter().map(|&(k, v)| {
173            (
174                interner::resolve(k).to_string(),
175                interner::resolve(v).to_string(),
176            )
177        })
178    }
179
180    /// Convert to `BTreeMap` (for template rendering)
181    #[must_use]
182    pub fn to_map(&self) -> BTreeMap<String, String> {
183        self.defines
184            .iter()
185            .map(|&(k, v)| {
186                (
187                    interner::resolve(k).to_string(),
188                    interner::resolve(v).to_string(),
189                )
190            })
191            .collect()
192    }
193
194    /// Merge shader defines from another `ShaderDefines`
195    ///
196    /// If there are conflicts, values from other will override values in self.
197    pub fn merge(&mut self, other: &ShaderDefines) {
198        for &(key, value) in &other.defines {
199            self.set_symbol(key, value);
200        }
201    }
202
203    /// Create a new merged `ShaderDefines`
204    #[must_use]
205    pub fn merged_with(&self, other: &ShaderDefines) -> ShaderDefines {
206        let mut result = self.clone();
207        result.merge(other);
208        result
209    }
210
211    /// Compute content hash (for caching)
212    #[must_use]
213    pub fn compute_hash(&self) -> u64 {
214        use std::hash::BuildHasher;
215
216        rustc_hash::FxBuildHasher.hash_one(self)
217    }
218
219    /// Get internal defines reference (for direct access)
220    #[inline]
221    #[must_use]
222    pub fn as_slice(&self) -> &[(Symbol, Symbol)] {
223        &self.defines
224    }
225}
226
227impl Hash for ShaderDefines {
228    fn hash<H: Hasher>(&self, state: &mut H) {
229        self.defines.hash(state);
230    }
231}
232
233impl PartialEq for ShaderDefines {
234    fn eq(&self, other: &Self) -> bool {
235        self.defines == other.defines
236    }
237}
238
239impl Eq for ShaderDefines {}
240
241/// Create `ShaderDefines` from list of macro definitions
242impl From<&[(&str, &str)]> for ShaderDefines {
243    fn from(defines: &[(&str, &str)]) -> Self {
244        let mut result = Self::with_capacity(defines.len());
245        for (k, v) in defines {
246            result.set(k, v);
247        }
248        result
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255
256    #[test]
257    fn test_set_and_get() {
258        let mut defines = ShaderDefines::new();
259        defines.set("USE_MAP", "1");
260        defines.set("USE_NORMAL_MAP", "1");
261
262        assert!(defines.contains("USE_MAP"));
263        assert!(defines.contains("USE_NORMAL_MAP"));
264        assert!(!defines.contains("USE_AO_MAP"));
265
266        assert_eq!(defines.get("USE_MAP"), Some("1".to_string()));
267    }
268
269    #[test]
270    fn test_ordering() {
271        let mut defines = ShaderDefines::new();
272        defines.set("B", "1");
273        defines.set("A", "1");
274        defines.set("C", "1");
275
276        // Verify internal sorting by Symbol (integer ID), ensuring hash consistency
277        // Note: Symbol order depends on intern order, not string lexicographic order
278        let symbols: Vec<_> = defines.iter().map(|(k, _)| k).collect();
279        assert!(
280            symbols.windows(2).all(|w| w[0] < w[1]),
281            "Symbols should be sorted by numeric value"
282        );
283
284        // Verify all keys exist
285        assert!(defines.contains("A"));
286        assert!(defines.contains("B"));
287        assert!(defines.contains("C"));
288    }
289
290    #[test]
291    fn test_merge() {
292        let mut d1 = ShaderDefines::new();
293        d1.set("A", "1");
294        d1.set("B", "2");
295
296        let mut d2 = ShaderDefines::new();
297        d2.set("B", "3");
298        d2.set("C", "4");
299
300        d1.merge(&d2);
301
302        assert_eq!(d1.get("A"), Some("1".to_string()));
303        assert_eq!(d1.get("B"), Some("3".to_string())); // Overwritten
304        assert_eq!(d1.get("C"), Some("4".to_string()));
305    }
306
307    #[test]
308    fn test_hash_consistency() {
309        let mut d1 = ShaderDefines::new();
310        d1.set("A", "1");
311        d1.set("B", "2");
312
313        let mut d2 = ShaderDefines::new();
314        d2.set("B", "2");
315        d2.set("A", "1");
316
317        assert_eq!(d1.compute_hash(), d2.compute_hash());
318    }
319}