use concinnity_core::render::error::{RenderError, RenderResult};
use concinnity_shader::HlslTarget;
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_foundation::NSString;
use objc2_metal::{MTLDevice, MTLFunction, MTLLibrary};
use crate::shader::cache::Key;
pub(super) fn metallib_key<'a>(source: &'a str, entry: &'a str) -> Option<Key<'a>> {
concinnity_shader::metallib::toolchain_id().map(|compiler| Key {
compiler,
source,
entry,
target: HlslTarget::Metallib.name(),
})
}
pub(super) fn compiled_library(
device: &ProtocolObject<dyn MTLDevice>,
source: &str,
label: &str,
embedded: Option<(u64, &'static [u8])>,
) -> RenderResult<Retained<ProtocolObject<dyn MTLLibrary>>> {
match metallib_bytes(source, label, embedded) {
Ok(bytes) => match super::pipeline::load_library(device, &bytes) {
Ok(library) => Ok(library),
Err(e) => {
tracing::warn!("{label}: metallib rejected ({e}), compiling from source");
source_library(device, source)
}
},
Err(e) => {
tracing::debug!("{label}: no metallib ({e}), compiling from source");
source_library(device, source)
}
}
}
fn metallib_bytes(
source: &str,
label: &str,
embedded: Option<(u64, &'static [u8])>,
) -> RenderResult<std::borrow::Cow<'static, [u8]>> {
let key = metallib_key(source, "main");
let compile = || match key {
Some(_) => compile_to_metallib(source, label),
None => Err(RenderError::ShaderCompile("no Metal toolchain".into())),
};
crate::shader::builtin::fetch(label, source, embedded, key.as_ref(), compile)
}
fn msl_text<'a>(msl: &'a [u8], label: &str) -> RenderResult<&'a str> {
std::str::from_utf8(msl)
.map_err(|e| RenderError::ShaderCompile(format!("{label}: artifact is not MSL text: {e}")))
}
pub(super) fn cooked_function(
device: &ProtocolObject<dyn MTLDevice>,
msl: &[u8],
entry: &str,
label: &str,
) -> RenderResult<Retained<ProtocolObject<dyn MTLFunction>>> {
let library = compiled_library(device, msl_text(msl, label)?, label, None)?;
library
.newFunctionWithName(&NSString::from_str(entry))
.ok_or_else(|| RenderError::ShaderCompile(format!("{label}: {entry} not found")))
}
fn source_library(
device: &ProtocolObject<dyn MTLDevice>,
source: &str,
) -> RenderResult<Retained<ProtocolObject<dyn MTLLibrary>>> {
let options = objc2_metal::MTLCompileOptions::new();
device
.newLibraryWithSource_options_error(&NSString::from_str(source), Some(&options))
.map_err(|e| RenderError::ShaderCompile(format!("{e:?}")))
}
fn compile_to_metallib(source: &str, label: &str) -> RenderResult<Vec<u8>> {
let work = crate::shader::compiler_work::dir().map_err(RenderError::Other)?;
concinnity_shader::metallib::compile(source, work.path())
.map_err(|e| RenderError::ShaderCompile(format!("{label}: {e}")))
}