Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Presentation Timestamp Correlation #3240

Merged
merged 17 commits into from
Dec 20, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
18 changes: 18 additions & 0 deletions wgpu-core/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -925,6 +925,24 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
.map_err(|_| InvalidAdapter)
}

pub fn adapter_correlate_presentation_timestamp<A: HalApi>(
&self,
adapter_id: AdapterId,
user_timestamp_function: &mut dyn FnMut(),
) -> 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
.correlate_presentation_timestamp(user_timestamp_function)
})
}

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 @@ -86,7 +86,7 @@ egl = { workspace = true, features = ["static", "no-pkg-config"] }
libloading = { workspace = true, optional = true }

[target.'cfg(windows)'.dependencies]
winapi = { workspace = true, features = ["libloaderapi", "windef", "winuser", "dcomp"] }
winapi = { workspace = true, features = ["profileapi", "libloaderapi", "windef", "winuser", "dcomp"] }
native = { workspace = true, features = ["libloading"], optional = true }

[target.'cfg(any(target_os="macos", target_os="ios"))'.dependencies]
Expand All @@ -99,6 +99,9 @@ wasm-bindgen.workspace = true
web-sys = { workspace = true, features = ["Window", "HtmlCanvasElement", "WebGl2RenderingContext", "OffscreenCanvas"] }
js-sys.workspace = true

[target.'cfg(unix)'.dependencies]
libc.workspace = true

[target.'cfg(target_os = "android")'.dependencies]
android_system_properties.workspace = true

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;
92 changes: 92 additions & 0 deletions wgpu-hal/src/auxil/dxgi/time.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#![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 {
QueryInterruptTimePrecise: 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 {
QueryInterruptTimePrecise,
} => f
.debug_struct("IPresentationManager")
.field(
"QueryInterruptTimePrecise",
&(QueryInterruptTimePrecise 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+
let kernel32 =
libloading::os::windows::Library::open_already_loaded("kernelbase.dll").unwrap();
// No concerns about lifetimes here as kernelbase is always there.
let ptr = unsafe { kernel32.get(b"QueryInterruptTimePrecise").unwrap() };
Self::IPresentationManager {
QueryInterruptTimePrecise: *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 {
QueryInterruptTimePrecise,
} => {
let mut counter = 0;
unsafe { QueryInterruptTimePrecise(&mut counter) };

// QueryInterruptTimePrecise uses units of 100ns for its tick.
counter as u128 * 100
}
}
}
}
7 changes: 7 additions & 0 deletions wgpu-hal/src/dx11/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ impl crate::Adapter<super::Api> for super::Adapter {
) -> Option<crate::SurfaceCapabilities> {
todo!()
}

unsafe fn correlate_presentation_timestamp(
&self,
user_timestamp_function: &mut dyn FnMut(),
) -> wgt::PresentationTimestamp {
todo!()
}
}

impl super::Adapter {
Expand Down
12 changes: 12 additions & 0 deletions wgpu-hal/src/dx12/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,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 @@ -237,6 +240,7 @@ impl super::Adapter {
device,
library: Arc::clone(library),
private_caps,
presentation_timer,
workarounds,
},
info,
Expand Down Expand Up @@ -539,4 +543,12 @@ impl crate::Adapter<super::Api> for super::Adapter {
composite_alpha_modes: vec![wgt::CompositeAlphaMode::Opaque],
})
}

unsafe fn correlate_presentation_timestamp(
&self,
user_timestamp_function: &mut dyn FnMut(),
) -> wgt::PresentationTimestamp {
user_timestamp_function();
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 @@ -167,6 +167,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
8 changes: 8 additions & 0 deletions wgpu-hal/src/empty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ impl crate::Adapter<Api> for Context {
unsafe fn surface_capabilities(&self, surface: &Context) -> Option<crate::SurfaceCapabilities> {
None
}

unsafe fn correlate_presentation_timestamp(
&self,
function: &mut dyn FnMut(),
) -> wgt::PresentationTimestamp {
function();
wgt::PresentationTimestamp::INVALID_TIMESTAMP
}
}

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

unsafe fn correlate_presentation_timestamp(
&self,
function: &mut dyn FnMut(),
) -> wgt::PresentationTimestamp {
function();
wgt::PresentationTimestamp::INVALID_TIMESTAMP
}
}

impl super::AdapterShared {
Expand Down
10 changes: 10 additions & 0 deletions wgpu-hal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,16 @@ 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](wgt::PresentationTimestamp) then immediately calls the user_timestamp_function,
/// allowing the user to correlate the presentation timestamp with another set of timestamps (like `Instant`s).
///
/// While user_timestamp_function is an FnMut, this is because of higher level internal details. It will only ever be called once
/// and it will unconditionally be called.
cwfitzgerald marked this conversation as resolved.
Show resolved Hide resolved
unsafe fn correlate_presentation_timestamp(
&self,
user_timestamp_function: &mut dyn FnMut(),
) -> wgt::PresentationTimestamp;
}

pub trait Device<A: Api>: Send + Sync {
Expand Down
31 changes: 31 additions & 0 deletions wgpu-hal/src/metal/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,37 @@ impl crate::Adapter<super::Api> for super::Adapter {
usage: crate::TextureUses::COLOR_TARGET | crate::TextureUses::COPY_DST, //TODO: expose more
})
}

unsafe fn correlate_presentation_timestamp(
&self,
user_tiemstamp_function: &mut dyn FnMut(),
) -> wgt::PresentationTimestamp {
#[repr(C)]
#[derive(Debug)]
struct mach_timebase_info {
numerator: u32,
denominator: u32,
}
extern "C" {
fn mach_timebase_info(out: *mut mach_timebase_info) -> u32;
fn mach_absolute_time() -> u64;
}
cwfitzgerald marked this conversation as resolved.
Show resolved Hide resolved

// Get the two timestamps as close as possible to each other.
// Doing timestamp processing _after_ both functions are called.
user_tiemstamp_function();
let timestamp_base = unsafe { mach_absolute_time() };

let mut info = mach_timebase_info {
numerator: 0,
denominator: 0,
};
unsafe { mach_timebase_info(&mut info) };
let timestamp_scaled =
(timestamp_base as u128 * info.numerator as u128) / info.denominator as u128;

wgt::PresentationTimestamp(timestamp_scaled)
}
}

const RESOURCE_HEAP_SUPPORT: &[MTLFeatureSet] = &[
Expand Down
26 changes: 26 additions & 0 deletions wgpu-hal/src/vulkan/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1582,6 +1582,32 @@ impl crate::Adapter<super::Api> for super::Adapter {
composite_alpha_modes: conv::map_vk_composite_alpha(caps.supported_composite_alpha),
})
}

unsafe fn correlate_presentation_timestamp(
&self,
user_timestamp_function: &mut dyn FnMut(),
) -> wgt::PresentationTimestamp {
#[cfg(unix)]
{
let mut timespec = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
user_timestamp_function();
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))]
{
user_timestamp_function();
wgt::PresentationTimestamp::INVALID_TIMESTAMP
}
}
}

fn is_format_16bit_norm_supported(instance: &ash::Instance, phd: vk::PhysicalDevice) -> bool {
Expand Down
37 changes: 37 additions & 0 deletions wgpu-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3953,6 +3953,43 @@ pub enum SurfaceStatus {
Lost,
}

/// Nanosecond timestamp used by the presentation engine.
///
/// These specific clock depends on the WSI used.
cwfitzgerald marked this conversation as resolved.
Show resolved Hide resolved
///
/// <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,
cwfitzgerald marked this conversation as resolved.
Show resolved Hide resolved
);

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.
Expand Down
Loading