| /* |
| * Copyright 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "Swappy.h" |
| |
| #define LOG_TAG "Swappy" |
| |
| #include <cmath> |
| #include <thread> |
| #include <cstdlib> |
| |
| #include "Settings.h" |
| #include "Thread.h" |
| #include "ChoreographerFilter.h" |
| #include "ChoreographerThread.h" |
| #include "EGL.h" |
| #include "FrameStatistics.h" |
| #include "SystemProperties.h" |
| |
| // uncomment below line to enable ALOGV messages |
| //#define SWAPPY_DEBUG |
| |
| #include "Log.h" |
| #include "Trace.h" |
| |
| namespace swappy { |
| |
| using std::chrono::milliseconds; |
| using std::chrono::nanoseconds; |
| |
| std::mutex Swappy::sInstanceMutex; |
| std::unique_ptr<Swappy> Swappy::sInstance; |
| |
| // NB These are only needed for C++14 |
| constexpr std::chrono::nanoseconds Swappy::FrameDuration::MAX_DURATION; |
| constexpr std::chrono::nanoseconds Swappy::FRAME_HYSTERESIS; |
| |
| void Swappy::init(JNIEnv *env, jobject jactivity) { |
| jclass activityClass = env->FindClass("android/app/NativeActivity"); |
| jclass windowManagerClass = env->FindClass("android/view/WindowManager"); |
| jclass displayClass = env->FindClass("android/view/Display"); |
| |
| jmethodID getWindowManager = env->GetMethodID( |
| activityClass, |
| "getWindowManager", |
| "()Landroid/view/WindowManager;"); |
| |
| jmethodID getDefaultDisplay = env->GetMethodID( |
| windowManagerClass, |
| "getDefaultDisplay", |
| "()Landroid/view/Display;"); |
| |
| jobject wm = env->CallObjectMethod(jactivity, getWindowManager); |
| jobject display = env->CallObjectMethod(wm, getDefaultDisplay); |
| |
| jmethodID getRefreshRate = env->GetMethodID( |
| displayClass, |
| "getRefreshRate", |
| "()F"); |
| |
| const float refreshRateHz = env->CallFloatMethod(display, getRefreshRate); |
| |
| jmethodID getAppVsyncOffsetNanos = env->GetMethodID( |
| displayClass, |
| "getAppVsyncOffsetNanos", "()J"); |
| |
| const long appVsyncOffsetNanos = env->CallLongMethod(display, getAppVsyncOffsetNanos); |
| |
| jmethodID getPresentationDeadlineNanos = env->GetMethodID( |
| displayClass, |
| "getPresentationDeadlineNanos", |
| "()J"); |
| |
| const long vsyncPresentationDeadlineNanos = env->CallLongMethod( |
| display, |
| getPresentationDeadlineNanos); |
| |
| const long ONE_MS_IN_NS = 1000000; |
| const long ONE_S_IN_NS = ONE_MS_IN_NS * 1000; |
| |
| const long vsyncPeriodNanos = static_cast<long>(ONE_S_IN_NS / refreshRateHz); |
| const long sfVsyncOffsetNanos = |
| vsyncPeriodNanos - (vsyncPresentationDeadlineNanos - ONE_MS_IN_NS); |
| |
| using std::chrono::nanoseconds; |
| JavaVM *vm; |
| env->GetJavaVM(&vm); |
| Swappy::init( |
| vm, |
| nanoseconds(vsyncPeriodNanos), |
| nanoseconds(appVsyncOffsetNanos), |
| nanoseconds(sfVsyncOffsetNanos)); |
| } |
| |
| void Swappy::init(JavaVM *vm, nanoseconds refreshPeriod, nanoseconds appOffset, nanoseconds sfOffset) { |
| std::lock_guard<std::mutex> lock(sInstanceMutex); |
| if (sInstance) { |
| ALOGE("Attempted to initialize Swappy twice"); |
| return; |
| } |
| sInstance = std::make_unique<Swappy>(vm, refreshPeriod, appOffset, sfOffset, ConstructorTag{}); |
| } |
| |
| void Swappy::onChoreographer(int64_t frameTimeNanos) { |
| TRACE_CALL(); |
| |
| Swappy *swappy = getInstance(); |
| if (!swappy) { |
| ALOGE("Failed to get Swappy instance in swap"); |
| return; |
| } |
| |
| if (!swappy->mUsingExternalChoreographer) { |
| swappy->mUsingExternalChoreographer = true; |
| swappy->mChoreographerThread = |
| ChoreographerThread::createChoreographerThread( |
| ChoreographerThread::Type::App, |
| nullptr, |
| [swappy] { swappy->handleChoreographer(); }); |
| } |
| |
| swappy->mChoreographerThread->postFrameCallbacks(); |
| } |
| |
| bool Swappy::swap(EGLDisplay display, EGLSurface surface) { |
| TRACE_CALL(); |
| |
| Swappy *swappy = getInstance(); |
| if (!swappy) { |
| ALOGE("Failed to get Swappy instance in swap"); |
| return EGL_FALSE; |
| } |
| |
| if (swappy->enabled()) { |
| return swappy->swapInternal(display, surface); |
| } else { |
| return eglSwapBuffers(display, surface) == EGL_TRUE; |
| } |
| } |
| |
| bool Swappy::swapInternal(EGLDisplay display, EGLSurface surface) { |
| if (!mUsingExternalChoreographer) { |
| mChoreographerThread->postFrameCallbacks(); |
| } |
| |
| // for non pipeline mode where both cpu and gpu work is done at the same stage |
| // wait for next frame will happen after swap |
| const bool needToSetPresentationTime = mPipelineMode ? |
| waitForNextFrame(display) : |
| (mAutoSwapInterval <= mAutoSwapIntervalThreshold); |
| |
| mSwapTime = std::chrono::steady_clock::now(); |
| |
| if (needToSetPresentationTime) { |
| bool setPresentationTimeResult = setPresentationTime(display, surface); |
| if (!setPresentationTimeResult) { |
| return setPresentationTimeResult; |
| } |
| } |
| |
| resetSyncFence(display); |
| |
| preSwapBuffersCallbacks(); |
| |
| bool swapBuffersResult = (eglSwapBuffers(display, surface) == EGL_TRUE); |
| |
| postSwapBuffersCallbacks(); |
| |
| if (updateSwapInterval()) { |
| swapIntervalChangedCallbacks(); |
| } |
| |
| updateSwapDuration(std::chrono::steady_clock::now() - mSwapTime); |
| |
| if (!mPipelineMode) { |
| waitForNextFrame(display); |
| } |
| |
| startFrame(); |
| |
| return swapBuffersResult; |
| } |
| |
| void Swappy::addTracer(const SwappyTracer *tracer) { |
| Swappy *swappy = getInstance(); |
| if (!swappy) { |
| ALOGE("Failed to get Swappy instance in addTracer"); |
| return; |
| } |
| swappy->addTracerCallbacks(*tracer); |
| } |
| |
| uint64_t Swappy::getSwapIntervalNS() { |
| Swappy *swappy = getInstance(); |
| if (!swappy) { |
| ALOGE("Failed to get Swappy instance in getSwapIntervalNS"); |
| return -1; |
| } |
| |
| std::lock_guard<std::mutex> lock(swappy->mFrameDurationsMutex); |
| return swappy->mAutoSwapInterval.load() * swappy->mRefreshPeriod.count(); |
| }; |
| |
| void Swappy::setAutoSwapInterval(bool enabled) { |
| Swappy *swappy = getInstance(); |
| if (!swappy) { |
| ALOGE("Failed to get Swappy instance in setAutoSwapInterval"); |
| return; |
| } |
| |
| std::lock_guard<std::mutex> lock(swappy->mFrameDurationsMutex); |
| swappy->mAutoSwapIntervalEnabled = enabled; |
| |
| // non pipeline mode is not supported when auto mode is disabled |
| if (!enabled) { |
| swappy->mPipelineMode = true; |
| } |
| } |
| |
| void Swappy::setAutoPipelineMode(bool enabled) { |
| Swappy *swappy = getInstance(); |
| if (!swappy) { |
| ALOGE("Failed to get Swappy instance in setAutoPipelineMode"); |
| return; |
| } |
| |
| std::lock_guard<std::mutex> lock(swappy->mFrameDurationsMutex); |
| swappy->mPipelineModeAutoMode = enabled; |
| if (!enabled) { |
| swappy->mPipelineMode = true; |
| } |
| } |
| |
| void Swappy::enableStats(bool enabled) { |
| Swappy *swappy = getInstance(); |
| if (!swappy) { |
| ALOGE("Failed to get Swappy instance in enableStats"); |
| return; |
| } |
| |
| if (!swappy->enabled()) { |
| return; |
| } |
| |
| if (!swappy->getEgl()->statsSupported()) { |
| ALOGI("stats are not suppored on this platform"); |
| return; |
| } |
| |
| if (enabled && swappy->mFrameStatistics == nullptr) { |
| swappy->mFrameStatistics = std::make_unique<FrameStatistics>( |
| swappy->mEgl, swappy->mRefreshPeriod); |
| ALOGI("Enabling stats"); |
| } else { |
| swappy->mFrameStatistics = nullptr; |
| ALOGI("Disabling stats"); |
| } |
| } |
| |
| void Swappy::recordFrameStart(EGLDisplay display, EGLSurface surface) { |
| TRACE_CALL(); |
| Swappy *swappy = getInstance(); |
| if (!swappy) { |
| ALOGE("Failed to get Swappy instance in recordFrameStart"); |
| return; |
| } |
| |
| if (swappy->mFrameStatistics) { |
| swappy->mFrameStatistics->capture(display, surface); |
| } else { |
| ALOGE("stats are not enabled"); |
| } |
| } |
| |
| void Swappy::getStats(Swappy_Stats *stats) { |
| Swappy *swappy = getInstance(); |
| if (!swappy) { |
| ALOGE("Failed to get Swappy instance in getStats"); |
| return; |
| } |
| |
| if (swappy->mFrameStatistics) { |
| *stats = swappy->mFrameStatistics->getStats(); |
| } else { |
| ALOGE("stats are not enabled"); |
| } |
| } |
| |
| Swappy *Swappy::getInstance() { |
| std::lock_guard<std::mutex> lock(sInstanceMutex); |
| return sInstance.get(); |
| } |
| |
| void Swappy::destroyInstance() { |
| std::lock_guard<std::mutex> lock(sInstanceMutex); |
| sInstance.reset(); |
| } |
| |
| template<typename Tracers, typename Func> void addToTracers(Tracers& tracers, Func func, void *userData) { |
| if (func != nullptr) { |
| tracers.push_back([func, userData](auto... params) { |
| func(userData, params...); |
| }); |
| } |
| } |
| |
| void Swappy::addTracerCallbacks(SwappyTracer tracer) { |
| addToTracers(mInjectedTracers.preWait, tracer.preWait, tracer.userData); |
| addToTracers(mInjectedTracers.postWait, tracer.postWait, tracer.userData); |
| addToTracers(mInjectedTracers.preSwapBuffers, tracer.preSwapBuffers, tracer.userData); |
| addToTracers(mInjectedTracers.postSwapBuffers, tracer.postSwapBuffers, tracer.userData); |
| addToTracers(mInjectedTracers.startFrame, tracer.startFrame, tracer.userData); |
| addToTracers(mInjectedTracers.swapIntervalChanged, tracer.swapIntervalChanged, tracer.userData); |
| } |
| |
| template<typename T, typename ...Args> void executeTracers(T& tracers, Args... args) { |
| for (const auto& tracer : tracers) { |
| tracer(std::forward<Args>(args)...); |
| } |
| } |
| |
| void Swappy::preSwapBuffersCallbacks() { |
| executeTracers(mInjectedTracers.preSwapBuffers); |
| } |
| |
| void Swappy::postSwapBuffersCallbacks() { |
| executeTracers(mInjectedTracers.postSwapBuffers, |
| (long) mPresentationTime.time_since_epoch().count()); |
| } |
| |
| void Swappy::preWaitCallbacks() { |
| executeTracers(mInjectedTracers.preWait); |
| } |
| |
| void Swappy::postWaitCallbacks() { |
| executeTracers(mInjectedTracers.postWait); |
| } |
| |
| void Swappy::startFrameCallbacks() { |
| executeTracers(mInjectedTracers.startFrame, |
| mCurrentFrame, |
| (long) mCurrentFrameTimestamp.time_since_epoch().count()); |
| } |
| |
| void Swappy::swapIntervalChangedCallbacks() { |
| executeTracers(mInjectedTracers.swapIntervalChanged); |
| } |
| |
| EGL *Swappy::getEgl() { |
| static thread_local EGL *egl = nullptr; |
| if (!egl) { |
| std::lock_guard<std::mutex> lock(mEglMutex); |
| egl = mEgl.get(); |
| } |
| return egl; |
| } |
| |
| Swappy::Swappy(JavaVM *vm, |
| nanoseconds refreshPeriod, |
| nanoseconds appOffset, |
| nanoseconds sfOffset, |
| ConstructorTag /*tag*/) |
| : mRefreshPeriod(refreshPeriod), |
| mFrameStatistics(nullptr), |
| mSfOffset(sfOffset), |
| mSwapDuration(std::chrono::nanoseconds(0)), |
| mSwapInterval(1), |
| mAutoSwapInterval(1) |
| { |
| mDisableSwappy = getSystemPropViaGetAsBool("swappy.disable", false); |
| if (!enabled()) { |
| ALOGI("Swappy is disabled"); |
| return; |
| } |
| |
| mChoreographerFilter = std::make_unique<ChoreographerFilter>(refreshPeriod, |
| sfOffset - appOffset, |
| [this]() { return wakeClient(); }); |
| |
| mChoreographerThread = ChoreographerThread::createChoreographerThread( |
| ChoreographerThread::Type::Swappy, |
| vm, |
| [this]{ handleChoreographer(); }); |
| |
| Settings::getInstance()->addListener([this]() { onSettingsChanged(); }); |
| |
| ALOGI("Initialized Swappy with refreshPeriod=%lld, appOffset=%lld, sfOffset=%lld", |
| refreshPeriod.count(), appOffset.count(), sfOffset.count()); |
| |
| std::lock_guard<std::mutex> lock(mEglMutex); |
| mEgl = EGL::create(refreshPeriod); |
| if (!mEgl) { |
| ALOGE("Failed to load EGL functions"); |
| exit(0); |
| } |
| |
| mAutoSwapIntervalThreshold = (1e9f / mRefreshPeriod.count()) / 20; // 20FPS |
| mFrameDurations.reserve(mFrameDurationSamples); |
| } |
| |
| void Swappy::onSettingsChanged() { |
| std::lock_guard<std::mutex> lock(mFrameDurationsMutex); |
| int32_t newSwapInterval = std::round(float(Settings::getInstance()->getSwapIntervalNS()) / |
| float(mRefreshPeriod.count())); |
| if (mSwapInterval != newSwapInterval || mAutoSwapInterval != newSwapInterval) { |
| mSwapInterval = newSwapInterval; |
| mAutoSwapInterval = mSwapInterval.load(); |
| mFrameDurations.clear(); |
| mFrameDurationsSum = {}; |
| } |
| } |
| |
| void Swappy::handleChoreographer() { |
| mChoreographerFilter->onChoreographer(); |
| } |
| |
| std::chrono::nanoseconds Swappy::wakeClient() { |
| std::lock_guard<std::mutex> lock(mWaitingMutex); |
| ++mCurrentFrame; |
| |
| // We're attempting to align with SurfaceFlinger's vsync, but it's always better to be a little |
| // late than a little early (since a little early could cause our frame to be picked up |
| // prematurely), so we pad by an additional millisecond. |
| mCurrentFrameTimestamp = std::chrono::steady_clock::now() + mSwapDuration.load() + 1ms; |
| mWaitingCondition.notify_all(); |
| return mSwapDuration; |
| } |
| |
| void Swappy::startFrame() { |
| TRACE_CALL(); |
| |
| int32_t currentFrame; |
| std::chrono::steady_clock::time_point currentFrameTimestamp; |
| { |
| std::unique_lock<std::mutex> lock(mWaitingMutex); |
| currentFrame = mCurrentFrame; |
| currentFrameTimestamp = mCurrentFrameTimestamp; |
| } |
| |
| startFrameCallbacks(); |
| |
| mTargetFrame = currentFrame + mAutoSwapInterval; |
| |
| const int intervals = (mPipelineMode) ? 2 : 1; |
| |
| // We compute the target time as now |
| // + the time the buffer will be on the GPU and in the queue to the compositor (1 swap period) |
| mPresentationTime = currentFrameTimestamp + (mAutoSwapInterval * intervals) * mRefreshPeriod; |
| |
| mStartFrameTime = std::chrono::steady_clock::now(); |
| } |
| |
| void Swappy::waitUntil(int32_t frameNumber) { |
| TRACE_CALL(); |
| std::unique_lock<std::mutex> lock(mWaitingMutex); |
| mWaitingCondition.wait(lock, [&]() { return mCurrentFrame >= frameNumber; }); |
| } |
| |
| void Swappy::waitOneFrame() { |
| TRACE_CALL(); |
| std::unique_lock<std::mutex> lock(mWaitingMutex); |
| const int32_t target = mCurrentFrame + 1; |
| mWaitingCondition.wait(lock, [&]() { return mCurrentFrame >= target; }); |
| } |
| |
| void Swappy::addFrameDuration(FrameDuration duration) { |
| ALOGV("cpuTime = %.2f", duration.getCpuTime().count() / 1e6f); |
| ALOGV("gpuTime = %.2f", duration.getGpuTime().count() / 1e6f); |
| |
| std::lock_guard<std::mutex> lock(mFrameDurationsMutex); |
| // keep a sliding window of mFrameDurationSamples |
| if (mFrameDurations.size() == mFrameDurationSamples) { |
| mFrameDurationsSum -= mFrameDurations.front(); |
| mFrameDurations.erase(mFrameDurations.begin()); |
| } |
| |
| mFrameDurations.push_back(duration); |
| mFrameDurationsSum += duration; |
| } |
| |
| int32_t Swappy::nanoToSwapInterval(std::chrono::nanoseconds nano) { |
| int32_t interval = nano / mRefreshPeriod; |
| |
| // round the number based on the nearest |
| if (nano.count() - (interval * mRefreshPeriod.count()) > mRefreshPeriod.count() / 2) { |
| return interval + 1; |
| } else { |
| return interval; |
| } |
| } |
| |
| bool Swappy::waitForNextFrame(EGLDisplay display) { |
| preWaitCallbacks(); |
| |
| int lateFrames = 0; |
| bool needToSetPresentationTime; |
| |
| std::chrono::nanoseconds cpuTime = std::chrono::steady_clock::now() - mStartFrameTime; |
| std::chrono::nanoseconds gpuTime; |
| |
| // if we are running slower than the threshold there is no point to sleep, just let the |
| // app run as fast as it can |
| if (mAutoSwapInterval <= mAutoSwapIntervalThreshold) { |
| waitUntil(mTargetFrame); |
| |
| // wait for the previous frame to be rendered |
| while (!getEgl()->lastFrameIsComplete(display)) { |
| gamesdk::ScopedTrace trace("lastFrameIncomplete"); |
| ALOGV("lastFrameIncomplete"); |
| lateFrames++; |
| waitOneFrame(); |
| } |
| |
| gpuTime = getEgl()->getFencePendingTime(); |
| |
| mPresentationTime += lateFrames * mRefreshPeriod; |
| needToSetPresentationTime = true; |
| |
| } else { |
| needToSetPresentationTime = false; |
| gpuTime = getEgl()->getFencePendingTime(); |
| |
| } |
| addFrameDuration({cpuTime, gpuTime}); |
| |
| postWaitCallbacks(); |
| return needToSetPresentationTime; |
| } |
| |
| void Swappy::swapSlower(const FrameDuration& averageFrameTime, |
| const std::chrono::nanoseconds& upperBound, |
| const std::chrono::nanoseconds& lowerBound, |
| const int32_t& newSwapInterval) { |
| ALOGV("Rendering takes too much time for the given config"); |
| |
| if (!mPipelineMode && averageFrameTime.getTime(true) <= upperBound) { |
| ALOGV("turning on pipelining"); |
| mPipelineMode = true; |
| } else { |
| mAutoSwapInterval = newSwapInterval; |
| ALOGV("Changing Swap interval to %d", mAutoSwapInterval.load()); |
| |
| // since we changed the swap interval, we may be able to turn off pipeline mode |
| nanoseconds newBound = mRefreshPeriod * mAutoSwapInterval.load(); |
| newBound -= (FRAME_HYSTERESIS * 2); |
| if (mPipelineModeAutoMode && averageFrameTime.getTime(false) < newBound) { |
| ALOGV("Turning off pipelining"); |
| mPipelineMode = false; |
| } else { |
| ALOGV("Turning on pipelining"); |
| mPipelineMode = true; |
| } |
| } |
| } |
| |
| void Swappy::swapFaster(const FrameDuration& averageFrameTime, |
| const std::chrono::nanoseconds& upperBound, |
| const std::chrono::nanoseconds& lowerBound, |
| const int32_t& newSwapInterval) { |
| ALOGV("Rendering is much shorter for the given config"); |
| mAutoSwapInterval = newSwapInterval; |
| ALOGV("Changing Swap interval to %d", mAutoSwapInterval.load()); |
| |
| // since we changed the swap interval, we may need to turn on pipeline mode |
| nanoseconds newBound = mRefreshPeriod * mAutoSwapInterval.load(); |
| newBound -= FRAME_HYSTERESIS; |
| if (!mPipelineModeAutoMode || averageFrameTime.getTime(false) > newBound) { |
| ALOGV("Turning on pipelining"); |
| mPipelineMode = true; |
| } else { |
| ALOGV("Turning off pipelining"); |
| mPipelineMode = false; |
| } |
| } |
| |
| bool Swappy::updateSwapInterval() { |
| std::lock_guard<std::mutex> lock(mFrameDurationsMutex); |
| if (!mAutoSwapIntervalEnabled) |
| return false; |
| |
| if (mFrameDurations.size() < mFrameDurationSamples) |
| return false; |
| |
| const auto averageFrameTime = mFrameDurationsSum / mFrameDurations.size(); |
| // define lower and upper bound based on the swap duration |
| nanoseconds upperBound = mRefreshPeriod * mAutoSwapInterval.load(); |
| nanoseconds lowerBound = mRefreshPeriod * (mAutoSwapInterval - 1); |
| |
| // to be on the conservative side, lower bounds by FRAME_HYSTERESIS |
| upperBound -= FRAME_HYSTERESIS; |
| lowerBound -= FRAME_HYSTERESIS; |
| |
| // add the hysteresis to one of the bounds to avoid going back and forth when frames |
| // are exactly at the edge. |
| lowerBound -= FRAME_HYSTERESIS; |
| |
| auto div_result = std::div((averageFrameTime.getTime(true) + FRAME_HYSTERESIS).count(), |
| mRefreshPeriod.count()); |
| auto framesPerRefresh = div_result.quot; |
| auto framesPerRefreshRemainder = div_result.rem; |
| |
| const int32_t newSwapInterval = framesPerRefresh + (framesPerRefreshRemainder ? 1 : 0); |
| |
| ALOGV("mPipelineMode = %d", mPipelineMode); |
| ALOGV("Average cpu frame time = %.2f", (averageFrameTime.getCpuTime().count()) / 1e6f); |
| ALOGV("Average gpu frame time = %.2f", (averageFrameTime.getGpuTime().count()) / 1e6f); |
| ALOGV("upperBound = %.2f", upperBound.count() / 1e6f); |
| ALOGV("lowerBound = %.2f", lowerBound.count() / 1e6f); |
| |
| bool configChanged = false; |
| if (averageFrameTime.getTime(mPipelineMode) > upperBound) { |
| swapSlower(averageFrameTime, upperBound, lowerBound, newSwapInterval); |
| configChanged = true; |
| } else if (mSwapInterval < mAutoSwapInterval && |
| (averageFrameTime.getTime(true) < lowerBound)) { |
| swapFaster(averageFrameTime, upperBound, lowerBound, newSwapInterval); |
| configChanged = true; |
| } else if (mPipelineModeAutoMode && mPipelineMode && |
| averageFrameTime.getTime(false) < upperBound - FRAME_HYSTERESIS) { |
| ALOGV("Rendering time fits the current swap interval without pipelining"); |
| mPipelineMode = false; |
| configChanged = true; |
| } |
| |
| if (configChanged) { |
| mFrameDurationsSum = {}; |
| mFrameDurations.clear(); |
| } |
| return configChanged; |
| } |
| |
| void Swappy::resetSyncFence(EGLDisplay display) { |
| getEgl()->resetSyncFence(display); |
| } |
| |
| bool Swappy::setPresentationTime(EGLDisplay display, EGLSurface surface) { |
| TRACE_CALL(); |
| |
| // if we are too close to the vsync, there is no need to set presentation time |
| if ((mPresentationTime - std::chrono::steady_clock::now()) < |
| (mRefreshPeriod - mSfOffset)) { |
| return EGL_TRUE; |
| } |
| |
| return getEgl()->setPresentationTime(display, surface, mPresentationTime); |
| } |
| |
| void Swappy::updateSwapDuration(std::chrono::nanoseconds duration) { |
| // TODO: The exponential smoothing factor here is arbitrary |
| mSwapDuration = (mSwapDuration.load() * 4 / 5) + duration / 5; |
| |
| // Clamp the swap duration to half the refresh period |
| // |
| // We do this since the swap duration can be a bit noisy during periods such as app startup, |
| // which can cause some stuttering as the smoothing catches up with the actual duration. By |
| // clamping, we reduce the maximum error which reduces the calibration time. |
| if (mSwapDuration.load() > (mRefreshPeriod / 2)) mSwapDuration = mRefreshPeriod / 2; |
| } |
| |
| } // namespace swappy |