diff --git a/src/pipewire.cpp b/src/pipewire.cpp index 013a9b4b68..bff610e07b 100644 --- a/src/pipewire.cpp +++ b/src/pipewire.cpp @@ -56,6 +56,11 @@ static void destroy_buffer(struct pipewire_buffer *buffer) { delete buffer; } +void pipewire_destroy_buffer(struct pipewire_buffer *buffer) +{ + destroy_buffer(buffer); +} + static void calculate_capture_size() { s_nCaptureWidth = s_nOutputWidth; @@ -575,8 +580,6 @@ static void stream_handle_remove_buffer(void *data, struct pw_buffer *pw_buffer) if (!buffer->copying) { destroy_buffer(buffer); - } else { - nudge_pipewire(); } } @@ -724,6 +727,12 @@ uint32_t get_pipewire_stream_node_id(void) return pipewire_state.stream_node_id; } +bool pipewire_is_streaming() +{ + struct pipewire_state *state = &pipewire_state; + return state->streaming; +} + struct pipewire_buffer *dequeue_pipewire_buffer(void) { struct pipewire_state *state = &pipewire_state; diff --git a/src/pipewire.hpp b/src/pipewire.hpp index f423a46af4..b1cf5166ad 100644 --- a/src/pipewire.hpp +++ b/src/pipewire.hpp @@ -45,7 +45,11 @@ struct pipewire_buffer { // The following fields are not thread-safe // The PipeWire buffer, or nullptr if it's been destroyed. - struct pw_buffer *buffer; + std::atomic buffer; + bool IsStale() const + { + return buffer == nullptr; + } // We pass the buffer to the steamcompmgr thread for copying. This is set // to true if the buffer is currently owned by the steamcompmgr thread. bool copying; @@ -54,5 +58,7 @@ struct pipewire_buffer { bool init_pipewire(void); uint32_t get_pipewire_stream_node_id(void); struct pipewire_buffer *dequeue_pipewire_buffer(void); +bool pipewire_is_streaming(); +void pipewire_destroy_buffer(struct pipewire_buffer *buffer); void push_pipewire_buffer(struct pipewire_buffer *buffer); void nudge_pipewire(void); diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index 0754bb17c7..f980c37c2f 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -1274,7 +1274,7 @@ import_commit ( steamcompmgr_win_t *w, struct wlr_surface *surf, struct wlr_buff } static int32_t -window_last_done_commit_id( steamcompmgr_win_t *w ) +window_last_done_commit_index( steamcompmgr_win_t *w ) { int32_t lastCommit = -1; for ( uint32_t i = 0; i < w->commit_queue.size(); i++ ) @@ -1291,13 +1291,13 @@ window_last_done_commit_id( steamcompmgr_win_t *w ) static bool window_has_commits( steamcompmgr_win_t *w ) { - return window_last_done_commit_id( w ) != -1; + return window_last_done_commit_index( w ) != -1; } static void get_window_last_done_commit( steamcompmgr_win_t *w, gamescope::Rc &commit ) { - int32_t lastCommit = window_last_done_commit_id( w ); + int32_t lastCommit = window_last_done_commit_index( w ); if ( lastCommit == -1 ) { @@ -1311,7 +1311,7 @@ get_window_last_done_commit( steamcompmgr_win_t *w, gamescope::Rc &com static commit_t* get_window_last_done_commit_peek( steamcompmgr_win_t *w ) { - int32_t lastCommit = window_last_done_commit_id( w ); + int32_t lastCommit = window_last_done_commit_index( w ); if ( lastCommit == -1 ) { @@ -1321,6 +1321,19 @@ get_window_last_done_commit_peek( steamcompmgr_win_t *w ) return w->commit_queue[ lastCommit ].get(); } +static int64_t +window_last_done_commit_id( steamcompmgr_win_t *w ) +{ + if ( !w ) + return 0; + + commit_t *pCommit = get_window_last_done_commit_peek( w ); + if ( !pCommit ) + return 0; + + return pCommit->commitID; +} + // For Steam, etc. static bool window_wants_no_focus_when_mouse_hidden( steamcompmgr_win_t *w ) @@ -2094,9 +2107,23 @@ static void update_touch_scaling( const struct FrameInfo_t *frameInfo ) } #if HAVE_PIPEWIRE -static void paint_pipewire( struct pipewire_buffer *pPipewireBuffer ) +static void paint_pipewire() { - if ( !pPipewireBuffer || !pPipewireBuffer->texture ) + static struct pipewire_buffer *s_pPipewireBuffer = nullptr; + + // If the stream stopped/changed, and the underlying pw_buffer was thus + // destroyed, then destroy this buffer and grab a new one. + if ( s_pPipewireBuffer && s_pPipewireBuffer->IsStale() ) + { + pipewire_destroy_buffer( s_pPipewireBuffer ); + s_pPipewireBuffer = nullptr; + } + + // Queue up a buffer with some metadata. + if ( !s_pPipewireBuffer ) + s_pPipewireBuffer = dequeue_pipewire_buffer(); + + if ( !s_pPipewireBuffer || !s_pPipewireBuffer->texture ) return; struct FrameInfo_t frameInfo = {}; @@ -2112,15 +2139,17 @@ static void paint_pipewire( struct pipewire_buffer *pPipewireBuffer ) frameInfo.shaperLut[nInputEOTF] = g_ScreenshotColorMgmtLuts[nInputEOTF].vk_lut1d; } + const uint64_t ulFocusAppId = pPipewireBuffer->gamescope_info.focus_appid; + focus_t *pFocus = nullptr; - if ( pPipewireBuffer->gamescope_info.focus_appid ) + if ( ulFocusAppId ) { static focus_t s_PipewireFocus{}; if ( s_PipewireFocus.IsDirty() ) { std::vector vecPossibleFocusWindows = GetGlobalPossibleFocusWindows(); - std::vector vecAppIds{ uint32_t( pPipewireBuffer->gamescope_info.focus_appid ) }; + std::vector vecAppIds{ uint32_t( ulFocusAppId ) }; pick_primary_focus_and_override( &s_PipewireFocus, None, vecPossibleFocusWindows, false, vecAppIds ); } pFocus = &s_PipewireFocus; @@ -2130,28 +2159,41 @@ static void paint_pipewire( struct pipewire_buffer *pPipewireBuffer ) pFocus = &global_focus; } - if ( pFocus->focusWindow ) - { - bool bAppIdMatches = !pPipewireBuffer->gamescope_info.focus_appid || pFocus->focusWindow->appID == pPipewireBuffer->gamescope_info.focus_appid; + if ( !pFocus->focusWindow ) + return; - if ( bAppIdMatches ) - { - paint_window( pFocus->focusWindow, pFocus->focusWindow, &frameInfo, nullptr, PaintWindowFlag::NoExpensiveFilter, 1.0f, pFocus->overrideWindow ); + const bool bAppIdMatches = !ulFocusAppId || pFocus->focusWindow->appID == ulFocusAppId; + if ( !bAppIdMatches ) + return; - if ( pFocus->overrideWindow && !pFocus->focusWindow->isSteamStreamingClient ) - paint_window( pFocus->overrideWindow, pFocus->focusWindow, &frameInfo, nullptr, PaintWindowFlag::NoFilter, 1.0f, pFocus->overrideWindow ); - } - } + // If the commits are the same as they were last time, don't repaint and don't push a new buffer on the stream. + static uint64_t s_ulLastFocusCommitId = 0; + static uint64_t s_ulLastOverrideCommitId = 0; + + uint64_t ulFocusCommitId = window_last_done_commit_id( pFocus->focusWindow ); + uint64_t ulOverrideCommitId = window_last_done_commit_id( pFocus->overrideWindow ); + + if ( ulFocusCommitId == s_ulLastFocusCommitId && + ulOverrideCommitId == s_ulLastOverrideCommitId ) + return; + + s_ulLastFocusCommitId = ulFocusCommitId; + s_ulLastOverrideCommitId = ulOverrideCommitId; + + // Paint the windows we have onto the Pipewire stream. + paint_window( pFocus->focusWindow, pFocus->focusWindow, &frameInfo, nullptr, PaintWindowFlag::NoExpensiveFilter, 1.0f, pFocus->overrideWindow ); + + if ( pFocus->overrideWindow && !pFocus->focusWindow->isSteamStreamingClient ) + paint_window( pFocus->overrideWindow, pFocus->focusWindow, &frameInfo, nullptr, PaintWindowFlag::NoFilter, 1.0f, pFocus->overrideWindow ); - std::optional oPipewireSequence = vulkan_composite( &frameInfo, pPipewireBuffer->texture, false, nullptr, false ); + std::optional oPipewireSequence = vulkan_composite( &frameInfo, s_pPipewireBuffer->texture, false, nullptr, false ); if ( oPipewireSequence ) { vulkan_wait( *oPipewireSequence, true ); - push_pipewire_buffer( pPipewireBuffer ); - // TODO: make sure the pw_buffer isn't lost in one of the failure - // code-paths above + push_pipewire_buffer( s_pPipewireBuffer ); + s_pPipewireBuffer = nullptr; } } #endif @@ -2481,8 +2523,8 @@ paint_all(bool async) } #if HAVE_PIPEWIRE - if ( struct pipewire_buffer *pw_buffer = dequeue_pipewire_buffer() ) - paint_pipewire( pw_buffer ); + if ( pipewire_is_streaming() ) + paint_pipewire(); #endif std::optional oScreenshotInfo =