Hi,

I noticed the "TearFree" patch was merged upstream in the XOrg
modesetting driver. Long time ago when it was sent I patched it into
my local Xenocara and used it ever since. Zero issues, zero tearing.

Maybe let's consider adding it to Xenocara too? I am attaching the
patch, commit message is from my local git repo, I am of course NOT an
author. It adds the "TearFree" option to the modesetting section of
your xorg.conf.

Feel free to fetch it directly from the Merge Request, the real author
is https://gitlab.freedesktop.org/kerneltoast:

https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/1006

I am just sending this as is, but I don't know enough about XOrg to
follow up with extra fixes to the code. I can only confirm that it
worked flawlessly on my Intel NUC 11 for a really long time.

Thanks,
Igor.
From ba437abd59efa8a18426748b5edd2ce7f67c5dbe Mon Sep 17 00:00:00 2001
From: Igor Petruk <ipet...@google.com>
Date: Sun, 4 Dec 2022 23:00:50 +0000
Subject: [PATCH] Added tearfree patch

---
 xserver/dix/pixmap.c                          |  15 +-
 .../hw/xfree86/drivers/modesetting/driver.c   |  68 ++++++
 .../hw/xfree86/drivers/modesetting/driver.h   |   3 +
 .../drivers/modesetting/drmmode_display.c     | 202 ++++++++++++++----
 .../drivers/modesetting/drmmode_display.h     |  16 ++
 .../drivers/modesetting/modesetting.man       |  11 +
 .../hw/xfree86/drivers/modesetting/pageflip.c | 108 ++++++++--
 .../hw/xfree86/drivers/modesetting/present.c  |  22 +-
 xserver/hw/xfree86/modes/xf86Crtc.h           |   5 +
 xserver/hw/xfree86/modes/xf86Rotate.c         |  22 +-
 xserver/include/pixmap.h                      |   5 +
 xserver/present/present.h                     |   4 +-
 xserver/present/present_execute.c             |   4 +-
 xserver/present/present_priv.h                |   1 +
 xserver/present/present_scmd.c                |  60 ++++--
 xserver/present/present_vblank.c              |  12 ++
 16 files changed, 465 insertions(+), 93 deletions(-)

diff --git a/xserver/dix/pixmap.c b/xserver/dix/pixmap.c
index 5a0146bbb..0b01c4ee0 100644
--- a/xserver/dix/pixmap.c
+++ b/xserver/dix/pixmap.c
@@ -262,12 +262,11 @@ PixmapStopDirtyTracking(DrawablePtr src, PixmapPtr secondary_dst)
     return TRUE;
 }
 
-static void
-PixmapDirtyCopyArea(PixmapPtr dst,
-                    PixmapDirtyUpdatePtr dirty,
+void
+PixmapDirtyCopyArea(PixmapPtr dst, DrawablePtr src,
+                    int x, int y, int dst_x, int dst_y,
                     RegionPtr dirty_region)
 {
-    DrawablePtr src = dirty->src;
     ScreenPtr pScreen = src->pScreen;
     int n;
     BoxPtr b;
@@ -294,9 +293,8 @@ PixmapDirtyCopyArea(PixmapPtr dst,
         h = dst_box.y2 - dst_box.y1;
 
         pGC->ops->CopyArea(src, &dst->drawable, pGC,
-                           dirty->x + dst_box.x1, dirty->y + dst_box.y1, w, h,
-                           dirty->dst_x + dst_box.x1,
-                           dirty->dst_y + dst_box.y1);
+                           x + dst_box.x1, y + dst_box.y1, w, h,
+                           dst_x + dst_box.x1, dst_y + dst_box.y1);
         b++;
     }
     FreeScratchGC(pGC);
@@ -408,7 +406,8 @@ Bool PixmapSyncDirtyHelper(PixmapDirtyUpdatePtr dirty)
     RegionTranslate(&pixregion, -dirty->x, -dirty->y);
 
     if (!pScreen->root || dirty->rotation == RR_Rotate_0)
-        PixmapDirtyCopyArea(dst, dirty, &pixregion);
+        PixmapDirtyCopyArea(dst, dirty->src, dirty->x, dirty->y,
+                            dirty->dst_x, dirty->dst_y, &pixregion);
     else
         PixmapDirtyCompositeRotate(dst, dirty, &pixregion);
     pScreen->SourceValidate = SourceValidate;
diff --git a/xserver/hw/xfree86/drivers/modesetting/driver.c b/xserver/hw/xfree86/drivers/modesetting/driver.c
index 62d471a26..e7691645f 100644
--- a/xserver/hw/xfree86/drivers/modesetting/driver.c
+++ b/xserver/hw/xfree86/drivers/modesetting/driver.c
@@ -151,6 +151,7 @@ static const OptionInfoRec Options[] = {
     {OPTION_VARIABLE_REFRESH, "VariableRefresh", OPTV_BOOLEAN, {0}, FALSE},
     {OPTION_USE_GAMMA_LUT, "UseGammaLUT", OPTV_BOOLEAN, {0}, FALSE},
     {OPTION_ASYNC_FLIP_SECONDARIES, "AsyncFlipSecondaries", OPTV_BOOLEAN, {0}, FALSE},
+    {OPTION_TEARFREE, "TearFree", OPTV_BOOLEAN, {0}, FALSE},
     {-1, NULL, OPTV_NONE, {0}, FALSE}
 };
 
@@ -521,6 +522,64 @@ GetRec(ScrnInfoPtr pScrn)
     return TRUE;
 }
 
