From: Matthieu Herrb Subject: Re: tearfree modesetting To: Theo Buehler Cc: tech@openbsd.org, Ted Unangst Date: Mon, 9 Jun 2025 18:55:28 +0200 On Mon, Jun 09, 2025 at 12:41:27PM +0200, Theo Buehler wrote: > On Mon, Jun 09, 2025 at 09:28:01AM +0200, Matthieu Herrb wrote: > > On Sat, Jun 07, 2025 at 09:29:48AM +0200, Matthieu Herrb wrote: > > > On Fri, Jun 06, 2025 at 05:11:50AM +0200, Theo Buehler wrote: > > > > On Thu, Jun 05, 2025 at 11:01:55PM -0400, Ted Unangst wrote: > > > > > This backports the x.org modesetting driver to get the tearfree option. > > > > > (On by default.) This makes scrolling much more enjoyable. > > > > > > > > > > I don't know what the x.org release cycle is, but this code has been > > > > > sitting upstream for years without making it into a released version. > > > > > > > > > > I wanted it, so here it is. > > > > > > > > This is pretty similar but not identical to naddy's patch from a few > > > > months ago. It would be really nice to have it: > > > > > > > > https://marc.info/?l=openbsd-tech&m=174118941524729&w=2 > > > > > > > > > > Hmm yes. With the current event in X.Org, a new release of the Xserver > > > main branch looks less and less likely in a foreseable future. I've > > > not had time to test the patches, but I'm ok on the principle. > > > > > > > I've looked closely at the different versions of the patch, > > the recent version by tedu@ is ok matthieu@. > > > > It reduces diffs with -current xorg a bit more and also avoids a useless > > increment of the driver ABI version (the corresponding changes are not > > in the tearfree patch). > > FWIW, it appears that tedu's diff breaks x11/sct. It works fine on > current and with naddy's diff. > I found the issue : https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/2011 Here's the updated diff. Ted, feel free to commit it, or should I ? Index: dix/pixmap.c =================================================================== RCS file: /cvs/OpenBSD/xenocara/xserver/dix/pixmap.c,v diff -u -p -u -r1.16 pixmap.c --- dix/pixmap.c 11 Nov 2021 09:03:03 -0000 1.16 +++ dix/pixmap.c 9 Jun 2025 16:52:22 -0000 @@ -262,12 +262,11 @@ PixmapStopDirtyTracking(DrawablePtr src, 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; @@ -293,10 +292,15 @@ PixmapDirtyCopyArea(PixmapPtr dst, w = dst_box.x2 - dst_box.x1; 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); + (void) pGC->ops->CopyArea(src, + &dst->drawable, + pGC, + 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 +412,8 @@ Bool PixmapSyncDirtyHelper(PixmapDirtyUp 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; Index: hw/xfree86/common/xf86Mode.c =================================================================== RCS file: /cvs/OpenBSD/xenocara/xserver/hw/xfree86/common/xf86Mode.c,v diff -u -p -u -r1.16 xf86Mode.c --- hw/xfree86/common/xf86Mode.c 7 Apr 2024 11:42:56 -0000 1.16 +++ hw/xfree86/common/xf86Mode.c 9 Jun 2025 16:52:22 -0000 @@ -230,6 +230,8 @@ xf86ModeStatusToString(ModeStatus status return "monitor doesn't support reduced blanking"; case MODE_BANDWIDTH: return "mode requires too much memory bandwidth"; + case MODE_DUPLICATE: + return "the same mode has been added"; case MODE_BAD: return "unknown reason"; case MODE_ERROR: Index: hw/xfree86/drivers/modesetting/dri2.c =================================================================== RCS file: /cvs/OpenBSD/xenocara/xserver/hw/xfree86/drivers/modesetting/dri2.c,v diff -u -p -u -r1.9 dri2.c --- hw/xfree86/drivers/modesetting/dri2.c 20 Feb 2022 17:41:35 -0000 1.9 +++ hw/xfree86/drivers/modesetting/dri2.c 9 Jun 2025 16:52:22 -0000 @@ -32,11 +32,11 @@ * This doesn't implement pageflipping yet. */ -#ifdef HAVE_DIX_CONFIG_H #include "dix-config.h" -#endif +#include #include + #include "list.h" #include "xf86.h" #include "driver.h" @@ -206,7 +206,7 @@ ms_dri2_create_buffer2(ScreenPtr screen, if (buffer->name == -1) { xf86DrvMsg(scrn->scrnIndex, X_ERROR, "Failed to get DRI2 name for pixmap\n"); - screen->DestroyPixmap(pixmap); + dixDestroyPixmap(pixmap, 0); free(private); free(buffer); return NULL; @@ -245,8 +245,7 @@ static void ms_dri2_destroy_buffer2(Scre if (buffer->driverPrivate) { ms_dri2_buffer_private_ptr private = buffer->driverPrivate; if (--private->refcnt == 0) { - ScreenPtr screen = private->pixmap->drawable.pScreen; - screen->DestroyPixmap(private->pixmap); + dixDestroyPixmap(private->pixmap, 0); free(private); free(buffer); } @@ -316,7 +315,7 @@ ms_dri2_copy_region2(ScreenPtr screen, D * callback chain so we know that will happen before the client * tries to render again. */ - gc->ops->CopyArea(src, dst, gc, + (void) gc->ops->CopyArea(src, dst, gc, 0, 0, drawable->width, drawable->height, off_x, off_y); @@ -483,7 +482,6 @@ ms_dri2_schedule_flip(ms_dri2_frame_even 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 +493,7 @@ ms_dri2_schedule_flip(ms_dri2_frame_even 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")) { @@ -522,7 +520,7 @@ update_front(DrawablePtr draw, DRI2Buffe front->name = name; - (*screen->DestroyPixmap) (priv->pixmap); + dixDestroyPixmap(priv->pixmap, 0); front->pitch = pixmap->devKind; front->cpp = pixmap->drawable.bitsPerPixel / 8; priv->pixmap = pixmap; @@ -547,10 +545,8 @@ can_exchange(ScrnInfoPtr scrn, DrawableP drmmode_crtc_private_ptr drmmode_crtc = config->crtc[i]->driver_private; /* Don't do pageflipping if CRTCs are rotated. */ -#ifdef GLAMOR_HAS_GBM if (drmmode_crtc->rotate_bo.gbm) return FALSE; -#endif if (xf86_crtc_on(config->crtc[i])) num_crtcs_on++; Index: hw/xfree86/drivers/modesetting/driver.c =================================================================== RCS file: /cvs/OpenBSD/xenocara/xserver/hw/xfree86/drivers/modesetting/driver.c,v diff -u -p -u -r1.13 driver.c --- hw/xfree86/drivers/modesetting/driver.c 2 Mar 2025 09:09:28 -0000 1.13 +++ hw/xfree86/drivers/modesetting/driver.c 9 Jun 2025 16:52:22 -0000 @@ -32,12 +32,14 @@ * */ -#ifdef HAVE_DIX_CONFIG_H #include "dix-config.h" -#endif +#include #include #include +#include +#include + #include "xf86.h" #include "xf86Priv.h" #include "xf86_OSproc.h" @@ -46,7 +48,6 @@ #include "mipointer.h" #include "mipointrst.h" #include "micmap.h" -#include #include "fb.h" #include "edid.h" #include "xf86i2c.h" @@ -54,7 +55,6 @@ #include "miscstruct.h" #include "dixstruct.h" #include "xf86xv.h" -#include #include #ifdef XSERVER_PLATFORM_BUS #include "xf86platformBus.h" @@ -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} }; @@ -159,21 +160,23 @@ int ms_entity_index = -1; static MODULESETUPPROTO(Setup); static XF86ModuleVersionInfo VersRec = { - "modesetting", - MODULEVENDORSTRING, - MODINFOSTRING1, - MODINFOSTRING2, - XORG_VERSION_CURRENT, - XORG_VERSION_MAJOR, - XORG_VERSION_MINOR, - XORG_VERSION_PATCH, - ABI_CLASS_VIDEODRV, - ABI_VIDEODRV_VERSION, - MOD_CLASS_VIDEODRV, - {0, 0, 0, 0} + .modname = "modesetting", + .vendor = MODULEVENDORSTRING, + ._modinfo1_ = MODINFOSTRING1, + ._modinfo2_ = MODINFOSTRING2, + .xf86version = XORG_VERSION_CURRENT, + .majorversion = XORG_VERSION_MAJOR, + .minorversion = XORG_VERSION_MINOR, + .patchlevel = XORG_VERSION_PATCH, + .abiclass = ABI_CLASS_VIDEODRV, + .abiversion = ABI_VIDEODRV_VERSION, + .moduleclass = MOD_CLASS_VIDEODRV, }; -_X_EXPORT XF86ModuleData modesettingModuleData = { &VersRec, Setup, NULL }; +_X_EXPORT XF86ModuleData modesettingModuleData = { + .vers = &VersRec, + .setup = Setup +}; static void * Setup(void *module, void *opts, int *errmaj, int *errmin) @@ -397,7 +400,7 @@ ms_setup_entity(ScrnInfoPtr scrn, int en xf86SetEntityInstanceForScreen(scrn, entity_num, xf86GetNumEntityInstances(entity_num) - 1); if (!pPriv->ptr) - pPriv->ptr = xnfcalloc(sizeof(modesettingEntRec), 1); + pPriv->ptr = XNFcallocarray(1, sizeof(modesettingEntRec)); } #ifdef XSERVER_LIBPCIACCESS @@ -516,81 +519,207 @@ GetRec(ScrnInfoPtr pScrn) if (pScrn->driverPrivate) return TRUE; - pScrn->driverPrivate = xnfcalloc(sizeof(modesettingRec), 1); + pScrn->driverPrivate = XNFcallocarray(1, sizeof(modesettingRec)); return TRUE; } -static void -rotate_clip(PixmapPtr pixmap, BoxPtr rect, drmModeClip *clip, Rotation rotation) +static int +rotate_clip(PixmapPtr pixmap, xf86CrtcPtr crtc, BoxPtr rect, drmModeClip *clip, + Rotation rotation, int x, int y) { - int w = pixmap->drawable.width; - int h = pixmap->drawable.height; + int w, h; + int x1, y1, x2, y2; - if (rotation == RR_Rotate_90) { - /* Rotate 90 degrees counter clockwise */ - clip->x1 = rect->y1; - clip->x2 = rect->y2; - clip->y1 = w - rect->x2; - clip->y2 = w - rect->x1; - } else if (rotation == RR_Rotate_180) { - /* Rotate 180 degrees */ - clip->x1 = w - rect->x2; - clip->x2 = w - rect->x1; - clip->y1 = h - rect->y2; - clip->y2 = h - rect->y1; - } else if (rotation == RR_Rotate_270) { - /* Rotate 90 degrees clockwise */ - clip->x1 = h - rect->y2; - clip->x2 = h - rect->y1; - clip->y1 = rect->x1; - clip->y2 = rect->x2; + if (rotation == RR_Rotate_90 || rotation == RR_Rotate_270) { + /* width and height are swapped if rotated 90 or 270 degrees */ + w = pixmap->drawable.height; + h = pixmap->drawable.width; } else { - clip->x1 = rect->x1; - clip->x2 = rect->x2; - clip->y1 = rect->y1; - clip->y2 = rect->y2; + w = pixmap->drawable.width; + h = pixmap->drawable.height; } + + /* check if the given rect covers any area in FB of the crtc */ + if (rect->x2 > crtc->x && rect->x1 < crtc->x + w && + rect->y2 > crtc->y && rect->y1 < crtc->y + h) { + /* new coordinate of the partial rect on the crtc area + * + x/y offsets in the framebuffer */ + x1 = max(rect->x1 - crtc->x, 0) + x; + y1 = max(rect->y1 - crtc->y, 0) + y; + x2 = min(rect->x2 - crtc->x, w) + x; + y2 = min(rect->y2 - crtc->y, h) + y; + + /* coordinate transposing/inversion and offset adjustment */ + if (rotation == RR_Rotate_90) { + clip->x1 = y1; + clip->y1 = w - x2; + clip->x2 = y2; + clip->y2 = w - x1; + } else if (rotation == RR_Rotate_180) { + clip->x1 = w - x2; + clip->y1 = h - y2; + clip->x2 = w - x1; + clip->y2 = h - y1; + } else if (rotation == RR_Rotate_270) { + clip->x1 = h - y2; + clip->y1 = x1; + clip->x2 = h - y1;; + clip->y2 = x2; + } else { + clip->x1 = x1; + clip->y1 = y1; + clip->x2 = x2; + clip->y2 = y2; + } + } else { + return -1; + } + + return 0; } 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, int x, int y) { 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); int i; + int c = 0; if (!clip) return -ENOMEM; - /* Rotate and copy rects into clips */ - for (i = 0; i < num_cliprects; i++, rect++) - rotate_clip(pixmap, rect, &clip[i], crtc->rotation); + /* Create clips for the given rects in case the rect covers any + * area in the FB. + */ + for (i = 0; i < num_cliprects; i++, rect++) { + if (rotate_clip(pixmap, crtc, rect, &clip[c], crtc->rotation, x, y) < 0) + continue; + + c++; + } + + if (!c) + return 0; /* TODO query connector property to see if this is needed */ - ret = drmModeDirtyFB(ms->fd, fb_id, clip, num_cliprects); + ret = drmModeDirtyFB(ms->fd, fb_id, clip, c); /* if we're swamping it with work, try one at a time */ if (ret == -EINVAL) { - for (i = 0; i < num_cliprects; i++) { + for (i = 0; i < c; i++) { if ((ret = drmModeDirtyFB(ms->fd, fb_id, &clip[i], 1)) < 0) break; } } + 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); } return ret; } +static int +dispatch_dirty_region(ScrnInfoPtr scrn, xf86CrtcPtr crtc, + PixmapPtr pixmap, DamagePtr damage, + int fb_id, int x, int y) +{ + return dispatch_damages(scrn, crtc, DamageRegion(damage), + pixmap, damage, fb_id, x, y); +} + +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, 0, 0); + } + } + 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, 0, 0); + RegionEmpty(&trf->buf[trf->back_idx ^ 1].dmg); + } + } +#endif +} + static void dispatch_dirty(ScreenPtr pScreen) { @@ -603,6 +732,8 @@ dispatch_dirty(ScreenPtr pScreen) for (c = 0; c < xf86_config->num_crtc; c++) { xf86CrtcPtr crtc = xf86_config->crtc[c]; + PixmapPtr pmap; + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; if (!drmmode_crtc) @@ -610,14 +741,16 @@ dispatch_dirty(ScreenPtr pScreen) drmmode_crtc_get_fb_id(crtc, &fb_id, &x, &y); - ret = dispatch_dirty_region(scrn, crtc, pixmap, ms->damage, fb_id); + if (crtc->rotatedPixmap) + pmap = crtc->rotatedPixmap; + else + pmap = pixmap; + + ret = dispatch_dirty_region(scrn, crtc, pmap, ms->damage, fb_id, x, y); if (ret == -EINVAL || ret == -ENOSYS) { - ms->dirty_enabled = FALSE; DamageUnregister(ms->damage); DamageDestroy(ms->damage); ms->damage = NULL; - xf86DrvMsg(scrn->scrnIndex, X_INFO, - "Disabling kernel dirty updates, not required.\n"); return; } } @@ -631,7 +764,7 @@ dispatch_dirty_pixmap(ScrnInfoPtr scrn, DamagePtr damage = ppriv->secondary_damage; int fb_id = ppriv->fb_id; - dispatch_dirty_region(scrn, crtc, ppix, damage, fb_id); + dispatch_dirty_region(scrn, crtc, ppix, damage, fb_id, 0, 0); } static void @@ -748,10 +881,13 @@ msBlockHandler(ScreenPtr pScreen, void * 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 @@ -893,7 +1029,7 @@ ms_unwrap_property_requests(ScrnInfoPtr } static void -FreeRec(ScrnInfoPtr pScrn) +FreeScreen(ScrnInfoPtr pScrn) { modesettingPtr ms; @@ -1011,16 +1147,21 @@ msShouldDoubleShadow(ScrnInfoPtr pScrn, { Bool ret = FALSE, asked; int from; - drmVersionPtr v = drmGetVersion(ms->fd); + drmVersionPtr v; if (!ms->drmmode.shadow_enable) return FALSE; - if (!strcmp(v->name, "mgag200") || - !strcmp(v->name, "ast")) /* XXX || rn50 */ - ret = TRUE; + if ((v = drmGetVersion(ms->fd))) { + if (!strcmp(v->name, "mgag200") || + !strcmp(v->name, "ast")) /* XXX || rn50 */ + ret = TRUE; - drmFreeVersion(v); + drmFreeVersion(v); + } + else + xf86DrvMsg(pScrn->scrnIndex, X_ERROR, + "Failed to query DRM version.\n"); asked = xf86GetOptValBool(ms->drmmode.Options, OPTION_DOUBLE_SHADOW, &ret); @@ -1203,15 +1344,15 @@ PreInit(ScrnInfoPtr pScrn, int flags) ms->drmmode.sw_cursor = TRUE; } - ms->cursor_width = 64; - ms->cursor_height = 64; + ms->max_cursor_width = 64; + ms->max_cursor_height = 64; ret = drmGetCap(ms->fd, DRM_CAP_CURSOR_WIDTH, &value); if (!ret) { - ms->cursor_width = value; + ms->max_cursor_width = value; } ret = drmGetCap(ms->fd, DRM_CAP_CURSOR_HEIGHT, &value); if (!ret) { - ms->cursor_height = value; + ms->max_cursor_height = value; } try_enable_glamor(pScrn); @@ -1283,9 +1424,34 @@ PreInit(ScrnInfoPtr pScrn, int flags) 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); @@ -1449,7 +1615,6 @@ msEnableSharedPixmapFlipping(RRCrtcPtr c ScreenPtr screen = crtc->pScreen; ScrnInfoPtr scrn = xf86ScreenToScrn(screen); modesettingPtr ms = modesettingPTR(scrn); - EntityInfoPtr pEnt = ms->pEnt; xf86CrtcPtr xf86Crtc = crtc->devPrivate; if (!xf86Crtc) @@ -1464,9 +1629,9 @@ msEnableSharedPixmapFlipping(RRCrtcPtr c return FALSE; #ifdef XSERVER_PLATFORM_BUS - if (pEnt->location.type == BUS_PLATFORM) { - char *syspath = - xf86_platform_device_odev_attributes(pEnt->location.id.plat)-> + if (ms->pEnt->location.type == BUS_PLATFORM) { + const char *syspath = + xf86_platform_device_odev_attributes(ms->pEnt->location.id.plat)-> syspath; /* Not supported for devices using USB transport due to misbehaved @@ -1634,13 +1799,13 @@ CreateScreenResources(ScreenPtr pScreen) err = drmModeDirtyFB(ms->fd, ms->drmmode.fb_id, NULL, 0); - if (err != -EINVAL && err != -ENOSYS) { + if ((err != -EINVAL && err != -ENOSYS) || ms->drmmode.tearfree_enable) { ms->damage = DamageCreate(NULL, NULL, DamageReportNone, TRUE, pScreen, rootPixmap); if (ms->damage) { DamageRegister(&rootPixmap->drawable, ms->damage); - ms->dirty_enabled = TRUE; + ms->dirty_enabled = err != -EINVAL && err != -ENOSYS; xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Damage tracking initialized\n"); } else { @@ -1921,7 +2086,7 @@ ScreenInit(ScreenPtr pScreen, int argc, /* Need to extend HWcursor support to handle mask interleave */ if (!ms->drmmode.sw_cursor) - xf86_cursors_init(pScreen, ms->cursor_width, ms->cursor_height, + xf86_cursors_init(pScreen, ms->max_cursor_width, ms->max_cursor_height, HARDWARE_CURSOR_SOURCE_MASK_INTERLEAVE_64 | HARDWARE_CURSOR_UPDATE_UNHIDDEN | HARDWARE_CURSOR_ARGB); @@ -2053,12 +2218,6 @@ AdjustFrame(ScrnInfoPtr pScrn, int x, in modesettingPtr ms = modesettingPTR(pScrn); drmmode_adjust_frame(pScrn, &ms->drmmode, x, y); -} - -static void -FreeScreen(ScrnInfoPtr pScrn) -{ - FreeRec(pScrn); } static void Index: hw/xfree86/drivers/modesetting/driver.h =================================================================== RCS file: /cvs/OpenBSD/xenocara/xserver/hw/xfree86/drivers/modesetting/driver.h,v diff -u -p -u -r1.9 driver.h --- hw/xfree86/drivers/modesetting/driver.h 11 Nov 2021 09:03:06 -0000 1.9 +++ hw/xfree86/drivers/modesetting/driver.h 9 Jun 2025 16:52:22 -0000 @@ -61,6 +61,7 @@ typedef enum { OPTION_VARIABLE_REFRESH, OPTION_USE_GAMMA_LUT, OPTION_ASYNC_FLIP_SECONDARIES, + OPTION_TEARFREE, } modesettingOpts; typedef struct @@ -86,10 +87,13 @@ struct ms_drm_queue { struct xorg_list list; xf86CrtcPtr crtc; uint32_t seq; + uint64_t msc; void *data; ScrnInfoPtr scrn; ms_drm_handler_proc handler; ms_drm_abort_proc abort; + Bool kernel_queued; + Bool aborted; }; typedef struct _modesettingRec { @@ -125,7 +129,8 @@ typedef struct _modesettingRec { DamagePtr damage; Bool dirty_enabled; - uint32_t cursor_width, cursor_height; + uint32_t min_cursor_width, min_cursor_height; + uint32_t max_cursor_width, max_cursor_height; Bool has_queue_sequence; Bool tried_queue_sequence; @@ -202,6 +207,8 @@ void ms_drm_abort(ScrnInfoPtr scrn, 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 +239,26 @@ typedef void (*ms_pageflip_abort_proc)(m 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); Index: hw/xfree86/drivers/modesetting/drmmode_display.c =================================================================== RCS file: /cvs/OpenBSD/xenocara/xserver/hw/xfree86/drivers/modesetting/drmmode_display.c,v diff -u -p -u -r1.14 drmmode_display.c --- hw/xfree86/drivers/modesetting/drmmode_display.c 2 Mar 2025 09:09:28 -0000 1.14 +++ hw/xfree86/drivers/modesetting/drmmode_display.c 9 Jun 2025 16:52:22 -0000 @@ -27,16 +27,11 @@ * */ -#ifdef HAVE_DIX_CONFIG_H #include "dix-config.h" -#endif #include #include #include -#if defined(CONFIG_KEVENT_KMS) -#include -#endif #include #include "dumb_bo.h" #include "inputstr.h" @@ -66,9 +61,9 @@ static PixmapPtr drmmode_create_pixmap_h void *pPixData); static const struct drm_color_ctm ctm_identity = { { - 1UL << 32, 0, 0, - 0, 1UL << 32, 0, - 0, 0, 1UL << 32 + 1ULL << 32, 0, 0, + 0, 1ULL << 32, 0, + 0, 0, 1ULL << 32 } }; static Bool ctm_is_identity(const struct drm_color_ctm *ctm) @@ -522,13 +517,13 @@ connector_add_prop(drmModeAtomicReq *req } static int -drmmode_CompareKModes(drmModeModeInfo * kmode, drmModeModeInfo * other) +drmmode_CompareKModes(const drmModeModeInfo * kmode, const drmModeModeInfo * other) { return memcmp(kmode, other, sizeof(*kmode)); } static int -drm_mode_ensure_blob(xf86CrtcPtr crtc, drmModeModeInfo mode_info) +drm_mode_ensure_blob(xf86CrtcPtr crtc, const drmModeModeInfo* mode_info) { modesettingPtr ms = modesettingPTR(crtc->scrn); drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; @@ -536,14 +531,14 @@ drm_mode_ensure_blob(xf86CrtcPtr crtc, d int ret; if (drmmode_crtc->current_mode && - drmmode_CompareKModes(&drmmode_crtc->current_mode->mode_info, &mode_info) == 0) + drmmode_CompareKModes(&drmmode_crtc->current_mode->mode_info, mode_info) == 0) return 0; - mode = calloc(sizeof(drmmode_mode_rec), 1); + mode = calloc(1, sizeof(drmmode_mode_rec)); if (!mode) return -1; - mode->mode_info = mode_info; + mode->mode_info = *mode_info; ret = drmModeCreatePropertyBlob(ms->fd, &mode->mode_info, sizeof(mode->mode_info), @@ -592,7 +587,7 @@ crtc_add_dpms_props(drmModeAtomicReq *re drmModeModeInfo kmode; drmmode_ConvertToKMode(crtc->scrn, &kmode, &crtc->mode); - ret |= drm_mode_ensure_blob(crtc, kmode); + ret |= drm_mode_ensure_blob(crtc, &kmode); ret |= crtc_add_prop(req, drmmode_crtc, DRMMODE_CRTC_ACTIVE, 1); @@ -635,6 +630,7 @@ drmmode_crtc_get_fb_id(xf86CrtcPtr crtc, { 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; @@ -649,6 +645,10 @@ drmmode_crtc_get_fb_id(xf86CrtcPtr crtc, *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; @@ -902,7 +902,7 @@ drmmode_crtc_set_mode(xf86CrtcPtr crtc, return ret; } - output_ids = calloc(sizeof(uint32_t), xf86_config->num_output); + output_ids = calloc(xf86_config->num_output, sizeof(uint32_t)); if (!output_ids) return -1; @@ -925,6 +925,10 @@ drmmode_crtc_set_mode(xf86CrtcPtr crtc, 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); @@ -933,7 +937,8 @@ drmmode_crtc_set_mode(xf86CrtcPtr crtc, } 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; @@ -945,7 +950,7 @@ drmmode_crtc_flip(xf86CrtcPtr crtc, uint 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); @@ -953,6 +958,26 @@ drmmode_crtc_flip(xf86CrtcPtr crtc, uint 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); } @@ -1538,19 +1563,122 @@ drmmode_copy_fb(ScrnInfoPtr pScrn, drmmo gc = GetScratchGC(pScrn->depth, pScreen); ValidateGC(&dst->drawable, gc); - (*gc->ops->CopyArea)(&src->drawable, &dst->drawable, gc, 0, 0, + (void) (*gc->ops->CopyArea)(&src->drawable, &dst->drawable, gc, 0, 0, pScrn->virtualX, pScrn->virtualY, 0, 0); FreeScratchGC(gc); pScreen->canDoBGNoneRoot = TRUE; - if (drmmode->fbcon_pixmap) - pScrn->pScreen->DestroyPixmap(drmmode->fbcon_pixmap); + dixDestroyPixmap(drmmode->fbcon_pixmap, 0); drmmode->fbcon_pixmap = NULL; #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) @@ -1566,6 +1694,9 @@ drmmode_set_mode_major(xf86CrtcPtr crtc, Bool can_test; int i; + if (mode) + drmmmode_prepare_modeset(crtc->scrn); + saved_mode = crtc->mode; saved_x = crtc->x; saved_y = crtc->y; @@ -1584,6 +1715,10 @@ drmmode_set_mode_major(xf86CrtcPtr crtc, 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 +1764,7 @@ drmmode_set_mode_major(xf86CrtcPtr crtc, crtc->y = saved_y; crtc->rotation = saved_rotation; crtc->mode = saved_mode; + drmmode_create_tearfree_shadow(crtc); } else crtc->active = TRUE; @@ -1651,12 +1787,11 @@ drmmode_set_cursor_position(xf86CrtcPtr } static Bool -drmmode_set_cursor(xf86CrtcPtr crtc) +drmmode_set_cursor(xf86CrtcPtr crtc, int width, int height) { drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; drmmode_ptr drmmode = drmmode_crtc->drmmode; uint32_t handle = drmmode_crtc->cursor_bo->handle; - modesettingPtr ms = modesettingPTR(crtc->scrn); CursorPtr cursor = xf86CurrentCursor(crtc->scrn->pScreen); int ret = -EINVAL; @@ -1664,14 +1799,14 @@ drmmode_set_cursor(xf86CrtcPtr crtc) return TRUE; ret = drmModeSetCursor2(drmmode->fd, drmmode_crtc->mode_crtc->crtc_id, - handle, ms->cursor_width, ms->cursor_height, + handle, width, height, cursor->bits->xhot, cursor->bits->yhot); /* -EINVAL can mean that an old kernel supports drmModeSetCursor but * not drmModeSetCursor2, though it can mean other things too. */ if (ret == -EINVAL) ret = drmModeSetCursor(drmmode->fd, drmmode_crtc->mode_crtc->crtc_id, - handle, ms->cursor_width, ms->cursor_height); + handle, width, height); /* -ENXIO normally means that the current drm driver supports neither * cursor_set nor cursor_set2. Disable hardware cursor support for @@ -1687,6 +1822,10 @@ drmmode_set_cursor(xf86CrtcPtr crtc) if (ret) /* fallback to swcursor */ return FALSE; + + drmmode_crtc->cursor_width = width; + drmmode_crtc->cursor_height = height; + return TRUE; } @@ -1703,31 +1842,55 @@ static Bool drmmode_load_cursor_argb_check(xf86CrtcPtr crtc, CARD32 *image) { modesettingPtr ms = modesettingPTR(crtc->scrn); + CursorPtr cursor = xf86CurrentCursor(crtc->scrn->pScreen); drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; - int i; + int width, height, x, y, i; uint32_t *ptr; /* cursor should be mapped already */ ptr = (uint32_t *) (drmmode_crtc->cursor_bo->ptr); - for (i = 0; i < ms->cursor_width * ms->cursor_height; i++) - ptr[i] = image[i]; // cpu_to_le32(image[i]); + /* FIXME deal with rotation */ + if (crtc->rotation == RR_Rotate_0) { + for (width = ms->min_cursor_width; width < cursor->bits->width; ) + width *= 2; + for (height = ms->min_cursor_height; height < cursor->bits->height; ) + height *= 2; + + /* assume only square works for now */ + width = height = max(width, height); + + /* if the max limits aren't square+POT we may have gone a bit over */ + width = min(width, ms->max_cursor_width); + height = min(height, ms->max_cursor_height); + } else { + width = ms->max_cursor_width; + height = ms->max_cursor_height; + } + + i = 0; + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) + ptr[i++] = image[y * ms->max_cursor_width + x]; // cpu_to_le32(image[i]); + } + /* clear the remainder for good measure */ + for (; i < ms->max_cursor_width * ms->max_cursor_height; i++) + ptr[i++] = 0; if (drmmode_crtc->cursor_up) - return drmmode_set_cursor(crtc); + return drmmode_set_cursor(crtc, width, height); return TRUE; } static void drmmode_hide_cursor(xf86CrtcPtr crtc) { - modesettingPtr ms = modesettingPTR(crtc->scrn); drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; drmmode_ptr drmmode = drmmode_crtc->drmmode; drmmode_crtc->cursor_up = FALSE; drmModeSetCursor(drmmode->fd, drmmode_crtc->mode_crtc->crtc_id, 0, - ms->cursor_width, ms->cursor_height); + drmmode_crtc->cursor_width, drmmode_crtc->cursor_height); } static Bool @@ -1735,7 +1898,7 @@ drmmode_show_cursor(xf86CrtcPtr crtc) { drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; drmmode_crtc->cursor_up = TRUE; - return drmmode_set_cursor(crtc); + return drmmode_set_cursor(crtc, drmmode_crtc->cursor_width, drmmode_crtc->cursor_height); } static void @@ -1747,8 +1910,9 @@ drmmode_set_gamma_lut(drmmode_crtc_priva drmmode_prop_info_ptr gamma_lut_info = &drmmode_crtc->props[DRMMODE_CRTC_GAMMA_LUT]; const uint32_t crtc_id = drmmode_crtc->mode_crtc->crtc_id; - uint32_t blob_id; - struct drm_color_lut lut[size]; + struct drm_color_lut *lut = calloc(size, sizeof(struct drm_color_lut)); + if (!lut) + return; assert(gamma_lut_info->prop_id != 0); @@ -1756,15 +1920,21 @@ drmmode_set_gamma_lut(drmmode_crtc_priva lut[i].red = red[i]; lut[i].green = green[i]; lut[i].blue = blue[i]; + lut[i].reserved = 0; } - if (drmModeCreatePropertyBlob(drmmode->fd, lut, sizeof(lut), &blob_id)) + uint32_t blob_id; + if (drmModeCreatePropertyBlob(drmmode->fd, lut, + sizeof(struct drm_color_lut)*size, &blob_id)) { + free(lut); return; + } drmModeObjectSetProperty(drmmode->fd, crtc_id, DRM_MODE_OBJECT_CRTC, gamma_lut_info->prop_id, blob_id); drmModeDestroyPropertyBlob(drmmode->fd, blob_id); + free(lut); } static void @@ -1934,33 +2104,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 @@ -1977,7 +2156,7 @@ drmmode_create_pixmap_header(ScreenPtr p if ((*pScreen->ModifyPixmapHeader)(pixmap, width, height, depth, bitsPerPixel, devKind, pPixData)) return pixmap; - (*pScreen->DestroyPixmap)(pixmap); + dixDestroyPixmap(pixmap, 0); } return NullPixmap; } @@ -1986,71 +2165,90 @@ 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); - } + dixDestroyPixmap(pixmap, 0); 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; @@ -2298,8 +2496,8 @@ drmmode_crtc_create_planes(xf86CrtcPtr c drmmode_crtc->plane_id = best_plane; if (best_kplane) { drmmode_crtc->num_formats = best_kplane->count_formats; - drmmode_crtc->formats = calloc(sizeof(drmmode_format_rec), - best_kplane->count_formats); + drmmode_crtc->formats = calloc(best_kplane->count_formats, + sizeof(drmmode_format_rec)); if (!populate_format_modifiers(crtc, best_kplane, blob_id)) { for (i = 0; i < best_kplane->count_formats; i++) drmmode_crtc->formats[i].format = best_kplane->formats[i]; @@ -2376,13 +2574,15 @@ drmmode_crtc_init(ScrnInfoPtr pScrn, drm crtc = xf86CrtcCreate(pScrn, &drmmode_crtc_funcs); if (crtc == NULL) return 0; - drmmode_crtc = xnfcalloc(sizeof(drmmode_crtc_private_rec), 1); + drmmode_crtc = XNFcallocarray(1, sizeof(drmmode_crtc_private_rec)); crtc->driver_private = drmmode_crtc; drmmode_crtc->mode_crtc = drmModeGetCrtc(drmmode->fd, mode_res->crtcs[num]); 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); @@ -2644,7 +2844,7 @@ static DisplayModePtr drmmode_output_add_gtf_modes(xf86OutputPtr output, DisplayModePtr Modes) { xf86MonPtr mon = output->MonInfo; - DisplayModePtr i, m, preferred = NULL; + DisplayModePtr i, j, m, preferred = NULL; int max_x = 0, max_y = 0; float max_vrefresh = 0.0; @@ -2676,6 +2876,17 @@ drmmode_output_add_gtf_modes(xf86OutputP i->VDisplay >= preferred->VDisplay && xf86ModeVRefresh(i) >= xf86ModeVRefresh(preferred)) i->status = MODE_VSYNC; + if (preferred && xf86ModeVRefresh(i) > 0.0) { + i->Clock = i->Clock * xf86ModeVRefresh(preferred) / xf86ModeVRefresh(i); + i->VRefresh = xf86ModeVRefresh(preferred); + } + for (j = m; j != i; j = j->next) { + if (!strcmp(i->name, j->name) && + xf86ModeVRefresh(i) * (1 + SYNC_TOLERANCE) >= xf86ModeVRefresh(j) && + xf86ModeVRefresh(i) * (1 - SYNC_TOLERANCE) <= xf86ModeVRefresh(j)) { + i->status = MODE_DUPLICATE; + } + } } xf86PruneInvalidModes(output->scrn, &m, FALSE); @@ -2714,7 +2925,7 @@ drmmode_output_get_modes(xf86OutputPtr o /* modes should already be available */ for (i = 0; i < koutput->count_modes; i++) { - Mode = xnfalloc(sizeof(DisplayModeRec)); + Mode = XNFalloc(sizeof(DisplayModeRec)); drmmode_ConvertFromKMode(output->scrn, &koutput->modes[i], Mode); Modes = xf86ModesAdd(Modes, Mode); @@ -3232,7 +3443,7 @@ drmmode_output_init(ScrnInfoPtr pScrn, d } } - kencoders = calloc(sizeof(drmModeEncoderPtr), koutput->count_encoders); + kencoders = calloc(koutput->count_encoders, sizeof(drmModeEncoderPtr)); if (!kencoders) { goto out_free_encoders; } @@ -3261,7 +3472,7 @@ drmmode_output_init(ScrnInfoPtr pScrn, d goto out_free_encoders; } - drmmode_output = calloc(sizeof(drmmode_output_private_rec), 1); + drmmode_output = calloc(1, sizeof(drmmode_output_private_rec)); if (!drmmode_output) { xf86OutputDestroy(output); goto out_free_encoders; @@ -3758,6 +3969,8 @@ drmmode_set_desired_modes(ScrnInfoPtr pS 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; @@ -3920,13 +4133,13 @@ drmmode_crtc_upgrade_lut(xf86CrtcPtr crt crtc->gamma_blue = gamma + size * 2; xf86DrvMsgVerb(pScrn->scrnIndex, X_INFO, MS_LOGLEVEL_DEBUG, - "Gamma ramp set to %ld entries on CRTC %d\n", - size, num); + "Gamma ramp set to %lld entries on CRTC %d\n", + (long long)size, num); } else { xf86DrvMsg(pScrn->scrnIndex, X_ERROR, - "Failed to allocate memory for %ld gamma ramp entries " + "Failed to allocate memory for %lld gamma ramp entries " "on CRTC %d.\n", - size, num); + (long long)size, num); return FALSE; } } @@ -4088,7 +4301,7 @@ out: #undef DRM_MODE_LINK_STATUS_BAD #undef DRM_MODE_LINK_STATUS_GOOD -#if defined(CONFIG_UDEV_KMS) || defined(CONFIG_KEVENT_KMS) +#ifdef CONFIG_UDEV_KMS static void drmmode_handle_uevents(int fd, void *closure) @@ -4097,17 +4310,10 @@ drmmode_handle_uevents(int fd, void *clo struct udev_device *dev; Bool found = FALSE; -#ifdef CONFIG_UDEV_KMS while ((dev = udev_monitor_receive_device(drmmode->uevent_monitor))) { udev_device_unref(dev); found = TRUE; } -#else - struct kevent ev; - if ((kevent(fd, NULL, 0, &ev, 1, NULL)) && ev.fflags & NOTE_CHANGE) - found = TRUE; -#endif - if (!found) return; @@ -4146,23 +4352,6 @@ drmmode_uevent_init(ScrnInfoPtr scrn, dr drmmode_handle_uevents, drmmode); drmmode->uevent_monitor = mon; -#elif CONFIG_KEVENT_KMS - int kq; - struct kevent ev; - - if (drmmode->kevent_handler) - return; - - if ((kq = kqueue()) <= 0) - return; - - EV_SET(&ev, drmmode->fd, EVFILT_DEVICE, EV_ADD | EV_ENABLE | EV_CLEAR, - NOTE_CHANGE, 0, NULL); - if (kevent(kq, &ev, 1, NULL, 0, NULL) < 0) - return; - - drmmode->kevent_handler = xf86AddGeneralHandler(kq, - drmmode_handle_uevents, drmmode); #endif } @@ -4178,15 +4367,56 @@ drmmode_uevent_fini(ScrnInfoPtr scrn, dr udev_monitor_unref(drmmode->uevent_monitor); udev_unref(u); } -#elif CONFIG_KEVENT_KMS - int kq; +#endif +} + +static void drmmode_probe_cursor_size(xf86CrtcPtr crtc) +{ + modesettingPtr ms = modesettingPTR(crtc->scrn); + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + uint32_t handle = drmmode_crtc->cursor_bo->handle; + drmmode_ptr drmmode = drmmode_crtc->drmmode; + int width, height, size; + + ms->min_cursor_width = ms->max_cursor_width; + ms->min_cursor_height = ms->max_cursor_height; - if (drmmode->kevent_handler) { - kq = xf86RemoveGeneralHandler(drmmode->kevent_handler); - close(kq); - drmmode->kevent_handler = NULL; + /* probe square min first */ + for (size = 1; size <= ms->max_cursor_width && + size <= ms->max_cursor_height; size *= 2) { + int ret; + + ret = drmModeSetCursor2(drmmode->fd, drmmode_crtc->mode_crtc->crtc_id, + handle, size, size, 0, 0); + if (ret == 0) + break; } -#endif + + /* check if smaller width works with non-square */ + for (width = 1; width <= size; width *= 2) { + int ret; + + ret = drmModeSetCursor2(drmmode->fd, drmmode_crtc->mode_crtc->crtc_id, + handle, width, size, 0, 0); + if (ret == 0) { + ms->min_cursor_width = width; + break; + } + } + + /* check if smaller height works with non-square */ + for (height = 1; height <= size; height *= 2) { + int ret; + + ret = drmModeSetCursor2(drmmode->fd, drmmode_crtc->mode_crtc->crtc_id, + handle, size, height, 0, 0); + if (ret == 0) { + ms->min_cursor_height = height; + break; + } + } + + drmModeSetCursor2(drmmode->fd, drmmode_crtc->mode_crtc->crtc_id, 0, 0, 0, 0, 0); } /* create front and cursor BOs */ @@ -4208,8 +4438,8 @@ drmmode_create_initial_bos(ScrnInfoPtr p return FALSE; pScrn->displayWidth = drmmode_bo_get_pitch(&drmmode->front_bo) / cpp; - width = ms->cursor_width; - height = ms->cursor_height; + width = ms->max_cursor_width; + height = ms->max_cursor_height; bpp = 32; for (i = 0; i < xf86_config->num_crtc; i++) { xf86CrtcPtr crtc = xf86_config->crtc[i]; @@ -4218,6 +4448,14 @@ drmmode_create_initial_bos(ScrnInfoPtr p drmmode_crtc->cursor_bo = dumb_bo_create(drmmode->fd, width, height, bpp); } + + drmmode_probe_cursor_size(xf86_config->crtc[0]); + + xf86DrvMsgVerb(pScrn->scrnIndex, X_INFO, MS_LOGLEVEL_DEBUG, + "Supported cursor sizes %dx%d -> %dx%d\n", + ms->min_cursor_width, ms->min_cursor_height, + ms->max_cursor_width, ms->max_cursor_height); + return TRUE; } @@ -4277,6 +4515,7 @@ drmmode_free_bos(ScrnInfoPtr pScrn, drmm drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; dumb_bo_destroy(drmmode->fd, drmmode_crtc->cursor_bo); + drmmode_destroy_tearfree_shadow(crtc); } } Index: hw/xfree86/drivers/modesetting/drmmode_display.h =================================================================== RCS file: /cvs/OpenBSD/xenocara/xserver/hw/xfree86/drivers/modesetting/drmmode_display.h,v diff -u -p -u -r1.9 drmmode_display.h --- hw/xfree86/drivers/modesetting/drmmode_display.h 2 Mar 2025 09:09:28 -0000 1.9 +++ hw/xfree86/drivers/modesetting/drmmode_display.h 9 Jun 2025 16:52:22 -0000 @@ -95,13 +95,10 @@ typedef struct { struct gbm_device *gbm; -#if defined(CONFIG_UDEV_KMS) +#ifdef CONFIG_UDEV_KMS struct udev_monitor *uevent_monitor; InputHandlerProc uevent_handler; #endif -#if defined(CONFIG_KEVENT_KMS) - InputHandlerProc kevent_handler; -#endif drmEventContext event_context; drmmode_bo front_bo; Bool sw_cursor; @@ -138,9 +135,12 @@ typedef struct { 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 { @@ -170,6 +170,20 @@ typedef struct { } 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; @@ -187,11 +201,14 @@ typedef struct { drmmode_bo rotate_bo; unsigned rotate_fb_id; + drmmode_tearfree_rec tearfree; PixmapPtr prime_pixmap; PixmapPtr prime_pixmap_back; unsigned prime_pixmap_x; + int src_x, src_y; + /** * @{ MSC (vblank count) handling for the PRESENT extension. * @@ -203,6 +220,10 @@ typedef struct { uint64_t msc_high; /** @} */ + uint64_t next_msc; + + int cursor_width, cursor_height; + Bool need_modeset; struct xorg_list mode_list; @@ -311,8 +332,11 @@ void drmmode_get_default_bpp(ScrnInfoPtr 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); Index: hw/xfree86/drivers/modesetting/dumb_bo.c =================================================================== RCS file: /cvs/OpenBSD/xenocara/xserver/hw/xfree86/drivers/modesetting/dumb_bo.c,v diff -u -p -u -r1.1 dumb_bo.c --- hw/xfree86/drivers/modesetting/dumb_bo.c 16 Sep 2015 19:10:22 -0000 1.1 +++ hw/xfree86/drivers/modesetting/dumb_bo.c 9 Jun 2025 16:52:22 -0000 @@ -25,9 +25,7 @@ * */ -#ifdef HAVE_DIX_CONFIG_H #include "dix-config.h" -#endif #include "dumb_bo.h" Index: hw/xfree86/drivers/modesetting/meson.build =================================================================== RCS file: /cvs/OpenBSD/xenocara/xserver/hw/xfree86/drivers/modesetting/meson.build,v diff -u -p -u -r1.1 meson.build --- hw/xfree86/drivers/modesetting/meson.build 22 Jan 2023 09:21:09 -0000 1.1 +++ hw/xfree86/drivers/modesetting/meson.build 9 Jun 2025 16:52:22 -0000 @@ -27,10 +27,11 @@ shared_module( ) # Test that we don't have any unresolved symbols from our module to Xorg. -xorg_build_root = join_paths(meson.build_root(), 'hw', 'xfree86') +xorg_build_root = join_paths(meson.project_build_root(), 'hw', 'xfree86') symbol_test_args = [] symbol_test_args += join_paths(xorg_build_root, 'libxorgserver.so') symbol_test_args += join_paths(xorg_build_root, 'dixmods', 'libshadow.so') +symbol_test_args += join_paths(xorg_build_root, 'dixmods', 'libglx.so') if gbm_dep.found() symbol_test_args += join_paths(xorg_build_root, 'glamor_egl', 'libglamoregl.so') endif Index: hw/xfree86/drivers/modesetting/modesetting.man =================================================================== RCS file: /cvs/OpenBSD/xenocara/xserver/hw/xfree86/drivers/modesetting/modesetting.man,v diff -u -p -u -r1.4 modesetting.man --- hw/xfree86/drivers/modesetting/modesetting.man 11 Nov 2021 09:03:06 -0000 1.4 +++ hw/xfree86/drivers/modesetting/modesetting.man 9 Jun 2025 16:52:22 -0000 @@ -22,16 +22,17 @@ If glamor is not enabled, a shadow frame KMS drivers' preference (unless the framebuffer is 24 bits per pixel, in which case the shadow framebuffer is always used). .SH SUPPORTED HARDWARE -The +The .B modesetting driver supports all hardware where a KMS driver is available. modesetting uses the Linux DRM KMS ioctls and dumb object create/map. .SH CONFIGURATION DETAILS -Please refer to @xconfigfile@(@filemansuffix@) for general configuration -details. This section only covers configuration details specific to -this driver. +Please refer to +.BR @xconfigfile@ (@filemansuffix@) +for general configuration details. +This section only covers configuration details specific to this driver. .PP -For this driver it is not required to specify modes in the screen +For this driver it is not required to specify modes in the screen section of the config file. The .B modesetting driver can pick up the currently used video mode from the kernel @@ -40,7 +41,7 @@ driver and will use it if there are no v For PCI boards you might have to add a BusID line to the Device section. See above for a sample line. .PP -The following driver +The following driver .B Options are supported: .TP @@ -109,8 +110,25 @@ When enabled, this option allows the dri 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 +.BI "Option \*qAtomic\*q \*q" boolean \*q +Enable atomic modesetting when supported. The default is +.B off. +.TP .SH "SEE ALSO" -@xservername@(@appmansuffix@), @xconfigfile@(@filemansuffix@), Xserver(@appmansuffix@), -X(@miscmansuffix@) +.BR @xservername@ (@appmansuffix@), +.BR @xconfigfile@ (@filemansuffix@), +.BR Xserver (@appmansuffix@), +.BR X (@miscmansuffix@) .SH AUTHORS Authors include: Dave Airlie Index: hw/xfree86/drivers/modesetting/pageflip.c =================================================================== RCS file: /cvs/OpenBSD/xenocara/xserver/hw/xfree86/drivers/modesetting/pageflip.c,v diff -u -p -u -r1.4 pageflip.c --- hw/xfree86/drivers/modesetting/pageflip.c 11 Nov 2021 09:03:06 -0000 1.4 +++ hw/xfree86/drivers/modesetting/pageflip.c 9 Jun 2025 16:52:22 -0000 @@ -20,11 +20,12 @@ * OF THIS SOFTWARE. */ -#ifdef HAVE_DIX_CONFIG_H #include "dix-config.h" -#endif + +#include #include + #include #include "driver.h" @@ -35,8 +36,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 +46,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 +64,19 @@ ms_flush_drm_events(ScreenPtr screen) 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 +107,8 @@ struct ms_crtc_pageflip { Bool on_reference_crtc; /* reference to the ms_flipdata */ struct ms_flipdata *flipdata; + struct xorg_list node; + uint32_t tearfree_seq; }; /** @@ -136,7 +152,8 @@ ms_pageflip_handler(uint64_t msc, uint64 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 +177,32 @@ ms_pageflip_abort(void *data) } static Bool -do_queue_flip_on_crtc(modesettingPtr ms, xf86CrtcPtr crtc, - uint32_t flags, uint32_t seq) +do_queue_flip_on_crtc(ScreenPtr screen, xf86CrtcPtr crtc, uint32_t flags, + uint32_t seq, uint32_t fb_id, int x, int y) { - return drmmode_crtc_flip(crtc, ms->drmmode.fb_id, flags, - (void *) (uintptr_t) seq); + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + drmmode_tearfree_ptr trf = &drmmode_crtc->tearfree; + + while (drmmode_crtc_flip(crtc, fb_id, x, y, flags, (void *)(long)seq)) { + /* We may have failed because the event queue was full. Flush it + * and retry. If there was nothing to flush, then we failed for + * some other reason and should just return an error. + */ + if (ms_flush_drm_events(screen) <= 0) { + /* The failure could be caused by a pending TearFree flip, in which + * case we should wait until there's a new event and try again. + */ + if (!trf->flip_seq || ms_flush_drm_events_timeout(screen, -1) < 0) { + ms_drm_abort_seq(crtc->scrn, seq); + return TRUE; + } + } + + /* We flushed some events, so try again. */ + xf86DrvMsg(crtc->scrn->scrnIndex, X_WARNING, "flip queue retry\n"); + } + + return FALSE; } enum queue_flip_status { @@ -177,11 +215,10 @@ enum queue_flip_status { 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 +230,7 @@ queue_flip_on_crtc(ScreenPtr screen, xf8 /* 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,20 +242,9 @@ queue_flip_on_crtc(ScreenPtr screen, xf8 /* take a reference on flipdata for use in flip */ flipdata->flip_count++; - while (do_queue_flip_on_crtc(ms, crtc, flags, seq)) { - /* We may have failed because the event queue was full. Flush it - * and retry. If there was nothing to flush, then we failed for - * some other reason and should just return an error. - */ - if (ms_flush_drm_events(screen) <= 0) { - /* Aborting will also decrement flip_count and free(flip). */ - ms_drm_abort_seq(scrn, seq); - return QUEUE_FLIP_DRM_FLUSH_FAILED; - } - - /* We flushed some events, so try again. */ - xf86DrvMsg(scrn->scrnIndex, X_WARNING, "flip queue retry\n"); - } + if (do_queue_flip_on_crtc(screen, crtc, flags, seq, ms->drmmode.fb_id, + crtc->x, crtc->y)) + return QUEUE_FLIP_DRM_FLUSH_FAILED; /* The page flip succeeded. */ return QUEUE_FLIP_SUCCESS; @@ -294,20 +320,75 @@ ms_print_pageflip_error(int screen_index } } +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 +396,22 @@ ms_do_pageflip(ScreenPtr screen, 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 +421,7 @@ ms_do_pageflip(ScreenPtr screen, 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 +429,7 @@ ms_do_pageflip(ScreenPtr screen, 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 +477,6 @@ ms_do_pageflip(ScreenPtr screen, 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 +497,11 @@ ms_do_pageflip(ScreenPtr screen, * 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 +550,150 @@ error_out: 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; +} + +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; -#endif /* GLAMOR_HAS_GBM */ } +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); +} Index: hw/xfree86/drivers/modesetting/present.c =================================================================== RCS file: /cvs/OpenBSD/xenocara/xserver/hw/xfree86/drivers/modesetting/present.c,v diff -u -p -u -r1.10 present.c --- hw/xfree86/drivers/modesetting/present.c 2 Mar 2025 09:09:28 -0000 1.10 +++ hw/xfree86/drivers/modesetting/present.c 9 Jun 2025 16:52:22 -0000 @@ -20,9 +20,7 @@ * OF THIS SOFTWARE. */ -#ifdef HAVE_DIX_CONFIG_H #include "dix-config.h" -#endif #include #include @@ -127,7 +125,7 @@ ms_present_queue_vblank(RRCrtcPtr crtc, struct ms_present_vblank_event *event; uint32_t seq; - event = calloc(sizeof(struct ms_present_vblank_event), 1); + event = calloc(1, sizeof(struct ms_present_vblank_event)); if (!event) return BadAlloc; event->event_id = event_id; @@ -165,6 +163,13 @@ ms_present_abort_vblank(RRCrtcPtr crtc, { 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 +323,35 @@ ms_present_check_flip(RRCrtcPtr crtc, 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 +369,12 @@ ms_present_flip(RRCrtcPtr crtc, 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 +387,12 @@ ms_present_flip(RRCrtcPtr crtc, 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 +404,7 @@ ms_present_flip(RRCrtcPtr crtc, 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 +437,7 @@ ms_present_unflip(ScreenPtr screen, uint 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; Index: hw/xfree86/drivers/modesetting/vblank.c =================================================================== RCS file: /cvs/OpenBSD/xenocara/xserver/hw/xfree86/drivers/modesetting/vblank.c,v diff -u -p -u -r1.8 vblank.c --- hw/xfree86/drivers/modesetting/vblank.c 11 Nov 2021 09:03:06 -0000 1.8 +++ hw/xfree86/drivers/modesetting/vblank.c 9 Jun 2025 16:52:22 -0000 @@ -25,11 +25,11 @@ * Support for tracking the DRM's vblank events. */ -#ifdef HAVE_DIX_CONFIG_H #include "dix-config.h" -#endif +#include #include + #include #include #include "driver.h" @@ -260,6 +260,51 @@ ms_get_kernel_ust_msc(xf86CrtcPtr crtc, } } +static void +ms_drm_set_seq_msc(uint32_t seq, uint64_t msc) +{ + struct ms_drm_queue *q; + + xorg_list_for_each_entry(q, &ms_drm_queue, list) { + if (q->seq == seq) { + q->msc = msc; + break; + } + } +} + +static void +ms_drm_set_seq_queued(uint32_t seq, uint64_t msc) +{ + drmmode_crtc_private_ptr drmmode_crtc; + struct ms_drm_queue *q; + + xorg_list_for_each_entry(q, &ms_drm_queue, list) { + if (q->seq == seq) { + drmmode_crtc = q->crtc->driver_private; + if (msc < drmmode_crtc->next_msc) + drmmode_crtc->next_msc = msc; + q->msc = msc; + q->kernel_queued = TRUE; + break; + } + } +} + +static Bool +ms_queue_coalesce(xf86CrtcPtr crtc, uint32_t seq, uint64_t msc) +{ + drmmode_crtc_private_ptr drmmode_crtc = crtc->driver_private; + + /* If the next MSC is too late, then this event can't be coalesced */ + if (msc < drmmode_crtc->next_msc) + return FALSE; + + /* Set the target MSC on this sequence number */ + ms_drm_set_seq_msc(seq, msc); + return TRUE; +} + Bool ms_queue_vblank(xf86CrtcPtr crtc, ms_queue_flag flags, uint64_t msc, uint64_t *msc_queued, uint32_t seq) @@ -271,6 +316,10 @@ ms_queue_vblank(xf86CrtcPtr crtc, ms_que drmVBlank vbl; int ret; + /* Try coalescing this event into another to avoid event queue exhaustion */ + if (flags == MS_QUEUE_ABSOLUTE && ms_queue_coalesce(crtc, seq, msc)) + return TRUE; + for (;;) { /* Queue an event at the specified sequence */ if (ms->has_queue_sequence || !ms->tried_queue_sequence) { @@ -287,8 +336,10 @@ ms_queue_vblank(xf86CrtcPtr crtc, ms_que ret = drmCrtcQueueSequence(ms->fd, drmmode_crtc->mode_crtc->crtc_id, drm_flags, msc, &kernel_queued, seq); if (ret == 0) { + msc = ms_kernel_msc_to_crtc_msc(crtc, kernel_queued, TRUE); + ms_drm_set_seq_queued(seq, msc); if (msc_queued) - *msc_queued = ms_kernel_msc_to_crtc_msc(crtc, kernel_queued, TRUE); + *msc_queued = msc; ms->has_queue_sequence = TRUE; return TRUE; } @@ -310,8 +361,10 @@ ms_queue_vblank(xf86CrtcPtr crtc, ms_que vbl.request.signal = seq; ret = drmWaitVBlank(ms->fd, &vbl); if (ret == 0) { + msc = ms_kernel_msc_to_crtc_msc(crtc, vbl.reply.sequence, FALSE); + ms_drm_set_seq_queued(seq, msc); if (msc_queued) - *msc_queued = ms_kernel_msc_to_crtc_msc(crtc, vbl.reply.sequence, FALSE); + *msc_queued = msc; return TRUE; } check: @@ -418,13 +471,15 @@ ms_drm_queue_alloc(xf86CrtcPtr crtc, if (!ms_drm_seq) ++ms_drm_seq; q->seq = ms_drm_seq++; + q->msc = UINT64_MAX; q->scrn = scrn; q->crtc = crtc; q->data = data; q->handler = handler; q->abort = abort; - xorg_list_add(&q->list, &ms_drm_queue); + /* Keep the list formatted in ascending order of sequence number */ + xorg_list_append(&q->list, &ms_drm_queue); return q->seq; } @@ -437,9 +492,18 @@ ms_drm_queue_alloc(xf86CrtcPtr crtc, static void ms_drm_abort_one(struct ms_drm_queue *q) { + if (q->aborted) + return; + + /* Don't remove vblank events if they were queued in the kernel */ + if (q->kernel_queued) { + q->abort(q->data); + q->aborted = TRUE; + } else { xorg_list_del(&q->list); q->abort(q->data); free(q); + } } /** @@ -500,16 +564,64 @@ ms_drm_sequence_handler(int fd, uint64_t { 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); + + /* 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; - msc = ms_kernel_msc_to_crtc_msc(q->crtc, frame, is64bit); + /* 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); } } } @@ -528,6 +640,12 @@ ms_drm_handler(int fd, uint32_t frame, u /* 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 Index: hw/xfree86/modes/xf86Crtc.h =================================================================== RCS file: /cvs/OpenBSD/xenocara/xserver/hw/xfree86/modes/xf86Crtc.h,v diff -u -p -u -r1.16 xf86Crtc.h --- hw/xfree86/modes/xf86Crtc.h 6 Dec 2021 19:41:55 -0000 1.16 +++ hw/xfree86/modes/xf86Crtc.h 9 Jun 2025 16:52:22 -0000 @@ -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. Index: hw/xfree86/modes/xf86Rotate.c =================================================================== RCS file: /cvs/OpenBSD/xenocara/xserver/hw/xfree86/modes/xf86Rotate.c,v diff -u -p -u -r1.21 xf86Rotate.c --- hw/xfree86/modes/xf86Rotate.c 11 Nov 2021 09:03:08 -0000 1.21 +++ hw/xfree86/modes/xf86Rotate.c 9 Jun 2025 16:52:23 -0000 @@ -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 return; src = CreatePicture(None, - &root->drawable, + src_drawable, format, CPSubwindowMode, &include_inferiors, serverClient, &error); @@ -70,9 +70,11 @@ xf86RotateCrtcRedisplay(xf86CrtcPtr crtc 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); } Index: include/displaymode.h =================================================================== RCS file: /cvs/OpenBSD/xenocara/xserver/include/displaymode.h,v diff -u -p -u -r1.1 displaymode.h --- include/displaymode.h 29 May 2016 12:02:39 -0000 1.1 +++ include/displaymode.h 9 Jun 2025 16:52:25 -0000 @@ -47,6 +47,7 @@ typedef enum { MODE_ONE_SIZE, /* only one resolution is supported */ MODE_NO_REDUCED, /* monitor doesn't accept reduced blanking */ MODE_BANDWIDTH, /* mode requires too much memory bandwidth */ + MODE_DUPLICATE, /* mode is duplicated */ MODE_BAD = -2, /* unspecified reason */ MODE_ERROR = -1 /* error condition */ } ModeStatus; Index: include/pixmap.h =================================================================== RCS file: /cvs/OpenBSD/xenocara/xserver/include/pixmap.h,v diff -u -p -u -r1.12 pixmap.h --- include/pixmap.h 11 Nov 2021 09:03:13 -0000 1.12 +++ include/pixmap.h 9 Jun 2025 16:52:25 -0000 @@ -134,4 +134,9 @@ PixmapStopDirtyTracking(DrawablePtr src, 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 */ Index: present/present.h =================================================================== RCS file: /cvs/OpenBSD/xenocara/xserver/present/present.h,v diff -u -p -u -r1.4 present.h --- present/present.h 11 Nov 2021 09:03:14 -0000 1.4 +++ present/present.h 9 Jun 2025 16:52:25 -0000 @@ -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 @@ present_event_notify(uint64_t event_id, 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, Index: present/present_screen.c =================================================================== RCS file: /cvs/OpenBSD/xenocara/xserver/present/present_screen.c,v diff -u -p -u -r1.7 present_screen.c --- present/present_screen.c 29 Oct 2023 16:45:39 -0000 1.7 +++ present/present_screen.c 9 Jun 2025 16:52:25 -0000 @@ -191,6 +191,26 @@ present_screen_priv_init(ScreenPtr scree 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) */