You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
MangoHud/subprojects/swappy/common/SwappyCommon.cpp

942 lines
33 KiB
C++

/*
* 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 "SwappyCommon.h"
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <thread>
#include <chrono>
#include "Log.h"
#include "Settings.h"
#include "Thread.h"
// #include "Trace.h"
#define LOG_TAG "SwappyCommon"
namespace swappy {
using std::chrono::milliseconds;
using std::chrono::nanoseconds;
// NB These are only needed for C++14
constexpr nanoseconds SwappyCommon::FrameDuration::MAX_DURATION;
constexpr nanoseconds SwappyCommon::FRAME_MARGIN;
constexpr nanoseconds SwappyCommon::DURATION_ROUNDING_MARGIN;
constexpr nanoseconds SwappyCommon::REFRESH_RATE_MARGIN;
constexpr int SwappyCommon::NON_PIPELINE_PERCENT;
constexpr int SwappyCommon::FRAME_DROP_THRESHOLD;
constexpr std::chrono::nanoseconds
SwappyCommon::FrameDurations::FRAME_DURATION_SAMPLE_SECONDS;
#if __ANDROID_API__ < 30
// Define ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_* to allow compilation on older
// versions
enum {
/**
* There are no inherent restrictions on the frame rate of this window.
*/
ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT = 0,
/**
* This window is being used to display content with an inherently fixed
* frame rate, e.g. a video that has a specific frame rate. When the system
* selects a frame rate other than what the app requested, the app will need
* to do pull down or use some other technique to adapt to the system's
* frame rate. The user experience is likely to be worse (e.g. more frame
* stuttering) than it would be if the system had chosen the app's requested
* frame rate.
*/
ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1
};
#endif
bool SwappyCommonSettings::getFromApp(//JNIEnv* env, jobject jactivity,
SwappyCommonSettings* out) {
if (out == nullptr) return false;
ALOGI("Swappy version {}.{}", SWAPPY_MAJOR_VERSION, SWAPPY_MINOR_VERSION);
// out->sdkVersion = getSDKVersion(env);
/*
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");
// getAppVsyncOffsetNanos was only added in API 21.
// Return gracefully if this device doesn't support it.
if (getAppVsyncOffsetNanos == 0 || env->ExceptionOccurred()) {
ALOGE("Error while getting method: getAppVsyncOffsetNanos");
env->ExceptionClear();
return false;
}
const long appVsyncOffsetNanos =
env->CallLongMethod(display, getAppVsyncOffsetNanos);
jmethodID getPresentationDeadlineNanos =
env->GetMethodID(displayClass, "getPresentationDeadlineNanos", "()J");
if (getPresentationDeadlineNanos == 0 || env->ExceptionOccurred()) {
ALOGE("Error while getting method: getPresentationDeadlineNanos");
return false;
}
const long vsyncPresentationDeadlineNanos =
env->CallLongMethod(display, getPresentationDeadlineNanos);*/
const long ONE_MS_IN_NS = 1000 * 1000;
const long ONE_S_IN_NS = ONE_MS_IN_NS * 1000;
// random hard coded crap
auto appVsyncOffsetNanos = 0;
const auto refreshRateHz = 144;
auto vsyncPresentationDeadlineNanos = ONE_S_IN_NS / refreshRateHz;
const long vsyncPeriodNanos =
static_cast<long>(ONE_S_IN_NS / refreshRateHz);
const long sfVsyncOffsetNanos =
vsyncPeriodNanos - (vsyncPresentationDeadlineNanos - ONE_MS_IN_NS);
using std::chrono::nanoseconds;
out->refreshPeriod = nanoseconds(vsyncPeriodNanos);
out->appVsyncOffset = nanoseconds(appVsyncOffsetNanos);
out->sfVsyncOffset = nanoseconds(sfVsyncOffsetNanos);
return true;
}
SwappyCommon::SwappyCommon(/*JNIEnv* env, jobject jactivity*/)
: //mJactivity(env->NewGlobalRef(jactivity)),
mMeasuredSwapDuration(nanoseconds(0)),
mAutoSwapInterval(1),
mValid(false) {
// mLibAndroid = dlopen("libandroid.so", RTLD_NOW | RTLD_LOCAL);
// if (mLibAndroid == nullptr) {
// ALOGE("FATAL: cannot open libandroid.so: %s", strerror(errno));
// return;
// }
//
// mANativeWindow_setFrameRate =
// reinterpret_cast<PFN_ANativeWindow_setFrameRate>(
// dlsym(mLibAndroid, "ANativeWindow_setFrameRate"));
//
if (!SwappyCommonSettings::getFromApp(/*env, mJactivity,*/ &mCommonSettings))
return;
//
// env->GetJavaVM(&mJVM);
//
// if (isDeviceUnsupported()) {
// ALOGE("Device is unsupported");
// return;
// }
//
mChoreographerFilter = std::make_unique<ChoreographerFilter>(
mCommonSettings.refreshPeriod,
mCommonSettings.sfVsyncOffset - mCommonSettings.appVsyncOffset,
[this]() { return wakeClient(); });
mChoreographerThread = ChoreographerThread::createChoreographerThread(
ChoreographerThread::Type::Swappy,
[this] { mChoreographerFilter->onChoreographer(); },
[this] { onRefreshRateChanged(); }, mCommonSettings.sdkVersion);
if (!mChoreographerThread->isInitialized()) {
ALOGE("failed to initialize ChoreographerThread");
return;
}
// mWakerThread = std::thread ([this]{
// while (!mQuitThread) {
// wakeClient();
// std::this_thread::sleep_for(7ms);
// }
// });
if (USE_DISPLAY_MANAGER && SwappyDisplayManager::useSwappyDisplayManager(
mCommonSettings.sdkVersion)) {
mDisplayManager =
std::make_unique<SwappyDisplayManager>(/*mJVM, jactivity*/);
if (!mDisplayManager->isInitialized()) {
mDisplayManager = nullptr;
ALOGE("failed to initialize DisplayManager");
return;
}
}
Settings::getInstance()->addListener([this]() { onSettingsChanged(); });
Settings::getInstance()->setDisplayTimings({mCommonSettings.refreshPeriod,
mCommonSettings.appVsyncOffset,
mCommonSettings.sfVsyncOffset});
ALOGI(
"Initialized Swappy with vsyncPeriod={}, appOffset={}, "
"sfOffset={}",
(long long)mCommonSettings.refreshPeriod.count(),
(long long)mCommonSettings.appVsyncOffset.count(),
(long long)mCommonSettings.sfVsyncOffset.count());
mValid = true;
}
// Used by tests
SwappyCommon::SwappyCommon(const SwappyCommonSettings& settings)
: //mJactivity(nullptr),
mCommonSettings(settings),
mMeasuredSwapDuration(nanoseconds(0)),
mAutoSwapInterval(1),
mValid(true) {
mChoreographerFilter = std::make_unique<ChoreographerFilter>(
mCommonSettings.refreshPeriod,
mCommonSettings.sfVsyncOffset - mCommonSettings.appVsyncOffset,
[this]() { return wakeClient(); });
mUsingExternalChoreographer = true;
mChoreographerThread = ChoreographerThread::createChoreographerThread(
ChoreographerThread::Type::App,
[this] { mChoreographerFilter->onChoreographer(); }, [] {},
mCommonSettings.sdkVersion);
Settings::getInstance()->addListener([this]() { onSettingsChanged(); });
Settings::getInstance()->setDisplayTimings({mCommonSettings.refreshPeriod,
mCommonSettings.appVsyncOffset,
mCommonSettings.sfVsyncOffset});
ALOGI(
"Initialized Swappy with vsyncPeriod={}, appOffset={}, "
"sfOffset={}",
(long long)mCommonSettings.refreshPeriod.count(),
(long long)mCommonSettings.appVsyncOffset.count(),
(long long)mCommonSettings.sfVsyncOffset.count());
}
SwappyCommon::~SwappyCommon() {
// destroy all threads first before the other members of this class
mChoreographerThread.reset();
mChoreographerFilter.reset();
Settings::reset();
// mQuitThread = true;
// if (mWakerThread.joinable())
// mWakerThread.join();
// if (mJactivity != nullptr) {
// JNIEnv* env;
// mJVM->AttachCurrentThread(&env, nullptr);
//
// env->DeleteGlobalRef(mJactivity);
// }
}
void SwappyCommon::onRefreshRateChanged() {
// JNIEnv* env;
// mJVM->AttachCurrentThread(&env, nullptr);
ALOGV("onRefreshRateChanged");
SwappyCommonSettings settings;
if (!SwappyCommonSettings::getFromApp(/*env, mJactivity,*/ &settings)) {
ALOGE("failed to query display timings");
return;
}
Settings::getInstance()->setDisplayTimings({settings.refreshPeriod,
settings.appVsyncOffset,
settings.sfVsyncOffset});
ALOGV("onRefreshRateChanged: refresh rate: {:.0f}Hz",
1e9f / settings.refreshPeriod.count());
}
nanoseconds SwappyCommon::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() + mMeasuredSwapDuration.load() + 1ms;
mWaitingCondition.notify_all();
return mMeasuredSwapDuration;
}
void SwappyCommon::onChoreographer(int64_t frameTimeNanos) {
// TRACE_CALL();
if (!mUsingExternalChoreographer) {
mUsingExternalChoreographer = true;
mChoreographerThread = ChoreographerThread::createChoreographerThread(
ChoreographerThread::Type::App,
[this] { mChoreographerFilter->onChoreographer(); },
[this] { onRefreshRateChanged(); }, mCommonSettings.sdkVersion);
}
mChoreographerThread->postFrameCallbacks();
}
bool SwappyCommon::waitForNextFrame(const SwapHandlers& h) {
int lateFrames = 0;
bool presentationTimeIsNeeded;
const nanoseconds cpuTime =
(mStartFrameTime.time_since_epoch().count() == 0)
? 0ns
: std::chrono::steady_clock::now() - mStartFrameTime;
// mCPUTracer.endTrace();
preWaitCallbacks();
// 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 (mCommonSettings.refreshPeriod * mAutoSwapInterval <=
mAutoSwapIntervalThreshold.load()) {
waitUntilTargetFrame();
// wait for the previous frame to be rendered
while (!h.lastFrameIsComplete()) {
lateFrames++;
waitOneFrame();
}
mPresentationTime += lateFrames * mCommonSettings.refreshPeriod;
presentationTimeIsNeeded = true;
} else {
presentationTimeIsNeeded = false;
}
const nanoseconds gpuTime = h.getPrevFrameGpuTime();
addFrameDuration({cpuTime, gpuTime, mCurrentFrame > mTargetFrame});
postWaitCallbacks(cpuTime, gpuTime);
return presentationTimeIsNeeded;
}
void SwappyCommon::updateDisplayTimings() {
// grab a pointer to the latest supported refresh rates
if (mDisplayManager) {
mSupportedRefreshPeriods =
mDisplayManager->getSupportedRefreshPeriods();
}
std::lock_guard<std::mutex> lock(mMutex);
ALOGW_ONCE_IF(!mWindow,
"ANativeWindow not configured, frame rate will not be "
"reported to Android platform");
if (!mTimingSettingsNeedUpdate && !mWindowChanged) {
return;
}
mTimingSettingsNeedUpdate = false;
if (!mWindowChanged &&
mCommonSettings.refreshPeriod == mNextTimingSettings.refreshPeriod &&
mSwapDuration == mNextTimingSettings.swapDuration) {
return;
}
mWindowChanged = false;
mCommonSettings.refreshPeriod = mNextTimingSettings.refreshPeriod;
const auto pipelineFrameTime =
mFrameDurations.getAverageFrameTime().getTime(PipelineMode::On);
const auto swapDuration =
pipelineFrameTime != 0ns ? pipelineFrameTime : mSwapDuration;
mAutoSwapInterval =
calculateSwapInterval(swapDuration, mCommonSettings.refreshPeriod);
mPipelineMode = PipelineMode::On;
const bool swapIntervalValid =
mNextTimingSettings.refreshPeriod * mAutoSwapInterval >=
mNextTimingSettings.swapDuration;
const bool swapIntervalChangedBySettings =
mSwapDuration != mNextTimingSettings.swapDuration;
mSwapDuration = mNextTimingSettings.swapDuration;
if (!mAutoSwapIntervalEnabled || swapIntervalChangedBySettings ||
!swapIntervalValid) {
mAutoSwapInterval =
calculateSwapInterval(mSwapDuration, mCommonSettings.refreshPeriod);
mPipelineMode = PipelineMode::On;
setPreferredRefreshPeriod(mSwapDuration);
}
if (mNextModeId == -1 && mLatestFrameRateVote == 0) {
setPreferredRefreshPeriod(mSwapDuration);
}
mFrameDurations.clear();
SPDLOG_TRACE("mSwapDuration {}", int(mSwapDuration.count()));
SPDLOG_TRACE("mAutoSwapInterval {}", mAutoSwapInterval);
SPDLOG_TRACE("mCommonSettings.refreshPeriod {}",
mCommonSettings.refreshPeriod.count());
SPDLOG_TRACE("mPipelineMode {}", static_cast<int>(mPipelineMode));
}
void SwappyCommon::onPreSwap(const SwapHandlers& h) {
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
if (mPipelineMode == PipelineMode::On) {
mPresentationTimeNeeded = waitForNextFrame(h);
} else {
mPresentationTimeNeeded =
(mCommonSettings.refreshPeriod * mAutoSwapInterval <=
mAutoSwapIntervalThreshold.load());
}
mSwapTime = std::chrono::steady_clock::now();
preSwapBuffersCallbacks();
}
void SwappyCommon::onPostSwap(const SwapHandlers& h) {
postSwapBuffersCallbacks();
updateMeasuredSwapDuration(std::chrono::steady_clock::now() - mSwapTime);
if (mPipelineMode == PipelineMode::Off) {
waitForNextFrame(h);
}
if (updateSwapInterval()) {
swapIntervalChangedCallbacks();
SPDLOG_TRACE("mPipelineMode {}", static_cast<int>(mPipelineMode));
SPDLOG_TRACE("mAutoSwapInterval {}", mAutoSwapInterval);
}
updateDisplayTimings();
startFrame();
}
void SwappyCommon::updateMeasuredSwapDuration(nanoseconds duration) {
// TODO: The exponential smoothing factor here is arbitrary
mMeasuredSwapDuration =
(mMeasuredSwapDuration.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 (mMeasuredSwapDuration.load() > (mCommonSettings.refreshPeriod / 2)) {
mMeasuredSwapDuration.store(mCommonSettings.refreshPeriod / 2);
}
}
nanoseconds SwappyCommon::getSwapDuration() {
std::lock_guard<std::mutex> lock(mMutex);
return mAutoSwapInterval * mCommonSettings.refreshPeriod;
};
void SwappyCommon::FrameDurations::add(FrameDuration frameDuration) {
const auto now = std::chrono::steady_clock::now();
mFrames.push_back({now, frameDuration});
mFrameDurationsSum += frameDuration;
if (frameDuration.frameMiss()) {
mMissedFrameCount++;
}
while (mFrames.size() >= 2 &&
now - (mFrames.begin() + 1)->first > FRAME_DURATION_SAMPLE_SECONDS) {
mFrameDurationsSum -= mFrames.front().second;
if (mFrames.front().second.frameMiss()) {
mMissedFrameCount--;
}
mFrames.pop_front();
}
}
bool SwappyCommon::FrameDurations::hasEnoughSamples() const {
return (!mFrames.empty()) && (mFrames.back().first - mFrames.front().first >
FRAME_DURATION_SAMPLE_SECONDS);
}
SwappyCommon::FrameDuration SwappyCommon::FrameDurations::getAverageFrameTime()
const {
if (hasEnoughSamples()) {
return mFrameDurationsSum / mFrames.size();
}
return {};
}
int SwappyCommon::FrameDurations::getMissedFramePercent() const {
return round(mMissedFrameCount * 100.0f / mFrames.size());
}
void SwappyCommon::FrameDurations::clear() {
mFrames.clear();
mFrameDurationsSum = {};
mMissedFrameCount = 0;
}
void SwappyCommon::addFrameDuration(FrameDuration duration) {
ALOGV("cpuTime = %.2f", duration.getCpuTime().count() / 1e6f);
ALOGV("gpuTime = %.2f", duration.getGpuTime().count() / 1e6f);
ALOGV("frame %s", duration.frameMiss() ? "MISS" : "on time");
std::lock_guard<std::mutex> lock(mMutex);
mFrameDurations.add(duration);
}
bool SwappyCommon::swapSlower(const FrameDuration& averageFrameTime,
const nanoseconds& upperBound,
int newSwapInterval) {
bool swappedSlower = false;
ALOGV("Rendering takes too much time for the given config");
const auto frameFitsUpperBound =
averageFrameTime.getTime(PipelineMode::On) <= upperBound;
const auto swapDurationWithinThreshold =
mCommonSettings.refreshPeriod * mAutoSwapInterval <=
mAutoSwapIntervalThreshold.load() + FRAME_MARGIN;
// Check if turning on pipeline is not enough
if ((mPipelineMode == PipelineMode::On || !frameFitsUpperBound) &&
swapDurationWithinThreshold) {
int originalAutoSwapInterval = mAutoSwapInterval;
if (newSwapInterval > mAutoSwapInterval) {
mAutoSwapInterval = newSwapInterval;
} else {
mAutoSwapInterval++;
}
if (mAutoSwapInterval != originalAutoSwapInterval) {
ALOGV("Changing Swap interval to %d from %d", mAutoSwapInterval,
originalAutoSwapInterval);
swappedSlower = true;
}
}
if (mPipelineMode == PipelineMode::Off) {
ALOGV("turning on pipelining");
mPipelineMode = PipelineMode::On;
}
return swappedSlower;
}
bool SwappyCommon::swapFaster(int newSwapInterval) {
bool swappedFaster = false;
int originalAutoSwapInterval = mAutoSwapInterval;
while (newSwapInterval < mAutoSwapInterval && swapFasterCondition()) {
mAutoSwapInterval--;
}
if (mAutoSwapInterval != originalAutoSwapInterval) {
ALOGV("Rendering is much shorter for the given config");
ALOGV("Changing Swap interval to %d from %d", mAutoSwapInterval,
originalAutoSwapInterval);
// since we changed the swap interval, we may need to turn on pipeline
// mode
ALOGV("Turning on pipelining");
mPipelineMode = PipelineMode::On;
swappedFaster = true;
}
return swappedFaster;
}
bool SwappyCommon::updateSwapInterval() {
std::lock_guard<std::mutex> lock(mMutex);
if (!mAutoSwapIntervalEnabled) return false;
if (!mFrameDurations.hasEnoughSamples()) return false;
const auto averageFrameTime = mFrameDurations.getAverageFrameTime();
const auto pipelineFrameTime = averageFrameTime.getTime(PipelineMode::On);
const auto nonPipelineFrameTime =
averageFrameTime.getTime(PipelineMode::Off);
// calculate the new swap interval based on average frame time assume we are
// in pipeline mode (prefer higher swap interval rather than turning off
// pipeline mode)
const int newSwapInterval =
calculateSwapInterval(pipelineFrameTime, mCommonSettings.refreshPeriod);
// Define upper and lower bounds based on the swap duration
const nanoseconds upperBoundForThisRefresh =
mCommonSettings.refreshPeriod * mAutoSwapInterval;
const nanoseconds lowerBoundForThisRefresh =
mCommonSettings.refreshPeriod * (mAutoSwapInterval - 1) - FRAME_MARGIN;
const int missedFramesPercent = mFrameDurations.getMissedFramePercent();
ALOGV("mPipelineMode = %d", static_cast<int>(mPipelineMode));
ALOGV("Average cpu frame time = %.2f",
(averageFrameTime.getCpuTime().count()) / 1e6f);
ALOGV("Average gpu frame time = %.2f",
(averageFrameTime.getGpuTime().count()) / 1e6f);
ALOGV("upperBound = %.2f", upperBoundForThisRefresh.count() / 1e6f);
ALOGV("lowerBound = %.2f", lowerBoundForThisRefresh.count() / 1e6f);
ALOGV("frame missed = %d%%", missedFramesPercent);
bool configChanged = false;
ALOGV("pipelineFrameTime = %.2f", pipelineFrameTime.count() / 1e6f);
const auto nonPipelinePercent = (100.f + NON_PIPELINE_PERCENT) / 100.f;
// Make sure the frame time fits in the current config to avoid missing
// frames
if (missedFramesPercent > FRAME_DROP_THRESHOLD) {
if (swapSlower(averageFrameTime, upperBoundForThisRefresh,
newSwapInterval))
configChanged = true;
}
// So we shouldn't miss any frames with this config but maybe we can go
// faster ? we check the pipeline frame time here as we prefer lower swap
// interval than no pipelining
else if (missedFramesPercent == 0 && swapFasterCondition() &&
pipelineFrameTime < lowerBoundForThisRefresh) {
if (swapFaster(newSwapInterval)) configChanged = true;
}
// If we reached to this condition it means that we fit into the boundaries.
// However we might be in pipeline mode and we could turn it off if we still
// fit. To be very conservative, switch to non-pipeline if frame time * 50%
// fits
else if (mPipelineModeAutoMode && mPipelineMode == PipelineMode::On &&
nonPipelineFrameTime * nonPipelinePercent <
upperBoundForThisRefresh) {
ALOGV(
"Rendering time fits the current swap interval without pipelining");
mPipelineMode = PipelineMode::Off;
configChanged = true;
}
if (configChanged) {
mFrameDurations.clear();
}
setPreferredRefreshPeriod(pipelineFrameTime);
return configChanged;
}
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 SwappyCommon::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 SwappyCommon::preSwapBuffersCallbacks() {
executeTracers(mInjectedTracers.preSwapBuffers);
}
void SwappyCommon::postSwapBuffersCallbacks() {
executeTracers(mInjectedTracers.postSwapBuffers,
(int64_t)mPresentationTime.time_since_epoch().count());
}
void SwappyCommon::preWaitCallbacks() {
executeTracers(mInjectedTracers.preWait);
}
void SwappyCommon::postWaitCallbacks(nanoseconds cpuTime, nanoseconds gpuTime) {
executeTracers(mInjectedTracers.postWait, cpuTime.count(), gpuTime.count());
}
void SwappyCommon::startFrameCallbacks() {
executeTracers(mInjectedTracers.startFrame, mCurrentFrame,
(int64_t)mPresentationTime.time_since_epoch().count());
}
void SwappyCommon::swapIntervalChangedCallbacks() {
executeTracers(mInjectedTracers.swapIntervalChanged);
}
void SwappyCommon::setAutoSwapInterval(bool enabled) {
std::lock_guard<std::mutex> lock(mMutex);
mAutoSwapIntervalEnabled = enabled;
// non pipeline mode is not supported when auto mode is disabled
if (!enabled) {
mPipelineMode = PipelineMode::On;
// TRACE_INT("mPipelineMode", static_cast<int>(mPipelineMode));
}
}
void SwappyCommon::setAutoPipelineMode(bool enabled) {
std::lock_guard<std::mutex> lock(mMutex);
mPipelineModeAutoMode = enabled;
// TRACE_INT("mPipelineModeAutoMode", mPipelineModeAutoMode);
if (!enabled) {
mPipelineMode = PipelineMode::On;
// TRACE_INT("mPipelineMode", static_cast<int>(mPipelineMode));
}
}
void SwappyCommon::setPreferredDisplayModeId(int modeId) {
if (!mDisplayManager || modeId < 0 || mNextModeId == modeId) {
return;
}
mNextModeId = modeId;
mDisplayManager->setPreferredDisplayModeId(modeId);
ALOGV("setPreferredDisplayModeId set to %d", modeId);
}
int SwappyCommon::calculateSwapInterval(nanoseconds frameTime,
nanoseconds refreshPeriod) {
if (frameTime < refreshPeriod) {
return 1;
}
auto div_result = div(frameTime.count(), refreshPeriod.count());
auto framesPerRefresh = div_result.quot;
auto framesPerRefreshRemainder = div_result.rem;
return (framesPerRefresh +
(framesPerRefreshRemainder > REFRESH_RATE_MARGIN.count() ? 1 : 0));
}
void SwappyCommon::setPreferredRefreshPeriod(nanoseconds frameTime) {
if (mANativeWindow_setFrameRate && mWindow) {
auto frameRate = 1e9f / frameTime.count();
frameRate = std::min(frameRate, 1e9f / (mSwapDuration).count());
if (std::abs(mLatestFrameRateVote - frameRate) >
FRAME_RATE_VOTE_MARGIN) {
mLatestFrameRateVote = frameRate;
ALOGV("ANativeWindow_setFrameRate(%.2f)", frameRate);
mANativeWindow_setFrameRate(
mWindow, frameRate,
ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT);
}
// TRACE_INT("preferredRefreshPeriod", (int)frameRate);
} else {
if (!mDisplayManager || !mSupportedRefreshPeriods) {
return;
}
// Loop across all supported refresh periods to find the best refresh
// period. Best refresh period means:
// Shortest swap period that can still accommodate the frame time
// and that has the longest refresh period possible to optimize
// power consumption.
std::pair<nanoseconds, int> bestRefreshConfig;
nanoseconds minSwapDuration = 1s;
for (const auto& refreshConfig : *mSupportedRefreshPeriods) {
const auto period = refreshConfig.first;
const int swapIntervalForPeriod =
calculateSwapInterval(frameTime, period);
const nanoseconds swapDuration = period * swapIntervalForPeriod;
// Don't allow swapping faster than mSwapDuration (see public
// header)
if (swapDuration + FRAME_MARGIN < mSwapDuration) {
continue;
}
// We iterate in ascending order of refresh period, so accepting any
// better or equal-within-margin duration here chooses the longest
// refresh period possible.
if (swapDuration < minSwapDuration + FRAME_MARGIN) {
minSwapDuration = swapDuration;
bestRefreshConfig = refreshConfig;
}
}
// Switch if we have a potentially better refresh rate
{
// TRACE_INT("preferredRefreshPeriod",
// bestRefreshConfig.first.count());
setPreferredDisplayModeId(bestRefreshConfig.second);
}
}
}
void SwappyCommon::onSettingsChanged() {
std::lock_guard<std::mutex> lock(mMutex);
TimingSettings timingSettings =
TimingSettings::from(*Settings::getInstance());
// If display timings has changed, cache the update and apply them on the
// next frame
if (timingSettings != mNextTimingSettings) {
mNextTimingSettings = timingSettings;
mTimingSettingsNeedUpdate = true;
}
}
void SwappyCommon::startFrame() {
// TRACE_CALL();
int32_t currentFrame;
std::chrono::steady_clock::time_point currentFrameTimestamp;
{
std::unique_lock<std::mutex> lock(mWaitingMutex);
currentFrame = mCurrentFrame;
currentFrameTimestamp = mCurrentFrameTimestamp;
}
// Whether to add a wait to fix buffer stuffing.
bool waitFrame = false;
const int intervals = (mPipelineMode == PipelineMode::On) ? 2 : 1;
// Use frame statistics to fix any buffer stuffing
if (mBufferStuffingFixWait > 0 && mFrameStatistics) {
int32_t lastLatency = mFrameStatistics->lastLatencyRecorded();
int expectedLatency = mAutoSwapInterval * intervals;
// TRACE_INT("ExpectedLatency", expectedLatency);
if (mBufferStuffingFixCounter == 0) {
if (lastLatency > expectedLatency) {
mMissedFrameCounter++;
if (mMissedFrameCounter >= mBufferStuffingFixWait) {
waitFrame = true;
mBufferStuffingFixCounter = 2 * lastLatency;
SPDLOG_TRACE("BufferStuffingFix {}", mBufferStuffingFixCounter);
}
} else {
mMissedFrameCounter = 0;
}
} else {
--mBufferStuffingFixCounter;
SPDLOG_TRACE("BufferStuffingFix {}", mBufferStuffingFixCounter);
}
}
mTargetFrame = currentFrame + mAutoSwapInterval;
if (waitFrame) mTargetFrame += 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) * mCommonSettings.refreshPeriod;
mStartFrameTime = std::chrono::steady_clock::now();
// mCPUTracer.startTrace();
startFrameCallbacks();
}
void SwappyCommon::waitUntil(int32_t target) {
// TRACE_CALL();
std::unique_lock<std::mutex> lock(mWaitingMutex);
mWaitingCondition.wait(lock, [&]() {
if (mCurrentFrame < target) {
if (!mUsingExternalChoreographer) {
mChoreographerThread->postFrameCallbacks();
}
return false;
}
return true;
});
}
void SwappyCommon::waitUntilTargetFrame() { waitUntil(mTargetFrame); }
void SwappyCommon::waitOneFrame() { waitUntil(mCurrentFrame + 1); }
SdkVersion SwappyCommonSettings::getSDKVersion(/*JNIEnv* env*/) {
return SdkVersion{1, 0};
}
void SwappyCommon::setANativeWindow(ANativeWindow* window) {
std::lock_guard<std::mutex> lock(mMutex);
if (mWindow == window) {
return;
}
if (mWindow != nullptr) {
// ANativeWindow_release(mWindow);
}
mWindow = window;
if (mWindow != nullptr) {
// ANativeWindow_acquire(mWindow);
mWindowChanged = true;
mLatestFrameRateVote = 0;
}
}
namespace {
struct DeviceIdentifier {
std::string manufacturer;
std::string model;
std::string display;
// Empty fields match against any value and we match the beginning of the
// input, e.g.
// A37 matches A37f, A37fw, etc.
bool match(const std::string& manufacturer_in, const std::string& model_in,
const std::string& display_in) {
if (!matchStartOfString(manufacturer, manufacturer_in)) return false;
if (!matchStartOfString(model, model_in)) return false;
if (!matchStartOfString(display, display_in)) return false;
return true;
}
bool matchStartOfString(const std::string& start,
const std::string& sample) {
return start.empty() || start == sample.substr(0, start.length());
}
};
} // anonymous namespace
bool SwappyCommon::isDeviceUnsupported() {
return false;
}
} // namespace swappy