+static void
+ms_tearfree_update_damages(ScrnInfoPtr scrn, RegionPtr dirty)
+{
+    xf86CrtcConfigPtr xf86_config = XF86_CRTC_CONFIG_PTR(scrn);
+    modesettingPtr ms = modesettingPTR(scrn);
+    int c, i;
+
+    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;
+        RegionRec region;
+
+        /* Skip CRTCs which aren't using TearFree */
+        if (!trf->buf[0].px)
+            continue;
+
+        /* Compute how much of the damage intersects with this CRTC */
+        RegionInit(&region, &crtc->bounds, 0);
+        RegionIntersect(&region, &region, dirty);
+        for (i = 0; i < ARRAY_SIZE(trf->buf); i++)
+            RegionUnion(&trf->buf[i].dmg, &trf->buf[i].dmg, &region);
+    }
+}
+
+static void
+ms_tearfree_do_flips(ScreenPtr pScreen)
+{
+#ifdef GLAMOR_HAS_GBM
+    ScrnInfoPtr scrn = xf86ScreenToScrn(pScreen);
+    xf86CrtcConfigPtr xf86_config = XF86_CRTC_CONFIG_PTR(scrn);
+    modesettingPtr ms = modesettingPTR(scrn);
+    int c;
+
+    if (!ms->drmmode.tearfree_enable)
+        return;
+
+    for (c = 0; c < xf86_config->num_crtc; c++) {
+        xf86CrtcPtr crtc = xf86_config->crtc[c];
+        drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
+        drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree;
+
+        /* Skip disabled CRTCs and those which aren't using TearFree */
+        if (!trf->buf[0].px || !crtc->scrn->vtSema || !xf86_crtc_on(crtc))
+            continue;
+
+        /* If we can, flip if the last flip finished and there's new damage */
+        if (!trf->flip_seq && !ms->drmmode.dri2_flipping &&
+            !ms->drmmode.present_flipping &&
+            !RegionNil(&trf->buf[trf->back_idx ^ 1].dmg))
+            ms_do_tearfree_flip(pScreen, crtc);
+    }
+#endif
+}
+
 static int
 dispatch_dirty_region(ScrnInfoPtr scrn,
                       PixmapPtr pixmap, DamagePtr damage, int fb_id)
@@ -558,6 +617,7 @@ dispatch_dirty_region(ScrnInfoPtr scrn,
         }
 
         free(clip);
+        ms_tearfree_update_damages(scrn, dirty);
         DamageEmpty(damage);
     }
     return ret;
@@ -713,6 +773,7 @@ msBlockHandler(ScreenPtr pScreen, void *timeout)
         dispatch_dirty(pScreen);
 
     ms_dirty_update(pScreen, timeout);
+    ms_tearfree_do_flips(pScreen);
 }
 
 static void
@@ -1219,6 +1280,13 @@ PreInit(ScrnInfoPtr pScrn, int flags)
     ms->drmmode.pageflip =
         xf86ReturnOptValBool(ms->drmmode.Options, OPTION_PAGEFLIP, TRUE);
 
