Skip to main content

ursus_materials/
strategy.rs

1use crate::error::MaterialError;
2use crate::field::FieldDesc;
3use crate::material::Material;
4use crate::requirements::Requirements;
5use crate::value::MaterialValue;
6use std::collections::HashMap;
7use std::sync::Arc;
8
9/// Interprets a `Material`'s properties for a specific way of shading it.
10/// A strategy describes what GPU fields it needs (`fields`) and how to
11/// resolve each one from a material (`resolve`); it does not decide how
12/// those fields are laid out in memory - that is the responsibility of a
13/// separate packing module (see `pack::aos`) that consumes both methods.
14///
15/// Custom strategies are ordinary implementations of this trait, registered
16/// into a `StrategyRegistry` - no changes to ursus-materials are needed to
17/// add one.
18pub trait ShadingStrategy: Send + Sync {
19    /// Stable name used for lookup in `StrategyRegistry` and stored on
20    /// `Material::strategy`.
21    fn name(&self) -> &'static str;
22
23    /// What this strategy needs from a material to be able to resolve it.
24    fn requirements(&self) -> &Requirements;
25
26    /// The fields this strategy produces, in a fixed order. `resolve()`
27    /// returns one value per field, in this same order.
28    fn fields(&self) -> &[FieldDesc];
29
30    /// Resolves this material's value for every field in `fields()`,
31    /// applying this strategy's own fallbacks for missing properties.
32    fn resolve(&self, material: &Material) -> Result<Vec<MaterialValue>, MaterialError>;
33
34    /// Validates a material's properties against `requirements()`.
35    fn validate(&self, material: &Material) -> Result<(), MaterialError> {
36        self.requirements().validate(material, self.name())
37    }
38}
39
40/// Registry of available shading strategies, looked up by name.
41#[derive(Default)]
42pub struct StrategyRegistry {
43    by_name: HashMap<&'static str, Arc<dyn ShadingStrategy>>,
44}
45
46impl StrategyRegistry {
47    pub fn register(&mut self, strategy: impl ShadingStrategy + 'static) {
48        let name = strategy.name();
49        if self.by_name.insert(name, Arc::new(strategy)).is_some() {
50            log::warn!("StrategyRegistry: strategy '{name}' was already registered, overwriting");
51        }
52    }
53
54    pub fn by_name(&self, name: &str) -> Option<Arc<dyn ShadingStrategy>> {
55        self.by_name.get(name).cloned()
56    }
57
58    /// Looks up the strategy named by `material.strategy`, validates the
59    /// material against it, and resolves it.
60    pub fn resolve(&self, material: &Material) -> Result<Vec<MaterialValue>, MaterialError> {
61        let strategy = self
62            .by_name(material.strategy)
63            .ok_or_else(|| MaterialError::UnknownStrategy(material.strategy.to_string()))?;
64        strategy.validate(material)?;
65        strategy.resolve(material)
66    }
67}