diff --git a/gnu/local.mk b/gnu/local.mk index fc3bb46bc9..d4019b94f1 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -2606,6 +2606,7 @@ dist_patch_DATA = \ %D%/packages/patches/xiphos-glib.patch \ %D%/packages/patches/xlispstat-fix-compilation-with-modern-gcc.patch \ %D%/packages/patches/xmonad-dynamic-linking.patch \ + %D%/packages/patches/xorg-server-tearfree-modesetting.patch \ %D%/packages/patches/xplanet-1.3.1-cxx11-eof.patch \ %D%/packages/patches/xplanet-1.3.1-libdisplay_DisplayOutput.cpp.patch \ %D%/packages/patches/xplanet-1.3.1-libimage_gif.c.patch \ diff --git a/gnu/packages/patches/xorg-server-tearfree-modesetting.patch b/gnu/packages/patches/xorg-server-tearfree-modesetting.patch new file mode 100644 index 0000000000..8ea8055d92 --- /dev/null +++ b/gnu/packages/patches/xorg-server-tearfree-modesetting.patch @@ -0,0 +1,1403 @@ +Patch taken from upstream MR +https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/1006 -- merged, +but not yet included in a release. + +A minor edit to the ABI_VIDEODRV_VERSION hunk was needed to apply to 21.1.21. + + +diff --git a/dix/pixmap.c b/dix/pixmap.c +index 5a0146bbb667a07bee5b53ff5390e435976d2d4b..0b01c4ee0640677fbd2f2fff36ad1517135e0a4e 100644 +--- a/dix/pixmap.c ++++ b/dix/pixmap.c +@@ -262,12 +262,11 @@ PixmapStopDirtyTracking(DrawablePtr src, PixmapPtr secondary_dst) + 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,9 +293,8 @@ PixmapDirtyCopyArea(PixmapPtr dst, + 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); +@@ -408,7 +406,8 @@ Bool PixmapSyncDirtyHelper(PixmapDirtyUpdatePtr dirty) + 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 --git a/hw/xfree86/common/xf86Module.h b/hw/xfree86/common/xf86Module.h +index 33cf0e56a45b0202ab45cfc86538fd43e550fc80..c59e53a55c68f1a55262e3b85865192e68380a5f 100644 +--- a/hw/xfree86/common/xf86Module.h ++++ b/hw/xfree86/common/xf86Module.h +@@ -74,7 +74,7 @@ + * mask is 0xFFFF0000. + */ + #define ABI_ANSIC_VERSION SET_ABI_VERSION(0, 4) +-#define ABI_VIDEODRV_VERSION SET_ABI_VERSION(25, 2) ++#define ABI_VIDEODRV_VERSION SET_ABI_VERSION(25, 4) + #define ABI_XINPUT_VERSION SET_ABI_VERSION(24, 4) + #define ABI_EXTENSION_VERSION SET_ABI_VERSION(10, 0) + +diff --git a/hw/xfree86/drivers/modesetting/driver.c b/hw/xfree86/drivers/modesetting/driver.c +index fe3315a9c49cfdb545f062e77db46fc2e3dcc42e..7a7d5c3887fcf226347582c3c3482ab395aa04e3 100644 +--- a/hw/xfree86/drivers/modesetting/driver.c ++++ b/hw/xfree86/drivers/modesetting/driver.c +@@ -145,6 +145,7 @@ static const OptionInfoRec Options[] = { + {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} + }; + +@@ -548,14 +549,16 @@ rotate_clip(PixmapPtr pixmap, BoxPtr rect, drmModeClip *clip, Rotation rotation) + } + + static int +-dispatch_dirty_region(ScrnInfoPtr scrn, xf86CrtcPtr crtc, +- 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); +@@ -579,12 +582,102 @@ dispatch_dirty_region(ScrnInfoPtr scrn, xf86CrtcPtr crtc, + } + } + ++ 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; ++ ++ /* Skip disabled CRTCs and those which aren't using TearFree */ ++ if (!trf->buf[0].px || !crtc->scrn->vtSema || !xf86_crtc_on(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) + { +@@ -606,12 +699,9 @@ dispatch_dirty(ScreenPtr pScreen) + + ret = dispatch_dirty_region(scrn, crtc, 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; + } + } +@@ -742,10 +832,13 @@ msBlockHandler(ScreenPtr pScreen, void *timeout) + 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 +@@ -1281,6 +1374,27 @@ PreInit(ScrnInfoPtr pScrn, int flags) + ms->atomic_modeset = FALSE; + } + ++ /* 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); + if (ret == 0 && value != 0) +@@ -1628,13 +1742,13 @@ CreateScreenResources(ScreenPtr pScreen) + + 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 --git a/hw/xfree86/drivers/modesetting/driver.h b/hw/xfree86/drivers/modesetting/driver.h +index 71aa8730ec2fd74bec50d2bf237b474abe5197df..3f2b1d1aef4170793a4c2bfd18543130ef5aa34f 100644 +--- a/hw/xfree86/drivers/modesetting/driver.h ++++ b/hw/xfree86/drivers/modesetting/driver.h +@@ -61,6 +61,7 @@ typedef enum { + OPTION_VARIABLE_REFRESH, + OPTION_USE_GAMMA_LUT, + OPTION_ASYNC_FLIP_SECONDARIES, ++ OPTION_TEARFREE, + } modesettingOpts; + + typedef struct +@@ -86,10 +87,13 @@ struct ms_drm_queue { + 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 { +@@ -238,6 +242,8 @@ Bool ms_do_pageflip(ScreenPtr screen, + ms_pageflip_abort_proc pageflip_abort, + const char *log_prefix); + ++Bool ms_do_tearfree_flip(ScreenPtr screen, xf86CrtcPtr crtc); ++ + #endif + + int ms_flush_drm_events(ScreenPtr screen); +diff --git a/hw/xfree86/drivers/modesetting/drmmode_display.c b/hw/xfree86/drivers/modesetting/drmmode_display.c +index 65e8e63353a04693de76fba1c808c8bfcfd63676..8f8e4060a9fc27c4dbc7b82c4fa01f9fbbb8463c 100644 +--- a/hw/xfree86/drivers/modesetting/drmmode_display.c ++++ b/hw/xfree86/drivers/modesetting/drmmode_display.c +@@ -632,6 +632,7 @@ 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 @@ drmmode_crtc_get_fb_id(xf86CrtcPtr crtc, uint32_t *fb_id, int *x, int *y) + *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_crtc_set_mode(xf86CrtcPtr crtc, Bool test_only) + 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 @@ drmmode_crtc_set_mode(xf86CrtcPtr crtc, Bool test_only) + } + + 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 @@ drmmode_crtc_flip(xf86CrtcPtr crtc, uint32_t fb_id, uint32_t flags, void *data) + 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 @@ drmmode_crtc_flip(xf86CrtcPtr crtc, uint32_t fb_id, uint32_t flags, void *data) + 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 @@ drmmode_copy_fb(ScrnInfoPtr pScrn, drmmode_ptr drmmode) + #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 @@ drmmode_set_mode_major(xf86CrtcPtr crtc, DisplayModePtr mode, + 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 @@ drmmode_set_mode_major(xf86CrtcPtr crtc, DisplayModePtr mode, + 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 @@ drmmode_clear_pixmap(PixmapPtr pixmap) + } + + 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,70 +2111,91 @@ static Bool + 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 pixmap; ++} ++ ++static PixmapPtr ++drmmode_shadow_create(xf86CrtcPtr crtc, void *data, int width, int height) ++{ ++ drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + +- return rotate_pixmap; ++ 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) + { +@@ -2380,6 +2529,7 @@ drmmode_crtc_init(ScrnInfoPtr pScrn, drmmode_ptr drmmode, drmModeResPtr mode_res + drmmode_crtc->drmmode = drmmode; + drmmode_crtc->vblank_pipe = drmmode_crtc_vblank_pipe(num); + xorg_list_init(&drmmode_crtc->mode_list); ++ drmmode_crtc->next_msc = UINT64_MAX; + + props = drmModeObjectGetProperties(drmmode->fd, mode_res->crtcs[num], + DRM_MODE_OBJECT_CRTC); +@@ -4242,6 +4392,7 @@ drmmode_free_bos(ScrnInfoPtr pScrn, drmmode_ptr drmmode) + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + + dumb_bo_destroy(drmmode->fd, drmmode_crtc->cursor_bo); ++ drmmode_destroy_tearfree_shadow(crtc); + } + } + +diff --git a/hw/xfree86/drivers/modesetting/drmmode_display.h b/hw/xfree86/drivers/modesetting/drmmode_display.h +index 2a9a915293a5ab092bd4de9749b795228a505b6f..145cb8cc7bac04da8f83d55dd85699523a4b873a 100644 +--- a/hw/xfree86/drivers/modesetting/drmmode_display.h ++++ b/hw/xfree86/drivers/modesetting/drmmode_display.h +@@ -135,6 +135,7 @@ typedef struct { + Bool async_flip_secondaries; + Bool dri2_enable; + Bool present_enable; ++ Bool tearfree_enable; + + uint32_t vrr_prop_id; + Bool use_ctm; +@@ -166,6 +167,19 @@ typedef struct { + uint64_t *modifiers; + } 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]; ++ uint32_t back_idx; ++ uint32_t flip_seq; ++} drmmode_tearfree_rec, *drmmode_tearfree_ptr; ++ + typedef struct { + drmmode_ptr drmmode; + drmModeCrtcPtr mode_crtc; +@@ -184,11 +198,14 @@ typedef struct { + + 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 +217,8 @@ typedef struct { + uint64_t msc_high; + /** @} */ + ++ uint64_t next_msc; ++ + Bool need_modeset; + struct xorg_list mode_list; + +@@ -308,8 +327,11 @@ void drmmode_get_default_bpp(ScrnInfoPtr pScrn, drmmode_ptr drmmmode, + 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, uint32_t flags, void *data); ++int drmmode_crtc_flip(xf86CrtcPtr crtc, uint32_t fb_id, int x, int y, ++ uint32_t flags, void *data); + + Bool drmmode_crtc_get_fb_id(xf86CrtcPtr crtc, uint32_t *fb_id, int *x, int *y); + +diff --git a/hw/xfree86/drivers/modesetting/modesetting.man b/hw/xfree86/drivers/modesetting/modesetting.man +index 71790011ec93f903a7b689980578086de144e70b..9e40b04f32251ac6090d16ccbb59324272f6ae7a 100644 +--- a/hw/xfree86/drivers/modesetting/modesetting.man ++++ b/hw/xfree86/drivers/modesetting/modesetting.man +@@ -109,6 +109,17 @@ When enabled, this option allows the driver to use gamma ramps with more + 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 + .SH "SEE ALSO" + @xservername@(@appmansuffix@), @xconfigfile@(@filemansuffix@), Xserver(@appmansuffix@), + X(@miscmansuffix@) +diff --git a/hw/xfree86/drivers/modesetting/pageflip.c b/hw/xfree86/drivers/modesetting/pageflip.c +index 23ee95f9a68d96c037c33ce28e2afb7761c552bb..8d57047efa6823bedd5de9f930fe2aa4ef6b3ce4 100644 +--- a/hw/xfree86/drivers/modesetting/pageflip.c ++++ b/hw/xfree86/drivers/modesetting/pageflip.c +@@ -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 @@ ms_flush_drm_events(ScreenPtr screen) + 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 @@ ms_flush_drm_events(ScreenPtr screen) + return 1; + } + ++int ++ms_flush_drm_events(ScreenPtr screen) ++{ ++ return ms_flush_drm_events_timeout(screen, 0); ++} ++ + #ifdef GLAMOR_HAS_GBM + + /* +@@ -160,11 +166,32 @@ ms_pageflip_abort(void *data) + } + + 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 { +@@ -205,20 +232,9 @@ queue_flip_on_crtc(ScreenPtr screen, xf86CrtcPtr crtc, + /* 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; +@@ -465,4 +481,50 @@ error_out: + #endif /* GLAMOR_HAS_GBM */ + } + ++static void ++ms_tearfree_flip_abort(void *data) ++{ ++ drmmode_tearfree_ptr trf = data; ++ ++ trf->flip_seq = 0; ++} ++ ++static void ++ms_tearfree_flip_handler(uint64_t msc, uint64_t usec, void *data) ++{ ++ drmmode_tearfree_ptr trf = data; ++ ++ /* Swap the buffers and complete the flip */ ++ trf->back_idx ^= 1; ++ trf->flip_seq = 0; ++} ++ ++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, trf, ms_tearfree_flip_handler, ++ ms_tearfree_flip_abort); ++ if (!seq) ++ 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 +diff --git a/hw/xfree86/drivers/modesetting/present.c b/hw/xfree86/drivers/modesetting/present.c +index c3266d87116a8841ea4c9c302f43d8e706c427e6..642f7baaff93228b7b350b8f084c0a47d2b74655 100644 +--- a/hw/xfree86/drivers/modesetting/present.c ++++ b/hw/xfree86/drivers/modesetting/present.c +@@ -318,14 +318,32 @@ ms_present_check_flip(RRCrtcPtr crtc, + 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 && ms->drmmode.tearfree_enable) { ++ xf86CrtcPtr xf86_crtc = crtc->devPrivate; ++ drmmode_crtc_private_ptr drmmode_crtc = xf86_crtc->driver_private; ++ drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree; ++ ++ if (trf->buf[0].px) { ++ 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; + } + + /* +diff --git a/hw/xfree86/drivers/modesetting/vblank.c b/hw/xfree86/drivers/modesetting/vblank.c +index ea9e7a88c5dfb55b553d66e34070f99e3fda44ac..4d250aa3480b704fdb94325885a1e453abb0c429 100644 +--- a/hw/xfree86/drivers/modesetting/vblank.c ++++ b/hw/xfree86/drivers/modesetting/vblank.c +@@ -260,6 +260,51 @@ ms_get_kernel_ust_msc(xf86CrtcPtr crtc, + } + } + ++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 @@ ms_queue_vblank(xf86CrtcPtr crtc, ms_queue_flag flags, + 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 @@ ms_queue_vblank(xf86CrtcPtr crtc, ms_queue_flag flags, + 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 @@ ms_queue_vblank(xf86CrtcPtr crtc, ms_queue_flag flags, + 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 @@ ms_drm_queue_alloc(xf86CrtcPtr crtc, + 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 @@ ms_drm_queue_alloc(xf86CrtcPtr crtc, + 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,18 +564,61 @@ ms_drm_sequence_handler(int fd, uint64_t frame, uint64_t ns, Bool is64bit, uint6 + { + 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; +- +- msc = ms_kernel_msc_to_crtc_msc(q->crtc, frame, is64bit); ++ crtc = q->crtc; ++ msc = ms_kernel_msc_to_crtc_msc(crtc, frame, is64bit); + 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; + } + } ++ ++ 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); ++ if (!q->aborted) ++ q->handler(msc, ns / 1000, q->data); ++ free(q); ++ } ++ } ++ ++ /* 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); ++ } ++ } + } + + static void +diff --git a/hw/xfree86/modes/xf86Crtc.h b/hw/xfree86/modes/xf86Crtc.h +index e36adbe0033a9afb1f1a56d6ff0934a3f22ec894..e9db8e3e53863b4b78692023a831c1dc8f8769ae 100644 +--- a/hw/xfree86/modes/xf86Crtc.h ++++ b/hw/xfree86/modes/xf86Crtc.h +@@ -912,6 +912,11 @@ extern _X_EXPORT void + 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 --git a/hw/xfree86/modes/xf86Rotate.c b/hw/xfree86/modes/xf86Rotate.c +index ea9c43c0f237acffaa808b8826d25a489d0bb48d..944a01b1c01ba9107e7e2bbcccd8bb2aa12fe29d 100644 +--- a/hw/xfree86/modes/xf86Rotate.c ++++ b/hw/xfree86/modes/xf86Rotate.c +@@ -39,13 +39,13 @@ + #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 +57,7 @@ xf86RotateCrtcRedisplay(xf86CrtcPtr crtc, RegionPtr region) + return; + + src = CreatePicture(None, +- &root->drawable, ++ src_drawable, + format, + CPSubwindowMode, + &include_inferiors, serverClient, &error); +@@ -70,9 +70,11 @@ xf86RotateCrtcRedisplay(xf86CrtcPtr crtc, RegionPtr region) + 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 +207,9 @@ xf86RotateRedisplay(ScreenPtr pScreen) + + /* 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 --git a/include/pixmap.h b/include/pixmap.h +index 7144bfb30e8ba1fcd1b21e21e33db31d24460080..e251690d5620b3d2cf969a1097349624a15bd392 100644 +--- a/include/pixmap.h ++++ b/include/pixmap.h +@@ -134,4 +134,9 @@ PixmapStopDirtyTracking(DrawablePtr src, PixmapPtr slave_dst); + 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 --git a/present/present.h b/present/present.h +index d41b36033f93fa9b8e0ff02c13f6fa9ec8524208..1d7b0ce42f192f5b0751cf31ad896d74560e207f 100644 +--- a/present/present.h ++++ b/present/present.h +@@ -29,7 +29,9 @@ + + typedef enum { + PRESENT_FLIP_REASON_UNKNOWN, +- PRESENT_FLIP_REASON_BUFFER_FORMAT ++ PRESENT_FLIP_REASON_BUFFER_FORMAT, ++ PRESENT_FLIP_REASON_DRIVER_TEARFREE, ++ PRESENT_FLIP_REASON_DRIVER_TEARFREE_FLIPPING + } PresentFlipReason; + + typedef struct present_vblank present_vblank_rec, *present_vblank_ptr; +diff --git a/present/present_scmd.c b/present/present_scmd.c +index 239055bc10d8c934555356c4213cda37dbff711e..46fd9a1fd3341715cf3ccbe3c6fa1d1f8fca7dd6 100644 +--- a/present/present_scmd.c ++++ b/present/present_scmd.c +@@ -71,6 +71,7 @@ present_check_flip(RRCrtcPtr crtc, + 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 @@ present_check_flip(RRCrtcPtr crtc, + 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) && +@@ -123,17 +145,10 @@ present_check_flip(RRCrtcPtr crtc, + 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 @@ present_check_flip_window (WindowPtr window) + 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; + } +@@ -537,6 +554,7 @@ present_execute(present_vblank_ptr vblank, uint64_t ust, uint64_t crtc_msc) + WindowPtr window = vblank->window; + ScreenPtr screen = window->drawable.pScreen; + present_screen_priv_ptr screen_priv = present_screen_priv(screen); ++ uint64_t completion_msc; + if (vblank && vblank->crtc) { + screen_priv=present_screen_priv(vblank->crtc->pScreen); + } +@@ -560,7 +578,9 @@ present_execute(present_vblank_ptr vblank, uint64_t ust, uint64_t crtc_msc) + 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 +647,30 @@ present_execute(present_vblank_ptr vblank, uint64_t ust, uint64_t crtc_msc) + + present_execute_copy(vblank, crtc_msc); + ++ /* The presentation will be visible at the next vblank with TearFree, so ++ * the PresentComplete notification needs to be sent at the next vblank. ++ * If TearFree is already flipping then the presentation will be visible ++ * at the *next* next vblank. ++ */ ++ completion_msc = crtc_msc + 1; ++ switch (vblank->reason) { ++ case PRESENT_FLIP_REASON_DRIVER_TEARFREE_FLIPPING: ++ if (vblank->exec_msc < crtc_msc) ++ completion_msc++; ++ case PRESENT_FLIP_REASON_DRIVER_TEARFREE: ++ if (Success == screen_priv->queue_vblank(screen, ++ window, ++ vblank->crtc, ++ vblank->event_id, ++ completion_msc)) { ++ /* Ensure present_execute_post() runs at the next MSC */ ++ vblank->exec_msc = vblank->target_msc; ++ vblank->queued = TRUE; ++ } ++ default: ++ break; ++ } ++ + if (vblank->queued) { + xorg_list_add(&vblank->event_queue, &present_exec_queue); + xorg_list_append(&vblank->window_list, +@@ -739,6 +783,11 @@ present_scmd_pixmap(WindowPtr window, + 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 +816,12 @@ present_scmd_pixmap(WindowPtr window, + + 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); diff --git a/gnu/packages/xorg.scm b/gnu/packages/xorg.scm index e8f5d348e7..364e3415ac 100644 --- a/gnu/packages/xorg.scm +++ b/gnu/packages/xorg.scm @@ -5216,19 +5216,22 @@ by the Xorg server.") (base32 "06dm3bxb1xgzn994wfk8a1irajhms78k1f14c2p5nr1zbdafbjy0")) (patches - (list + (cons ;; See: ;; https://lists.fedoraproject.org/archives/list/devel@lists. ;; fedoraproject.org/message/JU655YB7AM4OOEQ4MOMCRHJTYJ76VFOK/ (origin (method url-fetch) (uri (string-append - "http://pkgs.fedoraproject.org/cgit/rpms/xorg-x11-server.git" - "/plain/06_use-intel-only-on-pre-gen4.diff")) + "https://src.fedoraproject.org/rpms/xorg-x11-server/raw/" + "ee515e44b07e37689abf48cf2fffb41578f3bc1d/f/" + "06_use-intel-only-on-pre-gen4.diff")) (sha256 (base32 "0mm70y058r8s9y9jiv7q2myv0ycnaw3iqzm7d274410s0ik38w7q")) - (file-name "xorg-server-use-intel-only-on-pre-gen4.diff")))))) + (file-name "xorg-server-use-intel-only-on-pre-gen4.diff")) + ;; https://codeberg.org/guix/guix/issues/1006 + (search-patches "xorg-server-tearfree-modesetting.patch"))))) (build-system gnu-build-system) (propagated-inputs ;; The following libraries are required by xorg-server.pc. @@ -5341,7 +5344,9 @@ draggable titlebars and borders.") "/xserver/xorg-server-" version ".tar.xz")) (sha256 (base32 - "12g0g9ksswzx1kgn23gvrpa570fnpkdkmw1dfqjjg4422a884744"))))))) + "12g0g9ksswzx1kgn23gvrpa570fnpkdkmw1dfqjjg4422a884744")) + ;; Exclude the modesetting tearfree patch, it doesn't apply to 21.1.15. + (patches (list-head (origin-patches (package-source xorg-server)) 1))))))) ;;; XXX: Not really at home, but unless we break the inheritance between ;;; tigervnc-server and xorg-server, it must live here to avoid cyclic module