+#ifdef GLAMOR_HAS_GBM
+    ms->drmmode.tearfree_enable =
+        xf86ReturnOptValBool(ms->drmmode.Options, OPTION_TEARFREE, FALSE);
+    if (ms->drmmode.tearfree_enable)
+        xf86DrvMsg(pScrn->scrnIndex, X_INFO, "TearFree: enabled\n");
+#endif
+
     pScrn->capabilities = 0;
     ret = drmGetCap(ms->fd, DRM_CAP_PRIME, &value);
     if (ret == 0) {
diff --git a/xserver/hw/xfree86/drivers/modesetting/driver.h b/xserver/hw/xfree86/drivers/modesetting/driver.h
index 71aa8730e..a71190834 100644
--- a/xserver/hw/xfree86/drivers/modesetting/driver.h
+++ b/xserver/hw/xfree86/drivers/modesetting/driver.h
@@ -61,6 +61,7 @@ typedef enum {
     OPTION_VARIABLE_REFRESH,
     OPTION_USE_GAMMA_LUT,
     OPTION_ASYNC_FLIP_SECONDARIES,
+    OPTION_TEARFREE,
 } modesettingOpts;
 
 typedef struct
@@ -238,6 +239,8 @@ Bool ms_do_pageflip(ScreenPtr screen,
                     ms_pageflip_abort_proc pageflip_abort,
                     const char *log_prefix);
 
+void ms_do_tearfree_flip(ScreenPtr screen, xf86CrtcPtr crtc);
+
 #endif
 
 int ms_flush_drm_events(ScreenPtr screen);
diff --git a/xserver/hw/xfree86/drivers/modesetting/drmmode_display.c b/xserver/hw/xfree86/drivers/modesetting/drmmode_display.c
index 099999d80..160e38b2c 100644
--- a/xserver/hw/xfree86/drivers/modesetting/drmmode_display.c
+++ b/xserver/hw/xfree86/drivers/modesetting/drmmode_display.c
@@ -635,6 +635,8 @@ drmmode_crtc_get_fb_id(xf86CrtcPtr crtc, uint32_t *fb_id, int *x, int *y)
 {
     drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
     drmmode_ptr drmmode = drmmode_crtc->drmmode;
+    drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree;
+    uint32_t front_idx = trf->back_idx ^ 1;
     int ret;
 
     *fb_id = 0;
@@ -649,6 +651,10 @@ drmmode_crtc_get_fb_id(xf86CrtcPtr crtc, uint32_t *fb_id, int *x, int *y)
             *x = drmmode_crtc->prime_pixmap_x;
         *y = 0;
     }
+    else if (trf->buf[front_idx].px) {
+        *fb_id = trf->buf[front_idx].fb_id;
+        *x = *y = 0;
+    }
     else if (drmmode_crtc->rotate_fb_id) {
         *fb_id = drmmode_crtc->rotate_fb_id;
         *x = *y = 0;
@@ -937,7 +943,8 @@ drmmode_crtc_flip(xf86CrtcPtr crtc, uint32_t fb_id, uint32_t flags, void *data)
 {
     modesettingPtr ms = modesettingPTR(crtc->scrn);
     drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
-    int ret;
+    uint32_t fb_id_tmp;
+    int x, y, ret;
 
     if (ms->atomic_modeset) {
         drmModeAtomicReq *req = drmModeAtomicAlloc();
@@ -945,7 +952,9 @@ drmmode_crtc_flip(xf86CrtcPtr crtc, uint32_t fb_id, uint32_t flags, void *data)
         if (!req)
             return 1;
 
-        ret = plane_add_props(req, crtc, fb_id, crtc->x, crtc->y);
+        /* Get the correct x and y coordinates */
+        drmmode_crtc_get_fb_id(crtc, &fb_id_tmp, &x, &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);
@@ -1551,6 +1560,91 @@ drmmode_copy_fb(ScrnInfoPtr pScrn, drmmode_ptr drmmode)
 #endif
 }
 
+void
+drmmode_copy_damage(xf86CrtcPtr crtc, PixmapPtr dst, RegionPtr dmg)
+{
+#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 */
+    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);
+        trf->flip_seq = 0;
+    }
+
+    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;
+    uint32_t front_idx = trf->back_idx ^ 1;
+    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[front_idx].px, &trf->buf[front_idx].dmg);
+    return TRUE;
+}
+
 static Bool
 drmmode_set_mode_major(xf86CrtcPtr crtc, DisplayModePtr mode,
                        Rotation rotation, int x, int y)
