[PATCH] x11-servers/xorg-server: modesetting: add support for TearFree page flips

Christian Weisgerber <[email protected]>
Newsgroups gmane.os.freebsd.devel.x11
Message-ID <[email protected]>
If you experience tearing during scrolling or video display, and
don't find that "install a compositor!" is helpful or fixes the
problem, here's an attached patch against x11-servers/xorg-server
21.1.16.  This backports unreleased X.org support for a "TearFree"
option to the modesetting section of xorg.conf.

Original work by Joshua Stein for OpenBSD.
https://marc.info/?l=openbsd-tech&m=171591279709952

I have updated the patch so it applies to 21.1.16.

I'm _not_ submitting it for inclusion in the port.  This is just
something in my local Git repository that a few people might find
useful.

-- 
Christian "naddy" Weisgerber                          [email protected]
0001-x11-servers-xorg-server-modesetting-add-support-for-.patch (text/x-diff, 69.2 KB)
From b103c9f880bc83638c8ff23a7a3cc34efc707ce6 Mon Sep 17 00:00:00 2001
From: Christian Weisgerber <[email protected]>
Date: Wed, 3 Jul 2024 20:26:38 +0200
Subject: [PATCH] x11-servers/xorg-server: modesetting: add support for
 TearFree page flips

---
 x11-servers/xorg-server/Makefile              |    2 +
 .../xorg-server/files/extra-patch-tearfree    | 1902 +++++++++++++++++
 2 files changed, 1904 insertions(+)
 create mode 100644 x11-servers/xorg-server/files/extra-patch-tearfree

diff --git a/x11-servers/xorg-server/Makefile b/x11-servers/xorg-server/Makefile
index 6edb2e9a5ae9..203168bfe8d6 100644
--- a/x11-servers/xorg-server/Makefile
+++ b/x11-servers/xorg-server/Makefile
@@ -38,6 +38,8 @@ PLIST_FILES=	bin/${BINARY_NAME} \
 
 DESCR=	${.CURDIR}/pkg-descr-${FLAVOR}
 
+EXTRA_PATCHES+=	${FILESDIR}/extra-patch-tearfree
+
 BUILD_DEPENDS+=	${LOCALBASE}/libdata/pkgconfig/dri.pc:graphics/mesa-dri
 RUN_DEPENDS+=	xkeyboard-config>=2.5:x11/xkeyboard-config \
 		xkbcomp:x11/xkbcomp \
