diff --git a/CHANGELOG.md b/CHANGELOG.md index ca6081e140..d795621fd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -140,6 +140,7 @@ Additionally `Surface::get_default_config` now returns an Option and returns Non - Implement `Clone` for `ShaderSource` and `ShaderModuleDescriptor` in `wgpu`. By @daxpedda in [#3086](https://github.com/gfx-rs/wgpu/pull/3086). - Add `get_default_config` for `Surface` to simplify user creation of `SurfaceConfiguration`. By @jinleili in [#3034](https://github.com/gfx-rs/wgpu/pull/3034) - Native adapters can now use MSAA x2 and x8 if it's supported , previously only x1 and x4 were supported . By @39ali in [3140](https://github.com/gfx-rs/wgpu/pull/3140) +- Implemented correleation between user timestamps and platform specific presentation timestamps via [`Adapter::get_presentation_timestamp`]. By @cwfitzgerald in [#3240](https://github.com/gfx-rs/wgpu/pull/3240) - Added support for `Features::SHADER_PRIMITIVE_INDEX` on all backends. By @cwfitzgerald in [#3272](https://github.com/gfx-rs/wgpu/pull/3272) #### GLES @@ -226,7 +227,6 @@ Additionally `Surface::get_default_config` now returns an Option and returns Non - Make `wgpu::TextureFormat::Depth24PlusStencil8` available on all backends by making the feature unconditionally available and the feature unneeded to use the format. By @Healthire and @cwfitzgerald in [#3165](https://github.com/gfx-rs/wgpu/pull/3165) - ## wgpu-0.14.0 (2022-10-05) ### Major Changes diff --git a/Cargo.lock b/Cargo.lock index dbaec03eee..cb01f676ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2972,6 +2972,7 @@ dependencies = [ "gpu-descriptor", "js-sys", "khronos-egl", + "libc", "libloading", "log", "metal", diff --git a/Cargo.toml b/Cargo.toml index 7b693ae1a0..2874adc505 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ futures-intrusive = "0.4" fxhash = "0.2.1" glam = "0.21.3" libloading = "0.7" +libc = "0.2" log = "0.4" nanorand = { version = "0.7", default-features = false } # Opt out of noise's "default-features" to avoid "image" feature as a dependency count optimization. diff --git a/wgpu-core/src/instance.rs b/wgpu-core/src/instance.rs index 5b10d73840..7cb8a4f1fd 100644 --- a/wgpu-core/src/instance.rs +++ b/wgpu-core/src/instance.rs @@ -933,6 +933,18 @@ impl Global { .map_err(|_| InvalidAdapter) } + pub fn adapter_get_presentation_timestamp( + &self, + adapter_id: AdapterId, + ) -> Result { + let hub = A::hub(self); + let mut token = Token::root(); + let (adapter_guard, _) = hub.adapters.read(&mut token); + let adapter = adapter_guard.get(adapter_id).map_err(|_| InvalidAdapter)?; + + Ok(unsafe { adapter.raw.adapter.get_presentation_timestamp() }) + } + pub fn adapter_drop(&self, adapter_id: AdapterId) { profiling::scope!("Adapter::drop"); diff --git a/wgpu-hal/Cargo.toml b/wgpu-hal/Cargo.toml index 95aea0e80b..b5d6b6e645 100644 --- a/wgpu-hal/Cargo.toml +++ b/wgpu-hal/Cargo.toml @@ -90,7 +90,7 @@ egl = { package = "khronos-egl", version = "4.1", features = ["static", "no-pkg- libloading = { version = "0.7", optional = true } [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3", features = ["libloaderapi", "windef", "winuser", "dcomp"] } +winapi = { version = "0.3", features = ["profileapi", "libloaderapi", "windef", "winuser", "dcomp"] } native = { package = "d3d12", version = "0.5.0", features = ["libloading"], optional = true } [target.'cfg(any(target_os="macos", target_os="ios"))'.dependencies] @@ -103,6 +103,9 @@ wasm-bindgen = "0.2.83" web-sys = { version = "0.3.60", features = ["Window", "HtmlCanvasElement", "WebGl2RenderingContext", "OffscreenCanvas"] } js-sys = "0.3.60" +[target.'cfg(unix)'.dependencies] +libc = "0.2" + [target.'cfg(target_os = "android")'.dependencies] android_system_properties = "0.1.1" diff --git a/wgpu-hal/src/auxil/dxgi/mod.rs b/wgpu-hal/src/auxil/dxgi/mod.rs index 09a2b31bf3..559969633c 100644 --- a/wgpu-hal/src/auxil/dxgi/mod.rs +++ b/wgpu-hal/src/auxil/dxgi/mod.rs @@ -2,3 +2,4 @@ pub mod conv; pub mod exception; pub mod factory; pub mod result; +pub mod time; diff --git a/wgpu-hal/src/auxil/dxgi/time.rs b/wgpu-hal/src/auxil/dxgi/time.rs new file mode 100644 index 0000000000..fd99c097d7 --- /dev/null +++ b/wgpu-hal/src/auxil/dxgi/time.rs @@ -0,0 +1,94 @@ +#![allow(dead_code)] // IPresentationManager is unused currently + +use std::mem; + +use winapi::um::{ + profileapi::{QueryPerformanceCounter, QueryPerformanceFrequency}, + winnt::LARGE_INTEGER, +}; + +pub enum PresentationTimer { + /// DXGI uses QueryPerformanceCounter + Dxgi { + /// How many ticks of QPC per second + frequency: u64, + }, + /// IPresentationManager uses QueryInterruptTimePrecise + #[allow(non_snake_case)] + IPresentationManager { + fnQueryInterruptTimePrecise: unsafe extern "system" fn(*mut winapi::ctypes::c_ulonglong), + }, +} + +impl std::fmt::Debug for PresentationTimer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match *self { + Self::Dxgi { frequency } => f + .debug_struct("DXGI") + .field("frequency", &frequency) + .finish(), + Self::IPresentationManager { + fnQueryInterruptTimePrecise, + } => f + .debug_struct("IPresentationManager") + .field( + "QueryInterruptTimePrecise", + &(fnQueryInterruptTimePrecise as usize), + ) + .finish(), + } + } +} + +impl PresentationTimer { + /// Create a presentation timer using QueryPerformanceFrequency (what DXGI uses for presentation times) + pub fn new_dxgi() -> Self { + let mut frequency: LARGE_INTEGER = unsafe { mem::zeroed() }; + let success = unsafe { QueryPerformanceFrequency(&mut frequency) }; + assert_ne!(success, 0); + + Self::Dxgi { + frequency: unsafe { *frequency.QuadPart() } as u64, + } + } + + /// Create a presentation timer using QueryInterruptTimePrecise (what IPresentationManager uses for presentation times) + /// + /// Panics if QueryInterruptTimePrecise isn't found (below Win10) + pub fn new_ipresentation_manager() -> Self { + // We need to load this explicitly, as QueryInterruptTimePrecise is only available on Windows 10+ + // + // Docs say it's in kernel32.dll, but it's actually in kernelbase.dll. + let kernelbase = + libloading::os::windows::Library::open_already_loaded("kernelbase.dll").unwrap(); + // No concerns about lifetimes here as kernelbase is always there. + let ptr = unsafe { kernelbase.get(b"QueryInterruptTimePrecise").unwrap() }; + Self::IPresentationManager { + fnQueryInterruptTimePrecise: *ptr, + } + } + + /// Gets the current time in nanoseconds. + pub fn get_timestamp_ns(&self) -> u128 { + // Always do u128 math _after_ hitting the timing function. + match *self { + PresentationTimer::Dxgi { frequency } => { + let mut counter: LARGE_INTEGER = unsafe { mem::zeroed() }; + let success = unsafe { QueryPerformanceCounter(&mut counter) }; + assert_ne!(success, 0); + + // counter * (1_000_000_000 / freq) but re-ordered to make more precise + (unsafe { *counter.QuadPart() } as u128 * 1_000_000_000) / frequency as u128 + } + PresentationTimer::IPresentationManager { + fnQueryInterruptTimePrecise, + } => { + let mut counter = 0; + unsafe { fnQueryInterruptTimePrecise(&mut counter) }; + + // QueryInterruptTimePrecise uses units of 100ns for its tick. + counter as u128 * 100 + } + } + } +} diff --git a/wgpu-hal/src/dx11/adapter.rs b/wgpu-hal/src/dx11/adapter.rs index af9862c144..6e14b42f5a 100644 --- a/wgpu-hal/src/dx11/adapter.rs +++ b/wgpu-hal/src/dx11/adapter.rs @@ -24,6 +24,10 @@ impl crate::Adapter for super::Adapter { ) -> Option { todo!() } + + unsafe fn get_presentation_timestamp(&self) -> wgt::PresentationTimestamp { + todo!() + } } impl super::Adapter { diff --git a/wgpu-hal/src/dx12/adapter.rs b/wgpu-hal/src/dx12/adapter.rs index d1efd0d8c6..2119ebd522 100644 --- a/wgpu-hal/src/dx12/adapter.rs +++ b/wgpu-hal/src/dx12/adapter.rs @@ -230,6 +230,9 @@ impl super::Adapter { shader_model_support.HighestShaderModel >= d3d12::D3D_SHADER_MODEL_5_1, ); + // TODO: Determine if IPresentationManager is supported + let presentation_timer = auxil::dxgi::time::PresentationTimer::new_dxgi(); + let base = wgt::Limits::default(); Some(crate::ExposedAdapter { @@ -238,6 +241,7 @@ impl super::Adapter { device, library: Arc::clone(library), private_caps, + presentation_timer, workarounds, }, info, @@ -541,4 +545,8 @@ impl crate::Adapter for super::Adapter { composite_alpha_modes: vec![wgt::CompositeAlphaMode::Opaque], }) } + + unsafe fn get_presentation_timestamp(&self) -> wgt::PresentationTimestamp { + wgt::PresentationTimestamp(self.presentation_timer.get_timestamp_ns()) + } } diff --git a/wgpu-hal/src/dx12/mod.rs b/wgpu-hal/src/dx12/mod.rs index 1e34188ed4..b3be9e722c 100644 --- a/wgpu-hal/src/dx12/mod.rs +++ b/wgpu-hal/src/dx12/mod.rs @@ -169,6 +169,7 @@ pub struct Adapter { device: native::Device, library: Arc, private_caps: PrivateCapabilities, + presentation_timer: auxil::dxgi::time::PresentationTimer, //Note: this isn't used right now, but we'll need it later. #[allow(unused)] workarounds: Workarounds, diff --git a/wgpu-hal/src/empty.rs b/wgpu-hal/src/empty.rs index a761ef7fb1..30e8156e84 100644 --- a/wgpu-hal/src/empty.rs +++ b/wgpu-hal/src/empty.rs @@ -93,6 +93,10 @@ impl crate::Adapter for Context { unsafe fn surface_capabilities(&self, surface: &Context) -> Option { None } + + unsafe fn get_presentation_timestamp(&self) -> wgt::PresentationTimestamp { + wgt::PresentationTimestamp::INVALID_TIMESTAMP + } } impl crate::Queue for Context { diff --git a/wgpu-hal/src/gles/adapter.rs b/wgpu-hal/src/gles/adapter.rs index 326b449bfb..9d4231da86 100644 --- a/wgpu-hal/src/gles/adapter.rs +++ b/wgpu-hal/src/gles/adapter.rs @@ -874,6 +874,10 @@ impl crate::Adapter for super::Adapter { None } } + + unsafe fn get_presentation_timestamp(&self) -> wgt::PresentationTimestamp { + wgt::PresentationTimestamp::INVALID_TIMESTAMP + } } impl super::AdapterShared { diff --git a/wgpu-hal/src/lib.rs b/wgpu-hal/src/lib.rs index dcce62899d..db15ecf24e 100644 --- a/wgpu-hal/src/lib.rs +++ b/wgpu-hal/src/lib.rs @@ -242,6 +242,11 @@ pub trait Adapter: Send + Sync { /// /// `None` means presentation is not supported for it. unsafe fn surface_capabilities(&self, surface: &A::Surface) -> Option; + + /// Creates a [`PresentationTimestamp`] using the adapter's WSI. + /// + /// [`PresentationTimestamp`]: wgt::PresentationTimestamp + unsafe fn get_presentation_timestamp(&self) -> wgt::PresentationTimestamp; } pub trait Device: Send + Sync { diff --git a/wgpu-hal/src/metal/adapter.rs b/wgpu-hal/src/metal/adapter.rs index faf6067c46..5ade092c24 100644 --- a/wgpu-hal/src/metal/adapter.rs +++ b/wgpu-hal/src/metal/adapter.rs @@ -319,6 +319,12 @@ impl crate::Adapter for super::Adapter { usage: crate::TextureUses::COLOR_TARGET | crate::TextureUses::COPY_DST, //TODO: expose more }) } + + unsafe fn get_presentation_timestamp(&self) -> wgt::PresentationTimestamp { + let timestamp = self.shared.presentation_timer.get_timestamp_ns(); + + wgt::PresentationTimestamp(timestamp) + } } const RESOURCE_HEAP_SUPPORT: &[MTLFeatureSet] = &[ diff --git a/wgpu-hal/src/metal/mod.rs b/wgpu-hal/src/metal/mod.rs index 589562f46e..37f101cff7 100644 --- a/wgpu-hal/src/metal/mod.rs +++ b/wgpu-hal/src/metal/mod.rs @@ -18,6 +18,7 @@ mod command; mod conv; mod device; mod surface; +mod time; use std::{ fmt, iter, ops, @@ -253,6 +254,7 @@ struct AdapterShared { disabilities: PrivateDisabilities, private_caps: PrivateCapabilities, settings: Settings, + presentation_timer: time::PresentationTimer, } unsafe impl Send for AdapterShared {} @@ -268,6 +270,7 @@ impl AdapterShared { private_caps, device: Mutex::new(device), settings: Settings::default(), + presentation_timer: time::PresentationTimer::new(), } } } diff --git a/wgpu-hal/src/metal/time.rs b/wgpu-hal/src/metal/time.rs new file mode 100644 index 0000000000..5c6bec10cd --- /dev/null +++ b/wgpu-hal/src/metal/time.rs @@ -0,0 +1,38 @@ +//! Handling of global timestamps. + +#[repr(C)] +#[derive(Debug)] +struct MachTimebaseInfo { + numerator: u32, + denominator: u32, +} +extern "C" { + fn mach_timebase_info(out: *mut MachTimebaseInfo) -> u32; + fn mach_absolute_time() -> u64; +} + +/// A timer which uses mach_absolute_time to get its time. This is what the metal callbacks use. +#[derive(Debug)] +pub struct PresentationTimer { + scale: MachTimebaseInfo, +} +impl PresentationTimer { + /// Generates a new timer. + pub fn new() -> Self { + // Default to 1 / 1 in case the call to timebase_info fails. + let mut scale = MachTimebaseInfo { + numerator: 1, + denominator: 1, + }; + unsafe { mach_timebase_info(&mut scale) }; + + Self { scale } + } + + /// Gets the current time in nanoseconds. + pub fn get_timestamp_ns(&self) -> u128 { + let time = unsafe { mach_absolute_time() }; + + (time as u128 * self.scale.numerator as u128) / self.scale.denominator as u128 + } +} diff --git a/wgpu-hal/src/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs index fc03014ee2..e0c84ed43f 100644 --- a/wgpu-hal/src/vulkan/adapter.rs +++ b/wgpu-hal/src/vulkan/adapter.rs @@ -1598,6 +1598,31 @@ impl crate::Adapter for super::Adapter { composite_alpha_modes: conv::map_vk_composite_alpha(caps.supported_composite_alpha), }) } + + unsafe fn get_presentation_timestamp(&self) -> wgt::PresentationTimestamp { + // VK_GOOGLE_display_timing is the only way to get presentation + // timestamps on vulkan right now and it is only ever available + // on android and linux. This includes mac, but there's no alternative + // on mac, so this is fine. + #[cfg(unix)] + { + let mut timespec = libc::timespec { + tv_sec: 0, + tv_nsec: 0, + }; + unsafe { + libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut timespec); + } + + wgt::PresentationTimestamp( + timespec.tv_sec as u128 * 1_000_000_000 + timespec.tv_nsec as u128, + ) + } + #[cfg(not(unix))] + { + wgt::PresentationTimestamp::INVALID_TIMESTAMP + } + } } fn is_format_16bit_norm_supported(instance: &ash::Instance, phd: vk::PhysicalDevice) -> bool { diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index 854864036f..3cb9bbc858 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -3965,6 +3965,43 @@ pub enum SurfaceStatus { Lost, } +/// Nanosecond timestamp used by the presentation engine. +/// +/// The specific clock depends on the window system integration (WSI) API used. +/// +/// +/// +/// +/// +/// +/// +///
WSI +/// Clock +///
IDXGISwapchain +/// QueryPerformanceCounter +///
IPresentationManager +/// QueryInterruptTimePrecise +///
CAMetalLayer +/// mach_absolute_time +///
VK_GOOGLE_display_timing +/// clock_gettime(CLOCK_MONOTONIC) +///
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct PresentationTimestamp( + /// Timestamp in nanoseconds. + pub u128, +); + +impl PresentationTimestamp { + /// A timestamp that is invalid due to the platform not having a timestamp system. + pub const INVALID_TIMESTAMP: Self = Self(u128::MAX); + + /// Returns true if this timestamp is the invalid timestamp. + pub fn is_invalid(self) -> bool { + self == Self::INVALID_TIMESTAMP + } +} + /// RGBA double precision color. /// /// This is not to be used as a generic color type, only for specific wgpu interfaces. diff --git a/wgpu/src/backend/direct.rs b/wgpu/src/backend/direct.rs index db0ad568cd..ebdf41d61b 100644 --- a/wgpu/src/backend/direct.rs +++ b/wgpu/src/backend/direct.rs @@ -672,6 +672,18 @@ impl crate::Context for Context { } } + fn adapter_get_presentation_timestamp( + &self, + adapter: &Self::AdapterId, + _adapter_data: &Self::AdapterData, + ) -> wgt::PresentationTimestamp { + let global = &self.0; + match wgc::gfx_select!(*adapter => global.adapter_get_presentation_timestamp(*adapter)) { + Ok(timestamp) => timestamp, + Err(err) => self.handle_error_fatal(err, "Adapter::correlate_presentation_timestamp"), + } + } + fn surface_get_capabilities( &self, surface: &Self::SurfaceId, diff --git a/wgpu/src/backend/web.rs b/wgpu/src/backend/web.rs index a12b81e59d..bd490cd1a6 100644 --- a/wgpu/src/backend/web.rs +++ b/wgpu/src/backend/web.rs @@ -986,6 +986,14 @@ impl crate::context::Context for Context { format.describe().guaranteed_format_features } + fn adapter_get_presentation_timestamp( + &self, + _adapter: &Self::AdapterId, + _adapter_data: &Self::AdapterData, + ) -> wgt::PresentationTimestamp { + wgt::PresentationTimestamp::INVALID_TIMESTAMP + } + fn surface_get_capabilities( &self, _surface: &Self::SurfaceId, diff --git a/wgpu/src/context.rs b/wgpu/src/context.rs index 35bd01491d..cbdb95eb09 100644 --- a/wgpu/src/context.rs +++ b/wgpu/src/context.rs @@ -145,6 +145,11 @@ pub trait Context: Debug + Send + Sized + Sync { adapter_data: &Self::AdapterData, format: TextureFormat, ) -> TextureFormatFeatures; + fn adapter_get_presentation_timestamp( + &self, + adapter: &Self::AdapterId, + adapter_data: &Self::AdapterData, + ) -> wgt::PresentationTimestamp; fn surface_get_capabilities( &self, @@ -1090,6 +1095,12 @@ pub(crate) trait DynContext: Debug + Send + Sync { adapter_data: &crate::Data, format: TextureFormat, ) -> TextureFormatFeatures; + fn adapter_get_presentation_timestamp( + &self, + adapter: &ObjectId, + adapter_data: &crate::Data, + ) -> wgt::PresentationTimestamp; + fn surface_get_capabilities( &self, surface: &ObjectId, @@ -1969,6 +1980,15 @@ where let adapter_data = downcast_ref(adapter_data); Context::adapter_get_texture_format_features(self, &adapter, adapter_data, format) } + fn adapter_get_presentation_timestamp( + &self, + adapter: &ObjectId, + adapter_data: &crate::Data, + ) -> wgt::PresentationTimestamp { + let adapter = ::from(*adapter); + let adapter_data = downcast_ref(adapter_data); + Context::adapter_get_presentation_timestamp(self, &adapter, adapter_data) + } fn surface_get_capabilities( &self,