[PATCH 5/5] modesetting: Implement page flipping support for Present.

Kenneth Graunke kenneth at whitecape.org
Tue Apr 21 17:58:44 PDT 2015


Based on code by Keith Packard, Eric Anholt, and Jason Ekstrand.

v2:
- Fix double free and flip_count underrun (caught by Mario Kleiner).
- Don't leak flip_vblank_event on the error_out path (Mario).
- Use the updated ms_flush_drm_events API (Mario, Ken).

v3: Hack around DPMS shenanigans.  If all monitors are DPMS off, then
    there is no active framebuffer; attempting to pageflip will hit the
    error_undo paths, causing us to drmModeRmFB with no framebuffer,
    which confuses the kernel into doing full modesets and generally
    breaks things.  To avoid this, make ms_present_check_flip check that
    some CRTCs are enabled and DPMS on.  This is an ugly hack that would
    get better with atomic modesetting, or some core Present work.

v4:
- Don't do pageflipping if CRTCs are rotated (caught by Jason Ekstrand).
- Make pageflipping optional (Option "PageFlip" in xorg.conf.d), but
  enabled by default.

Signed-off-by: Kenneth Graunke <kenneth at whitecape.org>
---
 hw/xfree86/drivers/modesetting/driver.c          |   7 +
 hw/xfree86/drivers/modesetting/driver.h          |  10 +
 hw/xfree86/drivers/modesetting/drmmode_display.c |  33 +-
 hw/xfree86/drivers/modesetting/drmmode_display.h |   5 +
 hw/xfree86/drivers/modesetting/present.c         | 376 ++++++++++++++++++++++-
 5 files changed, 426 insertions(+), 5 deletions(-)

