diff --git a/hw/xfree86/drivers/modesetting/driver.c b/hw/xfree86/drivers/modesetting/driver.c index ae562a1e1..860333863 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} }; @@ -820,6 +822,8 @@ PreInit(ScrnInfoPtr pScrn, int flags) try_enable_glamor(pScrn); if (ms->drmmode.glamor) { + ms->drmmode.pageflip = + xf86ReturnOptValBool(ms->Options, OPTION_PAGEFLIP, TRUE); } else { Bool prefer_shadow = TRUE; @@ -836,6 +840,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..9ae4966fd 100644 --- a/hw/xfree86/drivers/modesetting/driver.h +++ b/hw/xfree86/drivers/modesetting/driver.h @@ -101,6 +101,12 @@ typedef struct _modesettingRec { drmEventContext event_context; + /** + * Page flipping stuff. + * @{ + */ + /** @} */ + DamagePtr damage; Bool dirty_enabled; diff --git a/hw/xfree86/drivers/modesetting/drmmode_display.c b/hw/xfree86/drivers/modesetting/drmmode_display.c index 506ea2426..8dbac07f2 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 @@ -142,6 +143,35 @@ drmmode_create_bo(drmmode_ptr drmmode, drmmode_bo *bo, return bo->dumb != NULL; } +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) @@ -368,6 +398,7 @@ drmmode_set_mode_major(xf86CrtcPtr crtc, DisplayModePtr mode, if (crtc->scrn->pScreen) xf86CrtcSetScreenSubpixelOrder(crtc->scrn->pScreen); + drmmode_crtc->need_modeset = FALSE; crtc->funcs->dpms(crtc, DPMSModeOn); /* go through all the outputs and force DPMS them back on? */ @@ -1003,6 +1034,7 @@ static void drmmode_output_dpms(xf86OutputPtr output, int mode) { drmmode_output_private_ptr drmmode_output = output->driver_private; + xf86CrtcPtr crtc = output->crtc; drmModeConnectorPtr koutput = drmmode_output->mode_output; drmmode_ptr drmmode = drmmode_output->drmmode; @@ -1011,6 +1043,13 @@ drmmode_output_dpms(xf86OutputPtr output, int mode) drmModeConnectorSetProperty(drmmode->fd, koutput->connector_id, drmmode_output->dpms_enum_id, mode); + + if (mode == DPMSModeOn && crtc) { + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + if (drmmode_crtc->need_modeset) + drmmode_set_mode_major(crtc, &crtc->mode, crtc->rotation, + crtc->x, crtc->y); + } return; } diff --git a/hw/xfree86/drivers/modesetting/drmmode_display.h b/hw/xfree86/drivers/modesetting/drmmode_display.h index 85a0ec435..fe363c577 100644 --- a/hw/xfree86/drivers/modesetting/drmmode_display.h +++ b/hw/xfree86/drivers/modesetting/drmmode_display.h @@ -46,7 +46,6 @@ typedef struct { typedef struct { int fd; unsigned fb_id; - unsigned old_fb_id; drmModeFBPtr mode_fb; int cpp; ScrnInfoPtr scrn; @@ -63,6 +62,8 @@ typedef struct { Bool glamor; Bool shadow_enable; + /** Is Option "PageFlip" enabled? */ + Bool pageflip; void *shadow_fb; /** @@ -107,6 +108,8 @@ typedef struct { uint32_t msc_prev; uint64_t msc_high; /** @} */ + + Bool need_modeset; } drmmode_crtc_private_rec, *drmmode_crtc_private_ptr; typedef struct { @@ -141,6 +144,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..090539861 100644 --- a/hw/xfree86/drivers/modesetting/present.c +++ b/hw/xfree86/drivers/modesetting/present.c @@ -44,6 +44,7 @@ #include #include "driver.h" +#include "drmmode_display.h" #if 0 #define DebugPresent(x) ErrorF x @@ -174,8 +175,10 @@ ms_present_queue_vblank(RRCrtcPtr crtc, /* If we hit EBUSY, then try to flush events. If we can't, then * this is an error. */ - if (errno != EBUSY || ms_flush_drm_events(screen) < 0) + if (errno != EBUSY || ms_flush_drm_events(screen) < 0) { + ms_drm_abort_seq(scrn, seq); return BadAlloc; + } } DebugPresent(("\t\tmq %lld seq %u msc %llu (hw msc %u)\n", (long long) event_id, seq, (long long) msc, @@ -221,6 +224,413 @@ ms_present_flush(WindowPtr window) #endif } +#ifdef GLAMOR + +/* + * Event data for an in progress flip. + * This contains a pointer to the vblank event, + * and information about the flip in progress. + * a reference to this is stored in the per-crtc + * flips. + */ +struct ms_flipdata { + ScreenPtr screen; + struct ms_present_vblank_event *event; + /* number of CRTC events referencing this */ + int flip_count; + uint64_t fe_msc; + uint64_t fe_usec; + uint32_t old_fb_id; +}; + +/* + * Per crtc pageflipping infomation, + * These are submitted to the queuing code + * one of them per crtc per flip. + */ +struct ms_crtc_pageflip { + Bool on_reference_crtc; + /* reference to the ms_flipdata */ + struct ms_flipdata *flipdata; +}; + +/** + * Free an ms_crtc_pageflip. + * + * Drops the reference count on the flipdata. + */ +static void +ms_present_flip_free(struct ms_crtc_pageflip *flip) +{ + struct ms_flipdata *flipdata = flip->flipdata; + + free(flip); + if (--flipdata->flip_count > 0) + return; + free(flipdata); +} + +/** + * 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_crtc_pageflip *flip = data; + ScreenPtr screen = flip->flipdata->screen; + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + modesettingPtr ms = modesettingPTR(scrn); + struct ms_flipdata *flipdata = flip->flipdata; + + DebugPresent(("\t\tms:fh %lld c %d msc %llu ust %llu\n", + (long long) flipdata->event->event_id, + flipdata->flip_count, + (long long) msc, (long long) ust)); + + if (flip->on_reference_crtc) { + flipdata->fe_msc = msc; + flipdata->fe_usec = ust; + } + + if (flipdata->flip_count == 1) { + DebugPresent(("\t\tms:fc %lld c %d msc %llu ust %llu\n", + (long long) flipdata->event->event_id, + flipdata->flip_count, + (long long) flipdata->fe_msc, (long long) flipdata->fe_usec)); + + + ms_present_vblank_handler(flipdata->fe_msc, + flipdata->fe_usec, + flipdata->event); + + drmModeRmFB(ms->fd, flipdata->old_fb_id); + } + ms_present_flip_free(flip); +} + +/* + * Callback for the DRM queue abort code. A flip has been aborted. + */ +static void +ms_present_flip_abort(void *data) +{ + struct ms_crtc_pageflip *flip = data; + struct ms_flipdata *flipdata = flip->flipdata; + + DebugPresent(("\t\tms:fa %lld c %d\n", (long long) flipdata->event->event_id, flipdata->flip_count)); + + if (flipdata->flip_count == 1) + free(flipdata->event); + + ms_present_flip_free(flip); +} + +static Bool +queue_flip_on_crtc(ScreenPtr screen, xf86CrtcPtr crtc, + struct ms_flipdata *flipdata, + int ref_crtc_vblank_pipe, uint32_t flags) +{ + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + modesettingPtr ms = modesettingPTR(scrn); + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + struct ms_crtc_pageflip *flip; + uint32_t seq; + int err; + + flip = calloc(1, sizeof(struct ms_crtc_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->flipdata = flipdata; + + 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 c %d -> %d seq %llu\n", + (long long) flipdata->event->event_id, + flipdata->flip_count, flipdata->flip_count + 1, + (long long) seq)); + + /* take a reference on flipdata for use in flip */ + flipdata->flip_count++; + + while (drmModePageFlip(ms->fd, drmmode_crtc->mode_crtc->crtc_id, + ms->drmmode.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, + struct ms_present_vblank_event *event, + int ref_crtc_vblank_pipe, + Bool async) +{ + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + modesettingPtr ms = modesettingPTR(scrn); + xf86CrtcConfigPtr config = XF86_CRTC_CONFIG_PTR(scrn); + drmmode_bo new_front_bo; + uint32_t flags; + int i; + struct ms_flipdata *flipdata; + glamor_block_handler(screen); + + 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"); + return FALSE; + } + + flipdata = calloc(1, sizeof(struct ms_flipdata)); + if (!flipdata) { + drmmode_bo_destroy(&ms->drmmode, &new_front_bo); + xf86DrvMsg(scrn->scrnIndex, X_ERROR, + "Failed to allocate flipdata.\n"); + return FALSE; + } + + flipdata->event = event; + flipdata->screen = screen; + + /* + * Take a local reference on flipdata. + * if the first flip fails, the sequence abort + * code will free the crtc flip data, and drop + * it's reference which would cause this to be + * freed when we still required it. + */ + flipdata->flip_count++; + + /* Create a new handle for the back buffer */ + flipdata->old_fb_id = ms->drmmode.fb_id; + 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), &ms->drmmode.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. + */ + 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, flipdata, + ref_crtc_vblank_pipe, + flags)) { + goto error_undo; + } + } + + /* + * Do we have more than our local reference, + * if so and no errors, then drop our local + * reference and return now. + */ + if (flipdata->flip_count > 1) { + flipdata->flip_count--; + return TRUE; + } + +error_undo: + + /* + * Have we just got the local reference? + * free the framebuffer if so since nobody successfully + * submitted anything + */ + if (flipdata->flip_count == 1) { + drmModeRmFB(ms->fd, ms->drmmode.fb_id); + ms->drmmode.fb_id = flipdata->old_fb_id; + } + +error_out: + xf86DrvMsg(scrn->scrnIndex, X_WARNING, "Page flip failed: %s\n", + strerror(errno)); + /* if only the local reference - free the structure, + * else drop the local reference and return */ + if (flipdata->flip_count == 1) + free(flipdata); + else + flipdata->flip_count--; + + 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; + struct ms_present_vblank_event *event; + + if (!ms_present_check_flip(crtc, screen->root, pixmap, sync_flip)) + return FALSE; + + event = calloc(1, sizeof(struct ms_present_vblank_event)); + if (!event) + return FALSE; + + event->event_id = event_id; + ret = ms_do_pageflip(screen, pixmap, event, drmmode_crtc->vblank_pipe, !sync_flip); + 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); + xf86CrtcConfigPtr config = XF86_CRTC_CONFIG_PTR(scrn); + int i; + struct ms_present_vblank_event *event; + + event = calloc(1, sizeof(struct ms_present_vblank_event)); + if (!event) + return; + + event->event_id = event_id; + + if (ms_present_check_flip(NULL, screen->root, pixmap, TRUE) && + ms_do_pageflip(screen, pixmap, event, -1, FALSE)) { + return; + } + + for (i = 0; i < config->num_crtc; i++) { + xf86CrtcPtr crtc = config->crtc[i]; + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + + if (!crtc->enabled) + continue; + + if (drmmode_crtc->dpms_mode == DPMSModeOn) + crtc->funcs->set_mode_major(crtc, &crtc->mode, crtc->rotation, + crtc->x, crtc->y); + else + drmmode_crtc->need_modeset = TRUE; + } + + present_event_notify(event_id, 0, 0); +} +#endif + static present_screen_info_rec ms_present_screen_info = { .version = PRESENT_SCREEN_INFO_VERSION, @@ -231,13 +641,24 @@ 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 ms_present_screen_init(ScreenPtr screen) { + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + modesettingPtr ms = modesettingPTR(scrn); + uint64_t value; + int ret; + + ret = drmGetCap(ms->fd, DRM_CAP_ASYNC_PAGE_FLIP, &value); + if (ret == 0 && value == 1) + ms_present_screen_info.capabilities |= PresentCapabilityAsync; + return present_screen_init(screen, &ms_present_screen_info); }