@@ -1584,6 +1678,10 @@ drmmode_set_mode_major(xf86CrtcPtr crtc, DisplayModePtr mode,
         crtc->funcs->gamma_set(crtc, crtc->gamma_red, crtc->gamma_green,
                                crtc->gamma_blue, crtc->gamma_size);
 
+        ret = drmmode_create_tearfree_shadow(crtc);
+        if (!ret)
+            goto done;
+
         can_test = drmmode_crtc_can_test_mode(crtc);
         if (drmmode_crtc_set_mode(crtc, can_test)) {
             xf86DrvMsg(crtc->scrn->scrnIndex, X_ERROR,
@@ -1629,6 +1727,7 @@ drmmode_set_mode_major(xf86CrtcPtr crtc, DisplayModePtr mode,
         crtc->y = saved_y;
         crtc->rotation = saved_rotation;
         crtc->mode = saved_mode;
+        drmmode_create_tearfree_shadow(crtc);
     } else
         crtc->active = TRUE;
 
@@ -1934,33 +2033,42 @@ drmmode_clear_pixmap(PixmapPtr pixmap)
 }
 
 static void *
-drmmode_shadow_allocate(xf86CrtcPtr crtc, int width, int height)
+drmmode_shadow_fb_allocate(xf86CrtcPtr crtc, int width, int height,
+                           drmmode_bo *bo, uint32_t *fb_id)
 {
     drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
     drmmode_ptr drmmode = drmmode_crtc->drmmode;
     int ret;
 
-    if (!drmmode_create_bo(drmmode, &drmmode_crtc->rotate_bo,
-                           width, height, drmmode->kbpp)) {
+    if (!drmmode_create_bo(drmmode, bo, width, height, drmmode->kbpp)) {
         xf86DrvMsg(crtc->scrn->scrnIndex, X_ERROR,
                "Couldn't allocate shadow memory for rotated CRTC\n");
         return NULL;
     }
 
-    ret = drmmode_bo_import(drmmode, &drmmode_crtc->rotate_bo,
-                            &drmmode_crtc->rotate_fb_id);
+    ret = drmmode_bo_import(drmmode, bo, fb_id);
 
     if (ret) {
         ErrorF("failed to add rotate fb\n");
-        drmmode_bo_destroy(drmmode, &drmmode_crtc->rotate_bo);
+        drmmode_bo_destroy(drmmode, bo);
         return NULL;
     }
 
 #ifdef GLAMOR_HAS_GBM
     if (drmmode->gbm)
-        return drmmode_crtc->rotate_bo.gbm;
+        return bo->gbm;
 #endif
-    return drmmode_crtc->rotate_bo.dumb;
+    return bo->dumb;
+}
+
+static void *
+drmmode_shadow_allocate(xf86CrtcPtr crtc, int width, int height)
+{
+    drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
+
+    return drmmode_shadow_fb_allocate(crtc, width, height,
+                                      &drmmode_crtc->rotate_bo,
+                                      &drmmode_crtc->rotate_fb_id);
 }
 
 static PixmapPtr
@@ -1986,70 +2094,91 @@ static Bool
 drmmode_set_pixmap_bo(drmmode_ptr drmmode, PixmapPtr pixmap, drmmode_bo *bo);
 
 static PixmapPtr
-drmmode_shadow_create(xf86CrtcPtr crtc, void *data, int width, int height)
+drmmode_shadow_fb_create(xf86CrtcPtr crtc, void *data, int width, int height,
+                         drmmode_bo *bo, uint32_t *fb_id)
 {
     ScrnInfoPtr scrn = crtc->scrn;
     drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
     drmmode_ptr drmmode = drmmode_crtc->drmmode;
-    uint32_t rotate_pitch;
-    PixmapPtr rotate_pixmap;
+    uint32_t pitch;
+    PixmapPtr pixmap;
     void *pPixData = NULL;
 
     if (!data) {
-        data = drmmode_shadow_allocate(crtc, width, height);
+        data = drmmode_shadow_fb_allocate(crtc, width, height, bo, fb_id);
         if (!data) {
             xf86DrvMsg(scrn->scrnIndex, X_ERROR,
-                       "Couldn't allocate shadow pixmap for rotated CRTC\n");
+                       "Couldn't allocate shadow pixmap for CRTC\n");
             return NULL;
         }
     }
 
-    if (!drmmode_bo_has_bo(&drmmode_crtc->rotate_bo)) {
+    if (!drmmode_bo_has_bo(bo)) {
         xf86DrvMsg(scrn->scrnIndex, X_ERROR,
-                   "Couldn't allocate shadow pixmap for rotated CRTC\n");
+                   "Couldn't allocate shadow pixmap for CRTC\n");
         return NULL;
     }
 
-    pPixData = drmmode_bo_map(drmmode, &drmmode_crtc->rotate_bo);
-    rotate_pitch = drmmode_bo_get_pitch(&drmmode_crtc->rotate_bo);
+    pPixData = drmmode_bo_map(drmmode, bo);
+    pitch = drmmode_bo_get_pitch(bo);
 
-    rotate_pixmap = drmmode_create_pixmap_header(scrn->pScreen,
-                                                 width, height,
-                                                 scrn->depth,
-                                                 drmmode->kbpp,
-                                                 rotate_pitch,
-                                                 pPixData);
+    pixmap = drmmode_create_pixmap_header(scrn->pScreen,
+                                          width, height,
+                                          scrn->depth,
+                                          drmmode->kbpp,
+                                          pitch,
+                                          pPixData);
 
-    if (rotate_pixmap == NULL) {
+    if (pixmap == NULL) {
         xf86DrvMsg(scrn->scrnIndex, X_ERROR,
-                   "Couldn't allocate shadow pixmap for rotated CRTC\n");
+                   "Couldn't allocate shadow pixmap for CRTC\n");
         return NULL;
     }
 
-    drmmode_set_pixmap_bo(drmmode, rotate_pixmap, &drmmode_crtc->rotate_bo);
+    drmmode_set_pixmap_bo(drmmode, pixmap, bo);
 
-    return 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)
 {
@@ -4277,6 +4406,7 @@ drmmode_free_bos(ScrnInfoPtr pScrn, drmmode_ptr drmmode)
         drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
 
         dumb_bo_destroy(drmmode->fd, drmmode_crtc->cursor_bo);
+        drmmode_destroy_tearfree_shadow(crtc);
     }
 }
 
diff --git a/xserver/hw/xfree86/drivers/modesetting/drmmode_display.h b/xserver/hw/xfree86/drivers/modesetting/drmmode_display.h
index d095319b7..44e24258d 100644
--- a/xserver/hw/xfree86/drivers/modesetting/drmmode_display.h
+++ b/xserver/hw/xfree86/drivers/modesetting/drmmode_display.h
@@ -138,6 +138,7 @@ typedef struct {
     Bool async_flip_secondaries;
     Bool dri2_enable;
     Bool present_enable;
+    Bool tearfree_enable;
 
     uint32_t vrr_prop_id;
     Bool use_ctm;
@@ -169,6 +170,19 @@ typedef struct {
     uint64_t *modifiers;
 } drmmode_format_rec, *drmmode_format_ptr;
 
+typedef struct {
+    drmmode_bo bo;
+    uint32_t fb_id;
+    PixmapPtr px;
+    RegionRec dmg;
+} drmmode_shadow_fb_rec, *drmmode_shadow_fb_ptr;
+
+typedef struct {
+    drmmode_shadow_fb_rec buf[2];
+    uint32_t back_idx;
+    uint32_t flip_seq;
+} drmmode_tearfree_rec, *drmmode_tearfree_ptr;
+
 typedef struct {
     drmmode_ptr drmmode;
     drmModeCrtcPtr mode_crtc;
@@ -187,6 +201,7 @@ typedef struct {
 
     drmmode_bo rotate_bo;
     unsigned rotate_fb_id;
+    drmmode_tearfree_rec tearfree;
 
     PixmapPtr prime_pixmap;
     PixmapPtr prime_pixmap_back;
@@ -311,6 +326,7 @@ void drmmode_get_default_bpp(ScrnInfoPtr pScrn, drmmode_ptr drmmmode,
                              int *depth, int *bpp);
 
 void drmmode_copy_fb(ScrnInfoPtr pScrn, drmmode_ptr drmmode);
+void drmmode_copy_damage(xf86CrtcPtr crtc, PixmapPtr dst, RegionPtr damage);
 
 int drmmode_crtc_flip(xf86CrtcPtr crtc, uint32_t fb_id, uint32_t flags, void *data);
 
diff --git a/xserver/hw/xfree86/drivers/modesetting/modesetting.man b/xserver/hw/xfree86/drivers/modesetting/modesetting.man
index 71790011e..9e40b04f3 100644
--- a/xserver/hw/xfree86/drivers/modesetting/modesetting.man
+++ b/xserver/hw/xfree86/drivers/modesetting/modesetting.man
@@ -109,6 +109,17 @@ When enabled, this option allows the driver to use gamma ramps with more
 entries, if supported by the kernel. By default, GAMMA_LUT will be used for
 kms drivers which are known to be safe for use of GAMMA_LUT.
 .TP
+.BI "Option \*qTearFree\*q \*q" boolean \*q
+Enable tearing prevention using the hardware page flipping mechanism.
+It allocates two extra scanout buffers for each CRTC and utilizes damage
+tracking to minimize buffer copying and skip unnecessary flips when the
+screen's contents have not changed. It works on transformed screens too, such
+as rotated and scaled CRTCs. When PageFlip is enabled, fullscreen DRI
+applications will still have the discretion to not use tearing prevention.
+.br
+The default is
+.B off.
+.TP
 .SH "SEE ALSO"
 @xservername@(@appmansuffix@), @xconfigfile@(@filemansuffix@), Xserver(@appmansuffix@),
 X(@miscmansuffix@)
diff --git a/xserver/hw/xfree86/drivers/modesetting/pageflip.c b/xserver/hw/xfree86/drivers/modesetting/pageflip.c
index 23ee95f9a..d33608d8f 100644
--- a/xserver/hw/xfree86/drivers/modesetting/pageflip.c
+++ b/xserver/hw/xfree86/drivers/modesetting/pageflip.c
@@ -35,8 +35,8 @@
  * Returns a negative value on error, 0 if there was nothing to process,
  * or 1 if we handled any events.
  */
-int
-ms_flush_drm_events(ScreenPtr screen)
+static int
+ms_flush_drm_events_timeout(ScreenPtr screen, int timeout)
 {
     ScrnInfoPtr scrn = xf86ScreenToScrn(screen);
     modesettingPtr ms = modesettingPTR(scrn);
@@ -45,7 +45,7 @@ ms_flush_drm_events(ScreenPtr screen)
     int r;
 
     do {
-            r = xserver_poll(&p, 1, 0);
+            r = xserver_poll(&p, 1, timeout);
     } while (r == -1 && (errno == EINTR || errno == EAGAIN));
 
     /* If there was an error, r will be < 0.  Return that.  If there was
@@ -63,6 +63,12 @@ ms_flush_drm_events(ScreenPtr screen)
     return 1;
 }
 
+int
+ms_flush_drm_events(ScreenPtr screen)
+{
+    return ms_flush_drm_events_timeout(screen, 0);
+}
+
 #ifdef GLAMOR_HAS_GBM
 
 /*
@@ -160,11 +166,24 @@ ms_pageflip_abort(void *data)
 }
 
 static Bool
-do_queue_flip_on_crtc(modesettingPtr ms, xf86CrtcPtr crtc,
-                      uint32_t flags, uint32_t seq)
+do_queue_flip_on_crtc(ScreenPtr screen, xf86CrtcPtr crtc, uint32_t flags,
+                      uint32_t seq, uint32_t fb_id)
 {
-    return drmmode_crtc_flip(crtc, ms->drmmode.fb_id, flags,
-                             (void *) (uintptr_t) seq);
+    while (drmmode_crtc_flip(crtc, fb_id, flags, (void *)(uintptr_t)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) {
+            ms_drm_abort_seq(crtc->scrn, seq);
+            return TRUE;
+        }
+
+        /* We flushed some events, so try again. */
+        xf86DrvMsg(crtc->scrn->scrnIndex, X_WARNING, "flip queue retry\n");
+    }
+
+    return FALSE;
 }
 
 enum queue_flip_status {
@@ -205,20 +224,8 @@ queue_flip_on_crtc(ScreenPtr screen, xf86CrtcPtr crtc,
     /* take a reference on flipdata for use in flip */
     flipdata->flip_count++;
 
-    while (do_queue_flip_on_crtc(ms, crtc, flags, seq)) {
-        /* We may have failed because the event queue was full.  Flush it
-         * and retry.  If there was nothing to flush, then we failed for
-         * some other reason and should just return an error.
-         */
-        if (ms_flush_drm_events(screen) <= 0) {
-            /* Aborting will also decrement flip_count and free(flip). */
-            ms_drm_abort_seq(scrn, seq);
-            return QUEUE_FLIP_DRM_FLUSH_FAILED;
-        }
-
-        /* We flushed some events, so try again. */
-        xf86DrvMsg(scrn->scrnIndex, X_WARNING, "flip queue retry\n");
-    }
+    if (do_queue_flip_on_crtc(screen, crtc, flags, seq, ms->drmmode.fb_id))
+        return QUEUE_FLIP_DRM_FLUSH_FAILED;
 
     /* The page flip succeeded. */
     return QUEUE_FLIP_SUCCESS;
@@ -368,6 +375,24 @@ ms_do_pageflip(ScreenPtr screen,
             ms->drmmode.flip_bo_import_failed = FALSE;
     }
 
+    /* Wait for any pending TearFree flips to finish on all CRTCs first */
+    if (ms->drmmode.tearfree_enable) {
+        for (i = 0; i < config->num_crtc; i++) {
+            xf86CrtcPtr crtc = config->crtc[i];
+            drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
+            drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree;
+
+            if (!xf86_crtc_on(crtc))
+                continue;
+
+            while (trf->flip_seq) {
+                /* Sleep if necessary to wait for a new event */
+                if (ms_flush_drm_events_timeout(screen, -1) < 0)
+                    goto error_undo;
+            }
+        }
+    }
+
     /* Queue flips on all enabled CRTCs.
      *
      * Note that if/when we get per-CRTC buffers, we'll have to update this.
@@ -465,4 +490,45 @@ error_out:
 #endif /* GLAMOR_HAS_GBM */
 }
 
+static void
+ms_tearfree_flip_abort(void *data)
+{
+    drmmode_tearfree_ptr trf = data;
+
+    trf->flip_seq = 0;
+}
+
+static void
+ms_tearfree_flip_handler(uint64_t msc, uint64_t usec, void *data)
+{
+    drmmode_tearfree_ptr trf = data;
+
+    /* Swap the buffers and complete the flip */
+    trf->back_idx ^= 1;
+    trf->flip_seq = 0;
+}
+
+void
+ms_do_tearfree_flip(ScreenPtr screen, xf86CrtcPtr crtc)
+{
+    drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private;
+    drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree;
+    uint32_t idx = trf->back_idx, seq;
+
+    seq = ms_drm_queue_alloc(crtc, trf, ms_tearfree_flip_handler,
+                             ms_tearfree_flip_abort);
+    if (!seq)
+        return;
+
+    /* 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);
+    if (do_queue_flip_on_crtc(screen, crtc, DRM_MODE_PAGE_FLIP_EVENT,
+                              seq, trf->buf[idx].fb_id)) {
+        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);
+    } else {
+        trf->flip_seq = seq;
+    }
+}
 #endif
diff --git a/xserver/hw/xfree86/drivers/modesetting/present.c b/xserver/hw/xfree86/drivers/modesetting/present.c
index c3266d871..642f7baaf 100644
--- a/xserver/hw/xfree86/drivers/modesetting/present.c
+++ b/xserver/hw/xfree86/drivers/modesetting/present.c
@@ -318,14 +318,32 @@ ms_present_check_flip(RRCrtcPtr crtc,
     modesettingPtr ms = modesettingPTR(scrn);
 
     if (ms->drmmode.sprites_visible > 0)
-        return FALSE;
+        goto no_flip;
 
     if(!ms_present_check_unflip(crtc, window, pixmap, sync_flip, reason))
-        return FALSE;
+        goto no_flip;
 
     ms->flip_window = window;
 
     return TRUE;
+
+no_flip:
+    /* Export some info about TearFree if Present can't flip anyway */
+    if (reason && ms->drmmode.tearfree_enable) {
+        xf86CrtcPtr xf86_crtc = crtc->devPrivate;
+        drmmode_crtc_private_ptr drmmode_crtc = xf86_crtc->driver_private;
+        drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree;
+
+        if (trf->buf[0].px) {
+            if (trf->flip_seq)
+                /* The driver has a TearFree flip pending */
+                *reason = PRESENT_FLIP_REASON_DRIVER_TEARFREE_FLIPPING;
+            else
+                /* The driver uses TearFree flips and there's no flip pending */
+                *reason = PRESENT_FLIP_REASON_DRIVER_TEARFREE;
+        }
+    }
+    return FALSE;
 }
 
 /*
diff --git a/xserver/hw/xfree86/modes/xf86Crtc.h b/xserver/hw/xfree86/modes/xf86Crtc.h
index 1e87f0ea0..e60d7b5b0 100644
--- a/xserver/hw/xfree86/modes/xf86Crtc.h
+++ b/xserver/hw/xfree86/modes/xf86Crtc.h
@@ -912,6 +912,11 @@ extern _X_EXPORT void
 extern _X_EXPORT Bool
  xf86CrtcRotate(xf86CrtcPtr crtc);
 
+extern _X_EXPORT void
+ xf86RotateCrtcRedisplay(xf86CrtcPtr crtc, PixmapPtr dst_pixmap,
+                         DrawableRec *src_drawable, RegionPtr region,
+                         Bool transform_src);
+
 /*
  * Clean up any rotation data, used when a crtc is turned off
  * as well as when rotation is disabled.
diff --git a/xserver/hw/xfree86/modes/xf86Rotate.c b/xserver/hw/xfree86/modes/xf86Rotate.c
index ea9c43c0f..944a01b1c 100644
--- a/xserver/hw/xfree86/modes/xf86Rotate.c
+++ b/xserver/hw/xfree86/modes/xf86Rotate.c
@@ -39,13 +39,13 @@
 #include "X11/extensions/dpmsconst.h"
 #include "X11/Xatom.h"
 
-static void
-xf86RotateCrtcRedisplay(xf86CrtcPtr crtc, RegionPtr region)
+void
+xf86RotateCrtcRedisplay(xf86CrtcPtr crtc, PixmapPtr dst_pixmap,
+                        DrawableRec *src_drawable, RegionPtr region,
+                        Bool transform_src)
 {
     ScrnInfoPtr scrn = crtc->scrn;
     ScreenPtr screen = scrn->pScreen;
-    WindowPtr root = screen->root;
-    PixmapPtr dst_pixmap = crtc->rotatedPixmap;
     PictFormatPtr format = PictureWindowFormat(screen->root);
     int error;
     PicturePtr src, dst;
@@ -57,7 +57,7 @@ xf86RotateCrtcRedisplay(xf86CrtcPtr crtc, RegionPtr region)
         return;
 
     src = CreatePicture(None,
-                        &root->drawable,
+                        src_drawable,
                         format,
                         CPSubwindowMode,
                         &include_inferiors, serverClient, &error);
@@ -70,9 +70,11 @@ xf86RotateCrtcRedisplay(xf86CrtcPtr crtc, RegionPtr region)
     if (!dst)
         return;
 
-    error = SetPictureTransform(src, &crtc->crtc_to_framebuffer);
-    if (error)
-        return;
+    if (transform_src) {
+        error = SetPictureTransform(src, &crtc->crtc_to_framebuffer);
+        if (error)
+            return;
+    }
     if (crtc->transform_in_use && crtc->filter)
         SetPicturePictFilter(src, crtc->filter, crtc->params, crtc->nparams);
 
@@ -205,7 +207,9 @@ xf86RotateRedisplay(ScreenPtr pScreen)
 
                 /* update damaged region */
                 if (RegionNotEmpty(&crtc_damage))
-                    xf86RotateCrtcRedisplay(crtc, &crtc_damage);
+                    xf86RotateCrtcRedisplay(crtc, crtc->rotatedPixmap,
+                                            &pScreen->root->drawable,
+                                            &crtc_damage, TRUE);
 
                 RegionUninit(&crtc_damage);
             }
diff --git a/xserver/include/pixmap.h b/xserver/include/pixmap.h
index 7144bfb30..e251690d5 100644
--- a/xserver/include/pixmap.h
+++ b/xserver/include/pixmap.h
@@ -134,4 +134,9 @@ PixmapStopDirtyTracking(DrawablePtr src, PixmapPtr slave_dst);
 extern _X_EXPORT Bool
 PixmapSyncDirtyHelper(PixmapDirtyUpdatePtr dirty);
 
+extern _X_EXPORT void
+PixmapDirtyCopyArea(PixmapPtr dst, DrawablePtr src,
+                    int x, int y, int dst_x, int dst_y,
+                    RegionPtr dirty_region);
+
 #endif                          /* PIXMAP_H */
diff --git a/xserver/present/present.h b/xserver/present/present.h
index d41b36033..1d7b0ce42 100644
--- a/xserver/present/present.h
+++ b/xserver/present/present.h
@@ -29,7 +29,9 @@
 
 typedef enum {
     PRESENT_FLIP_REASON_UNKNOWN,
-    PRESENT_FLIP_REASON_BUFFER_FORMAT
+    PRESENT_FLIP_REASON_BUFFER_FORMAT,
+    PRESENT_FLIP_REASON_DRIVER_TEARFREE,
+    PRESENT_FLIP_REASON_DRIVER_TEARFREE_FLIPPING
 } PresentFlipReason;
 
 typedef struct present_vblank present_vblank_rec, *present_vblank_ptr;
diff --git a/xserver/present/present_execute.c b/xserver/present/present_execute.c
index 68a5878be..5875e3c2a 100644
--- a/xserver/present/present_execute.c
+++ b/xserver/present/present_execute.c
@@ -87,7 +87,9 @@ present_execute_copy(present_vblank_ptr vblank, uint64_t crtc_msc)
     vblank->update = NULL;
     screen_priv->flush(window);
 
-    present_pixmap_idle(vblank->pixmap, vblank->window, vblank->serial, vblank->idle_fence);
+    /* The idle notification will be sent later if the driver uses TearFree */
+    if (!vblank->drv_tearfree)
+        present_pixmap_idle(vblank->pixmap, vblank->window, vblank->serial, vblank->idle_fence);
 }
 
 void
diff --git a/xserver/present/present_priv.h b/xserver/present/present_priv.h
index 6ebd009a2..367088e51 100644
--- a/xserver/present/present_priv.h
+++ b/xserver/present/present_priv.h
@@ -85,6 +85,7 @@ struct present_vblank {
     Bool                abort_flip;     /* aborting this flip */
     PresentFlipReason   reason;         /* reason for which flip is not possible */
     Bool                has_suboptimal; /* whether client can support SuboptimalCopy mode */
+    Bool                drv_tearfree;   /* whether the driver will present with a TearFree flip */
 };
 
 typedef struct present_screen_priv present_screen_priv_rec, *present_screen_priv_ptr;
diff --git a/xserver/present/present_scmd.c b/xserver/present/present_scmd.c
index 239055bc1..1fe6944d0 100644
--- a/xserver/present/present_scmd.c
+++ b/xserver/present/present_scmd.c
@@ -91,6 +91,19 @@ present_check_flip(RRCrtcPtr            crtc,
     if (!screen_priv->info->flip)
         return FALSE;
 
+    /* Ask the driver for permission. Do this now to see if there's TearFree. */
+    if (screen_priv->info->version >= 1 && screen_priv->info->check_flip2) {
+        if (!(*screen_priv->info->check_flip2) (crtc, window, pixmap, sync_flip, 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;
+        }
+    }
+
     /* Make sure the window hasn't been redirected with Composite */
     window_pixmap = screen->GetWindowPixmap(window);
     if (window_pixmap != screen->GetScreenPixmap(screen) &&
@@ -123,19 +136,6 @@ present_check_flip(RRCrtcPtr            crtc,
         return FALSE;
     }
 
-    /* Ask the driver for permission */
-    if (screen_priv->info->version >= 1 && screen_priv->info->check_flip2) {
-        if (!(*screen_priv->info->check_flip2) (crtc, window, pixmap, sync_flip, reason)) {
-            DebugPresent(("\td %08" PRIx32 " -> %08" PRIx32 "\n", window->drawable.id, pixmap ? pixmap->drawable.id : 0));
-            return FALSE;
-        }
-    } else if (screen_priv->info->check_flip) {
-        if (!(*screen_priv->info->check_flip) (crtc, window, pixmap, sync_flip)) {
-            DebugPresent(("\td %08" PRIx32 " -> %08" PRIx32 "\n", window->drawable.id, pixmap ? pixmap->drawable.id : 0));
-            return FALSE;
-        }
-    }
-
     return TRUE;
 }
 
@@ -541,6 +541,15 @@ present_execute(present_vblank_ptr vblank, uint64_t ust, uint64_t crtc_msc)
         screen_priv=present_screen_priv(vblank->crtc->pScreen);
     }
 
+    /* Send the idle notification for TearFree one vblank in advance. This is
+     * hit when TearFree happened to be flipping at the time the Present request
+     * was processed. The present_execute_wait() below will queue another vblank
+     * event at the next vblank which will mark the presentation's completion.
+     */
+    if (vblank->drv_tearfree && vblank->exec_msc == crtc_msc + 1)
+        present_pixmap_idle(vblank->pixmap, vblank->window, vblank->serial,
+                            vblank->idle_fence);
+
     if (present_execute_wait(vblank, crtc_msc))
         return;
 
@@ -625,7 +634,9 @@ present_execute(present_vblank_ptr vblank, uint64_t ust, uint64_t crtc_msc)
                 present_unflip(screen);
         }
 
-        present_execute_copy(vblank, crtc_msc);
+        /* The pixmap was copied earlier in the case of TearFree */
+        if (!vblank->drv_tearfree)
+            present_execute_copy(vblank, crtc_msc);
 
         if (vblank->queued) {
             xorg_list_add(&vblank->event_queue, &present_exec_queue);
@@ -773,10 +784,29 @@ present_scmd_pixmap(WindowPtr window,
     xorg_list_append(&vblank->event_queue, &present_exec_queue);
     vblank->queued = TRUE;
     if (msc_is_after(vblank->exec_msc, crtc_msc)) {
-        ret = present_queue_vblank(screen, window, target_crtc, vblank->event_id, vblank->exec_msc);
+        uint64_t exec_msc = vblank->exec_msc;
+
+        if (vblank->drv_tearfree) {
+            /* Copy the pixmap right now if the driver is using TearFree */
+            present_execute_copy(vblank, UINT64_MAX);
+
+            /* Send the idle notification one vblank before the presentation
+             * time. If the presentation time is the next vblank, send it now.
+             * Otherwise, the presentation time is the *next* next vblank, and
+             * so queue up a vblank event for the next vblank to send the idle
+             * notification.
+             */
+            if (exec_msc == crtc_msc + 1)
+                present_pixmap_idle(vblank->pixmap, vblank->window,
+                                    vblank->serial, vblank->idle_fence);
+            else
+                exec_msc--;
+        }
+        ret = present_queue_vblank(screen, window, target_crtc, vblank->event_id, exec_msc);
         if (ret == Success)
             return Success;
 
+        vblank->exec_msc = exec_msc;
         DebugPresent(("present_queue_vblank failed\n"));
     }
 
diff --git a/xserver/present/present_vblank.c b/xserver/present/present_vblank.c
index a9f17d4b2..6030850db 100644
--- a/xserver/present/present_vblank.c
+++ b/xserver/present/present_vblank.c
@@ -122,6 +122,17 @@ present_vblank_init(present_vblank_ptr vblank,
         }
     }
     vblank->reason = reason;
+    if (reason >= PRESENT_FLIP_REASON_DRIVER_TEARFREE) {
+        /* With TearFree, execution will occur at crtc_msc+1 at the soonest */
+        if (!msc_is_after(vblank->exec_msc, crtc_msc))
+            vblank->exec_msc = crtc_msc + 1;
+
+        /* The soonest execution is crtc_msc+2 if a TearFree flip is pending */
+        if (vblank->reason == PRESENT_FLIP_REASON_DRIVER_TEARFREE_FLIPPING &&
+            vblank->exec_msc == crtc_msc + 1)
+            vblank->exec_msc = crtc_msc + 2;
+        vblank->drv_tearfree = TRUE;
+    }
 
     if (wait_fence) {
         vblank->wait_fence = present_fence_create(wait_fence);
@@ -195,6 +206,7 @@ present_vblank_scrap(present_vblank_ptr vblank)
     vblank->pixmap = NULL;
     vblank->idle_fence = NULL;
     vblank->flip = FALSE;
+    vblank->drv_tearfree = FALSE;
 }
 
 void
-- 
2.40.0

Reply via email to