Merge 'Implement monotonic stage timestamps on headless' into 'main'

See merge request mesa/vulkan-wsi-layer!176
This commit is contained in:
Iason Paraskevopoulos 2025-07-18 11:39:41 +00:00
commit e34e3775d6
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_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{};
physical_device_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR;
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));
}
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)
{
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;
auto stage_timing_optional = slot.get_stage_timing(VK_PRESENT_STAGE_QUEUE_OPERATIONS_END_BIT_EXT);
const layer::device_private_data &device_data = layer::device_private_data::get(m_device);
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;
}
uint64_t time;
const layer::device_private_data &device_data = layer::device_private_data::get(m_device);
TRY(device_data.disp.GetQueryPoolResults(m_device, m_query_pool, image_index, 1, sizeof(time), &time, 0,
VK_QUERY_RESULT_64_BIT));
timing->set_time(ticks_to_ns(time, m_timestamp_period));
}
return VK_SUCCESS;
}
@ -449,14 +461,13 @@ std::optional<bool> swapchain_presentation_entry::is_complete(VkPresentStageFlag
{
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)
{
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) :
false;
return stage_timing_optional.has_value() ? !stage_timing_optional->get().m_set : false;
}
bool swapchain_presentation_entry::has_outstanding_stages()
@ -531,12 +542,11 @@ void swapchain_presentation_entry::populate(VkPastPresentationTimingEXT &timing)
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.pPresentStages[stage_index].stage = stage;
timing.pPresentStages[stage_index++].time =
stage_timing_optional->get().m_time.load(std::memory_order_relaxed);
timing.pPresentStages[stage_index++].time = stage_timing_optional->get().m_time;
}
}

View file

@ -66,36 +66,29 @@ struct swapchain_presentation_timing
/**
* 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
* implementation ultimately rejected (e.g. in MAILBOX the presentation engine
* rejected the one present request)
*/
std::atomic<bool> m_set{};
bool m_set{};
swapchain_presentation_timing()
{
}
swapchain_presentation_timing(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());
}
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(swapchain_presentation_timing &&) noexcept = default;
swapchain_presentation_timing &operator=(swapchain_presentation_timing &&) noexcept = default;
swapchain_presentation_timing(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);
template <typename T>
template <typename T, typename... arg_types>
static util::unique_ptr<T> create(const util::allocator &allocator,
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++)
{
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,
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.
*
@ -479,16 +488,33 @@ private:
*/
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.
*
* @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.
* If there is no pending entry for the image index, no-op.
*
* @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);

View file

@ -35,8 +35,10 @@
#include "layer/private_data.hpp"
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)
, m_monotonic_domain(monotonic_domain)
{
}
@ -61,14 +63,12 @@ util::unique_ptr<wsi_ext_present_timing_headless> wsi_ext_present_timing_headles
return nullptr;
}
VkTimeDomainEXT monotonic_domain_to_use = VK_TIME_DOMAIN_CLOCK_MONOTONIC_RAW_EXT;
bool monotonic_time_domain_supported = false;
std::optional<VkTimeDomainEXT> monotonic_domain;
for (auto [domain, supported] : monotonic_domains)
{
monotonic_domain_to_use = domain;
if (supported)
{
monotonic_time_domain_supported = true;
monotonic_domain = domain;
break;
}
}
@ -80,27 +80,27 @@ util::unique_ptr<wsi_ext_present_timing_headless> wsi_ext_present_timing_headles
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,
monotonic_domain_to_use)))
if (!domains.try_push_back(
allocator.make_unique<wsi::vulkan_time_domain>(VK_PRESENT_STAGE_IMAGE_LATCHED_BIT_EXT, *monotonic_domain)))
{
return nullptr;
}
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;
}
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 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(

View file

@ -48,11 +48,27 @@ public:
VkResult get_swapchain_timing_properties(uint64_t &timing_properties_counter,
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:
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 */
friend util::allocator;
/* Monotonic time domain supported by the driver */
std::optional<VkTimeDomainEXT> m_monotonic_domain;
};
#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())
{
auto *ext = get_swapchain_extension<wsi_ext_present_id>(true);
ext->mark_delivered(pending_present.present_id);
auto *ext_present_id = get_swapchain_extension<wsi_ext_present_id>(true);
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);
}

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(notify_presentation_engine(submit_info.pending_present));
#if VULKAN_WSI_LAYER_EXPERIMENTAL
if (present_timing_info != nullptr)
{
@ -680,6 +678,9 @@ VkResult swapchain_base::queue_present(VkQueue queue, const VkPresentInfoKHR *pr
present_timing_info->presentStageQueries));
}
#endif
TRY(notify_presentation_engine(submit_info.pending_present));
return VK_SUCCESS;
}