1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
//! Runtime behavior for the Shader asset. The authored schema (Shader,
//! StageSource, ShaderKind, and the ShaderPayload container) lives in
//! concinnity-asset; this file keeps the `Component` impl and the
//! `StageSourceExt::current_platform_source` extension the engine init and
//! hot-reload paths use. The JSON-args source selection and validation live in
//! concinnity-world (`source_args`, `check::shader`).
use alloc::string::String;
pub use concinnity_asset::{Shader, ShaderKind, ShaderPayload, StageSource};
use crate::ecs::{Component, PayloadLocator};
/// Resolve the source filename for the current build platform from a stage's
/// declared `source` / `sources`. Mirrors the build-time selection
/// (concinnity-world `source_args`) so the hot-reload subsystem picks the
/// same per-platform source the build read at compile time. Returns `None` when
/// no current-platform source is declared (e.g. a stage that only declares `glsl`
/// running on the Metal backend, which loads the embedded GLSL fallback at init
/// and has no on-disk file to hot-reload). Exposed as an extension trait because
/// the schema type lives in concinnity-asset.
pub trait StageSourceExt {
/// The source path declared for the running platform, or `None` when the
/// stage declares none.
fn current_platform_source(&self) -> Option<String>;
}
impl StageSourceExt for StageSource {
fn current_platform_source(&self) -> Option<String> {
let platform = crate::platform::Platform::current();
if let Some(sources) = &self.sources
&& let Some(src) = sources.get(platform.key())
{
return Some(src.clone());
}
if self.source.is_empty() {
return None;
}
let ext = super::path_extension(&self.source).unwrap_or("");
if platform.accepts_ext(ext) {
Some(self.source.clone())
} else {
None
}
}
}
impl Component for Shader {
const NAME: &'static str = "Shader";
fn from_baked(bytes: &[u8]) -> Result<Self, crate::result::CnResult> {
Ok(crate::blob::decode_exact(bytes)?)
}
fn inject_locator(&mut self, locator: PayloadLocator) {
self.locator = Some(locator);
}
fn inject_name(&mut self, id: crate::ecs::asset_id::AssetId) {
self.asset_id = id;
}
}
/// Returns the platform key used to look up entries in the `sources` map.
pub fn platform_key() -> &'static str {
crate::platform::Platform::current().key()
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
#[test]
fn compile_kind_maps_each_stage() {
assert_eq!(ShaderKind::Vertex.compile_kind(), "vertex");
assert_eq!(ShaderKind::VertexInstanced.compile_kind(), "vertex");
assert_eq!(ShaderKind::Fragment.compile_kind(), "fragment");
assert_eq!(ShaderKind::default(), ShaderKind::Vertex);
}
#[test]
fn current_platform_source_resolves_for_any_backend() {
// Declaring every platform source resolves on whichever backend the
// test build targets.
let stage = StageSource {
sources: Some(
[
("metal".to_string(), "v.metal".to_string()),
("hlsl".to_string(), "v.hlsl".to_string()),
("glsl".to_string(), "v.glsl".to_string()),
]
.into_iter()
.collect(),
),
..Default::default()
};
assert!(stage.current_platform_source().is_some());
}
#[test]
fn single_source_resolves_only_for_matching_extensions() {
let stage = StageSource {
source: "v.metal".to_string(),
sources: None,
};
let platform = crate::platform::Platform::current();
assert_eq!(
stage.current_platform_source().is_some(),
platform.accepts_ext("metal")
);
}
}