use concinnity_core::components::compiled_programs::CompiledProgram;
use concinnity_core::components::sdf_programs::SdfPrograms;
use concinnity_core::platform::Platform;
use concinnity_core::render::slang_programs::raymarch;
use concinnity_core::render::slang_source;
use concinnity_slang::{SlangJob, SlangTarget};
fn target(platform: Platform, stage: raymarch::Stage) -> SlangTarget {
match platform {
Platform::Metal => SlangTarget::Metal,
Platform::Glsl => SlangTarget::Spirv,
Platform::Hlsl => SlangTarget::Dxil(match stage {
raymarch::Stage::Vertex => "vs_6_0",
raymarch::Stage::Fragment => "ps_6_0",
}),
}
}
pub(super) fn compile(
name: &str,
field: &str,
platform: Platform,
volumetric: bool,
cast_shadows: bool,
) -> std::io::Result<SdfPrograms> {
let work = concinnity_host::scratch::Scratch::dir(&format!("sdf-{name}"))?;
let mut programs = Vec::new();
for family in raymarch::families(volumetric, cast_shadows) {
let source = raymarch::source(family, platform, field);
let digest = slang_source::source_digest(&source);
for group in entry_groups(family, platform) {
let entries: Vec<&str> = group.iter().map(|p| p.entry).collect();
let job = SlangJob {
source: &source,
file_name: raymarch::FILE,
entries: &entries,
target: target(platform, group[0].stage),
};
let artifact = concinnity_slang::compile(&job, work.path()).map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!(
"SdfVolume '{name}': compiling '{}': {e}",
entries.join(", ")
),
)
})?;
programs.push(CompiledProgram {
entries: entries.iter().map(|e| e.to_string()).collect(),
source_digest: digest,
artifact,
});
}
}
Ok(SdfPrograms {
field: field.to_string(),
programs,
})
}
fn entry_groups(
family: raymarch::Family,
platform: Platform,
) -> Vec<Vec<&'static raymarch::Program>> {
let of_family: Vec<&raymarch::Program> = raymarch::ALL
.iter()
.filter(|p| p.family == family)
.collect();
match platform {
Platform::Metal => vec![of_family],
_ => of_family.into_iter().map(|p| vec![p]).collect(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn each_host_emits_what_its_renderer_consumes() {
assert_eq!(
target(Platform::Metal, raymarch::Stage::Fragment),
SlangTarget::Metal
);
assert_eq!(
target(Platform::Glsl, raymarch::Stage::Vertex),
SlangTarget::Spirv
);
}
#[test]
fn the_dxil_profile_follows_the_stage() {
assert_eq!(
target(Platform::Hlsl, raymarch::Stage::Vertex),
SlangTarget::Dxil("vs_6_0")
);
assert_eq!(
target(Platform::Hlsl, raymarch::Stage::Fragment),
SlangTarget::Dxil("ps_6_0")
);
}
#[test]
fn metal_groups_a_family_into_one_artifact_and_the_others_split_it() {
let metal = entry_groups(raymarch::Family::Surface, Platform::Metal);
assert_eq!(metal.len(), 1);
assert_eq!(
metal[0].iter().map(|p| p.entry).collect::<Vec<_>>(),
["raymarch_vertex", "raymarch_fragment"]
);
for platform in [Platform::Hlsl, Platform::Glsl] {
let split = entry_groups(raymarch::Family::Shadow, platform);
assert_eq!(split.len(), 2, "{platform:?}");
assert!(split.iter().all(|g| g.len() == 1), "{platform:?}");
}
}
#[test]
fn a_grouping_covers_every_entry_of_its_family_once() {
for family in [
raymarch::Family::Surface,
raymarch::Family::Volumetric,
raymarch::Family::Shadow,
] {
let expected: Vec<&str> = raymarch::ALL
.iter()
.filter(|p| p.family == family)
.map(|p| p.entry)
.collect();
for platform in [Platform::Metal, Platform::Hlsl, Platform::Glsl] {
let mut got: Vec<&str> = entry_groups(family, platform)
.iter()
.flatten()
.map(|p| p.entry)
.collect();
got.sort_unstable();
let mut want = expected.clone();
want.sort_unstable();
assert_eq!(got, want, "{family:?} on {platform:?}");
}
}
}
}