#![deny(unsafe_op_in_unsafe_fn)]
use std::borrow::Cow;
use concinnity_core::platform::Platform;
use concinnity_core::render::error::{RenderError, RenderResult};
pub(super) use concinnity_core::render::shader_programs::ShaderProgram;
use concinnity_core::render::shader_programs::Variant;
pub(super) use concinnity_core::render::shader_programs::metal::{
HIZ_DOWNSAMPLE, HIZ_INIT_MSAA, HIZ_INIT_SINGLE,
};
pub(super) use concinnity_core::render::shader_programs::shared::*;
use concinnity_shader::HlslTarget;
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_metal::{MTLComputePipelineState, MTLDevice, MTLFunction, MTLLibrary};
use super::pipeline::{load_library, ns_str};
fn variant(program: &ShaderProgram) -> Variant<'_> {
program.at(false)
}
fn source(program: &ShaderProgram, hot_reload: bool) -> String {
crate::shader::source::assemble_variant(hot_reload, Platform::Metal, variant(program))
}
fn library(
device: &ProtocolObject<dyn MTLDevice>,
program: &ShaderProgram,
hot_reload: bool,
) -> RenderResult<Retained<ProtocolObject<dyn MTLLibrary>>> {
let source = source(program, hot_reload);
let name = variant(program).artifact_name();
let key = super::msl_cache::metallib_key(&source, program.entry);
let bytes = crate::shader::builtin::fetch(
&name,
&source,
super::metallib::embedded_metallib(&name),
key.as_ref(),
|| {
crate::shader::compile::compile(
program.file,
program.entry,
&source,
HlslTarget::Metallib,
)
},
)?;
let origin = match bytes {
Cow::Borrowed(_) => "precompiled metallib",
Cow::Owned(_) => "compiled metallib",
};
load_library(device, &bytes).map_err(|e| e.context(format_args!("{name}: {origin}")))
}
pub(super) fn entry_function(
device: &ProtocolObject<dyn MTLDevice>,
program: &ShaderProgram,
hot_reload: bool,
) -> RenderResult<Retained<ProtocolObject<dyn MTLFunction>>> {
let library = library(device, program, hot_reload)?;
library
.newFunctionWithName(&ns_str(program.entry))
.ok_or_else(|| {
RenderError::ShaderCompile(format!("{} not found in {}", program.entry, program.label))
})
}
pub(super) fn compute_pipeline(
device: &ProtocolObject<dyn MTLDevice>,
program: &ShaderProgram,
hot_reload: bool,
) -> RenderResult<Retained<ProtocolObject<dyn MTLComputePipelineState>>> {
let function = entry_function(device, program, hot_reload)?;
device
.newComputePipelineStateWithFunction_error(&function)
.map_err(|e| RenderError::ShaderCompile(format!("{} pipeline: {e:?}", program.label)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn variants_assemble_source_with_their_defines() {
use concinnity_core::render::shader_programs::metal::TABLE;
assert_eq!(
TABLE.msaa,
[false],
"the build script embeds what `variant` asks for"
);
for program in TABLE.programs() {
for hot_reload in [false, true] {
let src = source(program, hot_reload);
assert!(!src.trim().is_empty(), "{}: empty source", program.label);
for (k, v) in variant(program).defines() {
assert!(
src.starts_with('#') && src.contains(&format!("#define {k} {v}\n")),
"{}: missing injected define {k}",
program.label
);
}
}
}
}
}