Skip to content

Commit

Permalink
Implement Presentation Timestamp Correlation (#3240)
Browse files Browse the repository at this point in the history
Co-authored-by: Jim Blandy <[email protected]>
  • Loading branch information
cwfitzgerald and jimblandy authored Dec 20, 2022
1 parent f3c5091 commit 5241633
Show file tree
Hide file tree
Showing 21 changed files with 289 additions and 2 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
12 changes: 12 additions & 0 deletions wgpu-core/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,18 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
.map_err(|_| InvalidAdapter)
}

pub fn adapter_get_presentation_timestamp<A: HalApi>(
&self,
adapter_id: AdapterId,
) -> Result<wgt::PresentationTimestamp, InvalidAdapter> {
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<A: HalApi>(&self, adapter_id: AdapterId) {
profiling::scope!("Adapter::drop");

Expand Down
5 changes: 4 additions & 1 deletion wgpu-hal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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"

Expand Down
1 change: 1 addition & 0 deletions wgpu-hal/src/auxil/dxgi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ pub mod conv;
pub mod exception;
pub mod factory;
pub mod result;
pub mod time;
94 changes: 94 additions & 0 deletions wgpu-hal/src/auxil/dxgi/time.rs
Original file line number Diff line number Diff line change
@@ -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
}
}
}
}
4 changes: 4 additions & 0 deletions wgpu-hal/src/dx11/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ impl crate::Adapter<super::Api> for super::Adapter {
) -> Option<crate::SurfaceCapabilities> {
todo!()
}

unsafe fn get_presentation_timestamp(&self) -> wgt::PresentationTimestamp {
todo!()
}
}

impl super::Adapter {
Expand Down
8 changes: 8 additions & 0 deletions wgpu-hal/src/dx12/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -238,6 +241,7 @@ impl super::Adapter {
device,
library: Arc::clone(library),
private_caps,
presentation_timer,
workarounds,
},
info,
Expand Down Expand Up @@ -541,4 +545,8 @@ impl crate::Adapter<super::Api> 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())
}
}
1 change: 1 addition & 0 deletions wgpu-hal/src/dx12/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ pub struct Adapter {
device: native::Device,
library: Arc<native::D3D12Lib>,
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,
Expand Down
4 changes: 4 additions & 0 deletions wgpu-hal/src/empty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ impl crate::Adapter<Api> for Context {
unsafe fn surface_capabilities(&self, surface: &Context) -> Option<crate::SurfaceCapabilities> {
None
}

unsafe fn get_presentation_timestamp(&self) -> wgt::PresentationTimestamp {
wgt::PresentationTimestamp::INVALID_TIMESTAMP
}
}

impl crate::Queue<Api> for Context {
Expand Down
4 changes: 4 additions & 0 deletions wgpu-hal/src/gles/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,10 @@ impl crate::Adapter<super::Api> for super::Adapter {
None
}
}

unsafe fn get_presentation_timestamp(&self) -> wgt::PresentationTimestamp {
wgt::PresentationTimestamp::INVALID_TIMESTAMP
}
}

impl super::AdapterShared {
Expand Down
5 changes: 5 additions & 0 deletions wgpu-hal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,11 @@ pub trait Adapter<A: Api>: Send + Sync {
///
/// `None` means presentation is not supported for it.
unsafe fn surface_capabilities(&self, surface: &A::Surface) -> Option<SurfaceCapabilities>;

/// Creates a [`PresentationTimestamp`] using the adapter's WSI.
///
/// [`PresentationTimestamp`]: wgt::PresentationTimestamp
unsafe fn get_presentation_timestamp(&self) -> wgt::PresentationTimestamp;
}

pub trait Device<A: Api>: Send + Sync {
Expand Down
6 changes: 6 additions & 0 deletions wgpu-hal/src/metal/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,12 @@ impl crate::Adapter<super::Api> 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] = &[
Expand Down
3 changes: 3 additions & 0 deletions wgpu-hal/src/metal/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mod command;
mod conv;
mod device;
mod surface;
mod time;

use std::{
fmt, iter, ops,
Expand Down Expand Up @@ -253,6 +254,7 @@ struct AdapterShared {
disabilities: PrivateDisabilities,
private_caps: PrivateCapabilities,
settings: Settings,
presentation_timer: time::PresentationTimer,
}

unsafe impl Send for AdapterShared {}
Expand All @@ -268,6 +270,7 @@ impl AdapterShared {
private_caps,
device: Mutex::new(device),
settings: Settings::default(),
presentation_timer: time::PresentationTimer::new(),
}
}
}
Expand Down
38 changes: 38 additions & 0 deletions wgpu-hal/src/metal/time.rs
Original file line number Diff line number Diff line change
@@ -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
}
}
25 changes: 25 additions & 0 deletions wgpu-hal/src/vulkan/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1598,6 +1598,31 @@ impl crate::Adapter<super::Api> 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 {
Expand Down
Loading

0 comments on commit 5241633

Please sign in to comment.