diff --git a/hw/xfree86/drivers/modesetting/driver.c b/hw/xfree86/drivers/modesetting/driver.c
index e2f3846..bca0a5f 100644
--- a/hw/xfree86/drivers/modesetting/driver.c
+++ b/hw/xfree86/drivers/modesetting/driver.c
@@ -123,6 +123,7 @@ typedef enum {
     OPTION_DEVICE_PATH,
     OPTION_SHADOW_FB,
     OPTION_ACCEL_METHOD,
+    OPTION_PAGEFLIP,
 } modesettingOpts;
 
 static const OptionInfoRec Options[] = {
@@ -130,6 +131,7 @@ static const OptionInfoRec Options[] = {
     {OPTION_DEVICE_PATH, "kmsdev", OPTV_STRING, {0}, FALSE},
     {OPTION_SHADOW_FB, "ShadowFB", OPTV_BOOLEAN, {0}, FALSE},
     {OPTION_ACCEL_METHOD, "AccelMethod", OPTV_STRING, {0}, FALSE},
+    {OPTION_PAGEFLIP, "PageFlip", OPTV_BOOLEAN, {0}, FALSE},
     {-1, NULL, OPTV_NONE, {0}, FALSE}
 };
 
@@ -783,6 +785,9 @@ PreInit(ScrnInfoPtr pScrn, int flags)
 
     if (ms->drmmode.glamor) {
         xf86LoadSubModule(pScrn, "dri2");
+
+        ms->drmmode.pageflip =
+            xf86ReturnOptValBool(ms->Options, OPTION_PAGEFLIP, TRUE);
     } else {
         Bool prefer_shadow = TRUE;
 
@@ -799,6 +804,8 @@ PreInit(ScrnInfoPtr pScrn, int flags)
                    "ShadowFB: preferred %s, enabled %s\n",
                    prefer_shadow ? "YES" : "NO",
                    ms->drmmode.shadow_enable ? "YES" : "NO");
+
+        ms->drmmode.pageflip = FALSE;
     }
 
     if (drmmode_pre_init(pScrn, &ms->drmmode, pScrn->bitsPerPixel / 8) == FALSE) {
diff --git a/hw/xfree86/drivers/modesetting/driver.h b/hw/xfree86/drivers/modesetting/driver.h
index 843a105..2d63cae 100644
--- a/hw/xfree86/drivers/modesetting/driver.h
+++ b/hw/xfree86/drivers/modesetting/driver.h
@@ -101,6 +101,16 @@ typedef struct _modesettingRec {
 
     drmEventContext event_context;
 
+    /**
+     * Page flipping stuff.
+     *  @{
+     */
+    int flip_count;
+    uint64_t fe_msc;
+    uint64_t fe_usec;
+    struct ms_present_vblank_event *flip_vblank_event;
+    /** @} */
+
     DamagePtr damage;
     Bool dirty_enabled;
 
diff --git a/hw/xfree86/drivers/modesetting/drmmode_display.c b/hw/xfree86/drivers/modesetting/drmmode_display.c
index 1ea799b..7fd8669 100644
--- a/hw/xfree86/drivers/modesetting/drmmode_display.c
+++ b/hw/xfree86/drivers/modesetting/drmmode_display.c
@@ -50,7 +50,7 @@
 
 #include "driver.h"
 
-static int
+int
 drmmode_bo_destroy(drmmode_ptr drmmode, drmmode_bo *bo)
 {
     int ret;
@@ -71,7 +71,7 @@ drmmode_bo_destroy(drmmode_ptr drmmode, drmmode_bo *bo)
     return 0;
 }
 
-static uint32_t
+uint32_t
 drmmode_bo_get_pitch(drmmode_bo *bo)
 {
 #ifdef GLAMOR_HAS_GBM
@@ -142,6 +142,35 @@ drmmode_create_bo(drmmode_ptr drmmode, drmmode_bo *bo,
 }
 
 Bool
+drmmode_bo_for_pixmap(drmmode_ptr drmmode, drmmode_bo *bo, PixmapPtr pixmap)
+{
+    ScreenPtr screen = xf86ScrnToScreen(drmmode->scrn);
+    uint16_t pitch;
+    uint32_t size;
+    int fd;
+
+#ifdef GLAMOR_HAS_GBM
+    if (drmmode->glamor) {
+        bo->gbm = glamor_gbm_bo_from_pixmap(screen, pixmap);
+        bo->dumb = NULL;
+        return bo->gbm != NULL;
+    }
+#endif
+
+    fd = glamor_fd_from_pixmap(screen, pixmap, &pitch, &size);
+    if (fd < 0) {
+        xf86DrvMsg(drmmode->scrn->scrnIndex, X_ERROR,
+                   "Failed to get fd for flip to new front.\n");
+        return FALSE;
+    }
+
+    bo->dumb = dumb_get_bo_from_fd(drmmode->fd, fd, pitch, size);
+    close(fd);
+
+    return bo->dumb != NULL;
+}
+
+Bool
 drmmode_SetSlaveBO(PixmapPtr ppix,
                    drmmode_ptr drmmode, int fd_handle, int pitch, int size)
 {
diff --git a/hw/xfree86/drivers/modesetting/drmmode_display.h b/hw/xfree86/drivers/modesetting/drmmode_display.h
index 3a8959a..2cbf8be 100644
--- a/hw/xfree86/drivers/modesetting/drmmode_display.h
+++ b/hw/xfree86/drivers/modesetting/drmmode_display.h
@@ -64,6 +64,8 @@ typedef struct {
 
     Bool glamor;
     Bool shadow_enable;
+    /** Is Option "PageFlip" enabled? */
+    Bool pageflip;
     void *shadow_fb;
 
     /**
@@ -139,6 +141,9 @@ extern DevPrivateKeyRec msPixmapPrivateKeyRec;
 
 #define msGetPixmapPriv(drmmode, p) ((msPixmapPrivPtr)dixGetPrivateAddr(&(p)->devPrivates, &(drmmode)->pixmapPrivateKeyRec))
 
+Bool drmmode_bo_for_pixmap(drmmode_ptr drmmode, drmmode_bo *bo, PixmapPtr pixmap);
+int drmmode_bo_destroy(drmmode_ptr drmmode, drmmode_bo *bo);
+uint32_t drmmode_bo_get_pitch(drmmode_bo *bo);
 uint32_t drmmode_bo_get_handle(drmmode_bo *bo);
 Bool drmmode_glamor_handle_new_screen_pixmap(drmmode_ptr drmmode);
 void *drmmode_map_slave_bo(drmmode_ptr drmmode, msPixmapPrivPtr ppriv);
diff --git a/hw/xfree86/drivers/modesetting/present.c b/hw/xfree86/drivers/modesetting/present.c
index 3e8e72a..0ec0f40 100644
--- a/hw/xfree86/drivers/modesetting/present.c
+++ b/hw/xfree86/drivers/modesetting/present.c
@@ -44,6 +44,7 @@
 #include <present.h>
 
 #include "driver.h"
+#include "drmmode_display.h"
 
 #if 0
 #define DebugPresent(x) ErrorF x
@@ -221,6 +222,373 @@ ms_present_flush(WindowPtr window)
 #endif
 }
 
+#ifdef GLAMOR
+struct ms_pageflip {
+    ScreenPtr screen;
+    Bool on_reference_crtc;
+};
+
+/**
+ * Notify Present that the flip is complete
+ */
+static void
+ms_pageflip_complete(ScreenPtr screen)
+{
+    ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
+    modesettingPtr ms = modesettingPTR(scrn);
+    uint64_t event_id = ms->flip_vblank_event->event_id;
+
+    DebugPresent(("\t\tms:fc %lld %p c %d msc %llu ust %llu\n",
+                  (long long) event_id, ms->flip_vblank_event,
+                  ms->flip_count,
+                  (long long) ms->fe_msc, (long long) ms->fe_usec));
+
+    free(ms->flip_vblank_event);
+    ms->flip_vblank_event = NULL;
+
+    /* Release framebuffer */
+    drmModeRmFB(ms->fd, ms->drmmode.old_fb_id);
+
+    /* Notify Present that the flip is complete. */
+    present_event_notify(event_id, ms->fe_usec, ms->fe_msc);
+}
+
+/**
+ * Called after processing a pageflip complete event from DRM.
+ *
+ * Update the saved msc/ust values as needed, then check to see if the
+ * whole set of events are complete and notify the application at that
+ * point.
+ */
+static Bool
+ms_handle_pageflip(struct ms_pageflip *flip, uint64_t msc, uint64_t usec)
+{
+    ScreenPtr screen = flip->screen;
+    ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
+    modesettingPtr ms = modesettingPTR(scrn);
+
+    if (flip->on_reference_crtc) {
+        /* Cache msc, ust for later delivery with a Present event or
+         * GLX reply.
+         */
+        ms->fe_msc = msc;
+        ms->fe_usec = usec;
+    }
+    free(flip);
+
+    ms->flip_count--;
+
+    /* Tell the caller if this was the last DRM flip complete event expected. */
+    return ms->flip_count == 0;
+}
+
+/**
+ * Callback for the DRM event queue when a single flip has completed
+ *
+ * Once the flip has been completed on all pipes, notify the
+ * extension code telling it when that happened
+ */
+static void
+ms_flip_handler(uint64_t msc, uint64_t ust, void *data)
+{
+    struct ms_pageflip *flip = data;
+    ScreenPtr screen = flip->screen;
+    ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
+    modesettingPtr ms = modesettingPTR(scrn);
+    (void) ms;
+
+    DebugPresent(("\t\tms:fh %lld %p c %d msc %llu ust %llu\n",
+                 (long long) ms->flip_vblank_event->event_id,
+                 ms->flip_vblank_event, ms->flip_count,
+                 (long long) msc, (long long) ust));
+
+    if (ms_handle_pageflip(flip, msc, ust))
+        ms_pageflip_complete(screen);
+}
+
+/*
+ * Callback for the DRM queue abort code.  A flip has been aborted.
+ */
+static void
+ms_present_flip_abort(void *data)
+{
+    struct ms_pageflip *flip = data;
+    ScreenPtr screen = flip->screen;
+    ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
+    modesettingPtr ms = modesettingPTR(scrn);
+    struct ms_present_vblank_event *event = ms->flip_vblank_event;
+
+    DebugPresent(("\t\tms:fa %lld\n", (long long) event->event_id));
+
+    if (ms_handle_pageflip(flip, 0, 0)) {
+        /* Present abort handling */
+        free(event);
+        ms->flip_vblank_event = NULL;
+
+        /* Release framebuffer */
+        drmModeRmFB(ms->drmmode.fd, ms->drmmode.old_fb_id);
+    }
+}
+
+static Bool
+queue_flip_on_crtc(ScreenPtr screen, xf86CrtcPtr crtc,
+                   int ref_crtc_vblank_pipe, int new_fb_id, uint32_t flags)
+{
+    ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
+    modesettingPtr ms = modesettingPTR(scrn);
+    drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
+    struct ms_pageflip *flip;
+    uint32_t seq;
+    int err;
+
+    flip = calloc(1, sizeof(struct ms_pageflip));
+    if (flip == NULL) {
+        xf86DrvMsg(scrn->scrnIndex, X_WARNING,
+                   "flip queue: carrier alloc failed.\n");
+        return FALSE;
+    }
+
+    /* Only the reference crtc will finally deliver its page flip
+     * completion event. All other crtc's events will be discarded.
+     */
+    flip->on_reference_crtc = (drmmode_crtc->vblank_pipe == ref_crtc_vblank_pipe);
+    flip->screen = screen;
+
+    seq = ms_drm_queue_alloc(crtc, flip, ms_flip_handler, ms_present_flip_abort);
+    if (!seq) {
+        free(flip);
+        return FALSE;
+    }
+
+    DebugPresent(("\t\tms:fq %lld %p c %d -> %d seq %llu\n",
+                  (long long) ms->flip_vblank_event->event_id,
+                  ms->flip_vblank_event, ms->flip_count, ms->flip_count + 1,
+                  (long long) seq));
+    assert(ms->flip_count >= 0);
+    ms->flip_count++;
+
+    while (drmModePageFlip(ms->fd, drmmode_crtc->mode_crtc->crtc_id,
+                           new_fb_id, flags, (void *) (uintptr_t) seq)) {
+        err = errno;
+        /* We may have failed because the event queue was full.  Flush it
+         * and retry.  If there was nothing to flush, then we failed for
+         * some other reason and should just return an error.
+         */
+        if (ms_flush_drm_events(screen) <= 0) {
+            xf86DrvMsg(scrn->scrnIndex, X_WARNING,
+                       "flip queue failed: %s\n", strerror(err));
+            /* Aborting will also decrement flip_count and free(flip). */
+            ms_drm_abort_seq(scrn, seq);
+            return FALSE;
+        }
+
+        /* We flushed some events, so try again. */
+        xf86DrvMsg(scrn->scrnIndex, X_WARNING, "flip queue retry\n");
+    }
+
+    /* The page flip succeded. */
+    return TRUE;
+}
+
+
+static Bool
+ms_do_pageflip(ScreenPtr screen,
+               PixmapPtr new_front,
+               int ref_crtc_vblank_pipe,
+               Bool async,
+               uint64_t event_id)
+{
+    ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
+    modesettingPtr ms = modesettingPTR(scrn);
+    xf86CrtcConfigPtr config = XF86_CRTC_CONFIG_PTR(scrn);
+    drmmode_bo new_front_bo;
+    uint32_t new_fb_id;
+    uint32_t flags;
+    int i;
+
+    glamor_block_handler(screen);
+
+    ms->flip_vblank_event = calloc(1, sizeof(struct ms_present_vblank_event));
+    if (!ms->flip_vblank_event)
+        return FALSE;
+    ms->flip_vblank_event->event_id = event_id;
+
+    new_front_bo.gbm = glamor_gbm_bo_from_pixmap(screen, new_front);
+    new_front_bo.dumb = NULL;
+    if (!new_front_bo.gbm) {
+        xf86DrvMsg(scrn->scrnIndex, X_ERROR,
+                   "Failed to get GBM bo for flip to new front.\n");
+        free(ms->flip_vblank_event);
+        ms->flip_vblank_event = NULL;
+        return FALSE;
+    }
+
+    /* Create a new handle for the back buffer */
+    if (drmModeAddFB(ms->fd, scrn->virtualX, scrn->virtualY,
+                     scrn->depth, scrn->bitsPerPixel,
+                     drmmode_bo_get_pitch(&new_front_bo),
+                     drmmode_bo_get_handle(&new_front_bo), &new_fb_id))
+        goto error_out;
+
+    drmmode_bo_destroy(&ms->drmmode, &new_front_bo);
+
+    flags = DRM_MODE_PAGE_FLIP_EVENT;
+    if (async)
+        flags |= DRM_MODE_PAGE_FLIP_ASYNC;
+
+    /* Queue flips on all enabled CRTCs.
+     *
+     * Note that if/when we get per-CRTC buffers, we'll have to update this.
+     * Right now it assumes a single shared fb across all CRTCs, with the
+     * kernel fixing up the offset of each CRTC as necessary.
+     *
+     * Also, flips queued on disabled or incorrectly configured displays
+     * may never complete; this is a configuration error.
+     */
+    ms->fe_msc = 0;
+    ms->fe_usec = 0;
+
+    for (i = 0; i < config->num_crtc; i++) {
+        xf86CrtcPtr crtc = config->crtc[i];
+
+        if (!ms_crtc_on(crtc))
+            continue;
+
+        if (!queue_flip_on_crtc(screen, crtc, ref_crtc_vblank_pipe, new_fb_id,
+                                flags)) {
+            goto error_undo;
+        }
+    }
+
+    ms->drmmode.old_fb_id = ms->drmmode.fb_id;
+    ms->drmmode.fb_id = new_fb_id;
+
+    assert(ms->flip_count >= 0);
+    if (ms->flip_count == 0) {
+        ms_pageflip_complete(screen);
+    }
+
+    return TRUE;
+
+error_undo:
+    drmModeRmFB(ms->fd, new_fb_id);
+    for (i = 0; i < config->num_crtc; i++) {
+        if (config->crtc[i]->enabled) {
+            ErrorF("XXX: crtc apply\n");
+            /* intel_crtc_apply(config->crtc[i]); */
+        }
+    }
+
+error_out:
+    xf86DrvMsg(scrn->scrnIndex, X_WARNING, "Page flip failed: %s\n",
+               strerror(errno));
+
+    free(ms->flip_vblank_event);
+    ms->flip_vblank_event = NULL;
+
+    assert(ms->flip_count >= 0);
+    ms->flip_count = 0;
+    return FALSE;
+}
+
+/*
+ * Test to see if page flipping is possible on the target crtc
+ */
+static Bool
+ms_present_check_flip(RRCrtcPtr crtc,
+                      WindowPtr window,
+                      PixmapPtr pixmap,
+                      Bool sync_flip)
+{
+    ScreenPtr screen = window->drawable.pScreen;
+    ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
+    modesettingPtr ms = modesettingPTR(scrn);
+    xf86CrtcConfigPtr config = XF86_CRTC_CONFIG_PTR(scrn);
+    int num_crtcs_on;
+    int i;
+
+    if (!ms->drmmode.pageflip)
+        return FALSE;
+
+    if (!scrn->vtSema)
+        return FALSE;
+
+    for (i = 0; i < config->num_crtc; i++) {
+        drmmode_crtc_private_ptr drmmode_crtc = config->crtc[i]->driver_private;
+
+        /* Don't do pageflipping if CRTCs are rotated. */
+        if (drmmode_crtc->rotate_bo.gbm)
+            return FALSE;
+
+        if (ms_crtc_on(config->crtc[i]))
+            num_crtcs_on++;
+    }
+
+    /* We can't do pageflipping if all the CRTCs are off. */
+    if (num_crtcs_on == 0)
+        return FALSE;
+
+    /* Check stride, can't change that on flip */
+    if (pixmap->devKind != drmmode_bo_get_pitch(&ms->drmmode.front_bo))
+        return FALSE;
+
+    /* Make sure there's a bo we can get to */
+    /* XXX: actually do this.  also...is it sufficient?
+     * if (!glamor_get_pixmap_private(pixmap))
+     *     return FALSE;
+     */
+
+    return TRUE;
+}
+
+/*
+ * Queue a flip on 'crtc' to 'pixmap' at 'target_msc'. If 'sync_flip' is true,
+ * then wait for vblank. Otherwise, flip immediately
+ */
+static Bool
+ms_present_flip(RRCrtcPtr crtc,
+                uint64_t event_id,
+                uint64_t target_msc,
+                PixmapPtr pixmap,
+                Bool sync_flip)
+{
+    ScreenPtr screen = crtc->pScreen;
+    ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
+    xf86CrtcPtr xf86_crtc = crtc->devPrivate;
+    drmmode_crtc_private_ptr drmmode_crtc = xf86_crtc->driver_private;
+    Bool ret;
+
+    if (!ms_present_check_flip(crtc, screen->root, pixmap, sync_flip))
+        return FALSE;
+
+    ret = ms_do_pageflip(screen, pixmap, drmmode_crtc->vblank_pipe, !sync_flip,
+                         event_id);
+    if (!ret)
+        xf86DrvMsg(scrn->scrnIndex, X_ERROR, "present flip failed\n");
+
+    return ret;
+}
+
+/*
+ * Queue a flip back to the normal frame buffer
+ */
+static void
+ms_present_unflip(ScreenPtr screen, uint64_t event_id)
+{
+    ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
+    PixmapPtr pixmap = screen->GetScreenPixmap(screen);
+    Bool ret;
+
+    if (!ms_present_check_flip(NULL, screen->root, pixmap, FALSE))
+        return;
+
+    ret = ms_do_pageflip(screen, pixmap, -1, FALSE, event_id);
+    if (!ret) {
+        xf86DrvMsg(scrn->scrnIndex, X_ERROR, "present unflip failed\n");
+    }
+}
+#endif
+
 static present_screen_info_rec ms_present_screen_info = {
     .version = PRESENT_SCREEN_INFO_VERSION,
 
@@ -231,9 +599,11 @@ static present_screen_info_rec ms_present_screen_info = {
     .flush = ms_present_flush,
 
     .capabilities = PresentCapabilityNone,
-    .check_flip = 0,
-    .flip = 0,
-    .unflip = 0,
+    .check_flip = ms_present_check_flip,
+#ifdef GLAMOR
+    .flip = ms_present_flip,
+    .unflip = ms_present_unflip,
+#endif
 };
 
 Bool
-- 
2.3.5



More information about the xorg-devel mailing list