[d3d12] use windows-rs's bindings for DXC instead of hassle-rs

This commit is contained in:
teoxoy 2024-09-02 17:41:37 +02:00 committed by Teodor Tanasoaia
parent 912bdcd336
commit 9d24888848
8 changed files with 287 additions and 348 deletions

116
Cargo.lock generated
View File

@ -192,7 +192,7 @@ dependencies = [
"argh_shared",
"proc-macro2",
"quote",
"syn 2.0.75",
"syn",
]
[[package]]
@ -242,7 +242,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn",
]
[[package]]
@ -385,7 +385,7 @@ checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn",
]
[[package]]
@ -528,7 +528,7 @@ dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.75",
"syn",
]
[[package]]
@ -559,37 +559,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
[[package]]
name = "com"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6"
dependencies = [
"com_macros",
]
[[package]]
name = "com_macros"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5"
dependencies = [
"com_macros_support",
"proc-macro2",
"syn 1.0.109",
]
[[package]]
name = "com_macros_support"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "combine"
version = "4.6.7"
@ -770,7 +739,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
dependencies = [
"quote",
"syn 2.0.75",
"syn",
]
[[package]]
@ -867,7 +836,7 @@ dependencies = [
"quote",
"strum",
"strum_macros",
"syn 2.0.75",
"syn",
"thiserror",
]
@ -940,7 +909,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn",
]
[[package]]
@ -953,7 +922,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustc_version 0.4.0",
"syn 2.0.75",
"syn",
]
[[package]]
@ -1027,7 +996,7 @@ checksum = "b36f2ddfca91251bed7f931f24b192e4eaf0a0e0fa70cf81cfb1416a1973620e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn",
]
[[package]]
@ -1148,7 +1117,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn",
]
[[package]]
@ -1245,7 +1214,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn",
]
[[package]]
@ -1481,21 +1450,6 @@ dependencies = [
"allocator-api2",
]
[[package]]
name = "hassle-rs"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890"
dependencies = [
"bitflags 2.6.0",
"com",
"libc",
"libloading",
"thiserror",
"widestring",
"winapi",
]
[[package]]
name = "heck"
version = "0.4.1"
@ -2084,7 +2038,7 @@ dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 2.0.75",
"syn",
]
[[package]]
@ -2276,7 +2230,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn",
]
[[package]]
@ -2405,7 +2359,7 @@ checksum = "07c277e4e643ef00c1233393c673f655e3672cf7eb3ba08a00bdd0ea59139b5f"
dependencies = [
"proc-macro-rules-macros",
"proc-macro2",
"syn 2.0.75",
"syn",
]
[[package]]
@ -2417,7 +2371,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.75",
"syn",
]
[[package]]
@ -2740,7 +2694,7 @@ checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn",
]
[[package]]
@ -2958,18 +2912,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.75",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
"syn",
]
[[package]]
@ -3009,7 +2952,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn",
]
[[package]]
@ -3117,7 +3060,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn",
]
[[package]]
@ -3410,7 +3353,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.75",
"syn",
"wasm-bindgen-shared",
]
@ -3444,7 +3387,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -3478,7 +3421,7 @@ checksum = "4b8220be1fa9e4c889b30fd207d4906657e7e90b12e0e6b0c8b8d8709f5de021"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn",
]
[[package]]
@ -3730,7 +3673,6 @@ dependencies = [
"gpu-alloc",
"gpu-allocator",
"gpu-descriptor",
"hassle-rs",
"js-sys",
"khronos-egl",
"libc",
@ -3777,7 +3719,7 @@ version = "22.0.0"
dependencies = [
"heck 0.5.0",
"quote",
"syn 2.0.75",
"syn",
]
[[package]]
@ -3839,12 +3781,6 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "widestring"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311"
[[package]]
name = "winapi"
version = "0.3.9"
@ -3907,7 +3843,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn",
]
[[package]]
@ -3918,7 +3854,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn",
]
[[package]]
@ -4292,5 +4228,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.75",
"syn",
]

View File

@ -149,7 +149,6 @@ gpu-descriptor = "0.3"
bit-set = "0.8"
gpu-allocator = { version = "0.27", default-features = false }
range-alloc = "0.1"
hassle-rs = "0.11.0"
windows-core = { version = "0.58", default-features = false }
# Gles dependencies

View File

@ -78,6 +78,7 @@ dx12 = [
"gpu-allocator/d3d12",
"naga/hlsl-out-if-target-windows",
"windows/Win32_Graphics_Direct3D_Fxc",
"windows/Win32_Graphics_Direct3D_Dxc",
"windows/Win32_Graphics_Direct3D",
"windows/Win32_Graphics_Direct3D12",
"windows/Win32_Graphics_DirectComposition",
@ -89,7 +90,6 @@ dx12 = [
"windows/Win32_System_Threading",
"windows/Win32_UI_WindowsAndMessaging",
]
dxc_shader_compiler = ["dep:hassle-rs"]
renderdoc = ["dep:libloading", "dep:renderdoc-sys"]
fragile-send-sync-non-atomic-wasm = ["wgt/fragile-send-sync-non-atomic-wasm"]
# Panic when running into an out-of-memory error (for debugging purposes).
@ -156,7 +156,6 @@ windows = { workspace = true, optional = true }
bit-set = { workspace = true, optional = true }
range-alloc = { workspace = true, optional = true }
gpu-allocator = { workspace = true, optional = true }
hassle-rs = { workspace = true, optional = true }
# For core macros. This crate is also reexported as windows::core.
windows-core = { workspace = true, optional = true }

View File

@ -288,7 +288,7 @@ impl super::Device {
};
let full_stage = format!(
"{}_{}\0",
"{}_{}",
naga_stage.to_hlsl_str(),
naga_options.shader_model.to_str()
);
@ -306,28 +306,33 @@ impl super::Device {
let source_name = stage.module.raw_name.as_deref();
// Compile with DXC if available, otherwise fall back to FXC
let (result, log_level) = if let Some(ref dxc_container) = self.dxc_container {
let result = if let Some(ref dxc_container) = self.dxc_container {
shader_compilation::compile_dxc(
self,
&source,
source_name,
raw_ep,
stage_bit,
full_stage,
&full_stage,
dxc_container,
)
} else {
let full_stage = ffi::CStr::from_bytes_with_nul(full_stage.as_bytes()).unwrap();
shader_compilation::compile_fxc(
self,
&source,
source_name,
&ffi::CString::new(raw_ep.as_str()).unwrap(),
raw_ep,
stage_bit,
full_stage,
&full_stage,
)
};
let log_level = if result.is_ok() {
log::Level::Info
} else {
log::Level::Error
};
log::log!(
log_level,
"Naga generated shader for {:?} at {:?}:\n{}",

View File

@ -71,7 +71,10 @@ struct DynLib {
}
impl DynLib {
unsafe fn new(filename: &'static str) -> Result<Self, libloading::Error> {
unsafe fn new<P>(filename: P) -> Result<Self, libloading::Error>
where
P: AsRef<ffi::OsStr>,
{
unsafe { libloading::Library::new(filename) }.map(|inner| Self { inner })
}
@ -919,8 +922,7 @@ pub struct ShaderModule {
impl crate::DynShaderModule for ShaderModule {}
pub(super) enum CompiledShader {
#[allow(unused)]
Dxc(Vec<u8>),
Dxc(Direct3D::Dxc::IDxcBlob),
Fxc(Direct3D::ID3DBlob),
}
@ -928,8 +930,8 @@ impl CompiledShader {
fn create_native_shader(&self) -> Direct3D12::D3D12_SHADER_BYTECODE {
match self {
CompiledShader::Dxc(shader) => Direct3D12::D3D12_SHADER_BYTECODE {
pShaderBytecode: shader.as_ptr().cast(),
BytecodeLength: shader.len(),
pShaderBytecode: unsafe { shader.GetBufferPointer() },
BytecodeLength: unsafe { shader.GetBufferSize() },
},
CompiledShader::Fxc(shader) => Direct3D12::D3D12_SHADER_BYTECODE {
pShaderBytecode: unsafe { shader.GetBufferPointer() },

View File

@ -1,11 +1,11 @@
use crate::auxil::dxgi::result::HResult;
use std::ffi::CStr;
use std::ptr;
use std::path::PathBuf;
use windows::{
core::{Interface, PCSTR, PCWSTR},
Win32::Graphics::Direct3D::{Dxc, Fxc},
};
pub(super) use dxc::{compile_dxc, get_dxc_container, DxcContainer};
use windows::Win32::Graphics::Direct3D;
// This exists so that users who don't want to use dxc can disable the dxc_shader_compiler feature
// and not have to compile hassle_rs.
// Currently this will use Dxc if it is chosen as the dx12 compiler at `Instance` creation time, and will
// fallback to FXC if the Dxc libraries (dxil.dll and dxcompiler.dll) are not found, or if Fxc is chosen at'
// `Instance` creation time.
@ -14,40 +14,41 @@ pub(super) fn compile_fxc(
device: &super::Device,
source: &str,
source_name: Option<&CStr>,
raw_ep: &CStr,
raw_ep: &str,
stage_bit: wgt::ShaderStages,
full_stage: &CStr,
) -> (
Result<super::CompiledShader, crate::PipelineError>,
log::Level,
) {
full_stage: &str,
) -> Result<super::CompiledShader, crate::PipelineError> {
profiling::scope!("compile_fxc");
let mut shader_data = None;
let mut compile_flags = Direct3D::Fxc::D3DCOMPILE_ENABLE_STRICTNESS;
let mut compile_flags = Fxc::D3DCOMPILE_ENABLE_STRICTNESS;
if device
.private_caps
.instance_flags
.contains(wgt::InstanceFlags::DEBUG)
{
compile_flags |=
Direct3D::Fxc::D3DCOMPILE_DEBUG | Direct3D::Fxc::D3DCOMPILE_SKIP_OPTIMIZATION;
compile_flags |= Fxc::D3DCOMPILE_DEBUG | Fxc::D3DCOMPILE_SKIP_OPTIMIZATION;
}
let raw_ep = std::ffi::CString::new(raw_ep).unwrap();
let full_stage = std::ffi::CString::new(full_stage).unwrap();
// If no name has been set, D3DCompile wants the null pointer.
let source_name = source_name.map(|cstr| cstr.as_ptr()).unwrap_or(ptr::null());
let source_name = source_name
.map(|cstr| cstr.as_ptr().cast())
.unwrap_or(core::ptr::null());
let mut error = None;
let hr = unsafe {
profiling::scope!("Direct3D::Fxc::D3DCompile");
Direct3D::Fxc::D3DCompile(
profiling::scope!("Fxc::D3DCompile");
Fxc::D3DCompile(
// TODO: Update low-level bindings to accept a slice here
source.as_ptr().cast(),
source.len(),
windows::core::PCSTR(source_name.cast()),
PCSTR(source_name),
None,
None,
windows::core::PCSTR(raw_ep.as_ptr().cast()),
windows::core::PCSTR(full_stage.as_ptr().cast()),
PCSTR(raw_ep.as_ptr().cast()),
PCSTR(full_stage.as_ptr().cast()),
compile_flags,
0,
&mut shader_data,
@ -58,10 +59,7 @@ pub(super) fn compile_fxc(
match hr {
Ok(()) => {
let shader_data = shader_data.unwrap();
(
Ok(super::CompiledShader::Fxc(shader_data)),
log::Level::Info,
)
Ok(super::CompiledShader::Fxc(shader_data))
}
Err(e) => {
let mut full_msg = format!("FXC D3DCompile error ({e})");
@ -75,234 +73,237 @@ pub(super) fn compile_fxc(
};
let _ = write!(full_msg, ": {}", String::from_utf8_lossy(message));
}
(
Err(crate::PipelineError::Linkage(stage_bit, full_msg)),
log::Level::Warn,
)
Err(crate::PipelineError::Linkage(stage_bit, full_msg))
}
}
}
// The Dxc implementation is behind a feature flag so that users who don't want to use dxc can disable the feature.
#[cfg(feature = "dxc_shader_compiler")]
mod dxc {
use std::ffi::CStr;
use std::path::PathBuf;
trait DxcObj: Interface {
const CLSID: windows::core::GUID;
}
impl DxcObj for Dxc::IDxcCompiler3 {
const CLSID: windows::core::GUID = Dxc::CLSID_DxcCompiler;
}
impl DxcObj for Dxc::IDxcUtils {
const CLSID: windows::core::GUID = Dxc::CLSID_DxcUtils;
}
impl DxcObj for Dxc::IDxcValidator {
const CLSID: windows::core::GUID = Dxc::CLSID_DxcValidator;
}
// Destructor order should be fine since _dxil and _dxc don't rely on each other.
pub(crate) struct DxcContainer {
compiler: hassle_rs::DxcCompiler,
library: hassle_rs::DxcLibrary,
validator: hassle_rs::DxcValidator,
// Has to be held onto for the lifetime of the device otherwise shaders will fail to compile.
_dxc: hassle_rs::Dxc,
// Also Has to be held onto for the lifetime of the device otherwise shaders will fail to validate.
_dxil: hassle_rs::Dxil,
}
#[derive(Debug)]
struct DxcLib {
lib: crate::dx12::DynLib,
}
pub(crate) fn get_dxc_container(
dxc_path: Option<PathBuf>,
dxil_path: Option<PathBuf>,
) -> Result<Option<DxcContainer>, crate::DeviceError> {
// Make sure that dxil.dll exists.
let dxil = match hassle_rs::Dxil::new(dxil_path) {
Ok(dxil) => dxil,
Err(e) => {
log::warn!("Failed to load dxil.dll. Defaulting to FXC instead: {}", e);
return Ok(None);
impl DxcLib {
fn new(lib_path: Option<PathBuf>, lib_name: &'static str) -> Result<Self, libloading::Error> {
let lib_path = if let Some(lib_path) = lib_path {
if lib_path.is_file() {
lib_path
} else {
lib_path.join(lib_name)
}
} else {
PathBuf::from(lib_name)
};
// Needed for explicit validation.
let validator = dxil.create_validator()?;
let dxc = match hassle_rs::Dxc::new(dxc_path) {
Ok(dxc) => dxc,
Err(e) => {
log::warn!(
"Failed to load dxcompiler.dll. Defaulting to FXC instead: {}",
e
);
return Ok(None);
}
};
let compiler = dxc.create_compiler()?;
let library = dxc.create_library()?;
Ok(Some(DxcContainer {
_dxc: dxc,
compiler,
library,
_dxil: dxil,
validator,
}))
unsafe { crate::dx12::DynLib::new(lib_path).map(|lib| Self { lib }) }
}
pub(crate) fn compile_dxc(
device: &crate::dx12::Device,
source: &str,
source_name: Option<&CStr>,
raw_ep: &str,
stage_bit: wgt::ShaderStages,
full_stage: String,
dxc_container: &DxcContainer,
) -> (
Result<crate::dx12::CompiledShader, crate::PipelineError>,
log::Level,
) {
profiling::scope!("compile_dxc");
let mut compile_flags = arrayvec::ArrayVec::<&str, 6>::new_const();
compile_flags.push("-Ges"); // Direct3D::Fxc::D3DCOMPILE_ENABLE_STRICTNESS
compile_flags.push("-Vd"); // Disable implicit validation to work around bugs when dxil.dll isn't in the local directory.
compile_flags.push("-HV"); // Use HLSL 2018, Naga doesn't supported 2021 yet.
compile_flags.push("2018");
pub fn create_instance<T: DxcObj>(&self) -> Result<T, crate::DeviceError> {
type Fun = extern "system" fn(
rclsid: *const windows_core::GUID,
riid: *const windows_core::GUID,
ppv: *mut *mut core::ffi::c_void,
) -> windows_core::HRESULT;
let func: libloading::Symbol<Fun> = unsafe { self.lib.get(b"DxcCreateInstance\0") }?;
if device
.private_caps
.instance_flags
.contains(wgt::InstanceFlags::DEBUG)
{
compile_flags.push("-Zi"); // Direct3D::Fxc::D3DCOMPILE_SKIP_OPTIMIZATION
compile_flags.push("-Od"); // Direct3D::Fxc::D3DCOMPILE_DEBUG
}
let blob = match dxc_container
.library
.create_blob_with_encoding_from_str(source)
.map_err(|e| crate::PipelineError::Linkage(stage_bit, format!("DXC blob error: {e}")))
{
Ok(blob) => blob,
Err(e) => return (Err(e), log::Level::Error),
};
let source_name = source_name
.and_then(|cstr| cstr.to_str().ok())
.unwrap_or("");
let compiled = dxc_container.compiler.compile(
&blob,
source_name,
raw_ep,
&full_stage,
&compile_flags,
None,
&[],
);
let (result, log_level) = match compiled {
Ok(dxc_result) => match dxc_result.get_result() {
Ok(dxc_blob) => {
// Validate the shader.
match dxc_container.validator.validate(dxc_blob) {
Ok(validated_blob) => (
Ok(crate::dx12::CompiledShader::Dxc(validated_blob.to_vec())),
log::Level::Info,
),
Err(e) => (
Err(crate::PipelineError::Linkage(
stage_bit,
format!(
"DXC validation error: {:?}\n{:?}",
get_error_string_from_dxc_result(&dxc_container.library, &e.0)
.unwrap_or_default(),
e.1
),
)),
log::Level::Error,
),
}
}
Err(e) => (
Err(crate::PipelineError::Linkage(
stage_bit,
format!("DXC compile error: {e}"),
)),
log::Level::Error,
),
},
Err(e) => (
Err(crate::PipelineError::Linkage(
stage_bit,
format!(
"DXC compile error: {}",
get_error_string_from_dxc_result(&dxc_container.library, &e.0)
.unwrap_or_default()
),
)),
log::Level::Error,
),
};
(result, log_level)
}
impl From<hassle_rs::HassleError> for crate::DeviceError {
fn from(value: hassle_rs::HassleError) -> Self {
match value {
hassle_rs::HassleError::Win32Error(e) => {
// TODO: This returns an HRESULT, should we try and use the associated Windows error message?
log::error!("Win32 error: {e:?}");
crate::DeviceError::Lost
}
hassle_rs::HassleError::LoadLibraryError { filename, inner } => {
log::error!("Failed to load dxc library {filename:?}. Inner error: {inner:?}");
crate::DeviceError::Lost
}
hassle_rs::HassleError::LibLoadingError(e) => {
log::error!("Failed to load dxc library. {e:?}");
crate::DeviceError::Lost
}
hassle_rs::HassleError::WindowsOnly(e) => {
log::error!("Signing with dxil.dll is only supported on Windows. {e:?}");
crate::DeviceError::Lost
}
// `ValidationError` and `CompileError` should never happen in a context involving `DeviceError`
hassle_rs::HassleError::ValidationError(_e) => unimplemented!(),
hassle_rs::HassleError::CompileError(_e) => unimplemented!(),
}
}
}
fn get_error_string_from_dxc_result(
library: &hassle_rs::DxcLibrary,
error: &hassle_rs::DxcOperationResult,
) -> Result<String, hassle_rs::HassleError> {
error
.get_error_buffer()
.and_then(|error| library.get_blob_as_string(&hassle_rs::DxcBlob::from(error)))
let mut result__ = None;
(func)(&T::CLSID, &T::IID, <*mut _>::cast(&mut result__))
.ok()
.into_device_result("DxcCreateInstance")?;
result__.ok_or(crate::DeviceError::Unexpected)
}
}
// These are stubs for when the `dxc_shader_compiler` feature is disabled.
#[cfg(not(feature = "dxc_shader_compiler"))]
mod dxc {
use std::ffi::CStr;
use std::path::PathBuf;
// Destructor order should be fine since _dxil and _dxc don't rely on each other.
pub(super) struct DxcContainer {
compiler: Dxc::IDxcCompiler3,
utils: Dxc::IDxcUtils,
validator: Dxc::IDxcValidator,
// Has to be held onto for the lifetime of the device otherwise shaders will fail to compile.
_dxc: DxcLib,
// Also Has to be held onto for the lifetime of the device otherwise shaders will fail to validate.
_dxil: DxcLib,
}
pub(crate) struct DxcContainer {}
pub(super) fn get_dxc_container(
dxc_path: Option<PathBuf>,
dxil_path: Option<PathBuf>,
) -> Result<Option<DxcContainer>, crate::DeviceError> {
let dxc = match DxcLib::new(dxc_path, "dxcompiler.dll") {
Ok(dxc) => dxc,
Err(e) => {
log::warn!(
"Failed to load dxcompiler.dll. Defaulting to FXC instead: {}",
e
);
return Ok(None);
}
};
pub(crate) fn get_dxc_container(
_dxc_path: Option<PathBuf>,
_dxil_path: Option<PathBuf>,
) -> Result<Option<DxcContainer>, crate::DeviceError> {
// Falls back to Fxc and logs an error.
log::error!("DXC shader compiler was requested on Instance creation, but the DXC feature is disabled. Enable the `dxc_shader_compiler` feature on wgpu_hal to use DXC.");
Ok(None)
let dxil = match DxcLib::new(dxil_path, "dxil.dll") {
Ok(dxil) => dxil,
Err(e) => {
log::warn!("Failed to load dxil.dll. Defaulting to FXC instead: {}", e);
return Ok(None);
}
};
let compiler = dxc.create_instance::<Dxc::IDxcCompiler3>()?;
let utils = dxc.create_instance::<Dxc::IDxcUtils>()?;
let validator = dxil.create_instance::<Dxc::IDxcValidator>()?;
Ok(Some(DxcContainer {
compiler,
utils,
validator,
_dxc: dxc,
_dxil: dxil,
}))
}
/// Owned PCWSTR
#[allow(clippy::upper_case_acronyms)]
struct OPCWSTR {
inner: Vec<u16>,
}
impl OPCWSTR {
fn new(s: &str) -> Self {
let mut inner: Vec<_> = s.encode_utf16().collect();
inner.push(0);
Self { inner }
}
// It shouldn't be possible that this gets called with the `dxc_shader_compiler` feature disabled.
pub(crate) fn compile_dxc(
_device: &crate::dx12::Device,
_source: &str,
_source_name: Option<&CStr>,
_raw_ep: &str,
_stage_bit: wgt::ShaderStages,
_full_stage: String,
_dxc_container: &DxcContainer,
) -> (
Result<crate::dx12::CompiledShader, crate::PipelineError>,
log::Level,
) {
unimplemented!("Something went really wrong, please report this. Attempted to compile shader with DXC, but the DXC feature is disabled. Enable the `dxc_shader_compiler` feature on wgpu_hal to use DXC.");
fn ptr(&self) -> PCWSTR {
PCWSTR(self.inner.as_ptr())
}
}
fn get_output<T: Interface>(
res: &Dxc::IDxcResult,
kind: Dxc::DXC_OUT_KIND,
) -> Result<T, crate::DeviceError> {
let mut result__: Option<T> = None;
unsafe { res.GetOutput::<T>(kind, &mut None, <*mut _>::cast(&mut result__)) }
.into_device_result("GetOutput")?;
result__.ok_or(crate::DeviceError::Unexpected)
}
fn as_err_str(blob: &Dxc::IDxcBlobUtf8) -> Result<&str, crate::DeviceError> {
let ptr = unsafe { blob.GetStringPointer() };
let len = unsafe { blob.GetStringLength() };
core::str::from_utf8(unsafe { core::slice::from_raw_parts(ptr.0, len) })
.map_err(|_| crate::DeviceError::Unexpected)
}
pub(super) fn compile_dxc(
device: &crate::dx12::Device,
source: &str,
source_name: Option<&CStr>,
raw_ep: &str,
stage_bit: wgt::ShaderStages,
full_stage: &str,
dxc_container: &DxcContainer,
) -> Result<crate::dx12::CompiledShader, crate::PipelineError> {
profiling::scope!("compile_dxc");
let source_name = source_name.and_then(|cstr| cstr.to_str().ok());
let source_name = source_name.map(OPCWSTR::new);
let raw_ep = OPCWSTR::new(raw_ep);
let full_stage = OPCWSTR::new(full_stage);
let mut compile_args = arrayvec::ArrayVec::<PCWSTR, 12>::new_const();
if let Some(source_name) = source_name.as_ref() {
compile_args.push(source_name.ptr())
}
compile_args.extend([
windows::core::w!("-E"),
raw_ep.ptr(),
windows::core::w!("-T"),
full_stage.ptr(),
windows::core::w!("-HV"),
windows::core::w!("2018"), // Use HLSL 2018, Naga doesn't supported 2021 yet.
windows::core::w!("-no-warnings"),
Dxc::DXC_ARG_ENABLE_STRICTNESS,
Dxc::DXC_ARG_SKIP_VALIDATION, // Disable implicit validation to work around bugs when dxil.dll isn't in the local directory.
]);
if device
.private_caps
.instance_flags
.contains(wgt::InstanceFlags::DEBUG)
{
compile_args.push(Dxc::DXC_ARG_DEBUG);
compile_args.push(Dxc::DXC_ARG_SKIP_OPTIMIZATIONS);
}
let buffer = Dxc::DxcBuffer {
Ptr: source.as_ptr().cast(),
Size: source.len(),
Encoding: Dxc::DXC_CP_UTF8.0,
};
let compile_res: Dxc::IDxcResult = unsafe {
dxc_container
.compiler
.Compile(&buffer, Some(&compile_args), None)
}
.into_device_result("Compile")?;
drop(compile_args);
drop(source_name);
drop(raw_ep);
drop(full_stage);
let err_blob = get_output::<Dxc::IDxcBlobUtf8>(&compile_res, Dxc::DXC_OUT_ERRORS)?;
let len = unsafe { err_blob.GetStringLength() };
if len != 0 {
let err = as_err_str(&err_blob)?;
return Err(crate::PipelineError::Linkage(
stage_bit,
format!("DXC compile error: {err}"),
));
}
let blob = get_output::<Dxc::IDxcBlob>(&compile_res, Dxc::DXC_OUT_OBJECT)?;
let err_blob = {
let res = unsafe {
dxc_container
.validator
.Validate(&blob, Dxc::DxcValidatorFlags_InPlaceEdit)
}
.into_device_result("Validate")?;
unsafe { res.GetErrorBuffer() }.into_device_result("GetErrorBuffer")?
};
let size = unsafe { err_blob.GetBufferSize() };
if size != 0 {
let err_blob = unsafe { dxc_container.utils.GetBlobAsUtf8(&err_blob) }
.into_device_result("GetBlobAsUtf8")?;
let err = as_err_str(&err_blob)?;
return Err(crate::PipelineError::Linkage(
stage_bit,
format!("DXC validation error: {err}"),
));
}
Ok(crate::dx12::CompiledShader::Dxc(blob))
}

View File

@ -7334,10 +7334,6 @@ impl Default for ShaderBoundChecks {
/// Selects which DX12 shader compiler to use.
///
/// If the `wgpu-hal/dx12-shader-compiler` feature isn't enabled then this will fall back
/// to the Fxc compiler at runtime and log an error.
/// This feature is always enabled when using `wgpu`.
///
/// If the `Dxc` option is selected, but `dxcompiler.dll` and `dxil.dll` files aren't found,
/// then this will fall back to the Fxc compiler at runtime and log an error.
///
@ -7354,6 +7350,10 @@ pub enum Dx12Compiler {
///
/// However, it requires both `dxcompiler.dll` and `dxil.dll` to be shipped with the application.
/// These files can be downloaded from <https://github.com/microsoft/DirectXShaderCompiler/releases>.
///
/// Minimum supported version: [v1.5.2010](https://github.com/microsoft/DirectXShaderCompiler/releases/tag/v1.5.2010)
///
/// It also requires WDDM 2.1 (Windows 10 version 1607).
Dxc {
/// Path to the `dxil.dll` file, or path to the directory containing `dxil.dll` file. Passing `None` will use standard platform specific dll loading rules.
dxil_path: Option<PathBuf>,

View File

@ -161,10 +161,7 @@ hal = { workspace = true }
hal = { workspace = true, features = ["renderdoc"] }
[target.'cfg(windows)'.dependencies]
hal = { workspace = true, features = [
"dxc_shader_compiler",
"renderdoc",
] }
hal = { workspace = true, features = ["renderdoc"] }
[target.'cfg(target_arch = "wasm32")'.dependencies.hal]
workspace = true