renderer/native: Retry page flip after one vsync if EBUSY

We might fail to page flip a new buffer, often after resuming, due to
the FIFO being full. Prior to this commit, we handled this by switching
over to plain mode setting instead of page flipping. This is bad because
we won't be synchronized to the refresh rate anymore, but just the
clock.

Instead, deal with this by trying again until the FIFO is no longer
full. Do this on a v-sync based interval, until it works.

This also changes the error handling code for drivers not supporting
page flipping to rely on them returning -EINVAL. The handling is moved
from pretending a page flip working to explicit mode setting in
meta-renderer-native.c.

Fixes: https://gitlab.gnome.org/GNOME/mutter/issues/460
This commit is contained in:
Jonas Ådahl 2019-02-12 19:10:59 +01:00
parent 2c1ab8b3cc
commit 979e689278
3 changed files with 345 additions and 122 deletions

View File

@ -74,8 +74,6 @@ struct _MetaGpuKms
int max_buffer_width;
int max_buffer_height;
gboolean page_flips_not_supported;
gboolean resources_init_failed_before;
MetaGpuKmsFlag flags;
@ -199,7 +197,6 @@ invoke_flip_closure (GClosure *flip_closure,
g_value_init (&params[3], G_TYPE_INT64);
g_value_set_int64 (&params[3], page_flip_time_ns);
g_closure_invoke (flip_closure, NULL, 4, params, NULL);
g_closure_unref (flip_closure);
}
gboolean
@ -245,7 +242,7 @@ meta_gpu_kms_wrap_flip_closure (MetaGpuKms *gpu_kms,
closure_container = g_new0 (MetaGpuKmsFlipClosureContainer, 1);
*closure_container = (MetaGpuKmsFlipClosureContainer) {
.flip_closure = flip_closure,
.flip_closure = g_closure_ref (flip_closure),
.gpu_kms = gpu_kms,
.crtc = crtc
};
@ -256,20 +253,21 @@ meta_gpu_kms_wrap_flip_closure (MetaGpuKms *gpu_kms,
void
meta_gpu_kms_flip_closure_container_free (MetaGpuKmsFlipClosureContainer *closure_container)
{
g_closure_unref (closure_container->flip_closure);
g_free (closure_container);
}
gboolean
meta_gpu_kms_flip_crtc (MetaGpuKms *gpu_kms,
MetaCrtc *crtc,
int x,
int y,
uint32_t fb_id,
GClosure *flip_closure,
gboolean *fb_in_use)
GError **error)
{
MetaGpu *gpu = META_GPU (gpu_kms);
MetaMonitorManager *monitor_manager = meta_gpu_get_monitor_manager (gpu);
MetaGpuKmsFlipClosureContainer *closure_container;
int kms_fd = meta_gpu_kms_get_fd (gpu_kms);
uint32_t *connectors;
unsigned int n_connectors;
int ret = -1;
@ -283,11 +281,6 @@ meta_gpu_kms_flip_crtc (MetaGpuKms *gpu_kms,
g_assert (fb_id != 0);
if (!gpu_kms->page_flips_not_supported)
{
MetaGpuKmsFlipClosureContainer *closure_container;
int kms_fd = meta_gpu_kms_get_fd (gpu_kms);
closure_container = meta_gpu_kms_wrap_flip_closure (gpu_kms,
crtc,
flip_closure);
@ -297,28 +290,14 @@ meta_gpu_kms_flip_crtc (MetaGpuKms *gpu_kms,
fb_id,
DRM_MODE_PAGE_FLIP_EVENT,
closure_container);
if (ret != 0 && ret != -EACCES)
if (ret != 0)
{
meta_gpu_kms_flip_closure_container_free (closure_container);
g_warning ("Failed to flip: %s", strerror (-ret));
gpu_kms->page_flips_not_supported = TRUE;
}
}
if (gpu_kms->page_flips_not_supported)
{
if (meta_gpu_kms_apply_crtc_mode (gpu_kms, crtc, x, y, fb_id))
{
*fb_in_use = TRUE;
g_set_error (error, G_IO_ERROR,
g_io_error_from_errno (-ret),
"drmModePageFlip failed: %s", g_strerror (-ret));
return FALSE;
}
}
if (ret != 0)
return FALSE;
*fb_in_use = TRUE;
g_closure_ref (flip_closure);
return TRUE;
}
@ -365,13 +344,6 @@ meta_gpu_kms_wait_for_flip (MetaGpuKms *gpu_kms,
{
drmEventContext evctx;
if (gpu_kms->page_flips_not_supported)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Page flips not supported");
return FALSE;
}
memset (&evctx, 0, sizeof evctx);
evctx.version = 2;
evctx.page_flip_handler = page_flip_handler;

View File

@ -72,11 +72,9 @@ gboolean meta_gpu_kms_is_platform_device (MetaGpuKms *gpu_kms);
gboolean meta_gpu_kms_flip_crtc (MetaGpuKms *gpu_kms,
MetaCrtc *crtc,
int x,
int y,
uint32_t fb_id,
GClosure *flip_closure,
gboolean *fb_in_use);
GError **error);
gboolean meta_gpu_kms_wait_for_flip (MetaGpuKms *gpu_kms,
GError **error);

View File

@ -192,6 +192,9 @@ typedef struct _MetaOnscreenNative
int64_t pending_queue_swap_notify_frame_count;
int64_t pending_swap_notify_frame_count;
GList *pending_page_flip_retries;
GSource *retry_page_flips_source;
MetaRendererView *view;
int total_pending_flips;
} MetaOnscreenNative;
@ -302,6 +305,13 @@ meta_create_renderer_native_gpu_data (MetaGpuKms *gpu_kms)
return g_new0 (MetaRendererNativeGpuData, 1);
}
static MetaOnscreenNativeSecondaryGpuState *
meta_onscreen_native_get_secondary_gpu_state (MetaOnscreenNative *onscreen_native,
MetaGpuKms *gpu_kms)
{
return g_hash_table_lookup (onscreen_native->secondary_gpu_states, gpu_kms);
}
static MetaOnscreenNativeSecondaryGpuState *
get_secondary_gpu_state (CoglOnscreen *onscreen,
MetaGpuKms *gpu_kms)
@ -309,7 +319,8 @@ get_secondary_gpu_state (CoglOnscreen *onscreen,
CoglOnscreenEGL *onscreen_egl = onscreen->winsys;
MetaOnscreenNative *onscreen_native = onscreen_egl->platform;
return g_hash_table_lookup (onscreen_native->secondary_gpu_states, gpu_kms);
return meta_onscreen_native_get_secondary_gpu_state (onscreen_native,
gpu_kms);
}
static MetaEgl *
@ -1507,19 +1518,202 @@ flip_egl_stream (MetaOnscreenNative *onscreen_native,
return FALSE;
}
g_closure_ref (flip_closure);
return TRUE;
}
#endif /* HAVE_EGL_DEVICE */
static gboolean
is_timestamp_earlier_than (uint64_t ts1,
uint64_t ts2)
{
if (ts1 == ts2)
return FALSE;
else
return ts2 - ts1 < UINT64_MAX / 2;
}
typedef struct _RetryPageFlipData
{
MetaCrtc *crtc;
uint32_t fb_id;
GClosure *flip_closure;
uint64_t retry_time_us;
} RetryPageFlipData;
static void
retry_page_flip_data_free (RetryPageFlipData *retry_page_flip_data)
{
g_closure_unref (retry_page_flip_data->flip_closure);
g_free (retry_page_flip_data);
}
static gboolean
retry_page_flips (gpointer user_data)
{
MetaOnscreenNative *onscreen_native = user_data;
uint64_t now_us;
GList *l;
now_us = g_source_get_time (onscreen_native->retry_page_flips_source);
l = onscreen_native->pending_page_flip_retries;
while (l)
{
RetryPageFlipData *retry_page_flip_data = l->data;
MetaCrtc *crtc = retry_page_flip_data->crtc;
MetaGpuKms *gpu_kms = META_GPU_KMS (meta_crtc_get_gpu (crtc));
GList *l_next = l->next;
g_autoptr (GError) error = NULL;
gboolean did_flip;
if (is_timestamp_earlier_than (now_us,
retry_page_flip_data->retry_time_us))
{
l = l_next;
continue;
}
did_flip = meta_gpu_kms_flip_crtc (gpu_kms,
crtc,
retry_page_flip_data->fb_id,
retry_page_flip_data->flip_closure,
&error);
if (!did_flip &&
g_error_matches (error, G_IO_ERROR, G_IO_ERROR_BUSY))
{
retry_page_flip_data->retry_time_us +=
G_USEC_PER_SEC / crtc->current_mode->refresh_rate;
l = l_next;
continue;
}
onscreen_native->pending_page_flip_retries =
g_list_remove_link (onscreen_native->pending_page_flip_retries, l);
if (!did_flip)
{
if (!g_error_matches (error,
G_IO_ERROR,
G_IO_ERROR_PERMISSION_DENIED))
g_critical ("Failed to page flip: %s", error->message);
if (gpu_kms != onscreen_native->render_gpu)
{
MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state;
secondary_gpu_state =
meta_onscreen_native_get_secondary_gpu_state (onscreen_native,
gpu_kms);
secondary_gpu_state->pending_flips--;
}
onscreen_native->total_pending_flips--;
}
retry_page_flip_data_free (retry_page_flip_data);
l = l_next;
}
if (onscreen_native->pending_page_flip_retries)
{
GList *l;
uint64_t earliest_retry_time_us = 0;
for (l = onscreen_native->pending_page_flip_retries; l; l = l->next)
{
RetryPageFlipData *retry_page_flip_data = l->data;
if (l == onscreen_native->pending_page_flip_retries ||
is_timestamp_earlier_than (retry_page_flip_data->retry_time_us,
earliest_retry_time_us))
earliest_retry_time_us = retry_page_flip_data->retry_time_us;
}
g_source_set_ready_time (onscreen_native->retry_page_flips_source,
earliest_retry_time_us);
return G_SOURCE_CONTINUE;
}
else
{
g_clear_pointer (&onscreen_native->retry_page_flips_source,
g_source_unref);
return G_SOURCE_REMOVE;
}
}
static gboolean
retry_page_flips_source_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data)
{
return callback (user_data);
}
static GSourceFuncs retry_page_flips_source_funcs = {
.dispatch = retry_page_flips_source_dispatch,
};
static void
schedule_retry_page_flip (MetaOnscreenNative *onscreen_native,
MetaCrtc *crtc,
uint32_t fb_id,
GClosure *flip_closure)
{
RetryPageFlipData *retry_page_flip_data;
uint64_t now_us;
uint64_t retry_time_us;
now_us = g_get_monotonic_time ();
retry_time_us =
now_us + (G_USEC_PER_SEC / crtc->current_mode->refresh_rate);
retry_page_flip_data = g_new0 (RetryPageFlipData, 1);
retry_page_flip_data->crtc = crtc;
retry_page_flip_data->fb_id = fb_id;
retry_page_flip_data->flip_closure = g_closure_ref (flip_closure);
retry_page_flip_data->retry_time_us = retry_time_us;
if (!onscreen_native->retry_page_flips_source)
{
GSource *source;
source = g_source_new (&retry_page_flips_source_funcs, sizeof (GSource));
g_source_set_callback (source, retry_page_flips, onscreen_native, NULL);
g_source_set_ready_time (source, retry_time_us);
g_source_attach (source, NULL);
onscreen_native->retry_page_flips_source = source;
}
else
{
GList *l;
for (l = onscreen_native->pending_page_flip_retries; l; l = l->next)
{
RetryPageFlipData *pending_retry_page_flip_data = l->data;
uint64_t pending_retry_time_us =
pending_retry_page_flip_data->retry_time_us;
if (is_timestamp_earlier_than (retry_time_us, pending_retry_time_us))
{
g_source_set_ready_time (onscreen_native->retry_page_flips_source,
retry_time_us);
break;
}
}
}
onscreen_native->pending_page_flip_retries =
g_list_append (onscreen_native->pending_page_flip_retries,
retry_page_flip_data);
}
static gboolean
meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen,
GClosure *flip_closure,
MetaCrtc *crtc,
int x,
int y,
gboolean *fb_in_use)
GError **error)
{
CoglOnscreenEGL *onscreen_egl = onscreen->winsys;
MetaOnscreenNative *onscreen_native = onscreen_egl->platform;
@ -1533,8 +1727,9 @@ meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen,
gpu_kms = META_GPU_KMS (meta_crtc_get_gpu (crtc));
if (!meta_gpu_kms_is_crtc_active (gpu_kms, crtc))
{
*fb_in_use = FALSE;
return;
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Tried to flip inactive CRTC");
return FALSE;
}
renderer_gpu_data = meta_renderer_native_get_gpu_data (renderer_native,
@ -1554,11 +1749,23 @@ meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen,
if (!meta_gpu_kms_flip_crtc (gpu_kms,
crtc,
x, y,
fb_id,
flip_closure,
fb_in_use))
return;
error))
{
if (g_error_matches (*error,
G_IO_ERROR,
G_IO_ERROR_BUSY))
{
g_clear_error (error);
schedule_retry_page_flip (onscreen_native, crtc,
fb_id, flip_closure);
}
else
{
return FALSE;
}
}
onscreen_native->total_pending_flips++;
if (secondary_gpu_state)
@ -1570,35 +1777,30 @@ meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen,
if (flip_egl_stream (onscreen_native,
flip_closure))
onscreen_native->total_pending_flips++;
*fb_in_use = TRUE;
break;
#endif
}
return TRUE;
}
typedef struct _SetCrtcFbData
{
MetaGpuKms *render_gpu;
CoglOnscreen *onscreen;
uint32_t fb_id;
} SetCrtcFbData;
static void
set_crtc_fb (MetaLogicalMonitor *logical_monitor,
set_crtc_fb (CoglOnscreen *onscreen,
MetaLogicalMonitor *logical_monitor,
MetaCrtc *crtc,
gpointer user_data)
uint32_t render_fb_id)
{
SetCrtcFbData *data = user_data;
MetaGpuKms *render_gpu = data->render_gpu;
CoglOnscreen *onscreen = data->onscreen;
CoglOnscreenEGL *onscreen_egl = onscreen->winsys;
MetaOnscreenNative *onscreen_native = onscreen_egl->platform;
MetaGpuKms *render_gpu = onscreen_native->render_gpu;
MetaGpuKms *gpu_kms;
uint32_t fb_id;
int x, y;
uint32_t fb_id;
gpu_kms = META_GPU_KMS (meta_crtc_get_gpu (crtc));
if (gpu_kms == render_gpu)
{
fb_id = data->fb_id;
fb_id = render_fb_id;
}
else
{
@ -1617,6 +1819,23 @@ set_crtc_fb (MetaLogicalMonitor *logical_monitor,
meta_gpu_kms_apply_crtc_mode (gpu_kms, crtc, x, y, fb_id);
}
typedef struct _SetCrtcFbData
{
CoglOnscreen *onscreen;
uint32_t fb_id;
} SetCrtcFbData;
static void
set_crtc_fb_cb (MetaLogicalMonitor *logical_monitor,
MetaCrtc *crtc,
gpointer user_data)
{
SetCrtcFbData *data = user_data;
CoglOnscreen *onscreen = data->onscreen;
set_crtc_fb (onscreen, logical_monitor, crtc, data->fb_id);
}
static void
meta_onscreen_native_set_crtc_modes (CoglOnscreen *onscreen)
{
@ -1649,13 +1868,12 @@ meta_onscreen_native_set_crtc_modes (CoglOnscreen *onscreen)
if (logical_monitor)
{
SetCrtcFbData data = {
.render_gpu = render_gpu,
.onscreen = onscreen,
.fb_id = fb_id
};
meta_logical_monitor_foreach_crtc (logical_monitor,
set_crtc_fb,
set_crtc_fb_cb,
&data);
}
else
@ -1674,12 +1892,47 @@ meta_onscreen_native_set_crtc_modes (CoglOnscreen *onscreen)
}
}
static gboolean
crtc_mode_set_fallback (CoglOnscreen *onscreen,
MetaLogicalMonitor *logical_monitor,
MetaCrtc *crtc)
{
CoglOnscreenEGL *onscreen_egl = onscreen->winsys;
MetaOnscreenNative *onscreen_native = onscreen_egl->platform;
MetaGpuKms *render_gpu = onscreen_native->render_gpu;
MetaRendererNative *renderer_native;
MetaRendererNativeGpuData *renderer_gpu_data;
uint32_t fb_id;
static gboolean warned_once = FALSE;
if (!warned_once)
{
g_warning ("Page flipping not supported by driver, "
"relying on the clock from now on");
warned_once = TRUE;
}
renderer_native = meta_renderer_native_from_gpu (render_gpu);
renderer_gpu_data = meta_renderer_native_get_gpu_data (renderer_native,
render_gpu);
if (renderer_gpu_data->mode != META_RENDERER_NATIVE_MODE_GBM)
{
g_warning ("Mode set fallback not handled for EGLStreams");
return FALSE;
}
fb_id = onscreen_native->gbm.next_fb_id;
set_crtc_fb (onscreen, logical_monitor, crtc, fb_id);
return TRUE;
}
typedef struct _FlipCrtcData
{
CoglOnscreen *onscreen;
GClosure *flip_closure;
gboolean out_fb_in_use;
gboolean did_flip;
gboolean did_mode_set;
} FlipCrtcData;
static void
@ -1688,15 +1941,28 @@ flip_crtc (MetaLogicalMonitor *logical_monitor,
gpointer user_data)
{
FlipCrtcData *data = user_data;
int x, y;
GError *error = NULL;
x = crtc->rect.x - logical_monitor->rect.x;
y = crtc->rect.y - logical_monitor->rect.y;
meta_onscreen_native_flip_crtc (data->onscreen,
if (!meta_onscreen_native_flip_crtc (data->onscreen,
data->flip_closure,
crtc, x, y,
&data->out_fb_in_use);
crtc,
&error))
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
{
if (crtc_mode_set_fallback (data->onscreen, logical_monitor, crtc))
data->did_mode_set = TRUE;
}
else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED))
{
g_warning ("Failed to flip onscreen: %s", error->message);
}
g_error_free (error);
}
else
{
data->did_flip = TRUE;
}
}
static void
@ -1707,7 +1973,6 @@ meta_onscreen_native_flip_crtcs (CoglOnscreen *onscreen)
MetaRendererView *view = onscreen_native->view;
GClosure *flip_closure;
MetaLogicalMonitor *logical_monitor;
gboolean fb_in_use = FALSE;
/*
* Create a closure that either will be invoked or destructed.
@ -1735,29 +2000,12 @@ meta_onscreen_native_flip_crtcs (CoglOnscreen *onscreen)
meta_logical_monitor_foreach_crtc (logical_monitor, flip_crtc, &data);
/*
* If the framebuffer is in use, but we don't have any pending flips it means
* that flipping is not supported and we set the next framebuffer directly.
* Since we won't receive a flip callback, lets just notify listeners
* directly.
* If we didn't queue a page flip, but instead directly changed the mode due
* to the driver not supporting mode setting, wes must swap the buffers
* directly as we won't get a page flip callback.
*/
if (fb_in_use && onscreen_native->total_pending_flips == 0)
{
MetaRendererNative *renderer_native = onscreen_native->renderer_native;
MetaRendererNativeGpuData *renderer_gpu_data;
renderer_gpu_data = meta_renderer_native_get_gpu_data (renderer_native,
render_gpu);
switch (renderer_gpu_data->mode)
{
case META_RENDERER_NATIVE_MODE_GBM:
if (!data.did_flip && data.did_mode_set)
meta_onscreen_native_swap_drm_fb (onscreen);
break;
#ifdef HAVE_EGL_DEVICE
case META_RENDERER_NATIVE_MODE_EGL_DEVICE:
break;
#endif
}
}
onscreen_native->pending_queue_swap_notify = TRUE;
@ -2720,6 +2968,11 @@ meta_renderer_native_release_onscreen (CoglOnscreen *onscreen)
onscreen_native = onscreen_egl->platform;
g_list_free_full (onscreen_native->pending_page_flip_retries,
(GDestroyNotify) retry_page_flip_data_free);
g_clear_pointer (&onscreen_native->retry_page_flips_source,
g_source_destroy);
if (onscreen_egl->egl_surface != EGL_NO_SURFACE)
{
MetaEgl *egl = meta_onscreen_native_get_egl (onscreen_native);