From 71a0881d5cc5a248c3f6b94fe399d697fc367c83 Mon Sep 17 00:00:00 2001 From: Ginu Jacob Date: Tue, 25 Nov 2025 10:12:33 +0000 Subject: [PATCH] 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 --- layer/calibrated_timestamps_api.cpp | 83 +++++++++++-- layer/calibrated_timestamps_api.hpp | 7 ++ layer/layer.cpp | 4 + layer/wsi_layer_experimental.hpp | 21 ++-- wsi/extensions/present_timing.cpp | 149 ++++++++++------------- wsi/extensions/present_timing.hpp | 69 ++++++++++- wsi/headless/present_timing_handler.cpp | 9 ++ wsi/headless/present_timing_handler.hpp | 7 ++ wsi/swapchain_base.cpp | 32 +++-- wsi/wayland/present_timing_handler.cpp | 34 ++++-- wsi/wayland/present_timing_handler.hpp | 20 ++- wsi/wayland/swapchain.cpp | 12 +- wsi/wayland/wp_presentation_feedback.hpp | 4 +- 13 files changed, 311 insertions(+), 140 deletions(-) diff --git a/layer/calibrated_timestamps_api.cpp b/layer/calibrated_timestamps_api.cpp index 9573218..4ca5aa0 100644 --- a/layer/calibrated_timestamps_api.cpp +++ b/layer/calibrated_timestamps_api.cpp @@ -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 time_stamp_info{ util::allocator(device_data.get_allocator(), VK_SYSTEM_ALLOCATION_SCOPE_OBJECT) }; - util::vector calibration_index_and_offset{ util::allocator( + util::vector 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(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(present_timing_supported)) + { + /* Return error code */ + return std::get(present_timing_supported); + } + if (!std::get(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 */ diff --git a/layer/calibrated_timestamps_api.hpp b/layer/calibrated_timestamps_api.hpp index f31080e..fbe6e0e 100644 --- a/layer/calibrated_timestamps_api.hpp +++ b/layer/calibrated_timestamps_api.hpp @@ -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 diff --git a/layer/layer.cpp b/layer/layer.cpp index baa6a75..a1ab943 100644 --- a/layer/layer.cpp +++ b/layer/layer.cpp @@ -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)) { diff --git a/layer/wsi_layer_experimental.hpp b/layer/wsi_layer_experimental.hpp index 43f2b83..bcb751b 100644 --- a/layer/wsi_layer_experimental.hpp +++ b/layer/wsi_layer_experimental.hpp @@ -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; diff --git a/wsi/extensions/present_timing.cpp b/wsi/extensions/present_timing.cpp index 7fffb3b..cca320e 100644 --- a/wsi/extensions/present_timing.cpp +++ b/wsi/extensions/present_timing.cpp @@ -29,7 +29,6 @@ */ #include #include -#include #include #include @@ -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 ×tamp_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(ticks) * static_cast(timestamp_period); - return static_cast(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 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,83 +361,44 @@ 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) + bool incomplete = false; + + for (uint64_t i = 0; (i - removed_entries) < m_queue.size(); ++i) { - 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(last_zero_entry) : true; - return (e.m_present_id == present_id) && zero_extra_cond; - }); + auto slot = m_queue.begin() + (i - removed_entries); - if (slot != m_queue.end()) + if (!slot->has_completed_stages(allow_partial)) { - if (!slot->has_completed_stages(allow_partial)) + if (allow_out_of_order) { - if (allow_out_of_order) - { - in++; - continue; - } - else - { - break; - } + continue; } - - if (present_id == 0) + else { - seen_zero = true; - last_zero_entry = std::distance(m_queue.begin(), slot); + break; } + } - slot->populate(timings[in]); - - if (in != out) - { - timings[out] = timings[in]; - } - - ++out; - - if (timings[in].reportComplete) + if (timing_count < past_present_timing_properties->presentationTimingCount) + { + 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 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); - - pSwapchainTimeDomainProperties->pTimeDomains[0] = VK_TIME_DOMAIN_PRESENT_STAGE_LOCAL_EXT; - pSwapchainTimeDomainProperties->pTimeDomainIds[0] = 0; + 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 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 *domains, size_t domain_size) { diff --git a/wsi/extensions/present_timing.hpp b/wsi/extensions/present_timing.hpp index dc2e94e..674025b 100644 --- a/wsi/extensions/present_timing.hpp +++ b/wsi/extensions/present_timing.hpp @@ -47,6 +47,7 @@ #include #include #include +#include #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 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> 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 *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 + * - `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 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 ×tamp_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(ticks) * static_cast(timestamp_period); + return static_cast(std::llround(ns)); +} + } /* namespace wsi */ #endif diff --git a/wsi/headless/present_timing_handler.cpp b/wsi/headless/present_timing_handler.cpp index 3fe6ad8..c702c38 100644 --- a/wsi/headless/present_timing_handler.cpp +++ b/wsi/headless/present_timing_handler.cpp @@ -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 diff --git a/wsi/headless/present_timing_handler.hpp b/wsi/headless/present_timing_handler.hpp index e95a039..628e014 100644 --- a/wsi/headless/present_timing_handler.hpp +++ b/wsi/headless/present_timing_handler.hpp @@ -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 monotonic_domain); diff --git a/wsi/swapchain_base.cpp b/wsi/swapchain_base.cpp index f6d12d2..a06ca38 100644 --- a/wsi/swapchain_base.cpp +++ b/wsi/swapchain_base.cpp @@ -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(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(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,19 +689,12 @@ 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(VK_STRUCTURE_TYPE_PRESENT_TIMINGS_INFO_EXT, present_info->pNext); - if (present_timings_info != nullptr) + if ((present_timing_info != nullptr) && + (present_timing_info->presentStageQueries & VK_PRESENT_STAGE_QUEUE_OPERATIONS_END_BIT_EXT)) { - 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) - { - auto *ext_present_timing = get_swapchain_extension(true); - signal_semaphores[count_signal_semaphores++] = - ext_present_timing->get_image_present_semaphore(submit_info.pending_present.image_index); - } + auto *ext_present_timing = get_swapchain_extension(true); + signal_semaphores[count_signal_semaphores++] = + ext_present_timing->get_image_present_semaphore(submit_info.pending_present.image_index); } #endif queue_submit_semaphores semaphores = { diff --git a/wsi/wayland/present_timing_handler.cpp b/wsi/wayland/present_timing_handler.cpp index bace268..73ccd60 100644 --- a/wsi/wayland/present_timing_handler.cpp +++ b/wsi/wayland/present_timing_handler.cpp @@ -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> &×tamp_first_pixel_out_storage) + util::vector> &×tamp_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: return nullptr; } + util::vector> timestamp_first_pixel_out_storage(allocator); + bool stage_first_pixel_out_supported = false; if (image_first_pixel_out_time_domain.has_value()) { std::tuple monotonic_query = { *image_first_pixel_out_time_domain, false }; @@ -75,16 +78,22 @@ util::unique_ptr wsi_ext_present_timing_wayland: { return nullptr; } + if (!timestamp_first_pixel_out_storage.try_resize(num_images)) + { + return nullptr; + } + stage_first_pixel_out_supported = true; } } - util::vector> timestamp_first_pixel_out_storage(allocator); - if (!timestamp_first_pixel_out_storage.try_resize(num_images)) + else { - return nullptr; + timestamp_first_pixel_out_storage.clear(); + timestamp_first_pixel_out_storage.shrink_to_fit(); } return wsi_ext_present_timing::create( - 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 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 diff --git a/wsi/wayland/present_timing_handler.hpp b/wsi/wayland/present_timing_handler.hpp index b3ced7e..343c3ea 100644 --- a/wsi/wayland/present_timing_handler.hpp +++ b/wsi/wayland/present_timing_handler.hpp @@ -56,7 +56,7 @@ class wsi_ext_present_timing_wayland : public wsi::wsi_ext_present_timing public: static util::unique_ptr create( VkDevice device, const util::allocator &allocator, - std::optional image_first_pixel_visible_time_domain, uint32_t num_images); + std::optional 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> &×tamp_first_pixel_out_storage); + util::vector> &×tamp_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> 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; diff --git a/wsi/wayland/swapchain.cpp b/wsi/wayland/swapchain.cpp index ca04ff6..913bee9 100644 --- a/wsi/wayland/swapchain.cpp +++ b/wsi/wayland/swapchain.cpp @@ -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 image_first_pixel_visible_time_domain; + std::optional 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(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"); diff --git a/wsi/wayland/wp_presentation_feedback.hpp b/wsi/wayland/wp_presentation_feedback.hpp index 396d5d8..f71537a 100644 --- a/wsi/wayland/wp_presentation_feedback.hpp +++ b/wsi/wayland/wp_presentation_feedback.hpp @@ -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) {