Commit c81c6ffe authored by Keith Packard's avatar Keith Packard
Browse files

Start work on VK_MESA_present_period



This allows applications to specify the minimum time that a presented
image must be shown. Times may be specified as frames or ns.
Signed-off-by: Keith Packard's avatarKeith Packard <keithp@keithp.com>
parent 807dbe46
Pipeline #104367 failed with stages
in 43 minutes and 49 seconds
......@@ -560,6 +560,7 @@ typedef enum VkStructureType {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DEMOTE_TO_HELPER_INVOCATION_FEATURES_EXT = 1000276000,
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TEXEL_BUFFER_ALIGNMENT_FEATURES_EXT = 1000281000,
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TEXEL_BUFFER_ALIGNMENT_PROPERTIES_EXT = 1000281001,
VK_STRUCTURE_TYPE_PRESENT_PERIOD_MESA = 1000329000,
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTER_FEATURES = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES,
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETER_FEATURES = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES,
VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT,
......@@ -10715,6 +10716,18 @@ typedef struct VkPhysicalDeviceTexelBufferAlignmentPropertiesEXT {
#define VK_GOOGLE_USER_TYPE_SPEC_VERSION 1
#define VK_GOOGLE_USER_TYPE_EXTENSION_NAME "VK_GOOGLE_user_type"
 
#define VK_MESA_present_period 1
#define VK_MESA_PRESENT_PERIOD_SPEC_VERSION 1
#define VK_MESA_PRESENT_PERIOD_EXTENSION_NAME "VK_MESA_present_period"
typedef struct VkPresentPeriodMESA {
VkStructureType sType;
const void* pNext;
uint32_t swapchainCount;
const int64_t* pPresentPeriods;
  • I like this extension in general.

    However, I think allowing the period to be specified in frames might be a mistake, because it won't work well with variable refresh rate. But it'll be tempting for application / toolkit developers not to bother with proper time calculations but to just use frames instead.

    P.S. It would be good to create a WIP merge request for this in the main Mesa project, to have a central place for gathering feedback and tracking progress.

    Edited by Michel Dänzer
  • Thanks for your kind words!

    I added the ability to specify periods in frames because I wanted to make it familiar to developers used to GL's swap interval, but I have to agree that it may discourage support for variable refresh rate.

    Hrm. Given that applications are going to need the refresh period to compute when frames will be displayed, maybe having everything done in nanoseconds wouldn't be a huge burden.

Please register or sign in to reply
} VkPresentPeriodMESA;
#ifdef __cplusplus
}
#endif
......
......@@ -167,6 +167,7 @@ EXTENSIONS = [
Extension('VK_GOOGLE_decorate_string', 1, True),
Extension('VK_GOOGLE_hlsl_functionality1', 1, True),
Extension('VK_GOOGLE_display_timing', 1, True),
Extension('VK_MESA_present_period', 1, True),
Extension('VK_NV_compute_shader_derivatives', 1, 'device->rad_info.chip_class >= GFX8'),
]
......
......@@ -174,6 +174,7 @@ EXTENSIONS = [
Extension('VK_GOOGLE_hlsl_functionality1', 1, True),
Extension('VK_INTEL_performance_query', 1, 'device->perf'),
Extension('VK_INTEL_shader_integer_functions2', 1, 'device->info.gen >= 8'),
Extension('VK_MESA_present_period', 1, True),
Extension('VK_NV_compute_shader_derivatives', 1, True),
]
......
......@@ -2546,6 +2546,12 @@ typedef void <name>CAMetalLayer</name>;
<member><type>float</type> <name>x</name></member>
<member><type>float</type> <name>y</name></member>
</type>
<type category="struct" name="VkPresentPeriodMESA" structextends="VkPresentInfoKHR">
<member values="VK_STRUCTURE_TYPE_PRESENT_PERIOD_MESA"><type>VkStructureType</type> <name>sType</name></member>
<member>const <type>void</type>* <name>pNext</name></member>
<member><type>uint32_t</type> <name>swapchainCount</name><comment>Copy of VkPresentInfoKHR::swapchainCount</comment></member>
<member len="swapchainCount" optional="true">const <type>int64_t</type>* <name>pPresentPeriods</name><comment>Present period values for each swapchain</comment></member>
</type>
<type category="struct" name="VkHdrMetadataEXT">
<comment>Display primary in chromaticity coordinates</comment>
<member values="VK_STRUCTURE_TYPE_HDR_METADATA_EXT"><type>VkStructureType</type> <name>sType</name></member>
......@@ -12453,5 +12459,13 @@ typedef void <name>CAMetalLayer</name>;
<enum bitpos="2" extends="VkMemoryHeapFlagBits" name="VK_MEMORY_HEAP_RESERVED_2_BIT_KHR"/>
</require>
</extension>
<extension name="VK_MESA_present_period" number="330" type="device" requires="VK_KHR_swapchain" author="MESA" contact="Keith Packard @keithp" supported="vulkan">
<require>
<enum value="1" name="VK_MESA_PRESENT_PERIOD_SPEC_VERSION"/>
<enum value="&quot;VK_MESA_present_period&quot;" name="VK_MESA_PRESENT_PERIOD_EXTENSION_NAME"/>
<enum offset="0" extends="VkStructureType" name="VK_STRUCTURE_TYPE_PRESENT_PERIOD_MESA"/>
<type name="VkPresentPeriodMESA"/>
</require>
</extension>
</extensions>
</registry>
......@@ -1244,6 +1244,20 @@ wsi_next_timing(struct wsi_swapchain *chain, int image_index)
return timing;
}
double
mytime(void);
double
mytime(void)
{
static uint64_t first;
uint64_t now = wsi_common_get_current_time();
if (first == 0)
first = now;
return (double)(now - first) / 1.0e9;
}
void
wsi_present_complete(struct wsi_swapchain *swapchain,
struct wsi_image *image,
......@@ -1251,91 +1265,111 @@ wsi_present_complete(struct wsi_swapchain *swapchain,
uint64_t msc)
{
const struct wsi_device *wsi = swapchain->wsi;
struct wsi_timing *timing = image->timing;
if (!timing)
return;
// printf("%9.4f present complete %d %ld\n", mytime(), image->image_index, msc);
uint64_t render_timestamp;
if (image == swapchain->pending) {
struct wsi_image *pending = image->pending;
uint64_t target_msc = image->pending_msc + msc;
VkResult result = wsi->GetQueryPoolResults(
swapchain->device, image->query_pool,
0, 1, sizeof(render_timestamp), &render_timestamp,
sizeof (uint64_t),
VK_QUERY_RESULT_64_BIT|VK_QUERY_RESULT_WAIT_BIT);
if (result != VK_SUCCESS)
return;
while ((swapchain->pending = pending) != NULL) {
static const VkCalibratedTimestampInfoEXT timestampInfo[2] = {
{
.sType = VK_STRUCTURE_TYPE_CALIBRATED_TIMESTAMP_INFO_EXT,
.pNext = NULL,
.timeDomain = VK_TIME_DOMAIN_DEVICE_EXT,
},
{
.sType = VK_STRUCTURE_TYPE_CALIBRATED_TIMESTAMP_INFO_EXT,
.pNext = NULL,
.timeDomain = VK_TIME_DOMAIN_CLOCK_MONOTONIC_EXT,
},
};
if (target_msc > pending->timing->target_msc)
pending->timing->target_msc = target_msc;
// printf("delayed queuing index %d for %ld\n", pending->image_index, pending->timing->target_msc);
(void) swapchain->queue_present(swapchain, pending->image_index, image->region);
if (pending->pending_msc != 0)
break;
pending = pending->pending;
}
}
uint64_t timestamps[2];
uint64_t maxDeviation;
struct wsi_timing *timing = image->timing;
result = wsi->GetCalibratedTimestampsEXT(swapchain->device,
2,
timestampInfo,
timestamps,
&maxDeviation);
if (result != VK_SUCCESS)
return;
if (timing != NULL && timing->timing.presentID) {
uint64_t current_gpu_timestamp = timestamps[0];
uint64_t current_time = timestamps[1];
uint64_t render_timestamp;
VkRefreshCycleDurationGOOGLE display_timings;
swapchain->get_refresh_cycle_duration(swapchain, &display_timings);
VkResult result = wsi->GetQueryPoolResults(
swapchain->device, image->query_pool,
0, 1, sizeof(render_timestamp), &render_timestamp,
sizeof (uint64_t),
VK_QUERY_RESULT_64_BIT|VK_QUERY_RESULT_WAIT_BIT);
if (result != VK_SUCCESS)
return;
static const VkCalibratedTimestampInfoEXT timestampInfo[2] = {
{
.sType = VK_STRUCTURE_TYPE_CALIBRATED_TIMESTAMP_INFO_EXT,
.pNext = NULL,
.timeDomain = VK_TIME_DOMAIN_DEVICE_EXT,
},
{
.sType = VK_STRUCTURE_TYPE_CALIBRATED_TIMESTAMP_INFO_EXT,
.pNext = NULL,
.timeDomain = VK_TIME_DOMAIN_CLOCK_MONOTONIC_EXT,
},
};
uint64_t refresh_duration = display_timings.refreshDuration;
uint64_t timestamps[2];
uint64_t maxDeviation;
/* When did drawing complete (in nsec) */
result = wsi->GetCalibratedTimestampsEXT(swapchain->device,
2,
timestampInfo,
timestamps,
&maxDeviation);
if (result != VK_SUCCESS)
return;
int64_t since_render = (int64_t) floor ((double) (current_gpu_timestamp - render_timestamp) *
(double) wsi->timestamp_period + 0.5);
uint64_t render_time = current_time - since_render;
uint64_t current_gpu_timestamp = timestamps[0];
uint64_t current_time = timestamps[1];
if (render_time > ust)
render_time = ust;
VkRefreshCycleDurationGOOGLE display_timings;
swapchain->get_refresh_cycle_duration(swapchain, &display_timings);
uint64_t render_frames = (ust - render_time) / refresh_duration;
uint64_t refresh_duration = display_timings.refreshDuration;
uint64_t earliest_time = ust - render_frames * refresh_duration;
/* When did drawing complete (in nsec) */
/* Use the presentation mode to figure out when the image could have been
* displayed. It couldn't have been displayed before the previous image, so
* use that as a lower bound. If we're in FIFO mode, then it couldn't have
* been displayed before one frame *after* the previous image
*/
uint64_t possible_frame = swapchain->frame_ust;
int64_t since_render = (int64_t) floor ((double) (current_gpu_timestamp - render_timestamp) *
(double) wsi->timestamp_period + 0.5);
uint64_t render_time = current_time - since_render;
switch (swapchain->present_mode) {
case VK_PRESENT_MODE_FIFO_KHR:
case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
possible_frame += refresh_duration;
break;
default:
break;
}
if (earliest_time < possible_frame)
earliest_time = possible_frame;
if (render_time > ust)
render_time = ust;
if (earliest_time > ust)
earliest_time = ust;
uint64_t render_frames = (ust - render_time) / refresh_duration;
timing->timing.actualPresentTime = ust;
timing->timing.earliestPresentTime = earliest_time;
timing->timing.presentMargin = earliest_time - render_time;
timing->complete = true;
uint64_t earliest_time = ust - render_frames * refresh_duration;
/* Use the presentation mode to figure out when the image could have been
* displayed. It couldn't have been displayed before the previous image, so
* use that as a lower bound. If we're in FIFO mode, then it couldn't have
* been displayed before one frame *after* the previous image
*/
uint64_t possible_frame = swapchain->frame_ust;
switch (swapchain->present_mode) {
case VK_PRESENT_MODE_FIFO_KHR:
case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
possible_frame += refresh_duration;
break;
default:
break;
}
if (earliest_time < possible_frame)
earliest_time = possible_frame;
if (earliest_time > ust)
earliest_time = ust;
timing->timing.actualPresentTime = ust;
timing->timing.earliestPresentTime = earliest_time;
timing->timing.presentMargin = earliest_time - render_time;
}
if (timing)
timing->complete = true;
swapchain->frame_msc = msc;
swapchain->frame_ust = ust;
......@@ -1354,6 +1388,8 @@ wsi_common_queue_present(const struct wsi_device *wsi,
vk_find_struct_const(pPresentInfo->pNext, PRESENT_REGIONS_KHR);
const VkPresentTimesInfoGOOGLE *present_times_info =
vk_find_struct_const(pPresentInfo->pNext, PRESENT_TIMES_INFO_GOOGLE);
const VkPresentPeriodMESA *present_periods =
vk_find_struct_const(pPresentInfo->pNext, PRESENT_PERIOD_MESA);
for (uint32_t i = 0; i < pPresentInfo->swapchainCount; i++) {
WSI_FROM_HANDLE(wsi_swapchain, swapchain, pPresentInfo->pSwapchains[i]);
......@@ -1387,6 +1423,7 @@ wsi_common_queue_present(const struct wsi_device *wsi,
struct wsi_image *image =
swapchain->get_wsi_image(swapchain, image_index);
image->image_index = image_index;
struct wsi_memory_signal_submit_info mem_signal = {
.sType = VK_STRUCTURE_TYPE_WSI_MEMORY_SIGNAL_SUBMIT_INFO_MESA,
......@@ -1435,6 +1472,51 @@ wsi_common_queue_present(const struct wsi_device *wsi,
image->prime.blit_cmd_buffers[queue_family_index];
}
image->pending_msc = 0;
image->pending = NULL;
int64_t present_period = 0;
if (present_periods && present_periods->pPresentPeriods) {
present_period = present_periods->pPresentPeriods[i];
if (present_period) {
if (present_period < 0)
present_period = -present_period;
else {
VkRefreshCycleDurationGOOGLE refresh_timing;
swapchain->get_refresh_cycle_duration(swapchain,
&refresh_timing);
/* Round to nearest MSC */
present_period = (present_period + refresh_timing.refreshDuration/2) / refresh_timing.refreshDuration;
}
image->pending_msc = present_period;
// printf("%9.4f index %d pending_msc %ld\n", mytime(), image_index, present_period);
}
}
const VkPresentRegionKHR *region = NULL;
image->region = NULL;
if (regions && regions->pRegions) {
region = &regions->pRegions[i];
image->region = region;
}
if (swapchain->pending) {
if (timing == NULL) {
timing = wsi_next_timing(swapchain, pPresentInfo->pImageIndices[i]);
timing->timing.presentID = 0;
image->timing = timing;
}
}
if (swapchain->pending || image->pending_msc) {
struct wsi_image **prev = &swapchain->pending;
while (*prev)
prev = &(*prev)->pending;
*prev = image;
}
/* Set up GOOGLE_display_timing bits */
if (present_times_info &&
present_times_info->pTimes != NULL &&
......@@ -1443,11 +1525,13 @@ wsi_common_queue_present(const struct wsi_device *wsi,
const VkPresentTimeGOOGLE *present_time =
&present_times_info->pTimes[i];
timing = wsi_next_timing(swapchain, pPresentInfo->pImageIndices[i]);
if (timing == NULL) {
timing = wsi_next_timing(swapchain, pPresentInfo->pImageIndices[i]);
image->timing = timing;
}
timing->timing.presentID = present_time->presentID;
timing->timing.desiredPresentTime = present_time->desiredPresentTime;
timing->target_msc = 0;
image->timing = timing;
if (present_time->desiredPresentTime != 0)
{
......@@ -1469,8 +1553,10 @@ wsi_common_queue_present(const struct wsi_device *wsi,
}
}
submit_buffers[submit_info.commandBufferCount++] =
image->timestamp_buffer;
if (present_time->presentID) {
submit_buffers[submit_info.commandBufferCount++] =
image->timestamp_buffer;
}
}
result = wsi->QueueSubmit(queue, 1, &submit_info, swapchain->fences[image_index]);
......@@ -1478,13 +1564,11 @@ wsi_common_queue_present(const struct wsi_device *wsi,
if (result != VK_SUCCESS)
goto fail_present;
const VkPresentRegionKHR *region = NULL;
if (regions && regions->pRegions)
region = &regions->pRegions[i];
result = swapchain->queue_present(swapchain, image_index, region);
if (result != VK_SUCCESS)
goto fail_present;
if (!swapchain->pending || swapchain->pending == image) {
result = swapchain->queue_present(swapchain, image_index, region);
if (result != VK_SUCCESS)
goto fail_present;
}
fail_present:
if (pPresentInfo->pResults != NULL)
......
......@@ -54,6 +54,11 @@ struct wsi_image {
VkCommandBuffer timestamp_buffer;
struct wsi_timing *timing;
struct wsi_image *pending;
uint64_t pending_msc;
uint32_t image_index;
const VkPresentRegionKHR *region;
};
#define WSI_TIMING_HISTORY 16
......@@ -79,6 +84,8 @@ struct wsi_swapchain {
float timestamp_period;
struct wsi_image *pending;
/* Command pools, one per queue family */
VkCommandPool *cmd_pools;
......
......@@ -45,6 +45,8 @@
#include "wsi_common_x11.h"
#include "wsi_common_queue.h"
double mytime(void);
#define typed_memcpy(dest, src, count) ({ \
STATIC_ASSERT(sizeof(*src) == sizeof(*dest)); \
memcpy((dest), (src), (count) * sizeof(*(src))); \
......@@ -778,6 +780,10 @@ struct x11_swapchain {
struct wsi_queue acquire_queue;
pthread_t queue_manager;
pthread_t event_thread;
pthread_mutex_t event_mutex;
pthread_cond_t event_cond;
struct x11_image images[0];
};
WSI_DEFINE_NONDISP_HANDLE_CASTS(x11_swapchain, VkSwapchainKHR)
......@@ -940,62 +946,50 @@ static VkResult
x11_acquire_next_image_poll_x11(struct x11_swapchain *chain,
uint32_t *image_index, uint64_t timeout)
{
xcb_generic_event_t *event;
struct pollfd pfds;
uint64_t atimeout;
struct timespec abs_timeout = { 0, 0 };
while (1) {
pthread_mutex_lock(&chain->event_mutex);
for (uint32_t i = 0; i < chain->base.image_count; i++) {
if (!chain->images[i].busy) {
pthread_mutex_unlock(&chain->event_mutex);
/* We found a non-busy image */
xshmfence_await(chain->images[i].shm_fence);
*image_index = i;
chain->images[i].busy = true;
// printf("%9.4f image %d ready\n", mytime(), i);
return x11_swapchain_result(chain, VK_SUCCESS);
}
}
xcb_flush(chain->conn);
if (timeout == UINT64_MAX) {
event = xcb_wait_for_special_event(chain->conn, chain->special_event);
if (!event)
return x11_swapchain_result(chain, VK_ERROR_OUT_OF_DATE_KHR);
} else {
event = xcb_poll_for_special_event(chain->conn, chain->special_event);
if (!event) {
int ret;
if (timeout == 0)
return x11_swapchain_result(chain, VK_NOT_READY);
if (timeout == 0) {
pthread_mutex_unlock(&chain->event_mutex);
return x11_swapchain_result(chain, VK_NOT_READY);
}
if (!abs_timeout.tv_sec) {
uint64_t atimeout;
if (timeout == UINT64_MAX)
atimeout = UINT64_MAX;
else
atimeout = wsi_get_absolute_timeout(timeout);
abs_timeout.tv_sec = atimeout / 1000000000ULL;
abs_timeout.tv_nsec = atimeout % 1000000000ULL;
}
pfds.fd = xcb_get_file_descriptor(chain->conn);
pfds.events = POLLIN;
ret = poll(&pfds, 1, timeout / 1000 / 1000);
if (ret == 0)
return x11_swapchain_result(chain, VK_TIMEOUT);
if (ret == -1)
return x11_swapchain_result(chain, VK_ERROR_OUT_OF_DATE_KHR);
int ret = pthread_cond_timedwait(&chain->event_cond, &chain->event_mutex,
&abs_timeout);
/* If a non-special event happens, the fd will still
* poll. So recalculate the timeout now just in case.
*/
uint64_t current_time = wsi_common_get_current_time();
if (atimeout > current_time)
timeout = atimeout - current_time;
else
timeout = 0;
continue;
}
}
pthread_mutex_unlock(&chain->event_mutex);
// printf("%9.4f check for image ready\n", mytime());
/* Update the swapchain status here. We may catch non-fatal errors here,
* in which case we need to update the status and continue.
*/
VkResult result = x11_handle_dri3_present_event(chain, (void *)event);
free(event);
if (result < 0)
return x11_swapchain_result(chain, result);
if (ret) {
if (ret == ETIMEDOUT)
return x11_swapchain_result(chain, VK_NOT_READY);
return x11_swapchain_result(chain, VK_ERROR_SURFACE_LOST_KHR);
}
}
}
......@@ -1119,6 +1113,7 @@ x11_queue_present(struct wsi_swapchain *anv_chain,
chain->images[image_index].busy = true;
if (chain->has_present_queue) {
// printf("%9.4f queue push %d\n", mytime(), image_index);
wsi_queue_push(&chain->present_queue, image_index);
return chain->status;
} else {
......@@ -1162,7 +1157,9 @@ x11_manage_fifo_queues(void *state)
*/
uint32_t image_index = 0;
struct x11_image *image;
// printf("%9.4f wait for fifo queue\n", mytime());
result = wsi_queue_pull(&chain->present_queue, &image_index, INT64_MAX);
// printf("%9.4f fifo queue %d\n", mytime(), image_index);
assert(result != VK_TIMEOUT);
if (result < 0) {
goto fail;
......@@ -1193,25 +1190,26 @@ x11_manage_fifo_queues(void *state)
if (timing && timing->target_msc != 0 && timing->target_msc > target_msc)
target_msc = timing->target_msc;
printf("%9.4f present to x11 %d %ld\n", mytime(), image_index, target_msc);
result = x11_present_to_x11(chain, image_index, target_msc);
if (result < 0)
goto fail;
xcb_flush(chain->conn);
#if 1
if (chain->has_acquire_queue) {
pthread_mutex_lock(&chain->event_mutex);
while (chain->last_present_msc < target_msc) {
xcb_generic_event_t *event =
xcb_wait_for_special_event(chain->conn, chain->special_event);
if (!event) {
int ret = pthread_cond_wait(&chain->event_cond, &chain->event_mutex);
if (ret) {
pthread_mutex_unlock(&chain->event_mutex);
result = VK_ERROR_OUT_OF_DATE_KHR;
goto fail;
}
result = x11_handle_dri3_present_event(chain, (void *)event);
free(event);
if (result < 0)
goto fail;
}
pthread_mutex_unlock(&chain->event_mutex);
}
#endif
}
fail:
......@@ -1447,6 +1445,10 @@ x11_swapchain_destroy(struct wsi_swapchain *anv_chain,
XCB_PRESENT_EVENT_MASK_NO_EVENT);
xcb_discard_reply(chain->conn, cookie.sequence);
pthread_cancel(chain->event_thread);
pthread_join(chain->event_thread, NULL);
pthread_cond_destroy(&chain->event_cond);
pthread_mutex_destroy(&chain->event_mutex);
wsi_swapchain_finish(&chain->base);
vk_free(pAllocator, chain);
......@@ -1480,6 +1482,36 @@ wsi_x11_set_adaptive_sync_property(xcb_connection_t *conn,
free(reply);
}
static void*
x11_event_thread(void *data)
{
struct x11_swapchain *chain = data;
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
for (;;) {
xcb_generic_event_t *event;
xcb_flush(chain->conn);
// printf("%9.4f wait event\n", mytime());
event = xcb_wait_for_special_event(chain->conn, chain->special_event);
if (!event)