Skip to content

Commit

Permalink
Static linking for DXC via mach-dxcompiler (#6574)
Browse files Browse the repository at this point in the history
  • Loading branch information
DouglasDwyer authored Dec 2, 2024
1 parent 5e52a31 commit be02606
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 50 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ By @ErichDonGubler in [#6456](https://github.com/gfx-rs/wgpu/pull/6456), [#6148]

- Return submission index in `map_async` and `on_submitted_work_done` to track down completion of async callbacks. By @eliemichel in [#6360](https://github.com/gfx-rs/wgpu/pull/6360).
- Move raytracing alignments into HAL instead of in core. By @Vecvec in [#6563](https://github.com/gfx-rs/wgpu/pull/6563).
- Allow for statically linking DXC rather than including separate `.dll` files. By @DouglasDwyer in [#6574](https://github.com/gfx-rs/wgpu/pull/6574).

### Changes

Expand Down
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ gpu-descriptor = "0.3"
bit-set = "0.8"
gpu-allocator = { version = "0.27", default-features = false }
range-alloc = "0.1"
mach-dxcompiler-rs = { version = "0.1.2", default-features = false }
windows-core = { version = "0.58", default-features = false }

# Gles dependencies
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ All testing and example infrastructure share the same set of environment variabl
- `WGPU_ADAPTER_NAME` with a substring of the name of the adapter you want to use (ex. `1080` will match `NVIDIA GeForce 1080ti`).
- `WGPU_BACKEND` with a comma-separated list of the backends you want to use (`vulkan`, `metal`, `dx12`, or `gl`).
- `WGPU_POWER_PREF` with the power preference to choose when a specific adapter name isn't specified (`high`, `low` or `none`)
- `WGPU_DX12_COMPILER` with the DX12 shader compiler you wish to use (`dxc` or `fxc`, note that `dxc` requires `dxil.dll` and `dxcompiler.dll` to be in the working directory otherwise it will fall back to `fxc`)
- `WGPU_DX12_COMPILER` with the DX12 shader compiler you wish to use (`dxc`, `static-dxc`, or `fxc`). Note that `dxc` requires `dxil.dll` and `dxcompiler.dll` to be in the working directory, and `static-dxc` requires the `static-dxc` crate feature to be enabled. Otherwise, it will fall back to `fxc`.
- `WGPU_GLES_MINOR_VERSION` with the minor OpenGL ES 3 version number to request (`0`, `1`, `2` or `automatic`).
- `WGPU_ALLOW_UNDERLYING_NONCOMPLIANT_ADAPTER` with a boolean whether non-compliant drivers are enumerated (`0` for false, `1` for true).

Expand Down
3 changes: 3 additions & 0 deletions wgpu-hal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ dx12 = [
"windows/Win32_System_Threading",
"windows/Win32_UI_WindowsAndMessaging",
]
## Enables statically linking DXC.
static-dxc = ["dep:mach-dxcompiler-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).
Expand Down Expand Up @@ -158,6 +160,7 @@ windows = { workspace = true, optional = true }
bit-set = { workspace = true, optional = true }
range-alloc = { workspace = true, optional = true }
gpu-allocator = { workspace = true, optional = true }
mach-dxcompiler-rs = { workspace = true, optional = true }
# For core macros. This crate is also reexported as windows::core.
windows-core = { workspace = true, optional = true }

Expand Down
2 changes: 1 addition & 1 deletion wgpu-hal/examples/ray-traced-triangle/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ impl<A: hal::Api> Example<A> {
let instance_desc = hal::InstanceDescriptor {
name: "example",
flags: wgt::InstanceFlags::default(),
dx12_shader_compiler: wgt::Dx12Compiler::Dxc {
dx12_shader_compiler: wgt::Dx12Compiler::DynamicDxc {
dxil_path: None,
dxc_path: None,
},
Expand Down
25 changes: 20 additions & 5 deletions wgpu-hal/src/dx12/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,29 @@ impl crate::Instance for super::Instance {

// Initialize DXC shader compiler
let dxc_container = match desc.dx12_shader_compiler.clone() {
wgt::Dx12Compiler::Dxc {
wgt::Dx12Compiler::DynamicDxc {
dxil_path,
dxc_path,
} => {
let container = super::shader_compilation::get_dxc_container(dxc_path, dxil_path)
.map_err(|e| {
crate::InstanceError::with_source(String::from("Failed to load DXC"), e)
})?;
let container =
super::shader_compilation::get_dynamic_dxc_container(dxc_path, dxil_path)
.map_err(|e| {
crate::InstanceError::with_source(
String::from("Failed to load dynamic DXC"),
e,
)
})?;

container.map(Arc::new)
}
wgt::Dx12Compiler::StaticDxc => {
let container =
super::shader_compilation::get_static_dxc_container().map_err(|e| {
crate::InstanceError::with_source(
String::from("Failed to load static DXC"),
e,
)
})?;

container.map(Arc::new)
}
Expand Down
138 changes: 97 additions & 41 deletions wgpu-hal/src/dx12/shader_compilation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,10 @@ struct DxcLib {
}

impl DxcLib {
fn new(lib_path: Option<PathBuf>, lib_name: &'static str) -> Result<Self, libloading::Error> {
fn new_dynamic(
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
Expand All @@ -111,37 +114,54 @@ impl DxcLib {
}

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") }?;

let mut result__ = None;
(func)(&T::CLSID, &T::IID, <*mut _>::cast(&mut result__))
.ok()
.into_device_result("DxcCreateInstance")?;
result__.ok_or(crate::DeviceError::Unexpected)
unsafe {
type DxcCreateInstanceFn = unsafe 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<DxcCreateInstanceFn> =
self.lib.get(b"DxcCreateInstance\0")?;
dxc_create_instance::<T>(|clsid, iid, ppv| func(clsid, iid, ppv))
}
}
}

/// Invokes the provided library function to create a DXC object.
unsafe fn dxc_create_instance<T: DxcObj>(
f: impl Fn(
*const windows_core::GUID,
*const windows_core::GUID,
*mut *mut core::ffi::c_void,
) -> windows_core::HRESULT,
) -> Result<T, crate::DeviceError> {
let mut result__ = None;
f(&T::CLSID, &T::IID, <*mut _>::cast(&mut result__))
.ok()
.into_device_result("DxcCreateInstance")?;
result__.ok_or(crate::DeviceError::Unexpected)
}

// 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,
validator: Option<Dxc::IDxcValidator>,
// Has to be held onto for the lifetime of the device otherwise shaders will fail to compile.
_dxc: DxcLib,
// Only needed when using dynamic linking.
_dxc: Option<DxcLib>,
// Also Has to be held onto for the lifetime of the device otherwise shaders will fail to validate.
_dxil: DxcLib,
// Only needed when using dynamic linking.
_dxil: Option<DxcLib>,
}

pub(super) fn get_dxc_container(
pub(super) fn get_dynamic_dxc_container(
dxc_path: Option<PathBuf>,
dxil_path: Option<PathBuf>,
) -> Result<Option<DxcContainer>, crate::DeviceError> {
let dxc = match DxcLib::new(dxc_path, "dxcompiler.dll") {
let dxc = match DxcLib::new_dynamic(dxc_path, "dxcompiler.dll") {
Ok(dxc) => dxc,
Err(e) => {
log::warn!(
Expand All @@ -153,7 +173,7 @@ pub(super) fn get_dxc_container(
}
};

let dxil = match DxcLib::new(dxil_path, "dxil.dll") {
let dxil = match DxcLib::new_dynamic(dxil_path, "dxil.dll") {
Ok(dxil) => dxil,
Err(e) => {
log::warn!(
Expand All @@ -172,12 +192,47 @@ pub(super) fn get_dxc_container(
Ok(Some(DxcContainer {
compiler,
utils,
validator,
_dxc: dxc,
_dxil: dxil,
validator: Some(validator),
_dxc: Some(dxc),
_dxil: Some(dxil),
}))
}

/// Creates a [`DxcContainer`] that delegates to the statically-linked version of DXC.
pub(super) fn get_static_dxc_container() -> Result<Option<DxcContainer>, crate::DeviceError> {
#[cfg(feature = "static-dxc")]
{
unsafe {
let compiler = dxc_create_instance::<Dxc::IDxcCompiler3>(|clsid, iid, ppv| {
windows_core::HRESULT(mach_dxcompiler_rs::DxcCreateInstance(
clsid.cast(),
iid.cast(),
ppv,
))
})?;
let utils = dxc_create_instance::<Dxc::IDxcUtils>(|clsid, iid, ppv| {
windows_core::HRESULT(mach_dxcompiler_rs::DxcCreateInstance(
clsid.cast(),
iid.cast(),
ppv,
))
})?;

Ok(Some(DxcContainer {
compiler,
utils,
validator: None,
_dxc: None,
_dxil: None,
}))
}
}
#[cfg(not(feature = "static-dxc"))]
{
panic!("Attempted to create a static DXC shader compiler, but the static-dxc feature was not enabled")
}
}

/// Owned PCWSTR
#[allow(clippy::upper_case_acronyms)]
struct OPCWSTR {
Expand Down Expand Up @@ -245,9 +300,12 @@ pub(super) fn compile_dxc(
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 dxc_container.validator.is_some() {
compile_args.push(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
Expand Down Expand Up @@ -288,26 +346,24 @@ pub(super) fn compile_dxc(

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")?;
if let Some(validator) = &dxc_container.validator {
let err_blob = {
let res = unsafe { validator.Validate(&blob, Dxc::DxcValidatorFlags_InPlaceEdit) }
.into_device_result("Validate")?;

unsafe { res.GetErrorBuffer() }.into_device_result("GetErrorBuffer")?
};
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}"),
));
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))
Expand Down
5 changes: 4 additions & 1 deletion wgpu-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7431,12 +7431,15 @@ pub enum Dx12Compiler {
/// 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 {
DynamicDxc {
/// 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>,
/// Path to the `dxcompiler.dll` file, or path to the directory containing `dxcompiler.dll` file. Passing `None` will use standard platform specific dll loading rules.
dxc_path: Option<PathBuf>,
},
/// The statically-linked variant of Dxc.
/// The `static-dxc` feature is required to use this.
StaticDxc,
}

/// Selects which OpenGL ES 3 minor version to request.
Expand Down
12 changes: 12 additions & 0 deletions wgpu/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,18 @@ fragile-send-sync-non-atomic-wasm = [
"wgt/fragile-send-sync-non-atomic-wasm",
]


#! ### External libraries
# --------------------------------------------------------------------
#! The following features facilitate integration with third-party supporting libraries.

## Enables statically linking DXC.
## Normally, to use the modern DXC shader compiler with WGPU, the final application
## must be shipped alongside `dxcompiler.dll` and `dxil.dll` (which can be downloaded from Microsoft's GitHub).
## This feature statically links a version of DXC so that no external binaries are required
## to compile DX12 shaders.
static-dxc = ["hal/static-dxc"]

# wgpu-core is always available as an optional dependency, "wgc".
# Whenever wgpu-core is selected, we want raw window handle support.
[dependencies.wgc]
Expand Down
4 changes: 3 additions & 1 deletion wgpu/src/util/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,12 @@ pub fn dx12_shader_compiler_from_env() -> Option<wgt::Dx12Compiler> {
.map(str::to_lowercase)
.as_deref()
{
Ok("dxc") => wgt::Dx12Compiler::Dxc {
Ok("dxc") => wgt::Dx12Compiler::DynamicDxc {
dxil_path: None,
dxc_path: None,
},
#[cfg(feature = "static-dxc")]
Ok("static-dxc") => wgt::Dx12Compiler::StaticDxc,
Ok("fxc") => wgt::Dx12Compiler::Fxc,
_ => return None,
},
Expand Down

0 comments on commit be02606

Please sign in to comment.