diff options
author | Kenneth Graunke <kenneth@whitecape.org> | 2015-01-26 21:29:23 -0800 |
---|---|---|
committer | Dave Airlie <airlied@redhat.com> | 2015-06-26 08:58:12 +1000 |
commit | fcf9d6cd7a88057bf1a885520a09f08c4fbc9c20 (patch) | |
tree | 404f821fae9840d3ec4bc78a89f7b0752d2fd669 | |
parent | 55f78d5ecf89a444103502a052df5b209e8a6618 (diff) |
modesetting: Implement page flipping support for Present.
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.
v5: Initialize num_crtcs_on to 0 (caught by Michel Dänzer).
Signed-off-by: Kenneth Graunke <kenneth@whitecape.org>
-rw-r--r-- | hw/xfree86/drivers/modesetting/driver.c | 7 | ||||
-rw-r--r-- | hw/xfree86/drivers/modesetting/driver.h | 10 | ||||
-rw-r--r-- | hw/xfree86/drivers/modesetting/drmmode_display.c | 34 | ||||
-rw-r--r-- | hw/xfree86/drivers/modesetting/drmmode_display.h | 5 | ||||
-rw-r--r-- | hw/xfree86/drivers/modesetting/present.c | 376 |
5 files changed, 427 insertions, 5 deletions
diff --git a/hw/xfree86/drivers/modesetting/driver.c b/hw/xfree86/drivers/modesetting/driver.c index f421668c9..d14f94d28 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} }; @@ -821,6 +823,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; @@ -837,6 +842,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 843a105ed..2d63caebc 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 a03de1a5d..9dbe3c664 100644 --- a/hw/xfree86/drivers/modesetting/drmmode_display.c +++ b/hw/xfree86/drivers/modesetting/drmmode_display.c @@ -51,7 +51,8 @@ #include "driver.h" static Bool drmmode_xf86crtc_resize(ScrnInfoPtr scrn, int width, int height); -static int + +int drmmode_bo_destroy(drmmode_ptr drmmode, drmmode_bo *bo) { int ret; @@ -72,7 +73,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 @@ -143,6 +144,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 85a0ec435..09844e56d 100644 --- a/hw/xfree86/drivers/modesetting/drmmode_display.h +++ b/hw/xfree86/drivers/modesetting/drmmode_display.h @@ -63,6 +63,8 @@ typedef struct { Bool glamor; Bool shadow_enable; + /** Is Option "PageFlip" enabled? */ + Bool pageflip; void *shadow_fb; /** @@ -141,6 +143,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 43df148d1..a62c43df4 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 = 0; + 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 |