diff --git a/wgpu-hal/src/auxil/dxgi/conv.rs b/wgpu-hal/src/auxil/dxgi/conv.rs index a08d2dd5a53..dfcb95b0fc6 100644 --- a/wgpu-hal/src/auxil/dxgi/conv.rs +++ b/wgpu-hal/src/auxil/dxgi/conv.rs @@ -1,4 +1,4 @@ -use winapi::shared::{dxgiformat}; +use winapi::shared::dxgiformat; pub fn map_texture_format(format: wgt::TextureFormat) -> dxgiformat::DXGI_FORMAT { use wgt::TextureFormat as Tf; diff --git a/wgpu-hal/src/auxil/dxgi/exception.rs b/wgpu-hal/src/auxil/dxgi/exception.rs new file mode 100644 index 00000000000..31d5e6933a8 --- /dev/null +++ b/wgpu-hal/src/auxil/dxgi/exception.rs @@ -0,0 +1,98 @@ +use std::{borrow::Cow, slice}; + +use parking_lot::{lock_api::RawMutex, Mutex}; +use winapi::{ + um::{errhandlingapi, winnt}, + vc::excpt, +}; + +// This is a mutex as opposed to an atomic as we need to completely +// lock everyone out until we have registered or unregistered the +// exception handler, otherwise really nasty races could happen. +// +// By routing all the registration through these functions we can guarentee +// there is either 1 or 0 exception handlers registered, not multiple. +static EXCEPTION_HANLDER_COUNT: Mutex = Mutex::const_new(parking_lot::RawMutex::INIT, 0); + +pub fn register_exception_handler() { + let mut count_guard = EXCEPTION_HANLDER_COUNT.lock(); + if *count_guard == 0 { + unsafe { + errhandlingapi::AddVectoredExceptionHandler(0, Some(output_debug_string_handler)) + }; + } + *count_guard += 1; +} + +pub fn unregister_exception_handler() { + let mut count_guard = EXCEPTION_HANLDER_COUNT.lock(); + if *count_guard == 1 { + unsafe { + errhandlingapi::RemoveVectoredExceptionHandler(output_debug_string_handler as *mut _) + }; + } + *count_guard -= 1; +} + +const MESSAGE_PREFIXES: &[(&str, log::Level)] = &[ + ("CORRUPTION", log::Level::Error), + ("ERROR", log::Level::Error), + ("WARNING", log::Level::Warn), + ("INFO", log::Level::Info), + ("MESSAGE", log::Level::Debug), +]; + +unsafe extern "system" fn output_debug_string_handler( + exception_info: *mut winnt::EXCEPTION_POINTERS, +) -> i32 { + // See https://stackoverflow.com/a/41480827 + let record = &*(*exception_info).ExceptionRecord; + if record.NumberParameters != 2 { + return excpt::EXCEPTION_CONTINUE_SEARCH; + } + let message = match record.ExceptionCode { + winnt::DBG_PRINTEXCEPTION_C => String::from_utf8_lossy(slice::from_raw_parts( + record.ExceptionInformation[1] as *const u8, + record.ExceptionInformation[0], + )), + winnt::DBG_PRINTEXCEPTION_WIDE_C => { + Cow::Owned(String::from_utf16_lossy(slice::from_raw_parts( + record.ExceptionInformation[1] as *const u16, + record.ExceptionInformation[0], + ))) + } + _ => return excpt::EXCEPTION_CONTINUE_SEARCH, + }; + + let message = match message.strip_prefix("D3D12 ") { + Some(msg) => msg + .trim_end_matches("\n\0") + .trim_end_matches("[ STATE_CREATION WARNING #0: UNKNOWN]"), + None => return excpt::EXCEPTION_CONTINUE_SEARCH, + }; + + let (message, level) = match MESSAGE_PREFIXES + .iter() + .find(|&&(prefix, _)| message.starts_with(prefix)) + { + Some(&(prefix, level)) => (&message[prefix.len() + 2..], level), + None => (message, log::Level::Debug), + }; + + if level == log::Level::Warn && message.contains("#82") { + // This is are useless spammy warnings (#820, #821): + // "The application did not pass any clear value to resource creation" + return excpt::EXCEPTION_CONTINUE_SEARCH; + } + + let _ = std::panic::catch_unwind(|| { + log::log!(level, "{}", message); + }); + + if cfg!(debug_assertions) && level == log::Level::Error { + // Set canary and continue + crate::VALIDATION_CANARY.set(); + } + + excpt::EXCEPTION_CONTINUE_EXECUTION +} diff --git a/wgpu-hal/src/auxil/dxgi/factory.rs b/wgpu-hal/src/auxil/dxgi/factory.rs new file mode 100644 index 00000000000..74a4208a8d6 --- /dev/null +++ b/wgpu-hal/src/auxil/dxgi/factory.rs @@ -0,0 +1,117 @@ +use winapi::shared::dxgi1_2; + +use super::result::HResult as _; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum DxgiFactoryType { + Factory1, + Factory2, + Factory4, +} + +pub enum DxgiFactory { + Factory1(native::Factory1), + Factory2(native::Factory2), + Factory4(native::Factory4), +} + +impl DxgiFactory { + #[track_caller] + pub fn unwrap_factory4(self) -> native::Factory4 { + match self { + DxgiFactory::Factory1(_) => panic!("unwrapping a factory4, got a factory1"), + DxgiFactory::Factory2(_) => panic!("unwrapping a factory4, got a factory2"), + DxgiFactory::Factory4(f) => f, + } + } +} + +/// Tries to create a IDXGIFactory4, then a IDXGIFactory2, then a IDXGIFactory1, +/// returning the one that succeeds, or if the required_factory_type fails to be +/// created. +pub fn create_factory( + required_factory_type: DxgiFactoryType, + instance_flags: crate::InstanceFlags, +) -> Result<(native::DxgiLib, DxgiFactory), crate::InstanceError> { + let lib_dxgi = native::DxgiLib::new().map_err(|_| crate::InstanceError)?; + + let mut factory_flags = native::FactoryCreationFlags::empty(); + + if instance_flags.contains(crate::InstanceFlags::VALIDATION) { + // The `DXGI_CREATE_FACTORY_DEBUG` flag is only allowed to be passed to + // `CreateDXGIFactory2` if the debug interface is actually available. So + // we check for whether it exists first. + match lib_dxgi.get_debug_interface1() { + Ok(pair) => match pair.into_result() { + Ok(debug_controller) => { + unsafe { debug_controller.destroy() }; + factory_flags |= native::FactoryCreationFlags::DEBUG; + } + Err(err) => { + log::warn!("Unable to enable DXGI debug interface: {}", err); + } + }, + Err(err) => { + log::warn!("Debug interface function for DXGI not found: {:?}", err); + } + } + + // Intercept `OutputDebugString` calls + super::exception::register_exception_handler(); + } + + // Try to create IDXGIFactory4 + match lib_dxgi.create_factory2(factory_flags) { + Ok(pair) => match pair.into_result() { + Ok(factory) => return Ok((lib_dxgi, DxgiFactory::Factory4(factory))), + // We hard error here as we _should have_ been able to make a factory4 but couldn't. + Err(err) => { + log::error!("Failed to create IDXGIFactory4: {}", err); + return Err(crate::InstanceError); + } + }, + // If we require factory4, hard error. + Err(err) if required_factory_type == DxgiFactoryType::Factory4 => { + log::error!("IDXGIFactory1 creation function not found: {:?}", err); + return Err(crate::InstanceError); + } + // If we don't print it to info as all win7 will hit this case. + Err(err) => { + log::info!("IDXGIFactory1 creation function not found: {:?}", err); + } + }; + + // Try to create IDXGIFactory1 + let factory1 = match lib_dxgi.create_factory1() { + Ok(pair) => match pair.into_result() { + Ok(factory) => factory, + Err(err) => { + log::error!("Failed to create IDXGIFactory1: {}", err); + return Err(crate::InstanceError); + } + }, + // We always require at least factory1, so hard error + Err(err) => { + log::error!("IDXGIFactory1 creation function not found: {:?}", err); + return Err(crate::InstanceError); + } + }; + + // Try to cast the IDXGIFactory1 into IDXGIFactory2 + let factory2 = unsafe { factory1.cast::().into_result() }; + match factory2 { + Ok(factory2) => return Ok((lib_dxgi, DxgiFactory::Factory2(factory2))), + // If we require factory2, hard error. + Err(err) if required_factory_type == DxgiFactoryType::Factory2 => { + log::warn!("Failed to cast IDXGIFactory1 to IDXGIFactory2: {:?}", err); + return Err(crate::InstanceError); + } + // If we don't print it to info. + Err(err) => { + log::info!("Failed to cast IDXGIFactory1 to IDXGIFactory2: {:?}", err); + } + } + + // We tried to create 4 and 2, but only succeeded with 1. + Ok((lib_dxgi, DxgiFactory::Factory1(factory1))) +} diff --git a/wgpu-hal/src/auxil/dxgi/mod.rs b/wgpu-hal/src/auxil/dxgi/mod.rs index e906693400c..09a2b31bf3f 100644 --- a/wgpu-hal/src/auxil/dxgi/mod.rs +++ b/wgpu-hal/src/auxil/dxgi/mod.rs @@ -1 +1,4 @@ pub mod conv; +pub mod exception; +pub mod factory; +pub mod result; diff --git a/wgpu-hal/src/auxil/dxgi/result.rs b/wgpu-hal/src/auxil/dxgi/result.rs new file mode 100644 index 00000000000..db013d2dec4 --- /dev/null +++ b/wgpu-hal/src/auxil/dxgi/result.rs @@ -0,0 +1,42 @@ +use std::borrow::Cow; + +use winapi::shared::winerror; + +pub(crate) trait HResult { + fn into_result(self) -> Result>; + fn into_device_result(self, description: &str) -> Result; +} +impl HResult<()> for i32 { + fn into_result(self) -> Result<(), Cow<'static, str>> { + if self >= 0 { + return Ok(()); + } + let description = match self { + winerror::E_UNEXPECTED => "unexpected", + winerror::E_NOTIMPL => "not implemented", + winerror::E_OUTOFMEMORY => "out of memory", + winerror::E_INVALIDARG => "invalid argument", + _ => return Err(Cow::Owned(format!("0x{:X}", self as u32))), + }; + Err(Cow::Borrowed(description)) + } + fn into_device_result(self, description: &str) -> Result<(), crate::DeviceError> { + self.into_result().map_err(|err| { + log::error!("{} failed: {}", description, err); + if self == winerror::E_OUTOFMEMORY { + crate::DeviceError::OutOfMemory + } else { + crate::DeviceError::Lost + } + }) + } +} + +impl HResult for (T, i32) { + fn into_result(self) -> Result> { + self.1.into_result().map(|()| self.0) + } + fn into_device_result(self, description: &str) -> Result { + self.1.into_device_result(description).map(|()| self.0) + } +} diff --git a/wgpu-hal/src/dx11/instance.rs b/wgpu-hal/src/dx11/instance.rs index 174bf7575d7..d1fb58fdeaa 100644 --- a/wgpu-hal/src/dx11/instance.rs +++ b/wgpu-hal/src/dx11/instance.rs @@ -1,6 +1,13 @@ +use crate::auxil; + impl crate::Instance for super::Instance { unsafe fn init(desc: &crate::InstanceDescriptor) -> Result { - todo!() + let (lib_dxgi, factory) = auxil::dxgi::factory::create_factory( + auxil::dxgi::factory::DxgiFactoryType::Factory1, + desc.flags, + )?; + + Ok(super::Instance { lib_dxgi, factory }) } unsafe fn create_surface( diff --git a/wgpu-hal/src/dx11/mod.rs b/wgpu-hal/src/dx11/mod.rs index 41c488b8ff2..c068ad91eaa 100644 --- a/wgpu-hal/src/dx11/mod.rs +++ b/wgpu-hal/src/dx11/mod.rs @@ -1,5 +1,8 @@ +#![allow(dead_code)] #![allow(unused_variables)] +use crate::auxil; + mod adapter; mod command; mod device; @@ -34,7 +37,13 @@ impl crate::Api for Api { type ComputePipeline = ComputePipeline; } -pub struct Instance {} +pub struct Instance { + lib_dxgi: native::DxgiLib, + factory: auxil::dxgi::factory::DxgiFactory, +} + +unsafe impl Send for Instance {} +unsafe impl Sync for Instance {} pub struct Surface {} diff --git a/wgpu-hal/src/dx12/adapter.rs b/wgpu-hal/src/dx12/adapter.rs index a39586d1069..b28e52f86ef 100644 --- a/wgpu-hal/src/dx12/adapter.rs +++ b/wgpu-hal/src/dx12/adapter.rs @@ -1,6 +1,6 @@ use crate::{ - auxil, - dx12::{HResult as _, SurfaceTarget}, + auxil::{self, dxgi::result::HResult as _}, + dx12::SurfaceTarget, }; use std::{mem, sync::Arc, thread}; use winapi::{ diff --git a/wgpu-hal/src/dx12/command.rs b/wgpu-hal/src/dx12/command.rs index 5ed065da4a2..95546d213ab 100644 --- a/wgpu-hal/src/dx12/command.rs +++ b/wgpu-hal/src/dx12/command.rs @@ -1,6 +1,6 @@ -use crate::auxil; +use crate::auxil::{self, dxgi::result::HResult as _}; -use super::{conv, HResult as _}; +use super::conv; use std::{mem, ops::Range, ptr}; use winapi::um::d3d12; diff --git a/wgpu-hal/src/dx12/descriptor.rs b/wgpu-hal/src/dx12/descriptor.rs index 0a8bac41442..67c8eca4fee 100644 --- a/wgpu-hal/src/dx12/descriptor.rs +++ b/wgpu-hal/src/dx12/descriptor.rs @@ -1,4 +1,4 @@ -use super::HResult as _; +use crate::auxil::dxgi::result::HResult as _; use bit_set::BitSet; use parking_lot::Mutex; use range_alloc::RangeAllocator; diff --git a/wgpu-hal/src/dx12/device.rs b/wgpu-hal/src/dx12/device.rs index 88f8e716b67..778bd2e51a4 100644 --- a/wgpu-hal/src/dx12/device.rs +++ b/wgpu-hal/src/dx12/device.rs @@ -1,6 +1,9 @@ -use crate::{auxil, FormatAspects}; +use crate::{ + auxil::{self, dxgi::result::HResult as _}, + FormatAspects, +}; -use super::{conv, descriptor, view, HResult as _}; +use super::{conv, descriptor, view}; use parking_lot::Mutex; use std::{ffi, mem, num::NonZeroU32, ptr, slice, sync::Arc}; use winapi::{ diff --git a/wgpu-hal/src/dx12/instance.rs b/wgpu-hal/src/dx12/instance.rs index b8fa59cbc99..77a41a9236c 100644 --- a/wgpu-hal/src/dx12/instance.rs +++ b/wgpu-hal/src/dx12/instance.rs @@ -1,80 +1,16 @@ -use super::{HResult as _, SurfaceTarget}; -use std::{borrow::Cow, slice, sync::Arc}; +use super::SurfaceTarget; +use crate::auxil::{self, dxgi::result::HResult as _}; +use std::sync::Arc; use winapi::{ shared::{dxgi, dxgi1_2, dxgi1_6, winerror}, - um::{errhandlingapi, winnt}, - vc::excpt, Interface, }; -const MESSAGE_PREFIXES: &[(&str, log::Level)] = &[ - ("CORRUPTION", log::Level::Error), - ("ERROR", log::Level::Error), - ("WARNING", log::Level::Warn), - ("INFO", log::Level::Info), - ("MESSAGE", log::Level::Debug), -]; - -unsafe extern "system" fn output_debug_string_handler( - exception_info: *mut winnt::EXCEPTION_POINTERS, -) -> i32 { - // See https://stackoverflow.com/a/41480827 - let record = &*(*exception_info).ExceptionRecord; - if record.NumberParameters != 2 { - return excpt::EXCEPTION_CONTINUE_SEARCH; - } - let message = match record.ExceptionCode { - winnt::DBG_PRINTEXCEPTION_C => String::from_utf8_lossy(slice::from_raw_parts( - record.ExceptionInformation[1] as *const u8, - record.ExceptionInformation[0], - )), - winnt::DBG_PRINTEXCEPTION_WIDE_C => { - Cow::Owned(String::from_utf16_lossy(slice::from_raw_parts( - record.ExceptionInformation[1] as *const u16, - record.ExceptionInformation[0], - ))) - } - _ => return excpt::EXCEPTION_CONTINUE_SEARCH, - }; - - let message = match message.strip_prefix("D3D12 ") { - Some(msg) => msg - .trim_end_matches("\n\0") - .trim_end_matches("[ STATE_CREATION WARNING #0: UNKNOWN]"), - None => return excpt::EXCEPTION_CONTINUE_SEARCH, - }; - - let (message, level) = match MESSAGE_PREFIXES - .iter() - .find(|&&(prefix, _)| message.starts_with(prefix)) - { - Some(&(prefix, level)) => (&message[prefix.len() + 2..], level), - None => (message, log::Level::Debug), - }; - - if level == log::Level::Warn && message.contains("#82") { - // This is are useless spammy warnings (#820, #821): - // "The application did not pass any clear value to resource creation" - return excpt::EXCEPTION_CONTINUE_SEARCH; - } - - let _ = std::panic::catch_unwind(|| { - log::log!(level, "{}", message); - }); - - if cfg!(debug_assertions) && level == log::Level::Error { - // Set canary and continue - crate::VALIDATION_CANARY.set(); - } - - excpt::EXCEPTION_CONTINUE_EXECUTION -} - impl Drop for super::Instance { fn drop(&mut self) { unsafe { self.factory.destroy(); - errhandlingapi::RemoveVectoredExceptionHandler(output_debug_string_handler as *mut _); + crate::auxil::dxgi::exception::unregister_exception_handler(); } } } @@ -83,9 +19,6 @@ impl crate::Instance for super::Instance { unsafe fn init(desc: &crate::InstanceDescriptor) -> Result { let lib_main = native::D3D12Lib::new().map_err(|_| crate::InstanceError)?; - let lib_dxgi = native::DxgiLib::new().map_err(|_| crate::InstanceError)?; - let mut factory_flags = native::FactoryCreationFlags::empty(); - if desc.flags.contains(crate::InstanceFlags::VALIDATION) { // Enable debug layer match lib_main.get_debug_interface() { @@ -102,46 +35,17 @@ impl crate::Instance for super::Instance { log::warn!("Debug interface function for D3D12 not found: {:?}", err); } } - - // The `DXGI_CREATE_FACTORY_DEBUG` flag is only allowed to be passed to - // `CreateDXGIFactory2` if the debug interface is actually available. So - // we check for whether it exists first. - match lib_dxgi.get_debug_interface1() { - Ok(pair) => match pair.into_result() { - Ok(debug_controller) => { - debug_controller.destroy(); - factory_flags |= native::FactoryCreationFlags::DEBUG; - } - Err(err) => { - log::warn!("Unable to enable DXGI debug interface: {}", err); - } - }, - Err(err) => { - log::warn!("Debug interface function for DXGI not found: {:?}", err); - } - } - - // Intercept `OutputDebugString` calls - errhandlingapi::AddVectoredExceptionHandler(0, Some(output_debug_string_handler)); } - // Create DXGI factory - let factory = match lib_dxgi.create_factory2(factory_flags) { - Ok(pair) => match pair.into_result() { - Ok(factory) => factory, - Err(err) => { - log::warn!("Failed to create DXGI factory: {}", err); - return Err(crate::InstanceError); - } - }, - Err(err) => { - log::warn!("Factory creation function for DXGI not found: {:?}", err); - return Err(crate::InstanceError); - } - }; + // Create DXGIFactory4 + let (lib_dxgi, factory) = auxil::dxgi::factory::create_factory( + auxil::dxgi::factory::DxgiFactoryType::Factory4, + desc.flags, + )?; Ok(Self { - factory, + // The call to create_factory will only succeed if we get a factory4, so this is safe. + factory: factory.unwrap_factory4(), library: Arc::new(lib_main), _lib_dxgi: lib_dxgi, flags: desc.flags, diff --git a/wgpu-hal/src/dx12/mod.rs b/wgpu-hal/src/dx12/mod.rs index 06da9caf77e..cbf6d4d910f 100644 --- a/wgpu-hal/src/dx12/mod.rs +++ b/wgpu-hal/src/dx12/mod.rs @@ -41,11 +41,11 @@ mod device; mod instance; mod view; -use crate::auxil; +use crate::auxil::{self, dxgi::result::HResult as _}; use arrayvec::ArrayVec; use parking_lot::Mutex; -use std::{borrow::Cow, ffi, mem, num::NonZeroU32, sync::Arc}; +use std::{ffi, mem, num::NonZeroU32, sync::Arc}; use winapi::{ shared::{dxgi, dxgi1_2, dxgi1_4, dxgitype, windef, winerror}, um::{d3d12, dcomp, synchapi, winbase, winnt}, @@ -81,45 +81,6 @@ impl crate::Api for Api { type ComputePipeline = ComputePipeline; } -trait HResult { - fn into_result(self) -> Result>; - fn into_device_result(self, description: &str) -> Result; -} -impl HResult<()> for i32 { - fn into_result(self) -> Result<(), Cow<'static, str>> { - if self >= 0 { - return Ok(()); - } - let description = match self { - winerror::E_UNEXPECTED => "unexpected", - winerror::E_NOTIMPL => "not implemented", - winerror::E_OUTOFMEMORY => "out of memory", - winerror::E_INVALIDARG => "invalid argument", - _ => return Err(Cow::Owned(format!("0x{:X}", self as u32))), - }; - Err(Cow::Borrowed(description)) - } - fn into_device_result(self, description: &str) -> Result<(), crate::DeviceError> { - self.into_result().map_err(|err| { - log::error!("{} failed: {}", description, err); - if self == winerror::E_OUTOFMEMORY { - crate::DeviceError::OutOfMemory - } else { - crate::DeviceError::Lost - } - }) - } -} - -impl HResult for (T, i32) { - fn into_result(self) -> Result> { - self.1.into_result().map(|()| self.0) - } - fn into_device_result(self, description: &str) -> Result { - self.1.into_device_result(description).map(|()| self.0) - } -} - // Limited by D3D12's root signature size of 64. Each element takes 1 or 2 entries. const MAX_ROOT_ELEMENTS: usize = 64; const ZERO_BUFFER_SIZE: wgt::BufferAddress = 256 << 10;