From dd080e7fda2be54e2bcec3814473f90b326cb256 Mon Sep 17 00:00:00 2001 From: Ian Eure Date: Fri, 27 Mar 2026 20:49:08 -0700 Subject: [PATCH] gnu: xorg-server: Apply tearfree patch for modesetting driver. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Much of the modern Linux video stack depends on using kernel modesetting (KMS) drivers. Because of this, not much effort has been put into the legacy stuff, which have grown less supported and more buggy. Unfortunately, the modesetting Xorg driver didn’t initially have support for tear-free video (which most legacy drivers did), requiring additional software -- a compositor -- to fix this. Sadly, there’s no good option for a compositor -- they all require hardware-specific tweaking and tuning, and tend to be quite buggy. Support for a "TearFree" option merged in 2022[1], but there hasn’t been an Xorg release since it was merged. This PR applies the patch from that MR to the last release. It required a one-character edit to apply cleanly, necesitating inclusion in the Guix repo. I tested this on bare metal and it appears to work well. [1]: https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/1006 * gnu/local.mk (dist_patch_DATA): Add patch. * gnu/packages/patches/xorg-server-tearfree-modesetting.patch: New file. * gnu/packages/xorg.scm (xorg-server): Apply modesetting tearfree patch. Change-Id: I60a705b35cb51bfd7de79aba406bc4b7b3934e48 --- gnu/local.mk | 1 + .../xorg-server-tearfree-modesetting.patch | 1403 +++++++++++++++++ gnu/packages/xorg.scm | 15 +- 3 files changed, 1414 insertions(+), 5 deletions(-) create mode 100644 gnu/packages/patches/xorg-server-tearfree-modesetting.patch 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