From fec7140b85efda7db6edd3f4715794fc5d32090c Mon Sep 17 00:00:00 2001 From: teoxoy <28601907+teoxoy@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:59:58 +0100 Subject: [PATCH] [d3d12] add support for optional adapter telemetry --- deno_webgpu/lib.rs | 1 + player/src/bin/play.rs | 2 +- player/tests/player/main.rs | 2 +- wgpu-core/src/global.rs | 8 +- wgpu-core/src/instance.rs | 24 ++- wgpu-hal/examples/halmark/main.rs | 1 + wgpu-hal/examples/ray-traced-triangle/main.rs | 1 + wgpu-hal/src/dx12/adapter.rs | 177 +++++++++++++----- wgpu-hal/src/dx12/instance.rs | 2 + wgpu-hal/src/dx12/mod.rs | 27 +-- wgpu-hal/src/lib.rs | 7 + wgpu-hal/src/noop/mod.rs | 1 + wgpu/src/backend/wgpu_core.rs | 2 +- 13 files changed, 178 insertions(+), 77 deletions(-) diff --git a/deno_webgpu/lib.rs b/deno_webgpu/lib.rs index 2b6dfcfee4e..8c0f79cafd4 100644 --- a/deno_webgpu/lib.rs +++ b/deno_webgpu/lib.rs @@ -180,6 +180,7 @@ impl GPU { noop: wgpu_types::NoopBackendOptions::default(), }, }, + None, ))); state.borrow::() }; diff --git a/player/src/bin/play.rs b/player/src/bin/play.rs index 1497b96c7fd..e1af0975901 100644 --- a/player/src/bin/play.rs +++ b/player/src/bin/play.rs @@ -70,7 +70,7 @@ fn main() { .unwrap(); let instance_desc = wgt::InstanceDescriptor::from_env_or_default(); - let instance = wgc::instance::Instance::new("player", &instance_desc); + let instance = wgc::instance::Instance::new("player", &instance_desc, None); #[cfg(feature = "winit")] let surface = unsafe { diff --git a/player/tests/player/main.rs b/player/tests/player/main.rs index 11a5a3cbd88..cbbfbcb9cd4 100644 --- a/player/tests/player/main.rs +++ b/player/tests/player/main.rs @@ -182,7 +182,7 @@ impl Corpus { println!("\t\tTest '{test_path:?}'"); let instance_desc = wgt::InstanceDescriptor::from_env_or_default(); - let instance = wgc::instance::Instance::new("test", &instance_desc); + let instance = wgc::instance::Instance::new("test", &instance_desc, None); let adapter = match instance.request_adapter( &wgt::RequestAdapterOptions { power_preference: wgt::PowerPreference::None, diff --git a/wgpu-core/src/global.rs b/wgpu-core/src/global.rs index 150191d24f1..7aaf822efbd 100644 --- a/wgpu-core/src/global.rs +++ b/wgpu-core/src/global.rs @@ -31,10 +31,14 @@ pub struct Global { } impl Global { - pub fn new(name: &str, instance_desc: &wgt::InstanceDescriptor) -> Self { + pub fn new( + name: &str, + instance_desc: &wgt::InstanceDescriptor, + telemetry: Option, + ) -> Self { profiling::scope!("Global::new"); Self { - instance: Instance::new(name, instance_desc), + instance: Instance::new(name, instance_desc, telemetry), surfaces: Registry::new(), hub: Hub::new(), } diff --git a/wgpu-core/src/instance.rs b/wgpu-core/src/instance.rs index 4f5c831736c..7f3a8b391ac 100644 --- a/wgpu-core/src/instance.rs +++ b/wgpu-core/src/instance.rs @@ -92,7 +92,11 @@ pub struct Instance { } impl Instance { - pub fn new(name: &str, instance_desc: &wgt::InstanceDescriptor) -> Self { + pub fn new( + name: &str, + instance_desc: &wgt::InstanceDescriptor, + telemetry: Option, + ) -> Self { let mut this = Self { name: name.to_owned(), instance_per_backend: Vec::new(), @@ -102,21 +106,26 @@ impl Instance { }; #[cfg(vulkan)] - this.try_add_hal(hal::api::Vulkan, instance_desc); + this.try_add_hal(hal::api::Vulkan, instance_desc, telemetry); #[cfg(metal)] - this.try_add_hal(hal::api::Metal, instance_desc); + this.try_add_hal(hal::api::Metal, instance_desc, telemetry); #[cfg(dx12)] - this.try_add_hal(hal::api::Dx12, instance_desc); + this.try_add_hal(hal::api::Dx12, instance_desc, telemetry); #[cfg(gles)] - this.try_add_hal(hal::api::Gles, instance_desc); + this.try_add_hal(hal::api::Gles, instance_desc, telemetry); #[cfg(feature = "noop")] - this.try_add_hal(hal::api::Noop, instance_desc); + this.try_add_hal(hal::api::Noop, instance_desc, telemetry); this } /// Helper for `Instance::new()`; attempts to add a single `wgpu-hal` backend to this instance. - fn try_add_hal(&mut self, _: A, instance_desc: &wgt::InstanceDescriptor) { + fn try_add_hal( + &mut self, + _: A, + instance_desc: &wgt::InstanceDescriptor, + telemetry: Option, + ) { // Whether or not the backend was requested, and whether or not it succeeds, // note that we *could* try it. self.supported_backends |= A::VARIANT.into(); @@ -131,6 +140,7 @@ impl Instance { flags: self.flags, memory_budget_thresholds: instance_desc.memory_budget_thresholds, backend_options: instance_desc.backend_options.clone(), + telemetry, }; use hal::Instance as _; diff --git a/wgpu-hal/examples/halmark/main.rs b/wgpu-hal/examples/halmark/main.rs index f5391012ca3..0c9ba8c3ee4 100644 --- a/wgpu-hal/examples/halmark/main.rs +++ b/wgpu-hal/examples/halmark/main.rs @@ -99,6 +99,7 @@ impl Example { memory_budget_thresholds: wgpu_types::MemoryBudgetThresholds::default(), // Can't rely on having DXC available, so use FXC instead backend_options: wgpu_types::BackendOptions::default(), + telemetry: None, }; let instance = unsafe { A::Instance::init(&instance_desc)? }; let surface = { diff --git a/wgpu-hal/examples/ray-traced-triangle/main.rs b/wgpu-hal/examples/ray-traced-triangle/main.rs index 69c4ff2cf4e..cb1fa545ee4 100644 --- a/wgpu-hal/examples/ray-traced-triangle/main.rs +++ b/wgpu-hal/examples/ray-traced-triangle/main.rs @@ -246,6 +246,7 @@ impl Example { }, ..Default::default() }, + telemetry: None, }; let instance = unsafe { A::Instance::init(&instance_desc)? }; let surface = { diff --git a/wgpu-hal/src/dx12/adapter.rs b/wgpu-hal/src/dx12/adapter.rs index d8dd82f19bb..89f93f63093 100644 --- a/wgpu-hal/src/dx12/adapter.rs +++ b/wgpu-hal/src/dx12/adapter.rs @@ -58,6 +58,7 @@ impl super::Adapter { &self.raw } + #[allow(clippy::too_many_arguments)] pub(super) fn expose( adapter: DxgiAdapter, library: &Arc, @@ -66,19 +67,62 @@ impl super::Adapter { memory_budget_thresholds: wgt::MemoryBudgetThresholds, compiler_container: Arc, backend_options: wgt::Dx12BackendOptions, + telemetry: Option, ) -> Option> { + let desc = unsafe { adapter.GetDesc2() }.unwrap(); + let driver_version = + unsafe { adapter.CheckInterfaceSupport(&Dxgi::IDXGIDevice::IID) }.unwrap() as u64; + let driver_version = [ + (driver_version >> 48) as u16, + (driver_version >> 32) as u16, + (driver_version >> 16) as u16, + driver_version as u16, + ]; + + let get_telemetry_key = || { + // The key must be under 111 bytes: + // - backend identifier 'D' = 1 byte + // - 4 * u32's formatted as hex = 4 * 8 = 32 bytes + // - 4 * u16's formatted as dec = 4 * 5 = 20 bytes + // - separators = 8 bytes + // total = 61 bytes + format!( + "D:{:X}:{:X}:{:X}:{:X}:{}.{}.{}.{}", + desc.VendorId, + desc.DeviceId, + desc.SubSysId, + desc.Revision, + driver_version[0], + driver_version[1], + driver_version[2], + driver_version[3], + ) + }; + // Create the device so that we can get the capabilities. - let device = { + let res = { profiling::scope!("ID3D12Device::create_device"); - library - .create_device(&adapter, Direct3D::D3D_FEATURE_LEVEL_11_0) - .ok()?? + library.create_device(&adapter, Direct3D::D3D_FEATURE_LEVEL_11_0) }; + if let Some(telemetry) = telemetry { + if let Err(ref err) = res { + let category = match err { + crate::dx12::CreateDeviceError::GetProcAddress => "NONE:GetProcAddress", + crate::dx12::CreateDeviceError::D3D12CreateDevice(hresult) => { + &format!("NONE:D3D12CreateDevice:{:X}", hresult.0) + } + crate::dx12::CreateDeviceError::RetDeviceIsNull => "NONE:RetDeviceIsNull", + }; + (telemetry.expose_adapter)(&get_telemetry_key(), category); + } + } + let device = res.ok()?; profiling::scope!("feature queries"); // Detect the highest supported feature level. let d3d_feature_level = [ + Direct3D::D3D_FEATURE_LEVEL_12_2, Direct3D::D3D_FEATURE_LEVEL_12_1, Direct3D::D3D_FEATURE_LEVEL_12_0, Direct3D::D3D_FEATURE_LEVEL_11_1, @@ -99,10 +143,6 @@ impl super::Adapter { .unwrap(); let max_feature_level = device_levels.MaxSupportedFeatureLevel; - // We have found a possible adapter. - // Acquire the device information. - let desc = unsafe { adapter.GetDesc2() }.unwrap(); - let device_name = auxil::dxgi::conv::map_adapter_name(desc.Description); let mut features_architecture = Direct3D12::D3D12_FEATURE_DATA_ARCHITECTURE::default(); @@ -116,14 +156,6 @@ impl super::Adapter { } .unwrap(); - let driver_version = unsafe { adapter.CheckInterfaceSupport(&Dxgi::IDXGIDevice::IID) } - .ok() - .map(|i| { - const MASK: i64 = 0xFFFF; - (i >> 48, (i >> 32) & MASK, (i >> 16) & MASK, i & MASK) - }) - .unwrap_or((0, 0, 0, 0)); - let mut workarounds = super::Workarounds::default(); let is_warp = device_name.contains("Microsoft Basic Render Driver"); @@ -132,7 +164,7 @@ impl super::Adapter { // use a version that starts with 10.x.x.x. Versions that ship from Nuget use 1.0.x.x. // // As far as we know, this is only an issue on the Nuget versions. - if is_warp && driver_version >= (1, 0, 13, 0) && driver_version.0 < 10 { + if is_warp && driver_version >= [1, 0, 13, 0] && driver_version[0] < 10 { workarounds.avoid_shader_debug_info = true; } @@ -153,7 +185,7 @@ impl super::Adapter { device_pci_bus_id: get_adapter_pci_info(desc.VendorId, desc.DeviceId), driver: format!( "{}.{}.{}.{}", - driver_version.0, driver_version.1, driver_version.2, driver_version.3 + driver_version[0], driver_version[1], driver_version[2], driver_version[3] ), driver_info: String::new(), transient_saves_memory: false, @@ -170,6 +202,10 @@ impl super::Adapter { .unwrap(); if options.ResourceBindingTier.0 < Direct3D12::D3D12_RESOURCE_BINDING_TIER_2.0 { + if let Some(telemetry) = telemetry { + let category = "NONE:REQ_RBT2"; + (telemetry.expose_adapter)(&get_telemetry_key(), category); + } // We require Tier 2 or higher for the ability to make samplers bindless in all cases. return None; } @@ -263,6 +299,40 @@ impl super::Adapter { } }; + let mut all_shader_models = [ + Direct3D12::D3D_SHADER_MODEL_6_9, + Direct3D12::D3D_SHADER_MODEL_6_8, + Direct3D12::D3D_SHADER_MODEL_6_7, + Direct3D12::D3D_SHADER_MODEL_6_6, + Direct3D12::D3D_SHADER_MODEL_6_5, + Direct3D12::D3D_SHADER_MODEL_6_4, + Direct3D12::D3D_SHADER_MODEL_6_3, + Direct3D12::D3D_SHADER_MODEL_6_2, + Direct3D12::D3D_SHADER_MODEL_6_1, + Direct3D12::D3D_SHADER_MODEL_6_0, + ] + .iter(); + let highest_shader_model = loop { + if let Some(&sm) = all_shader_models.next() { + let mut sm = Direct3D12::D3D12_FEATURE_DATA_SHADER_MODEL { + HighestShaderModel: sm, + }; + if unsafe { + device.CheckFeatureSupport( + Direct3D12::D3D12_FEATURE_SHADER_MODEL, + <*mut _>::cast(&mut sm), + size_of_val(&sm) as u32, + ) + } + .is_ok() + { + break sm.HighestShaderModel; + } + } else { + break Direct3D12::D3D_SHADER_MODEL_5_1; + } + }; + 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, @@ -275,42 +345,18 @@ impl super::Adapter { wgt::DxcShaderModel::V6_7 => Direct3D12::D3D_SHADER_MODEL_6_7, }; - let mut versions = [ - Direct3D12::D3D_SHADER_MODEL_6_7, - Direct3D12::D3D_SHADER_MODEL_6_6, - Direct3D12::D3D_SHADER_MODEL_6_5, - Direct3D12::D3D_SHADER_MODEL_6_4, - Direct3D12::D3D_SHADER_MODEL_6_3, - Direct3D12::D3D_SHADER_MODEL_6_2, - Direct3D12::D3D_SHADER_MODEL_6_1, - Direct3D12::D3D_SHADER_MODEL_6_0, - ] - .iter() - .filter(|shader_model| shader_model.0 <= max_shader_model.0); - - let highest_shader_model = loop { - if let Some(&sm) = versions.next() { - let mut sm = Direct3D12::D3D12_FEATURE_DATA_SHADER_MODEL { - HighestShaderModel: sm, - }; - if unsafe { - device.CheckFeatureSupport( - Direct3D12::D3D12_FEATURE_SHADER_MODEL, - <*mut _>::cast(&mut sm), - size_of_val(&sm) as u32, - ) - } - .is_ok() - { - break sm.HighestShaderModel; + let shader_model = + Direct3D12::D3D_SHADER_MODEL(highest_shader_model.0.min(max_shader_model.0)); + + match shader_model { + Direct3D12::D3D_SHADER_MODEL_5_1 => { + if let Some(telemetry) = telemetry { + let category = "NONE:REQ_SM6"; + (telemetry.expose_adapter)(&get_telemetry_key(), category); } - } else { - break Direct3D12::D3D_SHADER_MODEL_5_1; + // don't expose this adapter if it doesn't support DXIL + return None; } - }; - - match highest_shader_model { - Direct3D12::D3D_SHADER_MODEL_5_1 => return None, // don't expose this adapter if it doesn't support DXIL Direct3D12::D3D_SHADER_MODEL_6_0 => naga::back::hlsl::ShaderModel::V6_0, Direct3D12::D3D_SHADER_MODEL_6_1 => naga::back::hlsl::ShaderModel::V6_1, Direct3D12::D3D_SHADER_MODEL_6_2 => naga::back::hlsl::ShaderModel::V6_2, @@ -646,6 +692,33 @@ impl super::Adapter { // See https://microsoft.github.io/DirectX-Specs/d3d/ViewInstancing.html#maximum-viewinstancecount let max_multiview_view_count = if view_instancing { 4 } else { 0 }; + if let Some(telemetry) = telemetry { + let fl = match max_feature_level { + Direct3D::D3D_FEATURE_LEVEL_12_2 => "12_2", + Direct3D::D3D_FEATURE_LEVEL_12_1 => "12_1", + Direct3D::D3D_FEATURE_LEVEL_12_0 => "12_0", + Direct3D::D3D_FEATURE_LEVEL_11_1 => "11_1", + Direct3D::D3D_FEATURE_LEVEL_11_0 => "11_0", + _ => unreachable!(), + }; + let sm = match highest_shader_model { + Direct3D12::D3D_SHADER_MODEL_6_9 => "6.9", + Direct3D12::D3D_SHADER_MODEL_6_8 => "6.8", + Direct3D12::D3D_SHADER_MODEL_6_7 => "6.7", + Direct3D12::D3D_SHADER_MODEL_6_6 => "6.6", + Direct3D12::D3D_SHADER_MODEL_6_5 => "6.5", + Direct3D12::D3D_SHADER_MODEL_6_4 => "6.4", + Direct3D12::D3D_SHADER_MODEL_6_3 => "6.3", + Direct3D12::D3D_SHADER_MODEL_6_2 => "6.2", + Direct3D12::D3D_SHADER_MODEL_6_1 => "6.1", + Direct3D12::D3D_SHADER_MODEL_6_0 => "6.0", + Direct3D12::D3D_SHADER_MODEL_5_1 => "5.1", + _ => unreachable!(), + }; + let category = &format!("SOME:FL{fl}:SM{sm}"); + (telemetry.expose_adapter)(&get_telemetry_key(), category); + } + Some(crate::ExposedAdapter { adapter: super::Adapter { raw: adapter, diff --git a/wgpu-hal/src/dx12/instance.rs b/wgpu-hal/src/dx12/instance.rs index 38d790bcb98..1686a143b7d 100644 --- a/wgpu-hal/src/dx12/instance.rs +++ b/wgpu-hal/src/dx12/instance.rs @@ -112,6 +112,7 @@ impl crate::Instance for super::Instance { memory_budget_thresholds: desc.memory_budget_thresholds, compiler_container: Arc::new(compiler_container), options: desc.backend_options.dx12.clone(), + telemetry: desc.telemetry, }) } @@ -164,6 +165,7 @@ impl crate::Instance for super::Instance { self.memory_budget_thresholds, self.compiler_container.clone(), self.options.clone(), + self.telemetry, ) }) .collect() diff --git a/wgpu-hal/src/dx12/mod.rs b/wgpu-hal/src/dx12/mod.rs index c2b3ae86904..54d9c3e93d6 100644 --- a/wgpu-hal/src/dx12/mod.rs +++ b/wgpu-hal/src/dx12/mod.rs @@ -149,6 +149,12 @@ struct D3D12Lib { lib: DynLib, } +enum CreateDeviceError { + GetProcAddress, + D3D12CreateDevice(windows_core::HRESULT), + RetDeviceIsNull, +} + impl D3D12Lib { fn new() -> Result { unsafe { DynLib::new("d3d12.dll").map(|lib| Self { lib }) } @@ -158,7 +164,7 @@ impl D3D12Lib { &self, adapter: &DxgiAdapter, feature_level: Direct3D::D3D_FEATURE_LEVEL, - ) -> Result, crate::DeviceError> { + ) -> Result { // Calls windows::Win32::Graphics::Direct3D12::D3D12CreateDevice on d3d12.dll type Fun = extern "system" fn( padapter: *mut ffi::c_void, @@ -167,7 +173,8 @@ impl D3D12Lib { ppdevice: *mut *mut ffi::c_void, ) -> windows_core::HRESULT; let func: libloading::Symbol = - unsafe { self.lib.get(c"D3D12CreateDevice".to_bytes()) }?; + unsafe { self.lib.get(c"D3D12CreateDevice".to_bytes()) } + .map_err(|_| CreateDeviceError::GetProcAddress)?; let mut result__: Option = None; @@ -177,20 +184,13 @@ impl D3D12Lib { // TODO: Generic? &Direct3D12::ID3D12Device::IID, <*mut _>::cast(&mut result__), - ) - .ok(); + ); - if let Err(ref err) = res { - match err.code() { - Dxgi::DXGI_ERROR_UNSUPPORTED => return Ok(None), - Dxgi::DXGI_ERROR_DRIVER_INTERNAL_ERROR => return Err(crate::DeviceError::Lost), - _ => {} - } + if res.is_err() { + return Err(CreateDeviceError::D3D12CreateDevice(res)); } - res.into_device_result("Device creation")?; - - result__.ok_or(crate::DeviceError::Unexpected).map(Some) + result__.ok_or(CreateDeviceError::RetDeviceIsNull) } fn serialize_root_signature( @@ -474,6 +474,7 @@ pub struct Instance { memory_budget_thresholds: wgt::MemoryBudgetThresholds, compiler_container: Arc, options: wgt::Dx12BackendOptions, + telemetry: Option, } impl Instance { diff --git a/wgpu-hal/src/lib.rs b/wgpu-hal/src/lib.rs index 8620d05a849..988bc4fba3a 100644 --- a/wgpu-hal/src/lib.rs +++ b/wgpu-hal/src/lib.rs @@ -1767,6 +1767,7 @@ pub struct InstanceDescriptor<'a> { pub flags: wgt::InstanceFlags, pub memory_budget_thresholds: wgt::MemoryBudgetThresholds, pub backend_options: wgt::BackendOptions, + pub telemetry: Option, } #[derive(Clone, Debug)] @@ -2692,3 +2693,9 @@ pub struct TlasInstance { pub mask: u8, pub blas_address: u64, } + +/// Strings should have a maximum of 111 characters and be comprised of printable ASCII characters. +#[derive(Debug, Clone, Copy)] +pub struct Telemetry { + pub expose_adapter: fn(key: &str, category: &str), +} diff --git a/wgpu-hal/src/noop/mod.rs b/wgpu-hal/src/noop/mod.rs index b6c110df8de..2e60ebf3f7c 100644 --- a/wgpu-hal/src/noop/mod.rs +++ b/wgpu-hal/src/noop/mod.rs @@ -98,6 +98,7 @@ impl crate::Instance for Context { name: _, flags: _, memory_budget_thresholds: _, + telemetry: _, } = *desc; if enable { Ok(Context) diff --git a/wgpu/src/backend/wgpu_core.rs b/wgpu/src/backend/wgpu_core.rs index 2c5b3fea787..aad7bb9d475 100644 --- a/wgpu/src/backend/wgpu_core.rs +++ b/wgpu/src/backend/wgpu_core.rs @@ -781,7 +781,7 @@ impl dispatch::InstanceInterface for ContextWgpuCore { where Self: Sized, { - Self(Arc::new(wgc::global::Global::new("wgpu", desc))) + Self(Arc::new(wgc::global::Global::new("wgpu", desc, None))) } unsafe fn create_surface(