From aee8cd165084ba708fbc98d513b5bf274107b96e Mon Sep 17 00:00:00 2001 From: Andy Leiserson Date: Mon, 15 Sep 2025 11:48:28 -0700 Subject: [PATCH] Lazy-load the dcomp library (#8216) --- Cargo.lock | 1 + wgpu-hal/Cargo.toml | 2 + wgpu-hal/src/dx12/adapter.rs | 5 ++- wgpu-hal/src/dx12/dcomp.rs | 73 +++++++++++++++++++++++++++++++---- wgpu-hal/src/dx12/device.rs | 4 +- wgpu-hal/src/dx12/instance.rs | 4 +- wgpu-hal/src/dx12/mod.rs | 7 +++- 7 files changed, 84 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 57ee57c54..42da5cf0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5126,6 +5126,7 @@ dependencies = [ "naga", "ndk-sys 0.6.0+11769913", "objc", + "once_cell", "ordered-float 5.0.0", "parking_lot", "portable-atomic", diff --git a/wgpu-hal/Cargo.toml b/wgpu-hal/Cargo.toml index 94dc0e07e..3b8c37c86 100644 --- a/wgpu-hal/Cargo.toml +++ b/wgpu-hal/Cargo.toml @@ -136,6 +136,7 @@ dx12 = [ "dep:libloading", "dep:log", "dep:ordered-float", + "dep:once_cell", "dep:parking_lot", "dep:profiling", "dep:range-alloc", @@ -258,6 +259,7 @@ windows-core = { workspace = true, optional = true } bit-set = { workspace = true, optional = true } range-alloc = { workspace = true, optional = true } gpu-allocator = { workspace = true, optional = true } +once_cell = { workspace = true, optional = true } # backend: GLES glutin_wgl_sys = { workspace = true, optional = true } diff --git a/wgpu-hal/src/dx12/adapter.rs b/wgpu-hal/src/dx12/adapter.rs index 7f96bbcba..6dce0ce97 100644 --- a/wgpu-hal/src/dx12/adapter.rs +++ b/wgpu-hal/src/dx12/adapter.rs @@ -17,7 +17,7 @@ use crate::{ self, dxgi::{factory::DxgiAdapter, result::HResult}, }, - dx12::{shader_compilation, SurfaceTarget}, + dx12::{dcomp::DCompLib, shader_compilation, SurfaceTarget}, }; impl Drop for super::Adapter { @@ -55,6 +55,7 @@ impl super::Adapter { pub(super) fn expose( adapter: DxgiAdapter, library: &Arc, + dcomp_lib: &Arc, instance_flags: wgt::InstanceFlags, memory_budget_thresholds: wgt::MemoryBudgetThresholds, compiler_container: Arc, @@ -559,6 +560,7 @@ impl super::Adapter { raw: adapter, device, library: Arc::clone(library), + dcomp_lib: Arc::clone(dcomp_lib), private_caps, presentation_timer, workarounds, @@ -726,6 +728,7 @@ impl crate::Adapter for super::Adapter { memory_hints, self.private_caps, &self.library, + &self.dcomp_lib, self.memory_budget_thresholds, self.compiler_container.clone(), self.options.clone(), diff --git a/wgpu-hal/src/dx12/dcomp.rs b/wgpu-hal/src/dx12/dcomp.rs index a6bc83352..e99849a6d 100644 --- a/wgpu-hal/src/dx12/dcomp.rs +++ b/wgpu-hal/src/dx12/dcomp.rs @@ -1,4 +1,65 @@ +use alloc::sync::Arc; +use core::{ffi, ptr}; + +use once_cell::sync::Lazy; use windows::Win32::{Foundation::HWND, Graphics::DirectComposition}; +use windows_core::Interface; + +use super::DynLib; + +// Lazy-loaded DirectComposition library +#[derive(Debug)] +pub(crate) struct DCompLib { + lib: Lazy>, +} + +impl DCompLib { + pub(crate) fn new() -> Self { + Self { + lib: Lazy::new(|| unsafe { + DynLib::new("dcomp.dll").map_err(|err| { + log::error!("Error loading dcomp.dll: {err}"); + crate::SurfaceError::Other("Error loading dcomp.dll") + }) + }), + } + } + + fn get_lib(&self) -> Result<&DynLib, crate::SurfaceError> { + match self.lib.as_ref() { + Ok(lib) => Ok(lib), + Err(err) => Err(err.clone()), + } + } + + pub(crate) fn create_device( + &self, + ) -> Result { + let lib = self.get_lib()?; + + // Calls windows::Win32::Graphics::DirectComposition::DCompositionCreateDevice2 on dcomp.dll + type Fun = extern "system" fn( + pdxdevice: *mut ffi::c_void, + riid: *const windows_core::GUID, + ppdcompdevice: *mut *mut ffi::c_void, + ) -> windows_core::HRESULT; + let func: libloading::Symbol = + unsafe { lib.get(c"DCompositionCreateDevice2".to_bytes()) }?; + + let mut res: Option = None; + + (func)( + ptr::null_mut(), + &DirectComposition::IDCompositionDevice::IID, + <*mut _>::cast(&mut res), + ) + .map(|| res.unwrap()) + .map_err(|err| { + log::error!("DirectComposition::DCompositionCreateDevice2 failed: {err}"); + crate::SurfaceError::Other("DirectComposition::DCompositionCreateDevice2") + }) + } +} #[derive(Default)] pub struct DCompState { @@ -10,10 +71,11 @@ impl DCompState { /// If the device is already initialized, it will return the existing state. pub unsafe fn get_or_init( &mut self, + lib: &Arc, hwnd: &HWND, ) -> Result<&mut InnerState, crate::SurfaceError> { if self.inner.is_none() { - self.inner = Some(unsafe { InnerState::init(hwnd) }?); + self.inner = Some(unsafe { InnerState::init(lib, hwnd) }?); } Ok(self.inner.as_mut().unwrap()) } @@ -28,14 +90,9 @@ pub struct InnerState { impl InnerState { /// Creates a DirectComposition device and a target for the given window handle. - pub unsafe fn init(hwnd: &HWND) -> Result { + pub unsafe fn init(lib: &Arc, hwnd: &HWND) -> Result { profiling::scope!("DCompState::init"); - let dcomp_device: DirectComposition::IDCompositionDevice = { - unsafe { DirectComposition::DCompositionCreateDevice2(None) }.map_err(|err| { - log::error!("DirectComposition::DCompositionCreateDevice failed: {err}"); - crate::SurfaceError::Other("DirectComposition::DCompositionCreateDevice") - })? - }; + let dcomp_device = lib.create_device()?; let target = unsafe { dcomp_device.CreateTargetForHwnd(*hwnd, false) }.map_err(|err| { log::error!("IDCompositionDevice::CreateTargetForHwnd failed: {err}"); diff --git a/wgpu-hal/src/dx12/device.rs b/wgpu-hal/src/dx12/device.rs index 34454c9f9..27628a19a 100644 --- a/wgpu-hal/src/dx12/device.rs +++ b/wgpu-hal/src/dx12/device.rs @@ -26,7 +26,7 @@ use crate::{ dxgi::{name::ObjectExt, result::HResult}, }, dx12::{ - borrow_optional_interface_temporarily, shader_compilation, suballocation, + borrow_optional_interface_temporarily, shader_compilation, suballocation, DCompLib, DynamicStorageBufferOffsets, Event, ShaderCacheKey, ShaderCacheValue, }, AccelerationStructureEntries, TlasInstance, @@ -46,6 +46,7 @@ impl super::Device { memory_hints: &wgt::MemoryHints, private_caps: super::PrivateCapabilities, library: &Arc, + dcomp_lib: &Arc, memory_budget_thresholds: wgt::MemoryBudgetThresholds, compiler_container: Arc, backend_options: wgt::Dx12BackendOptions, @@ -201,6 +202,7 @@ impl super::Device { )), options: backend_options, library: Arc::clone(library), + dcomp_lib: Arc::clone(dcomp_lib), #[cfg(feature = "renderdoc")] render_doc: Default::default(), null_rtv_handle, diff --git a/wgpu-hal/src/dx12/instance.rs b/wgpu-hal/src/dx12/instance.rs index 5f137836f..38d790bcb 100644 --- a/wgpu-hal/src/dx12/instance.rs +++ b/wgpu-hal/src/dx12/instance.rs @@ -12,7 +12,7 @@ use windows::{ use super::SurfaceTarget; use crate::{ auxil, - dx12::{shader_compilation::CompilerContainer, D3D12Lib}, + dx12::{shader_compilation::CompilerContainer, D3D12Lib, DCompLib}, }; impl crate::Instance for super::Instance { @@ -104,6 +104,7 @@ impl crate::Instance for super::Instance { factory, factory_media, library: Arc::new(lib_main), + dcomp_lib: Arc::new(DCompLib::new()), presentation_system: desc.backend_options.dx12.presentation_system, _lib_dxgi: lib_dxgi, supports_allow_tearing, @@ -158,6 +159,7 @@ impl crate::Instance for super::Instance { super::Adapter::expose( raw, &self.library, + &self.dcomp_lib, self.flags, self.memory_budget_thresholds, self.compiler_container.clone(), diff --git a/wgpu-hal/src/dx12/mod.rs b/wgpu-hal/src/dx12/mod.rs index a2570048b..b355582ab 100644 --- a/wgpu-hal/src/dx12/mod.rs +++ b/wgpu-hal/src/dx12/mod.rs @@ -101,6 +101,7 @@ use windows::{ }, }; +use self::dcomp::DCompLib; use crate::auxil::{ self, dxgi::{ @@ -460,6 +461,7 @@ pub struct Instance { factory: DxgiFactory, factory_media: Option, library: Arc, + dcomp_lib: Arc, supports_allow_tearing: bool, presentation_system: wgt::Dx12SwapchainKind, _lib_dxgi: DxgiLib, @@ -612,6 +614,7 @@ pub struct Adapter { raw: DxgiAdapter, device: Direct3D12::ID3D12Device, library: Arc, + dcomp_lib: Arc, private_caps: PrivateCapabilities, presentation_timer: auxil::dxgi::time::PresentationTimer, // Note: this isn't used right now, but we'll need it later. @@ -685,6 +688,7 @@ pub struct Device { srv_uav_pool: Mutex, // library library: Arc, + dcomp_lib: Arc, #[cfg(feature = "renderdoc")] render_doc: auxil::renderdoc::RenderDoc, null_rtv_handle: descriptor::Handle, @@ -1358,7 +1362,8 @@ impl crate::Surface for Surface { dcomp_state, } => { let mut dcomp_state = dcomp_state.lock(); - let dcomp_state = unsafe { dcomp_state.get_or_init(handle) }?; + let dcomp_state = + unsafe { dcomp_state.get_or_init(&device.dcomp_lib, handle) }?; // Set the new swap chain as the content for the backing visual // and commit the changes to the composition visual tree. {