Skip to content

Commit

Permalink
Fix predictive frame rendering to actually delay the next game loop u…
Browse files Browse the repository at this point in the history
…ntil the right time. Shoutouts to C++11 sleep actually sleeping for the time I asked for.
  • Loading branch information
xwidghet committed Feb 20, 2017
1 parent 44aa0d2 commit 3040671
Show file tree
Hide file tree
Showing 19 changed files with 85 additions and 174 deletions.
2 changes: 1 addition & 1 deletion src/Actor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ void Actor::Draw()
{
if( !m_bVisible ||
m_fHibernateSecondsLeft > 0 ||
this->EarlyAbortDraw() || !DISPLAY->ShouldRenderFrame() )
this->EarlyAbortDraw() )
{
return; // early abort
}
Expand Down
3 changes: 0 additions & 3 deletions src/ActorFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,6 @@ void ActorFrame::BeginDraw()

void ActorFrame::DrawPrimitives()
{
if (!DISPLAY->ShouldRenderFrame())
return;

if( m_bClearZBuffer )
{
LuaHelpers::ReportScriptErrorFmt( "ClearZBuffer not supported on ActorFrames" );
Expand Down
3 changes: 0 additions & 3 deletions src/ActorFrameTexture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,6 @@ void ActorFrameTexture::DrawPrimitives()
if( m_pRenderTarget == NULL )
return;

if (!DISPLAY->ShouldRenderFrame())
return;

m_pRenderTarget->BeginRenderingTo( m_bPreserveTexture );

ActorFrame::DrawPrimitives();
Expand Down
3 changes: 0 additions & 3 deletions src/ActorMultiTexture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,6 @@ void ActorMultiTexture::SetTextureMode( int iIndex, TextureMode tm )

void ActorMultiTexture::DrawPrimitives()
{
if (!DISPLAY->ShouldRenderFrame())
return;

Actor::SetGlobalRenderStates(); // set Actor-specified render states

RectF quadVerticies;
Expand Down
3 changes: 0 additions & 3 deletions src/ActorMultiVertex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,9 +216,6 @@ void ActorMultiVertex::SetVertexCoords( int index, float TexCoordX, float TexCoo

void ActorMultiVertex::DrawPrimitives()
{
if (!DISPLAY->ShouldRenderFrame())
return;

Actor::SetGlobalRenderStates(); // set Actor-specified render states

DISPLAY->ClearAllTextures();
Expand Down
3 changes: 0 additions & 3 deletions src/Background.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -843,9 +843,6 @@ void BackgroundImpl::Update( float fDeltaTime )

void BackgroundImpl::DrawPrimitives()
{
if (!DISPLAY->ShouldRenderFrame())
return;

if( g_fBGBrightness == 0.0f )
return;

Expand Down
3 changes: 0 additions & 3 deletions src/BeginnerHelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -234,9 +234,6 @@ void BeginnerHelper::DrawPrimitives()
if( !m_bInitialized )
return;

if (!DISPLAY->ShouldRenderFrame())
return;

ActorFrame::DrawPrimitives();
m_sFlash.Draw();

Expand Down
5 changes: 1 addition & 4 deletions src/BitmapText.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ void BitmapText::DrawChars( bool bUseStrokeTexture )
{
// bail if cropped all the way
if (m_pTempState->crop.left + m_pTempState->crop.right >= 1 ||
m_pTempState->crop.top + m_pTempState->crop.bottom >= 1 || !DISPLAY->ShouldRenderFrame())
m_pTempState->crop.top + m_pTempState->crop.bottom >= 1 )
{
return;
}
Expand Down Expand Up @@ -725,9 +725,6 @@ bool BitmapText::EarlyAbortDraw() const
// draw text at x, y using colorTop blended down to colorBottom, with size multiplied by scale
void BitmapText::DrawPrimitives()
{
if (!DISPLAY->ShouldRenderFrame())
return;

Actor::SetGlobalRenderStates(); // set Actor-specified render states
DISPLAY->SetTextureMode( TextureUnit_1, TextureMode_Modulate );

Expand Down
3 changes: 0 additions & 3 deletions src/DancingCharacters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -310,9 +310,6 @@ void DancingCharacters::Change2DAnimState( PlayerNumber pn, int iState )

void DancingCharacters::DrawPrimitives()
{
if (!DISPLAY->ShouldRenderFrame())
return;

DISPLAY->CameraPushMatrix();

float fPercentIntoSweep;
Expand Down
9 changes: 0 additions & 9 deletions src/Model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -293,9 +293,6 @@ bool Model::EarlyAbortDraw() const

void Model::DrawCelShaded()
{
if (!DISPLAY->ShouldRenderFrame())
return;

// First pass: shell. We only want the backfaces for this.
DISPLAY->SetCelShaded(1);
DISPLAY->SetCullMode(CULL_FRONT);
Expand All @@ -313,9 +310,6 @@ void Model::DrawCelShaded()

void Model::DrawPrimitives()
{
if (!DISPLAY->ShouldRenderFrame())
return;

Actor::SetGlobalRenderStates(); // set Actor-specified render states

// Don't if we're fully transparent
Expand Down Expand Up @@ -471,9 +465,6 @@ void Model::DrawPrimitives()

void Model::DrawMesh( int i ) const
{
if (!DISPLAY->ShouldRenderFrame())
return;

const msMesh *pMesh = &m_pGeometry->m_Meshes[i];

// apply mesh-specific bone (if any)
Expand Down
17 changes: 1 addition & 16 deletions src/NoteDisplay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -448,9 +448,6 @@ bool NoteDisplay::DrawHoldsInRange(const NoteFieldRenderArgs& field_args,
const NoteColumnRenderArgs& column_args,
const vector<NoteData::TrackMap::const_iterator>& tap_set)
{
if (!DISPLAY->ShouldRenderFrame())
return false;

bool any_upcoming = false;
for(vector<NoteData::TrackMap::const_iterator>::const_iterator tapit=
tap_set.begin(); tapit != tap_set.end(); ++tapit)
Expand Down Expand Up @@ -517,9 +514,6 @@ bool NoteDisplay::DrawTapsInRange(const NoteFieldRenderArgs& field_args,
const NoteColumnRenderArgs& column_args,
const vector<NoteData::TrackMap::const_iterator>& tap_set)
{
if (!DISPLAY->ShouldRenderFrame())
return false;

bool any_upcoming= false;
// draw notes from furthest to closest
for(vector<NoteData::TrackMap::const_iterator>::const_iterator tapit=
Expand Down Expand Up @@ -706,10 +700,7 @@ struct StripBuffer
}
void Draw()
{
if( DISPLAY->ShouldRenderFrame() )
{
DISPLAY->DrawSymmetricQuadStrip(buf, v - buf);
}
DISPLAY->DrawSymmetricQuadStrip(buf, v - buf);
}
int Used() const { return v - buf; }
int Free() const { return size - Used(); }
Expand Down Expand Up @@ -1342,9 +1333,6 @@ void NoteDisplay::DrawTap(const TapNote& tn,
bool bOnSameRowAsHoldStart, bool bOnSameRowAsRollStart,
bool bIsAddition, float fPercentFadeToFail)
{
if ( !DISPLAY->ShouldRenderFrame() )
return;

Actor* pActor = NULL;
NotePart part = NotePart_Tap;
/*
Expand Down Expand Up @@ -1491,9 +1479,6 @@ void NoteColumnRenderer::UpdateReceptorGhostStuff(Actor* receptor) const

void NoteColumnRenderer::DrawPrimitives()
{
if (!DISPLAY->ShouldRenderFrame())
return;

m_column_render_args.song_beat= m_field_render_args->player_state->GetDisplayedPosition().m_fSongBeatVisible;
m_column_render_args.pos_handler= &NCR_current.m_pos_handler;
m_column_render_args.rot_handler= &NCR_current.m_rot_handler;
Expand Down
3 changes: 0 additions & 3 deletions src/NoteField.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -706,9 +706,6 @@ void NoteField::CalcPixelsBeforeAndAfterTargets()

void NoteField::DrawPrimitives()
{
if (!DISPLAY->ShouldRenderFrame())
return;

//LOG->Trace( "NoteField::DrawPrimitives()" );

// This should be filled in on the first update.
Expand Down
3 changes: 0 additions & 3 deletions src/Player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1521,9 +1521,6 @@ void Player::ApplyWaitingTransforms()

void Player::DrawPrimitives()
{
if (!DISPLAY->ShouldRenderFrame())
return;

// TODO: Remove use of PlayerNumber.
PlayerNumber pn = m_pPlayerState->m_PlayerNumber;

Expand Down
104 changes: 46 additions & 58 deletions src/RageDisplay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ static int g_iFramesRenderedSinceLastCheck,
g_iNumChecksSinceLastReset;
static RageTimer g_LastFrameEndedAtRage( RageZeroTimer );
static auto g_LastFrameEndedAt = std::chrono::steady_clock::now();
static auto g_FrameExecutionTime = std::chrono::steady_clock::now();
static auto g_FrameRenderTime = std::chrono::steady_clock::now();
static std::chrono::nanoseconds g_LastFrameRenderTime;
static std::chrono::nanoseconds g_LastFramePresentTime;
Expand Down Expand Up @@ -127,11 +126,8 @@ void RageDisplay::ProcessStatsOnFlip()
{
if (PREFSMAN->m_bShowStats || LOG_FPS)
{
if (ShouldRenderFrame())
{
g_iFramesRenderedSinceLastCheck++;
g_iFramesRenderedSinceLastReset++;
}
g_iFramesRenderedSinceLastCheck++;
g_iFramesRenderedSinceLastReset++;

std::chrono::duration<double> timeDelta = std::chrono::steady_clock::now() - g_LastCheckTimer;
float checkTime = timeDelta.count();
Expand Down Expand Up @@ -937,7 +933,7 @@ void RageDisplay::DrawCircle( const RageSpriteVertex &v, float radius )

void RageDisplay::FrameLimitBeforeVsync()
{
if ( presentFrame && g_fPredictiveFrameLimit.Get() )
if ( g_fPredictiveFrameLimit.Get() )
{
auto afterRender = std::chrono::steady_clock::now();
auto endTime = afterRender - g_FrameRenderTime;
Expand Down Expand Up @@ -969,8 +965,8 @@ void RageDisplay::FrameLimitBeforeVsync()
}

// Frame pacing code
// The aim of this function is to make it so we only render frames the user can see
// And to render those frames as close to the display present time as possible to reduce display lag
// The aim of this function is to delay the start of the next loop as long as possible
// so that what the player sees when using VSync is as close to real time as possible.
//
// The issue:
// If we wait too long we miss the present and cause a stutter that displays information which could be over 1 frame old
Expand All @@ -985,63 +981,55 @@ void RageDisplay::FrameLimitAfterVsync( int iFPS )
else if ( !PREFSMAN->m_bVsync.Get() && g_fFrameLimit.Get() == 0 && g_fFrameLimitGameplay.Get() == 0 )
return;

if ( presentFrame )
g_LastFrameEndedAt = std::chrono::steady_clock::now();

// Get the target frame time
double waitTime = 0.0;
if (SCREENMAN && SCREENMAN->GetTopScreen())
{
presentFrame = false;
g_LastFrameEndedAt = std::chrono::steady_clock::now();
g_FrameExecutionTime = g_LastFrameEndedAt;
if (SCREENMAN->GetTopScreen()->GetScreenType() == gameplay && g_fFrameLimitGameplay.Get() > 0)
waitTime = 1.0 / g_fFrameLimitGameplay.Get();
else if (SCREENMAN->GetTopScreen()->GetScreenType() != gameplay && g_fFrameLimit.Get() > 0)
waitTime = 1.0 / g_fFrameLimit.Get();
}
else
{
// Get the timings for the game logic loop, render loop, and present time
// Along with how long we are waiting for, e.g. Frame Limiting
auto loopEndTime = std::chrono::steady_clock::now() - g_FrameExecutionTime;

double waitTime = 0.0;
if (SCREENMAN && SCREENMAN->GetTopScreen())
{
if (SCREENMAN->GetTopScreen()->GetScreenType() == gameplay && g_fFrameLimitGameplay.Get() > 0)
waitTime = 1.0 / g_fFrameLimitGameplay.Get();
else if (SCREENMAN->GetTopScreen()->GetScreenType() != gameplay && g_fFrameLimit.Get() > 0)
waitTime = 1.0 / g_fFrameLimit.Get();
}

// Not using frame limiter and vsync is enabled
// Or Frame limiter is set beyond vsync
if (PREFSMAN->m_bVsync.Get() && (waitTime == 0.0 || waitTime < (1.0 / iFPS)))
waitTime = 1.0 / iFPS;

auto waitTimeNano = std::chrono::duration<double>(waitTime);
auto waitTimeActuallyNano = std::chrono::duration_cast<std::chrono::nanoseconds>(waitTimeNano);
// Not using frame limiter and vsync is enabled
// Or Frame limiter is set beyond vsync
if (PREFSMAN->m_bVsync.Get() && (waitTime == 0.0 || waitTime < (1.0 / iFPS)))
waitTime = 1.0 / iFPS;

// Calculate wait time and attempt to not overshoot waiting
// Cautiousness is needed due to CPU power states and driver present times causing entire loop time variations
// of around +-250 microseconds.

// Conservative default of 10% target frame time is used incase someone is using really old hardware
double waitCautiousness = g_fFrameLimitPercent.Get() > 0 ? g_fFrameLimitPercent.Get() : 0.90;

// Calculate wait time and attempt to not overshoot waiting
//
// Cannot have anything which takes a while to execute after the afterLoop time get as it won't be taken into account
double waitCautiousness = g_fFrameLimitPercent.Get() > 0 ? g_fFrameLimitPercent.Get() : 0.90;
auto renderTime = g_LastFrameRenderTime + g_LastFramePresentTime - loopEndTime;
// Target frame time
waitTime *= waitCautiousness;
auto waitTimeNano = std::chrono::duration<double>(waitTime);
auto waitTimeActuallyNano = std::chrono::duration_cast<std::chrono::nanoseconds>(waitTimeNano);

auto afterLoop = std::chrono::steady_clock::now();
auto timeTillRender = afterLoop - g_LastFrameEndedAt;
// Last render time, DirectX and OpenGL do work in present time so we need to include them
auto renderTime = g_LastFrameRenderTime + g_LastFramePresentTime;

// ex. 8.33ms refresh rate - 1ms render time
auto startLoopTime = waitTimeActuallyNano - renderTime;

// Check if we have enough time to do another loop, or if that'll make us late
if (timeTillRender >= (waitCautiousness * (waitTimeActuallyNano - loopEndTime - renderTime)))
{
presentFrame = true;
g_FrameRenderTime = std::chrono::steady_clock::now();
}
// Check if we need to wait
if (startLoopTime.count() < 0)
{
// Just go, we're late
//LOG->Trace("Game loop start is late by %d", startLoopTime);
}
else
{
//LOG->Trace("Waiting before game loop by %d", startLoopTime);
// Wait until our desired time, aka g_fFrameLimitPercent of the way until needed present time
std::this_thread::sleep_for(startLoopTime);
}
}

// Both ShouldRenderFrame and ShouldPresentFrame are the same,
// but they are here in-case in the future we find theres a reason to change how they are used
bool RageDisplay::ShouldRenderFrame()
{
return presentFrame;
}

bool RageDisplay::ShouldPresentFrame()
{
return presentFrame;
g_FrameRenderTime = std::chrono::steady_clock::now();
}

void RageDisplay::SetPresentTime(std::chrono::nanoseconds presentTime)
Expand Down
3 changes: 0 additions & 3 deletions src/RageDisplay.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,10 +188,7 @@ class RageDisplay
virtual RString GetApiDescription() const = 0;
virtual void GetDisplayResolutions( DisplayResolutions &out ) const = 0;

bool ShouldRenderFrame();
bool ShouldPresentFrame();
void SetPresentTime(std::chrono::nanoseconds presentTime);
bool presentFrame = true;

// Don't override this. Override TryVideoMode() instead.
// This will set the video mode to be as close as possible to params.
Expand Down
16 changes: 5 additions & 11 deletions src/RageDisplay_D3D.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -586,11 +586,8 @@ bool RageDisplay_D3D::BeginFrame()
}
}

if (DISPLAY->ShouldRenderFrame())
{
g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0x00000000);
}

g_pd3dDevice->BeginScene();

Expand All @@ -603,14 +600,11 @@ void RageDisplay_D3D::EndFrame()

FrameLimitBeforeVsync();

if (ShouldPresentFrame())
{
auto beforePresent = std::chrono::steady_clock::now();
g_pd3dDevice->Present(0, 0, 0, 0);
auto beforePresent = std::chrono::steady_clock::now();
g_pd3dDevice->Present(0, 0, 0, 0);

auto afterPresent = std::chrono::steady_clock::now();
SetPresentTime(afterPresent - beforePresent);
}
auto afterPresent = std::chrono::steady_clock::now();
SetPresentTime(afterPresent - beforePresent);

FrameLimitAfterVsync( (*GetActualVideoModeParams()).rate );

Expand Down
Loading

0 comments on commit 3040671

Please sign in to comment.