diff --git a/desmume/src/frontend/cocoa/ClientExecutionControl.cpp b/desmume/src/frontend/cocoa/ClientExecutionControl.cpp index 6ca884195..7a45aa864 100644 --- a/desmume/src/frontend/cocoa/ClientExecutionControl.cpp +++ b/desmume/src/frontend/cocoa/ClientExecutionControl.cpp @@ -55,6 +55,9 @@ ClientExecutionControl::ClientExecutionControl() _frameTime = 0.0; _framesToSkip = 0; + _lastSetFrameSkip = 0.0; + _unskipStep = 0; + _dynamicBiasStep = 0; _prevExecBehavior = ExecutionBehavior_Pause; _isGdbStubStarted = false; @@ -643,6 +646,11 @@ bool ClientExecutionControl::GetEnableSpeedLimiter() return enable; } +bool ClientExecutionControl::GetEnableSpeedLimiterApplied() +{ + return this->_settingsApplied.enableExecutionSpeedLimiter; +} + void ClientExecutionControl::SetEnableSpeedLimiter(bool enable) { pthread_mutex_lock(&this->_mutexSettingsPendingOnExecutionLoopStart); @@ -661,6 +669,11 @@ double ClientExecutionControl::GetExecutionSpeed() return speedScalar; } +double ClientExecutionControl::GetExecutionSpeedApplied() +{ + return this->_settingsApplied.executionSpeed; +} + void ClientExecutionControl::SetExecutionSpeed(double speedScalar) { pthread_mutex_lock(&this->_mutexSettingsPendingOnExecutionLoopStart); @@ -1242,68 +1255,70 @@ double ClientExecutionControl::GetFrameTime() uint8_t ClientExecutionControl::CalculateFrameSkip(double startAbsoluteTime, double frameAbsoluteTime) { - static const double skipCurve[10] = {0.60, 0.58, 0.55, 0.51, 0.46, 0.40, 0.30, 0.20, 0.10, 0.00}; - static const double unskipCurve[10] = {0.75, 0.70, 0.65, 0.60, 0.50, 0.40, 0.30, 0.20, 0.10, 0.00}; - static size_t skipStep = 0; - static size_t unskipStep = 0; - static uint64_t lastSetFrameSkip = 0; + static const double unskipCurve[21] = {0.98, 0.95, 0.91, 0.86, 0.80, 0.73, 0.65, 0.56, 0.46, 0.35, 0.23, 0.20, 0.17, 0.14, 0.11, 0.08, 0.06, 0.04, 0.02, 0.01, 0.00}; + static const double dynamicBiasCurve[15] = {0.0, 0.2, 0.6, 1.2, 2.0, 3.0, 4.2, 5.6, 7.2, 9.0, 11.0, 13.2, 15.6, 18.2, 20.0}; // Calculate the time remaining. const double elapsed = this->GetCurrentAbsoluteTime() - startAbsoluteTime; - uint64_t framesToSkip = 0; + uint64_t framesToSkipInt = 0; if (elapsed > frameAbsoluteTime) { if (frameAbsoluteTime > 0) { - framesToSkip = (uint64_t)( (((elapsed - frameAbsoluteTime) * FRAME_SKIP_AGGRESSIVENESS) / frameAbsoluteTime) + FRAME_SKIP_BIAS ); + const double framesToSkipReal = ((elapsed * FRAME_SKIP_AGGRESSIVENESS) / frameAbsoluteTime) + dynamicBiasCurve[this->_dynamicBiasStep] + FRAME_SKIP_BIAS; + framesToSkipInt = (uint64_t)(framesToSkipReal + 0.5); - if (framesToSkip > lastSetFrameSkip) + const double frameSkipDiff = framesToSkipReal - this->_lastSetFrameSkip; + if (this->_unskipStep > 0) { - framesToSkip -= (uint64_t)((double)(framesToSkip - lastSetFrameSkip) * skipCurve[skipStep]); - if (skipStep < 9) + if (this->_dynamicBiasStep > 0) { - skipStep++; + this->_dynamicBiasStep--; } } - else + else if (frameSkipDiff > 0.0) { - framesToSkip += (uint64_t)((double)(lastSetFrameSkip - framesToSkip) * skipCurve[skipStep]); - if (skipStep > 0) + if (this->_dynamicBiasStep < 14) { - skipStep--; + this->_dynamicBiasStep++; } } + + this->_unskipStep = 0; + this->_lastSetFrameSkip = framesToSkipReal; } else { static const double frameRate100x = (double)FRAME_SKIP_AGGRESSIVENESS / CalculateFrameAbsoluteTime(1.0/100.0); - framesToSkip = (uint64_t)(elapsed * frameRate100x); + framesToSkipInt = (uint64_t)(elapsed * frameRate100x); } - - unskipStep = 0; } else { - framesToSkip = (uint64_t)((double)lastSetFrameSkip * unskipCurve[unskipStep]); - if (unskipStep < 9) + const double framesToSkipReal = this->_lastSetFrameSkip * unskipCurve[this->_unskipStep]; + framesToSkipInt = (uint64_t)(framesToSkipReal + 0.5); + + if (this->_unskipStep < 20) { - unskipStep++; + this->_unskipStep++; } - skipStep = 0; + if (framesToSkipInt == 0) + { + this->_lastSetFrameSkip = 0.0; + this->_unskipStep = 20; + } } // Bound the frame skip. static const uint64_t kMaxFrameSkip = (uint64_t)MAX_FRAME_SKIP; - if (framesToSkip > kMaxFrameSkip) + if (framesToSkipInt > kMaxFrameSkip) { - framesToSkip = kMaxFrameSkip; + framesToSkipInt = kMaxFrameSkip; } - lastSetFrameSkip = framesToSkip; - - return (uint8_t)framesToSkip; + return (uint8_t)framesToSkipInt; } double ClientExecutionControl::GetCurrentAbsoluteTime() diff --git a/desmume/src/frontend/cocoa/ClientExecutionControl.h b/desmume/src/frontend/cocoa/ClientExecutionControl.h index 2d7fac2a6..d416a71aa 100644 --- a/desmume/src/frontend/cocoa/ClientExecutionControl.h +++ b/desmume/src/frontend/cocoa/ClientExecutionControl.h @@ -40,11 +40,14 @@ #define DS_FRAMES_PER_SECOND 59.8261 // Number of DS frames per second. #define DS_SECONDS_PER_FRAME (1.0 / DS_FRAMES_PER_SECOND) // The length of time in seconds that, ideally, a frame should be processed within. -#define FRAME_SKIP_AGGRESSIVENESS 9.0 // Must be a value between 0.0 (inclusive) and positive infinity. +#define FRAME_SKIP_AGGRESSIVENESS 1.0 // Must be a value between 0.0 (inclusive) and positive infinity. // This value acts as a scalar multiple of the frame skip. -#define FRAME_SKIP_BIAS 0.1 // May be any real number. This value acts as a vector addition to the frame skip. +#define FRAME_SKIP_BIAS 0.9 // May be any real number. This value acts as a vector addition to the frame skip. #define MAX_FRAME_SKIP (DS_FRAMES_PER_SECOND / 2.98) +#define EXECUTION_WAIT_BIAS_MIN 0.70 +#define EXECUTION_WAIT_BIAS_MAX 1.10 + class ClientAVCaptureObject; enum ExecutionBehavior @@ -215,6 +218,9 @@ protected: double _frameTime; uint8_t _framesToSkip; + double _lastSetFrameSkip; + size_t _unskipStep; + size_t _dynamicBiasStep; ExecutionBehavior _prevExecBehavior; bool _isGdbStubStarted; @@ -312,9 +318,11 @@ public: void SetEnableCheats(bool enable); bool GetEnableSpeedLimiter(); + bool GetEnableSpeedLimiterApplied(); void SetEnableSpeedLimiter(bool enable); double GetExecutionSpeed(); + double GetExecutionSpeedApplied(); void SetExecutionSpeed(double speedScalar); bool GetEnableFrameSkip(); diff --git a/desmume/src/frontend/cocoa/cocoa_core.mm b/desmume/src/frontend/cocoa/cocoa_core.mm index 5560e5e8b..ef91088d2 100644 --- a/desmume/src/frontend/cocoa/cocoa_core.mm +++ b/desmume/src/frontend/cocoa/cocoa_core.mm @@ -1060,10 +1060,17 @@ static void* RunCoreThread(void *arg) double frameTime = 0.0; // The amount of time that is expected for the frame to run. const double standardNDSFrameTime = execControl->CalculateFrameAbsoluteTime(1.0); + double lastSelectedExecSpeedSelected = 1.0; double executionSpeedAverage = 0.0; double executionSpeedAverageFramesCollected = 0.0; + double executionWaitBias = 1.0; + double lastExecutionWaitBias = 1.0; + double lastExecutionSpeedDifference = 0.0; + bool needRestoreExecutionWaitBias = false; + bool lastExecutionSpeedLimitEnable = execControl->GetEnableSpeedLimiter(); ExecutionBehavior behavior = ExecutionBehavior_Pause; + ExecutionBehavior lastBehavior = ExecutionBehavior_Pause; uint64_t frameJumpTarget = 0; [[[cdsCore cdsGPU] sharedData] semaphoreFramebufferCreate]; @@ -1085,6 +1092,21 @@ static void* RunCoreThread(void *arg) behavior = execControl->GetExecutionBehaviorApplied(); } + if ( (lastBehavior == ExecutionBehavior_Run) && (behavior != ExecutionBehavior_Run) ) + { + lastExecutionWaitBias = executionWaitBias; + needRestoreExecutionWaitBias = true; + } + + if ( (behavior == ExecutionBehavior_Run) && lastExecutionSpeedLimitEnable && !execControl->GetEnableSpeedLimiter() ) + { + lastExecutionWaitBias = executionWaitBias; + } + else if ( (behavior == ExecutionBehavior_Run) && !lastExecutionSpeedLimitEnable && execControl->GetEnableSpeedLimiter() ) + { + needRestoreExecutionWaitBias = true; + } + frameTime = execControl->GetFrameTime(); frameJumpTarget = execControl->GetFrameJumpTargetApplied(); @@ -1144,7 +1166,39 @@ static void* RunCoreThread(void *arg) { if (executionSpeedAverageFramesCollected > 0.0001) { - execControl->SetFrameInfoExecutionSpeed((executionSpeedAverage / executionSpeedAverageFramesCollected) * 100.0); + const double execSpeedSelected = execControl->GetExecutionSpeedApplied(); + const double execSpeedCurrent = (executionSpeedAverage / executionSpeedAverageFramesCollected); + const double execSpeedDifference = execSpeedSelected - execSpeedCurrent; + + execControl->SetFrameInfoExecutionSpeed(execSpeedCurrent * 100.0); + + if ( (behavior == ExecutionBehavior_Run) && needRestoreExecutionWaitBias ) + { + executionWaitBias = lastExecutionWaitBias; + needRestoreExecutionWaitBias = false; + } + else + { + if (lastSelectedExecSpeedSelected == execSpeedSelected) + { + executionWaitBias -= execSpeedDifference; + lastExecutionSpeedDifference = execSpeedDifference; + + if (executionWaitBias < EXECUTION_WAIT_BIAS_MIN) + { + executionWaitBias = EXECUTION_WAIT_BIAS_MIN; + } + else if (executionWaitBias > EXECUTION_WAIT_BIAS_MAX) + { + executionWaitBias = EXECUTION_WAIT_BIAS_MAX; + } + } + else + { + executionWaitBias = 1.0; + lastSelectedExecSpeedSelected = execSpeedSelected; + } + } } executionSpeedAverage = 0.0; @@ -1200,7 +1254,8 @@ static void* RunCoreThread(void *arg) } else { - execControl->SetFramesToSkip( execControl->CalculateFrameSkip(startTime, frameTime) ); + const double frameTimeBias = (lastExecutionSpeedDifference > 0.0) ? 1.0 - lastExecutionSpeedDifference : 1.0; + execControl->SetFramesToSkip( execControl->CalculateFrameSkip(startTime, frameTime * frameTimeBias) ); } } break; @@ -1249,9 +1304,14 @@ static void* RunCoreThread(void *arg) else { // If there is any time left in the loop, go ahead and pad it. - if ( (execControl->GetCurrentAbsoluteTime() - startTime) < frameTime ) + const double biasedFrameTime = frameTime * executionWaitBias; + + if (biasedFrameTime > 0.0) { - execControl->WaitUntilAbsoluteTime(startTime + frameTime); + if ( (execControl->GetCurrentAbsoluteTime() - startTime) < frameTime ) + { + execControl->WaitUntilAbsoluteTime(startTime + biasedFrameTime); + } } } @@ -1259,6 +1319,8 @@ static void* RunCoreThread(void *arg) const double currentExecutionSpeed = standardNDSFrameTime / (endTime - startTime); executionSpeedAverage += currentExecutionSpeed; executionSpeedAverageFramesCollected += 1.0; + lastBehavior = behavior; + lastExecutionSpeedLimitEnable = execControl->GetEnableSpeedLimiterApplied(); } while(true);