Load FXC dynamically to remove the dependency on d3dcompiler_47.dll (#7588)

This commit is contained in:
RedMindZ 2025-05-30 01:41:03 +03:00 committed by GitHub
parent 97794f12a9
commit d190106c3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 269 additions and 155 deletions

View File

@ -57,7 +57,7 @@ impl super::Adapter {
library: &Arc<D3D12Lib>,
instance_flags: wgt::InstanceFlags,
memory_budget_thresholds: wgt::MemoryBudgetThresholds,
dxc_container: Option<Arc<shader_compilation::DxcContainer>>,
compiler_container: Arc<shader_compilation::CompilerContainer>,
) -> Option<crate::ExposedAdapter<super::Api>> {
// Create the device so that we can get the capabilities.
let device = {
@ -224,8 +224,8 @@ impl super::Adapter {
}
};
let shader_model = if let Some(ref dxc_container) = dxc_container {
let max_shader_model = match dxc_container.max_shader_model {
let shader_model = if let Some(max_shader_model) = compiler_container.max_shader_model() {
let max_shader_model = match max_shader_model {
wgt::DxcShaderModel::V6_0 => Direct3D12::D3D_SHADER_MODEL_6_0,
wgt::DxcShaderModel::V6_1 => Direct3D12::D3D_SHADER_MODEL_6_1,
wgt::DxcShaderModel::V6_2 => Direct3D12::D3D_SHADER_MODEL_6_2,
@ -519,7 +519,7 @@ impl super::Adapter {
presentation_timer,
workarounds,
memory_budget_thresholds,
dxc_container,
compiler_container,
},
info,
features,
@ -658,7 +658,7 @@ impl crate::Adapter for super::Adapter {
self.private_caps,
&self.library,
self.memory_budget_thresholds,
self.dxc_container.clone(),
self.compiler_container.clone(),
)?;
Ok(crate::OpenDevice {
device,

View File

@ -46,7 +46,7 @@ impl super::Device {
private_caps: super::PrivateCapabilities,
library: &Arc<D3D12Lib>,
memory_budget_thresholds: wgt::MemoryBudgetThresholds,
dxc_container: Option<Arc<shader_compilation::DxcContainer>>,
compiler_container: Arc<shader_compilation::CompilerContainer>,
) -> Result<Self, crate::DeviceError> {
if private_caps
.instance_flags
@ -202,7 +202,7 @@ impl super::Device {
render_doc: Default::default(),
null_rtv_handle,
mem_allocator,
dxc_container,
compiler_container,
counters: Default::default(),
})
}
@ -325,27 +325,14 @@ impl super::Device {
let source_name = stage.module.raw_name.as_deref();
// Compile with DXC if available, otherwise fall back to FXC
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,
dxc_container,
)
} else {
shader_compilation::compile_fxc(
self,
&source,
source_name,
raw_ep,
stage_bit,
&full_stage,
)
};
let result = self.compiler_container.compile(
self,
&source,
source_name,
raw_ep,
stage_bit,
&full_stage,
);
let log_level = if result.is_ok() {
log::Level::Info

View File

@ -10,7 +10,10 @@ use windows::{
};
use super::SurfaceTarget;
use crate::{auxil, dx12::D3D12Lib};
use crate::{
auxil,
dx12::{shader_compilation::CompilerContainer, D3D12Lib},
};
impl crate::Instance for super::Instance {
type A = super::Api;
@ -66,39 +69,34 @@ impl crate::Instance for super::Instance {
}
}
// Initialize DXC shader compiler
let dxc_container = match desc.backend_options.dx12.shader_compiler.clone() {
// Initialize the shader compiler
let compiler_container = match desc.backend_options.dx12.shader_compiler.clone() {
wgt::Dx12Compiler::DynamicDxc {
dxc_path,
max_shader_model,
} => {
let container = super::shader_compilation::get_dynamic_dxc_container(
dxc_path.into(),
max_shader_model,
)
.map_err(|e| {
} => CompilerContainer::new_dynamic_dxc(dxc_path.into(), max_shader_model).map_err(
|e| {
crate::InstanceError::with_source(String::from("Failed to load dynamic DXC"), e)
})?;
Some(Arc::new(container))
}
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,
)
})?;
Some(Arc::new(container))
}
wgt::Dx12Compiler::Fxc => None,
},
)?,
wgt::Dx12Compiler::StaticDxc => CompilerContainer::new_static_dxc().map_err(|e| {
crate::InstanceError::with_source(String::from("Failed to load static DXC"), e)
})?,
wgt::Dx12Compiler::Fxc => CompilerContainer::new_fxc().map_err(|e| {
crate::InstanceError::with_source(String::from("Failed to load FXC"), e)
})?,
};
match dxc_container {
Some(_) => log::debug!("Using DXC for shader compilation"),
None => log::debug!("Using FXC for shader compilation"),
match compiler_container {
CompilerContainer::DynamicDxc(..) => {
log::debug!("Using dynamic DXC for shader compilation")
}
CompilerContainer::StaticDxc(..) => {
log::debug!("Using static DXC for shader compilation")
}
CompilerContainer::Fxc(..) => {
log::debug!("Using FXC for shader compilation")
}
}
Ok(Self {
@ -110,7 +108,7 @@ impl crate::Instance for super::Instance {
supports_allow_tearing,
flags: desc.flags,
memory_budget_thresholds: desc.memory_budget_thresholds,
dxc_container,
compiler_container: Arc::new(compiler_container),
})
}
@ -148,7 +146,7 @@ impl crate::Instance for super::Instance {
&self.library,
self.flags,
self.memory_budget_thresholds,
self.dxc_container.clone(),
self.compiler_container.clone(),
)
})
.collect()

View File

@ -460,7 +460,7 @@ pub struct Instance {
_lib_dxgi: DxgiLib,
flags: wgt::InstanceFlags,
memory_budget_thresholds: wgt::MemoryBudgetThresholds,
dxc_container: Option<Arc<shader_compilation::DxcContainer>>,
compiler_container: Arc<shader_compilation::CompilerContainer>,
}
impl Instance {
@ -592,7 +592,7 @@ pub struct Adapter {
#[allow(unused)]
workarounds: Workarounds,
memory_budget_thresholds: wgt::MemoryBudgetThresholds,
dxc_container: Option<Arc<shader_compilation::DxcContainer>>,
compiler_container: Arc<shader_compilation::CompilerContainer>,
}
unsafe impl Send for Adapter {}
@ -655,7 +655,7 @@ pub struct Device {
render_doc: auxil::renderdoc::RenderDoc,
null_rtv_handle: descriptor::Handle,
mem_allocator: Allocator,
dxc_container: Option<Arc<shader_compilation::DxcContainer>>,
compiler_container: Arc<shader_compilation::CompilerContainer>,
counters: Arc<wgt::HalCounters>,
}

View File

@ -6,19 +6,224 @@ use crate::auxil::dxgi::result::HResult;
use thiserror::Error;
use windows::{
core::{Interface, PCSTR, PCWSTR},
Win32::Graphics::Direct3D::{Dxc, Fxc},
Win32::Graphics::Direct3D::{Dxc, Fxc, ID3DBlob, D3D_SHADER_MACRO},
};
pub(super) fn compile_fxc(
pub(super) enum CompilerContainer {
Fxc(CompilerFxc),
DynamicDxc(CompilerDynamicDxc),
#[cfg_attr(not(static_dxc), allow(unused))]
StaticDxc(CompilerStaticDxc),
}
pub(super) struct CompilerFxc {
fxc: FxcLib,
}
pub(super) struct CompilerDynamicDxc {
max_shader_model: wgt::DxcShaderModel,
compiler: Dxc::IDxcCompiler3,
// Has to be held onto for the lifetime of the device otherwise shaders will fail to compile.
// Only needed when using dynamic linking.
_dxc: DxcLib,
}
pub(super) struct CompilerStaticDxc {
max_shader_model: wgt::DxcShaderModel,
compiler: Dxc::IDxcCompiler3,
}
#[derive(Debug, Error)]
pub(super) enum GetContainerError {
#[error(transparent)]
Device(#[from] crate::DeviceError),
#[error("Failed to load {0}: {1}")]
FailedToLoad(&'static str, libloading::Error),
}
impl CompilerContainer {
pub(super) fn new_fxc() -> Result<Self, GetContainerError> {
FxcLib::new_dynamic().map(|fxc| Self::Fxc(CompilerFxc { fxc }))
}
pub(super) fn new_dynamic_dxc(
dxc_path: PathBuf,
max_shader_model: wgt::DxcShaderModel,
) -> Result<Self, GetContainerError> {
let dxc = DxcLib::new_dynamic(dxc_path)
.map_err(|e| GetContainerError::FailedToLoad("dxcompiler.dll", e))?;
let compiler = dxc.create_instance::<Dxc::IDxcCompiler3>()?;
Ok(Self::DynamicDxc(CompilerDynamicDxc {
max_shader_model,
compiler,
_dxc: dxc,
}))
}
/// Creates a [`CompilerContainer`] that delegates to the statically-linked version of DXC.
pub(super) fn new_static_dxc() -> Result<CompilerContainer, crate::DeviceError> {
#[cfg(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,
))
})?;
Ok(CompilerContainer::StaticDxc(CompilerStaticDxc {
max_shader_model: wgt::DxcShaderModel::V6_7,
compiler,
}))
}
}
#[cfg(not(static_dxc))]
{
panic!("Attempted to create a static DXC shader compiler, but the static-dxc feature was not enabled")
}
}
pub(super) fn max_shader_model(&self) -> Option<wgt::DxcShaderModel> {
match self {
CompilerContainer::Fxc(..) => None,
CompilerContainer::DynamicDxc(CompilerDynamicDxc {
max_shader_model, ..
})
| CompilerContainer::StaticDxc(CompilerStaticDxc {
max_shader_model, ..
}) => Some(max_shader_model.clone()),
}
}
pub(super) fn compile(
&self,
device: &super::Device,
source: &str,
source_name: Option<&CStr>,
raw_ep: &str,
stage_bit: wgt::ShaderStages,
full_stage: &str,
) -> Result<super::CompiledShader, crate::PipelineError> {
match self {
CompilerContainer::Fxc(CompilerFxc { fxc }) => compile_fxc(
device,
source,
source_name,
raw_ep,
stage_bit,
full_stage,
fxc,
),
CompilerContainer::DynamicDxc(CompilerDynamicDxc { compiler, .. })
| CompilerContainer::StaticDxc(CompilerStaticDxc { compiler, .. }) => compile_dxc(
device,
source,
source_name,
raw_ep,
stage_bit,
full_stage,
compiler,
),
}
}
}
type D3DCompileFn = unsafe extern "system" fn(
psrcdata: *const core::ffi::c_void,
srcdatasize: usize,
psourcename: PCSTR,
pdefines: *const D3D_SHADER_MACRO,
pinclude: *mut core::ffi::c_void,
pentrypoint: PCSTR,
ptarget: PCSTR,
flags1: u32,
flags2: u32,
ppcode: *mut *mut core::ffi::c_void,
pperrormsgs: *mut *mut core::ffi::c_void,
) -> windows_core::HRESULT;
#[derive(Debug)]
struct FxcLib {
// `d3dcompile_fn` points into `_lib`, so `_lib` must be held for as long
// as we want to keep compiling shaders with FXC.
_lib: crate::dx12::DynLib,
d3dcompile_fn: D3DCompileFn,
}
impl FxcLib {
const PATH: &str = "d3dcompiler_47.dll";
fn new_dynamic() -> Result<Self, GetContainerError> {
unsafe {
let lib = crate::dx12::DynLib::new(Self::PATH)
.map_err(|e| GetContainerError::FailedToLoad(FxcLib::PATH, e))?;
let d3dcompile_fn: D3DCompileFn = *lib.get::<D3DCompileFn>(c"D3DCompile".to_bytes())?;
Ok(Self {
_lib: lib,
d3dcompile_fn,
})
}
}
#[allow(clippy::too_many_arguments)]
fn compile(
&self,
source: &str,
source_name: Option<&CStr>,
raw_ep: &str,
full_stage: &str,
compile_flags: u32,
shader_data: &mut Option<ID3DBlob>,
error: &mut Option<ID3DBlob>,
) -> Result<windows_core::Result<()>, crate::DeviceError> {
unsafe {
let raw_ep = alloc::ffi::CString::new(raw_ep).unwrap();
let full_stage = alloc::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().cast())
.unwrap_or(core::ptr::null());
let shader_data: *mut Option<ID3DBlob> = shader_data;
let error: *mut Option<ID3DBlob> = error;
{
profiling::scope!("Fxc::D3DCompile");
Ok((self.d3dcompile_fn)(
source.as_ptr().cast(),
source.len(),
PCSTR(source_name),
core::ptr::null(),
core::ptr::null_mut(),
PCSTR(raw_ep.as_ptr().cast()),
PCSTR(full_stage.as_ptr().cast()),
compile_flags,
0,
shader_data.cast(),
error.cast(),
)
.ok())
}
}
}
}
fn compile_fxc(
device: &super::Device,
source: &str,
source_name: Option<&CStr>,
raw_ep: &str,
stage_bit: wgt::ShaderStages,
full_stage: &str,
fxc: &FxcLib,
) -> Result<super::CompiledShader, crate::PipelineError> {
profiling::scope!("compile_fxc");
let mut shader_data = None;
let mut compile_flags = Fxc::D3DCOMPILE_ENABLE_STRICTNESS;
if device
.shared
@ -29,32 +234,17 @@ pub(super) fn compile_fxc(
compile_flags |= Fxc::D3DCOMPILE_DEBUG | Fxc::D3DCOMPILE_SKIP_OPTIMIZATION;
}
let raw_ep = alloc::ffi::CString::new(raw_ep).unwrap();
let full_stage = alloc::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().cast())
.unwrap_or(core::ptr::null());
let mut shader_data = None;
let mut error = None;
let hr = unsafe {
profiling::scope!("Fxc::D3DCompile");
Fxc::D3DCompile(
// TODO: Update low-level bindings to accept a slice here
source.as_ptr().cast(),
source.len(),
PCSTR(source_name),
None,
None,
PCSTR(raw_ep.as_ptr().cast()),
PCSTR(full_stage.as_ptr().cast()),
compile_flags,
0,
&mut shader_data,
Some(&mut error),
)
};
let hr = fxc.compile(
source,
source_name,
raw_ep,
full_stage,
compile_flags,
&mut shader_data,
&mut error,
)?;
match hr {
Ok(()) => {
@ -132,64 +322,6 @@ unsafe fn dxc_create_instance<T: DxcObj>(
result__.ok_or(crate::DeviceError::Unexpected)
}
pub(super) struct DxcContainer {
pub(super) max_shader_model: wgt::DxcShaderModel,
compiler: Dxc::IDxcCompiler3,
// Has to be held onto for the lifetime of the device otherwise shaders will fail to compile.
// Only needed when using dynamic linking.
_dxc: Option<DxcLib>,
}
#[derive(Debug, Error)]
pub(super) enum GetDynamicDXCContainerError {
#[error(transparent)]
Device(#[from] crate::DeviceError),
#[error("Failed to load {0}: {1}")]
FailedToLoad(&'static str, libloading::Error),
}
pub(super) fn get_dynamic_dxc_container(
dxc_path: PathBuf,
max_shader_model: wgt::DxcShaderModel,
) -> Result<DxcContainer, GetDynamicDXCContainerError> {
let dxc = DxcLib::new_dynamic(dxc_path)
.map_err(|e| GetDynamicDXCContainerError::FailedToLoad("dxcompiler.dll", e))?;
let compiler = dxc.create_instance::<Dxc::IDxcCompiler3>()?;
Ok(DxcContainer {
max_shader_model,
compiler,
_dxc: Some(dxc),
})
}
/// Creates a [`DxcContainer`] that delegates to the statically-linked version of DXC.
pub(super) fn get_static_dxc_container() -> Result<DxcContainer, crate::DeviceError> {
#[cfg(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,
))
})?;
Ok(DxcContainer {
max_shader_model: wgt::DxcShaderModel::V6_7,
compiler,
_dxc: None,
})
}
}
#[cfg(not(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 {
@ -225,14 +357,14 @@ fn as_err_str(blob: &Dxc::IDxcBlobUtf8) -> Result<&str, crate::DeviceError> {
.map_err(|_| crate::DeviceError::Unexpected)
}
pub(super) fn compile_dxc(
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,
compiler: &Dxc::IDxcCompiler3,
) -> Result<crate::dx12::CompiledShader, crate::PipelineError> {
profiling::scope!("compile_dxc");
@ -279,12 +411,9 @@ pub(super) fn compile_dxc(
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")?;
let compile_res: Dxc::IDxcResult =
unsafe { compiler.Compile(&buffer, Some(&compile_args), None) }
.into_device_result("Compile")?;
drop(compile_args);
drop(source_name);