Skip to main content

primase_core/shader/
derivation.rs

1//! `core.shader_derivation`: a program derived from a material's source stage to compute one quantity of its output.
2use std::sync::Arc;
3
4use iridia_interface::{Label, RenderState};
5
6use crate::shader::{ConstantBinding, InputBinding, Shader, StageBinding, TextureSource};
7use crate::{Component, Entity, Geometry, World};
8
9/// `core.shader_derivation`, on a material entity: a program a pass derived from one of the material's source stages
10/// to compute one quantity that stage's output contains (an albedo inside a lit colour), for a consumer that draws that
11/// quantity. It is not a [`super::ShaderSpecialisations`] stage: a specialisation is the source program folded on the
12/// binding's stated words and still computes the source's outputs, where a derivation computes something else, cut out
13/// of the source program; what it cut (lighting, fog, tone mapping) is in its notes and what it computes is its stage's
14/// outputs. The material's `core.shader_binding` and the source `core.shader` stay as they are, as provenance. A
15/// derivation its pass could not make keeps the reason instead (`result` is `Err`), so a consumer does not draw the
16/// source program in its place.
17#[derive(Clone, Debug)]
18pub struct ShaderDerivation {
19    /// The derivation's name and version, its producer's (`<game>.generic-albedo.v1`).
20    pub name: String,
21    pub result: Result<DerivedStage, String>,
22}
23
24/// A derived program and how it is drawn.
25#[derive(Clone, Debug)]
26pub struct DerivedStage {
27    /// The binding the derived program is drawn with: `shader` names the source program it was derived from, the
28    /// inputs and outputs are the derived program's, the constants and textures the material's.
29    pub stage: StageBinding,
30    /// The render state it is drawn with.
31    pub state: RenderState,
32    pub program: Arc<iridia::Program>,
33    /// What the derivation cut from the source program or leaves unresolved, and which source it was derived from.
34    pub notes: Vec<String>,
35    /// A per-vertex program supplying one of `program`'s inputs, when the derivation needs one.
36    pub vertex_input: Option<DerivedInput>,
37}
38
39/// A program derived from a source vertex stage that supplies one float input of a derived pixel program (at most four
40/// lanes): evaluated once per vertex on the mesh's streams, then interpolated as the source vertex program's output is.
41#[derive(Clone, Debug)]
42pub struct DerivedInput {
43    /// The derived pixel program's input it supplies.
44    pub target: Label,
45    /// The derivation's name and version, its producer's.
46    pub name: String,
47    pub program: Arc<iridia::Program>,
48    /// What the derivation cut from the source program or leaves unresolved, and which source it was derived from.
49    pub notes: Vec<String>,
50    /// Its inputs by role.
51    pub inputs: Vec<InputBinding>,
52    /// Its constants by role.
53    pub constants: Vec<ConstantBinding>,
54}
55
56impl Component for ShaderDerivation {
57    const NAME: &'static str = "core.shader_derivation";
58    const GEOMETRY: Geometry = Geometry::None;
59    fn references(&self, out: &mut Vec<Entity>) {
60        if let Ok(derived) = &self.result {
61            out.push(derived.stage.shader);
62            for t in &derived.stage.textures {
63                if let TextureSource::Texture { texture, .. } = &t.source {
64                    out.push(*texture);
65                }
66            }
67        }
68    }
69    fn to_json(&self) -> Result<Option<serde_json::Value>, crate::JsonError> {
70        let result = match &self.result {
71            Ok(d) => serde_json::json!({
72                "shader": d.stage.shader,
73                "variant": d.stage.variant,
74                "outputs": d.stage.outputs,
75                "notes": d.notes,
76                "vertex_input": d.vertex_input.as_ref().map(|v| serde_json::json!({
77                    "target": v.target, "name": v.name, "notes": v.notes,
78                })),
79            }),
80            Err(why) => serde_json::json!({ "refusal": why }),
81        };
82        crate::json::to_value(&serde_json::json!({ "name": self.name, "result": result })).map(Some)
83    }
84    fn verify(&self, _entity: Entity, world: &World) -> Result<(), String> {
85        if self.name.is_empty() {
86            return Err("derivation names no derivation".into());
87        }
88        let Ok(derived) = &self.result else {
89            return Ok(());
90        };
91        if world.get::<Shader>(derived.stage.shader).is_none() {
92            return Err("derivation names a source stage whose shader entity has no core.shader".into());
93        }
94        if derived.program.stage != derived.stage.stage {
95            return Err("derived program is not a program of its stage's kind".into());
96        }
97        derived.program.validate().map_err(|e| format!("{e:?}"))?;
98        if let Some(input) = &derived.vertex_input {
99            if input.name.is_empty() {
100                return Err("derived input names no derivation".into());
101            }
102            if !derived
103                .program
104                .interface
105                .inputs
106                .iter()
107                .any(|v| v.label == input.target)
108            {
109                return Err("derived input supplies no input of the derived program".into());
110            }
111            input.program.validate().map_err(|e| format!("{e:?}"))?;
112        }
113        Ok(())
114    }
115    fn apply_linear(&self, _: &crate::Linear) -> crate::world::Linearity<Self> {
116        crate::world::Linearity::Invariant
117    }
118}