Implement monotonic stage timestamps on headless

Sets timestamps for `vkGetPastPresentationTimingEXT` for the remaining
present stages on headless:

- VK_PRESENT_STAGE_IMAGE_LATCHED_BIT_EXT
- VK_PRESENT_STAGE_IMAGE_FIRST_PIXEL_OUT_BIT_EXT
- VK_PRESENT_STAGE_IMAGE_FIRST_PIXEL_VISIBLE_BIT_EXT

These are all set to the same value: when swapchain::present_image is
called. It uses the best monotonic clock supported by the driver.

No need for slot member variables to be atomic any more because they
are only accessed while a lock on m_queue_mutex is held.

Signed-off-by: Alex Bates <alex.bates@arm.com>
Change-Id: I90ffc876890ee74620b7ba8da753794cae51dd36
This commit is contained in:
Alex Bates 2025-07-18 11:39:41 +00:00 committed by Iason Paraskevopoulos
parent 24a77e8300
commit c43dd058ed
6 changed files with 138 additions and 57 deletions

View file

@ -62,6 +62,12 @@ wsi_ext_present_timing::wsi_ext_present_timing(const util::allocator &allocator,
, m_present_semaphore(allocator) , m_present_semaphore(allocator)
, m_timestamp_period(0.f) , m_timestamp_period(0.f)
{ {
if (layer::device_private_data::get(m_device).is_present_id_enabled())
{
WSI_LOG_ERROR(VK_EXT_PRESENT_TIMING_EXTENSION_NAME
" enabled but required extension " VK_KHR_PRESENT_ID_EXTENSION_NAME " is not enabled.");
}
VkPhysicalDeviceProperties2KHR physical_device_properties{}; VkPhysicalDeviceProperties2KHR physical_device_properties{};
physical_device_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR; physical_device_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR;
const auto &dev = layer::device_private_data::get(m_device); const auto &dev = layer::device_private_data::get(m_device);
@ -160,22 +166,28 @@ static inline uint64_t ticks_to_ns(uint64_t ticks, const float &timestamp_period
return static_cast<uint64_t>(std::llround(ns)); 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)
{
for (auto &entry : m_queue)
{
if (entry.m_image_index == image_index && entry.is_pending(stage))
{
return &entry.get_stage_timing(stage)->get();
}
}
return nullptr;
}
VkResult wsi_ext_present_timing::get_queue_end_timing_to_queue(uint32_t image_index) VkResult wsi_ext_present_timing::get_queue_end_timing_to_queue(uint32_t image_index)
{ {
for (auto &slot : m_queue) if (auto timing = get_pending_stage_timing(image_index, VK_PRESENT_STAGE_QUEUE_OPERATIONS_END_BIT_EXT))
{ {
if ((slot.m_image_index == image_index) && slot.is_pending(VK_PRESENT_STAGE_QUEUE_OPERATIONS_END_BIT_EXT)) uint64_t time;
{ const layer::device_private_data &device_data = layer::device_private_data::get(m_device);
uint64_t time; TRY(device_data.disp.GetQueryPoolResults(m_device, m_query_pool, image_index, 1, sizeof(time), &time, 0,
auto stage_timing_optional = slot.get_stage_timing(VK_PRESENT_STAGE_QUEUE_OPERATIONS_END_BIT_EXT); VK_QUERY_RESULT_64_BIT));
const layer::device_private_data &device_data = layer::device_private_data::get(m_device); timing->set_time(ticks_to_ns(time, m_timestamp_period));
TRY(device_data.disp.GetQueryPoolResults(m_device, m_query_pool, image_index, 1, sizeof(time), &time, 0,
VK_QUERY_RESULT_64_BIT));
stage_timing_optional->get().m_time.store(ticks_to_ns(time, m_timestamp_period));
stage_timing_optional->get().m_set.store(true, std::memory_order_release);
/* For an image index, there can only be one entry in the internal queue with pending results. */
break;
}
} }
return VK_SUCCESS; return VK_SUCCESS;
} }
@ -449,14 +461,13 @@ std::optional<bool> swapchain_presentation_entry::is_complete(VkPresentStageFlag
{ {
return std::nullopt; return std::nullopt;
} }
return stage_timing_optional->get().m_set.load(std::memory_order_relaxed); return stage_timing_optional->get().m_set;
} }
bool swapchain_presentation_entry::is_pending(VkPresentStageFlagBitsEXT stage) bool swapchain_presentation_entry::is_pending(VkPresentStageFlagBitsEXT stage)
{ {
auto stage_timing_optional = get_stage_timing(stage); auto stage_timing_optional = get_stage_timing(stage);
return stage_timing_optional.has_value() ? !stage_timing_optional->get().m_set.load(std::memory_order_relaxed) : return stage_timing_optional.has_value() ? !stage_timing_optional->get().m_set : false;
false;
} }
bool swapchain_presentation_entry::has_outstanding_stages() bool swapchain_presentation_entry::has_outstanding_stages()
@ -531,12 +542,11 @@ void swapchain_presentation_entry::populate(VkPastPresentationTimingEXT &timing)
continue; continue;
} }
if (stage_timing_optional->get().m_set.load(std::memory_order_acquire)) if (stage_timing_optional->get().m_set)
{ {
timing.timeDomainId = stage_timing_optional->get().m_timedomain_id; timing.timeDomainId = stage_timing_optional->get().m_timedomain_id;
timing.pPresentStages[stage_index].stage = stage; timing.pPresentStages[stage_index].stage = stage;
timing.pPresentStages[stage_index++].time = timing.pPresentStages[stage_index++].time = stage_timing_optional->get().m_time;
stage_timing_optional->get().m_time.load(std::memory_order_relaxed);
} }
} }

