use std::borrow::Cow;
#[cfg(any(backend_dx, backend_vk))]
use concinnity_core::platform::Platform;
use concinnity_core::render::error::RenderResult;
#[cfg(any(backend_dx, backend_vk))]
use concinnity_core::render::shader_programs::{ShaderProgram, Variant};
use concinnity_core::render::shader_source::source_digest;
#[cfg(any(backend_dx, backend_vk))]
use concinnity_shader::HlslTarget;
use super::cache::{self, Key};
pub(crate) type Embedded = (u64, &'static [u8]);
pub(crate) fn fetch(
label: &str,
source: &str,
embedded: Option<Embedded>,
key: Option<&Key<'_>>,
compile: impl FnOnce() -> RenderResult<Vec<u8>>,
) -> RenderResult<Cow<'static, [u8]>> {
if let Some((digest, bytes)) = embedded
&& digest == source_digest(source)
{
return Ok(Cow::Borrowed(bytes));
}
let compiled = match key {
Some(key) => cache::cached(key, label, compile),
None => compile(),
};
compiled.map(Cow::Owned).map_err(|e| e.context(label))
}
#[cfg(any(backend_dx, backend_vk))]
pub(crate) trait Program {
fn variant(&self) -> Variant<'_>;
}
#[cfg(any(backend_dx, backend_vk))]
impl Program for ShaderProgram {
fn variant(&self) -> Variant<'_> {
debug_assert!(
!self.msaa,
"{} reads the sample count; compile it through `at`",
self.label
);
self.at(false)
}
}
#[cfg(any(backend_dx, backend_vk))]
impl Program for Variant<'_> {
fn variant(&self) -> Variant<'_> {
*self
}
}
#[cfg(any(backend_dx, backend_vk))]
pub(crate) struct Backend {
pub platform: Platform,
pub target: fn(&ShaderProgram) -> HlslTarget,
pub embedded: fn(&str) -> Option<Embedded>,
}
#[cfg(any(backend_dx, backend_vk))]
impl Backend {
pub(crate) fn source(&self, v: Variant<'_>, hot_reload: bool) -> String {
super::source::assemble_variant(hot_reload, self.platform, v)
}
pub(crate) fn cache_key<'a>(&self, v: Variant<'_>, source: &'a str) -> Key<'a> {
Key {
compiler: super::compile::COMPILER_TAG,
source,
entry: v.program.entry,
target: (self.target)(v.program).name(),
}
}
pub(crate) fn compile(&self, v: Variant<'_>, hot_reload: bool) -> RenderResult<Vec<u8>> {
let source = self.source(v, hot_reload);
let name = v.artifact_name();
let key = self.cache_key(v, &source);
fetch(&name, &source, (self.embedded)(&name), Some(&key), || {
super::compile::compile(
v.program.file,
v.program.entry,
&source,
(self.target)(v.program),
)
})
.map(Cow::into_owned)
}
}
#[cfg(test)]
mod tests {
use super::*;
use concinnity_core::render::error::RenderError;
const SOURCE: &str = "#define A 1\nBODY\n";
static EMBEDDED: &[u8] = b"embedded bytes";
fn no_compile() -> RenderResult<Vec<u8>> {
panic!("a matching embedded artifact reached the compiler")
}
#[test]
fn a_matching_embedded_artifact_is_taken_without_a_compile() {
let embedded = Some((source_digest(SOURCE), EMBEDDED));
let bytes = fetch("p", SOURCE, embedded, None, no_compile).unwrap();
assert!(matches!(bytes, Cow::Borrowed(b) if b == EMBEDDED));
}
#[test]
fn an_edited_source_compiles_instead_of_taking_the_embedded_copy() {
let embedded = Some((source_digest("#define A 0\nBODY\n"), EMBEDDED));
let bytes = fetch("p", SOURCE, embedded, None, || Ok(b"fresh".to_vec())).unwrap();
assert_eq!(&*bytes, b"fresh");
}
#[test]
fn a_program_with_nothing_embedded_compiles() {
let bytes = fetch("p", SOURCE, None, None, || Ok(b"fresh".to_vec())).unwrap();
assert!(matches!(bytes, Cow::Owned(_)));
}
#[test]
fn a_failed_compile_names_the_program() {
let err = fetch("the_program", SOURCE, None, None, || {
Err(RenderError::ShaderCompile("bad".into()))
})
.unwrap_err();
assert!(err.to_string().contains("the_program"), "{err}");
}
}