This change updates the present timing implementation with several fixes and improvements for better spec compliance:

* Updated present timing logic and corrected macros to match Vulkan specification values.
* Added support for vkGetPhysicalDeviceCalibrateableTimeDomainsKHR to include stage-local time domains.
* Updated handling of VK_TIME_DOMAIN_DEVICE_KHR to return timestamps in nanoseconds.
* Modified vkGetPastPresentationTimingEXT to allow retrieval of records without requiring presentIds.
* Improved vkQueuePresentKHR to terminate early when the present timing queue is full.
* Fixed VkPastPresentationTimingEXT::presentStageCount to correctly report the number of stages containing definitive results.

Signed-off-by: Ginu Jacob ginu.jacob@arm.com
This commit is contained in:
Ginu Jacob 2025-11-25 10:12:33 +00:00 committed by Iason Paraskevopoulos
parent 72a21a9e87
commit 71a0881d5c
13 changed files with 311 additions and 140 deletions

View file

@ -38,14 +38,15 @@ wsi_layer_vkGetCalibratedTimestampsKHR(VkDevice device, uint32_t timestampCount,
uint64_t *pMaxDeviation) VWL_API_POST
{
auto &device_data = layer::device_private_data::get(device);
struct stage_local_index_and_offset
struct stage_local_index_domain_offset
{
uint32_t index;
uint64_t calibration_offset;
uint64_t domain;
};
util::vector<VkCalibratedTimestampInfoKHR> time_stamp_info{ util::allocator(device_data.get_allocator(),
VK_SYSTEM_ALLOCATION_SCOPE_OBJECT) };
util::vector<stage_local_index_and_offset> calibration_index_and_offset{ util::allocator(
util::vector<stage_local_index_domain_offset> calibration_index_domain_offset{ util::allocator(
device_data.get_allocator(), VK_SYSTEM_ALLOCATION_SCOPE_OBJECT) };
for (uint32_t i = 0; i < timestampCount; ++i)
@ -62,8 +63,12 @@ wsi_layer_vkGetCalibratedTimestampsKHR(VkDevice device, uint32_t timestampCount,
/* The layer is only handling for present stage local time domains,
every other time domains including swapchain local (VK_TIME_DOMAIN_SWAPCHAIN_LOCAL_EXT)
is not handled by the layer. */
if ((ext != nullptr) && (pTimestampInfos[i].timeDomain == VK_TIME_DOMAIN_PRESENT_STAGE_LOCAL_EXT))
if (pTimestampInfos[i].timeDomain == VK_TIME_DOMAIN_PRESENT_STAGE_LOCAL_EXT)
{
/* If timeDomain is VK_TIME_DOMAIN_SWAPCHAIN_LOCAL_EXT or VK_TIME_DOMAIN_PRESENT_STAGE_LOCAL_EXT,
* the pNext chain must include a VkSwapchainCalibratedTimestampInfoEXT structure.
*/
assert(ext != nullptr);
assert(ext->swapchain != VK_NULL_HANDLE);
if (!device_data.layer_owns_swapchain(ext->swapchain))
{
@ -76,9 +81,10 @@ wsi_layer_vkGetCalibratedTimestampsKHR(VkDevice device, uint32_t timestampCount,
wsi::swapchain_calibrated_time calibrated_time;
TRY_LOG_CALL(present_timing_extension->get_swapchain_time_domains().calibrate(
static_cast<VkPresentStageFlagBitsEXT>(ext->presentStage), &calibrated_time));
stage_local_index_and_offset index_and_offset = { i, calibrated_time.offset };
stage_local_index_domain_offset index_domain_offset = { i, calibrated_time.offset,
calibrated_time.time_domain };
if (!calibration_index_and_offset.try_push_back(index_and_offset))
if (!calibration_index_domain_offset.try_push_back(index_domain_offset))
{
return VK_ERROR_OUT_OF_HOST_MEMORY;
}
@ -88,10 +94,21 @@ wsi_layer_vkGetCalibratedTimestampsKHR(VkDevice device, uint32_t timestampCount,
TRY_LOG_CALL(device_data.disp.GetCalibratedTimestampsKHR(device, timestampCount, &time_stamp_info[0], pTimestamps,
pMaxDeviation));
/* Loop through the calibration_index_and_offset vector and update the timestamps that are stage local
/* Loop through the calibration_index_domain_offset vector and update the timestamps that are stage local
with its respective offset. */
for (const auto &iter : calibration_index_and_offset)
for (const auto &iter : calibration_index_domain_offset)
{
/* For device domain, convert ticks to nanoseconds */
if (iter.domain == VK_TIME_DOMAIN_DEVICE_KHR)
{
VkPhysicalDeviceProperties2KHR physical_device_properties{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR,
nullptr,
{} };
device_data.instance_data.disp.GetPhysicalDeviceProperties2KHR(device_data.physical_device,
&physical_device_properties);
pTimestamps[iter.index] =
wsi::ticks_to_ns(pTimestamps[iter.index], physical_device_properties.properties.limits.timestampPeriod);
}
pTimestamps[iter.index] += iter.calibration_offset;
}
return VK_SUCCESS;
@ -104,4 +121,56 @@ wsi_layer_vkGetCalibratedTimestampsEXT(VkDevice device, uint32_t timestampCount,
{
return wsi_layer_vkGetCalibratedTimestampsKHR(device, timestampCount, pTimestampInfos, pTimestamps, pMaxDeviation);
}
VWL_VKAPI_CALL(VkResult)
wsi_layer_vkGetPhysicalDeviceCalibrateableTimeDomainsKHR(VkPhysicalDevice physicalDevice, uint32_t *pTimeDomainCount,
VkTimeDomainKHR *pTimeDomains) VWL_API_POST
{
auto &instance_data = layer::instance_private_data::get(physicalDevice);
assert(pTimeDomainCount != nullptr);
VkSwapchainTimeDomainPropertiesEXT swapchain_time_domain_properties = {
VK_STRUCTURE_TYPE_SWAPCHAIN_TIME_DOMAIN_PROPERTIES_EXT, nullptr, 0, nullptr, nullptr
};
uint32_t requested_domain_count = *pTimeDomainCount;
TRY(instance_data.disp.GetPhysicalDeviceCalibrateableTimeDomainsKHR(physicalDevice, pTimeDomainCount, pTimeDomains));
auto present_timing_supported = wsi::present_timing_dependencies_supported(physicalDevice);
if (std::holds_alternative<VkResult>(present_timing_supported))
{
/* Return error code */
return std::get<VkResult>(present_timing_supported);
}
if (!std::get<bool>(present_timing_supported))
{
/* Present timing dependencies not supported */
return VK_SUCCESS;
}
if (pTimeDomains == nullptr)
{
TRY(
wsi::swapchain_time_domains::get_swapchain_time_domain_properties(&swapchain_time_domain_properties, nullptr));
*pTimeDomainCount += swapchain_time_domain_properties.timeDomainCount;
return VK_SUCCESS;
}
if (requested_domain_count == *pTimeDomainCount)
{
return VK_INCOMPLETE;
}
swapchain_time_domain_properties.pTimeDomains = &pTimeDomains[*pTimeDomainCount];
swapchain_time_domain_properties.timeDomainCount = requested_domain_count - *pTimeDomainCount;
VkResult result =
wsi::swapchain_time_domains::get_swapchain_time_domain_properties(&swapchain_time_domain_properties, nullptr);
if ((result == VK_SUCCESS) || (result == VK_INCOMPLETE))
{
*pTimeDomainCount += swapchain_time_domain_properties.timeDomainCount;
}
return result;
}
VWL_VKAPI_CALL(VkResult)
wsi_layer_vkGetPhysicalDeviceCalibrateableTimeDomainsEXT(VkPhysicalDevice physicalDevice, uint32_t *pTimeDomainCount,
VkTimeDomainEXT *pTimeDomains) VWL_API_POST
{
return wsi_layer_vkGetPhysicalDeviceCalibrateableTimeDomainsKHR(physicalDevice, pTimeDomainCount, pTimeDomains);
}
#endif /* VULKAN_WSI_LAYER_EXPERIMENTAL */

View file

@ -42,4 +42,11 @@ VWL_VKAPI_CALL(VkResult)
wsi_layer_vkGetCalibratedTimestampsKHR(VkDevice device, uint32_t timestampCount,
const VkCalibratedTimestampInfoKHR *pTimestampInfos, uint64_t *pTimestamps,
uint64_t *pMaxDeviation) VWL_API_POST;
VWL_VKAPI_CALL(VkResult)
wsi_layer_vkGetPhysicalDeviceCalibrateableTimeDomainsKHR(VkPhysicalDevice physicalDevice, uint32_t *pTimeDomainCount,
VkTimeDomainKHR *pTimeDomains) VWL_API_POST;
VWL_VKAPI_CALL(VkResult)
wsi_layer_vkGetPhysicalDeviceCalibrateableTimeDomainsEXT(VkPhysicalDevice physicalDevice, uint32_t *pTimeDomainCount,
VkTimeDomainEXT *pTimeDomains) VWL_API_POST;
#endif

View file

@ -796,6 +796,10 @@ wsi_layer_vkGetInstanceProcAddr(VkInstance instance, const char *funcName) VWL_A
GET_PROC_ADDR(vkGetPhysicalDeviceSurfaceCapabilities2KHR);
GET_PROC_ADDR(vkGetPhysicalDeviceSurfaceFormats2KHR);
}
#if VULKAN_WSI_LAYER_EXPERIMENTAL
GET_PROC_ADDR(vkGetPhysicalDeviceCalibrateableTimeDomainsKHR);
GET_PROC_ADDR(vkGetPhysicalDeviceCalibrateableTimeDomainsEXT);
#endif
if (instance_data.is_instance_extension_enabled(VK_EXT_DISPLAY_SURFACE_COUNTER_EXTENSION_NAME))
{

View file

@ -40,20 +40,19 @@
#define VK_ERROR_PRESENT_TIMING_QUEUE_FULL_EXT ((VkResult)(-1000208000))
#define VK_TIME_DOMAIN_PRESENT_STAGE_LOCAL_EXT ((VkTimeDomainEXT)(1000208000))
#define VK_TIME_DOMAIN_SWAPCHAIN_LOCAL_EXT ((VkTimeDomainEXT)(1000208001))
#define VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_TIMING_FEATURES_EXT ((VkStructureType)1000208002)
#define VK_STRUCTURE_TYPE_PRESENT_TIMING_SURFACE_CAPABILITIES_EXT ((VkStructureType)1000208003)
#define VK_STRUCTURE_TYPE_SWAPCHAIN_TIMING_PROPERTIES_EXT ((VkStructureType)1000208004)
#define VK_STRUCTURE_TYPE_SWAPCHAIN_TIME_DOMAIN_PROPERTIES_EXT ((VkStructureType)1000208005)
#define VK_STRUCTURE_TYPE_SWAPCHAIN_CALIBRATED_TIMESTAMP_INFO_EXT ((VkStructureType)1000208006)
#define VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_TIMING_FEATURES_EXT ((VkStructureType)1000208000)
#define VK_STRUCTURE_TYPE_PRESENT_TIMING_SURFACE_CAPABILITIES_EXT ((VkStructureType)1000208008)
#define VK_STRUCTURE_TYPE_SWAPCHAIN_TIMING_PROPERTIES_EXT ((VkStructureType)1000208001)
#define VK_STRUCTURE_TYPE_SWAPCHAIN_TIME_DOMAIN_PROPERTIES_EXT ((VkStructureType)1000208002)
#define VK_STRUCTURE_TYPE_SWAPCHAIN_CALIBRATED_TIMESTAMP_INFO_EXT ((VkStructureType)1000208009)
#define VK_STRUCTURE_TYPE_PAST_PRESENTATION_TIMING_EXT ((VkStructureType)1000208007)
#define VK_STRUCTURE_TYPE_PAST_PRESENTATION_TIMING_PROPERTIES_EXT ((VkStructureType)1000208008)
#define VK_STRUCTURE_TYPE_PAST_PRESENTATION_TIMING_INFO_EXT ((VkStructureType)1000208009)
#define VK_STRUCTURE_TYPE_PRESENT_TIMING_INFO_EXT ((VkStructureType)1000208010)
#define VK_STRUCTURE_TYPE_PRESENT_TIMINGS_INFO_EXT ((VkStructureType)1000208011)
#define VK_STRUCTURE_TYPE_PAST_PRESENTATION_TIMING_PROPERTIES_EXT ((VkStructureType)1000208006)
#define VK_STRUCTURE_TYPE_PAST_PRESENTATION_TIMING_INFO_EXT ((VkStructureType)1000208005)
#define VK_STRUCTURE_TYPE_PRESENT_TIMING_INFO_EXT ((VkStructureType)1000208004)
#define VK_STRUCTURE_TYPE_PRESENT_TIMINGS_INFO_EXT ((VkStructureType)1000208003)
/* Placeholder. Need to get the real value. */
#define VK_SWAPCHAIN_CREATE_PRESENT_TIMING_BIT_EXT ((VkSwapchainCreateFlagsKHR)0x00010000)
#define VK_SWAPCHAIN_CREATE_PRESENT_TIMING_BIT_EXT ((VkSwapchainCreateFlagsKHR)0x00000200)
typedef VkFlags VkPresentStageFlagsEXT;
typedef VkFlags VkPresentTimingInfoFlagsEXT;

View file

@ -29,7 +29,6 @@
*/
#include <array>
#include <cassert>
#include <cmath>
#include <wsi/swapchain_base.hpp>
#include <util/helpers.hpp>
@ -114,15 +113,6 @@ VkResult wsi_ext_present_timing::get_pixel_out_timing_to_queue(
return VK_SUCCESS;
}
static inline uint64_t ticks_to_ns(uint64_t ticks, const float &timestamp_period)
{
/* timestamp_period is float (ns per tick). Use double so we keep
52-bit integer precision (4.5×10¹ ticks) without overflow. */
assert(std::isfinite(timestamp_period) && timestamp_period > 0.0f);
double ns = static_cast<double>(ticks) * static_cast<double>(timestamp_period);
return static_cast<uint64_t>(std::llround(ns));
}
swapchain_presentation_timing *wsi_ext_present_timing::get_pending_stage_timing(uint32_t image_index,
VkPresentStageFlagBitsEXT stage)
{
@ -268,7 +258,7 @@ VkResult wsi_ext_present_timing::add_presentation_query_entry(VkQueue queue, uin
return VK_ERROR_PRESENT_TIMING_QUEUE_FULL_EXT;
}
wsi::swapchain_presentation_entry presentation_entry(target_time, present_stage_queries, present_id, image_index,
m_device.get_best_queue_family_index());
m_device.get_best_queue_family_index(), stages_supported());
if (!m_queue.try_push_back(std::move(presentation_entry)))
{
return VK_ERROR_OUT_OF_HOST_MEMORY;
@ -314,6 +304,17 @@ VkResult wsi_ext_present_timing::add_presentation_entry(VkQueue queue, uint64_t
return VK_SUCCESS;
}
VkResult wsi_ext_present_timing::queue_has_space()
{
const util::unique_lock<util::mutex> lock(m_queue_mutex);
if (!lock)
{
WSI_LOG_WARNING("Failed to acquire queue mutex while checking for space in the queue");
return VK_ERROR_UNKNOWN;
}
return (m_queue.size() == m_queue.capacity()) ? VK_ERROR_PRESENT_TIMING_QUEUE_FULL_EXT : VK_SUCCESS;
}
swapchain_time_domains &wsi_ext_present_timing::get_swapchain_time_domains()
{
return m_time_domains;
@ -360,46 +361,20 @@ VkResult wsi_ext_present_timing::get_past_presentation_results(
return VK_SUCCESS;
}
uint64_t timing_count = 0, removed_entries = 0;
VkPastPresentationTimingEXT *timings = past_present_timing_properties->pPresentationTimings;
bool seen_zero = false;
size_t last_zero_entry = 0;
uint64_t in = 0;
uint64_t out = 0;
uint64_t removed_entries = 0;
const bool allow_partial = (flags & VK_PAST_PRESENTATION_TIMING_ALLOW_PARTIAL_RESULTS_BIT_EXT) != 0;
const bool allow_out_of_order = (flags & VK_PAST_PRESENTATION_TIMING_ALLOW_OUT_OF_ORDER_RESULTS_BIT_EXT) != 0;
/*
* Single forward pass over the caller-supplied pPresentationTimings array:
*
* Locate the first matching presentation slot in `m_queue`.
*
* When a matching slot exists and at least one stage has available timings,
* copy its timestamps into the current entry. Valid results are compacted
* in-place by writing to the `out` cursor while `in` continues to scan,
* so gaps are skipped without repeated shifting.
*/
while (in < past_present_timing_properties->presentationTimingCount)
{
const uint64_t present_id = timings[in].presentId;
/*
* If presentId != 0, match the exact ID.
* If presentId == 0, pick the next unused zero-ID slot appearing
* after `last_zero_entry`, ensuring we never report the same slot twice.
*/
auto slot = std::find_if(m_queue.begin(), m_queue.end(), [&](const swapchain_presentation_entry &e) {
bool zero_extra_cond =
(present_id == 0 && seen_zero) ? (&e - m_queue.data()) > static_cast<ptrdiff_t>(last_zero_entry) : true;
return (e.m_present_id == present_id) && zero_extra_cond;
});
bool incomplete = false;
if (slot != m_queue.end())
for (uint64_t i = 0; (i - removed_entries) < m_queue.size(); ++i)
{
auto slot = m_queue.begin() + (i - removed_entries);
if (!slot->has_completed_stages(allow_partial))
{
if (allow_out_of_order)
{
in++;
continue;
}
else
@ -408,35 +383,22 @@ VkResult wsi_ext_present_timing::get_past_presentation_results(
}
}
if (present_id == 0)
if (timing_count < past_present_timing_properties->presentationTimingCount)
{
seen_zero = true;
last_zero_entry = std::distance(m_queue.begin(), slot);
}
slot->populate(timings[in]);
if (in != out)
{
timings[out] = timings[in];
}
++out;
if (timings[in].reportComplete)
slot->populate(timings[timing_count]);
if (timings[timing_count++].reportComplete)
{
m_queue.erase(slot);
removed_entries++;
}
}
++in;
else
{
/* There are available results but were not returned. */
incomplete = true;
}
past_present_timing_properties->presentationTimingCount = out;
const bool incomplete = (out < in) || (out < (get_num_available_results(flags) + removed_entries));
}
past_present_timing_properties->presentationTimingCount = timing_count;
return incomplete ? VK_INCOMPLETE : VK_SUCCESS;
}
@ -472,7 +434,8 @@ VkResult wsi_ext_present_timing::physical_device_has_supported_queue_family(VkPh
swapchain_presentation_entry::swapchain_presentation_entry(uint64_t target_time,
VkPresentStageFlagsEXT present_stage_queries,
uint64_t present_id, uint32_t image_index,
uint32_t queue_family)
uint32_t queue_family,
VkPresentStageFlagsEXT stages_supported)
: m_target_time(target_time)
, m_target_stages(0)
, m_present_id(present_id)
@ -482,22 +445,25 @@ swapchain_presentation_entry::swapchain_presentation_entry(uint64_t target_time,
{
if (present_stage_queries & VK_PRESENT_STAGE_QUEUE_OPERATIONS_END_BIT_EXT)
{
m_queue_end_timing = swapchain_presentation_timing();
m_queue_end_timing =
swapchain_presentation_timing(stages_supported & VK_PRESENT_STAGE_QUEUE_OPERATIONS_END_BIT_EXT);
m_num_present_stages++;
}
if (present_stage_queries & VK_PRESENT_STAGE_REQUEST_DEQUEUED_BIT_EXT)
{
m_latch_timing = swapchain_presentation_timing();
m_latch_timing = swapchain_presentation_timing(stages_supported & VK_PRESENT_STAGE_REQUEST_DEQUEUED_BIT_EXT);
m_num_present_stages++;
}
if (present_stage_queries & VK_PRESENT_STAGE_IMAGE_FIRST_PIXEL_OUT_BIT_EXT)
{
m_first_pixel_out_timing = swapchain_presentation_timing();
m_first_pixel_out_timing =
swapchain_presentation_timing(stages_supported & VK_PRESENT_STAGE_IMAGE_FIRST_PIXEL_OUT_BIT_EXT);
m_num_present_stages++;
}
if (present_stage_queries & VK_PRESENT_STAGE_IMAGE_FIRST_PIXEL_VISIBLE_BIT_EXT)
{
m_first_pixel_visible_timing = swapchain_presentation_timing();
m_first_pixel_visible_timing =
swapchain_presentation_timing(stages_supported & VK_PRESENT_STAGE_IMAGE_FIRST_PIXEL_VISIBLE_BIT_EXT);
m_num_present_stages++;
}
}
@ -615,6 +581,8 @@ void swapchain_presentation_entry::populate(VkPastPresentationTimingEXT &timing)
timing.reportComplete = !has_outstanding_stages();
timing.targetTime = m_target_time;
}
/* Set how many stages have definitive results */
timing.presentStageCount = stage_index;
}
VkResult swapchain_time_domains::calibrate(VkPresentStageFlagBitsEXT present_stage,
@ -632,6 +600,15 @@ VkResult swapchain_time_domains::calibrate(VkPresentStageFlagBitsEXT present_sta
return VK_ERROR_OUT_OF_HOST_MEMORY;
}
bool swapchain_time_domains::add_time_domain(util::unique_ptr<swapchain_time_domain> time_domain)
{
if (time_domain)
{
return m_time_domains.try_push_back(std::move(time_domain));
}
return false;
}
VkResult swapchain_time_domains::get_swapchain_time_domain_properties(
VkSwapchainTimeDomainPropertiesEXT *pSwapchainTimeDomainProperties, uint64_t *pTimeDomainsCounter)
{
@ -653,23 +630,19 @@ VkResult swapchain_time_domains::get_swapchain_time_domain_properties(
const uint32_t requested_domains_count = pSwapchainTimeDomainProperties->timeDomainCount;
const uint32_t domains_count_to_write = std::min(requested_domains_count, available_domains_count);
if (pSwapchainTimeDomainProperties->pTimeDomains != nullptr)
{
pSwapchainTimeDomainProperties->pTimeDomains[0] = VK_TIME_DOMAIN_PRESENT_STAGE_LOCAL_EXT;
}
if (pSwapchainTimeDomainProperties->pTimeDomainIds != nullptr)
{
pSwapchainTimeDomainProperties->pTimeDomainIds[0] = 0;
}
pSwapchainTimeDomainProperties->timeDomainCount = domains_count_to_write;
return (domains_count_to_write < available_domains_count) ? VK_INCOMPLETE : VK_SUCCESS;
}
bool swapchain_time_domains::add_time_domain(util::unique_ptr<swapchain_time_domain> time_domain)
{
if (time_domain)
{
return m_time_domains.try_push_back(std::move(time_domain));
}
return false;
}
VkResult check_time_domain_support(VkPhysicalDevice physical_device, std::tuple<VkTimeDomainEXT, bool> *domains,
size_t domain_size)
{

View file

@ -47,6 +47,7 @@
#include <functional>
#include <cassert>
#include <variant>
#include <cmath>
#include "wsi_extension.hpp"
@ -79,7 +80,15 @@ struct swapchain_presentation_timing
*/
bool m_set{};
swapchain_presentation_timing()
/**
* For unsupported present stages (i.e. when stage_supported = false), the m_set is
* set to true. This allows the application to request stages that are not supported
* by the backend and get a response of zero.
*/
swapchain_presentation_timing(bool stage_supported)
: m_timedomain_id(0)
, m_time(0)
, m_set(!stage_supported)
{
}
@ -149,7 +158,7 @@ struct swapchain_presentation_entry
std::optional<swapchain_presentation_timing> m_first_pixel_visible_timing;
swapchain_presentation_entry(uint64_t target_time, VkPresentStageFlagsEXT present_stage_queries, uint64_t present_id,
uint32_t image_index, uint32_t queue_family);
uint32_t image_index, uint32_t queue_family, VkPresentStageFlagsEXT stages_supported);
swapchain_presentation_entry(swapchain_presentation_entry &&) noexcept = default;
swapchain_presentation_entry &operator=(swapchain_presentation_entry &&) noexcept = default;
@ -300,8 +309,8 @@ public:
*
* @return Returns VK_SUCCESS on success, otherwise an appropriate error code.
*/
VkResult get_swapchain_time_domain_properties(VkSwapchainTimeDomainPropertiesEXT *pSwapchainTimeDomainProperties,
uint64_t *pTimeDomainsCounter);
static VkResult get_swapchain_time_domain_properties(
VkSwapchainTimeDomainPropertiesEXT *pSwapchainTimeDomainProperties, uint64_t *pTimeDomainsCounter);
private:
util::vector<util::unique_ptr<swapchain_time_domain>> m_time_domains;
@ -534,6 +543,13 @@ public:
VkResult add_presentation_entry(VkQueue queue, uint64_t present_id, uint32_t image_index,
const VkPresentTimingInfoEXT &timing_info);
/**
* @brief Check whether the queue has space for an entry.
*
* @return VK_SUCCESS when the there is space left in the queue, error otherwise.
*/
VkResult queue_has_space();
/**
* @brief Set the time for a stage, if it exists and is pending.
*
@ -623,6 +639,13 @@ public:
*/
static VkResult physical_device_has_supported_queue_family(VkPhysicalDevice physical_device, bool &out);
/**
* @brief This function is used to check the present timing stages that are supported. It can be overriden by specific backends.
*
* @return The stages that are supported.
*/
virtual VkPresentStageFlagsEXT stages_supported() = 0;
protected:
/**
* @brief User provided memory allocation callbacks.
@ -782,6 +805,44 @@ private:
VkResult check_time_domain_support(VkPhysicalDevice physical_device, std::tuple<VkTimeDomainEXT, bool> *domains,
size_t domain_size);
/**
* @brief Checks whether present timing dependencies are supported on a given physical device.
*
* This function queries the list of available device extensions for the specified
* Vulkan physical device and determines whether the `VK_KHR_maintenance9` extension
* is supported. If the extension is found, it further queries the device features
* to check if the `maintenance9` feature is enabled.
*
* @param physical_device Physical device used for the query
*
* @return std::variant<bool, VkResult>
* - `true` if the device supports present timing dependencies
* (i.e., `VK_KHR_maintenance9` extension and its feature are available).
* - `false` if the device does not support the `VK_KHR_maintenance9` extension or its feature.
* - `VkResult` (e.g., `VK_ERROR_OUT_OF_HOST_MEMORY`) if an error occurs during property enumeration.
*/
std::variant<bool, VkResult> present_timing_dependencies_supported(VkPhysicalDevice physical_device);
/**
* @brief Converts a hardware tick count to nanoseconds.
*
* This function converts a given number of GPU hardware timestamp ticks
* into nanoseconds using the provided timestamp period.
*
* @param ticks The number of timestamp ticks to convert.
* @param timestamp_period The duration of a single tick, in nanoseconds per tick.
*
* @return The equivalent time duration in nanoseconds.
*
*/
inline uint64_t ticks_to_ns(uint64_t ticks, const float &timestamp_period)
{
/* timestamp_period is float (ns per tick). Use double so we keep
52-bit integer precision (4.5×10¹ ticks) without overflow. */
assert(std::isfinite(timestamp_period) && timestamp_period > 0.0f);
double ns = static_cast<double>(ticks) * static_cast<double>(timestamp_period);
return static_cast<uint64_t>(std::llround(ns));
}
} /* namespace wsi */
#endif

View file

@ -163,4 +163,13 @@ void wsi_ext_present_timing_headless::set_first_pixel_visible_timestamp_for_last
{
m_first_pixel_visible_timestamp_for_last_image = timestamp;
}
VkPresentStageFlagsEXT wsi_ext_present_timing_headless::stages_supported()
{
VkPresentStageFlagsEXT stages =
VK_PRESENT_STAGE_QUEUE_OPERATIONS_END_BIT_EXT | VK_PRESENT_STAGE_REQUEST_DEQUEUED_BIT_EXT |
VK_PRESENT_STAGE_IMAGE_FIRST_PIXEL_OUT_BIT_EXT | VK_PRESENT_STAGE_IMAGE_FIRST_PIXEL_VISIBLE_BIT_EXT;
return stages;
}
#endif

View file

@ -80,6 +80,13 @@ public:
*/
void set_first_pixel_visible_timestamp_for_last_image(uint64_t timestamp);
/*
* @brief The stages that are supported by the headless backend.
*
* @return A bitmask of supported presentation stages.
*/
VkPresentStageFlagsEXT stages_supported() override;
private:
wsi_ext_present_timing_headless(const util::allocator &allocator, VkDevice device, uint32_t num_images,
std::optional<VkTimeDomainEXT> monotonic_domain);

View file

@ -627,6 +627,21 @@ VkResult swapchain_base::notify_presentation_engine(const pending_present_reques
VkResult swapchain_base::queue_present(VkQueue queue, const VkPresentInfoKHR *present_info,
const swapchain_presentation_parameters &submit_info)
{
#if VULKAN_WSI_LAYER_EXPERIMENTAL
const VkPresentTimingInfoEXT *present_timing_info = nullptr;
const auto *present_timings_info =
util::find_extension<VkPresentTimingsInfoEXT>(VK_STRUCTURE_TYPE_PRESENT_TIMINGS_INFO_EXT, present_info->pNext);
if (present_timings_info != nullptr)
{
present_timing_info = present_timings_info->pTimingInfos;
assert(present_timing_info != nullptr);
auto *ext_present_timing = get_swapchain_extension<wsi::wsi_ext_present_timing>(true);
if (present_timing_info->presentStageQueries)
{
TRY(ext_present_timing->queue_has_space());
}
}
#endif
if (submit_info.switch_presentation_mode)
{
/* Assert when a presentation mode switch is requested and the swapchain_maintenance1 extension which implements this is not available */
@ -674,20 +689,13 @@ VkResult swapchain_base::queue_present(VkQueue queue, const VkPresentInfoKHR *pr
m_swapchain_images[submit_info.pending_present.image_index].present_fence_wait;
}
#if VULKAN_WSI_LAYER_EXPERIMENTAL
const VkPresentTimingInfoEXT *present_timing_info = nullptr;
const auto *present_timings_info =
util::find_extension<VkPresentTimingsInfoEXT>(VK_STRUCTURE_TYPE_PRESENT_TIMINGS_INFO_EXT, present_info->pNext);
if (present_timings_info != nullptr)
{
present_timing_info = present_timings_info->pTimingInfos;
assert(present_timing_info != nullptr);
if (present_timing_info->presentStageQueries & VK_PRESENT_STAGE_QUEUE_OPERATIONS_END_BIT_EXT)
if ((present_timing_info != nullptr) &&
(present_timing_info->presentStageQueries & VK_PRESENT_STAGE_QUEUE_OPERATIONS_END_BIT_EXT))
{
auto *ext_present_timing = get_swapchain_extension<wsi::wsi_ext_present_timing>(true);
signal_semaphores[count_signal_semaphores++] =
ext_present_timing->get_image_present_semaphore(submit_info.pending_present.image_index);
}
}
#endif
queue_submit_semaphores semaphores = {
wait_semaphores,

View file

@ -38,9 +38,10 @@ namespace wayland
{
wsi_ext_present_timing_wayland::wsi_ext_present_timing_wayland(
const util::allocator &allocator, VkDevice device, uint32_t num_images,
util::vector<std::optional<uint64_t>> &&timestamp_first_pixel_out_storage)
util::vector<std::optional<uint64_t>> &&timestamp_first_pixel_out_storage, bool stage_first_pixel_out_supported)
: wsi_ext_present_timing(allocator, device, num_images)
, m_timestamp_first_pixel_out(allocator)
, m_stage_first_pixel_out_supported(stage_first_pixel_out_supported)
{
m_timestamp_first_pixel_out.swap(timestamp_first_pixel_out_storage);
}
@ -57,6 +58,8 @@ util::unique_ptr<wsi_ext_present_timing_wayland> wsi_ext_present_timing_wayland:
return nullptr;
}
util::vector<std::optional<uint64_t>> timestamp_first_pixel_out_storage(allocator);
bool stage_first_pixel_out_supported = false;
if (image_first_pixel_out_time_domain.has_value())
{
std::tuple<VkTimeDomainEXT, bool> monotonic_query = { *image_first_pixel_out_time_domain, false };
@ -75,16 +78,22 @@ util::unique_ptr<wsi_ext_present_timing_wayland> wsi_ext_present_timing_wayland:
{
return nullptr;
}
}
}
util::vector<std::optional<uint64_t>> timestamp_first_pixel_out_storage(allocator);
if (!timestamp_first_pixel_out_storage.try_resize(num_images))
{
return nullptr;
}
stage_first_pixel_out_supported = true;
}
}
else
{
timestamp_first_pixel_out_storage.clear();
timestamp_first_pixel_out_storage.shrink_to_fit();
}
return wsi_ext_present_timing::create<wsi_ext_present_timing_wayland>(
allocator, domains.data(), domains.size(), device, num_images, std::move(timestamp_first_pixel_out_storage));
allocator, domains.data(), domains.size(), device, num_images, std::move(timestamp_first_pixel_out_storage),
stage_first_pixel_out_supported);
}
VkResult wsi_ext_present_timing_wayland::get_swapchain_timing_properties(
@ -113,7 +122,7 @@ void wsi_ext_present_timing_wayland::mark_buffer_release(uint32_t image_index)
}
presentation_feedback *wsi_ext_present_timing_wayland::insert_into_pending_present_feedback_list(
uint32_t image_index, struct wp_presentation_feedback *feedback_obj)
uint32_t image_index, struct wp_presentation_feedback *feedback_obj, uint64_t id)
{
util::unique_lock<util::mutex> lock(m_pending_presents_lock);
@ -127,7 +136,7 @@ presentation_feedback *wsi_ext_present_timing_wayland::insert_into_pending_prese
* that would discard or mark the pending present request as completed which could be an inidcation of a bug somewhere. */
assert(!m_pending_presents[image_index].has_value());
m_pending_presents[image_index] = presentation_feedback(feedback_obj, this, image_index);
m_pending_presents[image_index] = presentation_feedback(feedback_obj, this, image_index, id);
return &m_pending_presents[image_index].value();
}
@ -191,5 +200,16 @@ void wsi_ext_present_timing_wayland::init(wl_display *display, struct wl_event_q
m_queue = queue;
}
VkPresentStageFlagsEXT wsi_ext_present_timing_wayland::stages_supported()
{
VkPresentStageFlagsEXT stages = VK_PRESENT_STAGE_QUEUE_OPERATIONS_END_BIT_EXT;
if (m_stage_first_pixel_out_supported)
{
stages |= VK_PRESENT_STAGE_IMAGE_FIRST_PIXEL_OUT_BIT_EXT;
}
return stages;
}
} // namespace wayland
} // namespace wsi

View file

@ -56,7 +56,7 @@ class wsi_ext_present_timing_wayland : public wsi::wsi_ext_present_timing
public:
static util::unique_ptr<wsi_ext_present_timing_wayland> create(
VkDevice device, const util::allocator &allocator,
std::optional<VkTimeDomainKHR> image_first_pixel_visible_time_domain, uint32_t num_images);
std::optional<VkTimeDomainKHR> image_first_pixel_out_time_domain, uint32_t num_images);
VkResult get_swapchain_timing_properties(uint64_t &timing_properties_counter,
VkSwapchainTimingPropertiesEXT &timing_properties) override;
@ -89,7 +89,8 @@ public:
* @return Pointer to the presentation feedback object in the pending presents list.
*/
presentation_feedback *insert_into_pending_present_feedback_list(uint32_t image_index,
struct wp_presentation_feedback *feedback_obj);
struct wp_presentation_feedback *feedback_obj,
uint64_t id);
/*
* @brief Copies the pixel out timestamp from the internal array to the present timing queue.
*
@ -112,6 +113,13 @@ public:
*/
void init(wl_display *display, struct wl_event_queue *queue);
/*
* @brief The stages that are supported by the wayland backend.
*
* @return A bitmask of supported presentation stages.
*/
VkPresentStageFlagsEXT stages_supported() override;
private:
/**
* @brief Mutex for synchronising accesses to the pending present id list.
@ -123,7 +131,8 @@ private:
*/
wsi_ext_present_timing_wayland(const util::allocator &allocator, VkDevice device, uint32_t num_images,
util::vector<std::optional<uint64_t>> &&timestamp_first_pixel_out_storage);
util::vector<std::optional<uint64_t>> &&timestamp_first_pixel_out_storage,
bool stage_first_pixel_out_supported);
/**
* @brief Updates the first pixel out timing in the internal array.
@ -152,6 +161,11 @@ private:
*/
util::vector<std::optional<uint64_t>> m_timestamp_first_pixel_out;
/**
* @brief Whether the first pixel out stage is supported.
*/
bool m_stage_first_pixel_out_supported;
/* Allow util::allocator to access the private constructor */
friend util::allocator;

View file

@ -140,22 +140,22 @@ VkResult swapchain::add_required_extensions(VkDevice device, const VkSwapchainCr
bool swapchain_support_enabled = swapchain_create_info->flags & VK_SWAPCHAIN_CREATE_PRESENT_TIMING_BIT_EXT;
if (swapchain_support_enabled)
{
std::optional<VkTimeDomainKHR> image_first_pixel_visible_time_domain;
std::optional<VkTimeDomainKHR> image_first_pixel_out_time_domain;
if (m_wsi_surface->get_presentation_time_interface() != nullptr)
{
switch (m_wsi_surface->clockid())
{
case CLOCK_MONOTONIC:
image_first_pixel_visible_time_domain = VK_TIME_DOMAIN_CLOCK_MONOTONIC_KHR;
image_first_pixel_out_time_domain = VK_TIME_DOMAIN_CLOCK_MONOTONIC_KHR;
break;
case CLOCK_MONOTONIC_RAW:
image_first_pixel_visible_time_domain = VK_TIME_DOMAIN_CLOCK_MONOTONIC_RAW_KHR;
image_first_pixel_out_time_domain = VK_TIME_DOMAIN_CLOCK_MONOTONIC_RAW_KHR;
break;
}
}
if (!add_swapchain_extension(wsi_ext_present_timing_wayland::create(
m_device, m_allocator, image_first_pixel_visible_time_domain, swapchain_create_info->minImageCount)))
m_device, m_allocator, image_first_pixel_out_time_domain, swapchain_create_info->minImageCount)))
{
return VK_ERROR_OUT_OF_HOST_MEMORY;
}
@ -645,8 +645,8 @@ void swapchain::present_image(const pending_present_request &pending_present)
wp_presentation *pres = m_wsi_surface->get_presentation_time_interface();
struct wp_presentation_feedback *feedback = wp_presentation_feedback(pres, m_wsi_surface->get_wl_surface());
wl_proxy_set_queue(reinterpret_cast<wl_proxy *>(feedback), m_buffer_queue);
presentation_feedback *feedback_obj =
present_timing_ext->insert_into_pending_present_feedback_list(pending_present.image_index, feedback);
presentation_feedback *feedback_obj = present_timing_ext->insert_into_pending_present_feedback_list(
pending_present.image_index, feedback, pending_present.present_id);
if (feedback_obj == nullptr)
{
WSI_LOG_ERROR("Error adding to pending present feedback list");

View file

@ -57,10 +57,10 @@ class presentation_feedback
{
public:
presentation_feedback(struct wp_presentation_feedback *feedback, wsi_ext_present_timing_wayland *ext_present_timing,
uint32_t image_index)
uint32_t image_index, uint64_t present_id)
: m_feedback(feedback)
, m_ext_present_id(nullptr)
, m_present_id(0)
, m_present_id(present_id)
, m_ext_present_timing(ext_present_timing)
, m_image_index(image_index)
{