2020-01-26 22:59:51 +00:00
|
|
|
// Try to figure out when vblank is and notify steamcompmgr to render some time before it
|
|
|
|
|
2022-01-25 07:14:18 +00:00
|
|
|
#include <mutex>
|
2020-01-26 22:59:51 +00:00
|
|
|
#include <thread>
|
|
|
|
#include <vector>
|
|
|
|
#include <chrono>
|
2020-01-27 00:28:35 +00:00
|
|
|
#include <atomic>
|
2022-01-25 07:14:18 +00:00
|
|
|
#include <condition_variable>
|
2020-01-26 22:59:51 +00:00
|
|
|
|
2021-06-09 16:42:38 +00:00
|
|
|
#include <assert.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <unistd.h>
|
2020-01-26 22:59:51 +00:00
|
|
|
|
2020-01-27 00:28:35 +00:00
|
|
|
#include "gpuvis_trace_utils.h"
|
|
|
|
|
2020-01-26 22:59:51 +00:00
|
|
|
#include "vblankmanager.hpp"
|
|
|
|
#include "steamcompmgr.hpp"
|
2020-06-11 10:42:27 +00:00
|
|
|
#include "wlserver.hpp"
|
2020-01-26 22:59:51 +00:00
|
|
|
#include "main.hpp"
|
|
|
|
|
2021-06-09 16:42:38 +00:00
|
|
|
static int g_vblankPipe[2];
|
2020-01-26 22:59:51 +00:00
|
|
|
|
2020-09-05 16:53:27 +00:00
|
|
|
std::atomic<uint64_t> g_lastVblank;
|
2020-01-27 00:28:35 +00:00
|
|
|
|
2022-01-25 13:04:32 +00:00
|
|
|
// 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
|
2022-01-28 23:44:21 +00:00
|
|
|
// 2.0ms by default. (g_DefaultVBlankRedZone)
|
2022-01-25 13:04:32 +00:00
|
|
|
// 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.
|
2022-01-28 23:44:21 +00:00
|
|
|
uint64_t g_uVblankDrawBufferRedZoneNS = g_uDefaultVBlankRedZone;
|
2022-01-25 13:04:32 +00:00
|
|
|
|
|
|
|
// Tuneable
|
2022-01-28 23:44:21 +00:00
|
|
|
// 93% by default. (g_uVBlankRateOfDecayPercentage)
|
2022-01-25 13:04:32 +00:00
|
|
|
// The rate of decay (as a percentage) of the rolling average -> current draw time
|
2022-01-28 23:44:21 +00:00
|
|
|
uint64_t g_uVBlankRateOfDecayPercentage = g_uDefaultVBlankRateOfDecayPercentage;
|
2022-01-25 13:04:32 +00:00
|
|
|
|
2022-02-11 05:28:03 +00:00
|
|
|
const uint64_t g_uVBlankRateOfDecayMax = 1000;
|
2022-01-25 13:04:32 +00:00
|
|
|
|
2022-01-25 07:14:18 +00:00
|
|
|
static std::atomic<uint64_t> g_uRollingMaxDrawTime = { g_uStartingDrawTime };
|
|
|
|
|
2022-01-25 13:04:32 +00:00
|
|
|
//#define VBLANK_DEBUG
|
2020-01-27 00:28:35 +00:00
|
|
|
|
2020-01-26 22:59:51 +00:00
|
|
|
void vblankThreadRun( void )
|
|
|
|
{
|
2021-10-18 16:46:31 +00:00
|
|
|
pthread_setname_np( pthread_self(), "gamescope-vblk" );
|
|
|
|
|
2022-01-25 13:04:32 +00:00
|
|
|
// Start off our average with our starting draw time.
|
|
|
|
uint64_t rollingMaxDrawTime = g_uStartingDrawTime;
|
|
|
|
|
|
|
|
const uint64_t range = g_uVBlankRateOfDecayMax;
|
2020-01-26 22:59:51 +00:00
|
|
|
while ( true )
|
|
|
|
{
|
2022-01-25 13:04:32 +00:00
|
|
|
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;
|
2020-01-27 00:28:35 +00:00
|
|
|
|
2022-01-25 13:04:32 +00:00
|
|
|
// 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.
|
2022-02-11 05:28:03 +00:00
|
|
|
|
|
|
|
// If we go over half of our deadzone, be more defensive about things.
|
|
|
|
if ( int64_t(drawTime) - int64_t(g_uVblankDrawBufferRedZoneNS / 2) > int64_t(rollingMaxDrawTime) )
|
|
|
|
rollingMaxDrawTime = drawTime;
|
|
|
|
else
|
|
|
|
rollingMaxDrawTime = ( ( alpha * rollingMaxDrawTime ) + ( range - alpha ) * drawTime ) / range;
|
2022-01-25 13:04:32 +00:00
|
|
|
|
|
|
|
// 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.
|
2022-02-13 08:26:30 +00:00
|
|
|
rollingMaxDrawTime = std::min( rollingMaxDrawTime, nsecInterval - g_uVblankDrawBufferRedZoneNS );
|
2022-01-25 13:04:32 +00:00
|
|
|
|
2022-01-25 07:14:18 +00:00
|
|
|
g_uRollingMaxDrawTime = rollingMaxDrawTime;
|
|
|
|
|
2022-01-25 13:04:32 +00:00
|
|
|
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 )
|
2020-09-15 21:46:12 +00:00
|
|
|
{
|
2022-01-25 13:04:32 +00:00
|
|
|
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 );
|
2020-09-15 21:46:12 +00:00
|
|
|
}
|
|
|
|
|
2022-01-25 13:04:32 +00:00
|
|
|
lastDrawTime = drawTime;
|
|
|
|
lastOffset = offset;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
uint64_t lastVblank = g_lastVblank - offset;
|
|
|
|
|
2020-09-05 16:53:27 +00:00
|
|
|
uint64_t now = get_time_in_nanos();
|
|
|
|
uint64_t targetPoint = lastVblank + nsecInterval;
|
2020-01-27 00:28:35 +00:00
|
|
|
while ( targetPoint < now )
|
2020-09-05 16:53:27 +00:00
|
|
|
targetPoint += nsecInterval;
|
|
|
|
|
|
|
|
sleep_until_nanos( targetPoint );
|
2020-09-02 01:17:08 +00:00
|
|
|
|
|
|
|
// give the time of vblank to steamcompmgr
|
2020-09-05 16:24:20 +00:00
|
|
|
uint64_t vblanktime = get_time_in_nanos();
|
2021-06-09 16:42:38 +00:00
|
|
|
|
|
|
|
ssize_t ret = write( g_vblankPipe[ 1 ], &vblanktime, sizeof( vblanktime ) );
|
|
|
|
if ( ret <= 0 )
|
|
|
|
{
|
|
|
|
perror( "vblankmanager: write failed" );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
gpuvis_trace_printf( "sent vblank" );
|
|
|
|
}
|
2020-01-27 00:28:35 +00:00
|
|
|
|
|
|
|
// Get on the other side of it now
|
2022-01-25 13:04:32 +00:00
|
|
|
sleep_for_nanos( offset + 1'000'000 );
|
2020-01-26 22:59:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-09 16:42:38 +00:00
|
|
|
int vblank_init( void )
|
2020-01-26 22:59:51 +00:00
|
|
|
{
|
2021-06-09 16:42:38 +00:00
|
|
|
if ( pipe2( g_vblankPipe, O_CLOEXEC | O_NONBLOCK ) != 0 )
|
|
|
|
{
|
|
|
|
perror( "vblankmanager: pipe failed" );
|
|
|
|
return -1;
|
|
|
|
}
|
2020-01-27 00:28:35 +00:00
|
|
|
|
2020-09-05 16:53:27 +00:00
|
|
|
g_lastVblank = get_time_in_nanos();
|
2020-01-26 22:59:51 +00:00
|
|
|
|
|
|
|
std::thread vblankThread( vblankThreadRun );
|
|
|
|
vblankThread.detach();
|
2021-06-09 16:42:38 +00:00
|
|
|
|
|
|
|
return g_vblankPipe[ 0 ];
|
2020-01-26 22:59:51 +00:00
|
|
|
}
|
|
|
|
|
2022-01-25 14:35:55 +00:00
|
|
|
void vblank_mark_possible_vblank( uint64_t nanos )
|
2020-01-26 22:59:51 +00:00
|
|
|
{
|
2022-01-25 14:35:55 +00:00
|
|
|
g_lastVblank = nanos;
|
2020-01-26 22:59:51 +00:00
|
|
|
}
|
2022-01-25 07:14:18 +00:00
|
|
|
|
|
|
|
// fps limit manager
|
|
|
|
|
|
|
|
static std::mutex g_TargetFPSMutex;
|
|
|
|
static std::condition_variable g_TargetFPSCondition;
|
|
|
|
static int g_nFpsLimitTargetFPS = 0;
|
|
|
|
|
2022-02-12 12:11:00 +00:00
|
|
|
void steamcompmgr_fpslimit_release_commit( int consecutive_missed_frame_count );
|
2022-01-25 07:14:18 +00:00
|
|
|
void steamcompmgr_fpslimit_release_all();
|
|
|
|
void steamcompmgr_send_frame_done_to_focus_window();
|
|
|
|
|
|
|
|
// Dump some stats.
|
2022-02-13 08:26:30 +00:00
|
|
|
#define FPS_LIMIT_DEBUG
|
2022-01-25 07:14:18 +00:00
|
|
|
|
2022-02-12 11:54:51 +00:00
|
|
|
// 1.80ms for the app's deadzone to account for varying GPU clocks, other variances, etc
|
|
|
|
uint64_t g_uFPSLimiterRedZoneNS = 1'800'000;
|
2022-01-25 07:14:18 +00:00
|
|
|
|
2022-02-10 02:34:03 +00:00
|
|
|
bool g_bFPSLimitThreadRun = true;
|
|
|
|
|
2022-01-25 07:14:18 +00:00
|
|
|
void fpslimitThreadRun( void )
|
|
|
|
{
|
|
|
|
pthread_setname_np( pthread_self(), "gamescope-fps" );
|
|
|
|
|
|
|
|
uint64_t lastCommitReleased = get_time_in_nanos();
|
|
|
|
const uint64_t range = g_uVBlankRateOfDecayMax;
|
|
|
|
uint64_t rollingMaxFrameTime = g_uStartingDrawTime;
|
|
|
|
uint64_t vblank = 0;
|
2022-02-12 12:11:00 +00:00
|
|
|
int consecutive_missed_frame_count = 0;
|
2022-02-13 08:26:30 +00:00
|
|
|
bool last_frame_was_late = false;
|
2022-01-25 07:14:18 +00:00
|
|
|
while ( true )
|
|
|
|
{
|
|
|
|
int nTargetFPS;
|
|
|
|
uint64_t targetInterval;
|
|
|
|
bool no_frame = false;
|
|
|
|
|
|
|
|
{
|
|
|
|
std::unique_lock<std::mutex> lock( g_TargetFPSMutex );
|
2022-02-10 02:34:03 +00:00
|
|
|
|
|
|
|
if ( !g_bFPSLimitThreadRun )
|
|
|
|
return;
|
|
|
|
|
2022-01-25 07:14:18 +00:00
|
|
|
nTargetFPS = g_nFpsLimitTargetFPS;
|
|
|
|
if ( nTargetFPS == 0 )
|
|
|
|
{
|
|
|
|
g_TargetFPSCondition.wait(lock);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
targetInterval = 1'000'000'000ul / nTargetFPS;
|
|
|
|
auto wait_time = std::chrono::nanoseconds(int64_t(lastCommitReleased + targetInterval) - get_time_in_nanos());
|
|
|
|
if ( wait_time > std::chrono::nanoseconds(0) )
|
|
|
|
{
|
|
|
|
no_frame = g_TargetFPSCondition.wait_for(lock, std::chrono::nanoseconds(wait_time)) == std::cv_status::timeout;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
no_frame = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
nTargetFPS = g_nFpsLimitTargetFPS;
|
|
|
|
}
|
|
|
|
|
2022-02-13 08:26:30 +00:00
|
|
|
// If the last frame was late, and this isn't a late frame
|
|
|
|
// ignore it, as this is that late frame.
|
|
|
|
if ( !last_frame_was_late || no_frame )
|
2022-01-25 07:14:18 +00:00
|
|
|
{
|
2022-02-13 08:26:30 +00:00
|
|
|
if ( no_frame )
|
|
|
|
consecutive_missed_frame_count++;
|
|
|
|
else
|
|
|
|
consecutive_missed_frame_count = 0;
|
2022-01-25 07:14:18 +00:00
|
|
|
|
2022-02-13 08:26:30 +00:00
|
|
|
if ( nTargetFPS )
|
2022-01-25 07:14:18 +00:00
|
|
|
{
|
2022-02-13 08:26:30 +00:00
|
|
|
targetInterval = 1'000'000'000ul / nTargetFPS;
|
2022-01-25 07:14:18 +00:00
|
|
|
|
2022-02-13 08:26:30 +00:00
|
|
|
// Check if we are unaligned or not, as to whether
|
|
|
|
// we call frame callbacks from this thread instead of steamcompmgr based
|
|
|
|
// on vblank count.
|
|
|
|
bool useFrameCallbacks = fpslimit_use_frame_callbacks_for_focus_window( nTargetFPS, 0 );
|
|
|
|
|
|
|
|
uint64_t t0 = lastCommitReleased;
|
|
|
|
uint64_t t1 = get_time_in_nanos();
|
|
|
|
|
|
|
|
// Not the actual frame time of the game
|
|
|
|
// this is the time of the amount of work a 'frame' has done.
|
|
|
|
uint64_t frameTime = t1 - t0;
|
|
|
|
// If we didn't get a frame, set our frame time as the target interval.
|
|
|
|
if ( no_frame || !frameTime )
|
|
|
|
{
|
|
|
|
#ifdef FPS_LIMIT_DEBUG
|
|
|
|
fprintf( stderr, "no frame\n" );
|
|
|
|
#endif
|
|
|
|
frameTime = targetInterval;
|
|
|
|
}
|
2022-01-25 07:14:18 +00:00
|
|
|
|
2022-02-13 08:26:30 +00:00
|
|
|
const int refresh = g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh;
|
|
|
|
const uint64_t vblankInterval = 1'000'000'000ul / refresh;
|
|
|
|
|
|
|
|
// Currently,
|
|
|
|
// Only affect rolling max frame time by 0.07%
|
|
|
|
// Tends to be much more varied than the vblank timings.
|
|
|
|
// Try to be much more defensive about it.
|
|
|
|
//
|
|
|
|
// Do we want something better here? Right now, because this moves around all the time
|
|
|
|
// sometimes we can see judder in the mangoapp frametime graph when gpu clocks are changing around
|
|
|
|
// in the downtime when we aren't rendering as it measures done->done time,
|
|
|
|
// rather than present->present time, and done->done time changes as we move buffers around.
|
|
|
|
// Maybe we want to tweak this alpha value to like 99.something% or change this rolling max to something even more defensive
|
|
|
|
// to keep a more consistent latency. However, I also cannot feel this judder given how small it is, so maybe it doesn't matter?
|
|
|
|
// We can tune this later by tweaking alpha + range anyway...
|
|
|
|
const uint64_t alpha = 993;
|
|
|
|
// If we go over half of our deadzone, be more defensive about things.
|
|
|
|
if ( int64_t(frameTime) - int64_t(g_uFPSLimiterRedZoneNS / 2) > int64_t(rollingMaxFrameTime) )
|
|
|
|
rollingMaxFrameTime = frameTime;
|
|
|
|
else
|
|
|
|
rollingMaxFrameTime = ( ( alpha * rollingMaxFrameTime ) + ( range - alpha ) * frameTime ) / range;
|
2022-01-25 07:14:18 +00:00
|
|
|
|
2022-02-13 08:26:30 +00:00
|
|
|
rollingMaxFrameTime = std::min( rollingMaxFrameTime, targetInterval );
|
2022-01-25 07:14:18 +00:00
|
|
|
|
2022-02-13 08:26:30 +00:00
|
|
|
int64_t targetPoint;
|
|
|
|
int64_t sleepyTime = targetInterval;
|
|
|
|
uint64_t rollingMaxDrawTime = g_uRollingMaxDrawTime.load();
|
2022-01-25 07:14:18 +00:00
|
|
|
|
2022-02-13 08:26:30 +00:00
|
|
|
if ( refresh % nTargetFPS == 0 )
|
|
|
|
{
|
2022-01-25 07:14:18 +00:00
|
|
|
|
2022-02-13 08:26:30 +00:00
|
|
|
// Take the min of it to the target interval - the fps limiter redzone
|
|
|
|
// so that we don't go over the target interval - expected vblank time
|
|
|
|
sleepyTime -= std::min<int64_t>( rollingMaxFrameTime, targetInterval - g_uFPSLimiterRedZoneNS );
|
|
|
|
sleepyTime -= int64_t(g_uFPSLimiterRedZoneNS);
|
|
|
|
// Don't roll back before current vblank
|
|
|
|
// based on varying frame time otherwise we can become divergent
|
|
|
|
// if these value change how we do not expect and get stuck in a feedback loop.
|
|
|
|
sleepyTime = std::max<int64_t>( sleepyTime, 0 );
|
|
|
|
sleepyTime -= int64_t(rollingMaxDrawTime);
|
|
|
|
sleepyTime -= int64_t(g_uVblankDrawBufferRedZoneNS);
|
|
|
|
|
|
|
|
vblank = g_lastVblank;
|
|
|
|
while ( vblank < t1 )
|
|
|
|
vblank += vblankInterval;
|
|
|
|
targetPoint = int64_t(vblank) + sleepyTime;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sleepyTime -= int64_t(frameTime);
|
|
|
|
targetPoint = int64_t(t1) + sleepyTime;
|
|
|
|
}
|
2022-01-25 07:14:18 +00:00
|
|
|
|
|
|
|
|
2022-02-13 08:26:30 +00:00
|
|
|
#ifdef FPS_LIMIT_DEBUG
|
|
|
|
fprintf( stderr, "Sleeping from %lu to %ld (%ld - %.2fms) to reach %d fps - rollingMaxDrawTime: %.2fms vblank: %lu sleepytime: %.2fms rollingMaxFrameTime: %.2fms frametime: %.2fms\n", t1, targetPoint, targetPoint - int64_t(t1), (targetPoint - int64_t(t1)) / 1'000'000.0, nTargetFPS, rollingMaxDrawTime / 1'000'000.0, vblank, sleepyTime / 1'000'000.0, rollingMaxFrameTime / 1'000'000.0, frameTime / 1'000'000.0 );
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
sleep_until_nanos( targetPoint );
|
|
|
|
lastCommitReleased = get_time_in_nanos();
|
|
|
|
steamcompmgr_fpslimit_release_commit( consecutive_missed_frame_count );
|
|
|
|
|
|
|
|
// If we aren't vblank aligned, nudge ourselves to process done commits now.
|
|
|
|
if ( !useFrameCallbacks )
|
|
|
|
{
|
|
|
|
steamcompmgr_send_frame_done_to_focus_window();
|
|
|
|
nudge_steamcompmgr();
|
|
|
|
}
|
2022-01-25 07:14:18 +00:00
|
|
|
}
|
|
|
|
}
|
2022-02-13 08:26:30 +00:00
|
|
|
|
|
|
|
last_frame_was_late = no_frame;
|
2022-01-25 07:14:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void fpslimit_init( void )
|
|
|
|
{
|
|
|
|
std::thread fpslimitThread( fpslimitThreadRun );
|
|
|
|
fpslimitThread.detach();
|
|
|
|
}
|
|
|
|
|
2022-02-10 02:34:03 +00:00
|
|
|
void fpslimit_shutdown( void )
|
|
|
|
{
|
|
|
|
{
|
|
|
|
std::unique_lock<std::mutex> lock(g_TargetFPSMutex);
|
|
|
|
g_bFPSLimitThreadRun = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
g_TargetFPSCondition.notify_all();
|
|
|
|
}
|
|
|
|
|
2022-01-25 07:14:18 +00:00
|
|
|
void fpslimit_mark_frame( void )
|
|
|
|
{
|
|
|
|
g_TargetFPSCondition.notify_all();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool fpslimit_use_frame_callbacks_for_focus_window( int nTargetFPS, int nVBlankCount )
|
|
|
|
{
|
|
|
|
if ( !nTargetFPS )
|
|
|
|
return true;
|
|
|
|
|
|
|
|
const int refresh = g_nNestedRefresh ? g_nNestedRefresh : g_nOutputRefresh;
|
|
|
|
if ( refresh % nTargetFPS == 0 )
|
|
|
|
{
|
|
|
|
// Aligned, limit based on vblank count.
|
|
|
|
return nVBlankCount % ( refresh / nTargetFPS );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Unaligned from VBlank, never use frame callbacks on SteamCompMgr thread.
|
|
|
|
// call them from fpslimit
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Called from steamcompmgr thread
|
|
|
|
void fpslimit_set_target( int nTargetFPS )
|
|
|
|
{
|
|
|
|
{
|
|
|
|
std::unique_lock<std::mutex> lock(g_TargetFPSMutex);
|
|
|
|
g_nFpsLimitTargetFPS = nTargetFPS;
|
|
|
|
}
|
|
|
|
|
|
|
|
g_TargetFPSCondition.notify_all();
|
|
|
|
}
|