a/vk: Add vk_convert_timestamps_to_host_ns function

This commit is contained in:
Jakob Bornecrantz 2022-04-24 17:16:14 +01:00
parent f217046810
commit 917265d1b2
4 changed files with 181 additions and 0 deletions

View file

@ -271,6 +271,7 @@ if(XRT_HAVE_VULKAN)
vk/vk_print.c
vk/vk_state_creators.c
vk/vk_sync_objects.c
vk/vk_time.c
)
target_link_libraries(aux_vk PUBLIC aux_os aux_util)
target_link_libraries(aux_vk PUBLIC Vulkan::Vulkan)

View file

@ -315,6 +315,7 @@ lib_aux_vk = static_library(
'vk/vk_print.c',
'vk/vk_state_creators.c',
'vk/vk_sync_objects.c',
'vk/vk_time.c',
),
include_directories: [
xrt_include,

View file

@ -1234,6 +1234,37 @@ vk_create_timeline_semaphore_from_native(struct vk_bundle *vk, xrt_graphics_sync
#endif
/*
*
* Time function(s), in the vk_time.c file.
*
*/
#ifdef VK_EXT_calibrated_timestamps
/*!
* Convert timestamps in GPU ticks (as return by VkQueryPool timestamp queries)
* into host CPU nanoseconds, same time domain as @ref os_monotonic_get_ns.
*
* Note the timestamp needs to be in the past and not to old, this is because
* not all GPU has full 64 bit timer resolution. For instance a Intel GPU "only"
* have 36 bits of valid timestamp and a tick period 83.3333 nanosecond,
* equating to an epoch of 5726 seconds before overflowing. The functio can
* handle overflows happening between the given timestamps and when it is called
* but only for one such epoch overflow, any more will only be treated as one
* such overflow. So timestamps needs to be converted resonably soon after they
* have been captured.
*
* @param vk The Vulkan bundle.
* @param count Number of timestamps to be converted.
* @parma in_out_timestamps Array of timestamps to be converted, done in place.
*
* @ingroup aux_vk
*/
XRT_CHECK_RESULT VkResult
vk_convert_timestamps_to_host_ns(struct vk_bundle *vk, uint32_t count, uint64_t *in_out_timestamps);
#endif
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,148 @@
// Copyright 2022, Collabora, Ltd.
// SPDX-License-Identifier: BSL-1.0
/*!
* @file
* @brief Vulkan timestamp helpers.
*
* @author Jakob Bornecrantz <jakob@collabora.com>
* @ingroup aux_vk
*/
#include "os/os_time.h"
#include "math/m_mathinclude.h"
#include "vk/vk_helpers.h"
#ifdef VK_EXT_calibrated_timestamps
/*
*
* Helper(s)
*
*/
uint64_t
from_host_ticks_to_host_ns(uint64_t ticks)
{
#if defined(XRT_OS_LINUX)
// No-op on Linux.
return ticks;
#elif defined(XRT_OS_WINDOWS)
static int64_t ns_per_qpc_tick = 0;
if (ns_per_qpc_tick == 0) {
// Fixed at startup, so we can cache this.
LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
ns_per_qpc_tick = U_1_000_000_000 / freq.QuadPart;
}
return ticks / ns_per_qpc_tick;
#else
#error "Vulkan timestamp domain needs porting"
#endif
}
/*
*
* 'Exported' function(s).
*
*/
VkResult
vk_convert_timestamps_to_host_ns(struct vk_bundle *vk, uint32_t count, uint64_t *in_out_timestamps)
{
VkResult ret;
if (!vk->has_EXT_calibrated_timestamps) {
VK_ERROR(vk, "VK_EXT_calibrated_timestamps not enabled");
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
#if defined(XRT_OS_LINUX)
static const VkTimeDomainEXT cpu_time_domain = VK_TIME_DOMAIN_CLOCK_MONOTONIC_EXT;
#elif defined(XRT_OS_WINDOWS)
static const VkTimeDomainEXT cpu_time_domain = VK_TIME_DOMAIN_QUERY_PERFORMANCE_COUNTER_EXT;
#else
#error "Vulkan timestamp domain needs porting"
#endif
// Will always be the same, can be static.
static const VkCalibratedTimestampInfoEXT timestamp_info[2] = {
{
.sType = VK_STRUCTURE_TYPE_CALIBRATED_TIMESTAMP_INFO_EXT,
.pNext = NULL,
.timeDomain = VK_TIME_DOMAIN_DEVICE_EXT,
},
{
.sType = VK_STRUCTURE_TYPE_CALIBRATED_TIMESTAMP_INFO_EXT,
.pNext = NULL,
.timeDomain = cpu_time_domain,
},
};
assert(vk->vkGetCalibratedTimestampsEXT != NULL);
uint64_t timestamps[2];
uint64_t max_deviation;
ret = vk->vkGetCalibratedTimestampsEXT( //
vk->device, // device
2, // timestampCount
timestamp_info, // pTimestampInfos
timestamps, // pTimestamps
&max_deviation); // pMaxDeviation
if (ret != VK_SUCCESS) {
VK_ERROR(vk, "vkGetCalibratedTimestampsEXT: %s", vk_result_string(ret));
return ret;
}
uint64_t now_ticks = timestamps[0];
uint64_t now_ns = from_host_ticks_to_host_ns(timestamps[1]);
// Yupp it's floating point.
double period = (double)vk->features.timestamp_period;
uint64_t shift = vk->features.timestamp_valid_bits;
for (uint32_t i = 0; i < count; i++) {
// Read the GPU domain timestamp.
uint64_t timestamp_gpu_ticks = in_out_timestamps[i];
// The GPU timestamp has rolled over, move now to new epoch.
if (timestamp_gpu_ticks > now_ticks) {
/*
* If there are 64 bits of timestamps data then this
* code doesn't work, but we shouldn't be here then.
*/
assert(vk->features.timestamp_valid_bits < 64);
now_ticks += (uint64_t)1 << shift;
}
assert(now_ticks > timestamp_gpu_ticks);
/*
* Since these two timestamps should be close to each other and
* therefore a small value a double floating point value should
* be able to hold the value "safely".
*/
double diff_ticks_f = (double)(now_ticks - timestamp_gpu_ticks);
// Convert into nanoseconds.
int64_t diff_ns = (int64_t)floor(diff_ticks_f * period + 0.5);
assert(diff_ns > 0);
// And with the diff we can get the timestamp.
uint64_t timestamp_ns = now_ns - (uint64_t)diff_ns;
// Write out the result.
in_out_timestamps[i] = timestamp_ns;
}
return VK_SUCCESS;
}
#endif /* VK_EXT_calibrated_timestamps */