From 528eb7ede771c353cd8d36d37565556a464c934f Mon Sep 17 00:00:00 2001 From: Joshua Ashton Date: Wed, 15 Nov 2023 08:21:43 +0000 Subject: [PATCH] steamcompmgr: Refactor img wait to handle multiple image waits at once Refactors the image wait thread to handle multiple image waits at once. --- src/steamcompmgr.cpp | 171 ++++++++++++---------------------- src/waitable.h | 216 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 275 insertions(+), 112 deletions(-) create mode 100644 src/waitable.h diff --git a/src/steamcompmgr.cpp b/src/steamcompmgr.cpp index c4638db1e..081d342e5 100644 --- a/src/steamcompmgr.cpp +++ b/src/steamcompmgr.cpp @@ -72,6 +72,7 @@ #include #include #include +#include "waitable.h" #include "steamcompmgr_shared.hpp" @@ -103,6 +104,7 @@ static LogScope xwm_log("xwm"); +LogScope g_WaitableLog("waitable"); bool g_bWasPartialComposite = false; @@ -706,7 +708,9 @@ struct ignore { unsigned long sequence; }; -struct commit_t +gamescope::CAsyncWaiter g_ImageWaiter{ "gamescope_img" }; + +struct commit_t : public gamescope::IWaitable { commit_t() { @@ -758,6 +762,46 @@ struct commit_t uint64_t desired_present_time = 0; uint64_t earliest_present_time = 0; uint64_t present_margin = 0; + + // For waitable: + int GetFD() final + { + return m_nCommitFence; + } + + void OnPollIn() final + { + gpuvis_trace_end_ctx_printf( commitID, "wait fence" ); + + g_ImageWaiter.RemoveWaitable( this ); + close( m_nCommitFence ); + m_nCommitFence = -1; + + uint64_t frametime; + if ( m_bMangoNudge ) + { + uint64_t now = get_time_in_nanos(); + static uint64_t lastFrameTime = now; + frametime = now - lastFrameTime; + lastFrameTime = now; + } + + // TODO: Move this so it's called in the main loop. + // Instead of looping over all the windows like before. + // When we get the new IWaitable stuff in there. + { + std::unique_lock< std::mutex > lock( pDoneCommits->listCommitsDoneLock ); + pDoneCommits->listCommitsDone.push_back( CommitDoneEntry_t{ commitID, desired_present_time } ); + } + + if ( m_bMangoNudge ) + mangoapp_update( frametime, uint64_t(~0ull), uint64_t(~0ull) ); + + nudge_steamcompmgr(); + } + int m_nCommitFence = -1; + bool m_bMangoNudge = false; + CommitDoneList_t *pDoneCommits = nullptr; // I hate this }; static std::vector pollfds; @@ -1068,89 +1112,19 @@ class sem int count = 0; }; -struct WaitListEntry_t -{ - CommitDoneList_t *doneCommits; - int fence; - // Josh: Whether or not to nudge mangoapp that we got - // a frame as soon as we know this commit is done. - // This could technically be out of date if we change windows - // but for a max couple frames of inaccuracy when switching windows - // compared to being all over the place from handling in the - // steamcompmgr thread in handle_done_commits, it is worth it. - bool mangoapp_nudge; - uint64_t commitID; - uint64_t desiredPresentTime; -}; - -sem waitListSem; -std::mutex waitListLock; -std::vector< WaitListEntry_t > waitList; - -bool imageWaitThreadRun = true; - -void imageWaitThreadMain( void ) +static void +dispatch_nudge( int fd ) { - pthread_setname_np( pthread_self(), "gamescope-img" ); - -wait: - waitListSem.wait(); - - if ( imageWaitThreadRun == false ) - { - return; - } - - bool bFound = false; - WaitListEntry_t entry; - -retry: + for (;;) { - std::unique_lock< std::mutex > lock( waitListLock ); - - if( waitList.empty() ) + static char buf[1024]; + if ( read( fd, buf, sizeof(buf) ) < 0 ) { - goto wait; + if ( errno != EAGAIN ) + xwm_log.errorf_errno(" steamcompmgr: dispatch_nudge: read failed" ); + break; } - - entry = waitList[ 0 ]; - bFound = true; - waitList.erase( waitList.begin() ); - } - - assert( bFound == true ); - - gpuvis_trace_begin_ctx_printf( entry.commitID, "wait fence" ); - struct pollfd fd = { entry.fence, POLLIN, 0 }; - int ret = poll( &fd, 1, 100 ); - if ( ret < 0 ) - { - xwm_log.errorf_errno( "failed to poll fence FD" ); - } - gpuvis_trace_end_ctx_printf( entry.commitID, "wait fence" ); - - close( entry.fence ); - - uint64_t frametime; - if ( entry.mangoapp_nudge ) - { - uint64_t now = get_time_in_nanos(); - static uint64_t lastFrameTime = now; - frametime = now - lastFrameTime; - lastFrameTime = now; } - - { - std::unique_lock< std::mutex > lock( entry.doneCommits->listCommitsDoneLock ); - entry.doneCommits->listCommitsDone.push_back( CommitDoneEntry_t{ entry.commitID, entry.desiredPresentTime } ); - } - - nudge_steamcompmgr(); - - if ( entry.mangoapp_nudge ) - mangoapp_update( frametime, uint64_t(~0ull), uint64_t(~0ull) ); - - goto retry; } sem statsThreadSem; @@ -6196,8 +6170,7 @@ steamcompmgr_exit(void) g_HeldCommits[ HELD_COMMIT_BASE ] = nullptr; g_HeldCommits[ HELD_COMMIT_FADE ] = nullptr; - imageWaitThreadRun = false; - waitListSem.signal(); + g_ImageWaiter.Shutdown(); if ( statsThreadRun == true ) { @@ -6623,20 +6596,12 @@ void update_wayland_res(CommitDoneList_t *doneCommits, steamcompmgr_win_t *w, Re gpuvis_trace_printf( "pushing wait for commit %lu win %lx", newCommit->commitID, w->type == steamcompmgr_win_type_t::XWAYLAND ? w->xwayland().id : 0 ); { - std::unique_lock< std::mutex > lock( waitListLock ); - WaitListEntry_t entry - { - .doneCommits = doneCommits, - .fence = fence, - .mangoapp_nudge = mango_nudge, - .commitID = newCommit->commitID, - .desiredPresentTime = newCommit->desired_present_time, - }; - waitList.push_back( entry ); - } + newCommit->m_nCommitFence = fence; + newCommit->m_bMangoNudge = mango_nudge; + newCommit->pDoneCommits = doneCommits; - // Wake up commit wait thread if chilling - waitListSem.signal(); + g_ImageWaiter.AddWaitable( newCommit.get(), EPOLLIN ); + } w->commit_queue.push_back( std::move(newCommit) ); } @@ -7032,21 +6997,6 @@ dispatch_vblank( int fd ) return vblank; } -static void -dispatch_nudge( int fd ) -{ - for (;;) - { - static char buf[1024]; - if ( read( fd, buf, sizeof(buf) ) < 0 ) - { - if ( errno != EAGAIN ) - xwm_log.errorf_errno(" steamcompmgr: dispatch_nudge: read failed" ); - break; - } - } -} - struct rgba_t { uint8_t r,g,b,a; @@ -7738,9 +7688,6 @@ steamcompmgr_main(int argc, char **argv) spawn_client( &argv[ subCommandArg ] ); } - std::thread imageWaitThread( imageWaitThreadMain ); - imageWaitThread.detach(); - // EVENT_VBLANK pollfds.push_back(pollfd { .fd = vblankFD, diff --git a/src/waitable.h b/src/waitable.h new file mode 100644 index 000000000..76b0620a3 --- /dev/null +++ b/src/waitable.h @@ -0,0 +1,216 @@ +#pragma once + +#include +#include +#include + +#include "log.hpp" + +extern LogScope g_WaitableLog; + +namespace gamescope +{ + class IWaitable + { + public: + virtual ~IWaitable() {} + + virtual int GetFD() { return -1; } + + virtual void OnPollIn() {} + virtual void OnPollOut() {} + virtual void OnPollHangUp() {} + + void HandleEvents( uint32_t nEvents ) + { + if ( nEvents & EPOLLIN ) + this->OnPollIn(); + if ( nEvents & EPOLLOUT ) + this->OnPollOut(); + if ( nEvents & EPOLLHUP ) + this->OnPollHangUp(); + } + }; + + class CNudgeWaitable final : public IWaitable + { + public: + CNudgeWaitable() + { + if ( pipe2( m_nFDs, O_CLOEXEC | O_NONBLOCK ) != 0 ) + Shutdown(); + } + + ~CNudgeWaitable() + { + Shutdown(); + } + + void Shutdown() + { + for ( int i = 0; i < 2; i++ ) + { + if ( m_nFDs[i] >= 0 ) + close( m_nFDs[i] ); + } + } + + void Drain() + { + if ( m_nFDs[0] < 0 ) + return; + + char buf[1024]; + for (;;) + { + if ( read( m_nFDs[0], buf, sizeof( buf ) ) < 0 ) + { + if ( errno != EAGAIN ) + g_WaitableLog.errorf_errno( "Failed to drain CNudgeWaitable" ); + break; + } + } + } + + bool Nudge() + { + return write( m_nFDs[1], "\n", 1 ) >= 0; + } + + int GetFD() final { return m_nFDs[0]; } + private: + int m_nFDs[2] = { -1, -1 }; + }; + + template + class CWaiter + { + public: + CWaiter() + : m_nEpollFD{ epoll_create1( 0 ) } + { + AddWaitable( &m_NudgeWaitable, EPOLLIN ); + } + + ~CWaiter() + { + Shutdown(); + } + + void Shutdown() + { + if ( !m_bRunning ) + return; + + Nudge(); + m_bRunning = false; + + if ( m_nEpollFD >= 0 ) + { + close( m_nEpollFD ); + m_nEpollFD = -1; + } + } + + bool AddWaitable( IWaitable *pWaitable, uint32_t nEvents = EPOLLIN | EPOLLOUT | EPOLLHUP ) + { + epoll_event event = + { + .events = nEvents, + .data = + { + .ptr = reinterpret_cast( pWaitable ), + }, + }; + + if ( epoll_ctl( m_nEpollFD, EPOLL_CTL_ADD, pWaitable->GetFD(), &event ) != 0 ) + { + g_WaitableLog.errorf_errno( "Failed to add waitable" ); + return false; + } + + return true; + } + + void RemoveWaitable( IWaitable *pWaitable ) + { + epoll_ctl( m_nEpollFD, EPOLL_CTL_DEL, pWaitable->GetFD(), nullptr ); + } + + void PollEvents() + { + epoll_event events[MaxEvents]; + + int nEventCount = epoll_wait( m_nEpollFD, events, MaxEvents, -1); + + if ( !m_bRunning ) + return; + + if ( nEventCount < 0 ) + { + g_WaitableLog.errorf_errno( "Failed to epoll_wait in CAsyncWaiter" ); + return; + } + + for ( int i = 0; i < nEventCount; i++ ) + { + epoll_event &event = events[i]; + + IWaitable *pWaitable = reinterpret_cast(event.data.ptr); + pWaitable->HandleEvents( event.events ); + } + } + + bool Nudge() + { + return m_NudgeWaitable.Nudge(); + } + + bool IsRunning() + { + return m_bRunning; + } + + private: + std::atomic m_bRunning = { true }; + CNudgeWaitable m_NudgeWaitable; + + int m_nEpollFD = -1; + }; + + template + class CAsyncWaiter : public CWaiter + { + public: + CAsyncWaiter( const char *pszThreadName ) + : m_Thread{ [cWaiter = this, cName = pszThreadName](){ cWaiter->WaiterThreadFunc(cName); } } + { + } + + ~CAsyncWaiter() + { + Shutdown(); + } + + void Shutdown() + { + CWaiter::Shutdown(); + + if ( m_Thread.joinable() ) + m_Thread.join(); + } + + void WaiterThreadFunc( const char *pszThreadName ) + { + pthread_setname_np( pthread_self(), pszThreadName ); + + while ( this->IsRunning() ) + CWaiter::PollEvents(); + } + private: + std::thread m_Thread; + }; + + +} +