Implement Presentation Timestamp Correlation (#3240)

Co-authored-by: Jim Blandy <jimb@red-bean.com>
This commit is contained in:
Connor Fitzgerald 2022-12-20 12:52:08 -05:00 committed by GitHub
parent f3c50918c2
commit 5241633b3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 289 additions and 2 deletions

View File

@ -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

1
Cargo.lock generated
View File

@ -2972,6 +2972,7 @@ dependencies = [
"gpu-descriptor",
"js-sys",
"khronos-egl",
"libc",
"libloading",
"log",
"metal",

View File

@ -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.

View File

@ -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");

View File

@ -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"

View File

@ -2,3 +2,4 @@ pub mod conv;
pub mod exception;
pub mod factory;
pub mod result;
pub mod time;

View File

@ -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
}
}
}
}

View File

@ -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 {

View File

@ -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<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())
}
}

View File

@ -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,

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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] = &[

View File

@ -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(),
}
}
}

View File

@ -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
}
}

View File

@ -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 {

View File

@ -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.
///
/// <table>
/// <tr>
/// <td>WSI
/// <td>Clock
/// <tr>
/// <td>IDXGISwapchain
/// <td><a href="https://docs.microsoft.com/en-us/windows/win32/api/profileapi/nf-profileapi-queryperformancecounter">QueryPerformanceCounter</a>
/// <tr>
/// <td>IPresentationManager
/// <td><a href="https://docs.microsoft.com/en-us/windows/win32/api/realtimeapiset/nf-realtimeapiset-queryinterrupttimeprecise">QueryInterruptTimePrecise</a>
/// <tr>
/// <td>CAMetalLayer
/// <td><a href="https://developer.apple.com/documentation/kernel/1462446-mach_absolute_time">mach_absolute_time</a>
/// <tr>
/// <td>VK_GOOGLE_display_timing
/// <td><a href="https://linux.die.net/man/3/clock_gettime">clock_gettime(CLOCK_MONOTONIC)</a>
/// </table>
#[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.

View File

@ -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,

View File

@ -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,

View File

@ -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 = <T::AdapterId>::from(*adapter);
let adapter_data = downcast_ref(adapter_data);
Context::adapter_get_presentation_timestamp(self, &adapter, adapter_data)
}
fn surface_get_capabilities(
&self,