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;