use anyhow::{Context, Result};
use std::ffi::CString;
use std::ptr;
use std::sync::Arc;
use super::ffi::*;
use super::loader::SlangLibrary;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ShaderTarget {
Spirv,
Wgsl,
Hlsl,
Metal,
Glsl,
}
impl ShaderTarget {
fn to_slang_target(self) -> SlangCompileTarget {
match self {
ShaderTarget::Spirv => SlangCompileTarget::Spirv,
ShaderTarget::Wgsl => SlangCompileTarget::Wgsl,
ShaderTarget::Hlsl => SlangCompileTarget::Hlsl,
ShaderTarget::Metal => SlangCompileTarget::Metal,
ShaderTarget::Glsl => SlangCompileTarget::Glsl,
}
}
}
#[derive(Debug, Clone)]
pub struct CompiledShader {
pub data: Vec<u8>,
pub target: ShaderTarget,
}
impl CompiledShader {
pub fn as_str(&self) -> Option<&str> {
match self.target {
ShaderTarget::Wgsl | ShaderTarget::Hlsl | ShaderTarget::Metal | ShaderTarget::Glsl => {
std::str::from_utf8(&self.data).ok()
}
ShaderTarget::Spirv => None,
}
}
pub fn as_spirv(&self) -> Option<&[u32]> {
if self.target == ShaderTarget::Spirv && self.data.len() % 4 == 0 {
Some(bytemuck::cast_slice(&self.data))
} else {
None
}
}
}
pub struct SlangCompiler {
library: Arc<SlangLibrary>,
session: *mut SlangSession,
}
unsafe impl Send for SlangCompiler {}
unsafe impl Sync for SlangCompiler {}
impl SlangCompiler {
pub fn new() -> Result<Self> {
let library = Arc::new(SlangLibrary::load()?);
let session = unsafe { (library.create_session)(ptr::null()) };
if session.is_null() {
anyhow::bail!("Failed to create Slang session");
}
tracing::info!("Slang compiler initialized");
Ok(Self { library, session })
}
pub fn compile(&self, source: &str, target: ShaderTarget) -> Result<CompiledShader> {
self.compile_entry_point(source, target, None)
}
pub fn compile_entry_point(
&self,
source: &str,
target: ShaderTarget,
entry_point: Option<(&str, SlangStage)>,
) -> Result<CompiledShader> {
let entry_points: Vec<(&str, SlangStage)> = match entry_point {
Some(ep) => vec![ep],
None => vec![],
};
self.compile_with_entry_points(source, target, &entry_points)
}
pub fn compile_with_entry_points(
&self,
source: &str,
target: ShaderTarget,
entry_points: &[(&str, SlangStage)],
) -> Result<CompiledShader> {
self.compile_with_options(source, target, entry_points, &[])
}
pub fn compile_with_options(
&self,
source: &str,
target: ShaderTarget,
entry_points: &[(&str, SlangStage)],
search_paths: &[&str],
) -> Result<CompiledShader> {
let request = unsafe { (self.library.create_compile_request)(self.session) };
if request.is_null() {
anyhow::bail!("Failed to create Slang compile request");
}
let _guard = scopeguard::guard(request, |req| {
unsafe { (self.library.destroy_compile_request)(req) };
});
for path in search_paths {
let path_cstr = CString::new(*path).context("Search path contains null bytes")?;
unsafe {
(self.library.add_search_path)(request, path_cstr.as_ptr());
}
}
let target_index = unsafe {
(self.library.add_code_gen_target)(request, target.to_slang_target() as i32)
};
if target_index < 0 {
anyhow::bail!("Failed to add code generation target");
}
let unit_name = CString::new("shader").unwrap();
let translation_unit = unsafe {
(self.library.add_translation_unit)(
request,
SlangSourceLanguage::Slang as i32,
unit_name.as_ptr(),
)
};
if translation_unit < 0 {
anyhow::bail!("Failed to add translation unit");
}
let source_path = CString::new("shader.slang").unwrap();
let source_cstr = CString::new(source).context("Source contains null bytes")?;
unsafe {
(self.library.add_translation_unit_source_string)(
request,
translation_unit,
source_path.as_ptr(),
source_cstr.as_ptr(),
);
}
for (name, stage) in entry_points {
let name_cstr = CString::new(*name).context("Entry point name contains null bytes")?;
let entry_index = unsafe {
(self.library.add_entry_point)(
request,
translation_unit,
name_cstr.as_ptr(),
*stage as i32,
)
};
if entry_index < 0 {
anyhow::bail!("Failed to add entry point: {}", name);
}
}
let result = unsafe { (self.library.compile)(request) };
if !slang_succeeded(result) {
let diag_ptr = unsafe { (self.library.get_diagnostic_output)(request) };
let diagnostic = if !diag_ptr.is_null() {
unsafe { std::ffi::CStr::from_ptr(diag_ptr) }
.to_string_lossy()
.into_owned()
} else {
"Unknown compilation error".to_string()
};
anyhow::bail!("Slang compilation failed:\n{}", diagnostic);
}
let mut blob: *mut ISlangBlob = ptr::null_mut();
let result = unsafe {
(self.library.get_entry_point_code_blob)(request, 0, target_index, &mut blob)
};
if !slang_succeeded(result) || blob.is_null() {
anyhow::bail!("Failed to get compiled shader code");
}
let (data_ptr, data_size) = unsafe { blob_get_data(blob) };
let data = unsafe { std::slice::from_raw_parts(data_ptr, data_size) }.to_vec();
unsafe { blob_release(blob) };
Ok(CompiledShader { data, target })
}
}
impl Drop for SlangCompiler {
fn drop(&mut self) {
if !self.session.is_null() {
unsafe { (self.library.destroy_session)(self.session) };
}
}
}
static GLOBAL_COMPILER: std::sync::OnceLock<Result<SlangCompiler, String>> = std::sync::OnceLock::new();
#[deprecated(since = "0.2.0", note = "Use SlangCompiler::new() per context instead")]
pub fn global_compiler() -> Result<&'static SlangCompiler> {
GLOBAL_COMPILER
.get_or_init(|| SlangCompiler::new().map_err(|e| e.to_string()))
.as_ref()
.map_err(|e| anyhow::anyhow!("{}", e))
}