diff --git a/src/Actor.cpp b/src/Actor.cpp index 66cd3a7191..1fb7b0720d 100644 --- a/src/Actor.cpp +++ b/src/Actor.cpp @@ -305,10 +305,11 @@ void Actor::Draw() { if( !m_bVisible || m_fHibernateSecondsLeft > 0 || - this->EarlyAbortDraw() ) + this->EarlyAbortDraw() || !DISPLAY->ShouldRenderFrame() ) { return; // early abort } + if(m_FakeParent) { if(!m_FakeParent->m_bVisible || m_FakeParent->m_fHibernateSecondsLeft > 0 diff --git a/src/ActorFrame.cpp b/src/ActorFrame.cpp index dd165eac62..e19b76a763 100644 --- a/src/ActorFrame.cpp +++ b/src/ActorFrame.cpp @@ -222,6 +222,9 @@ void ActorFrame::BeginDraw() void ActorFrame::DrawPrimitives() { + if (!DISPLAY->ShouldRenderFrame()) + return; + if( m_bClearZBuffer ) { LuaHelpers::ReportScriptErrorFmt( "ClearZBuffer not supported on ActorFrames" ); diff --git a/src/ActorFrameTexture.cpp b/src/ActorFrameTexture.cpp index 0943843561..15cadb35e4 100644 --- a/src/ActorFrameTexture.cpp +++ b/src/ActorFrameTexture.cpp @@ -74,6 +74,9 @@ void ActorFrameTexture::DrawPrimitives() if( m_pRenderTarget == NULL ) return; + if (!DISPLAY->ShouldRenderFrame()) + return; + m_pRenderTarget->BeginRenderingTo( m_bPreserveTexture ); ActorFrame::DrawPrimitives(); diff --git a/src/ActorMultiTexture.cpp b/src/ActorMultiTexture.cpp index 01e4e2d958..de1f663916 100644 --- a/src/ActorMultiTexture.cpp +++ b/src/ActorMultiTexture.cpp @@ -89,6 +89,9 @@ void ActorMultiTexture::SetTextureMode( int iIndex, TextureMode tm ) void ActorMultiTexture::DrawPrimitives() { + if (!DISPLAY->ShouldRenderFrame()) + return; + Actor::SetGlobalRenderStates(); // set Actor-specified render states RectF quadVerticies; diff --git a/src/ActorMultiVertex.cpp b/src/ActorMultiVertex.cpp index 2e4af18d05..7605ec57e8 100644 --- a/src/ActorMultiVertex.cpp +++ b/src/ActorMultiVertex.cpp @@ -216,6 +216,9 @@ 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(); diff --git a/src/Background.cpp b/src/Background.cpp index 770dc87f5c..0e314a8e99 100644 --- a/src/Background.cpp +++ b/src/Background.cpp @@ -843,6 +843,9 @@ void BackgroundImpl::Update( float fDeltaTime ) void BackgroundImpl::DrawPrimitives() { + if (!DISPLAY->ShouldRenderFrame()) + return; + if( g_fBGBrightness == 0.0f ) return; diff --git a/src/BeginnerHelper.cpp b/src/BeginnerHelper.cpp index c4b23ba306..0ce621e677 100644 --- a/src/BeginnerHelper.cpp +++ b/src/BeginnerHelper.cpp @@ -234,6 +234,9 @@ void BeginnerHelper::DrawPrimitives() if( !m_bInitialized ) return; + if (!DISPLAY->ShouldRenderFrame()) + return; + ActorFrame::DrawPrimitives(); m_sFlash.Draw(); diff --git a/src/BitmapText.cpp b/src/BitmapText.cpp index 46fde29c21..99a549cc10 100644 --- a/src/BitmapText.cpp +++ b/src/BitmapText.cpp @@ -347,9 +347,11 @@ void BitmapText::BuildChars() 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 ) - return; + if (m_pTempState->crop.left + m_pTempState->crop.right >= 1 || + m_pTempState->crop.top + m_pTempState->crop.bottom >= 1 || !DISPLAY->ShouldRenderFrame()) + { + return; + } const int iNumGlyphs = m_vpFontPageTextures.size(); int iStartGlyph = lround( SCALE( m_pTempState->crop.left, 0, 1, 0, iNumGlyphs ) ); @@ -670,6 +672,9 @@ 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 ); diff --git a/src/DancingCharacters.cpp b/src/DancingCharacters.cpp index 9c4c9bb4fe..1d82911a7f 100644 --- a/src/DancingCharacters.cpp +++ b/src/DancingCharacters.cpp @@ -310,6 +310,9 @@ void DancingCharacters::Change2DAnimState( PlayerNumber pn, int iState ) void DancingCharacters::DrawPrimitives() { + if (!DISPLAY->ShouldRenderFrame()) + return; + DISPLAY->CameraPushMatrix(); float fPercentIntoSweep; diff --git a/src/Model.cpp b/src/Model.cpp index befa97e157..8794952e2d 100644 --- a/src/Model.cpp +++ b/src/Model.cpp @@ -293,6 +293,9 @@ 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); @@ -310,6 +313,9 @@ void Model::DrawCelShaded() void Model::DrawPrimitives() { + if (!DISPLAY->ShouldRenderFrame()) + return; + Actor::SetGlobalRenderStates(); // set Actor-specified render states // Don't if we're fully transparent @@ -465,6 +471,9 @@ 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) diff --git a/src/NoteDisplay.cpp b/src/NoteDisplay.cpp index ebce2a6392..ebe3ffa7d1 100644 --- a/src/NoteDisplay.cpp +++ b/src/NoteDisplay.cpp @@ -470,6 +470,9 @@ bool NoteDisplay::DrawHoldsInRange(const NoteFieldRenderArgs& field_args, const NoteColumnRenderArgs& column_args, const vector& tap_set) { + if (!DISPLAY->ShouldRenderFrame()) + return false; + bool any_upcoming = false; for(vector::const_iterator tapit= tap_set.begin(); tapit != tap_set.end(); ++tapit) @@ -536,6 +539,9 @@ bool NoteDisplay::DrawTapsInRange(const NoteFieldRenderArgs& field_args, const NoteColumnRenderArgs& column_args, const vector& tap_set) { + if (!DISPLAY->ShouldRenderFrame()) + return false; + bool any_upcoming= false; // draw notes from furthest to closest for(vector::const_iterator tapit= @@ -726,7 +732,10 @@ struct StripBuffer } void Draw() { - DISPLAY->DrawSymmetricQuadStrip( buf, v-buf ); + if( DISPLAY->ShouldRenderFrame() ) + { + DISPLAY->DrawSymmetricQuadStrip(buf, v - buf); + } } int Used() const { return v - buf; } int Free() const { return size - Used(); } @@ -1365,6 +1374,9 @@ 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; /* @@ -1511,6 +1523,9 @@ 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; diff --git a/src/NoteField.cpp b/src/NoteField.cpp index b55c6839b8..5f07c74614 100644 --- a/src/NoteField.cpp +++ b/src/NoteField.cpp @@ -750,6 +750,9 @@ void NoteField::CalcPixelsBeforeAndAfterTargets() void NoteField::DrawPrimitives() { + if (!DISPLAY->ShouldRenderFrame()) + return; + //LOG->Trace( "NoteField::DrawPrimitives()" ); // This should be filled in on the first update. diff --git a/src/Player.cpp b/src/Player.cpp index 1b7e3ca9a6..c43e7d4bae 100644 --- a/src/Player.cpp +++ b/src/Player.cpp @@ -1505,6 +1505,9 @@ void Player::ApplyWaitingTransforms() void Player::DrawPrimitives() { + if (!DISPLAY->ShouldRenderFrame()) + return; + // TODO: Remove use of PlayerNumber. PlayerNumber pn = m_pPlayerState->m_PlayerNumber; diff --git a/src/RageDisplay.cpp b/src/RageDisplay.cpp index 8378c0d437..cca1763260 100644 --- a/src/RageDisplay.cpp +++ b/src/RageDisplay.cpp @@ -11,11 +11,14 @@ #include "RageSurfaceUtils_Zoom.h" #include "RageSurface.h" #include "Preference.h" +#include "PrefsManager.h" #include "Screen.h" #include "ScreenManager.h" #include "LocalizedString.h" #include "DisplayResolutions.h" #include "arch/ArchHooks/ArchHooks.h" +#include +#include // Statistics stuff RageTimer g_LastCheckTimer; @@ -30,7 +33,11 @@ static int g_iFramesRenderedSinceLastCheck, g_iFramesRenderedSinceLastReset, g_iVertsRenderedSinceLastCheck, g_iNumChecksSinceLastReset; -static RageTimer g_LastFrameEndedAt( RageZeroTimer ); +static auto g_LastFrameEndedAt = std::chrono::high_resolution_clock::now(); +static auto g_FrameExecutionTime = std::chrono::high_resolution_clock::now(); +static auto g_FrameRenderTime = std::chrono::high_resolution_clock::now(); +static long long g_LastFrameRenderTime = 0; +static long long g_LastFramePresentTime = 0; struct Centering { @@ -48,9 +55,10 @@ static vector g_CenteringStack( 1, Centering(0, 0, 0, 0) ); RageDisplay* DISPLAY = NULL; // global and accessible from anywhere in our program Preference LOG_FPS( "LogFPS", true ); -Preference g_fFrameLimitPercent( "FrameLimitPercent", 0.0f ); +Preference g_fFrameLimitPercent( "FrameLimitPercent", 0.90f ); Preference g_fFrameLimit("FrameLimit", 0); Preference g_fFrameLimitGameplay("FrameLimitGameplay", 0); +Preference g_fPredictiveFrameLimit("PredictiveFrameLimit", 1); static const char *RagePixelFormatNames[] = { "RGBA8", @@ -114,34 +122,40 @@ RString RageDisplay::SetVideoMode( VideoModeParams p, bool &bNeedReloadTextures void RageDisplay::ProcessStatsOnFlip() { - g_iFramesRenderedSinceLastCheck++; - g_iFramesRenderedSinceLastReset++; - - if( g_LastCheckTimer.PeekDeltaTime() >= 1.0f ) // update stats every 1 sec. + if (PREFSMAN->m_bShowStats || LOG_FPS) { - float fActualTime = g_LastCheckTimer.GetDeltaTime(); - g_iNumChecksSinceLastReset++; - g_iFPS = lround( g_iFramesRenderedSinceLastCheck / fActualTime ); - g_iCFPS = g_iFramesRenderedSinceLastReset / g_iNumChecksSinceLastReset; - g_iCFPS = lround( g_iCFPS / fActualTime ); - g_iVPF = g_iVertsRenderedSinceLastCheck / g_iFramesRenderedSinceLastCheck; - g_iFramesRenderedSinceLastCheck = g_iVertsRenderedSinceLastCheck = 0; - if( LOG_FPS ) + g_iFramesRenderedSinceLastCheck++; + g_iFramesRenderedSinceLastReset++; + + if (g_LastCheckTimer.PeekDeltaTime() >= 1.0f) // update stats every 1 sec. { - RString sStats = GetStats(); - sStats.Replace( "\n", ", " ); - LOG->Trace( "%s", sStats.c_str() ); + float fActualTime = g_LastCheckTimer.GetDeltaTime(); + g_iNumChecksSinceLastReset++; + g_iFPS = lround(g_iFramesRenderedSinceLastCheck / fActualTime); + g_iCFPS = g_iFramesRenderedSinceLastReset / g_iNumChecksSinceLastReset; + g_iCFPS = lround(g_iCFPS / fActualTime); + g_iVPF = g_iVertsRenderedSinceLastCheck / g_iFramesRenderedSinceLastCheck; + g_iFramesRenderedSinceLastCheck = g_iVertsRenderedSinceLastCheck = 0; + if (LOG_FPS) + { + RString sStats = GetStats(); + sStats.Replace("\n", ", "); + LOG->Trace("%s", sStats.c_str()); + } } } } void RageDisplay::ResetStats() { - g_iFPS = g_iVPF = 0; - g_iFramesRenderedSinceLastCheck = g_iFramesRenderedSinceLastReset = 0; - g_iNumChecksSinceLastReset = 0; - g_iVertsRenderedSinceLastCheck = 0; - g_LastCheckTimer.GetDeltaTime(); + if (PREFSMAN->m_bShowStats || LOG_FPS) + { + g_iFPS = g_iVPF = 0; + g_iFramesRenderedSinceLastCheck = g_iFramesRenderedSinceLastReset = 0; + g_iNumChecksSinceLastReset = 0; + g_iVertsRenderedSinceLastCheck = 0; + g_LastCheckTimer.GetDeltaTime(); + } } RString RageDisplay::GetStats() const @@ -907,60 +921,102 @@ void RageDisplay::DrawCircle( const RageSpriteVertex &v, float radius ) this->DrawCircleInternal( v, radius ); } -void RageDisplay::FrameLimitBeforeVsync( int iFPS ) +void RageDisplay::FrameLimitBeforeVsync() { - ASSERT( iFPS != 0 ); - - int iDelayMicroseconds = 0; - if( g_fFrameLimitPercent.Get() > 0.0f && !g_LastFrameEndedAt.IsZero() ) + if (presentFrame && g_fPredictiveFrameLimit.Get()) { - float fFrameTime = g_LastFrameEndedAt.GetDeltaTime(); - float fExpectedTime = 1.0f / iFPS; - - /* This is typically used to turn some of the delay that would normally - * be waiting for vsync and turn it into a usleep, to make sure we give - * up the CPU. If we overshoot the sleep, then we'll miss the vsync, - * so allow tweaking the amount of time we expect a frame to take. - * Frame limiting is disabled by setting this to 0. */ - fExpectedTime *= g_fFrameLimitPercent.Get(); - float fExtraTime = fExpectedTime - fFrameTime; - - iDelayMicroseconds = int(fExtraTime * 1000000); + auto afterRender = std::chrono::high_resolution_clock::now(); + auto endTime = afterRender - g_FrameRenderTime; + + g_LastFrameRenderTime = std::chrono::duration_cast(endTime).count(); } - if( !HOOKS->AppHasFocus() ) - iDelayMicroseconds = max( iDelayMicroseconds, 10000 ); + if (!HOOKS->AppHasFocus()) + std::this_thread::sleep_for(std::chrono::milliseconds(10)); +} - if (iDelayMicroseconds > 0) - usleep(iDelayMicroseconds); - else if (!g_LastFrameEndedAt.IsZero()) +// 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 issue: +// If we wait too long we miss the present and cause a stutter that displays information which could be over 1 frame old +// If we are too cautious then we lose out on latency we could remove with better frame pacing +// +// Frame limit modes BUSY_LOOP and SLEEP render every frame/game loop, predictive runs game loop until it needs to render +void RageDisplay::FrameLimitAfterVsync( int iFPS ) +{ + if ( g_fFrameLimitPercent.Get() == 0 && g_fFrameLimit.Get() == 0 && + g_fFrameLimitGameplay.Get() == 0 ) + return; + + if ( presentFrame ) + { + presentFrame = !g_fPredictiveFrameLimit.Get(); + g_LastFrameEndedAt = std::chrono::high_resolution_clock::now(); + g_FrameExecutionTime = g_LastFrameEndedAt; + } + else if(g_fPredictiveFrameLimit.Get()) { - double expectedDelta = 0.0; - + // 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::high_resolution_clock::now() - g_FrameExecutionTime; + double executionTime = std::chrono::duration_cast(loopEndTime).count() / 1000000.0; + + double waitTime = 0.0; if (SCREENMAN && SCREENMAN->GetTopScreen()) { if (SCREENMAN->GetTopScreen()->GetScreenType() == gameplay && g_fFrameLimitGameplay.Get() > 0) - expectedDelta = 1.0 / g_fFrameLimitGameplay.Get(); - else if(SCREENMAN->GetTopScreen()->GetScreenType() != gameplay && g_fFrameLimit.Get() > 0) - expectedDelta = 1.0 / g_fFrameLimit.Get(); + waitTime = 1.0 / g_fFrameLimitGameplay.Get(); + else if (SCREENMAN->GetTopScreen()->GetScreenType() != gameplay && g_fFrameLimit.Get() > 0) + waitTime = 1.0 / g_fFrameLimit.Get(); } - double advanceDelay = expectedDelta - g_LastFrameEndedAt.GetDeltaTime(); + // Not using frame limiter and vsync is enabled + // Or Frame limiter is set beyond vsync + if (PREFSMAN->m_bVsync.Get() && (waitTime == 0.0 || waitTime > iFPS)) + waitTime = 1.0 / iFPS; + + // 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() : 1.0; + double renderTime = g_LastFrameRenderTime / 1000000.0 + g_LastFramePresentTime / 1000000.0; + renderTime -= executionTime; - while (advanceDelay > 0.0) - advanceDelay -= g_LastFrameEndedAt.GetDeltaTime(); + auto afterLoop = std::chrono::high_resolution_clock::now(); + auto timeTillRender = afterLoop - g_LastFrameEndedAt; + + double expectedFrameTime = std::chrono::duration_cast(timeTillRender).count() / 1000000.0; + // Check if we have enough time to do another loop, or if that'll make us late + if (expectedFrameTime >= (waitCautiousness * (waitTime - executionTime - renderTime))) + { + presentFrame = true; + g_FrameRenderTime = std::chrono::high_resolution_clock::now(); + } + } + else + { + presentFrame = true; } } -void RageDisplay::FrameLimitAfterVsync() +// 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() { - if( g_fFrameLimitPercent.Get() == 0 && g_fFrameLimit.Get() == 0 && - g_fFrameLimitGameplay.Get() == 0) - return; + return presentFrame; +} - g_LastFrameEndedAt.Touch(); +bool RageDisplay::ShouldPresentFrame() +{ + return presentFrame; } +void RageDisplay::SetPresentTime(long long presentTime) +{ + g_LastFramePresentTime = presentTime; +} RageCompiledGeometry::~RageCompiledGeometry() { diff --git a/src/RageDisplay.h b/src/RageDisplay.h index 28e959909b..6c3402a722 100644 --- a/src/RageDisplay.h +++ b/src/RageDisplay.h @@ -183,6 +183,11 @@ class RageDisplay virtual RString GetApiDescription() const = 0; virtual void GetDisplayResolutions( DisplayResolutions &out ) const = 0; + bool ShouldRenderFrame(); + bool ShouldPresentFrame(); + void SetPresentTime(long long 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. // Return true if device was re-created and we need to reload textures. @@ -405,10 +410,8 @@ class RageDisplay const RageMatrix* GetWorldTop() const; const RageMatrix* GetTextureTop() const; - // To limit the framerate, call FrameLimitBeforeVsync before waiting - // for vsync and FrameLimitAfterVsync after. - void FrameLimitBeforeVsync( int iFPS ); - void FrameLimitAfterVsync(); + void FrameLimitBeforeVsync(); + void FrameLimitAfterVsync( int iFPS ); }; diff --git a/src/RageDisplay_D3D.cpp b/src/RageDisplay_D3D.cpp index 55d5b65895..b579d1d586 100644 --- a/src/RageDisplay_D3D.cpp +++ b/src/RageDisplay_D3D.cpp @@ -12,6 +12,7 @@ #include "EnumHelper.h" #include "DisplayResolutions.h" #include "LocalizedString.h" +#include #include #include @@ -577,14 +578,23 @@ bool RageDisplay_D3D::BeginFrame() return RageDisplay::BeginFrame(); } -static RageTimer g_LastFrameEndedAt( RageZeroTimer ); void RageDisplay_D3D::EndFrame() { g_pd3dDevice->EndScene(); - FrameLimitBeforeVsync( (*GetActualVideoModeParams()).rate ); - g_pd3dDevice->Present( 0, 0, 0, 0 ); - FrameLimitAfterVsync(); + FrameLimitBeforeVsync(); + + if (ShouldPresentFrame()) + { + auto beforePresent = std::chrono::high_resolution_clock::now(); + g_pd3dDevice->Present(0, 0, 0, 0); + auto afterPresent = std::chrono::high_resolution_clock::now(); + auto endTime = afterPresent - beforePresent; + //LOG->Info("Present took %u microseconds", std::chrono::duration_cast(endTime).count()); + SetPresentTime(std::chrono::duration_cast(endTime).count()); + } + + FrameLimitAfterVsync( (*GetActualVideoModeParams()).rate ); RageDisplay::EndFrame(); } diff --git a/src/RageDisplay_OGL.cpp b/src/RageDisplay_OGL.cpp index adb7e51a18..dfd7fbab4f 100644 --- a/src/RageDisplay_OGL.cpp +++ b/src/RageDisplay_OGL.cpp @@ -21,6 +21,7 @@ using namespace RageDisplay_Legacy_Helpers; #include "arch/LowLevelWindow/LowLevelWindow.h" #include +#include #if defined(WINDOWS) #include @@ -798,22 +799,32 @@ bool RageDisplay_Legacy::BeginFrame() void RageDisplay_Legacy::EndFrame() { - FrameLimitBeforeVsync( (*g_pWind->GetActualVideoModeParams()).rate ); - g_pWind->SwapBuffers(); - FrameLimitAfterVsync(); - - // Some would advise against glFinish(), ever. Those people don't realize - // the degree of freedom GL hosts are permitted in queueing commands. - // If left to its own devices, the host could lag behind several frames' worth - // of commands. - // glFlush() only forces the host to not wait to execute all commands - // sent so far; it does NOT block on those commands until they finish. - // glFinish() blocks. We WANT to block. Why? This puts the engine state - // reflected by the next frame as close as possible to the on-screen - // appearance of that frame. - glFinish(); - - g_pWind->Update(); + FrameLimitBeforeVsync(); + if (ShouldPresentFrame()) + { + auto beforePresent = std::chrono::high_resolution_clock::now(); + g_pWind->SwapBuffers(); + + // Some would advise against glFinish(), ever. Those people don't realize + // the degree of freedom GL hosts are permitted in queueing commands. + // If left to its own devices, the host could lag behind several frames' worth + // of commands. + // glFlush() only forces the host to not wait to execute all commands + // sent so far; it does NOT block on those commands until they finish. + // glFinish() blocks. We WANT to block. Why? This puts the engine state + // reflected by the next frame as close as possible to the on-screen + // appearance of that frame. + glFinish(); + + g_pWind->Update(); + + auto afterPresent = std::chrono::high_resolution_clock::now(); + auto endTime = afterPresent - beforePresent; + + //LOG->Info("Present took %u microseconds", std::chrono::duration_cast(endTime).count()); + SetPresentTime(std::chrono::duration_cast(endTime).count()); + } + FrameLimitAfterVsync((*GetActualVideoModeParams()).rate); RageDisplay::EndFrame(); } diff --git a/src/ScreenManager.cpp b/src/ScreenManager.cpp index 0d52c6c0f8..23642aa380 100644 --- a/src/ScreenManager.cpp +++ b/src/ScreenManager.cpp @@ -504,17 +504,19 @@ void ScreenManager::Draw() if( !DISPLAY->BeginFrame() ) return; - DISPLAY->CameraPushMatrix(); - DISPLAY->LoadMenuPerspective( 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_CENTER_X, SCREEN_CENTER_Y ); - g_pSharedBGA->Draw(); - DISPLAY->CameraPopMatrix(); - - for( unsigned i=0; iDraw(); + if (DISPLAY->ShouldRenderFrame()) + { + DISPLAY->CameraPushMatrix(); + DISPLAY->LoadMenuPerspective(0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_CENTER_X, SCREEN_CENTER_Y); + g_pSharedBGA->Draw(); + DISPLAY->CameraPopMatrix(); - for( unsigned i=0; iDraw(); + for (unsigned i = 0; iDraw(); + for (unsigned i = 0; iDraw(); + } DISPLAY->EndFrame(); } diff --git a/src/Sprite.cpp b/src/Sprite.cpp index 569b60bde5..f55f337a58 100644 --- a/src/Sprite.cpp +++ b/src/Sprite.cpp @@ -478,6 +478,9 @@ void TexCoordArrayFromRect( float fImageCoords[8], const RectF &rect ) void Sprite::DrawTexture( const TweenState *state ) { + if (!DISPLAY->ShouldRenderFrame()) + return; + Actor::SetGlobalRenderStates(); // set Actor-specified render states RectF crop = state->crop; @@ -621,6 +624,9 @@ bool Sprite::EarlyAbortDraw() const void Sprite::DrawPrimitives() { + if (!DISPLAY->ShouldRenderFrame()) + return; + if( m_pTempState->fade.top > 0 || m_pTempState->fade.bottom > 0 || m_pTempState->fade.left > 0 ||