diff options
Diffstat (limited to 'src/nouveau_dri2.c')
-rw-r--r-- | src/nouveau_dri2.c | 298 |
1 files changed, 295 insertions, 3 deletions
diff --git a/src/nouveau_dri2.c b/src/nouveau_dri2.c index f7675fc..2dd6da4 100644 --- a/src/nouveau_dri2.c +++ b/src/nouveau_dri2.c @@ -49,6 +49,8 @@ nouveau_dri2_create_buffer(DrawablePtr pDraw, unsigned int attachment, if (attachment == DRI2BufferDepth || attachment == DRI2BufferDepthStencil) usage_hint |= NOUVEAU_CREATE_PIXMAP_ZETA; + else + usage_hint |= NOUVEAU_CREATE_PIXMAP_SCANOUT; ppix = pScreen->CreatePixmap(pScreen, pDraw->width, pDraw->height, pDraw->depth, @@ -92,8 +94,6 @@ nouveau_dri2_copy_region(DrawablePtr pDraw, RegionPtr pRegion, struct nouveau_dri2_buffer *dst = nouveau_dri2_buffer(pDstBuffer); PixmapPtr pspix = src->ppix, pdpix = dst->ppix; ScreenPtr pScreen = pDraw->pScreen; - ScrnInfoPtr pScrn = xf86Screens[pScreen->myNum]; - NVPtr pNv = NVPTR(pScrn); RegionPtr pCopyClip; GCPtr pGC; @@ -112,8 +112,297 @@ nouveau_dri2_copy_region(DrawablePtr pDraw, RegionPtr pRegion, pDraw->width, pDraw->height, 0, 0); FreeScratchGC(pGC); +} + +struct nouveau_dri2_vblank_state { + enum { + SWAP, + WAIT + } action; + + ClientPtr client; + XID draw; + + DRI2BufferPtr dst; + DRI2BufferPtr src; + DRI2SwapEventPtr func; + void *data; +}; + +static Bool +can_exchange(DrawablePtr draw, PixmapPtr dst_pix, PixmapPtr src_pix) +{ + ScrnInfoPtr scrn = xf86Screens[draw->pScreen->myNum]; + NVPtr pNv = NVPTR(scrn); + + return (!nouveau_exa_pixmap_is_onscreen(dst_pix) || + (DRI2CanFlip(draw) && pNv->has_pageflip)) && + dst_pix->drawable.width == src_pix->drawable.width && + dst_pix->drawable.height == src_pix->drawable.height && + dst_pix->drawable.depth == src_pix->drawable.depth && + dst_pix->devKind == src_pix->devKind; +} + +static Bool +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); +} + +static int +nouveau_wait_vblank(DrawablePtr draw, int type, CARD64 msc, + CARD64 *pmsc, CARD64 *pust, void *data) +{ + ScrnInfoPtr scrn = xf86Screens[draw->pScreen->myNum]; + NVPtr pNv = NVPTR(scrn); + int crtcs = nv_window_belongs_to_crtc(scrn, draw->x, draw->y, + draw->width, draw->height); + drmVBlank vbl; + int ret; + + vbl.request.type = type | (crtcs == 2 ? DRM_VBLANK_SECONDARY : 0); + vbl.request.sequence = msc; + vbl.request.signal = (unsigned long)data; + + ret = drmWaitVBlank(nouveau_device(pNv->dev)->fd, &vbl); + if (ret) { + xf86DrvMsg(scrn->scrnIndex, X_WARNING, + "Wait for VBlank failed: %s\n", strerror(errno)); + return ret; + } + + if (pmsc) + *pmsc = vbl.reply.sequence; + if (pust) + *pust = (CARD64)vbl.reply.tval_sec * 1000000 + + vbl.reply.tval_usec; + return 0; +} + +static void +nouveau_dri2_finish_swap(DrawablePtr draw, unsigned int frame, + unsigned int tv_sec, unsigned int tv_usec, + struct nouveau_dri2_vblank_state *s) +{ + ScrnInfoPtr scrn = xf86Screens[draw->pScreen->myNum]; + NVPtr pNv = NVPTR(scrn); + PixmapPtr dst_pix = nouveau_dri2_buffer(s->dst)->ppix; + PixmapPtr src_pix = nouveau_dri2_buffer(s->src)->ppix; + struct nouveau_bo *dst_bo = nouveau_pixmap_bo(dst_pix); + struct nouveau_bo *src_bo = nouveau_pixmap_bo(src_pix); + struct nouveau_channel *chan = pNv->chan; + RegionRec reg; + int type, ret; + + REGION_INIT(0, ®, (&(BoxRec){ 0, 0, draw->width, draw->height }), 0); + REGION_TRANSLATE(0, ®, draw->x, draw->y); + + /* Throttle on the previous frame before swapping */ + nouveau_bo_map(dst_bo, NOUVEAU_BO_RD); + nouveau_bo_unmap(dst_bo); + + if (can_sync_to_vblank(draw)) { + /* Reference the back buffer to sync it to vblank */ + WAIT_RING(chan, 1); + OUT_RELOC(chan, src_bo, 0, + NOUVEAU_BO_VRAM | NOUVEAU_BO_RD, 0, 0); + + if (pNv->Architecture >= NV_ARCH_50) + NV50SyncToVBlank(dst_pix, REGION_EXTENTS(0, ®)); + else + NV11SyncToVBlank(dst_pix, REGION_EXTENTS(0, ®)); + + FIRE_RING(chan); + } + + if (can_exchange(draw, dst_pix, src_pix)) { + type = DRI2_EXCHANGE_COMPLETE; + DamageRegionAppend(draw, ®); + + if (DRI2CanFlip(draw)) { + type = DRI2_FLIP_COMPLETE; + ret = drmmode_page_flip(draw, src_pix, s); + if (!ret) + goto out; + } + + SWAP(s->dst->name, s->src->name); + SWAP(nouveau_pixmap(dst_pix)->bo, nouveau_pixmap(src_pix)->bo); + + DamageRegionProcessPending(draw); + } else { + type = DRI2_BLIT_COMPLETE; + + /* Reference the front buffer to let throttling work + * on occluded drawables. */ + WAIT_RING(chan, 1); + OUT_RELOC(chan, dst_bo, 0, + NOUVEAU_BO_VRAM | NOUVEAU_BO_RD, 0, 0); + + REGION_TRANSLATE(0, ®, -draw->x, -draw->y); + nouveau_dri2_copy_region(draw, ®, s->dst, s->src); + } + + /* + * Tell the X server buffers are already swapped even if they're + * not, to prevent it from blocking the client on the next + * GetBuffers request (and let the client do triple-buffering). + * + * XXX - The DRI2SwapLimit() API will allow us to move this to + * the flip handler with no FPS hit. + */ + DRI2SwapComplete(s->client, draw, frame, tv_sec, tv_usec, + type, s->func, s->data); +out: + free(s); +} - FIRE_RING(pNv->chan); +static Bool +nouveau_dri2_schedule_swap(ClientPtr client, DrawablePtr draw, + DRI2BufferPtr dst, DRI2BufferPtr src, + CARD64 *target_msc, CARD64 divisor, CARD64 remainder, + DRI2SwapEventPtr func, void *data) +{ + struct nouveau_dri2_vblank_state *s; + CARD64 current_msc; + int ret; + + /* Initialize a swap structure */ + s = malloc(sizeof(*s)); + if (!s) + return FALSE; + + *s = (struct nouveau_dri2_vblank_state) + { SWAP, client, draw->id, dst, src, func, data }; + + if (can_sync_to_vblank(draw)) { + /* Get current sequence */ + ret = nouveau_wait_vblank(draw, DRM_VBLANK_RELATIVE, 0, + ¤t_msc, NULL, NULL); + if (ret) + goto fail; + + /* Calculate a swap target if we don't have one */ + if (current_msc >= *target_msc && divisor) + *target_msc = current_msc + divisor + - (current_msc - remainder) % divisor; + + /* Request a vblank event one frame before the target */ + ret = nouveau_wait_vblank(draw, DRM_VBLANK_ABSOLUTE | + DRM_VBLANK_EVENT, + max(current_msc, *target_msc - 1), + NULL, NULL, s); + if (ret) + goto fail; + + } else { + /* We can't/don't want to sync to vblank, just swap. */ + nouveau_dri2_finish_swap(draw, 0, 0, 0, s); + } + + return TRUE; + +fail: + free(s); + return FALSE; +} + +static Bool +nouveau_dri2_schedule_wait(ClientPtr client, DrawablePtr draw, + CARD64 target_msc, CARD64 divisor, CARD64 remainder) +{ + struct nouveau_dri2_vblank_state *s; + CARD64 current_msc; + int ret; + + if (!can_sync_to_vblank(draw)) { + DRI2WaitMSCComplete(client, draw, target_msc, 0, 0); + return TRUE; + } + + /* Initialize a vblank structure */ + s = malloc(sizeof(*s)); + if (!s) + return FALSE; + + *s = (struct nouveau_dri2_vblank_state) { WAIT, client, draw->id }; + + /* Get current sequence */ + ret = nouveau_wait_vblank(draw, DRM_VBLANK_RELATIVE, 0, + ¤t_msc, NULL, NULL); + if (ret) + goto fail; + + /* Calculate a wait target if we don't have one */ + if (current_msc > target_msc && divisor) + target_msc = current_msc + divisor + - (current_msc - remainder) % divisor; + + /* Request a vblank event */ + ret = nouveau_wait_vblank(draw, DRM_VBLANK_ABSOLUTE | + DRM_VBLANK_EVENT, + max(current_msc, target_msc), + NULL, NULL, s); + if (ret) + goto fail; + + DRI2BlockClient(client, draw); + return TRUE; +fail: + free(s); + return FALSE; +} + +static Bool +nouveau_dri2_get_msc(DrawablePtr draw, CARD64 *ust, CARD64 *msc) +{ + int ret; + + if (!can_sync_to_vblank(draw)) { + *ust = 0; + *msc = 0; + return TRUE; + } + + /* Get current sequence */ + ret = nouveau_wait_vblank(draw, DRM_VBLANK_RELATIVE, 0, msc, ust, NULL); + if (ret) + return FALSE; + + return TRUE; +} + +void +nouveau_dri2_vblank_handler(int fd, unsigned int frame, + unsigned int tv_sec, unsigned int tv_usec, + void *event_data) +{ + struct nouveau_dri2_vblank_state *s = event_data; + DrawablePtr draw; + int ret; + + ret = dixLookupDrawable(&draw, s->draw, serverClient, + M_ANY, DixWriteAccess); + if (ret) + return; + + switch (s->action) { + case SWAP: + nouveau_dri2_finish_swap(draw, frame, tv_sec, tv_usec, s); + break; + + case WAIT: + DRI2WaitMSCComplete(s->client, draw, frame, tv_sec, tv_usec); + free(s); + break; + } } Bool @@ -135,6 +424,9 @@ nouveau_dri2_init(ScreenPtr pScreen) dri2.CreateBuffer = nouveau_dri2_create_buffer; dri2.DestroyBuffer = nouveau_dri2_destroy_buffer; dri2.CopyRegion = nouveau_dri2_copy_region; + dri2.ScheduleSwap = nouveau_dri2_schedule_swap; + dri2.ScheduleWaitMSC = nouveau_dri2_schedule_wait; + dri2.GetMSC = nouveau_dri2_get_msc; return DRI2ScreenInit(pScreen, &dri2); } |