View file

@ -66,36 +66,29 @@ struct swapchain_presentation_timing
/** /**
* Timestamp for this entry. * Timestamp for this entry.
*/ */
std::atomic<uint64_t> m_time{}; uint64_t m_time{};
/** /**
* Needed to mark logically complete timings even for presentations that the WSI * Needed to mark logically complete timings even for presentations that the WSI
* implementation ultimately rejected (e.g. in MAILBOX the presentation engine * implementation ultimately rejected (e.g. in MAILBOX the presentation engine
* rejected the one present request) * rejected the one present request)
*/ */
std::atomic<bool> m_set{}; bool m_set{};
swapchain_presentation_timing() swapchain_presentation_timing()
{ {
} }
swapchain_presentation_timing(swapchain_presentation_timing &&rhs) noexcept swapchain_presentation_timing(swapchain_presentation_timing &&) noexcept = default;
{ swapchain_presentation_timing &operator=(swapchain_presentation_timing &&) noexcept = default;
m_timedomain_id = rhs.m_timedomain_id;
m_time.store(rhs.m_time.load());
m_set.store(rhs.m_set.load());
}
swapchain_presentation_timing &operator=(swapchain_presentation_timing &&rhs) noexcept
{
m_timedomain_id = rhs.m_timedomain_id;
m_time.store(rhs.m_time.load());
m_set.store(rhs.m_set.load());
return *this;
}
swapchain_presentation_timing(const swapchain_presentation_timing &) = delete; swapchain_presentation_timing(const swapchain_presentation_timing &) = delete;
swapchain_presentation_timing &operator=(const swapchain_presentation_timing &) = delete; swapchain_presentation_timing &operator=(const swapchain_presentation_timing &) = delete;
void set_time(uint64_t time)
{
m_time = time;
m_set = true;
}
}; };
/** /**
@ -299,12 +292,12 @@ public:
*/ */
WSI_DEFINE_EXTENSION(VK_EXT_PRESENT_TIMING_EXTENSION_NAME); WSI_DEFINE_EXTENSION(VK_EXT_PRESENT_TIMING_EXTENSION_NAME);
template <typename T> template <typename T, typename... arg_types>
static util::unique_ptr<T> create(const util::allocator &allocator, static util::unique_ptr<T> create(const util::allocator &allocator,
util::unique_ptr<wsi::vulkan_time_domain> *domains, size_t domain_count, util::unique_ptr<wsi::vulkan_time_domain> *domains, size_t domain_count,
VkDevice device, uint32_t num_images) VkDevice device, uint32_t num_images, arg_types &&...args)
{ {
auto present_timing = allocator.make_unique<T>(allocator, device, num_images); auto present_timing = allocator.make_unique<T>(allocator, device, num_images, std::forward<arg_types>(args)...);
for (size_t i = 0; i < domain_count; i++) for (size_t i = 0; i < domain_count; i++)
{ {
if (!present_timing->get_swapchain_time_domains().add_time_domain(std::move(domains[i]))) if (!present_timing->get_swapchain_time_domains().add_time_domain(std::move(domains[i])))
@ -364,6 +357,22 @@ public:
VkResult add_presentation_entry(const layer::device_private_data &device, VkQueue queue, uint64_t present_id, VkResult add_presentation_entry(const layer::device_private_data &device, VkQueue queue, uint64_t present_id,
uint32_t image_index, VkPresentStageFlagsEXT present_stage_queries); uint32_t image_index, VkPresentStageFlagsEXT present_stage_queries);
/**
* @brief Set the time for a stage, if it exists and is pending.
*
* @param image_index The index of the image in the present queue.
* @param stage The present stage to set the time for.
* @param time The time to set for the stage.
*/
void set_pending_stage_time(uint32_t image_index, VkPresentStageFlagBitsEXT stage, uint64_t time)
{
const std::lock_guard<std::mutex> lock(m_queue_mutex);
if (auto timing = get_pending_stage_timing(image_index, stage))
{
timing->set_time(time);
}
}
/** /**
* @brief Get the image's present semaphore. * @brief Get the image's present semaphore.
* *
@ -479,16 +488,33 @@ private:
*/ */
VkResult init_timing_resources(); VkResult init_timing_resources();
/**
* @pre Caller must hold m_queue_mutex for the call and lifetime of the returned pointer.
*
* @brief Search for a pending presentation entry and access its timing info.
*
* For an image index, there can only be one entry in the queue with pending stages.
* This does not take a present ID because zero is a valid, nonunique value and thus cannot uniquely identify an
* entry.
*
* @param image_index The index of the image in the present queue.
* @param stage The present stage to get the entry for.
*
* @return Pointer to timing information for the stage, or nullptr if it is not found or it is not pending.
*/
swapchain_presentation_timing *get_pending_stage_timing(uint32_t image_index, VkPresentStageFlagBitsEXT stage);
/** /**
* @pre Caller must hold m_queue_mutex. * @pre Caller must hold m_queue_mutex.
* *
* @brief Get the queue end timings for an image. * @brief Get the queue end timings for an image.
* *
* Gets the queue end timings for a swapchain image and stores it in the internal queue. * Gets the queue end timings for a swapchain image and stores it in the internal queue.
* If there is no pending entry for the image index, no-op.
* *
* @param image_index The index of the image in the swapchain. * @param image_index The index of the image in the swapchain.
* *
* @return VK_SUCCESS if the query is successful and error if otherwise. * @return VK_SUCCESS if the query is successful and error otherwise. VK_SUCCESS if no pending entry is found.
*/ */
VkResult get_queue_end_timing_to_queue(uint32_t image_index); VkResult get_queue_end_timing_to_queue(uint32_t image_index);

View file

@ -35,8 +35,10 @@
#include "layer/private_data.hpp" #include "layer/private_data.hpp"
wsi_ext_present_timing_headless::wsi_ext_present_timing_headless(const util::allocator &allocator, VkDevice device, wsi_ext_present_timing_headless::wsi_ext_present_timing_headless(const util::allocator &allocator, VkDevice device,
uint32_t num_images) uint32_t num_images,
std::optional<VkTimeDomainEXT> monotonic_domain)
: wsi::wsi_ext_present_timing(allocator, device, num_images) : wsi::wsi_ext_present_timing(allocator, device, num_images)
, m_monotonic_domain(monotonic_domain)
{ {
} }
@ -61,14 +63,12 @@ util::unique_ptr<wsi_ext_present_timing_headless> wsi_ext_present_timing_headles
return nullptr; return nullptr;
} }
VkTimeDomainEXT monotonic_domain_to_use = VK_TIME_DOMAIN_CLOCK_MONOTONIC_RAW_EXT; std::optional<VkTimeDomainEXT> monotonic_domain;
bool monotonic_time_domain_supported = false;
for (auto [domain, supported] : monotonic_domains) for (auto [domain, supported] : monotonic_domains)
{ {
monotonic_domain_to_use = domain;
if (supported) if (supported)
{ {
monotonic_time_domain_supported = true; monotonic_domain = domain;
break; break;
} }
} }
@ -80,27 +80,27 @@ util::unique_ptr<wsi_ext_present_timing_headless> wsi_ext_present_timing_headles
return nullptr; return nullptr;
} }
if (monotonic_time_domain_supported) if (monotonic_domain)
{ {
if (!domains.try_push_back(allocator.make_unique<wsi::vulkan_time_domain>(VK_PRESENT_STAGE_IMAGE_LATCHED_BIT_EXT, if (!domains.try_push_back(
monotonic_domain_to_use))) allocator.make_unique<wsi::vulkan_time_domain>(VK_PRESENT_STAGE_IMAGE_LATCHED_BIT_EXT, *monotonic_domain)))
{ {
return nullptr; return nullptr;
} }
if (!domains.try_push_back(allocator.make_unique<wsi::vulkan_time_domain>( if (!domains.try_push_back(allocator.make_unique<wsi::vulkan_time_domain>(
VK_PRESENT_STAGE_IMAGE_FIRST_PIXEL_OUT_BIT_EXT, monotonic_domain_to_use))) VK_PRESENT_STAGE_IMAGE_FIRST_PIXEL_OUT_BIT_EXT, *monotonic_domain)))
{ {
return nullptr; return nullptr;
} }
if (!domains.try_push_back(allocator.make_unique<wsi::vulkan_time_domain>( if (!domains.try_push_back(allocator.make_unique<wsi::vulkan_time_domain>(
VK_PRESENT_STAGE_IMAGE_FIRST_PIXEL_VISIBLE_BIT_EXT, monotonic_domain_to_use))) VK_PRESENT_STAGE_IMAGE_FIRST_PIXEL_VISIBLE_BIT_EXT, *monotonic_domain)))
{ {
return nullptr; return nullptr;
} }
} }
return wsi_ext_present_timing::create<wsi_ext_present_timing_headless>(allocator, domains.data(), domains.size(), return wsi_ext_present_timing::create<wsi_ext_present_timing_headless>(allocator, domains.data(), domains.size(),
device, num_images); device, num_images, monotonic_domain);
} }
VkResult wsi_ext_present_timing_headless::get_swapchain_timing_properties( VkResult wsi_ext_present_timing_headless::get_swapchain_timing_properties(

View file

@ -48,11 +48,27 @@ public:
VkResult get_swapchain_timing_properties(uint64_t &timing_properties_counter, VkResult get_swapchain_timing_properties(uint64_t &timing_properties_counter,
VkSwapchainTimingPropertiesEXT &timing_properties) override; VkSwapchainTimingPropertiesEXT &timing_properties) override;
/**
* @brief Get a monotonic time domain supported by the driver.
*
* If both MONOTONIC_RAW and MONOTONIC are supported, MONOTONIC_RAW is preferred.
*
* @return A supported monotonic time domain, or std::nullopt if no monotonic time domain is supported.
*/
std::optional<VkTimeDomainEXT> get_monotonic_domain() const
{
return m_monotonic_domain;
}
private: private:
wsi_ext_present_timing_headless(const util::allocator &allocator, VkDevice device, uint32_t num_images); wsi_ext_present_timing_headless(const util::allocator &allocator, VkDevice device, uint32_t num_images,
std::optional<VkTimeDomainEXT> monotonic_domain);
/* Allow util::allocator to access the private constructor */ /* Allow util::allocator to access the private constructor */
friend util::allocator; friend util::allocator;
/* Monotonic time domain supported by the driver */
std::optional<VkTimeDomainEXT> m_monotonic_domain;
}; };
#endif #endif

