#![allow( clippy::too_many_arguments, clippy::new_without_default, clippy::type_complexity )] use crate::ffi::*; use crate::os::{HRESULT, LPCWSTR, LPWSTR, WCHAR}; use crate::utils::{from_wide, to_wide, HassleError, Result}; use com_rs::ComPtr; use libloading::{Library, Symbol}; use std::path::{Path, PathBuf}; use std::pin::Pin; #[derive(Debug)] pub struct DxcBlob { inner: ComPtr, } impl DxcBlob { fn new(inner: ComPtr) -> Self { Self { inner } } pub fn as_slice(&self) -> &[T] { unsafe { std::slice::from_raw_parts( self.inner.get_buffer_pointer().cast(), self.inner.get_buffer_size() / std::mem::size_of::(), ) } } pub fn as_mut_slice(&mut self) -> &mut [T] { unsafe { std::slice::from_raw_parts_mut( self.inner.get_buffer_pointer().cast(), self.inner.get_buffer_size() / std::mem::size_of::(), ) } } pub fn to_vec(&self) -> Vec where T: Clone, { self.as_slice().to_vec() } } impl AsRef<[u8]> for DxcBlob { fn as_ref(&self) -> &[u8] { self.as_slice() } } impl AsMut<[u8]> for DxcBlob { fn as_mut(&mut self) -> &mut [u8] { self.as_mut_slice() } } #[derive(Debug)] pub struct DxcBlobEncoding { inner: ComPtr, } impl DxcBlobEncoding { fn new(inner: ComPtr) -> Self { Self { inner } } } impl From for DxcBlob { fn from(encoded_blob: DxcBlobEncoding) -> Self { DxcBlob::new((&encoded_blob.inner).into()) } } #[derive(Debug)] pub struct DxcOperationResult { inner: ComPtr, } impl DxcOperationResult { fn new(inner: ComPtr) -> Self { Self { inner } } pub fn get_status(&self) -> Result { let mut status: u32 = 0; unsafe { self.inner.get_status(&mut status) }.result_with_success(status) } pub fn get_result(&self) -> Result { let mut blob: ComPtr = ComPtr::new(); unsafe { self.inner.get_result(blob.as_mut_ptr()) }.result()?; Ok(DxcBlob::new(blob)) } pub fn get_error_buffer(&self) -> Result { let mut blob: ComPtr = ComPtr::new(); unsafe { self.inner.get_error_buffer(blob.as_mut_ptr()) }.result()?; Ok(DxcBlobEncoding::new(blob)) } } pub trait DxcIncludeHandler { fn load_source(&mut self, filename: String) -> Option; } #[repr(C)] struct DxcIncludeHandlerWrapperVtbl { query_interface: extern "system" fn( *const com_rs::IUnknown, &com_rs::IID, *mut *mut core::ffi::c_void, ) -> HRESULT, add_ref: extern "system" fn(*const com_rs::IUnknown) -> HRESULT, release: extern "system" fn(*const com_rs::IUnknown) -> HRESULT, load_source: extern "system" fn(*mut com_rs::IUnknown, LPCWSTR, *mut *mut IDxcBlob) -> HRESULT, } #[repr(C)] struct DxcIncludeHandlerWrapper<'a, 'i> { vtable: Box, handler: &'i mut dyn DxcIncludeHandler, pinned: Vec>, library: &'a DxcLibrary, } impl<'a, 'i> DxcIncludeHandlerWrapper<'a, 'i> { extern "system" fn query_interface( _me: *const com_rs::IUnknown, _rrid: &com_rs::IID, _ppv_obj: *mut *mut core::ffi::c_void, ) -> HRESULT { HRESULT(0) // dummy impl } extern "system" fn add_ref(_me: *const com_rs::IUnknown) -> HRESULT { HRESULT(0) // dummy impl } extern "system" fn release(_me: *const com_rs::IUnknown) -> HRESULT { HRESULT(0) // dummy impl } extern "system" fn load_source( me: *mut com_rs::IUnknown, filename: LPCWSTR, include_source: *mut *mut IDxcBlob, ) -> HRESULT { let me = me.cast::(); let filename = crate::utils::from_wide(filename); let source = unsafe { (*me).handler.load_source(filename) }; if let Some(source) = source { let source = Pin::new(source); let mut blob = unsafe { (*me) .library .create_blob_with_encoding_from_str(&source) .unwrap() }; unsafe { blob.inner.add_ref(); *include_source = *blob.inner.as_mut_ptr(); (*me).pinned.push(source); } 0 } else { -2_147_024_894 // ERROR_FILE_NOT_FOUND / 0x80070002 } .into() } } #[derive(Debug)] pub struct DxcCompiler { inner: ComPtr, library: DxcLibrary, } impl DxcCompiler { fn new(inner: ComPtr, library: DxcLibrary) -> Self { Self { inner, library } } fn prep_defines( defines: &[(&str, Option<&str>)], wide_defines: &mut Vec<(Vec, Vec)>, dxc_defines: &mut Vec, ) { for (name, value) in defines { if value.is_none() { wide_defines.push((to_wide(name), to_wide("1"))); } else { wide_defines.push((to_wide(name), to_wide(value.unwrap()))); } } for (ref name, ref value) in wide_defines { dxc_defines.push(DxcDefine { name: name.as_ptr(), value: value.as_ptr(), }); } } fn prep_args(args: &[&str], wide_args: &mut Vec>, dxc_args: &mut Vec) { for a in args { wide_args.push(to_wide(a)); } for a in wide_args { dxc_args.push(a.as_ptr()); } } fn prep_include_handler<'a, 'i>( library: &'a DxcLibrary, include_handler: Option<&'i mut dyn DxcIncludeHandler>, ) -> Option>> { if let Some(include_handler) = include_handler { let vtable = DxcIncludeHandlerWrapperVtbl { query_interface: DxcIncludeHandlerWrapper::query_interface, add_ref: DxcIncludeHandlerWrapper::add_ref, release: DxcIncludeHandlerWrapper::release, load_source: DxcIncludeHandlerWrapper::load_source, }; Some(Box::new(DxcIncludeHandlerWrapper { vtable: Box::new(vtable), handler: include_handler, library, pinned: vec![], })) } else { None } } pub fn compile( &self, blob: &DxcBlobEncoding, source_name: &str, entry_point: &str, target_profile: &str, args: &[&str], include_handler: Option<&mut dyn DxcIncludeHandler>, defines: &[(&str, Option<&str>)], ) -> Result { let mut wide_args = vec![]; let mut dxc_args = vec![]; Self::prep_args(args, &mut wide_args, &mut dxc_args); let mut wide_defines = vec![]; let mut dxc_defines = vec![]; Self::prep_defines(defines, &mut wide_defines, &mut dxc_defines); let handler_wrapper = Self::prep_include_handler(&self.library, include_handler); let mut result: ComPtr = ComPtr::new(); let result_hr = unsafe { self.inner.compile( blob.inner.as_ptr(), to_wide(source_name).as_ptr(), to_wide(entry_point).as_ptr(), to_wide(target_profile).as_ptr(), dxc_args.as_ptr(), dxc_args.len() as u32, dxc_defines.as_ptr(), dxc_defines.len() as u32, handler_wrapper .as_ref() .map_or(std::ptr::null(), |v| &**v as *const _ as *const _), result.as_mut_ptr(), ) }; let mut compile_error = 0u32; let status_hr = unsafe { result.get_status(&mut compile_error) }; if !result_hr.is_err() && !status_hr.is_err() && compile_error == 0 { Ok(DxcOperationResult::new(result)) } else { Err((DxcOperationResult::new(result), result_hr)) } } pub fn compile_with_debug( &self, blob: &DxcBlobEncoding, source_name: &str, entry_point: &str, target_profile: &str, args: &[&str], include_handler: Option<&mut dyn DxcIncludeHandler>, defines: &[(&str, Option<&str>)], ) -> Result<(DxcOperationResult, String, DxcBlob), (DxcOperationResult, HRESULT)> { let mut wide_args = vec![]; let mut dxc_args = vec![]; Self::prep_args(args, &mut wide_args, &mut dxc_args); let mut wide_defines = vec![]; let mut dxc_defines = vec![]; Self::prep_defines(defines, &mut wide_defines, &mut dxc_defines); let handler_wrapper = Self::prep_include_handler(&self.library, include_handler); let mut result: ComPtr = ComPtr::new(); let mut debug_blob: ComPtr = ComPtr::new(); let mut debug_filename: LPWSTR = std::ptr::null_mut(); let result_hr = unsafe { self.inner.compile_with_debug( blob.inner.as_ptr(), to_wide(source_name).as_ptr(), to_wide(entry_point).as_ptr(), to_wide(target_profile).as_ptr(), dxc_args.as_ptr(), dxc_args.len() as u32, dxc_defines.as_ptr(), dxc_defines.len() as u32, handler_wrapper .as_ref() .map_or(std::ptr::null(), |v| &**v as *const _ as *const _), result.as_mut_ptr(), &mut debug_filename, debug_blob.as_mut_ptr(), ) }; let mut compile_error = 0u32; let status_hr = unsafe { result.get_status(&mut compile_error) }; if !result_hr.is_err() && !status_hr.is_err() && compile_error == 0 { Ok(( DxcOperationResult::new(result), from_wide(debug_filename), DxcBlob::new(debug_blob), )) } else { Err((DxcOperationResult::new(result), result_hr)) } } pub fn preprocess( &self, blob: &DxcBlobEncoding, source_name: &str, args: &[&str], include_handler: Option<&mut dyn DxcIncludeHandler>, defines: &[(&str, Option<&str>)], ) -> Result { let mut wide_args = vec![]; let mut dxc_args = vec![]; Self::prep_args(args, &mut wide_args, &mut dxc_args); let mut wide_defines = vec![]; let mut dxc_defines = vec![]; Self::prep_defines(defines, &mut wide_defines, &mut dxc_defines); let handler_wrapper = Self::prep_include_handler(&self.library, include_handler); let mut result: ComPtr = ComPtr::new(); let result_hr = unsafe { self.inner.preprocess( blob.inner.as_ptr(), to_wide(source_name).as_ptr(), dxc_args.as_ptr(), dxc_args.len() as u32, dxc_defines.as_ptr(), dxc_defines.len() as u32, handler_wrapper .as_ref() .map_or(std::ptr::null(), |v| &**v as *const _ as *const _), result.as_mut_ptr(), ) }; let mut compile_error = 0u32; let status_hr = unsafe { result.get_status(&mut compile_error) }; if !result_hr.is_err() && !status_hr.is_err() && compile_error == 0 { Ok(DxcOperationResult::new(result)) } else { Err((DxcOperationResult::new(result), result_hr)) } } pub fn disassemble(&self, blob: &DxcBlob) -> Result { let mut result_blob: ComPtr = ComPtr::new(); unsafe { self.inner .disassemble(blob.inner.as_ptr(), result_blob.as_mut_ptr()) } .result()?; Ok(DxcBlobEncoding::new(result_blob)) } } #[derive(Debug)] pub struct DxcLibrary { inner: ComPtr, } impl DxcLibrary { fn new(inner: ComPtr) -> Self { Self { inner } } pub fn create_blob_with_encoding(&self, data: &[u8]) -> Result { let mut blob: ComPtr = ComPtr::new(); unsafe { self.inner.create_blob_with_encoding_from_pinned( data.as_ptr().cast(), data.len() as u32, 0, // Binary; no code page blob.as_mut_ptr(), ) } .result()?; Ok(DxcBlobEncoding::new(blob)) } pub fn create_blob_with_encoding_from_str(&self, text: &str) -> Result { let mut blob: ComPtr = ComPtr::new(); const CP_UTF8: u32 = 65001; // UTF-8 translation unsafe { self.inner.create_blob_with_encoding_from_pinned( text.as_ptr().cast(), text.len() as u32, CP_UTF8, blob.as_mut_ptr(), ) } .result()?; Ok(DxcBlobEncoding::new(blob)) } pub fn get_blob_as_string(&self, blob: &DxcBlob) -> Result { let mut blob_utf8: ComPtr = ComPtr::new(); unsafe { self.inner .get_blob_as_utf8(blob.inner.as_ptr(), blob_utf8.as_mut_ptr()) } .result()?; Ok(String::from_utf8(DxcBlob::new((&blob_utf8).into()).to_vec()).unwrap()) } } #[derive(Debug)] pub struct Dxc { dxc_lib: Library, } #[cfg(target_os = "windows")] fn dxcompiler_lib_name() -> &'static Path { Path::new("dxcompiler.dll") } #[cfg(any(target_os = "linux", target_os = "android"))] fn dxcompiler_lib_name() -> &'static Path { Path::new("./libdxcompiler.so") } #[cfg(target_os = "macos")] fn dxcompiler_lib_name() -> &'static Path { Path::new("./libdxcompiler.dylib") } impl Dxc { /// `dxc_path` can point to a library directly or the directory containing the library, /// in which case the appended filename depends on the platform. pub fn new(lib_path: Option) -> Result { let lib_path = if let Some(lib_path) = lib_path { if lib_path.is_file() { lib_path } else { lib_path.join(dxcompiler_lib_name()) } } else { dxcompiler_lib_name().to_owned() }; let dxc_lib = unsafe { Library::new(&lib_path) }.map_err(|e| HassleError::LoadLibraryError { filename: lib_path, inner: e, })?; Ok(Self { dxc_lib }) } pub(crate) fn get_dxc_create_instance(&self) -> Result> { Ok(unsafe { self.dxc_lib.get(b"DxcCreateInstance\0")? }) } pub fn create_compiler(&self) -> Result { let mut compiler: ComPtr = ComPtr::new(); self.get_dxc_create_instance()?( &CLSID_DxcCompiler, &IID_IDxcCompiler2, compiler.as_mut_ptr(), ) .result()?; Ok(DxcCompiler::new(compiler, self.create_library()?)) } pub fn create_library(&self) -> Result { let mut library: ComPtr = ComPtr::new(); self.get_dxc_create_instance()?(&CLSID_DxcLibrary, &IID_IDxcLibrary, library.as_mut_ptr()) .result()?; Ok(DxcLibrary::new(library)) } pub fn create_reflector(&self) -> Result { let mut reflector: ComPtr = ComPtr::new(); self.get_dxc_create_instance()?( &CLSID_DxcContainerReflection, &IID_IDxcContainerReflection, reflector.as_mut_ptr(), ) .result()?; Ok(DxcReflector::new(reflector)) } } #[derive(Debug)] pub struct DxcValidator { inner: ComPtr, } pub type DxcValidatorVersion = (u32, u32); impl DxcValidator { fn new(inner: ComPtr) -> Self { Self { inner } } pub fn version(&self) -> Result { let mut version: ComPtr = ComPtr::new(); HRESULT::from(unsafe { self.inner .query_interface(&IID_IDxcVersionInfo, version.as_mut_ptr()) }) .result()?; let mut major = 0; let mut minor = 0; unsafe { version.get_version(&mut major, &mut minor) }.result_with_success((major, minor)) } pub fn validate(&self, blob: DxcBlob) -> Result { let mut result: ComPtr = ComPtr::new(); let result_hr = unsafe { self.inner.validate( blob.inner.as_ptr(), DXC_VALIDATOR_FLAGS_IN_PLACE_EDIT, result.as_mut_ptr(), ) }; let mut validate_status = 0u32; let status_hr = unsafe { result.get_status(&mut validate_status) }; if !result_hr.is_err() && !status_hr.is_err() && validate_status == 0 { Ok(blob) } else { Err(( DxcOperationResult::new(result), HassleError::Win32Error(result_hr), )) } } } pub struct Reflection { inner: ComPtr, } impl Reflection { fn new(inner: ComPtr) -> Self { Self { inner } } pub fn thread_group_size(&self) -> [u32; 3] { let (mut size_x, mut size_y, mut size_z) = (0u32, 0u32, 0u32); unsafe { self.inner .get_thread_group_size(&mut size_x, &mut size_y, &mut size_z) }; [size_x, size_y, size_z] } } pub struct DxcReflector { inner: ComPtr, } impl DxcReflector { fn new(inner: ComPtr) -> Self { Self { inner } } pub fn reflect(&self, blob: DxcBlob) -> Result { let result_hr = unsafe { self.inner.load(blob.inner.as_ptr()) }; if result_hr.is_err() { return Err(HassleError::Win32Error(result_hr)); } let mut shader_idx = 0; let result_hr = unsafe { self.inner.find_first_part_kind(DFCC_DXIL, &mut shader_idx) }; if result_hr.is_err() { return Err(HassleError::Win32Error(result_hr)); } let mut reflection: ComPtr = ComPtr::new(); let result_hr = unsafe { self.inner.get_part_reflection( shader_idx, &IID_ID3D12ShaderReflection, reflection.as_mut_ptr(), ) }; if result_hr.is_err() { return Err(HassleError::Win32Error(result_hr)); } Ok(Reflection::new(reflection)) } } #[derive(Debug)] pub struct Dxil { dxil_lib: Library, } impl Dxil { #[cfg(not(windows))] pub fn new(_: Option) -> Result { Err(HassleError::WindowsOnly( "DXIL Signing is only supported on Windows".to_string(), )) } /// `dxil_path` can point to a library directly or the directory containing the library, /// in which case `dxil.dll` is appended. #[cfg(windows)] pub fn new(lib_path: Option) -> Result { let lib_path = if let Some(lib_path) = lib_path { if lib_path.is_file() { lib_path } else { lib_path.join("dxil.dll") } } else { PathBuf::from("dxil.dll") }; let dxil_lib = unsafe { Library::new(&lib_path) }.map_err(|e| HassleError::LoadLibraryError { filename: lib_path.to_owned(), inner: e, })?; Ok(Self { dxil_lib }) } fn get_dxc_create_instance(&self) -> Result> { Ok(unsafe { self.dxil_lib.get(b"DxcCreateInstance\0")? }) } pub fn create_validator(&self) -> Result { let mut validator: ComPtr = ComPtr::new(); self.get_dxc_create_instance()?( &CLSID_DxcValidator, &IID_IDxcValidator, validator.as_mut_ptr(), ) .result()?; Ok(DxcValidator::new(validator)) } }