diff --git a/x11-servers/xorg-server/files/extra-patch-tearfree b/x11-servers/xorg-server/files/extra-patch-tearfree
new file mode 100644
index 000000000000..ebc9145cd503
--- /dev/null
+++ b/x11-servers/xorg-server/files/extra-patch-tearfree
@@ -0,0 +1,1902 @@
+modesetting: add support for TearFree page flips
+
+https://marc.info/?l=openbsd-tech&m=174118941524729
+
+--- dix/pixmap.c.orig	2025-02-25 19:56:05.000000000 +0100
++++ dix/pixmap.c	2025-03-12 15:54:19.776797000 +0100
+@@ -262,12 +262,11 @@
+     return TRUE;
+ }
+ 
+-static void
+-PixmapDirtyCopyArea(PixmapPtr dst,
+-                    PixmapDirtyUpdatePtr dirty,
++void
++PixmapDirtyCopyArea(PixmapPtr dst, DrawablePtr src,
++                    int x, int y, int dst_x, int dst_y,
+                     RegionPtr dirty_region)
+ {
+-    DrawablePtr src = dirty->src;
+     ScreenPtr pScreen = src->pScreen;
+     int n;
+     BoxPtr b;
+@@ -294,9 +293,8 @@
+         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 @@
+     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;
+--- hw/xfree86/common/xf86Module.h.orig	2025-02-25 19:56:05.000000000 +0100
++++ hw/xfree86/common/xf86Module.h	2025-03-12 15:54:19.777966000 +0100
+@@ -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, 3)
+ #define ABI_XINPUT_VERSION	SET_ABI_VERSION(24, 4)
+ #define ABI_EXTENSION_VERSION	SET_ABI_VERSION(10, 0)
+ 
+--- hw/xfree86/drivers/modesetting/dri2.c.orig	2025-02-25 19:56:05.000000000 +0100
++++ hw/xfree86/drivers/modesetting/dri2.c	2025-03-12 15:54:19.780551000 +0100
+@@ -483,7 +483,6 @@
+     modesettingPtr ms = modesettingPTR(scrn);
+     ms_dri2_buffer_private_ptr back_priv = info->back->driverPrivate;
+     struct ms_dri2_vblank_event *event;
+-    drmmode_crtc_private_ptr drmmode_crtc = info->crtc->driver_private;
+ 
+     event = calloc(1, sizeof(struct ms_dri2_vblank_event));
+     if (!event)
+@@ -495,7 +494,7 @@
+     event->event_data = info->event_data;
+ 
+     if (ms_do_pageflip(screen, back_priv->pixmap, event,
+-                       drmmode_crtc->vblank_pipe, FALSE,
++                       info->crtc, FALSE,
+                        ms_dri2_flip_handler,
+                        ms_dri2_flip_abort,
+                        "DRI2-flip")) {
+--- hw/xfree86/drivers/modesetting/driver.c.orig	2025-02-25 19:56:05.000000000 +0100
++++ hw/xfree86/drivers/modesetting/driver.c	2025-03-12 15:54:19.784479000 +0100
+@@ -145,6 +145,7 @@
+     {OPTION_VARIABLE_REFRESH, "VariableRefresh", OPTV_BOOLEAN, {0}, FALSE},
+     {OPTION_USE_GAMMA_LUT, "UseGammaLUT", OPTV_BOOLEAN, {0}, FALSE},
+     {OPTION_ASYNC_FLIP_SECONDARIES, "AsyncFlipSecondaries", OPTV_BOOLEAN, {0}, FALSE},
++    {OPTION_TEARFREE, "TearFree", OPTV_BOOLEAN, {0}, FALSE},
+     {-1, NULL, OPTV_NONE, {0}, FALSE}
+ };
+ 
+@@ -548,14 +549,16 @@
+ }
+ 
+ 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,13 +582,105 @@
+             }
+         }
+ 
++        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(&region, &crtc->bounds, 0);
++        RegionIntersect(&region, &region, dirty);
++
++        if (trf->buf[0].px) {
++            for (i = 0; i < ARRAY_SIZE(trf->buf); i++)
++                RegionUnion(&trf->buf[i].dmg, &trf->buf[i].dmg, &region);
++        } else {
++            /* Just notify the kernel of the damages if TearFree isn't used */
++            dispatch_damages(scrn, crtc, &region,
++                             pScreen->GetScreenPixmap(pScreen),
++                             NULL, ms->drmmode.fb_id);
++        }
++    }
++    DamageEmpty(ms->damage);
++}
++
++static void
++ms_tearfree_do_flips(ScreenPtr pScreen)
++{
++#ifdef GLAMOR_HAS_GBM
++    ScrnInfoPtr scrn = xf86ScreenToScrn(pScreen);
++    xf86CrtcConfigPtr xf86_config = XF86_CRTC_CONFIG_PTR(scrn);
++    modesettingPtr ms = modesettingPTR(scrn);
++    int c;
++
++    if (!ms->drmmode.tearfree_enable)
++        return;
++
++    for (c = 0; c < xf86_config->num_crtc; c++) {
++        xf86CrtcPtr crtc = xf86_config->crtc[c];
++        drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
++        drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree;
++
++        if (!ms_tearfree_is_active_on_crtc(crtc)) {
++            /* Notify any lingering DRI clients waiting for a flip to finish */
++            ms_tearfree_dri_abort_all(crtc);
++            continue;
++        }
++
++        /* Skip if the last flip is still pending, a DRI client is flipping, or
++         * there isn't any damage on the front buffer.
++         */
++        if (trf->flip_seq || ms->drmmode.dri2_flipping ||
++            ms->drmmode.present_flipping ||
++            RegionNil(&trf->buf[trf->back_idx ^ 1].dmg))
++            continue;
++
++        /* Flip. If it fails, notify the kernel of the front buffer damages */
++        if (ms_do_tearfree_flip(pScreen, crtc)) {
++            dispatch_damages(scrn, crtc, &trf->buf[trf->back_idx ^ 1].dmg,
++                             trf->buf[trf->back_idx ^ 1].px, NULL,
++                             trf->buf[trf->back_idx ^ 1].fb_id);
++            RegionEmpty(&trf->buf[trf->back_idx ^ 1].dmg);
++        }
++    }
++#endif
++}
++
++static void
+ dispatch_dirty(ScreenPtr pScreen)
+ {
+     ScrnInfoPtr scrn = xf86ScreenToScrn(pScreen);
+@@ -742,10 +837,13 @@
+     pScreen->BlockHandler = msBlockHandler;
+     if (pScreen->isGPU && !ms->drmmode.reverse_prime_offload_mode)
+         dispatch_secondary_dirty(pScreen);
++    else if (ms->drmmode.tearfree_enable)
++        ms_tearfree_update_damages(pScreen);
+     else if (ms->dirty_enabled)
+         dispatch_dirty(pScreen);
+ 
+     ms_dirty_update(pScreen, timeout);
++    ms_tearfree_do_flips(pScreen);
+ }
+ 
+ static void
+@@ -1277,10 +1375,35 @@
+     if (xf86ReturnOptValBool(ms->drmmode.Options, OPTION_ATOMIC, FALSE)) {
+         ret = drmSetClientCap(ms->fd, DRM_CLIENT_CAP_ATOMIC, 1);
+         ms->atomic_modeset = (ret == 0);
++        if (!ms->atomic_modeset)
++            xf86DrvMsg(pScrn->scrnIndex, X_WARNING, "Atomic modesetting not supported\n");
+     } else {
+         ms->atomic_modeset = FALSE;
+     }
++    xf86DrvMsg(pScrn->scrnIndex, X_INFO,
++               "Atomic modesetting %sabled\n", ms->atomic_modeset ? "en" : "dis");
+ 
++    /* TearFree requires glamor and, if PageFlip is enabled, universal planes */
++    if (xf86ReturnOptValBool(ms->drmmode.Options, OPTION_TEARFREE, TRUE)) {
++        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 +1751,13 @@
+ 
+     err = drmModeDirtyFB(ms->fd, ms->drmmode.fb_id, NULL, 0);
+ 
+-    if (err != -EINVAL && err != -ENOSYS) {
++    if ((err != -EINVAL && err != -ENOSYS) || ms->drmmode.tearfree_enable) {
+         ms->damage = DamageCreate(NULL, NULL, DamageReportNone, TRUE,
+                                   pScreen, rootPixmap);
+ 
+         if (ms->damage) {
+             DamageRegister(&rootPixmap->drawable, ms->damage);
+-            ms->dirty_enabled = TRUE;
++            ms->dirty_enabled = err != -EINVAL && err != -ENOSYS;
+             xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Damage tracking initialized\n");
+         }
+         else {
+--- hw/xfree86/drivers/modesetting/driver.h.orig	2025-02-25 19:56:05.000000000 +0100
++++ hw/xfree86/drivers/modesetting/driver.h	2025-03-12 15:54:19.785844000 +0100
+@@ -61,6 +61,7 @@
+     OPTION_VARIABLE_REFRESH,
+     OPTION_USE_GAMMA_LUT,
+     OPTION_ASYNC_FLIP_SECONDARIES,
++    OPTION_TEARFREE,
+ } modesettingOpts;
+ 
+ typedef struct
+@@ -86,10 +87,13 @@
+     struct xorg_list list;
+     xf86CrtcPtr crtc;
+     uint32_t seq;
++    uint64_t msc;
+     void *data;
+     ScrnInfoPtr scrn;
+     ms_drm_handler_proc handler;
+     ms_drm_abort_proc abort;
++    Bool kernel_queued;
++    Bool aborted;
+ };
+ 
+ typedef struct _modesettingRec {
+@@ -202,6 +206,8 @@
+                   void *match_data);
+ void ms_drm_abort_seq(ScrnInfoPtr scrn, uint32_t seq);
+ 
++Bool ms_drm_queue_is_empty(void);
++
+ Bool xf86_crtc_on(xf86CrtcPtr crtc);
+ 
+ xf86CrtcPtr ms_dri2_crtc_covering_drawable(DrawablePtr pDraw);
+@@ -232,14 +238,26 @@
+ Bool ms_do_pageflip(ScreenPtr screen,
+                     PixmapPtr new_front,
+                     void *event,
+-                    int ref_crtc_vblank_pipe,
++                    xf86CrtcPtr ref_crtc,
+                     Bool async,
+                     ms_pageflip_handler_proc pageflip_handler,
+                     ms_pageflip_abort_proc pageflip_abort,
+                     const char *log_prefix);
+ 
++Bool
++ms_tearfree_dri_abort(xf86CrtcPtr crtc,
++                      Bool (*match)(void *data, void *match_data),
++                      void *match_data);
++
++void
++ms_tearfree_dri_abort_all(xf86CrtcPtr crtc);
++
++Bool ms_do_tearfree_flip(ScreenPtr screen, xf86CrtcPtr crtc);
++
+ #endif
+ 
+ int ms_flush_drm_events(ScreenPtr screen);
++void ms_drain_drm_events(ScreenPtr screen);
+ Bool ms_window_has_variable_refresh(modesettingPtr ms, WindowPtr win);
+ void ms_present_set_screen_vrr(ScrnInfoPtr scrn, Bool vrr_enabled);
++Bool ms_tearfree_is_active_on_crtc(xf86CrtcPtr crtc);
+--- hw/xfree86/drivers/modesetting/drmmode_display.c.orig	2025-02-25 19:56:05.000000000 +0100
++++ hw/xfree86/drivers/modesetting/drmmode_display.c	2025-03-12 15:54:19.791236000 +0100
+@@ -632,6 +632,7 @@
+ {
+     drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
+     drmmode_ptr drmmode = drmmode_crtc->drmmode;
++    drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree;
+     int ret;
+ 
+     *fb_id = 0;
+@@ -646,6 +647,10 @@
+             *x = drmmode_crtc->prime_pixmap_x;
+         *y = 0;
+     }
++    else if (trf->buf[trf->back_idx ^ 1].px) {
++        *fb_id = trf->buf[trf->back_idx ^ 1].fb_id;
++        *x = *y = 0;
++    }
+     else if (drmmode_crtc->rotate_fb_id) {
+         *fb_id = drmmode_crtc->rotate_fb_id;
+         *x = *y = 0;
+@@ -922,6 +927,10 @@
+     drmmode_ConvertToKMode(crtc->scrn, &kmode, &crtc->mode);
+     ret = drmModeSetCrtc(drmmode->fd, drmmode_crtc->mode_crtc->crtc_id,
+                          fb_id, x, y, output_ids, output_count, &kmode);
++    if (!ret && !ms->atomic_modeset) {
++        drmmode_crtc->src_x = x;
++        drmmode_crtc->src_y = y;
++    }
+ 
+     drmmode_set_ctm(crtc, ctm);
+ 
+@@ -930,7 +939,8 @@
+ }
+ 
+ int
+-drmmode_crtc_flip(xf86CrtcPtr crtc, uint32_t fb_id, uint32_t flags, void *data)
++drmmode_crtc_flip(xf86CrtcPtr crtc, uint32_t fb_id, int x, int y,
++                  uint32_t flags, void *data)
+ {
+     modesettingPtr ms = modesettingPTR(crtc->scrn);
+     drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
+@@ -942,7 +952,7 @@
+         if (!req)
+             return 1;
+ 
+-        ret = plane_add_props(req, crtc, fb_id, crtc->x, crtc->y);
++        ret = plane_add_props(req, crtc, fb_id, x, y);
+         flags |= DRM_MODE_ATOMIC_NONBLOCK;
+         if (ret == 0)
+             ret = drmModeAtomicCommit(ms->fd, req, flags, data);
+@@ -950,6 +960,26 @@
+         return ret;
+     }
+ 
++    /* The frame buffer source coordinates may change when switching between the
++     * primary frame buffer and a per-CRTC frame buffer. Set the correct source
++     * coordinates if they differ for this flip.
++     */
++    if (drmmode_crtc->src_x != x || drmmode_crtc->src_y != y) {
++        ret = drmModeSetPlane(ms->fd, drmmode_crtc->plane_id,
++                              drmmode_crtc->mode_crtc->crtc_id, fb_id, 0,
++                              0, 0, crtc->mode.HDisplay, crtc->mode.VDisplay,
++                              x << 16, y << 16, crtc->mode.HDisplay << 16,
++                              crtc->mode.VDisplay << 16);
++        if (ret) {
++            xf86DrvMsg(crtc->scrn->scrnIndex, X_WARNING,
++                       "error changing fb src coordinates for flip: %d\n", ret);
++            return ret;
++        }
++
++        drmmode_crtc->src_x = x;
++        drmmode_crtc->src_y = y;
++    }
++
+     return drmModePageFlip(ms->fd, drmmode_crtc->mode_crtc->crtc_id,
+                            fb_id, flags, data);
+ }
+@@ -1548,7 +1578,111 @@
+ #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 void drmmmode_prepare_modeset(ScrnInfoPtr scrn)
++{
++    ScreenPtr pScreen = scrn->pScreen;
++    modesettingPtr ms = modesettingPTR(scrn);
++
++    if (ms->drmmode.pending_modeset)
++        return;
++
++    /*
++     * Force present to unflip everything before we might
++     * try lighting up new displays. This makes sure fancy
++     * modifiers can't cause the modeset to fail.
++     */
++    ms->drmmode.pending_modeset = TRUE;
++    present_check_flips(pScreen->root);
++    ms->drmmode.pending_modeset = FALSE;
++
++    ms_drain_drm_events(pScreen);
++}
++
++static Bool
+ drmmode_set_mode_major(xf86CrtcPtr crtc, DisplayModePtr mode,
+                        Rotation rotation, int x, int y)
+ {
+@@ -1563,6 +1697,9 @@
+     Bool can_test;
+     int i;
+ 
++    if (mode)
++        drmmmode_prepare_modeset(crtc->scrn);
++
+     saved_mode = crtc->mode;
+     saved_x = crtc->x;
+     saved_y = crtc->y;
+@@ -1581,6 +1718,10 @@
+         crtc->funcs->gamma_set(crtc, crtc->gamma_red, crtc->gamma_green,
+                                crtc->gamma_blue, crtc->gamma_size);
+ 
++        ret = drmmode_create_tearfree_shadow(crtc);
++        if (!ret)
++            goto done;
++
+         can_test = drmmode_crtc_can_test_mode(crtc);
+         if (drmmode_crtc_set_mode(crtc, can_test)) {
+             xf86DrvMsg(crtc->scrn->scrnIndex, X_ERROR,
+@@ -1626,6 +1767,7 @@
+         crtc->y = saved_y;
+         crtc->rotation = saved_rotation;
+         crtc->mode = saved_mode;
++        drmmode_create_tearfree_shadow(crtc);
+     } else
+         crtc->active = TRUE;
+ 
+@@ -1931,35 +2073,44 @@
+ }
+ 
+ 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
+ drmmode_create_pixmap_header(ScreenPtr pScreen, int width, int height,
+                              int depth, int bitsPerPixel, int devKind,
+@@ -1983,71 +2134,91 @@
+ 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);
++    pixmap = drmmode_create_pixmap_header(scrn->pScreen,
++                                          width, height,
++                                          scrn->depth,
++                                          drmmode->kbpp,
++                                          pitch,
++                                          pPixData);
+ 
+-    rotate_pixmap = drmmode_create_pixmap_header(scrn->pScreen,
+-                                                 width, height,
+-                                                 scrn->depth,
+-                                                 drmmode->kbpp,
+-                                                 rotate_pitch,
+-                                                 pPixData);
+-
+-    if (rotate_pixmap == NULL) {
++    if (pixmap == NULL) {
+         xf86DrvMsg(scrn->scrnIndex, X_ERROR,
+-                   "Couldn't allocate shadow pixmap for rotated CRTC\n");
++                   "Couldn't allocate shadow pixmap for CRTC\n");
+         return NULL;
+     }
+ 
+-    drmmode_set_pixmap_bo(drmmode, rotate_pixmap, &drmmode_crtc->rotate_bo);
++    drmmode_set_pixmap_bo(drmmode, pixmap, bo);
+ 
+-    return rotate_pixmap;
++    return pixmap;
+ }
+ 
++static PixmapPtr
++drmmode_shadow_create(xf86CrtcPtr crtc, void *data, int width, int height)
++{
++    drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
++
++    return drmmode_shadow_fb_create(crtc, data, width, height,
++                                    &drmmode_crtc->rotate_bo,
++                                    &drmmode_crtc->rotate_fb_id);
++}
++
+ static void
+-drmmode_shadow_destroy(xf86CrtcPtr crtc, PixmapPtr rotate_pixmap, void *data)
++drmmode_shadow_fb_destroy(xf86CrtcPtr crtc, PixmapPtr pixmap,
++                          void *data, drmmode_bo *bo, uint32_t *fb_id)
+ {
+     drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
+     drmmode_ptr drmmode = drmmode_crtc->drmmode;
+ 
+-    if (rotate_pixmap) {
+-        rotate_pixmap->drawable.pScreen->DestroyPixmap(rotate_pixmap);
++    if (pixmap) {
++        pixmap->drawable.pScreen->DestroyPixmap(pixmap);
+     }
+ 
+     if (data) {
+-        drmModeRmFB(drmmode->fd, drmmode_crtc->rotate_fb_id);
+-        drmmode_crtc->rotate_fb_id = 0;
++        drmModeRmFB(drmmode->fd, *fb_id);
++        *fb_id = 0;
+ 
+-        drmmode_bo_destroy(drmmode, &drmmode_crtc->rotate_bo);
+-        memset(&drmmode_crtc->rotate_bo, 0, sizeof drmmode_crtc->rotate_bo);
++        drmmode_bo_destroy(drmmode, bo);
++        memset(bo, 0, sizeof(*bo));
+     }
+ }
+ 
+ static void
++drmmode_shadow_destroy(xf86CrtcPtr crtc, PixmapPtr pixmap, void *data)
++{
++    drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
++
++    drmmode_shadow_fb_destroy(crtc, pixmap, data, &drmmode_crtc->rotate_bo,
++                              &drmmode_crtc->rotate_fb_id);
++}
++
++static void
+ drmmode_crtc_destroy(xf86CrtcPtr crtc)
+ {
+     drmmode_mode_ptr iterator, next;
+@@ -2380,6 +2551,8 @@
+     drmmode_crtc->drmmode = drmmode;
+     drmmode_crtc->vblank_pipe = drmmode_crtc_vblank_pipe(num);
+     xorg_list_init(&drmmode_crtc->mode_list);
++    xorg_list_init(&drmmode_crtc->tearfree.dri_flip_list);
++    drmmode_crtc->next_msc = UINT64_MAX;
+ 
+     props = drmModeObjectGetProperties(drmmode->fd, mode_res->crtcs[num],
+                                        DRM_MODE_OBJECT_CRTC);
+@@ -3755,6 +3928,8 @@
+     Bool success = TRUE;
+     int c;
+ 
++    drmmmode_prepare_modeset(pScrn);
++
+     for (c = 0; c < config->num_crtc; c++) {
+         xf86CrtcPtr crtc = config->crtc[c];
+         drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
+@@ -4242,6 +4417,7 @@
+         drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
+ 
+         dumb_bo_destroy(drmmode->fd, drmmode_crtc->cursor_bo);
++	drmmode_destroy_tearfree_shadow(crtc);
+     }
+ }
+ 
+--- hw/xfree86/drivers/modesetting/drmmode_display.h.orig	2025-02-25 19:56:05.000000000 +0100
++++ hw/xfree86/drivers/modesetting/drmmode_display.h	2025-03-12 15:54:19.792774000 +0100
+@@ -135,9 +135,12 @@
+     Bool async_flip_secondaries;
+     Bool dri2_enable;
+     Bool present_enable;
++    Bool tearfree_enable;
+ 
+     uint32_t vrr_prop_id;
+     Bool use_ctm;
++
++    Bool pending_modeset;
+ } drmmode_rec, *drmmode_ptr;
+ 
+ typedef struct {
+@@ -167,6 +170,20 @@
+ } drmmode_format_rec, *drmmode_format_ptr;
+ 
+ typedef struct {
++    drmmode_bo bo;
++    uint32_t fb_id;
++    PixmapPtr px;
++    RegionRec dmg;
++} drmmode_shadow_fb_rec, *drmmode_shadow_fb_ptr;
++
++typedef struct {
++    drmmode_shadow_fb_rec buf[2];
++    struct xorg_list dri_flip_list;
++    uint32_t back_idx;
++    uint32_t flip_seq;
++} drmmode_tearfree_rec, *drmmode_tearfree_ptr;
++
++typedef struct {
+     drmmode_ptr drmmode;
+     drmModeCrtcPtr mode_crtc;
+     uint32_t vblank_pipe;
+@@ -184,11 +201,14 @@
+ 
+     drmmode_bo rotate_bo;
+     unsigned rotate_fb_id;
++    drmmode_tearfree_rec tearfree;
+ 
+     PixmapPtr prime_pixmap;
+     PixmapPtr prime_pixmap_back;
+     unsigned prime_pixmap_x;
+ 
++    int src_x, src_y;
++
+     /**
+      * @{ MSC (vblank count) handling for the PRESENT extension.
+      *
+@@ -200,6 +220,8 @@
+     uint64_t msc_high;
+     /** @} */
+ 
++    uint64_t next_msc;
++
+     Bool need_modeset;
+     struct xorg_list mode_list;
+ 
+@@ -308,8 +330,11 @@
+                              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);
+ 
+--- hw/xfree86/drivers/modesetting/modesetting.man.orig	2025-02-25 19:56:05.000000000 +0100
++++ hw/xfree86/drivers/modesetting/modesetting.man	2025-03-12 15:54:19.793860000 +0100
+@@ -109,6 +109,17 @@
+ 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 on.
++.TP
+ .SH "SEE ALSO"
+ @xservername@(@appmansuffix@), @xconfigfile@(@filemansuffix@), Xserver(@appmansuffix@),
+ X(@miscmansuffix@)
+--- hw/xfree86/drivers/modesetting/pageflip.c.orig	2025-02-25 19:56:05.000000000 +0100
++++ hw/xfree86/drivers/modesetting/pageflip.c	2025-03-12 15:54:19.796288000 +0100
+@@ -35,8 +35,8 @@
+  * Returns a negative value on error, 0 if there was nothing to process,
+  * or 1 if we handled any events.
+  */
+-int
+-ms_flush_drm_events(ScreenPtr screen)
++static int
++ms_flush_drm_events_timeout(ScreenPtr screen, int timeout)
+ {
+     ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
+     modesettingPtr ms = modesettingPTR(scrn);
+@@ -45,7 +45,7 @@
+     int r;
+ 
+     do {
+-            r = xserver_poll(&p, 1, 0);
++            r = xserver_poll(&p, 1, timeout);
+     } while (r == -1 && (errno == EINTR || errno == EAGAIN));
+ 
+     /* If there was an error, r will be < 0.  Return that.  If there was
+@@ -63,6 +63,19 @@
+     return 1;
+ }
+ 
++int
++ms_flush_drm_events(ScreenPtr screen)
++{
++    return ms_flush_drm_events_timeout(screen, 0);
++}
++
++void
++ms_drain_drm_events(ScreenPtr screen)
++{
++    while (!ms_drm_queue_is_empty())
++        ms_flush_drm_events_timeout(screen, -1);
++}
++
+ #ifdef GLAMOR_HAS_GBM
+ 
+ /*
+@@ -93,6 +106,8 @@
+     Bool on_reference_crtc;
+     /* reference to the ms_flipdata */
+     struct ms_flipdata *flipdata;
++    struct xorg_list node;
++    uint32_t tearfree_seq;
+ };
+ 
+ /**
+@@ -136,7 +151,8 @@
+                                 flipdata->fe_usec,
+                                 flipdata->event);
+ 
+-        drmModeRmFB(ms->fd, flipdata->old_fb_id);
++        if (flipdata->old_fb_id)
++            drmModeRmFB(ms->fd, flipdata->old_fb_id);
+     }
+     ms_pageflip_free(flip);
+ }
+@@ -160,11 +176,32 @@
+ }
+ 
+ static Bool
+-do_queue_flip_on_crtc(modesettingPtr ms, xf86CrtcPtr crtc,
+-                      uint32_t flags, uint32_t seq)
++do_queue_flip_on_crtc(ScreenPtr screen, xf86CrtcPtr crtc, uint32_t flags,
++                      uint32_t seq, uint32_t fb_id, int x, int y)
+ {
+-    return drmmode_crtc_flip(crtc, ms->drmmode.fb_id, flags,
+-                             (void *) (uintptr_t) seq);
++    drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
++    drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree;
++
++    while (drmmode_crtc_flip(crtc, fb_id, x, y, flags, (void *)(long)seq)) {
++        /* We may have failed because the event queue was full.  Flush it
++         * and retry.  If there was nothing to flush, then we failed for
++         * some other reason and should just return an error.
++         */
++        if (ms_flush_drm_events(screen) <= 0) {
++            /* The failure could be caused by a pending TearFree flip, in which
++             * case we should wait until there's a new event and try again.
++             */
++            if (!trf->flip_seq || ms_flush_drm_events_timeout(screen, -1) < 0) {
++                ms_drm_abort_seq(crtc->scrn, seq);
++                return TRUE;
++            }
++        }
++
++        /* We flushed some events, so try again. */
++        xf86DrvMsg(crtc->scrn->scrnIndex, X_WARNING, "flip queue retry\n");
++    }
++
++    return FALSE;
+ }
+ 
+ enum queue_flip_status {
+@@ -177,11 +214,10 @@
+ static int
+ queue_flip_on_crtc(ScreenPtr screen, xf86CrtcPtr crtc,
+                    struct ms_flipdata *flipdata,
+-                   int ref_crtc_vblank_pipe, uint32_t flags)
++                   xf86CrtcPtr ref_crtc, uint32_t flags)
+ {
+     ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
+     modesettingPtr ms = modesettingPTR(scrn);
+-    drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
+     struct ms_crtc_pageflip *flip;
+     uint32_t seq;
+ 
+@@ -193,7 +229,7 @@
+     /* Only the reference crtc will finally deliver its page flip
+      * completion event. All other crtc's events will be discarded.
+      */
+-    flip->on_reference_crtc = (drmmode_crtc->vblank_pipe == ref_crtc_vblank_pipe);
++    flip->on_reference_crtc = crtc == ref_crtc;
+     flip->flipdata = flipdata;
+ 
+     seq = ms_drm_queue_alloc(crtc, flip, ms_pageflip_handler, ms_pageflip_abort);
+@@ -205,21 +241,10 @@
+     /* 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;
+-        }
++    if (do_queue_flip_on_crtc(screen, crtc, flags, seq, ms->drmmode.fb_id,
++                              crtc->x, crtc->y))
++        return QUEUE_FLIP_DRM_FLUSH_FAILED;
+ 
+-        /* We flushed some events, so try again. */
+-        xf86DrvMsg(scrn->scrnIndex, X_WARNING, "flip queue retry\n");
+-    }
+-
+     /* The page flip succeeded. */
+     return QUEUE_FLIP_SUCCESS;
+ }
+@@ -294,20 +319,75 @@
+     }
+ }
+ 
++static Bool
++ms_tearfree_dri_flip(modesettingPtr ms, xf86CrtcPtr crtc, void *event,
++                     ms_pageflip_handler_proc pageflip_handler,
++                     ms_pageflip_abort_proc pageflip_abort)
++{
++    drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
++    drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree;
++    struct ms_crtc_pageflip *flip;
++    struct ms_flipdata *flipdata;
++    RegionRec region;
++    RegionPtr dirty;
+ 
++    if (!ms_tearfree_is_active_on_crtc(crtc))
++        return FALSE;
++
++    /* Check for damage on the primary scanout to know if TearFree will flip */
++    dirty = DamageRegion(ms->damage);
++    if (RegionNil(dirty))
++        return FALSE;
++
++    /* Compute how much of the current damage intersects with this CRTC */
++    RegionInit(&region, &crtc->bounds, 0);
++    RegionIntersect(&region, &region, dirty);
++
++    /* No damage on this CRTC means no TearFree flip. This means the DRI client
++     * didn't change this CRTC's contents at all with its presentation, possibly
++     * because its window is fully occluded by another window on this CRTC.
++     */
++    if (RegionNil(&region))
++        return FALSE;
++
++    flip = calloc(1, sizeof(*flip));
++    if (!flip)
++        return FALSE;
++
++    flipdata = calloc(1, sizeof(*flipdata));
++    if (!flipdata) {
++        free(flip);
++        return FALSE;
++    }
++
++    /* Only track the DRI client's fake flip on the reference CRTC, which aligns
++     * with the behavior of Present when a client copies its pixmap rather than
++     * directly flipping it onto the display.
++     */
++    flip->on_reference_crtc = TRUE;
++    flip->flipdata = flipdata;
++    flip->tearfree_seq = trf->flip_seq;
++    flipdata->screen = xf86ScrnToScreen(crtc->scrn);
++    flipdata->event = event;
++    flipdata->flip_count = 1;
++    flipdata->event_handler = pageflip_handler;
++    flipdata->abort_handler = pageflip_abort;
++
++    /* Keep the list in FIFO order so that clients are notified in order */
++    xorg_list_append(&flip->node, &trf->dri_flip_list);
++    return TRUE;
++}
++
+ Bool
+ ms_do_pageflip(ScreenPtr screen,
+                PixmapPtr new_front,
+                void *event,
+-               int ref_crtc_vblank_pipe,
++               xf86CrtcPtr ref_crtc,
+                Bool async,
+                ms_pageflip_handler_proc pageflip_handler,
+                ms_pageflip_abort_proc pageflip_abort,
+                const char *log_prefix)
+ {
+-#ifndef GLAMOR_HAS_GBM
+-    return FALSE;
+-#else
+     ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
+     modesettingPtr ms = modesettingPTR(scrn);
+     xf86CrtcConfigPtr config = XF86_CRTC_CONFIG_PTR(scrn);
+@@ -315,6 +395,22 @@
+     uint32_t flags;
+     int i;
+     struct ms_flipdata *flipdata;
++
++    /* A NULL pixmap indicates this DRI client's pixmap is to be flipped through
++     * TearFree instead. The pixmap is already copied to the primary scanout at
++     * this point, so all that's left is to wire up this fake flip to TearFree
++     * so that TearFree can send a notification to the DRI client when the
++     * pixmap actually appears on the display. This is the only way to let DRI
++     * clients accurately know when their pixmaps appear on the display when
++     * TearFree is enabled.
++     */
++    if (!new_front) {
++        if (!ms_tearfree_dri_flip(ms, ref_crtc, event, pageflip_handler,
++                                  pageflip_abort))
++            goto error_free_event;
++        return TRUE;
++    }
++
+     ms->glamor.block_handler(screen);
+ 
+     new_front_bo.gbm = ms->glamor.gbm_bo_from_pixmap(screen, new_front);
+@@ -324,7 +420,7 @@
+         xf86DrvMsg(scrn->scrnIndex, X_ERROR,
+                    "%s: Failed to get GBM BO for flip to new front.\n",
+                    log_prefix);
+-        return FALSE;
++        goto error_free_event;
+     }
+ 
+     flipdata = calloc(1, sizeof(struct ms_flipdata));
+@@ -332,7 +428,7 @@
+         drmmode_bo_destroy(&ms->drmmode, &new_front_bo);
+         xf86DrvMsg(scrn->scrnIndex, X_ERROR,
+                    "%s: Failed to allocate flipdata.\n", log_prefix);
+-        return FALSE;
++        goto error_free_event;
+     }
+ 
+     flipdata->event = event;
+@@ -380,7 +476,6 @@
+     for (i = 0; i < config->num_crtc; i++) {
+         enum queue_flip_status flip_status;
+         xf86CrtcPtr crtc = config->crtc[i];
+-        drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
+ 
+         if (!xf86_crtc_on(crtc))
+             continue;
+@@ -401,13 +496,11 @@
+          * outputs in a "clone-mode" or "mirror-mode" configuration.
+          */
+         if (ms->drmmode.can_async_flip && ms->drmmode.async_flip_secondaries &&
+-            (drmmode_crtc->vblank_pipe != ref_crtc_vblank_pipe) &&
+-            (ref_crtc_vblank_pipe >= 0))
++            ref_crtc && crtc != ref_crtc)
+             flags |= DRM_MODE_PAGE_FLIP_ASYNC;
+ 
+         flip_status = queue_flip_on_crtc(screen, crtc, flipdata,
+-                                         ref_crtc_vblank_pipe,
+-                                         flags);
++                                         ref_crtc, flags);
+ 
+         switch (flip_status) {
+             case QUEUE_FLIP_ALLOC_FAILED:
+@@ -456,13 +549,150 @@
+     drmmode_bo_destroy(&ms->drmmode, &new_front_bo);
+     /* if only the local reference - free the structure,
+      * else drop the local reference and return */
+-    if (flipdata->flip_count == 1)
++    if (flipdata->flip_count == 1) {
+         free(flipdata);
+-    else
++    } else {
+         flipdata->flip_count--;
++        return FALSE;
++    }
+ 
++error_free_event:
++    /* Free the event since the caller has no way to know it's safe to free */
++    free(event);
+     return FALSE;
+-#endif /* GLAMOR_HAS_GBM */
+ }
+ 
++Bool
++ms_tearfree_dri_abort(xf86CrtcPtr crtc,
++                      Bool (*match)(void *data, void *match_data),
++                      void *match_data)
++{
++    drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
++    drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree;
++    struct ms_crtc_pageflip *flip;
++
++    /* The window is getting destroyed; abort without notifying the client */
++    xorg_list_for_each_entry(flip, &trf->dri_flip_list, node) {
++        if (match(flip->flipdata->event, match_data)) {
++            xorg_list_del(&flip->node);
++            ms_pageflip_abort(flip);
++            return TRUE;
++        }
++    }
++
++    return FALSE;
++}
++
++void
++ms_tearfree_dri_abort_all(xf86CrtcPtr crtc)
++{
++    drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
++    drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree;
++    struct ms_crtc_pageflip *flip, *tmp;
++    uint64_t usec = 0, msc = 0;
++
++    /* Nothing to abort if there aren't any DRI clients waiting for a flip */
++    if (xorg_list_is_empty(&trf->dri_flip_list))
++        return;
++
++    /* Even though we're aborting, these clients' pixmaps were actually blitted,
++     * so technically the presentation isn't aborted. That's why the normal
++     * handler is called instead of the abort handler, along with the current
++     * time and MSC for this CRTC.
++     */
++    ms_get_crtc_ust_msc(crtc, &usec, &msc);
++    xorg_list_for_each_entry_safe(flip, tmp, &trf->dri_flip_list, node)
++        ms_pageflip_handler(msc, usec, flip);
++    xorg_list_init(&trf->dri_flip_list);
++}
++
++static void
++ms_tearfree_dri_notify(drmmode_tearfree_ptr trf, uint64_t msc, uint64_t usec)
++{
++    struct ms_crtc_pageflip *flip, *tmp;
++
++    xorg_list_for_each_entry_safe(flip, tmp, &trf->dri_flip_list, node) {
++        /* If a TearFree flip was already pending at the time this DRI client's
++         * pixmap was copied, then the pixmap isn't contained in this TearFree
++         * flip, but will be part of the next TearFree flip instead.
++         */
++        if (flip->tearfree_seq) {
++            flip->tearfree_seq = 0;
++        } else {
++            xorg_list_del(&flip->node);
++            ms_pageflip_handler(msc, usec, flip);
++        }
++    }
++}
++
++static void
++ms_tearfree_flip_abort(void *data)
++{
++    xf86CrtcPtr crtc = data;
++    drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
++    drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree;
++
++    trf->flip_seq = 0;
++    ms_tearfree_dri_abort_all(crtc);
++}
++
++static void
++ms_tearfree_flip_handler(uint64_t msc, uint64_t usec, void *data)
++{
++    xf86CrtcPtr crtc = data;
++    drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
++    drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree;
++
++    /* Swap the buffers and complete the flip */
++    trf->back_idx ^= 1;
++    trf->flip_seq = 0;
++
++    /* Notify DRI clients that their pixmaps are now visible on the display */
++    ms_tearfree_dri_notify(trf, msc, usec);
++}
++
++Bool
++ms_do_tearfree_flip(ScreenPtr screen, xf86CrtcPtr crtc)
++{
++    drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
++    drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree;
++    uint32_t idx = trf->back_idx, seq;
++
++    seq = ms_drm_queue_alloc(crtc, crtc, ms_tearfree_flip_handler,
++                             ms_tearfree_flip_abort);
++    if (!seq) {
++        /* Need to notify the DRI clients if a sequence wasn't allocated. Once a
++         * sequence is allocated, explicitly performing this cleanup isn't
++         * necessary since it's already done as part of aborting the sequence.
++         */
++        ms_tearfree_dri_abort_all(crtc);
++        goto no_flip;
++    }
++
++    /* Copy the damage to the back buffer and then flip it at the vblank */
++    drmmode_copy_damage(crtc, trf->buf[idx].px, &trf->buf[idx].dmg, TRUE);
++    if (do_queue_flip_on_crtc(screen, crtc, DRM_MODE_PAGE_FLIP_EVENT,
++                              seq, trf->buf[idx].fb_id, 0, 0))
++        goto no_flip;
++
++    trf->flip_seq = seq;
++    return FALSE;
++
++no_flip:
++    xf86DrvMsg(crtc->scrn->scrnIndex, X_WARNING,
++               "TearFree flip failed, rendering frame without TearFree\n");
++    drmmode_copy_damage(crtc, trf->buf[idx ^ 1].px,
++                        &trf->buf[idx ^ 1].dmg, FALSE);
++    return TRUE;
++}
+ #endif
++
++Bool
++ms_tearfree_is_active_on_crtc(xf86CrtcPtr crtc)
++{
++    drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
++    drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree;
++
++    /* If TearFree is enabled, XServer owns the VT, and the CRTC is active */
++    return trf->buf[0].px && crtc->scrn->vtSema && xf86_crtc_on(crtc);
++}
+--- hw/xfree86/drivers/modesetting/present.c.orig	2025-02-25 19:56:05.000000000 +0100
++++ hw/xfree86/drivers/modesetting/present.c	2025-03-12 15:54:19.797836000 +0100
+@@ -165,7 +165,14 @@
+ {
+     ScreenPtr screen = crtc->pScreen;
+     ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
++#ifdef GLAMOR_HAS_GBM
++    xf86CrtcPtr xf86_crtc = crtc->devPrivate;
+ 
++    /* Check if this is a fake flip routed through TearFree and abort it */
++    if (ms_tearfree_dri_abort(xf86_crtc, ms_present_event_match, &event_id))
++        return;
++#endif
++
+     ms_drm_abort(scrn, ms_present_event_match, &event_id);
+ }
+ 
+@@ -318,14 +325,34 @@
+     modesettingPtr ms = modesettingPTR(scrn);
+ 
+     if (ms->drmmode.sprites_visible > 0)
+-        return FALSE;
++        goto no_flip;
+ 
++    if (ms->drmmode.pending_modeset)
++        goto no_flip;
++
+     if(!ms_present_check_unflip(crtc, window, pixmap, sync_flip, reason))
+-        return FALSE;
++        goto no_flip;
+ 
+     ms->flip_window = window;
+ 
+     return TRUE;
++no_flip:
++    /* Export some info about TearFree if Present can't flip anyway */
++    if (reason) {
++        xf86CrtcPtr xf86_crtc = crtc->devPrivate;
++        drmmode_crtc_private_ptr drmmode_crtc = xf86_crtc->driver_private;
++        drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree;
++
++        if (ms_tearfree_is_active_on_crtc(xf86_crtc)) {
++            if (trf->flip_seq)
++                /* The driver has a TearFree flip pending */
++                *reason = PRESENT_FLIP_REASON_DRIVER_TEARFREE_FLIPPING;
++            else
++                /* The driver uses TearFree flips and there's no flip pending */
++                *reason = PRESENT_FLIP_REASON_DRIVER_TEARFREE;
++        }
++    }
++    return FALSE;
+ }
+ 
+ /*
+@@ -343,11 +370,12 @@
+     ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
+     modesettingPtr ms = modesettingPTR(scrn);
+     xf86CrtcPtr xf86_crtc = crtc->devPrivate;
+-    drmmode_crtc_private_ptr drmmode_crtc = xf86_crtc->driver_private;
+     Bool ret;
+     struct ms_present_vblank_event *event;
+ 
+-    if (!ms_present_check_flip(crtc, ms->flip_window, pixmap, sync_flip, NULL))
++    /* A NULL pixmap means this is a fake flip to be routed through TearFree */
++    if (pixmap &&
++        !ms_present_check_flip(crtc, ms->flip_window, pixmap, sync_flip, NULL))
+         return FALSE;
+ 
+     event = calloc(1, sizeof(struct ms_present_vblank_event));
+@@ -360,6 +388,12 @@
+     event->event_id = event_id;
+     event->unflip = FALSE;
+ 
++    /* Register the fake flip (indicated by a NULL pixmap) with TearFree */
++    if (!pixmap)
++        return ms_do_pageflip(screen, NULL, event, xf86_crtc, FALSE,
++                              ms_present_flip_handler, ms_present_flip_abort,
++                              "Present-TearFree-flip");
++
+     /* A window can only flip if it covers the entire X screen.
+      * Only one window can flip at a time.
+      *
+@@ -371,7 +405,7 @@
+         ms_present_set_screen_vrr(scrn, TRUE);
+     }
+ 
+-    ret = ms_do_pageflip(screen, pixmap, event, drmmode_crtc->vblank_pipe, !sync_flip,
++    ret = ms_do_pageflip(screen, pixmap, event, xf86_crtc, !sync_flip,
+                          ms_present_flip_handler, ms_present_flip_abort,
+                          "Present-flip");
+     if (ret)
+@@ -404,7 +438,7 @@
+         event->event_id = event_id;
+         event->unflip = TRUE;
+ 
+-        if (ms_do_pageflip(screen, pixmap, event, -1, FALSE,
++        if (ms_do_pageflip(screen, pixmap, event, NULL, FALSE,
+                            ms_present_flip_handler, ms_present_flip_abort,
+                            "Present-unflip")) {
+             return;
+--- hw/xfree86/drivers/modesetting/vblank.c.orig	2025-02-25 19:56:05.000000000 +0100
++++ hw/xfree86/drivers/modesetting/vblank.c	2025-03-12 15:54:19.799609000 +0100
+@@ -260,6 +260,51 @@
+     }
+ }
+ 
++static void
++ms_drm_set_seq_msc(uint32_t seq, uint64_t msc)
++{
++    struct ms_drm_queue *q;
++
++    xorg_list_for_each_entry(q, &ms_drm_queue, list) {
++        if (q->seq == seq) {
++            q->msc = msc;
++            break;
++        }
++    }
++}
++
++static void
++ms_drm_set_seq_queued(uint32_t seq, uint64_t msc)
++{
++    drmmode_crtc_private_ptr drmmode_crtc;
++    struct ms_drm_queue *q;
++
++    xorg_list_for_each_entry(q, &ms_drm_queue, list) {
++        if (q->seq == seq) {
++            drmmode_crtc = q->crtc->driver_private;
++            if (msc < drmmode_crtc->next_msc)
++                drmmode_crtc->next_msc = msc;
++            q->msc = msc;
++            q->kernel_queued = TRUE;
++            break;
++        }
++    }
++}
++
++static Bool
++ms_queue_coalesce(xf86CrtcPtr crtc, uint32_t seq, uint64_t msc)
++{
++    drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
++
++    /* If the next MSC is too late, then this event can't be coalesced */
++    if (msc < drmmode_crtc->next_msc)
++        return FALSE;
++
++    /* Set the target MSC on this sequence number */
++    ms_drm_set_seq_msc(seq, msc);
++    return TRUE;
++}
++
+ Bool
+ ms_queue_vblank(xf86CrtcPtr crtc, ms_queue_flag flags,
+                 uint64_t msc, uint64_t *msc_queued, uint32_t seq)
+@@ -271,6 +316,10 @@
+     drmVBlank vbl;
+     int ret;
+ 
++    /* Try coalescing this event into another to avoid event queue exhaustion */
++    if (flags == MS_QUEUE_ABSOLUTE && ms_queue_coalesce(crtc, seq, msc))
++        return TRUE;
++
+     for (;;) {
+         /* Queue an event at the specified sequence */
+         if (ms->has_queue_sequence || !ms->tried_queue_sequence) {
+@@ -287,8 +336,10 @@
+             ret = drmCrtcQueueSequence(ms->fd, drmmode_crtc->mode_crtc->crtc_id,
+                                        drm_flags, msc, &kernel_queued, seq);
+             if (ret == 0) {
++                msc = ms_kernel_msc_to_crtc_msc(crtc, kernel_queued, TRUE);
++                ms_drm_set_seq_queued(seq, msc);
+                 if (msc_queued)
+-                    *msc_queued = ms_kernel_msc_to_crtc_msc(crtc, kernel_queued, TRUE);
++                    *msc_queued = msc;
+                 ms->has_queue_sequence = TRUE;
+                 return TRUE;
+             }
+@@ -310,8 +361,10 @@
+         vbl.request.signal = seq;
+         ret = drmWaitVBlank(ms->fd, &vbl);
+         if (ret == 0) {
++            msc = ms_kernel_msc_to_crtc_msc(crtc, vbl.reply.sequence, FALSE);
++            ms_drm_set_seq_queued(seq, msc);
+             if (msc_queued)
+-                *msc_queued = ms_kernel_msc_to_crtc_msc(crtc, vbl.reply.sequence, FALSE);
++                *msc_queued = msc;
+             return TRUE;
+         }
+     check:
+@@ -418,13 +471,15 @@
+     if (!ms_drm_seq)
+         ++ms_drm_seq;
+     q->seq = ms_drm_seq++;
++    q->msc = UINT64_MAX;
+     q->scrn = scrn;
+     q->crtc = crtc;
+     q->data = data;
+     q->handler = handler;
+     q->abort = abort;
+ 
+-    xorg_list_add(&q->list, &ms_drm_queue);
++    /* Keep the list formatted in ascending order of sequence number */
++    xorg_list_append(&q->list, &ms_drm_queue);
+ 
+     return q->seq;
+ }
+@@ -437,9 +492,18 @@
+ static void
+ ms_drm_abort_one(struct ms_drm_queue *q)
+ {
++    if (q->aborted)
++        return;
++
++    /* Don't remove vblank events if they were queued in the kernel */
++    if (q->kernel_queued) {
++        q->abort(q->data);
++        q->aborted = TRUE;
++    } else {
+         xorg_list_del(&q->list);
+         q->abort(q->data);
+         free(q);
++    }
+ }
+ 
+ /**
+@@ -500,18 +564,66 @@
+ {
+     struct ms_drm_queue *q, *tmp;
+     uint32_t seq = (uint32_t) user_data;
++    xf86CrtcPtr crtc = NULL;
++    drmmode_crtc_private_ptr drmmode_crtc;
++    uint64_t msc, next_msc = UINT64_MAX;
+ 
+-    xorg_list_for_each_entry_safe(q, tmp, &ms_drm_queue, list) {
++    /* Handle the seq for this event first in order to get the CRTC */
++    xorg_list_for_each_entry(q, &ms_drm_queue, list) {
+         if (q->seq == seq) {
+-            uint64_t msc;
++            crtc = q->crtc;
++            msc = ms_kernel_msc_to_crtc_msc(crtc, frame, is64bit);
+ 
+-            msc = ms_kernel_msc_to_crtc_msc(q->crtc, frame, is64bit);
++            /* Write the current MSC to this event to ensure its handler runs in
++             * the loop below. This is done because we don't want to run the
++             * handler right now, since we need to ensure all events are handled
++             * in FIFO order with respect to one another. Otherwise, if this
++             * event were handled first just because it was queued to the
++             * kernel, it could run before older events expiring at this MSC.
++             */
++            q->msc = msc;
++            break;
++        }
++    }
++
++    if (!crtc)
++        return;
++
++    /* Now run all of the vblank events for this CRTC with an expired MSC */
++    xorg_list_for_each_entry_safe(q, tmp, &ms_drm_queue, list) {
++        if (q->crtc == crtc && q->msc <= msc) {
+             xorg_list_del(&q->list);
+-            q->handler(msc, ns / 1000, q->data);
++            if (!q->aborted)
++                q->handler(msc, ns / 1000, q->data);
+             free(q);
+-            break;
+         }
+     }
++
++    /* Find this CRTC's next queued MSC and next non-queued MSC to be handled */
++    msc = UINT64_MAX;
++    xorg_list_for_each_entry(q, &ms_drm_queue, list) {
++        if (q->crtc == crtc) {
++            if (q->kernel_queued) {
++                if (q->msc < next_msc)
++                    next_msc = q->msc;
++            } else if (q->msc < msc) {
++                msc = q->msc;
++                seq = q->seq;
++            }
++        }
++    }
++
++    /* Queue an event if the next queued MSC isn't soon enough */
++    drmmode_crtc = crtc->driver_private;
++    drmmode_crtc->next_msc = next_msc;
++    if (msc < next_msc && !ms_queue_vblank(crtc, MS_QUEUE_ABSOLUTE, msc, NULL, seq)) {
++        xf86DrvMsg(crtc->scrn->scrnIndex, X_WARNING,
++                   "failed to queue next vblank event, aborting lost events\n");
++        xorg_list_for_each_entry_safe(q, tmp, &ms_drm_queue, list) {
++            if (q->crtc == crtc && q->msc < next_msc)
++                ms_drm_abort_one(q);
++        }
++    }
+ }
+ 
+ static void
+@@ -528,6 +640,12 @@
+     /* frame is 32 bit wrapped into 64 bit */
+     ms_drm_sequence_handler(fd, frame, ((uint64_t) sec * 1000000 + usec) * 1000,
+                             FALSE, (uint32_t) (uintptr_t) user_ptr);
++}
++
++Bool
++ms_drm_queue_is_empty(void)
++{
++    return xorg_list_is_empty(&ms_drm_queue);
+ }
+ 
+ Bool
+--- hw/xfree86/modes/xf86Crtc.h.orig	2025-02-25 19:56:05.000000000 +0100
++++ hw/xfree86/modes/xf86Crtc.h	2025-03-12 15:54:19.801357000 +0100
+@@ -912,6 +912,12 @@
+ 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.
+--- hw/xfree86/modes/xf86Rotate.c.orig	2025-02-25 19:56:05.000000000 +0100
++++ hw/xfree86/modes/xf86Rotate.c	2025-03-12 15:54:19.802878000 +0100
+@@ -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 @@
+         return;
+ 
+     src = CreatePicture(None,
+-                        &root->drawable,
++                        src_drawable,
+                         format,
+                         CPSubwindowMode,
+                         &include_inferiors, serverClient, &error);
+@@ -70,9 +70,11 @@
+     if (!dst)
+         return;
+ 
+-    error = SetPictureTransform(src, &crtc->crtc_to_framebuffer);
+-    if (error)
+-        return;
++    if (transform_src) {
++        error = SetPictureTransform(src, &crtc->crtc_to_framebuffer);
++        if (error)
++            return;
++    }
+     if (crtc->transform_in_use && crtc->filter)
+         SetPicturePictFilter(src, crtc->filter, crtc->params, crtc->nparams);
+ 
+@@ -205,7 +207,9 @@
+ 
+                 /* update damaged region */
+                 if (RegionNotEmpty(&crtc_damage))
+-                    xf86RotateCrtcRedisplay(crtc, &crtc_damage);
++                    xf86RotateCrtcRedisplay(crtc, crtc->rotatedPixmap,
++                                            &pScreen->root->drawable,
++                                            &crtc_damage, TRUE);
+ 
+                 RegionUninit(&crtc_damage);
+             }
+--- include/pixmap.h.orig	2025-02-25 19:56:05.000000000 +0100
++++ include/pixmap.h	2025-03-12 15:54:19.804007000 +0100
+@@ -134,4 +134,9 @@
+ extern _X_EXPORT Bool
+ PixmapSyncDirtyHelper(PixmapDirtyUpdatePtr dirty);
+ 
++extern _X_EXPORT void
++PixmapDirtyCopyArea(PixmapPtr dst, DrawablePtr src,
++                    int x, int y, int dst_x, int dst_y,
++                    RegionPtr dirty_region);
++
+ #endif                          /* PIXMAP_H */
+--- present/present.h.orig	2025-02-25 19:56:05.000000000 +0100
++++ present/present.h	2025-03-12 15:54:19.805130000 +0100
+@@ -29,7 +29,15 @@
+ 
+ typedef enum {
+     PRESENT_FLIP_REASON_UNKNOWN,
+-    PRESENT_FLIP_REASON_BUFFER_FORMAT
++    PRESENT_FLIP_REASON_BUFFER_FORMAT,
++
++    /* Don't add new flip reasons after the TearFree ones, since it's expected
++     * that the TearFree reasons are the highest ones in order to allow doing
++     * `reason >= PRESENT_FLIP_REASON_DRIVER_TEARFREE` to check if a reason is
++     * PRESENT_FLIP_REASON_DRIVER_TEARFREE{_FLIPPING}.
++     */
++    PRESENT_FLIP_REASON_DRIVER_TEARFREE,
++    PRESENT_FLIP_REASON_DRIVER_TEARFREE_FLIPPING
+ } PresentFlipReason;
+ 
+ typedef struct present_vblank present_vblank_rec, *present_vblank_ptr;
+@@ -148,6 +156,9 @@
+ 
+ extern _X_EXPORT Bool
+ present_screen_init(ScreenPtr screen, present_screen_info_ptr info);
++
++extern _X_EXPORT void
++present_check_flips(WindowPtr window);
+ 
+ typedef void (*present_complete_notify_proc)(WindowPtr window,
+                                              CARD8 kind,
+--- present/present_scmd.c.orig	2025-02-25 19:56:05.000000000 +0100
++++ present/present_scmd.c	2025-03-12 15:54:19.807041000 +0100
+@@ -71,6 +71,7 @@
+     PixmapPtr                   window_pixmap;
+     WindowPtr                   root = screen->root;
+     present_screen_priv_ptr     screen_priv = present_screen_priv(screen);
++    PresentFlipReason           tmp_reason = PRESENT_FLIP_REASON_UNKNOWN;
+ 
+     if (crtc) {
+        screen_priv = present_screen_priv(crtc->pScreen);
+@@ -91,6 +92,27 @@
+     if (!screen_priv->info->flip)
+         return FALSE;
+ 
++    /* Ask the driver for permission. Do this now to see if there's TearFree. */
++    if (screen_priv->info->version >= 1 && screen_priv->info->check_flip2) {
++        if (!(*screen_priv->info->check_flip2) (crtc, window, pixmap, sync_flip, &tmp_reason)) {
++            DebugPresent(("\td %08" PRIx32 " -> %08" PRIx32 "\n", window->drawable.id, pixmap ? pixmap->drawable.id : 0));
++            /* It's fine to return now unless the page flip failure reason is
++             * PRESENT_FLIP_REASON_BUFFER_FORMAT; we must only output that
++             * reason if all the other checks pass.
++             */
++            if (!reason || tmp_reason != PRESENT_FLIP_REASON_BUFFER_FORMAT) {
++                if (reason)
++                    *reason = tmp_reason;
++                return FALSE;
++            }
++        }
++    } else if (screen_priv->info->check_flip) {
++        if (!(*screen_priv->info->check_flip) (crtc, window, pixmap, sync_flip)) {
++            DebugPresent(("\td %08" PRIx32 " -> %08" PRIx32 "\n", window->drawable.id, pixmap ? pixmap->drawable.id : 0));
++            return FALSE;
++        }
++    }
++
+     /* Make sure the window hasn't been redirected with Composite */
+     window_pixmap = screen->GetWindowPixmap(window);
+     if (window_pixmap != screen->GetScreenPixmap(screen) &&
+@@ -123,17 +145,10 @@
+         return FALSE;
+     }
+ 
+-    /* Ask the driver for permission */
+-    if (screen_priv->info->version >= 1 && screen_priv->info->check_flip2) {
+-        if (!(*screen_priv->info->check_flip2) (crtc, window, pixmap, sync_flip, reason)) {
+-            DebugPresent(("\td %08" PRIx32 " -> %08" PRIx32 "\n", window->drawable.id, pixmap ? pixmap->drawable.id : 0));
+-            return FALSE;
+-        }
+-    } else if (screen_priv->info->check_flip) {
+-        if (!(*screen_priv->info->check_flip) (crtc, window, pixmap, sync_flip)) {
+-            DebugPresent(("\td %08" PRIx32 " -> %08" PRIx32 "\n", window->drawable.id, pixmap ? pixmap->drawable.id : 0));
+-            return FALSE;
+-        }
++    if (tmp_reason == PRESENT_FLIP_REASON_BUFFER_FORMAT) {
++        if (reason)
++            *reason = tmp_reason;
++        return FALSE;
+     }
+ 
+     return TRUE;
+@@ -456,7 +471,9 @@
+     xorg_list_for_each_entry(vblank, &window_priv->vblank, window_list) {
+         if (vblank->queued && vblank->flip && !present_check_flip(vblank->crtc, window, vblank->pixmap, vblank->sync_flip, NULL, 0, 0, &reason)) {
+             vblank->flip = FALSE;
+-            vblank->reason = reason;
++            /* Don't spuriously flag this as a TearFree presentation */
++            if (reason < PRESENT_FLIP_REASON_DRIVER_TEARFREE)
++                vblank->reason = reason;
+             if (vblank->sync_flip)
+                 vblank->exec_msc = vblank->target_msc;
+         }
+@@ -560,7 +577,9 @@
+     xorg_list_del(&vblank->window_list);
+     vblank->queued = FALSE;
+ 
+-    if (vblank->pixmap && vblank->window) {
++    if (vblank->pixmap && vblank->window &&
++        (vblank->reason < PRESENT_FLIP_REASON_DRIVER_TEARFREE ||
++         vblank->exec_msc != vblank->target_msc)) {
+ 
+         if (vblank->flip) {
+ 
+@@ -627,6 +646,51 @@
+ 
+         present_execute_copy(vblank, crtc_msc);
+ 
++        /* With TearFree, there's no way to tell exactly when the presentation
++         * will be visible except by waiting for a notification from the kernel
++         * driver indicating that the page flip is complete. This is because the
++         * CRTC's MSC can change while the target MSC is calculated and even
++         * while the page flip IOCTL is sent to the kernel due to scheduling
++         * delays and/or unfortunate timing. Even worse, a page flip isn't
++         * actually guaranteed to be finished after one vblank; it may be
++         * several MSCs until a flip actually finishes depending on delays and
++         * load in hardware.
++         *
++         * So, to get a notification from the driver with TearFree active, the
++         * driver expects a present_flip() call with a NULL pixmap to indicate
++         * that this is a fake flip for a pixmap that's already been copied to
++         * the primary scanout, which will then be flipped by TearFree. TearFree
++         * will then send a notification once the flip containing this pixmap is
++         * complete.
++         *
++         * If the fake flip attempt fails, then fall back to just enqueuing a
++         * vblank event targeting the next MSC.
++         */
++        if (!vblank->queued &&
++            vblank->reason >= PRESENT_FLIP_REASON_DRIVER_TEARFREE) {
++            uint64_t completion_msc = crtc_msc + 1;
++
++            /* If TearFree is already flipping then the presentation will be
++             * visible at the *next* next vblank. This calculation only matters
++             * for the vblank event fallback.
++             */
++            if (vblank->reason == PRESENT_FLIP_REASON_DRIVER_TEARFREE_FLIPPING &&
++                vblank->exec_msc < crtc_msc)
++                    completion_msc++;
++
++            /* Try the fake flip first and then fall back to a vblank event */
++            if (present_flip(vblank->crtc, vblank->event_id, 0, NULL, TRUE) ||
++                Success == screen_priv->queue_vblank(screen,
++                                                     window,
++                                                     vblank->crtc,
++                                                     vblank->event_id,
++                                                     completion_msc)) {
++                /* Ensure present_execute_post() runs at the next execution */
++                vblank->exec_msc = vblank->target_msc;
++                vblank->queued = TRUE;
++            }
++        }
++
+         if (vblank->queued) {
+             xorg_list_add(&vblank->event_queue, &present_exec_queue);
+             xorg_list_append(&vblank->window_list,
+@@ -739,6 +803,11 @@
+             if (vblank->crtc != target_crtc || vblank->target_msc != target_msc)
+                 continue;
+ 
++            /* Too late to abort now if TearFree execution already happened */
++            if (vblank->reason >= PRESENT_FLIP_REASON_DRIVER_TEARFREE &&
++                vblank->exec_msc == vblank->target_msc)
++                continue;
++
+             present_vblank_scrap(vblank);
+             if (vblank->flip_ready)
+                 present_re_execute(vblank);
+@@ -767,7 +836,12 @@
+ 
+     vblank->event_id = ++present_scmd_event_id;
+ 
+-    if (vblank->flip && vblank->sync_flip)
++    /* The soonest presentation is crtc_msc+2 if TearFree is already flipping */
++    if (vblank->reason == PRESENT_FLIP_REASON_DRIVER_TEARFREE_FLIPPING &&
++        !msc_is_after(vblank->exec_msc, crtc_msc + 1))
++        vblank->exec_msc -= 2;
++    else if (vblank->reason >= PRESENT_FLIP_REASON_DRIVER_TEARFREE ||
++             (vblank->flip && vblank->sync_flip))
+         vblank->exec_msc--;
+ 
+     xorg_list_append(&vblank->event_queue, &present_exec_queue);
+--- present/present_screen.c.orig	2025-02-25 19:56:05.000000000 +0100
++++ present/present_screen.c	2025-03-12 15:54:19.808113000 +0100
+@@ -191,6 +191,26 @@
+     return screen_priv;
+ }
+ 
++static int
++check_flip_visit(WindowPtr window, void *data)
++{
++    ScreenPtr screen = window->drawable.pScreen;
++    present_screen_priv_ptr screen_priv = present_screen_priv(screen);
++
++    if (!screen_priv)
++        return WT_DONTWALKCHILDREN;
++
++    screen_priv->check_flip_window(window);
++
++    return WT_WALKCHILDREN;
++}
++
++void
++present_check_flips(WindowPtr window)
++{
++    TraverseTree(window, check_flip_visit, NULL);
++}
++
+ /*
+  * Initialize a screen for use with present in default screen flip mode (scmd)
+  */
-- 
2.48.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.