use concinnity_core::components::sdf_programs::SdfPrograms;
use concinnity_core::platform::Platform;
use concinnity_core::render::error::RenderResult;
use concinnity_core::render::shader_programs::raymarch::{self, Family};
use concinnity_core::render::shader_source::{self, SourceFile};
use std::borrow::Cow;
pub(crate) fn decode(payload: &[u8], label: &str) -> Result<SdfPrograms, String> {
SdfPrograms::decode(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.text)
}
pub(crate) struct Request<'a> {
pub family: Family,
pub platform: Platform,
pub entry: &'a str,
pub hot_reload: bool,
pub label: &'a str,
}
pub(crate) fn artifact<'a>(
programs: &'a SdfPrograms,
req: &Request<'_>,
compile: impl FnOnce(Platform, &str, &str, &str) -> RenderResult<Vec<u8>>,
) -> RenderResult<Cow<'a, [u8]>> {
let Request { label, entry, .. } = *req;
let source = source(
req.family,
req.platform,
programs.field.as_file(),
req.hot_reload,
);
let digest = shader_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");
compile(req.platform, raymarch::FILE, entry, &source)
.map(Cow::Owned)
.map_err(|e| e.context(format_args!("SdfVolume '{label}': compiling '{entry}'")))
}
#[cfg(any(backend_dx, backend_vk))]
pub(crate) fn family_artifacts(
programs: &SdfPrograms,
family: Family,
platform: Platform,
hot_reload: bool,
label: &str,
) -> RenderResult<(Vec<u8>, Vec<u8>)> {
let mut stages = raymarch::ALL.iter().filter(|p| p.family == family);
let mut stage = |which: &str| -> RenderResult<Vec<u8>> {
let entry = stages
.next()
.unwrap_or_else(|| panic!("a family declares a {which} entry"))
.entry;
entry_artifact(programs, family, entry, platform, hot_reload, label)
};
let vertex = stage("vertex")?;
let fragment = stage("fragment")?;
Ok((vertex, fragment))
}
#[cfg(any(backend_dx, backend_vk))]
pub(crate) fn face_artifacts(
programs: &SdfPrograms,
family: Family,
faces: raymarch::ProxyFaces,
platform: Platform,
hot_reload: bool,
label: &str,
) -> RenderResult<(Vec<u8>, Vec<u8>)> {
let missing = || {
concinnity_core::render::error::RenderError::Other(format!(
"no {faces:?}-face entry for {family:?}"
))
};
let vertex = raymarch::ALL
.iter()
.find(|p| p.family == family)
.ok_or_else(missing)?
.entry;
let fragment = faces.fragment(family).ok_or_else(missing)?;
Ok((
entry_artifact(programs, family, vertex, platform, hot_reload, label)?,
entry_artifact(programs, family, fragment, platform, hot_reload, label)?,
))
}
#[cfg(any(backend_dx, backend_vk))]
pub(crate) fn entry_artifact(
programs: &SdfPrograms,
family: Family,
entry: &str,
platform: Platform,
hot_reload: bool,
label: &str,
) -> RenderResult<Vec<u8>> {
let req = Request {
family,
platform,
entry,
hot_reload,
label,
};
artifact(programs, &req, crate::shader::compile::cooked).map(Cow::into_owned)
}
fn source(family: Family, platform: Platform, field: SourceFile<'_>, hot_reload: bool) -> String {
if !hot_reload {
return raymarch::source(family, platform, field);
}
raymarch::source_with(
family,
platform,
field,
crate::shader::source::from_checkout,
)
}
#[cfg(test)]
mod tests {
use super::*;
use concinnity_core::components::ShaderSource;
use concinnity_core::components::compiled_programs::CompiledProgram;
use concinnity_core::render::error::RenderError;
const FIELD: SourceFile<'static> = SourceFile {
path: "shaders/blob.hlsl",
text: "// a field",
};
fn stored(family: Family, platform: Platform, entry: &str, bytes: &[u8]) -> SdfPrograms {
let src = raymarch::source(family, platform, FIELD);
SdfPrograms {
field: ShaderSource {
path: FIELD.path.to_string(),
text: FIELD.text.to_string(),
},
programs: vec![CompiledProgram {
entry: entry.to_string(),
source_digest: shader_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",
b"stored bytes",
);
let got = artifact(&programs, &request("raymarch_vertex"), |_, _, _, _| {
panic!("a matching artifact reached the compiler")
})
.expect("stored artifact");
assert_eq!(got.as_ref(), b"stored bytes");
assert!(matches!(got, Cow::Borrowed(_)), "no compile was needed");
}
#[test]
fn a_stale_artifact_compiles_the_assembled_source_and_names_the_volume() {
let mut programs = stored(
Family::Surface,
Platform::Metal,
"raymarch_vertex",
b"stored bytes",
);
programs.programs[0].source_digest ^= 1;
let want = raymarch::source(Family::Surface, Platform::Metal, FIELD);
let got = artifact(
&programs,
&request("raymarch_vertex"),
|platform, file, entry, src| {
assert_eq!(platform, Platform::Metal);
assert_eq!((file, entry), (raymarch::FILE, "raymarch_vertex"));
assert_eq!(src, want);
Ok(b"fresh".to_vec())
},
)
.expect("compiled");
assert_eq!(got.as_ref(), b"fresh");
let err = artifact(&programs, &request("raymarch_vertex"), |_, _, _, _| {
Err(RenderError::ShaderCompile("no compiler".to_string()))
})
.unwrap_err()
.to_string();
assert!(err.contains("SdfVolume 'blob'"), "got: {err}");
assert!(err.contains("raymarch_vertex"), "got: {err}");
assert!(err.contains("no compiler"), "got: {err}");
}
fn request(entry: &str) -> Request<'_> {
Request {
family: Family::Surface,
platform: Platform::Metal,
entry,
hot_reload: false,
label: "blob",
}
}
#[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::DirectX, FIELD);
assert!(
metal_surface
.artifact(
"raymarch_vertex",
shader_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",
shader_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 = shader_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), "'{}' calls nothing", FIELD.text);
programs.field.text = 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() {
concinnity_shader::require_dxc!();
let work = concinnity_host::scratch::Scratch::dir("raymarch-compile-guard")
.expect("scratch directory");
for platform in Platform::ALL {
let target = concinnity_shader::HlslTarget::cooked(platform);
for family in [
Family::Surface,
Family::Volumetric,
Family::Shadow,
Family::Prepass,
] {
let text = if family == Family::Volumetric {
VOLUMETRIC_FIELD
} else {
SURFACE_FIELD
};
let field = SourceFile {
path: FIELD.path,
text,
};
let source = raymarch::source(family, platform, field);
for program in raymarch::ALL.iter().filter(|p| p.family == family) {
let job = concinnity_shader::HlslJob {
source: &source,
file_name: raymarch::FILE,
entry: program.entry,
target,
};
concinnity_shader::compile(&job, work.path()).unwrap_or_else(|e| {
panic!("{:?}/{:?} {}: {e}", platform, family, program.entry)
});
}
}
}
}
}