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

Kenneth Graunke kenneth at whitecape.org
Thu Jan 29 01:51:10 PST 2015


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

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

 Here's a first stab at implementing page flipping support for the
 modesetting driver, assuming you're using DRI3/Present.

 I've been running this all day, with cloned eDP and external DP monitors,
 with and without compositing, and it appears to be working fine.  I haven't
 seen any tearing with this patch; it was consistently present before while
 playing a game.  Jason Ekstrand also tested it.  drago01 also tested it on
 AMD.

 A couple of thoughts:
 1. The page flipping fields may need to be moved from modesettingRec to
    drmmode_crtc.  Not sure.
 2. Do we need to alter drmmode->front_bo at all?
 3. We can probably do this without Glamor, I just haven't tried it and
    hooked up the dumb BO stuff properly.  We should.
 4. DRI2 pageflipping?  I don't see any real point - it's a bunch of extra
    complexity for no real gain.  DRI3 is working fine.  We do need to port
    Mesa's EGL code to use DRI3, still, but that's doable.
 5. Option "Pageflip"...we should probably add one.  And man page updates.

 I doubt this is ready to merge, but I figured I'd send it so people could
 take a look and provide initial feedback.  This is pretty unfamiliar
 territory for me, so don't assume I know what I'm doing :)

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..adc2de6 100644
--- a/hw/xfree86/drivers/modesetting/drmmode_display.h
+++ b/hw/xfree86/drivers/modesetting/drmmode_display.h
@@ -139,6 +139,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 359e113..3ca1b94 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
@@ -206,6 +207,360 @@ 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)) {
+            xf86DrvMsg(scrn->scrnIndex, X_WARNING,
+                       "flip queue failed: %s\n", strerror(err));
+            ms->flip_count--;
+            ms_drm_abort_seq(scrn, seq);
+            free(flip);
+            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));
+
+    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);
+
+    /* We only support page flipping with Glamor, for now. */
+    if (!ms->drmmode.glamor)
+        return FALSE;
+
+    if (!scrn->vtSema)
+        return FALSE;
+
+    if (ms->drmmode.shadow_enable)
+        return FALSE;
+
+    if (crtc && !ms_crtc_on(crtc->devPrivate))
+        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,
 
@@ -216,9 +571,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.2.2



More information about the xorg-devel mailing list