From a4ab1b04594ff64702831d010925dc20afdd6128 Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Wed, 24 Aug 2022 19:53:44 +0000 Subject: [PATCH] drm, steamcompmgr: Allow async flips if supported with GAMESCOPE_ALLOW_TEARING Reworks some logic re. frame pacing and presentation to flip directly when an app asks when this is enabled. The next step for us would be to make this respect whether the app has vsync enabled or not using the tearing protocol too. This is a good starting point though. --- src/drm.cpp | 15 +++++++++++- src/drm.hpp | 4 +++- src/steamcompmgr.cpp | 56 +++++++++++++++++++++++++++++++++++--------- src/xwayland_ctx.hpp | 2 ++ 4 files changed, 64 insertions(+), 13 deletions(-) diff --git a/src/drm.cpp b/src/drm.cpp index fce6052..a528e2a 100644 --- a/src/drm.cpp +++ b/src/drm.cpp @@ -41,6 +41,12 @@ bool g_bUseLayers = true; bool g_bDebugLayers = false; const char *g_sOutputName = nullptr; +#ifndef DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP +#define DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP 0x15 +#endif + +bool g_bSupportsAsyncFlips = false; + enum drm_mode_generation g_drmModeGeneration = DRM_MODE_GENERATE_CVT; static LogScope drm_log("drm"); @@ -800,6 +806,10 @@ bool init_drm(struct drm_t *drm, int width, int height, int refresh) drm->allow_modifiers = true; } + g_bSupportsAsyncFlips = drmGetCap(drm->fd, DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP, &cap) == 0 && cap != 0; + if (!g_bSupportsAsyncFlips) + drm_log.errorf("Immediate flips are not supported by the KMS driver"); + if (!get_resources(drm)) { return false; } @@ -1536,7 +1546,7 @@ drm_prepare_liftoff( struct drm_t *drm, const struct FrameInfo_t *frameInfo, boo /* Prepares an atomic commit for the provided scene-graph. Returns 0 on success, * negative errno on failure or if the scene-graph can't be presented directly. */ -int drm_prepare( struct drm_t *drm, const struct FrameInfo_t *frameInfo ) +int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameInfo ) { drm->pending.screen_type = drm_get_screen_type(drm); @@ -1556,6 +1566,9 @@ int drm_prepare( struct drm_t *drm, const struct FrameInfo_t *frameInfo ) // We do internal refcounting with these events flags |= DRM_MODE_PAGE_FLIP_EVENT; + if ( async ) + flags |= DRM_MODE_PAGE_FLIP_ASYNC; + if ( needs_modeset ) { flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; diff --git a/src/drm.hpp b/src/drm.hpp index 7cf2710..a722c13 100644 --- a/src/drm.hpp +++ b/src/drm.hpp @@ -197,7 +197,7 @@ extern enum drm_mode_generation g_drmModeGeneration; bool init_drm(struct drm_t *drm, int width, int height, int refresh); void finish_drm(struct drm_t *drm); int drm_commit(struct drm_t *drm, const struct FrameInfo_t *frameInfo ); -int drm_prepare( struct drm_t *drm, const struct FrameInfo_t *frameInfo ); +int drm_prepare( struct drm_t *drm, bool async, const struct FrameInfo_t *frameInfo ); void drm_rollback( struct drm_t *drm ); bool drm_poll_state(struct drm_t *drm); uint32_t drm_fbid_from_dmabuf( struct drm_t *drm, struct wlr_buffer *buf, struct wlr_dmabuf_attributes *dma_buf ); @@ -221,3 +221,5 @@ drm_screen_type drm_get_screen_type(struct drm_t *drm); char *find_drm_node_by_devid(dev_t devid); int drm_get_default_refresh(struct drm_t *drm); + +extern bool g_bSupportsAsyncFlips; diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index 9f4e327..c01c937 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -208,6 +208,8 @@ struct commit_t #define MWM_TEAROFF_WINDOW 1 +static bool g_bAsyncFlipsEnabled = false; + struct motif_hints_t { unsigned long flags; @@ -307,6 +309,7 @@ uint32_t lastPublishedInputCounter; bool focusDirty = false; bool hasRepaint = false; +bool hasRepaintNonBasePlane = false; unsigned long damageSequence = 0; @@ -1569,7 +1572,7 @@ static void update_touch_scaling( const struct FrameInfo_t *frameInfo ) } static void -paint_all() +paint_all(bool async) { gamescope_xwayland_server_t *root_server = wlserver_get_xwayland_server(0); xwayland_ctx_t *root_ctx = root_server->ctx.get(); @@ -1823,7 +1826,7 @@ paint_all() if ( !bNeedsComposite ) { - int ret = drm_prepare( &g_DRM, &frameInfo ); + int ret = drm_prepare( &g_DRM, async, &frameInfo ); if ( ret == 0 ) bDoComposite = false; else if ( ret == -EACCES ) @@ -1877,7 +1880,7 @@ paint_all() layer->linearFilter = false; - int ret = drm_prepare( &g_DRM, &frameInfo ); + int ret = drm_prepare( &g_DRM, async, &frameInfo ); // Happens when we're VT-switched away if ( ret == -EACCES ) @@ -1896,7 +1899,7 @@ paint_all() drm_rollback( &g_DRM ); // Try once again to in case we need to fall back to another mode. - ret = drm_prepare( &g_DRM, &frameInfo ); + ret = drm_prepare( &g_DRM, async, &frameInfo ); // Happens when we're VT-switched away if ( ret == -EACCES ) @@ -3753,7 +3756,7 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) if ( gameFocused && ( w == ctx->focus.overlayWindow || w == ctx->focus.notificationWindow ) ) { - hasRepaint = true; + hasRepaintNonBasePlane = true; } if ( w == ctx->focus.externalOverlayWindow ) { @@ -4181,6 +4184,10 @@ handle_property_notify(xwayland_ctx_t *ctx, XPropertyEvent *ev) g_bIsCompositeDebug = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeCompositeDebug, 0 ); hasRepaint = true; } + if ( ev->atom == ctx->atoms.gamescopeAllowTearing ) + { + g_bAsyncFlipsEnabled = !!get_prop( ctx, ctx->root, ctx->atoms.gamescopeAllowTearing, 0 ); + } if (ev->atom == ctx->atoms.wineHwndStyle) { win * w = find_win(ctx, ev->window); @@ -4416,12 +4423,12 @@ void handle_done_commits( xwayland_ctx_t *ctx ) { if ( w == global_focus.overlayWindow && w->opacity != TRANSLUCENT ) { - hasRepaint = true; + hasRepaintNonBasePlane = true; } if ( w == global_focus.notificationWindow && w->opacity != TRANSLUCENT ) { - hasRepaint = true; + hasRepaintNonBasePlane = true; } } if ( ctx->focus.outdatedInteractiveFocus ) @@ -4432,7 +4439,7 @@ void handle_done_commits( xwayland_ctx_t *ctx ) // If this is an external overlay, repaint if ( w == ctx->focus.externalOverlayWindow && w->opacity != TRANSLUCENT ) { - hasRepaint = true; + hasRepaintNonBasePlane = true; } // If this is the main plane, repaint if ( w == global_focus.focusWindow && !w->isSteamStreamingClient ) @@ -4443,7 +4450,7 @@ void handle_done_commits( xwayland_ctx_t *ctx ) if ( w == global_focus.overrideWindow ) { - hasRepaint = true; + hasRepaintNonBasePlane = true; } if ( w->isSteamStreamingClientVideo && global_focus.focusWindow && global_focus.focusWindow->isSteamStreamingClient ) @@ -5094,6 +5101,8 @@ void init_xwayland_ctx(gamescope_xwayland_server_t *xwayland_server) ctx->atoms.gamescopeCompositeForce = XInternAtom( ctx->dpy, "GAMESCOPE_COMPOSITE_FORCE", false ); ctx->atoms.gamescopeCompositeDebug = XInternAtom( ctx->dpy, "GAMESCOPE_COMPOSITE_DEBUG", false ); + ctx->atoms.gamescopeAllowTearing = XInternAtom( ctx->dpy, "GAMESCOPE_ALLOW_TEARING", false ); + ctx->atoms.wineHwndStyle = XInternAtom( ctx->dpy, "_WINE_HWND_STYLE", false ); ctx->atoms.wineHwndStyleEx = XInternAtom( ctx->dpy, "_WINE_HWND_EXSTYLE", false ); @@ -5424,12 +5433,37 @@ steamcompmgr_main(int argc, char **argv) if (focusDirty) determine_and_apply_focus(); - if ( ( g_bTakeScreenshot == true || hasRepaint == true || is_fading_out() ) && vblank == true ) + static int nMissedOverlayPaints = 0; + + const bool bForceSyncFlip = g_bTakeScreenshot || is_fading_out(); + // If we are compositing, always force sync flips because we currently wait + // for composition to finish before submitting. + // If we want to do async + composite, we should set up syncfile stuff and have DRM wait on it. + const bool bNeedsSyncFlip = bForceSyncFlip || g_bCurrentlyCompositing || nMissedOverlayPaints; + const bool bDoAsyncFlip = g_bAsyncFlipsEnabled && g_bSupportsAsyncFlips && !bNeedsSyncFlip; + + bool bShouldPaint = false; + if ( bDoAsyncFlip ) { - paint_all(); + if ( hasRepaint && !g_bCurrentlyCompositing ) + bShouldPaint = true; + } + else + { + bShouldPaint = vblank && ( hasRepaint || hasRepaintNonBasePlane || bForceSyncFlip ); + } + + if ( !bShouldPaint && hasRepaintNonBasePlane && vblank ) + nMissedOverlayPaints++; + + if ( bShouldPaint ) + { + paint_all( !vblank ); // Consumed the need to repaint here hasRepaint = false; + hasRepaintNonBasePlane = false; + nMissedOverlayPaints = 0; // If we're in the middle of a fade, pump an event into the loop to // make sure we keep pushing frames even if the app isn't updating. diff --git a/src/xwayland_ctx.hpp b/src/xwayland_ctx.hpp index 0053923..badc64e 100644 --- a/src/xwayland_ctx.hpp +++ b/src/xwayland_ctx.hpp @@ -146,6 +146,8 @@ struct xwayland_ctx_t Atom gamescopeCompositeForce; Atom gamescopeCompositeDebug; + Atom gamescopeAllowTearing; + Atom wineHwndStyle; Atom wineHwndStyleEx; } atoms;