Skip to main content

concinnity_core/components/
compiled_programs.rs

1//! One compiled shader artifact and the digest-keyed lookup over a set of
2//! them, shared by every asset whose shader source is only complete once a
3//! world is loaded: an `SdfVolume`'s distance field and a `Shader`'s hooks.
4//!
5//! The cook runs slangc and stores what it emitted; the renderer assembles the
6//! source it expects, digests it, and takes a stored artifact only on a match.
7//! A hot-reload edit to an engine template misses every entry and recompiles,
8//! which is the behaviour that makes editing one possible at all.
9
10use alloc::string::String;
11use alloc::vec::Vec;
12
13/// One compiled artifact, the entries it holds, and the source it came from.
14///
15/// An artifact carries more than one entry where the target allows it: slangc
16/// emits one MSL translation unit for a pair of stages, and the Metal runtime
17/// wants both in one library. DXIL has no such form, so a container there
18/// holds exactly one.
19#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
20pub struct CompiledProgram {
21    /// Entry point names this artifact holds, as the shader source spells them.
22    pub entries: Vec<String>,
23    /// `slang_source::source_digest` of the assembled source this artifact was
24    /// built from. A renderer whose assembly digests differently has a template
25    /// the artifact predates and must compile rather than load.
26    pub source_digest: u64,
27    /// The emitted artifact: SPIR-V, a signed DXIL container, or MSL text.
28    pub artifact: Vec<u8>,
29}
30
31/// The artifact holding `entry`, if one was compiled from source matching
32/// `digest`. A mismatch is a stale artifact and reads as absent.
33pub fn artifact<'a>(programs: &'a [CompiledProgram], entry: &str, digest: u64) -> Option<&'a [u8]> {
34    programs
35        .iter()
36        .find(|p| p.source_digest == digest && p.entries.iter().any(|e| e == entry))
37        .map(|p| p.artifact.as_slice())
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43    use alloc::string::ToString;
44    use alloc::vec;
45
46    fn programs() -> Vec<CompiledProgram> {
47        vec![
48            CompiledProgram {
49                entries: vec!["vertex_main".to_string()],
50                source_digest: 7,
51                artifact: vec![1, 2, 3],
52            },
53            // One artifact holding both stages, the shape the Metal target
54            // takes: a library the runtime pulls two functions out of.
55            CompiledProgram {
56                entries: vec![
57                    "vertex_main_bindless".to_string(),
58                    "fragment_main_bindless".to_string(),
59                ],
60                source_digest: 9,
61                artifact: vec![4, 5],
62            },
63        ]
64    }
65
66    #[test]
67    fn an_entry_is_found_under_its_own_digest_only() {
68        let p = programs();
69        assert_eq!(artifact(&p, "vertex_main", 7), Some(&[1u8, 2, 3][..]));
70        assert_eq!(artifact(&p, "vertex_main", 8), None, "stale digest");
71        assert_eq!(artifact(&p, "no_such_entry", 7), None);
72    }
73
74    #[test]
75    fn a_shared_artifact_is_found_under_either_entry() {
76        let p = programs();
77        assert_eq!(artifact(&p, "vertex_main_bindless", 9), Some(&[4u8, 5][..]));
78        assert_eq!(
79            artifact(&p, "fragment_main_bindless", 9),
80            Some(&[4u8, 5][..])
81        );
82    }
83
84    #[test]
85    fn a_program_round_trips_through_postcard() {
86        let p = programs();
87        let bytes = postcard::to_allocvec(&p).unwrap();
88        let back: Vec<CompiledProgram> = postcard::from_bytes(&bytes).unwrap();
89        assert_eq!(back, p);
90    }
91}