From nobody Thu Mar 13 14:50:57 2025 X-Original-To: x11@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4ZDPyN1mnGz5qBQJ for ; Fri, 14 Mar 2025 00:15:08 +0000 (UTC) (envelope-from naddy@mips.inka.de) Received: from mail.inka.de (mail.inka.de [IPv6:2a04:c9c7:0:1073:217:a4ff:fe3b:e77c]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (prime256v1) server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) by mx1.freebsd.org (Postfix) with ESMTPS id 4ZDPyL6tknz3mSh for ; Fri, 14 Mar 2025 00:15:06 +0000 (UTC) (envelope-from naddy@mips.inka.de) Authentication-Results: mx1.freebsd.org; dkim=none; dmarc=none; spf=pass (mx1.freebsd.org: domain of naddy@mips.inka.de designates 2a04:c9c7:0:1073:217:a4ff:fe3b:e77c as permitted sender) smtp.mailfrom=naddy@mips.inka.de Received: from mips.inka.de (naddy@[127.0.0.1]) by mail.inka.de with uucp (rmailwrap 0.5) id 1tsshc-0082Va-H1; Fri, 14 Mar 2025 01:15:04 +0100 Received: from lorvorc.mips.inka.de (localhost [127.0.0.1]) by lorvorc.mips.inka.de (8.18.1/8.18.1) with ESMTP id 52E0CGHE037626 for ; Fri, 14 Mar 2025 01:12:16 +0100 (CET) (envelope-from naddy@lorvorc.mips.inka.de) Received: (from naddy@localhost) by lorvorc.mips.inka.de (8.18.1/8.18.1/Submit) id 52E0CGRl037625 for x11@freebsd.org; Fri, 14 Mar 2025 01:12:16 +0100 (CET) (envelope-from naddy) Resent-From: Christian Weisgerber Resent-Date: Fri, 14 Mar 2025 01:12:16 +0100 Resent-Message-ID: Resent-To: x11@freebsd.org Date: Thu, 13 Mar 2025 15:50:57 +0100 From: Christian Weisgerber To: x11@freebsd.org Subject: [PATCH] x11-servers/xorg-server: modesetting: add support for TearFree page flips Message-ID: List-Id: X11 List-Archive: https://lists.freebsd.org/archives/freebsd-x11 List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: freebsd-x11@freebsd.org Sender: owner-freebsd-x11@FreeBSD.org MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="3yJnRBblJT1laWIg" Content-Disposition: inline X-Spamd-Result: default: False [0.70 / 15.00]; NEURAL_SPAM_LONG(1.00)[1.000]; NEURAL_SPAM_MEDIUM(1.00)[1.000]; NEURAL_HAM_SHORT(-1.00)[-0.999]; R_SPF_ALLOW(-0.20)[+mx:c]; MIME_GOOD(-0.10)[multipart/mixed,text/plain,text/x-diff]; ARC_NA(0.00)[]; ASN(0.00)[asn:202113, ipnet:2a04:c9c7::/32, country:DE]; FREEFALL_USER(0.00)[naddy]; MIME_TRACE(0.00)[0:+,1:+,2:+]; MID_RHS_MATCH_FROMTLD(0.00)[]; MISSING_XM_UA(0.00)[]; R_DKIM_NA(0.00)[]; MLMMJ_DEST(0.00)[x11@freebsd.org]; RCVD_COUNT_THREE(0.00)[3]; TO_DN_NONE(0.00)[]; FROM_EQ_ENVFROM(0.00)[]; FROM_HAS_DN(0.00)[]; RCPT_COUNT_ONE(0.00)[1]; TO_MATCH_ENVRCPT_ALL(0.00)[]; RCVD_TLS_LAST(0.00)[]; PREVIOUSLY_DELIVERED(0.00)[x11@freebsd.org]; DMARC_NA(0.00)[inka.de]; HAS_ATTACHMENT(0.00)[] X-Rspamd-Queue-Id: 4ZDPyL6tknz3mSh X-Spamd-Bar: / --3yJnRBblJT1laWIg Content-Type: text/plain; charset=us-ascii Content-Disposition: inline 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 naddy@mips.inka.de --3yJnRBblJT1laWIg Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename=0001-x11-servers-xorg-server-modesetting-add-support-for-.patch From b103c9f880bc83638c8ff23a7a3cc34efc707ce6 Mon Sep 17 00:00:00 2001 From: Christian Weisgerber 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(®ion, &crtc->bounds, 0); ++ RegionIntersect(®ion, ®ion, dirty); ++ ++ if (trf->buf[0].px) { ++ for (i = 0; i < ARRAY_SIZE(trf->buf); i++) ++ RegionUnion(&trf->buf[i].dmg, &trf->buf[i].dmg, ®ion); ++ } else { ++ /* Just notify the kernel of the damages if TearFree isn't used */ ++ dispatch_damages(scrn, crtc, ®ion, ++ pScreen->GetScreenPixmap(pScreen), ++ NULL, ms->drmmode.fb_id); ++ } ++ } ++ DamageEmpty(ms->damage); ++} ++ ++static void ++ms_tearfree_do_flips(ScreenPtr pScreen) ++{ ++#ifdef GLAMOR_HAS_GBM ++ ScrnInfoPtr scrn = xf86ScreenToScrn(pScreen); ++ xf86CrtcConfigPtr xf86_config = XF86_CRTC_CONFIG_PTR(scrn); ++ modesettingPtr ms = modesettingPTR(scrn); ++ int c; ++ ++ if (!ms->drmmode.tearfree_enable) ++ return; ++ ++ for (c = 0; c < xf86_config->num_crtc; c++) { ++ xf86CrtcPtr crtc = xf86_config->crtc[c]; ++ drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; ++ drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree; ++ ++ if (!ms_tearfree_is_active_on_crtc(crtc)) { ++ /* Notify any lingering DRI clients waiting for a flip to finish */ ++ ms_tearfree_dri_abort_all(crtc); ++ continue; ++ } ++ ++ /* Skip if the last flip is still pending, a DRI client is flipping, or ++ * there isn't any damage on the front buffer. ++ */ ++ if (trf->flip_seq || ms->drmmode.dri2_flipping || ++ ms->drmmode.present_flipping || ++ RegionNil(&trf->buf[trf->back_idx ^ 1].dmg)) ++ continue; ++ ++ /* Flip. If it fails, notify the kernel of the front buffer damages */ ++ if (ms_do_tearfree_flip(pScreen, crtc)) { ++ dispatch_damages(scrn, crtc, &trf->buf[trf->back_idx ^ 1].dmg, ++ trf->buf[trf->back_idx ^ 1].px, NULL, ++ trf->buf[trf->back_idx ^ 1].fb_id); ++ RegionEmpty(&trf->buf[trf->back_idx ^ 1].dmg); ++ } ++ } ++#endif ++} ++ ++static void + dispatch_dirty(ScreenPtr pScreen) + { + ScrnInfoPtr scrn = xf86ScreenToScrn(pScreen); +@@ -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(®ion, &crtc->bounds, 0); ++ RegionIntersect(®ion, ®ion, dirty); ++ ++ /* No damage on this CRTC means no TearFree flip. This means the DRI client ++ * didn't change this CRTC's contents at all with its presentation, possibly ++ * because its window is fully occluded by another window on this CRTC. ++ */ ++ if (RegionNil(®ion)) ++ return FALSE; ++ ++ flip = calloc(1, sizeof(*flip)); ++ if (!flip) ++ return FALSE; ++ ++ flipdata = calloc(1, sizeof(*flipdata)); ++ if (!flipdata) { ++ free(flip); ++ return FALSE; ++ } ++ ++ /* Only track the DRI client's fake flip on the reference CRTC, which aligns ++ * with the behavior of Present when a client copies its pixmap rather than ++ * directly flipping it onto the display. ++ */ ++ flip->on_reference_crtc = TRUE; ++ flip->flipdata = flipdata; ++ flip->tearfree_seq = trf->flip_seq; ++ flipdata->screen = xf86ScrnToScreen(crtc->scrn); ++ flipdata->event = event; ++ flipdata->flip_count = 1; ++ flipdata->event_handler = pageflip_handler; ++ flipdata->abort_handler = pageflip_abort; ++ ++ /* Keep the list in FIFO order so that clients are notified in order */ ++ xorg_list_append(&flip->node, &trf->dri_flip_list); ++ return TRUE; ++} ++ + Bool + ms_do_pageflip(ScreenPtr screen, + PixmapPtr new_front, + void *event, +- int ref_crtc_vblank_pipe, ++ xf86CrtcPtr ref_crtc, + Bool async, + ms_pageflip_handler_proc pageflip_handler, + ms_pageflip_abort_proc pageflip_abort, + const char *log_prefix) + { +-#ifndef GLAMOR_HAS_GBM +- return FALSE; +-#else + ScrnInfoPtr scrn = xf86ScreenToScrn(screen); + modesettingPtr ms = modesettingPTR(scrn); + xf86CrtcConfigPtr config = XF86_CRTC_CONFIG_PTR(scrn); +@@ -315,6 +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 --3yJnRBblJT1laWIg--