From 5c22141866e5335dd111b64349f6ff2ee484a6b4 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Wed, 11 Mar 2015 12:09:51 -0400 Subject: [PATCH] kms-winsys: try to hobble along if driver doesn't support page flips Some drivers ( like mgag200 ) don't yet support drmModePageFlip. This commit tries to emulate the functionality using drmWaitVBlank and drmModeSetCrtc in those cases. Failing all else, it just falls back to flipping right away. https://bugzilla.gnome.org/show_bug.cgi?id=746042 --- cogl/winsys/cogl-winsys-egl-kms.c | 130 +++++++++++++++++++++++++++++++++----- 1 file changed, 115 insertions(+), 15 deletions(-) diff --git a/cogl/winsys/cogl-winsys-egl-kms.c b/cogl/winsys/cogl-winsys-egl-kms.c index b06c1da..c66eb45 100644 --- a/cogl/winsys/cogl-winsys-egl-kms.c +++ b/cogl/winsys/cogl-winsys-egl-kms.c @@ -45,104 +45,107 @@ #include #include #include #include #include #include #include #include #include "cogl-winsys-egl-kms-private.h" #include "cogl-winsys-egl-private.h" #include "cogl-renderer-private.h" #include "cogl-framebuffer-private.h" #include "cogl-onscreen-private.h" #include "cogl-kms-renderer.h" #include "cogl-kms-display.h" #include "cogl-version.h" #include "cogl-error-private.h" #include "cogl-poll-private.h" static const CoglWinsysEGLVtable _cogl_winsys_egl_vtable; static const CoglWinsysVtable *parent_vtable; typedef struct _CoglRendererKMS { int fd; int opened_fd; struct gbm_device *gbm; CoglClosure *swap_notify_idle; + CoglBool page_flips_not_supported; + CoglBool vblank_not_supported; } CoglRendererKMS; typedef struct _CoglOutputKMS { drmModeConnector *connector; drmModeEncoder *encoder; drmModeCrtc *saved_crtc; drmModeModeInfo *modes; int n_modes; drmModeModeInfo mode; } CoglOutputKMS; typedef struct _CoglDisplayKMS { GList *outputs; GList *crtcs; int width, height; CoglBool pending_set_crtc; struct gbm_surface *dummy_gbm_surface; CoglOnscreen *onscreen; } CoglDisplayKMS; typedef struct _CoglFlipKMS { CoglOnscreen *onscreen; int pending; } CoglFlipKMS; typedef struct _CoglOnscreenKMS { struct gbm_surface *surface; uint32_t current_fb_id; uint32_t next_fb_id; struct gbm_bo *current_bo; struct gbm_bo *next_bo; CoglBool pending_swap_notify; EGLSurface *pending_egl_surface; struct gbm_surface *pending_surface; } CoglOnscreenKMS; static const char device_name[] = "/dev/dri/card0"; +static void setup_crtc_modes (CoglDisplay *display, int fb_id); static void _cogl_winsys_renderer_disconnect (CoglRenderer *renderer) { CoglRendererEGL *egl_renderer = renderer->winsys; CoglRendererKMS *kms_renderer = egl_renderer->platform; eglTerminate (egl_renderer->edpy); if (kms_renderer->opened_fd >= 0) close (kms_renderer->opened_fd); g_slice_free (CoglRendererKMS, kms_renderer); g_slice_free (CoglRendererEGL, egl_renderer); } static void flush_pending_swap_notify_cb (void *data, void *user_data) { CoglFramebuffer *framebuffer = data; if (framebuffer->type == COGL_FRAMEBUFFER_TYPE_ONSCREEN) { CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer); CoglOnscreenEGL *egl_onscreen = onscreen->winsys; CoglOnscreenKMS *kms_onscreen = egl_onscreen->platform; if (kms_onscreen->pending_swap_notify) { @@ -197,101 +200,168 @@ free_current_bo (CoglOnscreen *onscreen) kms_onscreen->current_bo = NULL; } } static void queue_swap_notify_for_onscreen (CoglOnscreen *onscreen) { CoglOnscreenEGL *egl_onscreen = onscreen->winsys; CoglOnscreenKMS *kms_onscreen = egl_onscreen->platform; CoglContext *context = COGL_FRAMEBUFFER (onscreen)->context; CoglRenderer *renderer = context->display->renderer; CoglRendererEGL *egl_renderer = renderer->winsys; CoglRendererKMS *kms_renderer = egl_renderer->platform; /* We only want to notify that the swap is complete when the * application calls cogl_context_dispatch so instead of * immediately notifying we queue an idle callback */ if (!kms_renderer->swap_notify_idle) { kms_renderer->swap_notify_idle = _cogl_poll_renderer_add_idle (renderer, flush_pending_swap_notify_idle, context, NULL); } kms_onscreen->pending_swap_notify = TRUE; } static void -page_flip_handler (int fd, - unsigned int frame, - unsigned int sec, - unsigned int usec, - void *data) +process_flip (CoglFlipKMS *flip) { - CoglFlipKMS *flip = data; - /* We're only ready to dispatch a swap notification once all outputs * have flipped... */ flip->pending--; if (flip->pending == 0) { CoglOnscreen *onscreen = flip->onscreen; CoglOnscreenEGL *egl_onscreen = onscreen->winsys; CoglOnscreenKMS *kms_onscreen = egl_onscreen->platform; queue_swap_notify_for_onscreen (onscreen); free_current_bo (onscreen); kms_onscreen->current_fb_id = kms_onscreen->next_fb_id; kms_onscreen->next_fb_id = 0; kms_onscreen->current_bo = kms_onscreen->next_bo; kms_onscreen->next_bo = NULL; cogl_object_unref (flip->onscreen); g_slice_free (CoglFlipKMS, flip); } } static void +page_flip_handler (int fd, + unsigned int frame, + unsigned int sec, + unsigned int usec, + void *data) +{ + CoglFlipKMS *flip = data; + + process_flip (flip); +} + +static void +process_vblank (CoglFlipKMS *flip) +{ + /* Normally the driver would set the next fb up for + * scan out after vblank for us and then call the + * page flip handler to clean things up. + * + * Not all drivers support the newer page flipping interface + * that provides this functionality, though, so try to emulate + * it + */ + if (flip->pending == 1) + { + CoglOnscreen *onscreen = flip->onscreen; + CoglOnscreenEGL *egl_onscreen = onscreen->winsys; + CoglOnscreenKMS *kms_onscreen = egl_onscreen->platform; + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglContext *context = framebuffer->context; + CoglDisplay *display = context->display; + + setup_crtc_modes (display, kms_onscreen->next_fb_id); + + process_flip (flip); + } +} + +static void +vblank_handler (int fd, + unsigned int frame, + unsigned int sec, + unsigned int usec, + void *data) +{ + CoglFlipKMS *flip = data; + + process_vblank (flip); +} + +static gboolean +fake_vblank_timeout_handler (gpointer data) +{ + CoglFlipKMS *flip = data; + + /* Normally the next fb would get setup for + * scan out after a vblank interval. + * + * Not all drivers support vblank notification, though, + * so this handler just processes things right away. + */ + process_vblank (flip); + + return G_SOURCE_REMOVE; +} + +static void handle_drm_event (CoglRendererKMS *kms_renderer) { drmEventContext evctx; + if (kms_renderer->page_flips_not_supported && + kms_renderer->vblank_not_supported) + return; + memset (&evctx, 0, sizeof evctx); evctx.version = DRM_EVENT_CONTEXT_VERSION; - evctx.page_flip_handler = page_flip_handler; + if (kms_renderer->page_flips_not_supported) + evctx.vblank_handler = vblank_handler; + else + evctx.page_flip_handler = page_flip_handler; drmHandleEvent (kms_renderer->fd, &evctx); } static void dispatch_kms_events (void *user_data, int revents) { CoglRenderer *renderer = user_data; CoglRendererEGL *egl_renderer = renderer->winsys; CoglRendererKMS *kms_renderer = egl_renderer->platform; if (!revents) return; handle_drm_event (kms_renderer); } static CoglBool _cogl_winsys_renderer_connect (CoglRenderer *renderer, CoglError **error) { CoglRendererEGL *egl_renderer; CoglRendererKMS *kms_renderer; renderer->winsys = g_slice_new0 (CoglRendererEGL); egl_renderer = renderer->winsys; egl_renderer->platform_vtable = &_cogl_winsys_egl_vtable; egl_renderer->platform = g_slice_new0 (CoglRendererKMS); kms_renderer = egl_renderer->platform; @@ -552,75 +622,105 @@ setup_crtc_modes (CoglDisplay *display, int fb_id) CoglRendererEGL *egl_renderer = display->renderer->winsys; CoglRendererKMS *kms_renderer = egl_renderer->platform; GList *l; for (l = kms_display->crtcs; l; l = l->next) { CoglKmsCrtc *crtc = l->data; int ret = drmModeSetCrtc (kms_renderer->fd, crtc->id, fb_id, crtc->x, crtc->y, crtc->connectors, crtc->count, crtc->count ? &crtc->mode : NULL); if (ret) g_warning ("Failed to set crtc mode %s: %m", crtc->mode.name); } } static void flip_all_crtcs (CoglDisplay *display, CoglFlipKMS *flip, int fb_id) { CoglDisplayEGL *egl_display = display->winsys; CoglDisplayKMS *kms_display = egl_display->platform; CoglRendererEGL *egl_renderer = display->renderer->winsys; CoglRendererKMS *kms_renderer = egl_renderer->platform; GList *l; for (l = kms_display->crtcs; l; l = l->next) { CoglKmsCrtc *crtc = l->data; - int ret; + int ret = -1; if (crtc->count == 0 || crtc->ignore) continue; - ret = drmModePageFlip (kms_renderer->fd, - crtc->id, fb_id, - DRM_MODE_PAGE_FLIP_EVENT, flip); + if (!kms_renderer->page_flips_not_supported) + { + ret = drmModePageFlip (kms_renderer->fd, + crtc->id, fb_id, + DRM_MODE_PAGE_FLIP_EVENT, flip); + if (ret) + { + g_warning ("Failed to flip: %m"); + kms_renderer->page_flips_not_supported = TRUE; + } + } - if (ret) + if (kms_renderer->page_flips_not_supported) { - g_warning ("Failed to flip: %m"); - continue; + drmVBlank watch_for_next_vblank_operation = { 0 }; + + if (!kms_renderer->vblank_not_supported) + { + /* fall back to older way of waiting for vblanks directly + */ + watch_for_next_vblank_operation.request.type = DRM_VBLANK_RELATIVE | DRM_VBLANK_EVENT; + watch_for_next_vblank_operation.request.sequence = 1; + watch_for_next_vblank_operation.request.signal = (gulong) flip; + + ret = drmWaitVBlank (kms_renderer->fd, &watch_for_next_vblank_operation); + + if (ret) + { + kms_renderer->vblank_not_supported = TRUE; + g_warning ("Failed to fall back to waiting for vblanks directly: %m"); + } + } } + if (kms_renderer->page_flips_not_supported && + kms_renderer->vblank_not_supported) + { + g_idle_add (fake_vblank_timeout_handler, flip); + } + flip->pending++; } } static void crtc_free (CoglKmsCrtc *crtc) { g_free (crtc->connectors); g_slice_free (CoglKmsCrtc, crtc); } static CoglKmsCrtc * crtc_copy (CoglKmsCrtc *from) { CoglKmsCrtc *new; new = g_slice_new (CoglKmsCrtc); *new = *from; new->connectors = g_memdup (from->connectors, from->count * sizeof(uint32_t)); return new; } static CoglBool _cogl_winsys_egl_display_setup (CoglDisplay *display, CoglError **error) { CoglDisplayEGL *egl_display = display->winsys; CoglDisplayKMS *kms_display; -- 2.3.1