mirror of
https://github.com/gfx-rs/wgpu.git
synced 2024-11-25 16:24:24 +00:00
Implement Presentation Timestamp Correlation (#3240)
Co-authored-by: Jim Blandy <jimb@red-bean.com>
This commit is contained in:
parent
f3c50918c2
commit
5241633b3a
@ -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
1
Cargo.lock
generated
@ -2972,6 +2972,7 @@ dependencies = [
|
||||
"gpu-descriptor",
|
||||
"js-sys",
|
||||
"khronos-egl",
|
||||
"libc",
|
||||
"libloading",
|
||||
"log",
|
||||
"metal",
|
||||
|
@ -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.
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
@ -2,3 +2,4 @@ pub mod conv;
|
||||
pub mod exception;
|
||||
pub mod factory;
|
||||
pub mod result;
|
||||
pub mod time;
|
||||
|
94
wgpu-hal/src/auxil/dxgi/time.rs
Normal file
94
wgpu-hal/src/auxil/dxgi/time.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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] = &[
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
38
wgpu-hal/src/metal/time.rs
Normal file
38
wgpu-hal/src/metal/time.rs
Normal 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
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user