Updated Dxc integration for DX12 backend (#3356)

Co-authored-by: unknown <alimilhim5@gmail.com>
Co-authored-by: Connor Fitzgerald <connorwadefitzgerald@gmail.com>
Closes https://github.com/gfx-rs/wgpu/issues/2722
closes https://github.com/gfx-rs/wgpu/pull/3147
This commit is contained in:
Elabajaba 2023-01-18 16:25:56 -05:00 committed by GitHub
parent 0849e78600
commit 81569dd6c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 593 additions and 127 deletions

View File

@ -103,10 +103,54 @@ Additionally `Surface::get_default_config` now returns an Option and returns Non
`Instance::create_surface()` now returns `Result<Surface, CreateSurfaceError>` instead of `Surface`. This allows an error to be returned instead of panicking if the given window is a HTML canvas and obtaining a WebGPU or WebGL 2 context fails. (No other platforms currently report any errors through this path.) By @kpreid in [#3052](https://github.com/gfx-rs/wgpu/pull/3052/)
#### Instance creation now takes `InstanceDescriptor` instead of `Backends`
`Instance::new()` and `hub::Global::new()` now take an `InstanceDescriptor` struct which cointains both the existing `Backends` selection as well as a new `Dx12Compiler` field for selecting which Dx12 shader compiler to use.
```diff
- let instance = Instance::new(wgpu::Backends::all());
+ let instance = Instance::new(wgpu::InstanceDescriptor {
+ backends: wgpu::Backends::all(),
+ dx12_shader_compiler: wgpu::Dx12Compiler::Fxc,
+ });
```
```diff
- let global = wgc::hub::Global::new(
- "player",
- IdentityPassThroughFactory,
- wgpu::Backends::all(),
- );
+ let global = wgc::hub::Global::new(
+ "player",
+ IdentityPassThroughFactory,
+ wgpu::InstanceDescriptor {
+ backends: wgpu::Backends::all(),
+ dx12_shader_compiler: wgpu::Dx12Compiler::Fxc,
+ },
+ );
```
`Instance` now also also implements `Default`, which uses `wgpu::Backends::all()` and `wgpu::Dx12Compiler::Fxc` for `InstanceDescriptor`
```diff
- let instance = Instance::new(wgpu::InstanceDescriptor {
- backends: wgpu::Backends::all(),
- dx12_shader_compiler: wgpu::Dx12Compiler::Fxc,
- });
+ let instance = Instance::default();
```
By @Elabajaba in [#3356](https://github.com/gfx-rs/wgpu/pull/3356)
#### Suballocate DX12 buffers and textures
`wgpu`'s DX12 backend can now suballocate buffers and textures when the `windows_rs` feature is enabled, which can give a significant increase in performance (in testing I've seen a 10000%+ improvement in a simple scene with 200 `write_buffer` calls per frame, and a 40%+ improvement in [Bistro using Bevy](https://github.com/vleue/bevy_bistro_playground)). Previously `wgpu-hal`'s DX12 backend created a new heap on the GPU every time you called write_buffer (by calling `CreateCommittedResource`), whereas now with the `windows_rs` feature enabled it uses [`gpu_allocator`](https://crates.io/crates/gpu-allocator) to manage GPU memory (and calls `CreatePlacedResource` with a suballocated heap). By @Elabajaba in [#3163](https://github.com/gfx-rs/wgpu/pull/3163)
#### DXC Shader Compiler Support for DX12
You can now choose to use the DXC compiler for DX12 instead of FXC. The DXC compiler is faster, less buggy, and allows for new features compared to the old, unmaintained FXC compiler. You can choose which compiler to use at `Instance` creation using the `Dx12Compiler` field in the `InstanceDescriptor` struct. Note that DXC requires both `dxcompiler.dll` and `dxil.dll`, which can be downloaded from https://github.com/microsoft/DirectXShaderCompiler/releases. Both .dlls need to be shipped with your application when targeting DX12 and using the `DXC` compiler. If the .dlls can't be loaded, then it will fall back to the FXC compiler. By @39ali and @Elabajaba in [#3356](https://github.com/gfx-rs/wgpu/pull/3356)
#### Texture Format Reinterpretation
The `view_formats` field is used to specify formats that are compatible with the texture format to allow the creation of views with different formats, currently, only changing srgb-ness is allowed.

28
Cargo.lock generated
View File

@ -340,6 +340,12 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "com-rs"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf43edc576402991846b093a7ca18a3477e0ef9c588cde84964b5d3e43016642"
[[package]]
name = "concurrent-queue"
version = "2.0.0"
@ -1178,6 +1184,21 @@ dependencies = [
"ahash",
]
[[package]]
name = "hassle-rs"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90601c6189668c7345fc53842cb3f3a3d872203d523be1b3cb44a36a3e62fb85"
dependencies = [
"bitflags",
"com-rs",
"libc",
"libloading",
"thiserror",
"widestring",
"winapi",
]
[[package]]
name = "heck"
version = "0.3.3"
@ -2971,6 +2992,7 @@ dependencies = [
"gpu-alloc",
"gpu-allocator",
"gpu-descriptor",
"hassle-rs",
"js-sys",
"khronos-egl",
"libc",
@ -3021,6 +3043,12 @@ dependencies = [
"once_cell",
]
[[package]]
name = "widestring"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983"
[[package]]
name = "winapi"
version = "0.3.9"

View File

@ -100,6 +100,7 @@ gpu-allocator = { version = "0.21", default_features = false, features = ["d3d12
native = { package = "d3d12", version = "0.5.0" }
range-alloc = "0.1"
winapi = "0.3"
hassle-rs = "0.9.0"
# Gles dependencies
egl = { package = "khronos-egl", version = "4.1" }

View File

@ -130,6 +130,7 @@ All testing and example infrastructure shares the same set of environment variab
- `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`, `dx11`, or `gl`).
- `WGPU_POWER_PREF` with the power preference to choose when a specific adapter name isn't specified (`high` or `low`)
- `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`)
When running the CTS, use the variables `DENO_WEBGPU_ADAPTER_NAME`, `DENO_WEBGPU_BACKEND`, `DENO_WEBGPU_POWER_PREFERENCE`.

View File

@ -252,7 +252,10 @@ pub async fn op_webgpu_request_adapter(
state.put(wgpu_core::hub::Global::new(
"webgpu",
wgpu_core::hub::IdentityManagerFactory,
backends,
wgpu_types::InstanceDescriptor {
backends,
dx12_shader_compiler: wgpu_types::Dx12Compiler::Fxc,
},
));
state.borrow::<Instance>()
};

View File

@ -43,7 +43,11 @@ fn main() {
.build(&event_loop)
.unwrap();
let global = wgc::hub::Global::new("player", IdentityPassThroughFactory, wgt::Backends::all());
let global = wgc::hub::Global::new(
"player",
IdentityPassThroughFactory,
wgt::InstanceDescriptor::default(),
);
let mut command_buffer_id_manager = wgc::hub::IdentityManager::default();
#[cfg(feature = "winit")]

View File

@ -178,7 +178,14 @@ impl Corpus {
let dir = path.parent().unwrap();
let corpus: Corpus = ron::de::from_reader(File::open(&path).unwrap()).unwrap();
let global = wgc::hub::Global::new("test", IdentityPassThroughFactory, corpus.backends);
let global = wgc::hub::Global::new(
"test",
IdentityPassThroughFactory,
wgt::InstanceDescriptor {
backends: corpus.backends,
dx12_shader_compiler: wgt::Dx12Compiler::Fxc,
},
);
for &backend in BACKENDS {
if !corpus.backends.contains(backend.into()) {
continue;

View File

@ -1108,10 +1108,10 @@ pub struct Global<G: GlobalIdentityHandlerFactory> {
}
impl<G: GlobalIdentityHandlerFactory> Global<G> {
pub fn new(name: &str, factory: G, backends: wgt::Backends) -> Self {
pub fn new(name: &str, factory: G, instance_desc: wgt::InstanceDescriptor) -> Self {
profiling::scope!("Global::new");
Self {
instance: Instance::new(name, backends),
instance: Instance::new(name, instance_desc),
surfaces: Registry::without_backend(&factory, "Surface"),
hubs: Hubs::new(&factory),
}

View File

@ -67,9 +67,9 @@ pub struct Instance {
}
impl Instance {
pub fn new(name: &str, backends: Backends) -> Self {
fn init<A: HalApi>(_: A, mask: Backends) -> Option<A::Instance> {
if mask.contains(A::VARIANT.into()) {
pub fn new(name: &str, instance_desc: wgt::InstanceDescriptor) -> Self {
fn init<A: HalApi>(_: A, instance_desc: &wgt::InstanceDescriptor) -> Option<A::Instance> {
if instance_desc.backends.contains(A::VARIANT.into()) {
let mut flags = hal::InstanceFlags::empty();
if cfg!(debug_assertions) {
flags |= hal::InstanceFlags::VALIDATION;
@ -78,6 +78,7 @@ impl Instance {
let hal_desc = hal::InstanceDescriptor {
name: "wgpu",
flags,
dx12_shader_compiler: instance_desc.dx12_shader_compiler.clone(),
};
unsafe { hal::Instance::init(&hal_desc).ok() }
} else {
@ -88,15 +89,15 @@ impl Instance {
Self {
name: name.to_string(),
#[cfg(feature = "vulkan")]
vulkan: init(hal::api::Vulkan, backends),
vulkan: init(hal::api::Vulkan, &instance_desc),
#[cfg(feature = "metal")]
metal: init(hal::api::Metal, backends),
metal: init(hal::api::Metal, &instance_desc),
#[cfg(feature = "dx12")]
dx12: init(hal::api::Dx12, backends),
dx12: init(hal::api::Dx12, &instance_desc),
#[cfg(feature = "dx11")]
dx11: init(hal::api::Dx11, backends),
dx11: init(hal::api::Dx11, &instance_desc),
#[cfg(feature = "gles")]
gl: init(hal::api::Gles, backends),
gl: init(hal::api::Gles, &instance_desc),
}
}

View File

@ -35,6 +35,7 @@ dx11 = ["naga/hlsl-out", "native", "libloading", "winapi/d3d11", "winapi/d3d11_1
dx12 = ["naga/hlsl-out", "native", "bit-set", "range-alloc", "winapi/d3d12", "winapi/d3d12shader", "winapi/d3d12sdklayers", "winapi/dxgi1_6"]
# TODO: This is a separate feature until Mozilla okays windows-rs, see https://github.com/gfx-rs/wgpu/issues/3207 for the tracking issue.
windows_rs = ["gpu-allocator"]
dxc_shader_compiler = ["hassle-rs"]
renderdoc = ["libloading", "renderdoc-sys"]
emscripten = ["gles"]
@ -75,6 +76,7 @@ glow = { git = "https://github.com/grovesNL/glow", rev = "c8a011fcd57a5c68cc917e
bit-set = { version = "0.5", optional = true }
range-alloc = { version = "0.1", optional = true }
gpu-allocator = { version = "0.21", default_features = false, features = ["d3d12", "windows", "public-winapi"], optional = true }
hassle-rs = { version = "0.9", optional = true }
[dependencies.wgt]
package = "wgpu-types"

View File

@ -96,6 +96,8 @@ impl<A: hal::Api> Example<A> {
} else {
hal::InstanceFlags::empty()
},
// Can't rely on having DXC available, so use FXC instead
dx12_shader_compiler: wgt::Dx12Compiler::Fxc,
};
let instance = unsafe { A::Instance::init(&instance_desc)? };
let mut surface = unsafe {

View File

@ -52,6 +52,7 @@ impl super::Adapter {
adapter: native::DxgiAdapter,
library: &Arc<native::D3D12Lib>,
instance_flags: crate::InstanceFlags,
dx12_shader_compiler: &wgt::Dx12Compiler,
) -> Option<crate::ExposedAdapter<super::Api>> {
// Create the device so that we can get the capabilities.
let device = {
@ -243,6 +244,7 @@ impl super::Adapter {
private_caps,
presentation_timer,
workarounds,
dx12_shader_compiler: dx12_shader_compiler.clone(),
},
info,
features,
@ -347,7 +349,13 @@ impl crate::Adapter<super::Api> for super::Adapter {
.into_device_result("Queue creation")?
};
let device = super::Device::new(self.device, queue, self.private_caps, &self.library)?;
let device = super::Device::new(
self.device,
queue,
self.private_caps,
&self.library,
self.dx12_shader_compiler.clone(),
)?;
Ok(crate::OpenDevice {
device,
queue: super::Queue {

View File

@ -5,10 +5,10 @@ use crate::{
use super::{conv, descriptor, view};
use parking_lot::Mutex;
use std::{ffi, mem, num::NonZeroU32, ptr, slice, sync::Arc};
use std::{ffi, mem, num::NonZeroU32, ptr, sync::Arc};
use winapi::{
shared::{dxgiformat, dxgitype, minwindef::BOOL, winerror},
um::{d3d12, d3dcompiler, synchapi, winbase},
um::{d3d12, synchapi, winbase},
Interface,
};
@ -21,9 +21,18 @@ impl super::Device {
present_queue: native::CommandQueue,
private_caps: super::PrivateCapabilities,
library: &Arc<native::D3D12Lib>,
dx12_shader_compiler: wgt::Dx12Compiler,
) -> Result<Self, crate::DeviceError> {
let mem_allocator = super::suballocation::create_allocator_wrapper(&raw)?;
let dxc_container = match dx12_shader_compiler {
wgt::Dx12Compiler::Dxc {
dxil_path,
dxc_path,
} => super::shader_compilation::get_dxc_container(dxc_path, dxil_path)?,
wgt::Dx12Compiler::Fxc => None,
};
let mut idle_fence = native::Fence::null();
let hr = unsafe {
profiling::scope!("ID3D12Device::CreateFence");
@ -166,6 +175,7 @@ impl super::Device {
render_doc: Default::default(),
null_rtv_handle,
mem_allocator,
dxc_container,
})
}
@ -192,7 +202,7 @@ impl super::Device {
stage: &crate::ProgrammableStage<super::Api>,
layout: &super::PipelineLayout,
naga_stage: naga::ShaderStage,
) -> Result<native::Blob, crate::PipelineError> {
) -> Result<super::CompiledShader, crate::PipelineError> {
use naga::back::hlsl;
let stage_bit = crate::auxil::map_naga_stage(naga_stage);
@ -212,72 +222,44 @@ impl super::Device {
naga_stage.to_hlsl_str(),
layout.naga_options.shader_model.to_str()
);
let ep_index = module
.entry_points
.iter()
.position(|ep| ep.stage == naga_stage && ep.name == stage.entry_point)
.ok_or(crate::PipelineError::EntryPoint(naga_stage))?;
let raw_ep = reflection_info.entry_point_names[ep_index]
.as_ref()
.map(|name| ffi::CString::new(name.as_str()).unwrap())
.map_err(|e| crate::PipelineError::Linkage(stage_bit, format!("{}", e)))?;
let mut shader_data = native::Blob::null();
let mut error = native::Blob::null();
let mut compile_flags = d3dcompiler::D3DCOMPILE_ENABLE_STRICTNESS;
if self
.private_caps
.instance_flags
.contains(crate::InstanceFlags::DEBUG)
{
compile_flags |=
d3dcompiler::D3DCOMPILE_DEBUG | d3dcompiler::D3DCOMPILE_SKIP_OPTIMIZATION;
}
let source_name = stage
.module
.raw_name
.as_ref()
.and_then(|cstr| cstr.to_str().ok())
.unwrap_or_default();
let source_name = match stage.module.raw_name {
Some(ref cstr) => cstr.as_c_str().as_ptr(),
None => ptr::null(),
};
let hr = unsafe {
profiling::scope!("d3dcompiler::D3DCompile");
d3dcompiler::D3DCompile(
source.as_ptr() as *const _,
source.len(),
// Compile with DXC if available, otherwise fall back to FXC
let (result, log_level) = if let Some(ref dxc_container) = self.dxc_container {
super::shader_compilation::compile_dxc(
self,
&source,
source_name,
ptr::null(),
ptr::null_mut(),
raw_ep.as_ptr(),
full_stage.as_ptr() as *const i8,
compile_flags,
0,
shader_data.mut_void() as *mut *mut _,
error.mut_void() as *mut *mut _,
raw_ep,
stage_bit,
full_stage,
dxc_container,
)
} else {
super::shader_compilation::compile_fxc(
self,
&source,
source_name,
&ffi::CString::new(raw_ep.as_str()).unwrap(),
stage_bit,
full_stage,
)
};
let (result, log_level) = match hr.into_result() {
Ok(()) => (Ok(shader_data), log::Level::Info),
Err(e) => {
let mut full_msg = format!("D3DCompile error ({})", e);
if !error.is_null() {
use std::fmt::Write as _;
let message = unsafe {
slice::from_raw_parts(
error.GetBufferPointer() as *const u8,
error.GetBufferSize(),
)
};
let _ = write!(full_msg, ": {}", String::from_utf8_lossy(message));
unsafe {
error.destroy();
}
}
(
Err(crate::PipelineError::Linkage(stage_bit, full_msg)),
log::Level::Warn,
)
}
};
log::log!(
@ -1078,7 +1060,12 @@ impl crate::Device<super::Api> for super::Device {
},
bind_group_infos,
naga_options: hlsl::Options {
shader_model: hlsl::ShaderModel::V5_1,
shader_model: match self.dxc_container {
// DXC
Some(_) => hlsl::ShaderModel::V6_0,
// FXC doesn't support SM 6.0
None => hlsl::ShaderModel::V5_1,
},
binding_map,
fake_missing_bindings: false,
special_constants_binding,
@ -1294,9 +1281,9 @@ impl crate::Device<super::Api> for super::Device {
let blob_fs = match desc.fragment_stage {
Some(ref stage) => {
shader_stages |= wgt::ShaderStages::FRAGMENT;
self.load_shader(stage, desc.layout, naga::ShaderStage::Fragment)?
Some(self.load_shader(stage, desc.layout, naga::ShaderStage::Fragment)?)
}
None => native::Blob::null(),
None => None,
};
let mut vertex_strides = [None; crate::MAX_VERTEX_BUFFERS];
@ -1369,11 +1356,10 @@ impl crate::Device<super::Api> for super::Device {
let raw_desc = d3d12::D3D12_GRAPHICS_PIPELINE_STATE_DESC {
pRootSignature: desc.layout.shared.signature.as_mut_ptr(),
VS: *native::Shader::from_blob(blob_vs),
PS: if blob_fs.is_null() {
*native::Shader::null()
} else {
*native::Shader::from_blob(blob_fs)
VS: *blob_vs.create_native_shader(),
PS: match blob_fs {
Some(ref shader) => *shader.create_native_shader(),
None => *native::Shader::null(),
},
GS: *native::Shader::null(),
DS: *native::Shader::null(),
@ -1445,9 +1431,9 @@ impl crate::Device<super::Api> for super::Device {
};
unsafe { blob_vs.destroy() };
if !blob_fs.is_null() {
if let Some(blob_fs) = blob_fs {
unsafe { blob_fs.destroy() };
}
};
hr.into_result()
.map_err(|err| crate::PipelineError::Linkage(shader_stages, err.into_owned()))?;
@ -1478,7 +1464,7 @@ impl crate::Device<super::Api> for super::Device {
profiling::scope!("ID3D12Device::CreateComputePipelineState");
self.raw.create_compute_pipeline_state(
desc.layout.shared.signature,
native::Shader::from_blob(blob_cs),
blob_cs.create_native_shader(),
0,
native::CachedPSO::null(),
native::PipelineStateFlags::empty(),

View File

@ -64,6 +64,7 @@ impl crate::Instance<super::Api> for super::Instance {
_lib_dxgi: lib_dxgi,
supports_allow_tearing,
flags: desc.flags,
dx12_shader_compiler: desc.dx12_shader_compiler.clone(),
})
}
@ -91,7 +92,9 @@ impl crate::Instance<super::Api> for super::Instance {
adapters
.into_iter()
.filter_map(|raw| super::Adapter::expose(raw, &self.library, self.flags))
.filter_map(|raw| {
super::Adapter::expose(raw, &self.library, self.flags, &self.dx12_shader_compiler)
})
.collect()
}
}

View File

@ -39,6 +39,7 @@ mod conv;
mod descriptor;
mod device;
mod instance;
mod shader_compilation;
mod suballocation;
mod view;
@ -92,6 +93,7 @@ pub struct Instance {
supports_allow_tearing: bool,
_lib_dxgi: native::DxgiLib,
flags: crate::InstanceFlags,
dx12_shader_compiler: wgt::Dx12Compiler,
}
impl Instance {
@ -173,6 +175,7 @@ pub struct Adapter {
//Note: this isn't used right now, but we'll need it later.
#[allow(unused)]
workarounds: Workarounds,
dx12_shader_compiler: wgt::Dx12Compiler,
}
unsafe impl Send for Adapter {}
@ -241,6 +244,7 @@ pub struct Device {
render_doc: crate::auxil::renderdoc::RenderDoc,
null_rtv_handle: descriptor::Handle,
mem_allocator: Option<Mutex<suballocation::GpuAllocatorWrapper>>,
dxc_container: Option<shader_compilation::DxcContainer>,
}
unsafe impl Send for Device {}
@ -538,6 +542,30 @@ pub struct ShaderModule {
raw_name: Option<ffi::CString>,
}
pub(super) enum CompiledShader {
#[allow(unused)]
Dxc(Vec<u8>),
Fxc(native::Blob),
}
impl CompiledShader {
fn create_native_shader(&self) -> native::Shader {
match *self {
CompiledShader::Dxc(ref shader) => native::Shader::from_raw(shader),
CompiledShader::Fxc(shader) => native::Shader::from_blob(shader),
}
}
unsafe fn destroy(self) {
match self {
CompiledShader::Dxc(_) => {}
CompiledShader::Fxc(shader) => unsafe {
shader.destroy();
},
}
}
}
pub struct RenderPipeline {
raw: native::PipelineState,
layout: PipelineLayoutShared,

View File

@ -0,0 +1,255 @@
use std::ptr;
pub(super) use dxc::{compile_dxc, get_dxc_container, DxcContainer};
use winapi::um::d3dcompiler;
use crate::auxil::dxgi::result::HResult;
// 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.
pub(super) fn compile_fxc(
device: &super::Device,
source: &String,
source_name: &str,
raw_ep: &std::ffi::CString,
stage_bit: wgt::ShaderStages,
full_stage: String,
) -> (
Result<super::CompiledShader, crate::PipelineError>,
log::Level,
) {
profiling::scope!("compile_fxc");
let mut shader_data = native::Blob::null();
let mut compile_flags = d3dcompiler::D3DCOMPILE_ENABLE_STRICTNESS;
if device
.private_caps
.instance_flags
.contains(crate::InstanceFlags::DEBUG)
{
compile_flags |= d3dcompiler::D3DCOMPILE_DEBUG | d3dcompiler::D3DCOMPILE_SKIP_OPTIMIZATION;
}
let mut error = native::Blob::null();
let hr = unsafe {
profiling::scope!("d3dcompiler::D3DCompile");
d3dcompiler::D3DCompile(
source.as_ptr().cast(),
source.len(),
source_name.as_ptr().cast(),
ptr::null(),
ptr::null_mut(),
raw_ep.as_ptr(),
full_stage.as_ptr().cast(),
compile_flags,
0,
shader_data.mut_void().cast(),
error.mut_void().cast(),
)
};
match hr.into_result() {
Ok(()) => (
Ok(super::CompiledShader::Fxc(shader_data)),
log::Level::Info,
),
Err(e) => {
let mut full_msg = format!("FXC D3DCompile error ({})", e);
if !error.is_null() {
use std::fmt::Write as _;
let message = unsafe {
std::slice::from_raw_parts(
error.GetBufferPointer() as *const u8,
error.GetBufferSize(),
)
};
let _ = write!(full_msg, ": {}", String::from_utf8_lossy(message));
unsafe {
error.destroy();
}
}
(
Err(crate::PipelineError::Linkage(stage_bit, full_msg)),
log::Level::Warn,
)
}
}
}
// 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::path::PathBuf;
pub(crate) struct DxcContainer {
pub compiler: hassle_rs::DxcCompiler,
pub library: hassle_rs::DxcLibrary,
// Has to be held onto for the lifetime of the device otherwise shaders will fail to compile
_dxc: hassle_rs::Dxc,
}
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.
match hassle_rs::Dxil::new(dxil_path) {
Ok(_) => (),
Err(e) => {
log::warn!("Failed to load dxil.dll. Defaulting to Fxc instead: {}", e);
return Ok(None);
}
};
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 dxc_compiler = dxc.create_compiler()?;
let dxc_library = dxc.create_library()?;
Ok(Some(DxcContainer {
_dxc: dxc,
compiler: dxc_compiler,
library: dxc_library,
}))
}
pub(crate) fn compile_dxc(
device: &crate::dx12::Device,
source: &str,
source_name: &str,
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, 3>::new_const();
compile_flags.push("-Ges"); // d3dcompiler::D3DCOMPILE_ENABLE_STRICTNESS
if device
.private_caps
.instance_flags
.contains(crate::InstanceFlags::DEBUG)
{
compile_flags.push("-Zi"); // d3dcompiler::D3DCOMPILE_SKIP_OPTIMIZATION
compile_flags.push("-Od"); // d3dcompiler::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),
};
// DXC will automatically validate the shaders during compilation as long as dxil.dll is available, so we don't need to do it ourselves.
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) => (
Ok(crate::dx12::CompiledShader::Dxc(dxc_blob.to_vec())),
log::Level::Info,
),
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: {:?}", e),
)),
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!(),
}
}
}
}
// These are stubs for when the `dxc_shader_compiler` feature is disabled.
#[cfg(not(feature = "dxc_shader_compiler"))]
mod dxc {
use std::path::PathBuf;
pub(crate) struct DxcContainer {}
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)
}
// 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: &str,
_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.");
}
}

View File

@ -743,6 +743,7 @@ bitflags::bitflags! {
pub struct InstanceDescriptor<'a> {
pub name: &'a str,
pub flags: InstanceFlags,
pub dx12_shader_compiler: wgt::Dx12Compiler,
}
#[derive(Clone, Debug)]

View File

@ -281,7 +281,7 @@ mod inner {
env_logger::init();
let args: Vec<_> = std::env::args().skip(1).collect();
let instance = wgpu::Instance::new(wgpu::Backends::all());
let instance = wgpu::Instance::default();
let adapters: Vec<_> = instance.enumerate_adapters(wgpu::Backends::all()).collect();
let adapter_count = adapters.len();

View File

@ -12,6 +12,7 @@
#[cfg(any(feature = "serde", test))]
use serde::{Deserialize, Serialize};
use std::hash::{Hash, Hasher};
use std::path::PathBuf;
use std::{num::NonZeroU32, ops::Range};
pub mod assertions;
@ -5362,3 +5363,50 @@ impl Default for ShaderBoundChecks {
Self::new()
}
}
/// 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.
///
/// `wgpu::utils::init::dx12_shader_compiler_from_env` can be used to set the compiler
/// from the `WGPU_DX12_SHADER_COMPILER` environment variable, but this should only be used for testing.
#[derive(Clone, Debug, Default)]
pub enum Dx12Compiler {
/// The Fxc compiler (default) is old, slow and unmaintained.
///
/// However, it doesn't require any additional .dlls to be shipped with the application.
#[default]
Fxc,
/// The Dxc compiler is new, fast and maintained.
///
/// 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
Dxc {
/// Path to the `dxcompiler.dll` file. Passing `None` will use standard platform specific dll loading rules.
dxil_path: Option<PathBuf>,
/// Path to the `dxil.dll` file. Passing `None` will use standard platform specific dll loading rules.
dxc_path: Option<PathBuf>,
},
}
/// Options for creating an instance.
pub struct InstanceDescriptor {
/// Which `Backends` to enable.
pub backends: Backends,
/// Which DX12 shader compiler to use.
pub dx12_shader_compiler: Dx12Compiler,
}
impl Default for InstanceDescriptor {
fn default() -> Self {
Self {
backends: Backends::all(),
dx12_shader_compiler: Dx12Compiler::default(),
}
}
}

View File

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

View File

@ -36,12 +36,15 @@ async fn create_red_image_with_dimensions(
width: usize,
height: usize,
) -> (Device, Buffer, BufferDimensions, SubmissionIndex) {
let adapter = wgpu::Instance::new(
wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all),
)
.request_adapter(&wgpu::RequestAdapterOptions::default())
.await
.unwrap();
let backends = wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all);
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends,
dx12_shader_compiler: wgpu::Dx12Compiler::default(),
});
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions::default())
.await
.unwrap();
let (device, queue) = adapter
.request_device(

View File

@ -157,9 +157,13 @@ async fn setup<E: Example>(title: &str) -> Setup {
log::info!("Initializing the surface...");
let backend = wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all);
let backends = wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all);
let dx12_shader_compiler = wgpu::util::dx12_shader_compiler_from_env().unwrap_or_default();
let instance = wgpu::Instance::new(backend);
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends,
dx12_shader_compiler,
});
let (size, surface) = unsafe {
let size = window.inner_size();
@ -180,7 +184,7 @@ async fn setup<E: Example>(title: &str) -> Setup {
(size, surface)
};
let adapter =
wgpu::util::initialize_adapter_from_env_or_default(&instance, backend, Some(&surface))
wgpu::util::initialize_adapter_from_env_or_default(&instance, backends, Some(&surface))
.await
.expect("No suitable GPU adapters found on the system!");

View File

@ -33,7 +33,7 @@ async fn run() {
async fn execute_gpu(numbers: &[u32]) -> Option<Vec<u32>> {
// Instantiates instance of WebGPU
let instance = wgpu::Instance::new(wgpu::Backends::all());
let instance = wgpu::Instance::default();
// `request_adapter` instantiates the general connection to the GPU
let adapter = instance

View File

@ -7,7 +7,9 @@ use winit::{
async fn run(event_loop: EventLoop<()>, window: Window) {
let size = window.inner_size();
let instance = wgpu::Instance::new(wgpu::Backends::all());
let instance = wgpu::Instance::default();
let surface = unsafe { instance.create_surface(&window) }.unwrap();
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {

View File

@ -62,7 +62,7 @@ impl Viewport {
}
async fn run(event_loop: EventLoop<()>, viewports: Vec<(Window, wgpu::Color)>) {
let instance = wgpu::Instance::new(wgpu::Backends::all());
let instance = wgpu::Instance::default();
let viewports: Vec<_> = viewports
.into_iter()
.map(|(window, color)| ViewportDesc::new(window, color, &instance))

View File

@ -2,7 +2,7 @@
async fn run() {
#[cfg_attr(target_arch = "wasm32", allow(unused_variables))]
let adapter = {
let instance = wgpu::Instance::new(wgpu::Backends::all());
let instance = wgpu::Instance::default();
#[cfg(not(target_arch = "wasm32"))]
{
log::info!("Available adapters:");

View File

@ -511,11 +511,11 @@ impl crate::Context for Context {
type PopErrorScopeFuture = Ready<Option<crate::Error>>;
fn init(backends: wgt::Backends) -> Self {
fn init(instance_desc: wgt::InstanceDescriptor) -> Self {
Self(wgc::hub::Global::new(
"wgpu",
wgc::hub::IdentityManagerFactory,
backends,
instance_desc,
))
}

View File

@ -805,7 +805,7 @@ impl crate::context::Context for Context {
type PopErrorScopeFuture =
MakeSendFuture<wasm_bindgen_futures::JsFuture, fn(JsFutureResult) -> Option<crate::Error>>;
fn init(_backends: wgt::Backends) -> Self {
fn init(_instance_desc: wgt::InstanceDescriptor) -> Self {
let global: Global = js_sys::global().unchecked_into();
let gpu = if !global.window().is_undefined() {
global.unchecked_into::<web_sys::Window>().navigator().gpu()

View File

@ -95,7 +95,7 @@ pub trait Context: Debug + Send + Sized + Sync {
+ 'static;
type PopErrorScopeFuture: Future<Output = Option<Error>> + Send + 'static;
fn init(backends: wgt::Backends) -> Self;
fn init(instance_desc: wgt::InstanceDescriptor) -> Self;
fn instance_create_surface(
&self,
display_handle: raw_window_handle::RawDisplayHandle,

View File

@ -33,18 +33,18 @@ pub use wgt::{
BindingType, BlendComponent, BlendFactor, BlendOperation, BlendState, BufferAddress,
BufferBindingType, BufferSize, BufferUsages, Color, ColorTargetState, ColorWrites,
CommandBufferDescriptor, CompareFunction, CompositeAlphaMode, DepthBiasState,
DepthStencilState, DeviceType, DownlevelCapabilities, DownlevelFlags, DynamicOffset, Extent3d,
Face, Features, FilterMode, FrontFace, ImageDataLayout, ImageSubresourceRange, IndexFormat,
Limits, MultisampleState, Origin3d, PipelineStatisticsTypes, PolygonMode, PowerPreference,
PresentMode, PresentationTimestamp, PrimitiveState, PrimitiveTopology, PushConstantRange,
QueryType, RenderBundleDepthStencil, SamplerBindingType, SamplerBorderColor, ShaderLocation,
ShaderModel, ShaderStages, StencilFaceState, StencilOperation, StencilState,
StorageTextureAccess, SurfaceCapabilities, SurfaceConfiguration, SurfaceStatus, TextureAspect,
TextureDimension, TextureFormat, TextureFormatFeatureFlags, TextureFormatFeatures,
TextureSampleType, TextureUsages, TextureViewDimension, VertexAttribute, VertexFormat,
VertexStepMode, COPY_BUFFER_ALIGNMENT, COPY_BYTES_PER_ROW_ALIGNMENT, MAP_ALIGNMENT,
PUSH_CONSTANT_ALIGNMENT, QUERY_RESOLVE_BUFFER_ALIGNMENT, QUERY_SET_MAX_QUERIES, QUERY_SIZE,
VERTEX_STRIDE_ALIGNMENT,
DepthStencilState, DeviceType, DownlevelCapabilities, DownlevelFlags, Dx12Compiler,
DynamicOffset, Extent3d, Face, Features, FilterMode, FrontFace, ImageDataLayout,
ImageSubresourceRange, IndexFormat, InstanceDescriptor, Limits, MultisampleState, Origin3d,
PipelineStatisticsTypes, PolygonMode, PowerPreference, PresentMode, PresentationTimestamp,
PrimitiveState, PrimitiveTopology, PushConstantRange, QueryType, RenderBundleDepthStencil,
SamplerBindingType, SamplerBorderColor, ShaderLocation, ShaderModel, ShaderStages,
StencilFaceState, StencilOperation, StencilState, StorageTextureAccess, SurfaceCapabilities,
SurfaceConfiguration, SurfaceStatus, TextureAspect, TextureDimension, TextureFormat,
TextureFormatFeatureFlags, TextureFormatFeatures, TextureSampleType, TextureUsages,
TextureViewDimension, VertexAttribute, VertexFormat, VertexStepMode, COPY_BUFFER_ALIGNMENT,
COPY_BYTES_PER_ROW_ALIGNMENT, MAP_ALIGNMENT, PUSH_CONSTANT_ALIGNMENT,
QUERY_RESOLVE_BUFFER_ALIGNMENT, QUERY_SET_MAX_QUERIES, QUERY_SIZE, VERTEX_STRIDE_ALIGNMENT,
};
/// Filter for error scopes.
@ -1286,16 +1286,25 @@ impl Display for SurfaceError {
impl error::Error for SurfaceError {}
impl Default for Instance {
/// Creates a new instance of wgpu with default options.
///
/// Backends are set to `Backends::all()`, and FXC is chosen as the `dx12_shader_compiler`.
fn default() -> Self {
Self::new(InstanceDescriptor::default())
}
}
impl Instance {
/// Create an new instance of wgpu.
///
/// # Arguments
///
/// - `backends` - Controls from which [backends][Backends] wgpu will choose
/// during instantiation.
pub fn new(backends: Backends) -> Self {
/// - `instance_desc` - Has fields for which [backends][Backends] wgpu will choose
/// during instantiation, and which [DX12 shader compiler][Dx12Compiler] wgpu will use.
pub fn new(instance_desc: InstanceDescriptor) -> Self {
Self {
context: Arc::from(crate::backend::Context::init(backends)),
context: Arc::from(crate::backend::Context::init(instance_desc)),
}
}

View File

@ -86,3 +86,23 @@ pub async fn initialize_adapter_from_env_or_default(
}
}
}
/// Choose which DX12 shader compiler to use from the environment variable `WGPU_DX12_COMPILER`.
///
/// Possible values are `dxc` and `fxc`. Case insensitive.
pub fn dx12_shader_compiler_from_env() -> Option<wgt::Dx12Compiler> {
Some(
match std::env::var("WGPU_DX12_COMPILER")
.as_deref()
.map(str::to_lowercase)
.as_deref()
{
Ok("dxc") => wgt::Dx12Compiler::Dxc {
dxil_path: None,
dxc_path: None,
},
Ok("fxc") => wgt::Dx12Compiler::Fxc,
_ => return None,
},
)
}

View File

@ -331,8 +331,12 @@ pub fn initialize_test(parameters: TestParameters, test_function: impl FnOnce(Te
}
fn initialize_adapter() -> (Adapter, SurfaceGuard) {
let backend_bits = wgpu::util::backend_bits_from_env().unwrap_or_else(Backends::all);
let instance = Instance::new(backend_bits);
let backends = wgpu::util::backend_bits_from_env().unwrap_or_else(Backends::all);
let dx12_shader_compiler = wgpu::util::dx12_shader_compiler_from_env().unwrap_or_default();
let instance = Instance::new(wgpu::InstanceDescriptor {
backends,
dx12_shader_compiler,
});
let surface_guard;
let compatible_surface;
@ -358,7 +362,7 @@ fn initialize_adapter() -> (Adapter, SurfaceGuard) {
let compatible_surface: Option<&Surface> = compatible_surface.as_ref();
let adapter = pollster::block_on(wgpu::util::initialize_adapter_from_env_or_default(
&instance,
backend_bits,
backends,
compatible_surface,
))
.expect("could not find suitable adapter on the system");

View File

@ -3,15 +3,17 @@ use wasm_bindgen_test::*;
#[test]
#[wasm_bindgen_test]
fn initialize() {
let _ = wgpu::Instance::new(
wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all),
);
let _ = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all),
dx12_shader_compiler: wgpu::util::dx12_shader_compiler_from_env().unwrap_or_default(),
});
}
fn request_adapter_inner(power: wgt::PowerPreference) {
let instance = wgpu::Instance::new(
wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all),
);
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all),
dx12_shader_compiler: wgpu::util::dx12_shader_compiler_from_env().unwrap_or_default(),
});
let _adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: power,