Submitted By: Rahul Chandra Date: 2023-07-20 Initial Package Version: 21.1.9 Upstream Status: In Master Origin: Master Gitlab Description: Backport of the TearFree option from Xorg master diff --color -Naur --exclude .git xorg-server-21.1.9/dix/pixmap.c xorg-server-21.1.9-patched/dix/pixmap.c --- xorg-server-21.1.9/dix/pixmap.c 2023-10-25 08:55:03.000000000 -0400 +++ xorg-server-21.1.9-patched/dix/pixmap.c 2023-07-19 23:22:36.142198348 -0400 @@ -262,12 +262,11 @@ return TRUE; } -static void -PixmapDirtyCopyArea(PixmapPtr dst, - PixmapDirtyUpdatePtr dirty, +void +PixmapDirtyCopyArea(PixmapPtr dst, DrawablePtr src, + int x, int y, int dst_x, int dst_y, RegionPtr dirty_region) { - DrawablePtr src = dirty->src; ScreenPtr pScreen = src->pScreen; int n; BoxPtr b; @@ -294,14 +293,14 @@ h = dst_box.y2 - dst_box.y1; pGC->ops->CopyArea(src, &dst->drawable, pGC, - dirty->x + dst_box.x1, dirty->y + dst_box.y1, w, h, - dirty->dst_x + dst_box.x1, - dirty->dst_y + dst_box.y1); + x + dst_box.x1, y + dst_box.y1, w, h, + dst_x + dst_box.x1, dst_y + dst_box.y1); b++; } FreeScratchGC(pGC); } + static void PixmapDirtyCompositeRotate(PixmapPtr dst_pixmap, PixmapDirtyUpdatePtr dirty, @@ -408,7 +407,8 @@ RegionTranslate(&pixregion, -dirty->x, -dirty->y); if (!pScreen->root || dirty->rotation == RR_Rotate_0) - PixmapDirtyCopyArea(dst, dirty, &pixregion); + PixmapDirtyCopyArea(dst, dirty->src, dirty->x, dirty->y, + dirty->dst_x, dirty->dst_y, &pixregion); else PixmapDirtyCompositeRotate(dst, dirty, &pixregion); pScreen->SourceValidate = SourceValidate; diff --color -Naur --exclude .git xorg-server-21.1.9/hw/xfree86/drivers/modesetting/driver.c xorg-server-21.1.9-patched/hw/xfree86/drivers/modesetting/driver.c --- xorg-server-21.1.9/hw/xfree86/drivers/modesetting/driver.c 2023-10-25 08:55:03.000000000 -0400 +++ xorg-server-21.1.9-patched/hw/xfree86/drivers/modesetting/driver.c 2023-07-19 23:11:47.097508779 -0400 @@ -145,6 +145,7 @@ {OPTION_VARIABLE_REFRESH, "VariableRefresh", OPTV_BOOLEAN, {0}, FALSE}, {OPTION_USE_GAMMA_LUT, "UseGammaLUT", OPTV_BOOLEAN, {0}, FALSE}, {OPTION_ASYNC_FLIP_SECONDARIES, "AsyncFlipSecondaries", OPTV_BOOLEAN, {0}, FALSE}, + {OPTION_TEARFREE, "TearFree", OPTV_BOOLEAN, {0}, FALSE}, {-1, NULL, OPTV_NONE, {0}, FALSE} }; @@ -515,15 +516,49 @@ return TRUE; } +static void +rotate_clip(PixmapPtr pixmap, BoxPtr rect, drmModeClip *clip, Rotation rotation) +{ + int w = pixmap->drawable.width; + int h = pixmap->drawable.height; + + if (rotation == RR_Rotate_90) { + /* Rotate 90 degrees counter clockwise */ + clip->x1 = rect->y1; + clip->x2 = rect->y2; + clip->y1 = w - rect->x2; + clip->y2 = w - rect->x1; + } else if (rotation == RR_Rotate_180) { + /* Rotate 180 degrees */ + clip->x1 = w - rect->x2; + clip->x2 = w - rect->x1; + clip->y1 = h - rect->y2; + clip->y2 = h - rect->y1; + } else if (rotation == RR_Rotate_270) { + /* Rotate 90 degrees clockwise */ + clip->x1 = h - rect->y2; + clip->x2 = h - rect->y1; + clip->y1 = rect->x1; + clip->y2 = rect->x2; + } else { + clip->x1 = rect->x1; + clip->x2 = rect->x2; + clip->y1 = rect->y1; + clip->y2 = rect->y2; + } +} + static int -dispatch_dirty_region(ScrnInfoPtr scrn, - PixmapPtr pixmap, DamagePtr damage, int fb_id) +dispatch_damages(ScrnInfoPtr scrn, xf86CrtcPtr crtc, RegionPtr dirty, + PixmapPtr pixmap, DamagePtr damage, int fb_id) { modesettingPtr ms = modesettingPTR(scrn); - RegionPtr dirty = DamageRegion(damage); unsigned num_cliprects = REGION_NUM_RECTS(dirty); int ret = 0; + if (!ms->dirty_enabled) + return 0; + if (num_cliprects) { drmModeClip *clip = xallocarray(num_cliprects, sizeof(drmModeClip)); BoxPtr rect = REGION_RECTS(dirty); @@ -532,13 +567,9 @@ if (!clip) return -ENOMEM; - /* XXX no need for copy? */ - for (i = 0; i < num_cliprects; i++, rect++) { - clip[i].x1 = rect->x1; - clip[i].y1 = rect->y1; - clip[i].x2 = rect->x2; - clip[i].y2 = rect->y2; - } + /* Rotate and copy rects into clips */ + for (i = 0; i < num_cliprects; i++, rect++) + rotate_clip(pixmap, rect, &clip[i], crtc->rotation); /* TODO query connector property to see if this is needed */ ret = drmModeDirtyFB(ms->fd, fb_id, clip, num_cliprects); @@ -551,30 +582,130 @@ } } + if (ret == -EINVAL || ret == -ENOSYS) { + xf86DrvMsg(scrn->scrnIndex, X_INFO, + "Disabling kernel dirty updates, not required.\n"); + ms->dirty_enabled = FALSE; + } + free(clip); - DamageEmpty(damage); + if (damage) + DamageEmpty(damage); } return ret; } +static int +dispatch_dirty_region(ScrnInfoPtr scrn, xf86CrtcPtr crtc, + PixmapPtr pixmap, DamagePtr damage, int fb_id) +{ + return dispatch_damages(scrn, crtc, DamageRegion(damage), + pixmap, damage, fb_id); +} + +static void +ms_tearfree_update_damages(ScreenPtr pScreen) +{ + ScrnInfoPtr scrn = xf86ScreenToScrn(pScreen); + xf86CrtcConfigPtr xf86_config = XF86_CRTC_CONFIG_PTR(scrn); + modesettingPtr ms = modesettingPTR(scrn); + RegionPtr dirty = DamageRegion(ms->damage); + int c, i; + + if (RegionNil(dirty)) + return; + + for (c = 0; c < xf86_config->num_crtc; c++) { + xf86CrtcPtr crtc = xf86_config->crtc[c]; + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree; + RegionRec region; + + /* Compute how much of the damage intersects with this CRTC */ + RegionInit(®ion, &crtc->bounds, 0); + RegionIntersect(®ion, ®ion, dirty); + + if (trf->buf[0].px) { + for (i = 0; i < ARRAY_SIZE(trf->buf); i++) + RegionUnion(&trf->buf[i].dmg, &trf->buf[i].dmg, ®ion); + } else { + /* Just notify the kernel of the damages if TearFree isn't used */ + dispatch_damages(scrn, crtc, ®ion, + pScreen->GetScreenPixmap(pScreen), + NULL, ms->drmmode.fb_id); + } + } + DamageEmpty(ms->damage); +} + +static void +ms_tearfree_do_flips(ScreenPtr pScreen) +{ +#ifdef GLAMOR_HAS_GBM + ScrnInfoPtr scrn = xf86ScreenToScrn(pScreen); + xf86CrtcConfigPtr xf86_config = XF86_CRTC_CONFIG_PTR(scrn); + modesettingPtr ms = modesettingPTR(scrn); + int c; + + if (!ms->drmmode.tearfree_enable) + return; + + for (c = 0; c < xf86_config->num_crtc; c++) { + xf86CrtcPtr crtc = xf86_config->crtc[c]; + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree; + + if (!ms_tearfree_is_active_on_crtc(crtc)) { + /* Notify any lingering DRI clients waiting for a flip to finish */ + ms_tearfree_dri_abort_all(crtc); + continue; + } + + /* Skip if the last flip is still pending, a DRI client is flipping, or + * there isn't any damage on the front buffer. + */ + if (trf->flip_seq || ms->drmmode.dri2_flipping || + ms->drmmode.present_flipping || + RegionNil(&trf->buf[trf->back_idx ^ 1].dmg)) + continue; + + /* Flip. If it fails, notify the kernel of the front buffer damages */ + if (ms_do_tearfree_flip(pScreen, crtc)) { + dispatch_damages(scrn, crtc, &trf->buf[trf->back_idx ^ 1].dmg, + trf->buf[trf->back_idx ^ 1].px, NULL, + trf->buf[trf->back_idx ^ 1].fb_id); + RegionEmpty(&trf->buf[trf->back_idx ^ 1].dmg); + } + } +#endif +} + static void dispatch_dirty(ScreenPtr pScreen) { ScrnInfoPtr scrn = xf86ScreenToScrn(pScreen); + xf86CrtcConfigPtr xf86_config = XF86_CRTC_CONFIG_PTR(scrn); modesettingPtr ms = modesettingPTR(scrn); PixmapPtr pixmap = pScreen->GetScreenPixmap(pScreen); - int fb_id = ms->drmmode.fb_id; - int ret; + uint32_t fb_id; + int ret, c, x, y ; - ret = dispatch_dirty_region(scrn, pixmap, ms->damage, fb_id); - if (ret == -EINVAL || ret == -ENOSYS) { - ms->dirty_enabled = FALSE; - DamageUnregister(ms->damage); - DamageDestroy(ms->damage); - ms->damage = NULL; - xf86DrvMsg(scrn->scrnIndex, X_INFO, - "Disabling kernel dirty updates, not required.\n"); - return; + for (c = 0; c < xf86_config->num_crtc; c++) { + xf86CrtcPtr crtc = xf86_config->crtc[c]; + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + + if (!drmmode_crtc) + continue; + + drmmode_crtc_get_fb_id(crtc, &fb_id, &x, &y); + + ret = dispatch_dirty_region(scrn, crtc, pixmap, ms->damage, fb_id); + if (ret == -EINVAL || ret == -ENOSYS) { + DamageUnregister(ms->damage); + DamageDestroy(ms->damage); + ms->damage = NULL; + return; + } } } @@ -586,7 +717,7 @@ DamagePtr damage = ppriv->secondary_damage; int fb_id = ppriv->fb_id; - dispatch_dirty_region(scrn, ppix, damage, fb_id); + dispatch_dirty_region(scrn, crtc, ppix, damage, fb_id); } static void @@ -703,10 +834,13 @@ pScreen->BlockHandler = msBlockHandler; if (pScreen->isGPU && !ms->drmmode.reverse_prime_offload_mode) dispatch_secondary_dirty(pScreen); + else if (ms->drmmode.tearfree_enable) + ms_tearfree_update_damages(pScreen); else if (ms->dirty_enabled) dispatch_dirty(pScreen); ms_dirty_update(pScreen, timeout); + ms_tearfree_do_flips(pScreen); } static void @@ -1238,9 +1372,34 @@ if (xf86ReturnOptValBool(ms->drmmode.Options, OPTION_ATOMIC, FALSE)) { ret = drmSetClientCap(ms->fd, DRM_CLIENT_CAP_ATOMIC, 1); ms->atomic_modeset = (ret == 0); + if (!ms->atomic_modeset) + xf86DrvMsg(pScrn->scrnIndex, X_WARNING, "Atomic modesetting not supported\n"); } else { ms->atomic_modeset = FALSE; } + xf86DrvMsg(pScrn->scrnIndex, X_INFO, + "Atomic modesetting %sabled\n", ms->atomic_modeset ? "en" : "dis"); + + /* TearFree requires glamor and, if PageFlip is enabled, universal planes */ + if (xf86ReturnOptValBool(ms->drmmode.Options, OPTION_TEARFREE, FALSE)) { + if (pScrn->is_gpu) { + xf86DrvMsg(pScrn->scrnIndex, X_WARNING, + "TearFree cannot synchronize PRIME; use 'PRIME Synchronization' instead\n"); + } else if (ms->drmmode.glamor) { + /* Atomic modesetting implicitly enables universal planes */ + if (!ms->drmmode.pageflip || ms->atomic_modeset || + !drmSetClientCap(ms->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) { + ms->drmmode.tearfree_enable = TRUE; + xf86DrvMsg(pScrn->scrnIndex, X_INFO, "TearFree: enabled\n"); + } else { + xf86DrvMsg(pScrn->scrnIndex, X_WARNING, + "TearFree requires either universal planes, or setting 'Option \"PageFlip\" \"off\"'\n"); + } + } else { + xf86DrvMsg(pScrn->scrnIndex, X_WARNING, + "TearFree requires Glamor acceleration\n"); + } + } ms->kms_has_modifiers = FALSE; ret = drmGetCap(ms->fd, DRM_CAP_ADDFB2_MODIFIERS, &value); @@ -1589,13 +1748,13 @@ err = drmModeDirtyFB(ms->fd, ms->drmmode.fb_id, NULL, 0); - if (err != -EINVAL && err != -ENOSYS) { + if ((err != -EINVAL && err != -ENOSYS) || ms->drmmode.tearfree_enable) { ms->damage = DamageCreate(NULL, NULL, DamageReportNone, TRUE, pScreen, rootPixmap); if (ms->damage) { DamageRegister(&rootPixmap->drawable, ms->damage); - ms->dirty_enabled = TRUE; + ms->dirty_enabled = err != -EINVAL && err != -ENOSYS; xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Damage tracking initialized\n"); } else { diff --color -Naur --exclude .git xorg-server-21.1.9/hw/xfree86/drivers/modesetting/driver.h xorg-server-21.1.9-patched/hw/xfree86/drivers/modesetting/driver.h --- xorg-server-21.1.9/hw/xfree86/drivers/modesetting/driver.h 2023-10-25 08:55:03.000000000 -0400 +++ xorg-server-21.1.9-patched/hw/xfree86/drivers/modesetting/driver.h 2023-07-19 23:11:47.097508779 -0400 @@ -61,6 +61,7 @@ OPTION_VARIABLE_REFRESH, OPTION_USE_GAMMA_LUT, OPTION_ASYNC_FLIP_SECONDARIES, + OPTION_TEARFREE, } modesettingOpts; typedef struct @@ -86,10 +87,13 @@ struct xorg_list list; xf86CrtcPtr crtc; uint32_t seq; + uint64_t msc; void *data; ScrnInfoPtr scrn; ms_drm_handler_proc handler; ms_drm_abort_proc abort; + Bool kernel_queued; + Bool aborted; }; typedef struct _modesettingRec { @@ -232,14 +236,25 @@ Bool ms_do_pageflip(ScreenPtr screen, PixmapPtr new_front, void *event, - int ref_crtc_vblank_pipe, + xf86CrtcPtr ref_crtc, Bool async, ms_pageflip_handler_proc pageflip_handler, ms_pageflip_abort_proc pageflip_abort, const char *log_prefix); +Bool +ms_tearfree_dri_abort(xf86CrtcPtr crtc, + Bool (*match)(void *data, void *match_data), + void *match_data); + +void +ms_tearfree_dri_abort_all(xf86CrtcPtr crtc); + +Bool ms_do_tearfree_flip(ScreenPtr screen, xf86CrtcPtr crtc); + #endif int ms_flush_drm_events(ScreenPtr screen); Bool ms_window_has_variable_refresh(modesettingPtr ms, WindowPtr win); void ms_present_set_screen_vrr(ScrnInfoPtr scrn, Bool vrr_enabled); +Bool ms_tearfree_is_active_on_crtc(xf86CrtcPtr crtc); diff --color -Naur --exclude .git xorg-server-21.1.9/hw/xfree86/drivers/modesetting/drmmode_display.c xorg-server-21.1.9-patched/hw/xfree86/drivers/modesetting/drmmode_display.c --- xorg-server-21.1.9/hw/xfree86/drivers/modesetting/drmmode_display.c 2023-10-25 08:55:03.000000000 -0400 +++ xorg-server-21.1.9-patched/hw/xfree86/drivers/modesetting/drmmode_display.c 2023-07-19 23:11:47.097508779 -0400 @@ -519,13 +519,13 @@ } static int -drmmode_CompareKModes(drmModeModeInfo * kmode, drmModeModeInfo * other) +drmmode_CompareKModes(const drmModeModeInfo * kmode, const drmModeModeInfo * other) { return memcmp(kmode, other, sizeof(*kmode)); } static int -drm_mode_ensure_blob(xf86CrtcPtr crtc, drmModeModeInfo mode_info) +drm_mode_ensure_blob(xf86CrtcPtr crtc, const drmModeModeInfo* mode_info) { modesettingPtr ms = modesettingPTR(crtc->scrn); drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; @@ -533,14 +533,14 @@ int ret; if (drmmode_crtc->current_mode && - drmmode_CompareKModes(&drmmode_crtc->current_mode->mode_info, &mode_info) == 0) + drmmode_CompareKModes(&drmmode_crtc->current_mode->mode_info, mode_info) == 0) return 0; mode = calloc(sizeof(drmmode_mode_rec), 1); if (!mode) return -1; - mode->mode_info = mode_info; + mode->mode_info = *mode_info; ret = drmModeCreatePropertyBlob(ms->fd, &mode->mode_info, sizeof(mode->mode_info), @@ -589,7 +589,7 @@ drmModeModeInfo kmode; drmmode_ConvertToKMode(crtc->scrn, &kmode, &crtc->mode); - ret |= drm_mode_ensure_blob(crtc, kmode); + ret |= drm_mode_ensure_blob(crtc, &kmode); ret |= crtc_add_prop(req, drmmode_crtc, DRMMODE_CRTC_ACTIVE, 1); @@ -627,11 +627,12 @@ return ms->atomic_modeset; } -static Bool +Bool drmmode_crtc_get_fb_id(xf86CrtcPtr crtc, uint32_t *fb_id, int *x, int *y) { drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; drmmode_ptr drmmode = drmmode_crtc->drmmode; + drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree; int ret; *fb_id = 0; @@ -646,6 +647,10 @@ *x = drmmode_crtc->prime_pixmap_x; *y = 0; } + else if (trf->buf[trf->back_idx ^ 1].px) { + *fb_id = trf->buf[trf->back_idx ^ 1].fb_id; + *x = *y = 0; + } else if (drmmode_crtc->rotate_fb_id) { *fb_id = drmmode_crtc->rotate_fb_id; *x = *y = 0; @@ -922,6 +927,10 @@ drmmode_ConvertToKMode(crtc->scrn, &kmode, &crtc->mode); ret = drmModeSetCrtc(drmmode->fd, drmmode_crtc->mode_crtc->crtc_id, fb_id, x, y, output_ids, output_count, &kmode); + if (!ret && !ms->atomic_modeset) { + drmmode_crtc->src_x = x; + drmmode_crtc->src_y = y; + } drmmode_set_ctm(crtc, ctm); @@ -930,7 +939,8 @@ } int -drmmode_crtc_flip(xf86CrtcPtr crtc, uint32_t fb_id, uint32_t flags, void *data) +drmmode_crtc_flip(xf86CrtcPtr crtc, uint32_t fb_id, int x, int y, + uint32_t flags, void *data) { modesettingPtr ms = modesettingPTR(crtc->scrn); drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; @@ -942,7 +952,7 @@ if (!req) return 1; - ret = plane_add_props(req, crtc, fb_id, crtc->x, crtc->y); + ret = plane_add_props(req, crtc, fb_id, x, y); flags |= DRM_MODE_ATOMIC_NONBLOCK; if (ret == 0) ret = drmModeAtomicCommit(ms->fd, req, flags, data); @@ -950,6 +960,26 @@ return ret; } + /* The frame buffer source coordinates may change when switching between the + * primary frame buffer and a per-CRTC frame buffer. Set the correct source + * coordinates if they differ for this flip. + */ + if (drmmode_crtc->src_x != x || drmmode_crtc->src_y != y) { + ret = drmModeSetPlane(ms->fd, drmmode_crtc->plane_id, + drmmode_crtc->mode_crtc->crtc_id, fb_id, 0, + 0, 0, crtc->mode.HDisplay, crtc->mode.VDisplay, + x << 16, y << 16, crtc->mode.HDisplay << 16, + crtc->mode.VDisplay << 16); + if (ret) { + xf86DrvMsg(crtc->scrn->scrnIndex, X_WARNING, + "error changing fb src coordinates for flip: %d\n", ret); + return ret; + } + + drmmode_crtc->src_x = x; + drmmode_crtc->src_y = y; + } + return drmModePageFlip(ms->fd, drmmode_crtc->mode_crtc->crtc_id, fb_id, flags, data); } @@ -1548,6 +1578,90 @@ #endif } +void +drmmode_copy_damage(xf86CrtcPtr crtc, PixmapPtr dst, RegionPtr dmg, Bool empty) +{ +#ifdef GLAMOR_HAS_GBM + ScreenPtr pScreen = xf86ScrnToScreen(crtc->scrn); + DrawableRec *src; + + /* Copy the screen's pixmap into the destination pixmap */ + if (crtc->rotatedPixmap) { + src = &crtc->rotatedPixmap->drawable; + xf86RotateCrtcRedisplay(crtc, dst, src, dmg, FALSE); + } else { + src = &pScreen->GetScreenPixmap(pScreen)->drawable; + PixmapDirtyCopyArea(dst, src, 0, 0, -crtc->x, -crtc->y, dmg); + } + + /* Reset the damages if requested */ + if (empty) + RegionEmpty(dmg); + + /* Wait until the GC operations finish */ + modesettingPTR(crtc->scrn)->glamor.finish(pScreen); +#endif +} + +static void +drmmode_shadow_fb_destroy(xf86CrtcPtr crtc, PixmapPtr pixmap, + void *data, drmmode_bo *bo, uint32_t *fb_id); +static void +drmmode_destroy_tearfree_shadow(xf86CrtcPtr crtc) +{ + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree; + int i; + + if (trf->flip_seq) + ms_drm_abort_seq(crtc->scrn, trf->flip_seq); + + for (i = 0; i < ARRAY_SIZE(trf->buf); i++) { + if (trf->buf[i].px) { + drmmode_shadow_fb_destroy(crtc, trf->buf[i].px, (void *)(long)1, + &trf->buf[i].bo, &trf->buf[i].fb_id); + trf->buf[i].px = NULL; + RegionUninit(&trf->buf[i].dmg); + } + } +} + +static PixmapPtr +drmmode_shadow_fb_create(xf86CrtcPtr crtc, void *data, int width, int height, + drmmode_bo *bo, uint32_t *fb_id); +static Bool +drmmode_create_tearfree_shadow(xf86CrtcPtr crtc) +{ + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + drmmode_ptr drmmode = drmmode_crtc->drmmode; + drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree; + uint32_t w = crtc->mode.HDisplay, h = crtc->mode.VDisplay; + int i; + + if (!drmmode->tearfree_enable) + return TRUE; + + /* Destroy the old mode's buffers and make new ones */ + drmmode_destroy_tearfree_shadow(crtc); + for (i = 0; i < ARRAY_SIZE(trf->buf); i++) { + trf->buf[i].px = drmmode_shadow_fb_create(crtc, NULL, w, h, + &trf->buf[i].bo, + &trf->buf[i].fb_id); + if (!trf->buf[i].px) { + drmmode_destroy_tearfree_shadow(crtc); + xf86DrvMsg(crtc->scrn->scrnIndex, X_ERROR, + "shadow creation failed for TearFree buf%d\n", i); + return FALSE; + } + RegionInit(&trf->buf[i].dmg, &crtc->bounds, 0); + } + + /* Initialize the front buffer with the current scanout */ + drmmode_copy_damage(crtc, trf->buf[trf->back_idx ^ 1].px, + &trf->buf[trf->back_idx ^ 1].dmg, TRUE); + return TRUE; +} + static Bool drmmode_set_mode_major(xf86CrtcPtr crtc, DisplayModePtr mode, Rotation rotation, int x, int y) @@ -1581,6 +1695,10 @@ crtc->funcs->gamma_set(crtc, crtc->gamma_red, crtc->gamma_green, crtc->gamma_blue, crtc->gamma_size); + ret = drmmode_create_tearfree_shadow(crtc); + if (!ret) + goto done; + can_test = drmmode_crtc_can_test_mode(crtc); if (drmmode_crtc_set_mode(crtc, can_test)) { xf86DrvMsg(crtc->scrn->scrnIndex, X_ERROR, @@ -1626,6 +1744,7 @@ crtc->y = saved_y; crtc->rotation = saved_rotation; crtc->mode = saved_mode; + drmmode_create_tearfree_shadow(crtc); } else crtc->active = TRUE; @@ -1931,33 +2050,42 @@ } static void * -drmmode_shadow_allocate(xf86CrtcPtr crtc, int width, int height) +drmmode_shadow_fb_allocate(xf86CrtcPtr crtc, int width, int height, + drmmode_bo *bo, uint32_t *fb_id) { drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; drmmode_ptr drmmode = drmmode_crtc->drmmode; int ret; - if (!drmmode_create_bo(drmmode, &drmmode_crtc->rotate_bo, - width, height, drmmode->kbpp)) { + if (!drmmode_create_bo(drmmode, bo, width, height, drmmode->kbpp)) { xf86DrvMsg(crtc->scrn->scrnIndex, X_ERROR, "Couldn't allocate shadow memory for rotated CRTC\n"); return NULL; } - ret = drmmode_bo_import(drmmode, &drmmode_crtc->rotate_bo, - &drmmode_crtc->rotate_fb_id); + ret = drmmode_bo_import(drmmode, bo, fb_id); if (ret) { ErrorF("failed to add rotate fb\n"); - drmmode_bo_destroy(drmmode, &drmmode_crtc->rotate_bo); + drmmode_bo_destroy(drmmode, bo); return NULL; } #ifdef GLAMOR_HAS_GBM if (drmmode->gbm) - return drmmode_crtc->rotate_bo.gbm; + return bo->gbm; #endif - return drmmode_crtc->rotate_bo.dumb; + return bo->dumb; +} + +static void * +drmmode_shadow_allocate(xf86CrtcPtr crtc, int width, int height) +{ + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + + return drmmode_shadow_fb_allocate(crtc, width, height, + &drmmode_crtc->rotate_bo, + &drmmode_crtc->rotate_fb_id); } static PixmapPtr @@ -1983,71 +2111,92 @@ drmmode_set_pixmap_bo(drmmode_ptr drmmode, PixmapPtr pixmap, drmmode_bo *bo); static PixmapPtr -drmmode_shadow_create(xf86CrtcPtr crtc, void *data, int width, int height) +drmmode_shadow_fb_create(xf86CrtcPtr crtc, void *data, int width, int height, + drmmode_bo *bo, uint32_t *fb_id) { ScrnInfoPtr scrn = crtc->scrn; drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; drmmode_ptr drmmode = drmmode_crtc->drmmode; - uint32_t rotate_pitch; - PixmapPtr rotate_pixmap; + uint32_t pitch; + PixmapPtr pixmap; void *pPixData = NULL; if (!data) { - data = drmmode_shadow_allocate(crtc, width, height); + data = drmmode_shadow_fb_allocate(crtc, width, height, bo, fb_id); if (!data) { xf86DrvMsg(scrn->scrnIndex, X_ERROR, - "Couldn't allocate shadow pixmap for rotated CRTC\n"); + "Couldn't allocate shadow pixmap for CRTC\n"); return NULL; } } - if (!drmmode_bo_has_bo(&drmmode_crtc->rotate_bo)) { + if (!drmmode_bo_has_bo(bo)) { xf86DrvMsg(scrn->scrnIndex, X_ERROR, - "Couldn't allocate shadow pixmap for rotated CRTC\n"); + "Couldn't allocate shadow pixmap for CRTC\n"); return NULL; } - pPixData = drmmode_bo_map(drmmode, &drmmode_crtc->rotate_bo); - rotate_pitch = drmmode_bo_get_pitch(&drmmode_crtc->rotate_bo); + pPixData = drmmode_bo_map(drmmode, bo); + pitch = drmmode_bo_get_pitch(bo); - rotate_pixmap = drmmode_create_pixmap_header(scrn->pScreen, - width, height, - scrn->depth, - drmmode->kbpp, - rotate_pitch, - pPixData); + pixmap = drmmode_create_pixmap_header(scrn->pScreen, + width, height, + scrn->depth, + drmmode->kbpp, + pitch, + pPixData); - if (rotate_pixmap == NULL) { + if (pixmap == NULL) { xf86DrvMsg(scrn->scrnIndex, X_ERROR, - "Couldn't allocate shadow pixmap for rotated CRTC\n"); + "Couldn't allocate shadow pixmap for CRTC\n"); return NULL; } - drmmode_set_pixmap_bo(drmmode, rotate_pixmap, &drmmode_crtc->rotate_bo); + drmmode_set_pixmap_bo(drmmode, pixmap, bo); - return rotate_pixmap; + return pixmap; +} + +static PixmapPtr +drmmode_shadow_create(xf86CrtcPtr crtc, void *data, int width, int height) +{ + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + + return drmmode_shadow_fb_create(crtc, data, width, height, + &drmmode_crtc->rotate_bo, + &drmmode_crtc->rotate_fb_id); } static void -drmmode_shadow_destroy(xf86CrtcPtr crtc, PixmapPtr rotate_pixmap, void *data) +drmmode_shadow_fb_destroy(xf86CrtcPtr crtc, PixmapPtr pixmap, + void *data, drmmode_bo *bo, uint32_t *fb_id) { drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; drmmode_ptr drmmode = drmmode_crtc->drmmode; - if (rotate_pixmap) { - rotate_pixmap->drawable.pScreen->DestroyPixmap(rotate_pixmap); + if (pixmap) { + pixmap->drawable.pScreen->DestroyPixmap(pixmap); } if (data) { - drmModeRmFB(drmmode->fd, drmmode_crtc->rotate_fb_id); - drmmode_crtc->rotate_fb_id = 0; + drmModeRmFB(drmmode->fd, *fb_id); + *fb_id = 0; - drmmode_bo_destroy(drmmode, &drmmode_crtc->rotate_bo); - memset(&drmmode_crtc->rotate_bo, 0, sizeof drmmode_crtc->rotate_bo); + drmmode_bo_destroy(drmmode, bo); + memset(bo, 0, sizeof(*bo)); } } static void +drmmode_shadow_destroy(xf86CrtcPtr crtc, PixmapPtr pixmap, void *data) +{ + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + + drmmode_shadow_fb_destroy(crtc, pixmap, data, &drmmode_crtc->rotate_bo, + &drmmode_crtc->rotate_fb_id); +} + +static void drmmode_crtc_destroy(xf86CrtcPtr crtc) { drmmode_mode_ptr iterator, next; @@ -2380,6 +2529,8 @@ drmmode_crtc->drmmode = drmmode; drmmode_crtc->vblank_pipe = drmmode_crtc_vblank_pipe(num); xorg_list_init(&drmmode_crtc->mode_list); + xorg_list_init(&drmmode_crtc->tearfree.dri_flip_list); + drmmode_crtc->next_msc = UINT64_MAX; props = drmModeObjectGetProperties(drmmode->fd, mode_res->crtcs[num], DRM_MODE_OBJECT_CRTC); @@ -4242,6 +4393,7 @@ drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; dumb_bo_destroy(drmmode->fd, drmmode_crtc->cursor_bo); + drmmode_destroy_tearfree_shadow(crtc); } } diff --color -Naur --exclude .git xorg-server-21.1.9/hw/xfree86/drivers/modesetting/drmmode_display.h xorg-server-21.1.9-patched/hw/xfree86/drivers/modesetting/drmmode_display.h --- xorg-server-21.1.9/hw/xfree86/drivers/modesetting/drmmode_display.h 2023-10-25 08:55:03.000000000 -0400 +++ xorg-server-21.1.9-patched/hw/xfree86/drivers/modesetting/drmmode_display.h 2023-07-19 23:11:47.097508779 -0400 @@ -135,6 +135,7 @@ Bool async_flip_secondaries; Bool dri2_enable; Bool present_enable; + Bool tearfree_enable; uint32_t vrr_prop_id; Bool use_ctm; @@ -167,6 +168,20 @@ } drmmode_format_rec, *drmmode_format_ptr; typedef struct { + drmmode_bo bo; + uint32_t fb_id; + PixmapPtr px; + RegionRec dmg; +} drmmode_shadow_fb_rec, *drmmode_shadow_fb_ptr; + +typedef struct { + drmmode_shadow_fb_rec buf[2]; + struct xorg_list dri_flip_list; + uint32_t back_idx; + uint32_t flip_seq; +} drmmode_tearfree_rec, *drmmode_tearfree_ptr; + +typedef struct { drmmode_ptr drmmode; drmModeCrtcPtr mode_crtc; uint32_t vblank_pipe; @@ -184,11 +199,14 @@ drmmode_bo rotate_bo; unsigned rotate_fb_id; + drmmode_tearfree_rec tearfree; PixmapPtr prime_pixmap; PixmapPtr prime_pixmap_back; unsigned prime_pixmap_x; + int src_x, src_y; + /** * @{ MSC (vblank count) handling for the PRESENT extension. * @@ -200,6 +218,8 @@ uint64_t msc_high; /** @} */ + uint64_t next_msc; + Bool need_modeset; struct xorg_list mode_list; @@ -308,8 +328,13 @@ int *depth, int *bpp); void drmmode_copy_fb(ScrnInfoPtr pScrn, drmmode_ptr drmmode); +void drmmode_copy_damage(xf86CrtcPtr crtc, PixmapPtr dst, RegionPtr damage, + Bool empty); + +int drmmode_crtc_flip(xf86CrtcPtr crtc, uint32_t fb_id, int x, int y, + uint32_t flags, void *data); -int drmmode_crtc_flip(xf86CrtcPtr crtc, uint32_t fb_id, uint32_t flags, void *data); +Bool drmmode_crtc_get_fb_id(xf86CrtcPtr crtc, uint32_t *fb_id, int *x, int *y); void drmmode_set_dpms(ScrnInfoPtr scrn, int PowerManagementMode, int flags); void drmmode_crtc_set_vrr(xf86CrtcPtr crtc, Bool enabled); diff --color -Naur --exclude .git xorg-server-21.1.9/hw/xfree86/drivers/modesetting/modesetting.man xorg-server-21.1.9-patched/hw/xfree86/drivers/modesetting/modesetting.man --- xorg-server-21.1.9/hw/xfree86/drivers/modesetting/modesetting.man 2023-10-25 08:55:03.000000000 -0400 +++ xorg-server-21.1.9-patched/hw/xfree86/drivers/modesetting/modesetting.man 2023-07-19 23:11:47.097508779 -0400 @@ -109,6 +109,21 @@ entries, if supported by the kernel. By default, GAMMA_LUT will be used for kms drivers which are known to be safe for use of GAMMA_LUT. .TP +.BI "Option \*qTearFree\*q \*q" boolean \*q +Enable tearing prevention using the hardware page flipping mechanism. +It allocates two extra scanout buffers for each CRTC and utilizes damage +tracking to minimize buffer copying and skip unnecessary flips when the +screen's contents have not changed. It works on transformed screens too, such +as rotated and scaled CRTCs. When PageFlip is enabled, fullscreen DRI +applications will still have the discretion to not use tearing prevention. +.br +The default is +.B off. +.TP +.BI "Option \*qAtomic\*q \*q" boolean \*q +Enable atomic modesetting when supported. The default is +.B off. +.TP .SH "SEE ALSO" @xservername@(@appmansuffix@), @xconfigfile@(@filemansuffix@), Xserver(@appmansuffix@), X(@miscmansuffix@) diff --color -Naur --exclude .git xorg-server-21.1.9/hw/xfree86/drivers/modesetting/pageflip.c xorg-server-21.1.9-patched/hw/xfree86/drivers/modesetting/pageflip.c --- xorg-server-21.1.9/hw/xfree86/drivers/modesetting/pageflip.c 2023-10-25 08:55:03.000000000 -0400 +++ xorg-server-21.1.9-patched/hw/xfree86/drivers/modesetting/pageflip.c 2023-07-19 23:11:47.098508794 -0400 @@ -35,8 +35,8 @@ * Returns a negative value on error, 0 if there was nothing to process, * or 1 if we handled any events. */ -int -ms_flush_drm_events(ScreenPtr screen) +static int +ms_flush_drm_events_timeout(ScreenPtr screen, int timeout) { ScrnInfoPtr scrn = xf86ScreenToScrn(screen); modesettingPtr ms = modesettingPTR(scrn); @@ -45,7 +45,7 @@ int r; do { - r = xserver_poll(&p, 1, 0); + r = xserver_poll(&p, 1, timeout); } while (r == -1 && (errno == EINTR || errno == EAGAIN)); /* If there was an error, r will be < 0. Return that. If there was @@ -63,6 +63,12 @@ return 1; } +int +ms_flush_drm_events(ScreenPtr screen) +{ + return ms_flush_drm_events_timeout(screen, 0); +} + #ifdef GLAMOR_HAS_GBM /* @@ -93,6 +99,8 @@ Bool on_reference_crtc; /* reference to the ms_flipdata */ struct ms_flipdata *flipdata; + struct xorg_list node; + uint32_t tearfree_seq; }; /** @@ -136,7 +144,8 @@ flipdata->fe_usec, flipdata->event); - drmModeRmFB(ms->fd, flipdata->old_fb_id); + if (flipdata->old_fb_id) + drmModeRmFB(ms->fd, flipdata->old_fb_id); } ms_pageflip_free(flip); } @@ -160,11 +169,32 @@ } static Bool -do_queue_flip_on_crtc(modesettingPtr ms, xf86CrtcPtr crtc, - uint32_t flags, uint32_t seq) +do_queue_flip_on_crtc(ScreenPtr screen, xf86CrtcPtr crtc, uint32_t flags, + uint32_t seq, uint32_t fb_id, int x, int y) { - return drmmode_crtc_flip(crtc, ms->drmmode.fb_id, flags, - (void *) (uintptr_t) seq); + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree; + + while (drmmode_crtc_flip(crtc, fb_id, x, y, flags, (void *)(long)seq)) { + /* 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) { + /* The failure could be caused by a pending TearFree flip, in which + * case we should wait until there's a new event and try again. + */ + if (!trf->flip_seq || ms_flush_drm_events_timeout(screen, -1) < 0) { + ms_drm_abort_seq(crtc->scrn, seq); + return TRUE; + } + } + + /* We flushed some events, so try again. */ + xf86DrvMsg(crtc->scrn->scrnIndex, X_WARNING, "flip queue retry\n"); + } + + return FALSE; } enum queue_flip_status { @@ -177,11 +207,10 @@ static int queue_flip_on_crtc(ScreenPtr screen, xf86CrtcPtr crtc, struct ms_flipdata *flipdata, - int ref_crtc_vblank_pipe, uint32_t flags) + xf86CrtcPtr ref_crtc, 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; @@ -193,7 +222,7 @@ /* 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->on_reference_crtc = crtc == ref_crtc; flip->flipdata = flipdata; seq = ms_drm_queue_alloc(crtc, flip, ms_pageflip_handler, ms_pageflip_abort); @@ -205,20 +234,9 @@ /* take a reference on flipdata for use in flip */ flipdata->flip_count++; - while (do_queue_flip_on_crtc(ms, crtc, flags, seq)) { - /* 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) { - /* Aborting will also decrement flip_count and free(flip). */ - ms_drm_abort_seq(scrn, seq); - return QUEUE_FLIP_DRM_FLUSH_FAILED; - } - - /* We flushed some events, so try again. */ - xf86DrvMsg(scrn->scrnIndex, X_WARNING, "flip queue retry\n"); - } + if (do_queue_flip_on_crtc(screen, crtc, flags, seq, ms->drmmode.fb_id, + crtc->x, crtc->y)) + return QUEUE_FLIP_DRM_FLUSH_FAILED; /* The page flip succeeded. */ return QUEUE_FLIP_SUCCESS; @@ -294,20 +312,75 @@ } } +static Bool +ms_tearfree_dri_flip(modesettingPtr ms, xf86CrtcPtr crtc, void *event, + ms_pageflip_handler_proc pageflip_handler, + ms_pageflip_abort_proc pageflip_abort) +{ + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree; + struct ms_crtc_pageflip *flip; + struct ms_flipdata *flipdata; + RegionRec region; + RegionPtr dirty; + + if (!ms_tearfree_is_active_on_crtc(crtc)) + return FALSE; + + /* Check for damage on the primary scanout to know if TearFree will flip */ + dirty = DamageRegion(ms->damage); + if (RegionNil(dirty)) + return FALSE; + + /* Compute how much of the current damage intersects with this CRTC */ + RegionInit(®ion, &crtc->bounds, 0); + RegionIntersect(®ion, ®ion, dirty); + + /* No damage on this CRTC means no TearFree flip. This means the DRI client + * didn't change this CRTC's contents at all with its presentation, possibly + * because its window is fully occluded by another window on this CRTC. + */ + if (RegionNil(®ion)) + return FALSE; + + flip = calloc(1, sizeof(*flip)); + if (!flip) + return FALSE; + + flipdata = calloc(1, sizeof(*flipdata)); + if (!flipdata) { + free(flip); + return FALSE; + } + + /* Only track the DRI client's fake flip on the reference CRTC, which aligns + * with the behavior of Present when a client copies its pixmap rather than + * directly flipping it onto the display. + */ + flip->on_reference_crtc = TRUE; + flip->flipdata = flipdata; + flip->tearfree_seq = trf->flip_seq; + flipdata->screen = xf86ScrnToScreen(crtc->scrn); + flipdata->event = event; + flipdata->flip_count = 1; + flipdata->event_handler = pageflip_handler; + flipdata->abort_handler = pageflip_abort; + + /* Keep the list in FIFO order so that clients are notified in order */ + xorg_list_append(&flip->node, &trf->dri_flip_list); + return TRUE; +} Bool ms_do_pageflip(ScreenPtr screen, PixmapPtr new_front, void *event, - int ref_crtc_vblank_pipe, + xf86CrtcPtr ref_crtc, Bool async, ms_pageflip_handler_proc pageflip_handler, ms_pageflip_abort_proc pageflip_abort, const char *log_prefix) { -#ifndef GLAMOR_HAS_GBM - return FALSE; -#else ScrnInfoPtr scrn = xf86ScreenToScrn(screen); modesettingPtr ms = modesettingPTR(scrn); xf86CrtcConfigPtr config = XF86_CRTC_CONFIG_PTR(scrn); @@ -315,6 +388,22 @@ uint32_t flags; int i; struct ms_flipdata *flipdata; + + /* A NULL pixmap indicates this DRI client's pixmap is to be flipped through + * TearFree instead. The pixmap is already copied to the primary scanout at + * this point, so all that's left is to wire up this fake flip to TearFree + * so that TearFree can send a notification to the DRI client when the + * pixmap actually appears on the display. This is the only way to let DRI + * clients accurately know when their pixmaps appear on the display when + * TearFree is enabled. + */ + if (!new_front) { + if (!ms_tearfree_dri_flip(ms, ref_crtc, event, pageflip_handler, + pageflip_abort)) + goto error_free_event; + return TRUE; + } + ms->glamor.block_handler(screen); new_front_bo.gbm = ms->glamor.gbm_bo_from_pixmap(screen, new_front); @@ -324,7 +413,7 @@ xf86DrvMsg(scrn->scrnIndex, X_ERROR, "%s: Failed to get GBM BO for flip to new front.\n", log_prefix); - return FALSE; + goto error_free_event; } flipdata = calloc(1, sizeof(struct ms_flipdata)); @@ -332,7 +421,7 @@ drmmode_bo_destroy(&ms->drmmode, &new_front_bo); xf86DrvMsg(scrn->scrnIndex, X_ERROR, "%s: Failed to allocate flipdata.\n", log_prefix); - return FALSE; + goto error_free_event; } flipdata->event = event; @@ -380,7 +469,6 @@ for (i = 0; i < config->num_crtc; i++) { enum queue_flip_status flip_status; xf86CrtcPtr crtc = config->crtc[i]; - drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; if (!xf86_crtc_on(crtc)) continue; @@ -401,13 +489,11 @@ * outputs in a "clone-mode" or "mirror-mode" configuration. */ if (ms->drmmode.can_async_flip && ms->drmmode.async_flip_secondaries && - (drmmode_crtc->vblank_pipe != ref_crtc_vblank_pipe) && - (ref_crtc_vblank_pipe >= 0)) + ref_crtc && crtc != ref_crtc) flags |= DRM_MODE_PAGE_FLIP_ASYNC; flip_status = queue_flip_on_crtc(screen, crtc, flipdata, - ref_crtc_vblank_pipe, - flags); + ref_crtc, flags); switch (flip_status) { case QUEUE_FLIP_ALLOC_FAILED: @@ -456,13 +542,150 @@ drmmode_bo_destroy(&ms->drmmode, &new_front_bo); /* if only the local reference - free the structure, * else drop the local reference and return */ - if (flipdata->flip_count == 1) + if (flipdata->flip_count == 1) { free(flipdata); - else + } else { flipdata->flip_count--; + return FALSE; + } +error_free_event: + /* Free the event since the caller has no way to know it's safe to free */ + free(event); return FALSE; -#endif /* GLAMOR_HAS_GBM */ } +Bool +ms_tearfree_dri_abort(xf86CrtcPtr crtc, + Bool (*match)(void *data, void *match_data), + void *match_data) +{ + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree; + struct ms_crtc_pageflip *flip; + + /* The window is getting destroyed; abort without notifying the client */ + xorg_list_for_each_entry(flip, &trf->dri_flip_list, node) { + if (match(flip->flipdata->event, match_data)) { + xorg_list_del(&flip->node); + ms_pageflip_abort(flip); + return TRUE; + } + } + + return FALSE; +} + +void +ms_tearfree_dri_abort_all(xf86CrtcPtr crtc) +{ + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree; + struct ms_crtc_pageflip *flip, *tmp; + uint64_t usec = 0, msc = 0; + + /* Nothing to abort if there aren't any DRI clients waiting for a flip */ + if (xorg_list_is_empty(&trf->dri_flip_list)) + return; + + /* Even though we're aborting, these clients' pixmaps were actually blitted, + * so technically the presentation isn't aborted. That's why the normal + * handler is called instead of the abort handler, along with the current + * time and MSC for this CRTC. + */ + ms_get_crtc_ust_msc(crtc, &usec, &msc); + xorg_list_for_each_entry_safe(flip, tmp, &trf->dri_flip_list, node) + ms_pageflip_handler(msc, usec, flip); + xorg_list_init(&trf->dri_flip_list); +} + +static void +ms_tearfree_dri_notify(drmmode_tearfree_ptr trf, uint64_t msc, uint64_t usec) +{ + struct ms_crtc_pageflip *flip, *tmp; + + xorg_list_for_each_entry_safe(flip, tmp, &trf->dri_flip_list, node) { + /* If a TearFree flip was already pending at the time this DRI client's + * pixmap was copied, then the pixmap isn't contained in this TearFree + * flip, but will be part of the next TearFree flip instead. + */ + if (flip->tearfree_seq) { + flip->tearfree_seq = 0; + } else { + xorg_list_del(&flip->node); + ms_pageflip_handler(msc, usec, flip); + } + } +} + +static void +ms_tearfree_flip_abort(void *data) +{ + xf86CrtcPtr crtc = data; + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree; + + trf->flip_seq = 0; + ms_tearfree_dri_abort_all(crtc); +} + +static void +ms_tearfree_flip_handler(uint64_t msc, uint64_t usec, void *data) +{ + xf86CrtcPtr crtc = data; + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree; + + /* Swap the buffers and complete the flip */ + trf->back_idx ^= 1; + trf->flip_seq = 0; + + /* Notify DRI clients that their pixmaps are now visible on the display */ + ms_tearfree_dri_notify(trf, msc, usec); +} + +Bool +ms_do_tearfree_flip(ScreenPtr screen, xf86CrtcPtr crtc) +{ + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree; + uint32_t idx = trf->back_idx, seq; + + seq = ms_drm_queue_alloc(crtc, crtc, ms_tearfree_flip_handler, + ms_tearfree_flip_abort); + if (!seq) { + /* Need to notify the DRI clients if a sequence wasn't allocated. Once a + * sequence is allocated, explicitly performing this cleanup isn't + * necessary since it's already done as part of aborting the sequence. + */ + ms_tearfree_dri_abort_all(crtc); + goto no_flip; + } + + /* Copy the damage to the back buffer and then flip it at the vblank */ + drmmode_copy_damage(crtc, trf->buf[idx].px, &trf->buf[idx].dmg, TRUE); + if (do_queue_flip_on_crtc(screen, crtc, DRM_MODE_PAGE_FLIP_EVENT, + seq, trf->buf[idx].fb_id, 0, 0)) + goto no_flip; + + trf->flip_seq = seq; + return FALSE; + +no_flip: + xf86DrvMsg(crtc->scrn->scrnIndex, X_WARNING, + "TearFree flip failed, rendering frame without TearFree\n"); + drmmode_copy_damage(crtc, trf->buf[idx ^ 1].px, + &trf->buf[idx ^ 1].dmg, FALSE); + return TRUE; +} #endif + +Bool +ms_tearfree_is_active_on_crtc(xf86CrtcPtr crtc) +{ + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree; + + /* If TearFree is enabled, XServer owns the VT, and the CRTC is active */ + return trf->buf[0].px && crtc->scrn->vtSema && xf86_crtc_on(crtc); +} diff --color -Naur --exclude .git xorg-server-21.1.9/hw/xfree86/drivers/modesetting/present.c xorg-server-21.1.9-patched/hw/xfree86/drivers/modesetting/present.c --- xorg-server-21.1.9/hw/xfree86/drivers/modesetting/present.c 2023-10-25 08:55:03.000000000 -0400 +++ xorg-server-21.1.9-patched/hw/xfree86/drivers/modesetting/present.c 2023-07-19 23:11:47.098508794 -0400 @@ -165,6 +165,13 @@ { ScreenPtr screen = crtc->pScreen; ScrnInfoPtr scrn = xf86ScreenToScrn(screen); +#ifdef GLAMOR_HAS_GBM + xf86CrtcPtr xf86_crtc = crtc->devPrivate; + + /* Check if this is a fake flip routed through TearFree and abort it */ + if (ms_tearfree_dri_abort(xf86_crtc, ms_present_event_match, &event_id)) + return; +#endif ms_drm_abort(scrn, ms_present_event_match, &event_id); } @@ -318,14 +325,32 @@ modesettingPtr ms = modesettingPTR(scrn); if (ms->drmmode.sprites_visible > 0) - return FALSE; + goto no_flip; if(!ms_present_check_unflip(crtc, window, pixmap, sync_flip, reason)) - return FALSE; + goto no_flip; ms->flip_window = window; return TRUE; + +no_flip: + /* Export some info about TearFree if Present can't flip anyway */ + if (reason) { + xf86CrtcPtr xf86_crtc = crtc->devPrivate; + drmmode_crtc_private_ptr drmmode_crtc = xf86_crtc->driver_private; + drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree; + + if (ms_tearfree_is_active_on_crtc(xf86_crtc)) { + if (trf->flip_seq) + /* The driver has a TearFree flip pending */ + *reason = PRESENT_FLIP_REASON_DRIVER_TEARFREE_FLIPPING; + else + /* The driver uses TearFree flips and there's no flip pending */ + *reason = PRESENT_FLIP_REASON_DRIVER_TEARFREE; + } + } + return FALSE; } /* @@ -343,11 +368,12 @@ ScrnInfoPtr scrn = xf86ScreenToScrn(screen); modesettingPtr ms = modesettingPTR(scrn); 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, ms->flip_window, pixmap, sync_flip, NULL)) + /* A NULL pixmap means this is a fake flip to be routed through TearFree */ + if (pixmap && + !ms_present_check_flip(crtc, ms->flip_window, pixmap, sync_flip, NULL)) return FALSE; event = calloc(1, sizeof(struct ms_present_vblank_event)); @@ -360,6 +386,12 @@ event->event_id = event_id; event->unflip = FALSE; + /* Register the fake flip (indicated by a NULL pixmap) with TearFree */ + if (!pixmap) + return ms_do_pageflip(screen, NULL, event, xf86_crtc, FALSE, + ms_present_flip_handler, ms_present_flip_abort, + "Present-TearFree-flip"); + /* A window can only flip if it covers the entire X screen. * Only one window can flip at a time. * @@ -371,7 +403,7 @@ ms_present_set_screen_vrr(scrn, TRUE); } - ret = ms_do_pageflip(screen, pixmap, event, drmmode_crtc->vblank_pipe, !sync_flip, + ret = ms_do_pageflip(screen, pixmap, event, xf86_crtc, !sync_flip, ms_present_flip_handler, ms_present_flip_abort, "Present-flip"); if (ret) @@ -403,7 +435,7 @@ event->unflip = TRUE; if (ms_present_check_unflip(NULL, screen->root, pixmap, TRUE, NULL) && - ms_do_pageflip(screen, pixmap, event, -1, FALSE, + ms_do_pageflip(screen, pixmap, event, NULL, FALSE, ms_present_flip_handler, ms_present_flip_abort, "Present-unflip")) { return; diff --color -Naur --exclude .git xorg-server-21.1.9/hw/xfree86/drivers/modesetting/vblank.c xorg-server-21.1.9-patched/hw/xfree86/drivers/modesetting/vblank.c --- xorg-server-21.1.9/hw/xfree86/drivers/modesetting/vblank.c 2023-10-25 08:55:03.000000000 -0400 +++ xorg-server-21.1.9-patched/hw/xfree86/drivers/modesetting/vblank.c 2023-07-19 23:11:47.098508794 -0400 @@ -260,6 +260,51 @@ } } +static void +ms_drm_set_seq_msc(uint32_t seq, uint64_t msc) +{ + struct ms_drm_queue *q; + + xorg_list_for_each_entry(q, &ms_drm_queue, list) { + if (q->seq == seq) { + q->msc = msc; + break; + } + } +} + +static void +ms_drm_set_seq_queued(uint32_t seq, uint64_t msc) +{ + drmmode_crtc_private_ptr drmmode_crtc; + struct ms_drm_queue *q; + + xorg_list_for_each_entry(q, &ms_drm_queue, list) { + if (q->seq == seq) { + drmmode_crtc = q->crtc->driver_private; + if (msc < drmmode_crtc->next_msc) + drmmode_crtc->next_msc = msc; + q->msc = msc; + q->kernel_queued = TRUE; + break; + } + } +} + +static Bool +ms_queue_coalesce(xf86CrtcPtr crtc, uint32_t seq, uint64_t msc) +{ + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + + /* If the next MSC is too late, then this event can't be coalesced */ + if (msc < drmmode_crtc->next_msc) + return FALSE; + + /* Set the target MSC on this sequence number */ + ms_drm_set_seq_msc(seq, msc); + return TRUE; +} + Bool ms_queue_vblank(xf86CrtcPtr crtc, ms_queue_flag flags, uint64_t msc, uint64_t *msc_queued, uint32_t seq) @@ -271,6 +316,10 @@ drmVBlank vbl; int ret; + /* Try coalescing this event into another to avoid event queue exhaustion */ + if (flags == MS_QUEUE_ABSOLUTE && ms_queue_coalesce(crtc, seq, msc)) + return TRUE; + for (;;) { /* Queue an event at the specified sequence */ if (ms->has_queue_sequence || !ms->tried_queue_sequence) { @@ -287,8 +336,10 @@ ret = drmCrtcQueueSequence(ms->fd, drmmode_crtc->mode_crtc->crtc_id, drm_flags, msc, &kernel_queued, seq); if (ret == 0) { + msc = ms_kernel_msc_to_crtc_msc(crtc, kernel_queued, TRUE); + ms_drm_set_seq_queued(seq, msc); if (msc_queued) - *msc_queued = ms_kernel_msc_to_crtc_msc(crtc, kernel_queued, TRUE); + *msc_queued = msc; ms->has_queue_sequence = TRUE; return TRUE; } @@ -310,8 +361,10 @@ vbl.request.signal = seq; ret = drmWaitVBlank(ms->fd, &vbl); if (ret == 0) { + msc = ms_kernel_msc_to_crtc_msc(crtc, vbl.reply.sequence, FALSE); + ms_drm_set_seq_queued(seq, msc); if (msc_queued) - *msc_queued = ms_kernel_msc_to_crtc_msc(crtc, vbl.reply.sequence, FALSE); + *msc_queued = msc; return TRUE; } check: @@ -418,13 +471,15 @@ if (!ms_drm_seq) ++ms_drm_seq; q->seq = ms_drm_seq++; + q->msc = UINT64_MAX; q->scrn = scrn; q->crtc = crtc; q->data = data; q->handler = handler; q->abort = abort; - xorg_list_add(&q->list, &ms_drm_queue); + /* Keep the list formatted in ascending order of sequence number */ + xorg_list_append(&q->list, &ms_drm_queue); return q->seq; } @@ -437,9 +492,18 @@ static void ms_drm_abort_one(struct ms_drm_queue *q) { + if (q->aborted) + return; + + /* Don't remove vblank events if they were queued in the kernel */ + if (q->kernel_queued) { + q->abort(q->data); + q->aborted = TRUE; + } else { xorg_list_del(&q->list); q->abort(q->data); free(q); + } } /** @@ -500,16 +564,64 @@ { struct ms_drm_queue *q, *tmp; uint32_t seq = (uint32_t) user_data; + xf86CrtcPtr crtc = NULL; + drmmode_crtc_private_ptr drmmode_crtc; + uint64_t msc, next_msc = UINT64_MAX; - xorg_list_for_each_entry_safe(q, tmp, &ms_drm_queue, list) { + /* Handle the seq for this event first in order to get the CRTC */ + xorg_list_for_each_entry(q, &ms_drm_queue, list) { if (q->seq == seq) { - uint64_t msc; + crtc = q->crtc; + msc = ms_kernel_msc_to_crtc_msc(crtc, frame, is64bit); + + /* Write the current MSC to this event to ensure its handler runs in + * the loop below. This is done because we don't want to run the + * handler right now, since we need to ensure all events are handled + * in FIFO order with respect to one another. Otherwise, if this + * event were handled first just because it was queued to the + * kernel, it could run before older events expiring at this MSC. + */ + q->msc = msc; + break; + } + } - msc = ms_kernel_msc_to_crtc_msc(q->crtc, frame, is64bit); + if (!crtc) + return; + + /* Now run all of the vblank events for this CRTC with an expired MSC */ + xorg_list_for_each_entry_safe(q, tmp, &ms_drm_queue, list) { + if (q->crtc == crtc && q->msc <= msc) { xorg_list_del(&q->list); - q->handler(msc, ns / 1000, q->data); + if (!q->aborted) + q->handler(msc, ns / 1000, q->data); free(q); - break; + } + } + + /* Find this CRTC's next queued MSC and next non-queued MSC to be handled */ + msc = UINT64_MAX; + xorg_list_for_each_entry(q, &ms_drm_queue, list) { + if (q->crtc == crtc) { + if (q->kernel_queued) { + if (q->msc < next_msc) + next_msc = q->msc; + } else if (q->msc < msc) { + msc = q->msc; + seq = q->seq; + } + } + } + + /* Queue an event if the next queued MSC isn't soon enough */ + drmmode_crtc = crtc->driver_private; + drmmode_crtc->next_msc = next_msc; + if (msc < next_msc && !ms_queue_vblank(crtc, MS_QUEUE_ABSOLUTE, msc, NULL, seq)) { + xf86DrvMsg(crtc->scrn->scrnIndex, X_WARNING, + "failed to queue next vblank event, aborting lost events\n"); + xorg_list_for_each_entry_safe(q, tmp, &ms_drm_queue, list) { + if (q->crtc == crtc && q->msc < next_msc) + ms_drm_abort_one(q); } } } diff --color -Naur --exclude .git xorg-server-21.1.9/hw/xfree86/modes/xf86Crtc.h xorg-server-21.1.9-patched/hw/xfree86/modes/xf86Crtc.h --- xorg-server-21.1.9/hw/xfree86/modes/xf86Crtc.h 2023-10-25 08:55:03.000000000 -0400 +++ xorg-server-21.1.9-patched/hw/xfree86/modes/xf86Crtc.h 2023-07-19 23:14:28.826509432 -0400 @@ -837,11 +837,11 @@ static _X_INLINE xf86OutputPtr xf86CompatOutput(ScrnInfoPtr pScrn) { - xf86CrtcConfigPtr config = XF86_CRTC_CONFIG_PTR(pScrn); + xf86CrtcConfigPtr config; if (xf86CrtcConfigPrivateIndex == -1) return NULL; - + config = XF86_CRTC_CONFIG_PTR(pScrn); if (config->compat_output < 0) return NULL; return config->output[config->compat_output]; @@ -912,6 +912,11 @@ extern _X_EXPORT Bool xf86CrtcRotate(xf86CrtcPtr crtc); +extern _X_EXPORT void + xf86RotateCrtcRedisplay(xf86CrtcPtr crtc, PixmapPtr dst_pixmap, + DrawableRec *src_drawable, RegionPtr region, + Bool transform_src); + /* * Clean up any rotation data, used when a crtc is turned off * as well as when rotation is disabled. diff --color -Naur --exclude .git xorg-server-21.1.9/hw/xfree86/modes/xf86Rotate.c xorg-server-21.1.9-patched/hw/xfree86/modes/xf86Rotate.c --- xorg-server-21.1.9/hw/xfree86/modes/xf86Rotate.c 2023-10-25 08:55:03.000000000 -0400 +++ xorg-server-21.1.9-patched/hw/xfree86/modes/xf86Rotate.c 2023-07-19 23:19:57.089448424 -0400 @@ -39,13 +39,15 @@ #include "X11/extensions/dpmsconst.h" #include "X11/Xatom.h" -static void -xf86RotateCrtcRedisplay(xf86CrtcPtr crtc, RegionPtr region) + + +void +xf86RotateCrtcRedisplay(xf86CrtcPtr crtc, PixmapPtr dst_pixmap, + DrawableRec *src_drawable, RegionPtr region, + Bool transform_src) { ScrnInfoPtr scrn = crtc->scrn; ScreenPtr screen = scrn->pScreen; - WindowPtr root = screen->root; - PixmapPtr dst_pixmap = crtc->rotatedPixmap; PictFormatPtr format = PictureWindowFormat(screen->root); int error; PicturePtr src, dst; @@ -57,7 +59,7 @@ return; src = CreatePicture(None, - &root->drawable, + src_drawable, format, CPSubwindowMode, &include_inferiors, serverClient, &error); @@ -70,9 +72,11 @@ if (!dst) return; - error = SetPictureTransform(src, &crtc->crtc_to_framebuffer); - if (error) - return; + if (transform_src) { + error = SetPictureTransform(src, &crtc->crtc_to_framebuffer); + if (error) + return; + } if (crtc->transform_in_use && crtc->filter) SetPicturePictFilter(src, crtc->filter, crtc->params, crtc->nparams); @@ -205,7 +209,9 @@ /* update damaged region */ if (RegionNotEmpty(&crtc_damage)) - xf86RotateCrtcRedisplay(crtc, &crtc_damage); + xf86RotateCrtcRedisplay(crtc, crtc->rotatedPixmap, + &pScreen->root->drawable, + &crtc_damage, TRUE); RegionUninit(&crtc_damage); } diff --color -Naur --exclude .git xorg-server-21.1.9/include/pixmap.h xorg-server-21.1.9-patched/include/pixmap.h --- xorg-server-21.1.9/include/pixmap.h 2023-10-25 08:55:03.000000000 -0400 +++ xorg-server-21.1.9-patched/include/pixmap.h 2023-07-19 23:25:00.751651234 -0400 @@ -105,13 +105,15 @@ extern _X_EXPORT Bool CreateScratchPixmapsForScreen(ScreenPtr /*pScreen */ ); -extern _X_EXPORT void FreeScratchPixmapsForScreen(ScreenPtr /*pScreen */ ); +extern _X_EXPORT Bool PixmapScreenInit(ScreenPtr /*pScreen */ ); extern _X_EXPORT PixmapPtr AllocatePixmap(ScreenPtr /*pScreen */ , int /*pixDataSize */ ); extern _X_EXPORT void FreePixmap(PixmapPtr /*pPixmap */ ); +extern _X_EXPORT void FreeScratchPixmapsForScreen(ScreenPtr /*pScreen */ ); + extern _X_EXPORT PixmapPtr PixmapShareToSecondary(PixmapPtr pixmap, ScreenPtr secondary); @@ -134,4 +136,9 @@ extern _X_EXPORT Bool PixmapSyncDirtyHelper(PixmapDirtyUpdatePtr dirty); +extern _X_EXPORT void +PixmapDirtyCopyArea(PixmapPtr dst, DrawablePtr src, + int x, int y, int dst_x, int dst_y, + RegionPtr dirty_region); + #endif /* PIXMAP_H */ diff --color -Naur --exclude .git xorg-server-21.1.9/meson.build xorg-server-21.1.9-patched/meson.build --- xorg-server-21.1.9/meson.build 2023-10-25 08:55:03.000000000 -0400 +++ xorg-server-21.1.9-patched/meson.build 2023-07-19 23:32:39.011238907 -0400 @@ -4,7 +4,7 @@ 'c_std=gnu99', ], version: '21.1.9', - meson_version: '>= 0.47.0', + meson_version: '>= 0.56.0', ) release_date = '2023-10-25' diff --color -Naur --exclude .git xorg-server-21.1.9/present/present.c xorg-server-21.1.9-patched/present/present.c --- xorg-server-21.1.9/present/present.c 2023-10-25 08:55:03.000000000 -0400 +++ xorg-server-21.1.9-patched/present/present.c 2023-07-19 23:11:47.098508794 -0400 @@ -157,7 +157,7 @@ uint64_t remainder, uint32_t options) { - const Bool synced_flip = !(options & PresentOptionAsync); + const Bool synced_flip = !(options & PresentAllAsyncOptions); uint64_t target_msc; /* If the specified target-msc lies in the future, then this diff --color -Naur --exclude .git xorg-server-21.1.9/present/present.h xorg-server-21.1.9-patched/present/present.h --- xorg-server-21.1.9/present/present.h 2023-10-25 08:55:03.000000000 -0400 +++ xorg-server-21.1.9-patched/present/present.h 2023-07-19 23:11:47.098508794 -0400 @@ -29,7 +29,15 @@ typedef enum { PRESENT_FLIP_REASON_UNKNOWN, - PRESENT_FLIP_REASON_BUFFER_FORMAT + PRESENT_FLIP_REASON_BUFFER_FORMAT, + + /* Don't add new flip reasons after the TearFree ones, since it's expected + * that the TearFree reasons are the highest ones in order to allow doing + * `reason >= PRESENT_FLIP_REASON_DRIVER_TEARFREE` to check if a reason is + * PRESENT_FLIP_REASON_DRIVER_TEARFREE{_FLIPPING}. + */ + PRESENT_FLIP_REASON_DRIVER_TEARFREE, + PRESENT_FLIP_REASON_DRIVER_TEARFREE_FLIPPING } PresentFlipReason; typedef struct present_vblank present_vblank_rec, *present_vblank_ptr; diff --color -Naur --exclude .git xorg-server-21.1.9/present/present_scmd.c xorg-server-21.1.9-patched/present/present_scmd.c --- xorg-server-21.1.9/present/present_scmd.c 2023-10-25 08:55:03.000000000 -0400 +++ xorg-server-21.1.9-patched/present/present_scmd.c 2023-07-19 23:11:47.099508810 -0400 @@ -71,6 +71,7 @@ PixmapPtr window_pixmap; WindowPtr root = screen->root; present_screen_priv_ptr screen_priv = present_screen_priv(screen); + PresentFlipReason tmp_reason = PRESENT_FLIP_REASON_UNKNOWN; if (crtc) { screen_priv = present_screen_priv(crtc->pScreen); @@ -91,6 +92,27 @@ if (!screen_priv->info->flip) return FALSE; + /* Ask the driver for permission. Do this now to see if there's TearFree. */ + if (screen_priv->info->version >= 1 && screen_priv->info->check_flip2) { + if (!(*screen_priv->info->check_flip2) (crtc, window, pixmap, sync_flip, &tmp_reason)) { + DebugPresent(("\td %08" PRIx32 " -> %08" PRIx32 "\n", window->drawable.id, pixmap ? pixmap->drawable.id : 0)); + /* It's fine to return now unless the page flip failure reason is + * PRESENT_FLIP_REASON_BUFFER_FORMAT; we must only output that + * reason if all the other checks pass. + */ + if (!reason || tmp_reason != PRESENT_FLIP_REASON_BUFFER_FORMAT) { + if (reason) + *reason = tmp_reason; + return FALSE; + } + } + } else if (screen_priv->info->check_flip) { + if (!(*screen_priv->info->check_flip) (crtc, window, pixmap, sync_flip)) { + DebugPresent(("\td %08" PRIx32 " -> %08" PRIx32 "\n", window->drawable.id, pixmap ? pixmap->drawable.id : 0)); + return FALSE; + } + } + /* Make sure the window hasn't been redirected with Composite */ window_pixmap = screen->GetWindowPixmap(window); if (window_pixmap != screen->GetScreenPixmap(screen) && @@ -115,7 +137,7 @@ /* Does the window match the pixmap exactly? */ if (window->drawable.x != 0 || window->drawable.y != 0 || -#ifdef COMPOSITE +#if defined(COMPOSITE) || defined(ROOTLESS) window->drawable.x != pixmap->screen_x || window->drawable.y != pixmap->screen_y || #endif window->drawable.width != pixmap->drawable.width || @@ -123,17 +145,10 @@ return FALSE; } - /* Ask the driver for permission */ - if (screen_priv->info->version >= 1 && screen_priv->info->check_flip2) { - if (!(*screen_priv->info->check_flip2) (crtc, window, pixmap, sync_flip, reason)) { - DebugPresent(("\td %08" PRIx32 " -> %08" PRIx32 "\n", window->drawable.id, pixmap ? pixmap->drawable.id : 0)); - return FALSE; - } - } else if (screen_priv->info->check_flip) { - if (!(*screen_priv->info->check_flip) (crtc, window, pixmap, sync_flip)) { - DebugPresent(("\td %08" PRIx32 " -> %08" PRIx32 "\n", window->drawable.id, pixmap ? pixmap->drawable.id : 0)); - return FALSE; - } + if (tmp_reason == PRESENT_FLIP_REASON_BUFFER_FORMAT) { + if (reason) + *reason = tmp_reason; + return FALSE; } return TRUE; @@ -456,7 +471,9 @@ xorg_list_for_each_entry(vblank, &window_priv->vblank, window_list) { if (vblank->queued && vblank->flip && !present_check_flip(vblank->crtc, window, vblank->pixmap, vblank->sync_flip, NULL, 0, 0, &reason)) { vblank->flip = FALSE; - vblank->reason = reason; + /* Don't spuriously flag this as a TearFree presentation */ + if (reason < PRESENT_FLIP_REASON_DRIVER_TEARFREE) + vblank->reason = reason; if (vblank->sync_flip) vblank->exec_msc = vblank->target_msc; } @@ -560,7 +577,9 @@ xorg_list_del(&vblank->window_list); vblank->queued = FALSE; - if (vblank->pixmap && vblank->window) { + if (vblank->pixmap && vblank->window && + (vblank->reason < PRESENT_FLIP_REASON_DRIVER_TEARFREE || + vblank->exec_msc != vblank->target_msc)) { if (vblank->flip) { @@ -627,6 +646,51 @@ present_execute_copy(vblank, crtc_msc); + /* With TearFree, there's no way to tell exactly when the presentation + * will be visible except by waiting for a notification from the kernel + * driver indicating that the page flip is complete. This is because the + * CRTC's MSC can change while the target MSC is calculated and even + * while the page flip IOCTL is sent to the kernel due to scheduling + * delays and/or unfortunate timing. Even worse, a page flip isn't + * actually guaranteed to be finished after one vblank; it may be + * several MSCs until a flip actually finishes depending on delays and + * load in hardware. + * + * So, to get a notification from the driver with TearFree active, the + * driver expects a present_flip() call with a NULL pixmap to indicate + * that this is a fake flip for a pixmap that's already been copied to + * the primary scanout, which will then be flipped by TearFree. TearFree + * will then send a notification once the flip containing this pixmap is + * complete. + * + * If the fake flip attempt fails, then fall back to just enqueuing a + * vblank event targeting the next MSC. + */ + if (!vblank->queued && + vblank->reason >= PRESENT_FLIP_REASON_DRIVER_TEARFREE) { + uint64_t completion_msc = crtc_msc + 1; + + /* If TearFree is already flipping then the presentation will be + * visible at the *next* next vblank. This calculation only matters + * for the vblank event fallback. + */ + if (vblank->reason == PRESENT_FLIP_REASON_DRIVER_TEARFREE_FLIPPING && + vblank->exec_msc < crtc_msc) + completion_msc++; + + /* Try the fake flip first and then fall back to a vblank event */ + if (present_flip(vblank->crtc, vblank->event_id, 0, NULL, TRUE) || + Success == screen_priv->queue_vblank(screen, + window, + vblank->crtc, + vblank->event_id, + completion_msc)) { + /* Ensure present_execute_post() runs at the next execution */ + vblank->exec_msc = vblank->target_msc; + vblank->queued = TRUE; + } + } + if (vblank->queued) { xorg_list_add(&vblank->event_queue, &present_exec_queue); xorg_list_append(&vblank->window_list, @@ -739,6 +803,11 @@ if (vblank->crtc != target_crtc || vblank->target_msc != target_msc) continue; + /* Too late to abort now if TearFree execution already happened */ + if (vblank->reason >= PRESENT_FLIP_REASON_DRIVER_TEARFREE && + vblank->exec_msc == vblank->target_msc) + continue; + present_vblank_scrap(vblank); if (vblank->flip_ready) present_re_execute(vblank); @@ -767,7 +836,12 @@ vblank->event_id = ++present_scmd_event_id; - if (vblank->flip && vblank->sync_flip) + /* The soonest presentation is crtc_msc+2 if TearFree is already flipping */ + if (vblank->reason == PRESENT_FLIP_REASON_DRIVER_TEARFREE_FLIPPING && + !msc_is_after(vblank->exec_msc, crtc_msc + 1)) + vblank->exec_msc -= 2; + else if (vblank->reason >= PRESENT_FLIP_REASON_DRIVER_TEARFREE || + (vblank->flip && vblank->sync_flip)) vblank->exec_msc--; xorg_list_append(&vblank->event_queue, &present_exec_queue);