• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "Swappy.h"
18 
19 #define LOG_TAG "Swappy"
20 
21 #include <cmath>
22 #include <thread>
23 #include <cstdlib>
24 #include <cinttypes>
25 
26 #include "Settings.h"
27 #include "Thread.h"
28 #include "ChoreographerFilter.h"
29 #include "ChoreographerThread.h"
30 #include "EGL.h"
31 #include "FrameStatistics.h"
32 #include "SystemProperties.h"
33 
34 // uncomment below line to enable ALOGV messages
35 //#define SWAPPY_DEBUG
36 
37 #include "Log.h"
38 #include "Trace.h"
39 
40 namespace swappy {
41 
42 using std::chrono::milliseconds;
43 using std::chrono::nanoseconds;
44 
45 std::mutex Swappy::sInstanceMutex;
46 std::unique_ptr<Swappy> Swappy::sInstance;
47 
48 // NB These are only needed for C++14
49 constexpr std::chrono::nanoseconds Swappy::FrameDuration::MAX_DURATION;
50 constexpr std::chrono::nanoseconds Swappy::FRAME_HYSTERESIS;
51 
init(JNIEnv * env,jobject jactivity)52 void Swappy::init(JNIEnv *env, jobject jactivity) {
53     jclass activityClass = env->FindClass("android/app/NativeActivity");
54     jclass windowManagerClass = env->FindClass("android/view/WindowManager");
55     jclass displayClass = env->FindClass("android/view/Display");
56 
57     jmethodID getWindowManager = env->GetMethodID(
58             activityClass,
59             "getWindowManager",
60             "()Landroid/view/WindowManager;");
61 
62     jmethodID getDefaultDisplay = env->GetMethodID(
63             windowManagerClass,
64             "getDefaultDisplay",
65             "()Landroid/view/Display;");
66 
67     jobject wm = env->CallObjectMethod(jactivity, getWindowManager);
68     jobject display = env->CallObjectMethod(wm, getDefaultDisplay);
69 
70     jmethodID getRefreshRate = env->GetMethodID(
71             displayClass,
72             "getRefreshRate",
73             "()F");
74 
75     const float refreshRateHz = env->CallFloatMethod(display, getRefreshRate);
76 
77     jmethodID getAppVsyncOffsetNanos = env->GetMethodID(
78             displayClass,
79             "getAppVsyncOffsetNanos", "()J");
80 
81     // getAppVsyncOffsetNanos was only added in API 21.
82     // Return gracefully if this device doesn't support it.
83     if (getAppVsyncOffsetNanos == 0 || env->ExceptionOccurred()) {
84         env->ExceptionClear();
85         return;
86     }
87     const long appVsyncOffsetNanos = env->CallLongMethod(display, getAppVsyncOffsetNanos);
88 
89     jmethodID getPresentationDeadlineNanos = env->GetMethodID(
90             displayClass,
91             "getPresentationDeadlineNanos",
92             "()J");
93 
94     const long vsyncPresentationDeadlineNanos = env->CallLongMethod(
95             display,
96             getPresentationDeadlineNanos);
97 
98     const long ONE_MS_IN_NS = 1000000;
99     const long ONE_S_IN_NS = ONE_MS_IN_NS * 1000;
100 
101     const long vsyncPeriodNanos = static_cast<long>(ONE_S_IN_NS / refreshRateHz);
102     const long sfVsyncOffsetNanos =
103             vsyncPeriodNanos - (vsyncPresentationDeadlineNanos - ONE_MS_IN_NS);
104 
105     using std::chrono::nanoseconds;
106     JavaVM *vm;
107     env->GetJavaVM(&vm);
108     Swappy::init(
109             vm,
110             nanoseconds(vsyncPeriodNanos),
111             nanoseconds(appVsyncOffsetNanos),
112             nanoseconds(sfVsyncOffsetNanos));
113 }
114 
init(JavaVM * vm,nanoseconds refreshPeriod,nanoseconds appOffset,nanoseconds sfOffset)115 void Swappy::init(JavaVM *vm, nanoseconds refreshPeriod, nanoseconds appOffset, nanoseconds sfOffset) {
116     std::lock_guard<std::mutex> lock(sInstanceMutex);
117     if (sInstance) {
118         ALOGE("Attempted to initialize Swappy twice");
119         return;
120     }
121     sInstance = std::make_unique<Swappy>(vm, refreshPeriod, appOffset, sfOffset, ConstructorTag{});
122 }
123 
onChoreographer(int64_t frameTimeNanos)124 void Swappy::onChoreographer(int64_t frameTimeNanos) {
125     TRACE_CALL();
126 
127     Swappy *swappy = getInstance();
128     if (!swappy) {
129         ALOGE("Failed to get Swappy instance in swap");
130         return;
131     }
132 
133     if (!swappy->mUsingExternalChoreographer) {
134         swappy->mUsingExternalChoreographer = true;
135         swappy->mChoreographerThread =
136                 ChoreographerThread::createChoreographerThread(
137                         ChoreographerThread::Type::App,
138                         nullptr,
139                         [swappy] { swappy->handleChoreographer(); });
140     }
141 
142     swappy->mChoreographerThread->postFrameCallbacks();
143 }
144 
swap(EGLDisplay display,EGLSurface surface)145 bool Swappy::swap(EGLDisplay display, EGLSurface surface) {
146     TRACE_CALL();
147 
148     Swappy *swappy = getInstance();
149     if (!swappy) {
150         ALOGE("Failed to get Swappy instance in swap");
151         return EGL_FALSE;
152     }
153 
154     if (swappy->enabled()) {
155         return swappy->swapInternal(display, surface);
156     } else {
157         return eglSwapBuffers(display, surface) == EGL_TRUE;
158     }
159 }
160 
swapInternal(EGLDisplay display,EGLSurface surface)161 bool Swappy::swapInternal(EGLDisplay display, EGLSurface surface) {
162     if (!mUsingExternalChoreographer) {
163         mChoreographerThread->postFrameCallbacks();
164     }
165 
166     // for non pipeline mode where both cpu and gpu work is done at the same stage
167     // wait for next frame will happen after swap
168     const bool needToSetPresentationTime = mPipelineMode ?
169             waitForNextFrame(display) :
170             (mAutoSwapInterval <= mAutoSwapIntervalThreshold);
171 
172     mSwapTime = std::chrono::steady_clock::now();
173 
174     if (needToSetPresentationTime) {
175         bool setPresentationTimeResult = setPresentationTime(display, surface);
176         if (!setPresentationTimeResult) {
177             return setPresentationTimeResult;
178         }
179     }
180 
181     resetSyncFence(display);
182 
183     preSwapBuffersCallbacks();
184 
185     bool swapBuffersResult = (eglSwapBuffers(display, surface) == EGL_TRUE);
186 
187     postSwapBuffersCallbacks();
188 
189     if (updateSwapInterval()) {
190         swapIntervalChangedCallbacks();
191     }
192 
193     updateSwapDuration(std::chrono::steady_clock::now() - mSwapTime);
194 
195     if (!mPipelineMode) {
196         waitForNextFrame(display);
197     }
198 
199     startFrame();
200 
201     return swapBuffersResult;
202 }
203 
addTracer(const SwappyTracer * tracer)204 void Swappy::addTracer(const SwappyTracer *tracer) {
205     Swappy *swappy = getInstance();
206     if (!swappy) {
207         ALOGE("Failed to get Swappy instance in addTracer");
208         return;
209     }
210     swappy->addTracerCallbacks(*tracer);
211 }
212 
getSwapIntervalNS()213 uint64_t Swappy::getSwapIntervalNS() {
214     Swappy *swappy = getInstance();
215     if (!swappy) {
216         ALOGE("Failed to get Swappy instance in getSwapIntervalNS");
217         return -1;
218     }
219 
220     std::lock_guard<std::mutex> lock(swappy->mFrameDurationsMutex);
221     return swappy->mAutoSwapInterval.load() * swappy->mRefreshPeriod.count();
222 };
223 
setAutoSwapInterval(bool enabled)224 void Swappy::setAutoSwapInterval(bool enabled) {
225     Swappy *swappy = getInstance();
226     if (!swappy) {
227         ALOGE("Failed to get Swappy instance in setAutoSwapInterval");
228         return;
229     }
230 
231     std::lock_guard<std::mutex> lock(swappy->mFrameDurationsMutex);
232     swappy->mAutoSwapIntervalEnabled = enabled;
233 
234     // non pipeline mode is not supported when auto mode is disabled
235     if (!enabled) {
236         swappy->mPipelineMode = true;
237     }
238 }
239 
setAutoPipelineMode(bool enabled)240 void Swappy::setAutoPipelineMode(bool enabled) {
241     Swappy *swappy = getInstance();
242     if (!swappy) {
243         ALOGE("Failed to get Swappy instance in setAutoPipelineMode");
244         return;
245     }
246 
247     std::lock_guard<std::mutex> lock(swappy->mFrameDurationsMutex);
248     swappy->mPipelineModeAutoMode = enabled;
249     if (!enabled) {
250         swappy->mPipelineMode = true;
251     }
252 }
253 
enableStats(bool enabled)254 void Swappy::enableStats(bool enabled) {
255     Swappy *swappy = getInstance();
256     if (!swappy) {
257         ALOGE("Failed to get Swappy instance in enableStats");
258             return;
259     }
260 
261     if (!swappy->enabled()) {
262         return;
263     }
264 
265     if (!swappy->getEgl()->statsSupported()) {
266         ALOGI("stats are not suppored on this platform");
267         return;
268     }
269 
270     if (enabled && swappy->mFrameStatistics == nullptr) {
271         swappy->mFrameStatistics = std::make_unique<FrameStatistics>(
272                 swappy->mEgl, swappy->mRefreshPeriod);
273         ALOGI("Enabling stats");
274     } else {
275         swappy->mFrameStatistics = nullptr;
276         ALOGI("Disabling stats");
277     }
278 }
279 
recordFrameStart(EGLDisplay display,EGLSurface surface)280 void Swappy::recordFrameStart(EGLDisplay display, EGLSurface surface) {
281     TRACE_CALL();
282     Swappy *swappy = getInstance();
283     if (!swappy) {
284         ALOGE("Failed to get Swappy instance in recordFrameStart");
285         return;
286     }
287 
288     if (swappy->mFrameStatistics)
289         swappy->mFrameStatistics->capture(display, surface);
290 }
291 
getStats(Swappy_Stats * stats)292 void Swappy::getStats(Swappy_Stats *stats) {
293     Swappy *swappy = getInstance();
294     if (!swappy) {
295         ALOGE("Failed to get Swappy instance in getStats");
296         return;
297     }
298 
299     if (swappy->mFrameStatistics)
300         *stats = swappy->mFrameStatistics->getStats();
301 }
302 
getInstance()303 Swappy *Swappy::getInstance() {
304     std::lock_guard<std::mutex> lock(sInstanceMutex);
305     return sInstance.get();
306 }
307 
isEnabled()308 bool Swappy::isEnabled() {
309     Swappy *swappy = getInstance();
310     if (!swappy) {
311         ALOGE("Failed to get Swappy instance in getStats");
312         return false;
313     }
314     return swappy->enabled();
315 }
316 
destroyInstance()317 void Swappy::destroyInstance() {
318     std::lock_guard<std::mutex> lock(sInstanceMutex);
319     sInstance.reset();
320 }
321 
addToTracers(Tracers & tracers,Func func,void * userData)322 template<typename Tracers, typename Func> void addToTracers(Tracers& tracers, Func func, void *userData) {
323     if (func != nullptr) {
324         tracers.push_back([func, userData](auto... params) {
325             func(userData, params...);
326         });
327     }
328 }
329 
addTracerCallbacks(SwappyTracer tracer)330 void Swappy::addTracerCallbacks(SwappyTracer tracer) {
331     addToTracers(mInjectedTracers.preWait, tracer.preWait, tracer.userData);
332     addToTracers(mInjectedTracers.postWait, tracer.postWait, tracer.userData);
333     addToTracers(mInjectedTracers.preSwapBuffers, tracer.preSwapBuffers, tracer.userData);
334     addToTracers(mInjectedTracers.postSwapBuffers, tracer.postSwapBuffers, tracer.userData);
335     addToTracers(mInjectedTracers.startFrame, tracer.startFrame, tracer.userData);
336     addToTracers(mInjectedTracers.swapIntervalChanged, tracer.swapIntervalChanged, tracer.userData);
337 }
338 
executeTracers(T & tracers,Args...args)339 template<typename T, typename ...Args> void executeTracers(T& tracers, Args... args) {
340     for (const auto& tracer : tracers) {
341         tracer(std::forward<Args>(args)...);
342     }
343 }
344 
preSwapBuffersCallbacks()345 void Swappy::preSwapBuffersCallbacks() {
346     executeTracers(mInjectedTracers.preSwapBuffers);
347 }
348 
postSwapBuffersCallbacks()349 void Swappy::postSwapBuffersCallbacks() {
350     executeTracers(mInjectedTracers.postSwapBuffers,
351                    (long) mPresentationTime.time_since_epoch().count());
352 }
353 
preWaitCallbacks()354 void Swappy::preWaitCallbacks() {
355     executeTracers(mInjectedTracers.preWait);
356 }
357 
postWaitCallbacks()358 void Swappy::postWaitCallbacks() {
359     executeTracers(mInjectedTracers.postWait);
360 }
361 
startFrameCallbacks()362 void Swappy::startFrameCallbacks() {
363     executeTracers(mInjectedTracers.startFrame,
364                    mCurrentFrame,
365                    (long) mCurrentFrameTimestamp.time_since_epoch().count());
366 }
367 
swapIntervalChangedCallbacks()368 void Swappy::swapIntervalChangedCallbacks() {
369     executeTracers(mInjectedTracers.swapIntervalChanged);
370 }
371 
getEgl()372 EGL *Swappy::getEgl() {
373     static thread_local EGL *egl = nullptr;
374     if (!egl) {
375         std::lock_guard<std::mutex> lock(mEglMutex);
376         egl = mEgl.get();
377     }
378     return egl;
379 }
380 
Swappy(JavaVM * vm,nanoseconds refreshPeriod,nanoseconds appOffset,nanoseconds sfOffset,ConstructorTag)381 Swappy::Swappy(JavaVM *vm,
382                nanoseconds refreshPeriod,
383                nanoseconds appOffset,
384                nanoseconds sfOffset,
385                ConstructorTag /*tag*/)
386     : mRefreshPeriod(refreshPeriod),
387       mFrameStatistics(nullptr),
388       mSfOffset(sfOffset),
389       mSwapDuration(std::chrono::nanoseconds(0)),
390       mSwapInterval(1),
391       mAutoSwapInterval(1)
392 {
393     mDisableSwappy = getSystemPropViaGetAsBool("swappy.disable", false);
394     if (!enabled()) {
395         ALOGI("Swappy is disabled");
396         return;
397     }
398 
399     std::lock_guard<std::mutex> lock(mEglMutex);
400     mEgl = EGL::create(refreshPeriod);
401     if (!mEgl) {
402         ALOGE("Failed to load EGL functions");
403         mDisableSwappy = true;
404         return;
405     }
406     mChoreographerFilter = std::make_unique<ChoreographerFilter>(refreshPeriod,
407                                                                  sfOffset - appOffset,
408                                                                  [this]() { return wakeClient(); });
409 
410     mChoreographerThread = ChoreographerThread::createChoreographerThread(
411         ChoreographerThread::Type::Swappy,
412         vm,
413         [this]{ handleChoreographer(); });
414     Settings::getInstance()->addListener([this]() { onSettingsChanged(); });
415     mAutoSwapIntervalThreshold = (1e9f / mRefreshPeriod.count()) / 20; // 20FPS
416     mFrameDurations.reserve(mFrameDurationSamples);
417     ALOGI("Initialized Swappy with refreshPeriod=%lld, appOffset=%lld, sfOffset=%lld" ,
418           (long long)refreshPeriod.count(), (long long)appOffset.count(),
419           (long long)sfOffset.count());
420 }
421 
onSettingsChanged()422 void Swappy::onSettingsChanged() {
423     std::lock_guard<std::mutex> lock(mFrameDurationsMutex);
424     int32_t newSwapInterval = ::round(float(Settings::getInstance()->getSwapIntervalNS()) /
425                                float(mRefreshPeriod.count()));
426     if (mSwapInterval != newSwapInterval || mAutoSwapInterval != newSwapInterval) {
427         mSwapInterval = newSwapInterval;
428         mAutoSwapInterval = mSwapInterval.load();
429         mFrameDurations.clear();
430         mFrameDurationsSum = {};
431     }
432 }
433 
handleChoreographer()434 void Swappy::handleChoreographer() {
435     mChoreographerFilter->onChoreographer();
436 }
437 
wakeClient()438 std::chrono::nanoseconds Swappy::wakeClient() {
439     std::lock_guard<std::mutex> lock(mWaitingMutex);
440     ++mCurrentFrame;
441 
442     // We're attempting to align with SurfaceFlinger's vsync, but it's always better to be a little
443     // late than a little early (since a little early could cause our frame to be picked up
444     // prematurely), so we pad by an additional millisecond.
445     mCurrentFrameTimestamp = std::chrono::steady_clock::now() + mSwapDuration.load() + 1ms;
446     mWaitingCondition.notify_all();
447     return mSwapDuration;
448 }
449 
startFrame()450 void Swappy::startFrame() {
451     TRACE_CALL();
452 
453     int32_t currentFrame;
454     std::chrono::steady_clock::time_point currentFrameTimestamp;
455     {
456         std::unique_lock<std::mutex> lock(mWaitingMutex);
457         currentFrame = mCurrentFrame;
458         currentFrameTimestamp = mCurrentFrameTimestamp;
459     }
460 
461     startFrameCallbacks();
462 
463     mTargetFrame = currentFrame + mAutoSwapInterval;
464 
465     const int intervals = (mPipelineMode) ? 2 : 1;
466 
467     // We compute the target time as now
468     //   + the time the buffer will be on the GPU and in the queue to the compositor (1 swap period)
469     mPresentationTime = currentFrameTimestamp + (mAutoSwapInterval * intervals) * mRefreshPeriod;
470 
471     mStartFrameTime = std::chrono::steady_clock::now();
472 }
473 
waitUntil(int32_t frameNumber)474 void Swappy::waitUntil(int32_t frameNumber) {
475     TRACE_CALL();
476     std::unique_lock<std::mutex> lock(mWaitingMutex);
477     mWaitingCondition.wait(lock, [&]() { return mCurrentFrame >= frameNumber; });
478 }
479 
waitOneFrame()480 void Swappy::waitOneFrame() {
481     TRACE_CALL();
482     std::unique_lock<std::mutex> lock(mWaitingMutex);
483     const int32_t target = mCurrentFrame + 1;
484     mWaitingCondition.wait(lock, [&]() { return mCurrentFrame >= target; });
485 }
486 
addFrameDuration(FrameDuration duration)487 void Swappy::addFrameDuration(FrameDuration duration) {
488     ALOGV("cpuTime = %.2f", duration.getCpuTime().count() / 1e6f);
489     ALOGV("gpuTime = %.2f", duration.getGpuTime().count() / 1e6f);
490 
491     std::lock_guard<std::mutex> lock(mFrameDurationsMutex);
492     // keep a sliding window of mFrameDurationSamples
493     if (mFrameDurations.size() == mFrameDurationSamples) {
494         mFrameDurationsSum -= mFrameDurations.front();
495         mFrameDurations.erase(mFrameDurations.begin());
496     }
497 
498     mFrameDurations.push_back(duration);
499     mFrameDurationsSum += duration;
500 }
501 
nanoToSwapInterval(std::chrono::nanoseconds nano)502 int32_t Swappy::nanoToSwapInterval(std::chrono::nanoseconds nano) {
503     int32_t interval = nano / mRefreshPeriod;
504 
505     // round the number based on the nearest
506     if (nano.count() - (interval * mRefreshPeriod.count()) > mRefreshPeriod.count() / 2) {
507         return interval + 1;
508     } else {
509         return interval;
510     }
511 }
512 
waitForNextFrame(EGLDisplay display)513 bool Swappy::waitForNextFrame(EGLDisplay display) {
514     preWaitCallbacks();
515 
516     int lateFrames = 0;
517     bool needToSetPresentationTime;
518 
519     std::chrono::nanoseconds cpuTime = std::chrono::steady_clock::now() - mStartFrameTime;
520     std::chrono::nanoseconds gpuTime;
521 
522     // if we are running slower than the threshold there is no point to sleep, just let the
523     // app run as fast as it can
524     if (mAutoSwapInterval <= mAutoSwapIntervalThreshold) {
525         waitUntil(mTargetFrame);
526 
527         // wait for the previous frame to be rendered
528         while (!getEgl()->lastFrameIsComplete(display)) {
529             gamesdk::ScopedTrace trace("lastFrameIncomplete");
530             ALOGV("lastFrameIncomplete");
531             lateFrames++;
532             waitOneFrame();
533         }
534 
535         gpuTime = getEgl()->getFencePendingTime();
536 
537         mPresentationTime += lateFrames * mRefreshPeriod;
538         needToSetPresentationTime = true;
539 
540     } else {
541         needToSetPresentationTime = false;
542         gpuTime = getEgl()->getFencePendingTime();
543 
544     }
545     addFrameDuration({cpuTime, gpuTime});
546 
547     postWaitCallbacks();
548     return needToSetPresentationTime;
549 }
550 
swapSlower(const FrameDuration & averageFrameTime,const std::chrono::nanoseconds & upperBound,const std::chrono::nanoseconds & lowerBound,const int32_t & newSwapInterval)551 void Swappy::swapSlower(const FrameDuration& averageFrameTime,
552                         const std::chrono::nanoseconds& upperBound,
553                         const std::chrono::nanoseconds& lowerBound,
554                         const int32_t& newSwapInterval) {
555     ALOGV("Rendering takes too much time for the given config");
556 
557     if (!mPipelineMode && averageFrameTime.getTime(true) <= upperBound) {
558         ALOGV("turning on pipelining");
559         mPipelineMode = true;
560     } else {
561         mAutoSwapInterval = newSwapInterval;
562         ALOGV("Changing Swap interval to %d", mAutoSwapInterval.load());
563 
564         // since we changed the swap interval, we may be able to turn off pipeline mode
565         nanoseconds newBound = mRefreshPeriod * mAutoSwapInterval.load();
566         newBound -= (FRAME_HYSTERESIS * 2);
567         if (mPipelineModeAutoMode && averageFrameTime.getTime(false) < newBound) {
568             ALOGV("Turning off pipelining");
569             mPipelineMode = false;
570         } else {
571             ALOGV("Turning on pipelining");
572             mPipelineMode = true;
573         }
574     }
575 }
576 
swapFaster(const FrameDuration & averageFrameTime,const std::chrono::nanoseconds & upperBound,const std::chrono::nanoseconds & lowerBound,const int32_t & newSwapInterval)577 void Swappy::swapFaster(const FrameDuration& averageFrameTime,
578                         const std::chrono::nanoseconds& upperBound,
579                         const std::chrono::nanoseconds& lowerBound,
580                         const int32_t& newSwapInterval) {
581     ALOGV("Rendering is much shorter for the given config");
582     mAutoSwapInterval = newSwapInterval;
583     ALOGV("Changing Swap interval to %d", mAutoSwapInterval.load());
584 
585     // since we changed the swap interval, we may need to turn on pipeline mode
586     nanoseconds newBound = mRefreshPeriod * mAutoSwapInterval.load();
587     newBound -= FRAME_HYSTERESIS;
588     if (!mPipelineModeAutoMode || averageFrameTime.getTime(false) > newBound) {
589         ALOGV("Turning on pipelining");
590         mPipelineMode = true;
591     } else {
592         ALOGV("Turning off pipelining");
593         mPipelineMode = false;
594     }
595 }
596 
updateSwapInterval()597 bool Swappy::updateSwapInterval() {
598     std::lock_guard<std::mutex> lock(mFrameDurationsMutex);
599     if (!mAutoSwapIntervalEnabled)
600         return false;
601 
602     if (mFrameDurations.size() < mFrameDurationSamples)
603         return false;
604 
605     const auto averageFrameTime = mFrameDurationsSum / mFrameDurations.size();
606     // define lower and upper bound based on the swap duration
607     nanoseconds upperBound = mRefreshPeriod * mAutoSwapInterval.load();
608     nanoseconds lowerBound = mRefreshPeriod * (mAutoSwapInterval - 1);
609 
610     // to be on the conservative side, lower bounds by FRAME_HYSTERESIS
611     upperBound -= FRAME_HYSTERESIS;
612     lowerBound -= FRAME_HYSTERESIS;
613 
614     // add the hysteresis to one of the bounds to avoid going back and forth when frames
615     // are exactly at the edge.
616     lowerBound -= FRAME_HYSTERESIS;
617 
618     auto div_result = ::div((averageFrameTime.getTime(true) + FRAME_HYSTERESIS).count(),
619                                mRefreshPeriod.count());
620     auto framesPerRefresh = div_result.quot;
621     auto framesPerRefreshRemainder = div_result.rem;
622 
623     const int32_t newSwapInterval = framesPerRefresh + (framesPerRefreshRemainder ? 1 : 0);
624 
625     ALOGV("mPipelineMode = %d", mPipelineMode);
626     ALOGV("Average cpu frame time = %.2f", (averageFrameTime.getCpuTime().count()) / 1e6f);
627     ALOGV("Average gpu frame time = %.2f", (averageFrameTime.getGpuTime().count()) / 1e6f);
628     ALOGV("upperBound = %.2f", upperBound.count() / 1e6f);
629     ALOGV("lowerBound = %.2f", lowerBound.count() / 1e6f);
630 
631     bool configChanged = false;
632     if (averageFrameTime.getTime(mPipelineMode) > upperBound) {
633         swapSlower(averageFrameTime, upperBound, lowerBound, newSwapInterval);
634         configChanged = true;
635     } else if (mSwapInterval < mAutoSwapInterval &&
636                                             (averageFrameTime.getTime(true) < lowerBound)) {
637         swapFaster(averageFrameTime, upperBound, lowerBound, newSwapInterval);
638         configChanged = true;
639     } else if (mPipelineModeAutoMode && mPipelineMode &&
640                             averageFrameTime.getTime(false) < upperBound - FRAME_HYSTERESIS) {
641         ALOGV("Rendering time fits the current swap interval without pipelining");
642         mPipelineMode = false;
643         configChanged = true;
644     }
645 
646     if (configChanged) {
647         mFrameDurationsSum = {};
648         mFrameDurations.clear();
649     }
650     return configChanged;
651 }
652 
resetSyncFence(EGLDisplay display)653 void Swappy::resetSyncFence(EGLDisplay display) {
654     getEgl()->resetSyncFence(display);
655 }
656 
setPresentationTime(EGLDisplay display,EGLSurface surface)657 bool Swappy::setPresentationTime(EGLDisplay display, EGLSurface surface) {
658     TRACE_CALL();
659 
660     // if we are too close to the vsync, there is no need to set presentation time
661     if ((mPresentationTime - std::chrono::steady_clock::now()) <
662             (mRefreshPeriod - mSfOffset)) {
663         return EGL_TRUE;
664     }
665 
666     return getEgl()->setPresentationTime(display, surface, mPresentationTime);
667 }
668 
updateSwapDuration(std::chrono::nanoseconds duration)669 void Swappy::updateSwapDuration(std::chrono::nanoseconds duration) {
670     // TODO: The exponential smoothing factor here is arbitrary
671     mSwapDuration = (mSwapDuration.load() * 4 / 5) + duration / 5;
672 
673     // Clamp the swap duration to half the refresh period
674     //
675     // We do this since the swap duration can be a bit noisy during periods such as app startup,
676     // which can cause some stuttering as the smoothing catches up with the actual duration. By
677     // clamping, we reduce the maximum error which reduces the calibration time.
678     if (mSwapDuration.load() > (mRefreshPeriod / 2)) mSwapDuration = mRefreshPeriod / 2;
679 }
680 
681 } // namespace swappy
682