steamcompmgr: Refactor img wait to handle multiple image waits at once

Refactors the image wait thread to handle multiple image waits at once.
This commit is contained in:
Joshua Ashton 2023-11-15 08:21:43 +00:00 committed by Joshie
parent abdc97e796
commit 528eb7ede7
2 changed files with 275 additions and 112 deletions

View file

@ -72,6 +72,7 @@
#include <signal.h>
#include <linux/input-event-codes.h>
#include <X11/Xmu/CurUtil.h>
#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<pollfd> pollfds;
@ -1068,89 +1112,19 @@ private:
int count = 0;
};
struct WaitListEntry_t
static void
dispatch_nudge( int fd )
{
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 )
for (;;)
{
pthread_setname_np( pthread_self(), "gamescope-img" );
wait:
waitListSem.wait();
if ( imageWaitThreadRun == false )
static char buf[1024];
if ( read( fd, buf, sizeof(buf) ) < 0 )
{
return;
if ( errno != EAGAIN )
xwm_log.errorf_errno(" steamcompmgr: dispatch_nudge: read failed" );
break;
}
bool bFound = false;
WaitListEntry_t entry;
retry:
{
std::unique_lock< std::mutex > lock( waitListLock );
if( waitList.empty() )
{
goto wait;
}
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,

216
src/waitable.h Normal file
View file

@ -0,0 +1,216 @@
#pragma once
#include <thread>
#include <stdint.h>
#include <sys/epoll.h>
#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 <size_t MaxEvents = 1024>
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<void *>( 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<IWaitable *>(event.data.ptr);
pWaitable->HandleEvents( event.events );
}
}
bool Nudge()
{
return m_NudgeWaitable.Nudge();
}
bool IsRunning()
{
return m_bRunning;
}
private:
std::atomic<bool> m_bRunning = { true };
CNudgeWaitable m_NudgeWaitable;
int m_nEpollFD = -1;
};
template <size_t MaxEvents = 1024>
class CAsyncWaiter : public CWaiter<MaxEvents>
{
public:
CAsyncWaiter( const char *pszThreadName )
: m_Thread{ [cWaiter = this, cName = pszThreadName](){ cWaiter->WaiterThreadFunc(cName); } }
{
}
~CAsyncWaiter()
{
Shutdown();
}
void Shutdown()
{
CWaiter<MaxEvents>::Shutdown();
if ( m_Thread.joinable() )
m_Thread.join();
}
void WaiterThreadFunc( const char *pszThreadName )
{
pthread_setname_np( pthread_self(), pszThreadName );
while ( this->IsRunning() )
CWaiter<MaxEvents>::PollEvents();
}
private:
std::thread m_Thread;
};
}