Skip to main content

oxiphysics_gpu/shaders/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4use super::functions::*;
5use super::functions::{
6    BOUNDARY_ENFORCE_WGSL, BROADPHASE_SORT_SHADER, INTEGRATE_WGSL, LBM_BGK_D2Q9_WGSL,
7    LBM_STREAMING_SHADER, RIGID_INTEGRATE_SHADER, SPH_DENSITY_WGSL, SPH_FORCE_WGSL,
8};
9use std::collections::HashMap;
10
11/// Reflection data extracted from a (mock) SPIR-V module or WGSL source.
12#[derive(Debug, Clone)]
13pub struct SpirVModule {
14    /// Entry point function names.
15    pub entry_points: Vec<String>,
16    /// Number of bindings.
17    pub binding_count: usize,
18    /// Workgroup size \[x, y, z\].
19    pub workgroup_size: [u32; 3],
20    /// Raw SPIR-V bytes.
21    pub spirv_bytes: Vec<u8>,
22}
23impl SpirVModule {
24    /// Build a SpirV module from WGSL source (mock reflection).
25    pub fn from_wgsl(source: &str) -> Self {
26        let mut entry_points = Vec::new();
27        for line in source.lines() {
28            let trimmed = line.trim();
29            if let Some(pos) = trimmed.find("fn ") {
30                let rest = &trimmed[pos + 3..];
31                let name: String = rest
32                    .chars()
33                    .take_while(|c| c.is_alphanumeric() || *c == '_')
34                    .collect();
35                if !name.is_empty() {
36                    entry_points.push(name);
37                }
38            }
39        }
40        let binding_count = source.matches("@binding(").count();
41        let workgroup_size = parse_workgroup_size(source);
42        let spirv_bytes = mock_compile_to_spirv(
43            source,
44            entry_points.first().map(|s| s.as_str()).unwrap_or("main"),
45        );
46        Self {
47            entry_points,
48            binding_count,
49            workgroup_size,
50            spirv_bytes,
51        }
52    }
53    /// Check whether this module has a specific entry point.
54    pub fn has_entry_point(&self, name: &str) -> bool {
55        self.entry_points.iter().any(|e| e == name)
56    }
57    /// Size of the SPIR-V binary in bytes.
58    pub fn byte_size(&self) -> usize {
59        self.spirv_bytes.len()
60    }
61}
62/// A push constant range that can be set per draw/dispatch call.
63#[derive(Debug, Clone, Copy)]
64pub struct PushConstantRange {
65    /// Byte offset into the push constant block.
66    pub offset: u32,
67    /// Size in bytes.
68    pub size: u32,
69    /// Shader stage.
70    pub stage: ShaderStage,
71}
72impl PushConstantRange {
73    /// Create a new push constant range.
74    pub fn new(offset: u32, size: u32, stage: ShaderStage) -> Self {
75        Self {
76            offset,
77            size,
78            stage,
79        }
80    }
81    /// Check if this range fits within the standard 128-byte GPU limit.
82    pub fn fits_standard_limit(&self) -> bool {
83        self.offset + self.size <= 128
84    }
85}
86/// Texture address mode.
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum AddressMode {
89    /// Clamp coordinates to \[0, 1\].
90    ClampToEdge,
91    /// Wrap coordinates (repeat).
92    Repeat,
93    /// Mirror-repeat coordinates.
94    MirrorRepeat,
95}
96/// A layout describing all bindings in a descriptor set group.
97#[derive(Debug, Clone)]
98pub struct DescriptorSetLayout {
99    /// Group index (set number).
100    pub group: u32,
101    /// All binding entries.
102    pub bindings: Vec<DescriptorBinding>,
103}
104impl DescriptorSetLayout {
105    /// Create a new empty layout for the given group.
106    pub fn new(group: u32) -> Self {
107        Self {
108            group,
109            bindings: Vec::new(),
110        }
111    }
112    /// Add a storage buffer binding.
113    pub fn add_storage_buffer(&mut self, binding: u32, stage: ShaderStage, read_only: bool) {
114        self.bindings.push(DescriptorBinding {
115            binding,
116            descriptor_type: DescriptorType::StorageBuffer,
117            stage,
118            read_only,
119        });
120    }
121    /// Add a uniform buffer binding.
122    pub fn add_uniform_buffer(&mut self, binding: u32, stage: ShaderStage) {
123        self.bindings.push(DescriptorBinding {
124            binding,
125            descriptor_type: DescriptorType::UniformBuffer,
126            stage,
127            read_only: true,
128        });
129    }
130    /// Add a combined image sampler binding.
131    pub fn add_sampler(&mut self, binding: u32, stage: ShaderStage) {
132        self.bindings.push(DescriptorBinding {
133            binding,
134            descriptor_type: DescriptorType::CombinedImageSampler,
135            stage,
136            read_only: true,
137        });
138    }
139    /// Number of bindings in this layout.
140    pub fn len(&self) -> usize {
141        self.bindings.len()
142    }
143    /// Whether the layout has no bindings.
144    pub fn is_empty(&self) -> bool {
145        self.bindings.is_empty()
146    }
147}
148/// A storage binding entry in a bind group.
149#[derive(Debug, Clone)]
150pub struct StorageBinding {
151    /// Name in WGSL.
152    pub name: String,
153    /// Binding index.
154    pub binding: u32,
155    /// Whether read-only.
156    pub read_only: bool,
157}
158/// Manages shader source files and detects changes for hot-reloading.
159#[derive(Debug, Default)]
160pub struct ShaderHotReloadManager {
161    /// Map from shader name to current source.
162    pub(super) sources: HashMap<String, String>,
163    /// Map from shader name to source hash (for change detection).
164    pub(super) hashes: HashMap<String, u64>,
165}
166impl ShaderHotReloadManager {
167    /// Create a new hot-reload manager.
168    pub fn new() -> Self {
169        Self::default()
170    }
171    /// Start watching a shader file with initial source.
172    pub fn watch(&mut self, name: &str, source: &str) {
173        let hash = simple_hash(source);
174        self.sources.insert(name.to_string(), source.to_string());
175        self.hashes.insert(name.to_string(), hash);
176    }
177    /// Stop watching a shader.
178    pub fn unwatch(&mut self, name: &str) {
179        self.sources.remove(name);
180        self.hashes.remove(name);
181    }
182    /// Update a shader's source. Returns `true` if the source changed.
183    pub fn update(&mut self, name: &str, new_source: &str) -> bool {
184        let new_hash = simple_hash(new_source);
185        if let Some(old_hash) = self.hashes.get(name)
186            && *old_hash == new_hash
187        {
188            return false;
189        }
190        self.sources
191            .insert(name.to_string(), new_source.to_string());
192        self.hashes.insert(name.to_string(), new_hash);
193        true
194    }
195    /// Check if a shader is being watched.
196    pub fn is_watched(&self, name: &str) -> bool {
197        self.sources.contains_key(name)
198    }
199    /// Get the current source for a watched shader.
200    pub fn get_source(&self, name: &str) -> Option<&str> {
201        self.sources.get(name).map(|s| s.as_str())
202    }
203    /// Return names of all watched shaders.
204    pub fn watched_names(&self) -> Vec<&str> {
205        self.sources.keys().map(|s| s.as_str()).collect()
206    }
207    /// Number of watched shaders.
208    pub fn len(&self) -> usize {
209        self.sources.len()
210    }
211    /// Whether no shaders are watched.
212    pub fn is_empty(&self) -> bool {
213        self.sources.is_empty()
214    }
215}
216/// Type of a GPU descriptor.
217#[derive(Debug, Clone, Copy, PartialEq, Eq)]
218pub enum DescriptorType {
219    /// Uniform buffer.
220    UniformBuffer,
221    /// Storage buffer.
222    StorageBuffer,
223    /// Combined image sampler.
224    CombinedImageSampler,
225    /// Storage image.
226    StorageImage,
227}
228/// Registry mapping shader names to their [`ShaderMetadata`].
229#[derive(Debug, Default)]
230pub struct ShaderMetaRegistry {
231    pub(super) entries: HashMap<String, ShaderMetadata>,
232}
233impl ShaderMetaRegistry {
234    /// Create an empty registry.
235    pub fn new() -> Self {
236        Self::default()
237    }
238    /// Register a shader under `name`.
239    pub fn register(&mut self, name: &str, meta: ShaderMetadata) {
240        self.entries.insert(name.to_string(), meta);
241    }
242    /// Look up a shader by name.
243    pub fn lookup(&self, name: &str) -> Option<&ShaderMetadata> {
244        self.entries.get(name)
245    }
246    /// Return all registered shader names.
247    pub fn all_names(&self) -> Vec<&str> {
248        self.entries.keys().map(|s| s.as_str()).collect()
249    }
250    /// Number of registered shaders.
251    pub fn len(&self) -> usize {
252        self.entries.len()
253    }
254    /// True if no shaders are registered.
255    pub fn is_empty(&self) -> bool {
256        self.entries.is_empty()
257    }
258}
259/// A specialization constant for shader compilation.
260#[derive(Debug, Clone)]
261pub struct SpecializationConstant {
262    /// Name of the constant in the shader source.
263    pub name: String,
264    /// Default value as a string.
265    pub default_value: String,
266    /// Description of what the constant controls.
267    pub description: String,
268}
269impl SpecializationConstant {
270    /// Create a new specialization constant.
271    pub fn new(name: &str, default_value: &str, description: &str) -> Self {
272        Self {
273            name: name.to_string(),
274            default_value: default_value.to_string(),
275            description: description.to_string(),
276        }
277    }
278}
279/// A parameterised WGSL shader template.
280///
281/// Placeholders of the form `{KEY}` in the template string are replaced by
282/// the corresponding values supplied via [`ShaderTemplate::instantiate`].
283#[derive(Debug, Clone)]
284pub struct ShaderTemplate {
285    /// The raw template source with `{KEY}` placeholders.
286    pub template: String,
287}
288impl ShaderTemplate {
289    /// Create a new shader template from a source string.
290    pub fn new(template: impl Into<String>) -> Self {
291        Self {
292            template: template.into(),
293        }
294    }
295    /// Instantiate the template by replacing all `{KEY}` placeholders.
296    ///
297    /// # Arguments
298    /// * `params` – map from placeholder name (without braces) to replacement string.
299    ///
300    /// # Returns
301    /// A new `String` with all recognised placeholders replaced.  Unknown
302    /// placeholders are left as-is.
303    pub fn instantiate(&self, params: &HashMap<&str, &str>) -> String {
304        let mut result = self.template.clone();
305        for (key, value) in params {
306            let placeholder = format!("{{{}}}", key);
307            result = result.replace(&placeholder, value);
308        }
309        result
310    }
311    /// Return a list of placeholder names found in the template.
312    ///
313    /// Only returns names that look like valid template placeholders:
314    /// `{IDENTIFIER}` where IDENTIFIER is uppercase letters, digits, and
315    /// underscores, starting with an uppercase letter.
316    pub fn placeholders(&self) -> Vec<String> {
317        let mut result = Vec::new();
318        let chars: Vec<char> = self.template.chars().collect();
319        let mut i = 0;
320        while i < chars.len() {
321            if chars[i] == '{' {
322                if i + 1 < chars.len() && chars[i + 1].is_ascii_uppercase() {
323                    let start = i + 1;
324                    let mut end = start;
325                    while end < chars.len() && chars[end] != '}' {
326                        end += 1;
327                    }
328                    if end < chars.len() {
329                        let name: String = chars[start..end].iter().collect();
330                        let is_valid = name
331                            .chars()
332                            .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_');
333                        if is_valid && !result.contains(&name) {
334                            result.push(name);
335                        }
336                        i = end + 1;
337                    } else {
338                        i += 1;
339                    }
340                } else {
341                    i += 1;
342                }
343            } else {
344                i += 1;
345            }
346        }
347        result
348    }
349    /// Check if all placeholders are provided in the params map.
350    pub fn all_placeholders_provided(&self, params: &HashMap<&str, &str>) -> bool {
351        for p in self.placeholders() {
352            if !params.contains_key(p.as_str()) {
353                return false;
354            }
355        }
356        true
357    }
358}
359/// Texture format for attachments.
360#[derive(Debug, Clone, Copy, PartialEq, Eq)]
361pub enum TextureFormat {
362    /// 8-bit RGBA unorm.
363    Rgba8Unorm,
364    /// 8-bit RGBA sRGB.
365    Rgba8Srgb,
366    /// 16-bit float RGBA (HDR).
367    Rgba16Float,
368    /// 32-bit float single channel.
369    R32Float,
370    /// 32-bit depth.
371    Depth32Float,
372    /// 24-bit depth + 8-bit stencil.
373    Depth24PlusStencil8,
374}
375/// A flexible bind group layout builder.
376#[derive(Debug, Clone, Default)]
377pub struct BindGroupLayout {
378    pub(super) uniforms: Vec<UniformBinding>,
379    pub(super) storages: Vec<StorageBinding>,
380}
381impl BindGroupLayout {
382    /// Create an empty layout.
383    pub fn new() -> Self {
384        Self::default()
385    }
386    /// Add a uniform buffer binding.
387    pub fn add_uniform(&mut self, name: &str, _group: u32, binding: u32, size_bytes: u32) {
388        self.uniforms.push(UniformBinding {
389            name: name.to_string(),
390            binding,
391            size_bytes,
392        });
393    }
394    /// Add a storage buffer binding.
395    pub fn add_storage(&mut self, name: &str, _group: u32, binding: u32, read_only: bool) {
396        self.storages.push(StorageBinding {
397            name: name.to_string(),
398            binding,
399            read_only,
400        });
401    }
402    /// Total number of bindings.
403    pub fn binding_count(&self) -> usize {
404        self.uniforms.len() + self.storages.len()
405    }
406    /// Whether this layout has no bindings.
407    pub fn is_empty(&self) -> bool {
408        self.uniforms.is_empty() && self.storages.is_empty()
409    }
410    /// Generate a WGSL snippet declaring all bindings.
411    pub fn to_wgsl_snippet(&self) -> String {
412        let mut out = String::new();
413        for u in &self.uniforms {
414            out.push_str(&format!(
415                "@group(0) @binding({}) var<uniform> {}: {};\n",
416                u.binding,
417                u.name.to_lowercase(),
418                u.name
419            ));
420        }
421        for s in &self.storages {
422            let access = if s.read_only { "read" } else { "read_write" };
423            out.push_str(&format!(
424                "@group(0) @binding({}) var<storage, {}> {}: array<f32>;\n",
425                s.binding,
426                access,
427                s.name.to_lowercase()
428            ));
429        }
430        out
431    }
432}
433/// Description of a depth/stencil attachment.
434#[derive(Debug, Clone)]
435pub struct DepthAttachmentDesc {
436    /// Format of the depth attachment.
437    pub format: TextureFormat,
438    /// Load operation for depth.
439    pub load_op: LoadOp,
440    /// Store operation for depth.
441    pub store_op: StoreOp,
442    /// Clear depth value.
443    pub clear_depth: f32,
444}
445/// A pipeline for compiling WGSL shaders with preprocessing steps.
446///
447/// Steps: resolve includes -> apply specialization -> validate -> cache.
448pub struct ShaderCompilationPipeline {
449    pub(super) includes: HashMap<String, String>,
450    pub(super) cache: ShaderCache,
451}
452impl ShaderCompilationPipeline {
453    /// Create a new compilation pipeline.
454    pub fn new() -> Self {
455        Self {
456            includes: HashMap::new(),
457            cache: ShaderCache::new(),
458        }
459    }
460    /// Register an include file for `#include` resolution.
461    pub fn add_include(&mut self, name: &str, source: &str) {
462        self.includes.insert(name.to_string(), source.to_string());
463    }
464    /// Compile a shader source through the pipeline.
465    ///
466    /// Returns the processed source string. Caches the result.
467    pub fn compile(
468        &mut self,
469        name: &str,
470        source: &str,
471        spec_map: Option<&SpecializationMap>,
472    ) -> Result<String, String> {
473        if let Some(cached) = self.cache.entries.get(name) {
474            return Ok(cached.clone());
475        }
476        let includes_ref: HashMap<&str, &str> = self
477            .includes
478            .iter()
479            .map(|(k, v)| (k.as_str(), v.as_str()))
480            .collect();
481        let resolved = resolve_includes(source, &includes_ref);
482        let specialized = if let Some(sm) = spec_map {
483            sm.apply(&resolved)
484        } else {
485            resolved
486        };
487        if !validate_wgsl_structure(&specialized) {
488            return Err(format!("shader '{}' failed structural validation", name));
489        }
490        self.cache
491            .entries
492            .insert(name.to_string(), specialized.clone());
493        Ok(specialized)
494    }
495    /// Return the number of cached shaders.
496    pub fn cache_size(&self) -> usize {
497        self.cache.len()
498    }
499    /// Clear the compilation cache.
500    pub fn clear_cache(&mut self) {
501        self.cache.clear();
502    }
503}
504/// A registry of named compute shader descriptors.
505#[derive(Debug, Default)]
506pub struct ShaderRegistry {
507    pub(super) shaders: HashMap<String, ComputeShaderDesc>,
508}
509impl ShaderRegistry {
510    /// Create a new empty registry.
511    pub fn new() -> Self {
512        Self::default()
513    }
514    /// Register a shader under the given name.
515    pub fn register(&mut self, name: impl Into<String>, desc: ComputeShaderDesc) {
516        self.shaders.insert(name.into(), desc);
517    }
518    /// Retrieve a shader descriptor by name.
519    pub fn get(&self, name: &str) -> Option<&ComputeShaderDesc> {
520        self.shaders.get(name)
521    }
522    /// Return the number of registered shaders.
523    pub fn len(&self) -> usize {
524        self.shaders.len()
525    }
526    /// Return true if the registry is empty.
527    pub fn is_empty(&self) -> bool {
528        self.shaders.is_empty()
529    }
530    /// Return an iterator over registered shader names.
531    pub fn names(&self) -> impl Iterator<Item = &str> {
532        self.shaders.keys().map(|s| s.as_str())
533    }
534    /// Remove a shader from the registry.
535    pub fn unregister(&mut self, name: &str) -> Option<ComputeShaderDesc> {
536        self.shaders.remove(name)
537    }
538    /// Check if a shader is registered.
539    pub fn contains(&self, name: &str) -> bool {
540        self.shaders.contains_key(name)
541    }
542    /// Create a registry pre-populated with the built-in shaders.
543    pub fn with_builtins() -> Self {
544        let mut reg = Self::new();
545        reg.register(
546            "sph_density",
547            ComputeShaderDesc::new("main", [64, 1, 1], SPH_DENSITY_WGSL),
548        );
549        reg.register(
550            "sph_force",
551            ComputeShaderDesc::new("main", [64, 1, 1], SPH_FORCE_WGSL),
552        );
553        reg.register(
554            "integrate",
555            ComputeShaderDesc::new("main", [64, 1, 1], INTEGRATE_WGSL),
556        );
557        reg.register(
558            "lbm_bgk_d2q9",
559            ComputeShaderDesc::new("main", [64, 1, 1], LBM_BGK_D2Q9_WGSL),
560        );
561        reg.register(
562            "lbm_streaming",
563            ComputeShaderDesc::new("main", [64, 1, 1], LBM_STREAMING_SHADER),
564        );
565        reg.register(
566            "rigid_integrate",
567            ComputeShaderDesc::new("main", [64, 1, 1], RIGID_INTEGRATE_SHADER),
568        );
569        reg.register(
570            "broadphase_sort",
571            ComputeShaderDesc::new("main", [64, 1, 1], BROADPHASE_SORT_SHADER),
572        );
573        reg.register(
574            "boundary_enforce",
575            ComputeShaderDesc::new("main", [64, 1, 1], BOUNDARY_ENFORCE_WGSL),
576        );
577        reg
578    }
579}
580/// Descriptor for a WGSL compute shader.
581#[derive(Debug, Clone)]
582pub struct ComputeShaderDesc {
583    /// Name of the entry-point function (e.g. `"main"`).
584    pub entry_point: String,
585    /// Workgroup size `[x, y, z]`.
586    pub workgroup_size: [u32; 3],
587    /// Full WGSL source string.
588    pub source: String,
589}
590impl ComputeShaderDesc {
591    /// Create a new compute shader descriptor.
592    pub fn new(
593        entry_point: impl Into<String>,
594        workgroup_size: [u32; 3],
595        source: impl Into<String>,
596    ) -> Self {
597        Self {
598            entry_point: entry_point.into(),
599            workgroup_size,
600            source: source.into(),
601        }
602    }
603    /// Number of threads per workgroup.
604    pub fn threads_per_workgroup(&self) -> u32 {
605        self.workgroup_size[0] * self.workgroup_size[1] * self.workgroup_size[2]
606    }
607    /// Count the number of binding annotations in the shader source.
608    pub fn binding_count(&self) -> usize {
609        self.source.matches("@binding(").count()
610    }
611}
612/// Sampler descriptor for creating GPU samplers.
613#[derive(Debug, Clone)]
614pub struct SamplerDesc {
615    /// Minification filter.
616    pub filter_min: FilterMode,
617    /// Magnification filter.
618    pub filter_mag: FilterMode,
619    /// Address mode for all axes.
620    pub address_mode: AddressMode,
621    /// Anisotropy level (1 = disabled).
622    pub anisotropy: u32,
623    /// LOD bias.
624    pub lod_bias: f32,
625    /// Maximum LOD level.
626    pub lod_max: f32,
627}
628impl SamplerDesc {
629    /// Create a linear sampler with clamp-to-edge.
630    pub fn linear() -> Self {
631        Self {
632            filter_min: FilterMode::Linear,
633            filter_mag: FilterMode::Linear,
634            address_mode: AddressMode::ClampToEdge,
635            anisotropy: 1,
636            lod_bias: 0.0,
637            lod_max: 16.0,
638        }
639    }
640    /// Create a nearest-neighbor sampler.
641    pub fn nearest() -> Self {
642        Self {
643            filter_min: FilterMode::Nearest,
644            filter_mag: FilterMode::Nearest,
645            address_mode: AddressMode::ClampToEdge,
646            anisotropy: 1,
647            lod_bias: 0.0,
648            lod_max: 0.0,
649        }
650    }
651    /// Create an anisotropic sampler with repeat addressing.
652    pub fn anisotropic(max_anisotropy: u32) -> Self {
653        Self {
654            filter_min: FilterMode::Linear,
655            filter_mag: FilterMode::Linear,
656            address_mode: AddressMode::Repeat,
657            anisotropy: max_anisotropy,
658            lod_bias: 0.0,
659            lod_max: 16.0,
660        }
661    }
662}
663/// Description of a full render pass.
664#[derive(Debug, Clone)]
665pub struct RenderPassDesc {
666    /// Color attachments.
667    pub color_attachments: Vec<ColorAttachmentDesc>,
668    /// Optional depth/stencil attachment.
669    pub depth_attachment: Option<DepthAttachmentDesc>,
670    /// Name of this render pass (for debugging).
671    pub name: String,
672}
673impl RenderPassDesc {
674    /// Create a simple render pass with one RGBA8 color attachment, no depth.
675    pub fn new_simple_color() -> Self {
676        Self {
677            color_attachments: vec![ColorAttachmentDesc {
678                format: TextureFormat::Rgba8Unorm,
679                load_op: LoadOp::Clear,
680                store_op: StoreOp::Store,
681                clear_color: [0.0, 0.0, 0.0, 1.0],
682            }],
683            depth_attachment: None,
684            name: "SimpleColor".to_string(),
685        }
686    }
687    /// Create a render pass with an HDR color attachment and depth buffer.
688    pub fn new_with_depth() -> Self {
689        Self {
690            color_attachments: vec![ColorAttachmentDesc {
691                format: TextureFormat::Rgba16Float,
692                load_op: LoadOp::Clear,
693                store_op: StoreOp::Store,
694                clear_color: [0.0, 0.0, 0.0, 1.0],
695            }],
696            depth_attachment: Some(DepthAttachmentDesc {
697                format: TextureFormat::Depth32Float,
698                load_op: LoadOp::Clear,
699                store_op: StoreOp::Store,
700                clear_depth: 1.0,
701            }),
702            name: "ColorDepth".to_string(),
703        }
704    }
705    /// Number of total attachments (color + optional depth).
706    pub fn total_attachment_count(&self) -> usize {
707        self.color_attachments.len()
708            + if self.depth_attachment.is_some() {
709                1
710            } else {
711                0
712            }
713    }
714}
715/// High-level category for a GPU compute shader.
716#[derive(Debug, Clone, PartialEq, Eq, Hash)]
717pub enum ShaderVariant {
718    /// General physics integration / force kernels.
719    Physics,
720    /// Collision detection and response.
721    Collision,
722    /// Smoothed particle hydrodynamics.
723    Sph,
724    /// Lattice Boltzmann method.
725    Lbm,
726    /// Rigid body dynamics.
727    RigidBody,
728    /// Neural network inference.
729    NeuralInference,
730}
731/// Metadata describing a single compute shader.
732#[derive(Debug, Clone)]
733pub struct ShaderMetadata {
734    /// The high-level shader category.
735    pub variant: ShaderVariant,
736    /// Name of the entry-point function (e.g. `"main"`).
737    pub entry_point: String,
738    /// Workgroup size `[x, y, z]`.
739    pub workgroup_size: [u32; 3],
740    /// Number of bind groups required by the shader.
741    pub bind_group_count: u32,
742}
743impl ShaderMetadata {
744    /// Create a new metadata record.
745    pub fn new(
746        variant: ShaderVariant,
747        entry_point: impl Into<String>,
748        workgroup_size: [u32; 3],
749        bind_group_count: u32,
750    ) -> Self {
751        Self {
752            variant,
753            entry_point: entry_point.into(),
754            workgroup_size,
755            bind_group_count,
756        }
757    }
758    /// Total number of threads per workgroup.
759    pub fn threads_per_workgroup(&self) -> u32 {
760        self.workgroup_size[0] * self.workgroup_size[1] * self.workgroup_size[2]
761    }
762}
763/// A simple cache for compiled shader sources.
764///
765/// Caches shader source strings by a composite key of name + specialization.
766#[derive(Debug, Default)]
767pub struct ShaderCache {
768    pub(super) entries: HashMap<String, String>,
769}
770impl ShaderCache {
771    /// Create a new empty cache.
772    pub fn new() -> Self {
773        Self::default()
774    }
775    /// Get a cached shader source by key, or compute and cache it.
776    pub fn get_or_insert(&mut self, key: &str, compute: impl FnOnce() -> String) -> &str {
777        self.entries.entry(key.to_string()).or_insert_with(compute)
778    }
779    /// Check if a shader is cached.
780    pub fn contains(&self, key: &str) -> bool {
781        self.entries.contains_key(key)
782    }
783    /// Return the number of cached entries.
784    pub fn len(&self) -> usize {
785        self.entries.len()
786    }
787    /// Check if the cache is empty.
788    pub fn is_empty(&self) -> bool {
789        self.entries.is_empty()
790    }
791    /// Clear the cache.
792    pub fn clear(&mut self) {
793        self.entries.clear();
794    }
795    /// Remove a specific entry.
796    pub fn remove(&mut self, key: &str) -> Option<String> {
797        self.entries.remove(key)
798    }
799}
800/// Shader stage flags.
801#[derive(Debug, Clone, Copy, PartialEq, Eq)]
802pub enum ShaderStage {
803    /// Vertex shader stage.
804    Vertex,
805    /// Fragment shader stage.
806    Fragment,
807    /// Compute shader stage.
808    Compute,
809    /// All stages.
810    All,
811}
812/// A parameterised shader template that replaces `#define KEY` tokens.
813///
814/// Instantiation replaces occurrences of each key found in `defines`
815/// with the corresponding value.  The key must appear as a whole word
816/// (surrounded by non-identifier characters or at start/end of line).
817#[derive(Debug, Clone)]
818pub struct ShaderTemplateV2 {
819    /// Raw template source text.
820    pub source: String,
821    /// Map from token name to replacement value.
822    pub defines: HashMap<String, String>,
823}
824impl ShaderTemplateV2 {
825    /// Create a new template with the given source and defines.
826    pub fn new(source: impl Into<String>, defines: HashMap<String, String>) -> Self {
827        Self {
828            source: source.into(),
829            defines,
830        }
831    }
832    /// Instantiate the template by performing all `#define` substitutions.
833    ///
834    /// Each entry in `defines` replaces all occurrences of the key
835    /// in the source with the corresponding value.
836    pub fn instantiate(&self) -> String {
837        let mut result = self.source.clone();
838        for (key, value) in &self.defines {
839            result = result.replace(key.as_str(), value.as_str());
840        }
841        result
842    }
843}
844/// Description of a color attachment in a render pass.
845#[derive(Debug, Clone)]
846pub struct ColorAttachmentDesc {
847    /// Texture format of the attachment.
848    pub format: TextureFormat,
849    /// Load operation.
850    pub load_op: LoadOp,
851    /// Store operation.
852    pub store_op: StoreOp,
853    /// Clear color \[r, g, b, a\].
854    pub clear_color: [f32; 4],
855}
856/// A uniform binding entry in a bind group.
857#[derive(Debug, Clone)]
858pub struct UniformBinding {
859    /// Name in WGSL.
860    pub name: String,
861    /// Binding index.
862    pub binding: u32,
863    /// Size in bytes.
864    pub size_bytes: u32,
865}
866/// Store operation for an attachment.
867#[derive(Debug, Clone, Copy, PartialEq, Eq)]
868pub enum StoreOp {
869    /// Store the rendered content.
870    Store,
871    /// Discard the content after rendering.
872    Discard,
873}
874/// A single binding entry in a descriptor set layout.
875#[derive(Debug, Clone)]
876pub struct DescriptorBinding {
877    /// Binding index.
878    pub binding: u32,
879    /// Type of the descriptor.
880    pub descriptor_type: DescriptorType,
881    /// Shader stage that uses this binding.
882    pub stage: ShaderStage,
883    /// Whether a storage buffer is read-only.
884    pub read_only: bool,
885}
886/// Texture filtering mode.
887#[derive(Debug, Clone, Copy, PartialEq, Eq)]
888pub enum FilterMode {
889    /// Nearest neighbor (point) filtering.
890    Nearest,
891    /// Bilinear/trilinear filtering.
892    Linear,
893}
894/// Load operation for an attachment.
895#[derive(Debug, Clone, Copy, PartialEq, Eq)]
896pub enum LoadOp {
897    /// Load the existing content.
898    Load,
899    /// Clear to a specified value.
900    Clear,
901    /// Content doesn't matter.
902    DontCare,
903}
904/// A collection of specialization constants for a shader.
905#[derive(Debug, Clone, Default)]
906pub struct SpecializationMap {
907    pub(super) constants: Vec<SpecializationConstant>,
908    pub(super) overrides: HashMap<String, String>,
909}
910impl SpecializationMap {
911    /// Create a new empty specialization map.
912    pub fn new() -> Self {
913        Self::default()
914    }
915    /// Define a specialization constant with a default value.
916    pub fn define(&mut self, name: &str, default_value: &str, description: &str) {
917        self.constants.push(SpecializationConstant::new(
918            name,
919            default_value,
920            description,
921        ));
922    }
923    /// Override a constant's value.
924    pub fn set(&mut self, name: &str, value: &str) {
925        self.overrides.insert(name.to_string(), value.to_string());
926    }
927    /// Get the effective value for a constant (override or default).
928    pub fn get(&self, name: &str) -> Option<&str> {
929        if let Some(v) = self.overrides.get(name) {
930            return Some(v.as_str());
931        }
932        for c in &self.constants {
933            if c.name == name {
934                return Some(c.default_value.as_str());
935            }
936        }
937        None
938    }
939    /// Return the number of defined constants.
940    pub fn len(&self) -> usize {
941        self.constants.len()
942    }
943    /// Check if there are no constants defined.
944    pub fn is_empty(&self) -> bool {
945        self.constants.is_empty()
946    }
947    /// Apply specialization constants to a shader source by replacing
948    /// `const {NAME} = {DEFAULT};` patterns with overridden values.
949    pub fn apply(&self, source: &str) -> String {
950        let mut result = source.to_string();
951        for c in &self.constants {
952            let value = self
953                .overrides
954                .get(&c.name)
955                .map(|s| s.as_str())
956                .unwrap_or(&c.default_value);
957            let old = format!("const {} = {};", c.name, c.default_value);
958            let new = format!("const {} = {};", c.name, value);
959            result = result.replace(&old, &new);
960        }
961        result
962    }
963}
964/// Description of a uniform buffer binding.
965#[derive(Debug, Clone)]
966pub struct UniformBufferDesc {
967    /// Name of the uniform block.
968    pub name: String,
969    /// Bind group index.
970    pub group: u32,
971    /// Binding slot within the group.
972    pub binding: u32,
973    /// Size of the uniform buffer in bytes.
974    pub size_bytes: u32,
975}
976impl UniformBufferDesc {
977    /// Create a new uniform buffer description.
978    pub fn new(name: &str, group: u32, binding: u32, size_bytes: u32) -> Self {
979        Self {
980            name: name.to_string(),
981            group,
982            binding,
983            size_bytes,
984        }
985    }
986    /// Generate the WGSL binding annotation string.
987    pub fn wgsl_annotation(&self) -> String {
988        format!(
989            "@group({}) @binding({}) var<uniform> {}: {};",
990            self.group,
991            self.binding,
992            self.name.to_lowercase(),
993            self.name
994        )
995    }
996}
997/// Cache for compiled shader bytecode (mock compiled blobs).
998///
999/// Supports a maximum total byte budget; when the budget is exceeded,
1000/// `evict_oldest` removes the oldest-inserted entry.
1001#[derive(Debug)]
1002pub struct BytecodeShaderCache {
1003    /// Map from shader name to compiled bytecode.
1004    pub cache: HashMap<String, Vec<u8>>,
1005    /// Ordered insertion keys for LRU-like eviction.
1006    pub(super) insertion_order: Vec<String>,
1007    /// Maximum total bytes allowed.
1008    pub max_size: usize,
1009}
1010impl BytecodeShaderCache {
1011    /// Create a new cache with the given byte budget.
1012    pub fn new(max_size: usize) -> Self {
1013        Self {
1014            cache: HashMap::new(),
1015            insertion_order: Vec::new(),
1016            max_size,
1017        }
1018    }
1019    /// Insert (or replace) a shader's compiled bytecode.
1020    ///
1021    /// If the total size would exceed `max_size`, `evict_oldest` is called
1022    /// before inserting.
1023    pub fn insert(&mut self, name: &str, bytecode: Vec<u8>) {
1024        if self.cache.contains_key(name) {
1025            self.insertion_order.retain(|k| k != name);
1026        }
1027        self.cache.insert(name.to_string(), bytecode);
1028        self.insertion_order.push(name.to_string());
1029        while self.total_bytes() > self.max_size && !self.insertion_order.is_empty() {
1030            self.evict_oldest();
1031        }
1032    }
1033    /// Retrieve compiled bytecode by shader name.
1034    pub fn get(&self, name: &str) -> Option<&Vec<u8>> {
1035        self.cache.get(name)
1036    }
1037    /// Evict the oldest cached entry.  No-op if cache is empty.
1038    pub fn evict_oldest(&mut self) {
1039        if let Some(oldest) = self.insertion_order.first().cloned() {
1040            self.insertion_order.remove(0);
1041            self.cache.remove(&oldest);
1042        }
1043    }
1044    /// Total bytes currently stored across all cached entries.
1045    pub fn total_bytes(&self) -> usize {
1046        self.cache.values().map(|v| v.len()).sum()
1047    }
1048    /// Number of cached entries.
1049    pub fn len(&self) -> usize {
1050        self.cache.len()
1051    }
1052    /// True if the cache is empty.
1053    pub fn is_empty(&self) -> bool {
1054        self.cache.is_empty()
1055    }
1056}