use std::borrow::Cow;
use concinnity_core::components::sdf_programs::SdfPrograms;
use concinnity_core::platform::Platform;
use concinnity_core::render::slang_programs::raymarch::{self, Family};
use concinnity_core::render::slang_source;
use concinnity_slang::{SlangJob, SlangTarget};
pub(crate) fn decode(payload: &[u8], label: &str) -> Result<SdfPrograms, String> {
postcard::from_bytes(payload)
.map_err(|e| format!("SdfVolume '{label}': compiled field does not decode: {e}"))
}
pub(crate) fn taps_scene(programs: &SdfPrograms) -> bool {
raymarch::field_taps_scene(&programs.field)
}
pub(crate) struct Request<'a> {
pub family: Family,
pub platform: Platform,
pub entries: &'a [&'a str],
pub target: SlangTarget,
pub hot_reload: bool,
pub label: &'a str,
}
pub(crate) fn artifact<'a>(
programs: &'a SdfPrograms,
req: &Request<'_>,
) -> Result<Cow<'a, [u8]>, String> {
let label = req.label;
let entry = req.entries.first().copied().unwrap_or_default();
let source = source(req.family, req.platform, &programs.field, req.hot_reload);
let digest = slang_source::source_digest(&source);
if let Some(bytes) = programs.artifact(entry, digest) {
return Ok(Cow::Borrowed(bytes));
}
tracing::debug!("SdfVolume '{label}': {entry} predates the engine template, compiling");
let job = SlangJob {
source: &source,
file_name: raymarch::FILE,
entries: req.entries,
target: req.target,
};
let work = concinnity_host::scratch::Scratch::dir(&format!("sdf-{label}"))
.map_err(|e| format!("SdfVolume '{label}': no scratch directory: {e}"))?;
concinnity_slang::compile(&job, work.path())
.map(Cow::Owned)
.map_err(|e| format!("SdfVolume '{label}': compiling '{entry}': {e}"))
}
fn source(family: Family, platform: Platform, field: &str, hot_reload: bool) -> String {
if !hot_reload {
return raymarch::source(family, platform, field);
}
raymarch::source_with(family, platform, field, crate::slang_source::from_checkout)
}
#[cfg(test)]
mod tests {
use super::*;
use concinnity_core::components::compiled_programs::CompiledProgram;
const FIELD: &str = "// a field";
fn stored(family: Family, platform: Platform, entries: &[&str], bytes: &[u8]) -> SdfPrograms {
let src = raymarch::source(family, platform, FIELD);
SdfPrograms {
field: FIELD.to_string(),
programs: vec![CompiledProgram {
entries: entries.iter().map(|e| e.to_string()).collect(),
source_digest: slang_source::source_digest(&src),
artifact: bytes.to_vec(),
}],
}
}
#[test]
fn a_matching_artifact_is_taken_without_compiling() {
let programs = stored(
Family::Surface,
Platform::Metal,
&["raymarch_vertex", "raymarch_fragment"],
b"stored bytes",
);
let got = artifact(
&programs,
&Request {
family: Family::Surface,
platform: Platform::Metal,
entries: &["raymarch_vertex", "raymarch_fragment"],
target: SlangTarget::Metal,
hot_reload: false,
label: "blob",
},
)
.expect("stored artifact");
assert_eq!(got.as_ref(), b"stored bytes");
assert!(matches!(got, Cow::Borrowed(_)), "no compile was needed");
}
#[test]
fn an_artifact_from_another_host_or_family_does_not_match() {
let metal_surface = stored(
Family::Surface,
Platform::Metal,
&["raymarch_vertex"],
b"stored bytes",
);
let src_other_host = raymarch::source(Family::Surface, Platform::Hlsl, FIELD);
assert!(
metal_surface
.artifact(
"raymarch_vertex",
slang_source::source_digest(&src_other_host)
)
.is_none()
);
let src_other_family = raymarch::source(Family::Shadow, Platform::Metal, FIELD);
assert!(
metal_surface
.artifact(
"raymarch_vertex",
slang_source::source_digest(&src_other_family)
)
.is_none()
);
}
#[test]
fn an_entry_the_cook_did_not_emit_is_absent() {
let programs = stored(
Family::Surface,
Platform::Metal,
&["raymarch_vertex"],
b"stored bytes",
);
let src = raymarch::source(Family::Surface, Platform::Metal, FIELD);
let digest = slang_source::source_digest(&src);
assert!(
programs
.artifact("raymarch_shadow_vertex", digest)
.is_none()
);
}
#[test]
fn only_a_volume_whose_field_taps_the_scene_reads_as_refractive() {
let mut programs = stored(
Family::Surface,
Platform::Metal,
&["raymarch_vertex"],
b"stored bytes",
);
assert!(!taps_scene(&programs), "'{FIELD}' calls nothing");
programs.field = SURFACE_FIELD.to_string();
assert!(taps_scene(&programs), "the surface field calls the tap");
}
#[test]
fn a_corrupt_payload_names_the_volume() {
let err = decode(&[0xff, 0xff, 0xff, 0xff], "chrome_blob").unwrap_err();
assert!(err.starts_with("SdfVolume 'chrome_blob':"), "got: {err}");
}
const SURFACE_FIELD: &str = r#"
float map(float3 p, SdfParams params, float time)
{
float3 rp = p + float3(sdf_param(params, 0u), 0.0, 0.0) * time;
return opSmoothUnion(sdSphere(rp, 0.5), sdTorus(rp, float2(0.6, 0.2)), 0.25);
}
SdfSurface shade(float3 p, float3 normal, SdfParams params, float time, float2 frag_uv)
{
SdfSurface s;
s.albedo = float3(0.85, 0.86, 0.88);
s.roughness = clamp(sdf_param(params, 3u), 0.02, 1.0);
s.metallic = 1.0;
s.emissive = float3(0.0, 0.0, 0.0);
// The scene tap, so the guard covers the one declaration an authored field
// can pull in that nothing else references.
s.transmitted = sampleSceneRefracted(frag_uv, normal, 0.05);
return s;
}
"#;
const VOLUMETRIC_FIELD: &str = r#"
VolumeSample sampleVolume(float3 p, SdfParams params, float time)
{
VolumeSample vs;
vs.density = max(0.0, sdf_param(params, 4u) * (0.5 + 0.5 * sin(p.x + time)));
vs.scattering = float3(0.8, 0.8, 0.85);
vs.emission = float3(0.0, 0.0, 0.0);
return vs;
}
"#;
#[test]
fn every_raymarch_entry_compiles_on_every_backend() {
if !crate::slangc_gate::slangc_available() {
return;
}
let work = concinnity_host::scratch::Scratch::dir("raymarch-compile-guard")
.expect("scratch directory");
for platform in [Platform::Metal, Platform::Hlsl, Platform::Glsl] {
let target = |stage| match platform {
Platform::Metal => SlangTarget::Metal,
Platform::Glsl => SlangTarget::Spirv,
Platform::Hlsl => SlangTarget::Hlsl(match stage {
raymarch::Stage::Vertex => "vs_6_0",
raymarch::Stage::Fragment => "ps_6_0",
}),
};
for family in [Family::Surface, Family::Volumetric, Family::Shadow] {
let field = if family == Family::Volumetric {
VOLUMETRIC_FIELD
} else {
SURFACE_FIELD
};
let source = raymarch::source(family, platform, field);
for program in raymarch::ALL.iter().filter(|p| p.family == family) {
let job = SlangJob {
source: &source,
file_name: raymarch::FILE,
entries: &[program.entry],
target: target(program.stage),
};
concinnity_slang::compile(&job, work.path()).unwrap_or_else(|e| {
panic!("{:?}/{:?} {}: {e}", platform, family, program.entry)
});
}
}
}
}
}