gamescope/src/vblankmanager.cpp

149 lines
4.6 KiB
C++
Raw Normal View History

// Try to figure out when vblank is and notify steamcompmgr to render some time before it
#include <thread>
#include <vector>
#include <chrono>
#include <atomic>
#include <assert.h>
#include <fcntl.h>
#include <unistd.h>
#include "gpuvis_trace_utils.h"
#include "vblankmanager.hpp"
#include "steamcompmgr.hpp"
#include "wlserver.hpp"
#include "main.hpp"
static int g_vblankPipe[2];
std::atomic<uint64_t> g_lastVblank;
// 3ms by default -- a good starting value.
const uint64_t g_uStartingDrawTime = 3'000'000;
// This is the last time a draw took.
std::atomic<uint64_t> g_uVblankDrawTimeNS = { g_uStartingDrawTime };
// Tuneable
// 2.0ms by default. (g_DefaultVBlankRedZone)
// This is the leeway we always apply to our buffer.
// This also accounts for some time we cannot account for (which (I think) is the drm_commit -> triggering the pageflip)
// It would be nice to make this lower if we can find a way to track that effectively
// Perhaps the missing time is spent elsewhere, but given we track from the pipe write
// to after the return from `drm_commit` -- I am very doubtful.
uint64_t g_uVblankDrawBufferRedZoneNS = g_uDefaultVBlankRedZone;
// Tuneable
// 93% by default. (g_uVBlankRateOfDecayPercentage)
// The rate of decay (as a percentage) of the rolling average -> current draw time
uint64_t g_uVBlankRateOfDecayPercentage = g_uDefaultVBlankRateOfDecayPercentage;
const uint64_t g_uVBlankRateOfDecayMax = 100;
//#define VBLANK_DEBUG
void vblankThreadRun( void )
{
pthread_setname_np( pthread_self(), "gamescope-vblk" );
// Start off our average with our starting draw time.
uint64_t rollingMaxDrawTime = g_uStartingDrawTime;
const uint64_t range = g_uVBlankRateOfDecayMax;
while ( true )
{
const uint64_t alpha = g_uVBlankRateOfDecayPercentage;
const int refresh = g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh;
const uint64_t nsecInterval = 1'000'000'000ul / refresh;
const uint64_t drawTime = g_uVblankDrawTimeNS;
// This is a rolling average when drawTime < rollingMaxDrawTime,
// and a a max when drawTime > rollingMaxDrawTime.
// This allows us to deal with spikes in the draw buffer time very easily.
// eg. if we suddenly spike up (eg. because of test commits taking a stupid long time),
// we will then be able to deal with spikes in the long term, even if several commits after
// we get back into a good state and then regress again.
rollingMaxDrawTime = ( ( alpha * std::max( rollingMaxDrawTime, drawTime ) ) + ( range - alpha ) * drawTime ) / range;
// If we need to offset for our draw more than half of our vblank, something is very wrong.
// Clamp our max time to half of the vblank if we can.
rollingMaxDrawTime = std::min( rollingMaxDrawTime + g_uVblankDrawBufferRedZoneNS, nsecInterval / 2 ) - g_uVblankDrawBufferRedZoneNS;
uint64_t offset = rollingMaxDrawTime + g_uVblankDrawBufferRedZoneNS;
#ifdef VBLANK_DEBUG
// Debug stuff for logging missed vblanks
static uint64_t vblankIdx = 0;
static uint64_t lastDrawTime = g_uVblankDrawTimeNS;
static uint64_t lastOffset = g_uVblankDrawTimeNS + g_uVblankDrawBufferRedZoneNS;
if ( vblankIdx++ % 300 == 0 || drawTime > lastOffset )
{
if ( drawTime > lastOffset )
fprintf( stderr, " !! missed vblank " );
fprintf( stderr, "redZone: %.2fms decayRate: %lu%% - rollingMaxDrawTime: %.2fms lastDrawTime: %.2fms lastOffset: %.2fms - drawTime: %.2fms offset: %.2fms\n",
g_uVblankDrawBufferRedZoneNS / 1'000'000.0,
g_uVBlankRateOfDecayPercentage,
rollingMaxDrawTime / 1'000'000.0,
lastDrawTime / 1'000'000.0,
lastOffset / 1'000'000.0,
drawTime / 1'000'000.0,
offset / 1'000'000.0 );
}
lastDrawTime = drawTime;
lastOffset = offset;
#endif
uint64_t lastVblank = g_lastVblank - offset;
uint64_t now = get_time_in_nanos();
uint64_t targetPoint = lastVblank + nsecInterval;
while ( targetPoint < now )
targetPoint += nsecInterval;
sleep_until_nanos( targetPoint );
// give the time of vblank to steamcompmgr
uint64_t vblanktime = get_time_in_nanos();
ssize_t ret = write( g_vblankPipe[ 1 ], &vblanktime, sizeof( vblanktime ) );
if ( ret <= 0 )
{
perror( "vblankmanager: write failed" );
}
else
{
gpuvis_trace_printf( "sent vblank" );
}
// Get on the other side of it now
sleep_for_nanos( offset + 1'000'000 );
}
}
int vblank_init( void )
{
if ( pipe2( g_vblankPipe, O_CLOEXEC | O_NONBLOCK ) != 0 )
{
perror( "vblankmanager: pipe failed" );
return -1;
}
g_lastVblank = get_time_in_nanos();
std::thread vblankThread( vblankThreadRun );
vblankThread.detach();
return g_vblankPipe[ 0 ];
}
void vblank_mark_possible_vblank( uint64_t nanos )
{
g_lastVblank = nanos;
}