View file

@ -211,10 +211,38 @@ void swapchain::present_image(const pending_present_request &pending_present)
{ {
if (m_device_data.is_present_id_enabled()) if (m_device_data.is_present_id_enabled())
{ {
auto *ext = get_swapchain_extension<wsi_ext_present_id>(true); auto *ext_present_id = get_swapchain_extension<wsi_ext_present_id>(true);
ext->mark_delivered(pending_present.present_id); ext_present_id->mark_delivered(pending_present.present_id);
} }
#if VULKAN_WSI_LAYER_EXPERIMENTAL
auto *ext_present_timing = get_swapchain_extension<wsi_ext_present_timing_headless>(false);
if (ext_present_timing && ext_present_timing->get_monotonic_domain().has_value())
{
clockid_t clockid = ext_present_timing->get_monotonic_domain().value() == VK_TIME_DOMAIN_CLOCK_MONOTONIC_EXT ?
CLOCK_MONOTONIC :
CLOCK_MONOTONIC_RAW;
struct timespec now = {};
if (clock_gettime(clockid, &now) != 0)
{
WSI_LOG_ERROR("Failed to get time of clock %d, error: %d (%s)", clockid, errno, strerror(errno));
}
else
{
uint64_t time = now.tv_sec * 1e9 + now.tv_nsec;
VkPresentStageFlagBitsEXT stages[] = {
VK_PRESENT_STAGE_IMAGE_LATCHED_BIT_EXT,
VK_PRESENT_STAGE_IMAGE_FIRST_PIXEL_OUT_BIT_EXT,
VK_PRESENT_STAGE_IMAGE_FIRST_PIXEL_VISIBLE_BIT_EXT,
};
for (auto stage : stages)
{
ext_present_timing->set_pending_stage_time(pending_present.image_index, stage, time);
}
}
}
#endif
unpresent_image(pending_present.image_index); unpresent_image(pending_present.image_index);
} }

View file

@ -669,8 +669,6 @@ VkResult swapchain_base::queue_present(VkQueue queue, const VkPresentInfoKHR *pr
TRY(sync_queue_submit(m_device_data, queue, submit_info.present_fence, wait_semaphores)); TRY(sync_queue_submit(m_device_data, queue, submit_info.present_fence, wait_semaphores));
} }
TRY(notify_presentation_engine(submit_info.pending_present));
#if VULKAN_WSI_LAYER_EXPERIMENTAL #if VULKAN_WSI_LAYER_EXPERIMENTAL
if (present_timing_info != nullptr) if (present_timing_info != nullptr)
{ {
@ -680,6 +678,9 @@ VkResult swapchain_base::queue_present(VkQueue queue, const VkPresentInfoKHR *pr
present_timing_info->presentStageQueries)); present_timing_info->presentStageQueries));
} }
#endif #endif
TRY(notify_presentation_engine(submit_info.pending_present));
return VK_SUCCESS; return VK_SUCCESS;
} }