[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