Mario Kleiner
2011-Sep-02 17:33 UTC
[Nouveau] nouveau-ddx: Improvements to DRI2 kms pageflip and swapbuffers support.
Hi. The following series of three patches provides some improvements and bug fixes to DRI2 swap scheduling, kms pageflipping and pageflip completion timestamping. And a fix for desktop corruption when switching between redirected and unredirected fullscreen windows. These are mostly direct translations of similar functionality and bug-fixes from the intel ddx and ati ddx. All successfully tested on a GeForce-7800 GTX and QuadroFX-570 in single display mode and dual-display modes (xinerama desktop spanning and clone mode). I'll send another separate patch for the Linux nouveau-kms driver's pageflip completion routine, so it reports back proper pageflip completion events with correct timestamp and vblank count (according to OML_sync_control spec). Lucas Stach has an almost finished patch for the nouveau-kms driver to implement the drm high-precision vblank timestamping hook. All patches taken together were tested on NV-47 with high precision measurement equipment. Results show that the pageflip completion timestamps reported with these patches are accurate with respect to reality, with a residual error of less than 40 microseconds. Please review and apply as you see fit. thanks, -mario
Mario Kleiner
2011-Sep-02 17:33 UTC
[Nouveau] [PATCH 1/3] dri2: Implement handling of pageflip completion events.
Requests pageflip completion events from the kernel. Implements pageflip completion handler to finalize and timestamp swaps. Completion handler includes a consistency check, and disambiguation if multiple crtc's are involved in a pageflip (e.g., clone mode, extendend desktop). Only the timestamp of the crtc whose vblank event initially triggered the swap is used, but handler waits for flip completion on all involved crtc's before completing the swap and releasing the old framebuffer. This code is almost identical to the code used in the ati/radeon ddx and intel ddx. Signed-off-by: Mario Kleiner <mario.kleiner at tuebingen.mpg.de> --- src/drmmode_display.c | 105 +++++++++++++++++++++++++++++++++++++++++++++++-- src/nouveau_dri2.c | 89 +++++++++++++++++++++++++++++++++++++++-- src/nv_proto.h | 5 ++- 3 files changed, 189 insertions(+), 10 deletions(-) diff --git a/src/drmmode_display.c b/src/drmmode_display.c index 3afef66..bcb2a94 100644 --- a/src/drmmode_display.c +++ b/src/drmmode_display.c @@ -83,6 +83,21 @@ typedef struct { drmmode_prop_ptr props; } drmmode_output_private_rec, *drmmode_output_private_ptr; +typedef struct { + drmmode_ptr drmmode; + unsigned old_fb_id; + int flip_count; + void *event_data; + unsigned int fe_frame; + unsigned int fe_tv_sec; + unsigned int fe_tv_usec; +} drmmode_flipdata_rec, *drmmode_flipdata_ptr; + +typedef struct { + drmmode_flipdata_ptr flipdata; + Bool dispatch_me; +} drmmode_flipevtcarrier_rec, *drmmode_flipevtcarrier_ptr; + static void drmmode_output_dpms(xf86OutputPtr output, int mode); static drmmode_ptr @@ -1246,13 +1261,17 @@ drmmode_cursor_init(ScreenPtr pScreen) } Bool -drmmode_page_flip(DrawablePtr draw, PixmapPtr back, void *priv) +drmmode_page_flip(DrawablePtr draw, PixmapPtr back, void *priv, + unsigned int ref_crtc_hw_id) { ScrnInfoPtr scrn = xf86Screens[draw->pScreen->myNum]; xf86CrtcConfigPtr config = XF86_CRTC_CONFIG_PTR(scrn); drmmode_crtc_private_ptr crtc = config->crtc[0]->driver_private; drmmode_ptr mode = crtc->drmmode; int ret, i, old_fb_id; + int emitted = 0; + drmmode_flipdata_ptr flipdata; + drmmode_flipevtcarrier_ptr flipcarrier; old_fb_id = mode->fb_id; ret = drmModeAddFB(mode->fd, scrn->virtualX, scrn->virtualY, @@ -1265,24 +1284,64 @@ drmmode_page_flip(DrawablePtr draw, PixmapPtr back, void *priv) return FALSE; } + flipdata = calloc(1, sizeof(drmmode_flipdata_rec)); + if (!flipdata) { + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "flip queue: data alloc failed.\n"); + goto error_undo; + } + + flipdata->event_data = priv; + flipdata->drmmode = mode; + for (i = 0; i < config->num_crtc; i++) { crtc = config->crtc[i]->driver_private; if (!config->crtc[i]->enabled) continue; + flipdata->flip_count++; + + flipcarrier = calloc(1, sizeof(drmmode_flipevtcarrier_rec)); + if (!flipcarrier) { + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "flip queue: carrier alloc failed.\n"); + if (emitted == 0) + free(flipdata); + goto error_undo; + } + + /* Only the reference crtc will finally deliver its page flip + * completion event. All other crtc's events will be discarded. + */ + flipcarrier->dispatch_me = ((1 << i) == ref_crtc_hw_id); + flipcarrier->flipdata = flipdata; + ret = drmModePageFlip(mode->fd, crtc->mode_crtc->crtc_id, - mode->fb_id, 0, priv); + mode->fb_id, DRM_MODE_PAGE_FLIP_EVENT, + flipcarrier); if (ret) { xf86DrvMsg(scrn->scrnIndex, X_WARNING, "flip queue failed: %s\n", strerror(errno)); - return FALSE; + + free(flipcarrier); + if (emitted == 0) + free(flipdata); + goto error_undo; } + + emitted++; } - drmModeRmFB(mode->fd, old_fb_id); + /* Will release old fb after all crtc's completed flip. */ + flipdata->old_fb_id = old_fb_id; return TRUE; + +error_undo: + drmModeRmFB(mode->fd, mode->fb_id); + mode->fb_id = old_fb_id; + return FALSE; } #ifdef HAVE_LIBUDEV @@ -1348,6 +1407,40 @@ drmmode_uevent_fini(ScrnInfoPtr scrn) } static void +drmmode_flip_handler(int fd, unsigned int frame, unsigned int tv_sec, + unsigned int tv_usec, void *event_data) +{ + drmmode_flipevtcarrier_ptr flipcarrier = event_data; + drmmode_flipdata_ptr flipdata = flipcarrier->flipdata; + drmmode_ptr drmmode = flipdata->drmmode; + + /* Is this the event whose info shall be delivered to higher level? */ + if (flipcarrier->dispatch_me) { + /* Yes: Cache msc, ust for later delivery. */ + flipdata->fe_frame = frame; + flipdata->fe_tv_sec = tv_sec; + flipdata->fe_tv_usec = tv_usec; + } + free(flipcarrier); + + /* Last crtc completed flip? */ + flipdata->flip_count--; + if (flipdata->flip_count > 0) + return; + + /* Release framebuffer */ + drmModeRmFB(drmmode->fd, flipdata->old_fb_id); + + if (flipdata->event_data == NULL) + return; + + /* Deliver cached msc, ust from reference crtc to flip event handler */ + nouveau_dri2_flip_event_handler(flipdata->fe_frame, flipdata->fe_tv_sec, + flipdata->fe_tv_usec, flipdata->event_data); + free(flipdata); +} + +static void drmmode_wakeup_handler(pointer data, int err, pointer p) { ScrnInfoPtr scrn = data; @@ -1377,6 +1470,10 @@ drmmode_screen_init(ScreenPtr pScreen) /* Plug in a vblank event handler */ drmmode->event_context.version = DRM_EVENT_CONTEXT_VERSION; drmmode->event_context.vblank_handler = nouveau_dri2_vblank_handler; + + /* Plug in a pageflip completion event handler */ + drmmode->event_context.page_flip_handler = drmmode_flip_handler; + AddGeneralSocket(drmmode->fd); /* Register a wakeup handler to get informed on DRM events */ diff --git a/src/nouveau_dri2.c b/src/nouveau_dri2.c index 1a68ed3..87eaf08 100644 --- a/src/nouveau_dri2.c +++ b/src/nouveau_dri2.c @@ -136,6 +136,7 @@ struct nouveau_dri2_vblank_state { DRI2BufferPtr src; DRI2SwapEventPtr func; void *data; + unsigned int frame; }; static Bool @@ -222,6 +223,18 @@ nouveau_dri2_finish_swap(DrawablePtr draw, unsigned int frame, REGION_INIT(0, ®, (&(BoxRec){ 0, 0, draw->width, draw->height }), 0); REGION_TRANSLATE(0, ®, draw->x, draw->y); + /* Main crtc for this drawable shall finally deliver pageflip event. */ + unsigned int ref_crtc_hw_id = nv_window_belongs_to_crtc(scrn, draw->x, + draw->y, + draw->width, + draw->height); + + /* Whenever first crtc is involved, choose it as reference, as + * its vblank event triggered this swap. + */ + if (ref_crtc_hw_id & 1) + ref_crtc_hw_id = 1; + /* Throttle on the previous frame before swapping */ nouveau_bo_map(dst_bo, NOUVEAU_BO_RD); nouveau_bo_unmap(dst_bo); @@ -246,7 +259,7 @@ nouveau_dri2_finish_swap(DrawablePtr draw, unsigned int frame, if (DRI2CanFlip(draw)) { type = DRI2_FLIP_COMPLETE; - ret = drmmode_page_flip(draw, src_pix, s); + ret = drmmode_page_flip(draw, src_pix, s, ref_crtc_hw_id); if (!ret) goto out; } @@ -255,6 +268,10 @@ nouveau_dri2_finish_swap(DrawablePtr draw, unsigned int frame, SWAP(nouveau_pixmap(dst_pix)->bo, nouveau_pixmap(src_pix)->bo); DamageRegionProcessPending(draw); + + /* If it is a page flip, finish it in the flip event handler. */ + if (type == DRI2_FLIP_COMPLETE) + return; } else { type = DRI2_BLIT_COMPLETE; @@ -289,7 +306,7 @@ nouveau_dri2_schedule_swap(ClientPtr client, DrawablePtr draw, DRI2SwapEventPtr func, void *data) { struct nouveau_dri2_vblank_state *s; - CARD64 current_msc; + CARD64 current_msc, expect_msc; int ret; /* Initialize a swap structure */ @@ -298,7 +315,7 @@ nouveau_dri2_schedule_swap(ClientPtr client, DrawablePtr draw, return FALSE; *s = (struct nouveau_dri2_vblank_state) - { SWAP, client, draw->id, dst, src, func, data }; + { SWAP, client, draw->id, dst, src, func, data, 0 }; if (can_sync_to_vblank(draw)) { /* Get current sequence */ @@ -316,10 +333,10 @@ nouveau_dri2_schedule_swap(ClientPtr client, DrawablePtr draw, ret = nouveau_wait_vblank(draw, DRM_VBLANK_ABSOLUTE | DRM_VBLANK_EVENT, max(current_msc, *target_msc - 1), - NULL, NULL, s); + &expect_msc, NULL, s); if (ret) goto fail; - + s->frame = (unsigned int) expect_msc & 0xffffffff; } else { /* We can't/don't want to sync to vblank, just swap. */ nouveau_dri2_finish_swap(draw, 0, 0, 0, s); @@ -423,6 +440,68 @@ nouveau_dri2_vblank_handler(int fd, unsigned int frame, } } +void +nouveau_dri2_flip_event_handler(unsigned int frame, unsigned int tv_sec, + unsigned int tv_usec, void *event_data) +{ + struct nouveau_dri2_vblank_state *flip = event_data; + DrawablePtr draw; + ScreenPtr screen; + ScrnInfoPtr scrn; + int status; + PixmapPtr pixmap; + + status = dixLookupDrawable(&draw, flip->draw, serverClient, + M_ANY, DixWriteAccess); + if (status != Success) { + free(flip); + return; + } + + screen = draw->pScreen; + scrn = xf86Screens[screen->myNum]; + + pixmap = screen->GetScreenPixmap(screen); + xf86DrvMsg(scrn->scrnIndex, X_INFO, + "%s:%d fevent[%p] width %d pitch %d (/4 %d)\n", + __func__, __LINE__, flip, pixmap->drawable.width, + pixmap->devKind, pixmap->devKind/4); + + /* We assume our flips arrive in order, so we don't check the frame */ + switch (flip->action) { + case SWAP: + /* Check for too small vblank count of pageflip completion, + * taking wraparound into account. This usually means some + * defective kms pageflip completion, causing wrong (msc, ust) + * return values and possible visual corruption. + * Skip test for frame == 0, as this is a valid constant value + * reported by all Linux kernels at least up to Linux 3.0. + */ + if ((frame != 0) && + (frame < flip->frame) && (flip->frame - frame < 5)) { + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "%s: Pageflip has impossible msc %d < target_msc %d\n", + __func__, frame, flip->frame); + /* All-Zero values signal failure of (msc, ust) + * timestamping to client. + */ + frame = tv_sec = tv_usec = 0; + } + + DRI2SwapComplete(flip->client, draw, frame, tv_sec, tv_usec, + DRI2_FLIP_COMPLETE, flip->func, + flip->data); + break; + default: + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "%s: unknown vblank event received\n", __func__); + /* Unknown type */ + break; + } + + free(flip); +} + Bool nouveau_dri2_init(ScreenPtr pScreen) { diff --git a/src/nv_proto.h b/src/nv_proto.h index 2e4fce0..2bd2ac7 100644 --- a/src/nv_proto.h +++ b/src/nv_proto.h @@ -7,7 +7,8 @@ void drmmode_adjust_frame(ScrnInfoPtr pScrn, int x, int y, int flags); void drmmode_remove_fb(ScrnInfoPtr pScrn); Bool drmmode_cursor_init(ScreenPtr pScreen); void drmmode_fbcon_copy(ScreenPtr pScreen); -Bool drmmode_page_flip(DrawablePtr draw, PixmapPtr back, void *priv); +Bool drmmode_page_flip(DrawablePtr draw, PixmapPtr back, void *priv, + unsigned int ref_crtc_hw_id); void drmmode_screen_init(ScreenPtr pScreen); void drmmode_screen_fini(ScreenPtr pScreen); @@ -26,6 +27,8 @@ Bool nouveau_allocate_surface(ScrnInfoPtr scrn, int width, int height, void nouveau_dri2_vblank_handler(int fd, unsigned int frame, unsigned int tv_sec, unsigned int tv_usec, void *event_data); +void nouveau_dri2_flip_event_handler(unsigned int frame, unsigned int tv_sec, + unsigned int tv_usec, void *event_data); Bool nouveau_dri2_init(ScreenPtr pScreen); void nouveau_dri2_fini(ScreenPtr pScreen); -- 1.7.1
Mario Kleiner
2011-Sep-02 17:33 UTC
[Nouveau] [PATCH 2/3] dri2: Update front buffer pixmap and name before exchanging buffers
Buffer exchange assumes that the front buffer pixmap and name information is accurate. That may not be the case eg. if the window has been (un)redirected since the buffer was created. This is a translation to nouveau of a fix that was originally developed by Ville Syrjala <syrjala at sci.fi> for the ati/radeon ddx to fix the same bug there. See thread at: http://lists.x.org/archives/xorg-devel/2011-May/021908.html Signed-off-by: Mario Kleiner <mario.kleiner at tuebingen.mpg.de> --- src/nouveau_dri2.c | 45 ++++++++++++++++++++++++++++++++++++++++++--- 1 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/nouveau_dri2.c b/src/nouveau_dri2.c index 87eaf08..9f0ee97 100644 --- a/src/nouveau_dri2.c +++ b/src/nouveau_dri2.c @@ -140,6 +140,35 @@ struct nouveau_dri2_vblank_state { }; static Bool +update_front(DrawablePtr draw, DRI2BufferPtr front) +{ + int r; + PixmapPtr pixmap; + struct nouveau_dri2_buffer *nvbuf = nouveau_dri2_buffer(front); + + if (draw->type == DRAWABLE_PIXMAP) + pixmap = (PixmapPtr)draw; + else + pixmap = (*draw->pScreen->GetWindowPixmap)((WindowPtr)draw); + + pixmap->refcnt++; + + exaMoveInPixmap(pixmap); + r = nouveau_bo_handle_get(nouveau_pixmap_bo(pixmap), &front->name); + if (r) { + (*draw->pScreen->DestroyPixmap)(pixmap); + return FALSE; + } + + (*draw->pScreen->DestroyPixmap)(nvbuf->ppix); + front->pitch = pixmap->devKind; + front->cpp = pixmap->drawable.bitsPerPixel / 8; + nvbuf->ppix = pixmap; + + return TRUE; +} + +static Bool can_exchange(DrawablePtr draw, PixmapPtr dst_pix, PixmapPtr src_pix) { ScrnInfoPtr scrn = xf86Screens[draw->pScreen->myNum]; @@ -212,13 +241,14 @@ nouveau_dri2_finish_swap(DrawablePtr draw, unsigned int frame, { ScrnInfoPtr scrn = xf86Screens[draw->pScreen->myNum]; NVPtr pNv = NVPTR(scrn); - PixmapPtr dst_pix = nouveau_dri2_buffer(s->dst)->ppix; + PixmapPtr dst_pix; PixmapPtr src_pix = nouveau_dri2_buffer(s->src)->ppix; - struct nouveau_bo *dst_bo = nouveau_pixmap_bo(dst_pix); + struct nouveau_bo *dst_bo; struct nouveau_bo *src_bo = nouveau_pixmap_bo(src_pix); struct nouveau_channel *chan = pNv->chan; RegionRec reg; int type, ret; + Bool front_updated; REGION_INIT(0, ®, (&(BoxRec){ 0, 0, draw->width, draw->height }), 0); REGION_TRANSLATE(0, ®, draw->x, draw->y); @@ -235,6 +265,15 @@ nouveau_dri2_finish_swap(DrawablePtr draw, unsigned int frame, if (ref_crtc_hw_id & 1) ref_crtc_hw_id = 1; + /* Update frontbuffer pixmap and name: Could have changed due to + * window (un)redirection as part of compositing. + */ + front_updated = update_front(draw, s->dst); + + /* Assign frontbuffer pixmap, after update in update_front() */ + dst_pix = nouveau_dri2_buffer(s->dst)->ppix; + dst_bo = nouveau_pixmap_bo(dst_pix); + /* Throttle on the previous frame before swapping */ nouveau_bo_map(dst_bo, NOUVEAU_BO_RD); nouveau_bo_unmap(dst_bo); @@ -253,7 +292,7 @@ nouveau_dri2_finish_swap(DrawablePtr draw, unsigned int frame, FIRE_RING(chan); } - if (can_exchange(draw, dst_pix, src_pix)) { + if (front_updated && can_exchange(draw, dst_pix, src_pix)) { type = DRI2_EXCHANGE_COMPLETE; DamageRegionAppend(draw, ®); -- 1.7.1
Mario Kleiner
2011-Sep-02 17:33 UTC
[Nouveau] [PATCH 3/3] dri2: Fixes to swap scheduling, especially for copy-swaps.
Treats vblank event scheduling for the non-pageflip swap case correctly. Allows vblank controlled swaps for redirected windows. Fixes some corner-cases in OML_sync_control scheduling when divisor and remainder parameters are used. Signed-off-by: Mario Kleiner <mario.kleiner at tuebingen.mpg.de> --- src/nouveau_dri2.c | 71 ++++++++++++++++++++++++++++++++++++++++++++------- 1 files changed, 61 insertions(+), 10 deletions(-) diff --git a/src/nouveau_dri2.c b/src/nouveau_dri2.c index 9f0ee97..f14dea4 100644 --- a/src/nouveau_dri2.c +++ b/src/nouveau_dri2.c @@ -196,10 +196,8 @@ can_sync_to_vblank(DrawablePtr draw) { ScrnInfoPtr scrn = xf86Screens[draw->pScreen->myNum]; NVPtr pNv = NVPTR(scrn); - PixmapPtr pix = NVGetDrawablePixmap(draw); return pNv->glx_vblank && - nouveau_exa_pixmap_is_onscreen(pix) && nv_window_belongs_to_crtc(scrn, draw->x, draw->y, draw->width, draw->height); } @@ -344,9 +342,40 @@ nouveau_dri2_schedule_swap(ClientPtr client, DrawablePtr draw, CARD64 *target_msc, CARD64 divisor, CARD64 remainder, DRI2SwapEventPtr func, void *data) { + PixmapPtr dst_pix; + PixmapPtr src_pix = nouveau_dri2_buffer(src)->ppix; struct nouveau_dri2_vblank_state *s; CARD64 current_msc, expect_msc; - int ret; + int ret, flip = 0; + Bool front_updated; + + /* Truncate to match kernel interfaces; means occasional overflow + * misses, but that's generally not a big deal. + */ + *target_msc &= 0xffffffff; + divisor &= 0xffffffff; + remainder &= 0xffffffff; + + /* Update frontbuffer pixmap and name: Could have changed due to + * window (un)redirection as part of compositing. + */ + front_updated = update_front(draw, dst); + + /* Assign frontbuffer pixmap, after update in update_front() */ + dst_pix = nouveau_dri2_buffer(dst)->ppix; + + /* Flips need to be submitted one frame before */ + if (DRI2CanFlip(draw) && front_updated && + can_exchange(draw, dst_pix, src_pix)) { + flip = 1; + } + + /* Correct target_msc by 'flip' if this is a page-flipped swap. + * Do it early, so handling of different timing constraints + * for divisor, remainder and msc vs. target_msc works. + */ + if (*target_msc > 0) + *target_msc -= (CARD64) flip; /* Initialize a swap structure */ s = malloc(sizeof(*s)); @@ -363,19 +392,34 @@ nouveau_dri2_schedule_swap(ClientPtr client, DrawablePtr draw, if (ret) goto fail; - /* Calculate a swap target if we don't have one */ - if (current_msc >= *target_msc && divisor) + /* Calculate a swap target if we don't have one or if + * divisor/remainder relationship must be satisfied. + */ + if (current_msc >= *target_msc && divisor) { *target_msc = current_msc + divisor - (current_msc - remainder) % divisor; - /* Request a vblank event one frame before the target */ + /* Account for extra pageflip delay if flip > 0 */ + *target_msc -= (CARD64) flip; + } + + /* Request a vblank event one frame before the target, unless + * this is a copy-swap, in which case we need to make sure + * it is only dispatched at the target frame or later. + */ ret = nouveau_wait_vblank(draw, DRM_VBLANK_ABSOLUTE | - DRM_VBLANK_EVENT, - max(current_msc, *target_msc - 1), + DRM_VBLANK_EVENT | + ((flip) ? 0 : DRM_VBLANK_NEXTONMISS), + max(current_msc, *target_msc), &expect_msc, NULL, s); if (ret) goto fail; - s->frame = (unsigned int) expect_msc & 0xffffffff; + + /* Store expected target_msc for later consistency check and + * return it to server for proper swap_interval implementation. + */ + s->frame = ((unsigned int) (expect_msc & 0xffffffff)) + flip ; + *target_msc = s->frame; } else { /* We can't/don't want to sync to vblank, just swap. */ nouveau_dri2_finish_swap(draw, 0, 0, 0, s); @@ -396,6 +440,13 @@ nouveau_dri2_schedule_wait(ClientPtr client, DrawablePtr draw, CARD64 current_msc; int ret; + /* Truncate to match kernel interfaces; means occasional overflow + * misses, but that's generally not a big deal. + */ + target_msc &= 0xffffffff; + divisor &= 0xffffffff; + remainder &= 0xffffffff; + if (!can_sync_to_vblank(draw)) { DRI2WaitMSCComplete(client, draw, target_msc, 0, 0); return TRUE; @@ -415,7 +466,7 @@ nouveau_dri2_schedule_wait(ClientPtr client, DrawablePtr draw, goto fail; /* Calculate a wait target if we don't have one */ - if (current_msc > target_msc && divisor) + if (current_msc >= target_msc && divisor) target_msc = current_msc + divisor - (current_msc - remainder) % divisor; -- 1.7.1