mirror of
https://gitlab.freedesktop.org/mesa/vulkan-wsi-layer.git
synced 2025-12-20 03:20:09 +01:00
Merge 'Fix and enhance Vulkan present timing and time domain handling' into 'main'
See merge request mesa/vulkan-wsi-layer!214
This commit is contained in:
commit
a0947a5ae8
13 changed files with 311 additions and 140 deletions
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ×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<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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
#include <functional>
|
||||
#include <cassert>
|
||||
#include <variant>
|
||||
#include <cmath>
|
||||
|
||||
#include <layer/wsi_layer_experimental.hpp>
|
||||
#include <layer/private_data.hpp>
|
||||
|
|
@ -78,7 +79,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)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -148,7 +157,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;
|
||||
|
||||
|
|
@ -299,8 +308,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;
|
||||
|
|
@ -533,6 +542,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.
|
||||
*
|
||||
|
|
@ -622,6 +638,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.
|
||||
|
|
@ -781,6 +804,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 ×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<double>(ticks) * static_cast<double>(timestamp_period);
|
||||
return static_cast<uint64_t>(std::llround(ns));
|
||||
}
|
||||
|
||||
} /* namespace wsi */
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -601,6 +601,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 */
|
||||
|
|
@ -649,20 +664,13 @@ VkResult swapchain_base::queue_present(VkQueue queue, const VkPresentInfoKHR *pr
|
|||
m_swapchain_images[submit_info.pending_present.image_index].get_present_fence_wait_semaphore();
|
||||
}
|
||||
#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,
|
||||
|
|
|
|||
|
|
@ -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>> &×tamp_first_pixel_out_storage)
|
||||
util::vector<std::optional<uint64_t>> &×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> 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
|
||||
|
|
|
|||
|
|
@ -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>> &×tamp_first_pixel_out_storage);
|
||||
util::vector<std::optional<uint64_t>> &×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<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;
|
||||
|
||||
|
|
|
|||
|
|
@ -153,22 +153,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;
|
||||
}
|
||||
|
|
@ -444,8 +444,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");
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue