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