Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Static linking for DXC via mach-dxcompiler #6574

Merged
merged 6 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,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 @@ -7395,12 +7395,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 {
cwfitzgerald marked this conversation as resolved.
Show resolved Hide resolved
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