Add franken-Swappy. Look away.
parent
019de913ca
commit
8551fac97c
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2019 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 "CPUTracer.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
//#include "../../common/Log.h"
|
||||
//#include "../../common/Trace.h"
|
||||
|
||||
namespace swappy {
|
||||
|
||||
CPUTracer::CPUTracer() {}
|
||||
|
||||
CPUTracer::~CPUTracer() { joinThread(); }
|
||||
|
||||
void CPUTracer::joinThread() {
|
||||
bool join = false;
|
||||
if (mThread && mThread->joinable()) {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mTrace = false;
|
||||
mRunning = false;
|
||||
mCond.notify_one();
|
||||
join = true;
|
||||
}
|
||||
if (join) {
|
||||
mThread->join();
|
||||
}
|
||||
mThread.reset();
|
||||
}
|
||||
|
||||
void CPUTracer::startTrace() {
|
||||
if (TRACE_ENABLED()) {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (!mThread) {
|
||||
mRunning = true;
|
||||
mThread = std::make_unique<Thread>([this]() { threadMain(); });
|
||||
}
|
||||
mTrace = true;
|
||||
mCond.notify_one();
|
||||
} else {
|
||||
joinThread();
|
||||
}
|
||||
}
|
||||
|
||||
void CPUTracer::endTrace() {
|
||||
if (TRACE_ENABLED()) {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mTrace = false;
|
||||
mCond.notify_one();
|
||||
} else {
|
||||
joinThread();
|
||||
}
|
||||
}
|
||||
|
||||
void CPUTracer::threadMain() NO_THREAD_SAFETY_ANALYSIS {
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
while (mRunning) {
|
||||
if (mTrace) {
|
||||
gamesdk::ScopedTrace trace("Swappy: CPU frame time");
|
||||
mCond.wait(lock);
|
||||
} else {
|
||||
mCond.wait(lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace swappy
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include "Thread.h"
|
||||
|
||||
namespace swappy {
|
||||
|
||||
class CPUTracer {
|
||||
public:
|
||||
CPUTracer();
|
||||
~CPUTracer();
|
||||
|
||||
CPUTracer(CPUTracer&) = delete;
|
||||
|
||||
void startTrace();
|
||||
void endTrace();
|
||||
|
||||
private:
|
||||
void threadMain();
|
||||
void joinThread();
|
||||
|
||||
std::mutex mMutex;
|
||||
std::condition_variable_any mCond GUARDED_BY(mMutex);
|
||||
std::unique_ptr<Thread> mThread;
|
||||
bool mRunning GUARDED_BY(mMutex) = true;
|
||||
bool mTrace GUARDED_BY(mMutex) = false;
|
||||
};
|
||||
|
||||
} // namespace swappy
|
@ -0,0 +1,232 @@
|
||||
/*
|
||||
* 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 "ChoreographerFilter.h"
|
||||
|
||||
#define LOG_TAG "ChoreographerFilter"
|
||||
|
||||
#include <sched.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <deque>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "Log.h"
|
||||
#include "Settings.h"
|
||||
#include "Thread.h"
|
||||
// #include "Trace.h"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
using time_point = std::chrono::steady_clock::time_point;
|
||||
|
||||
namespace {
|
||||
|
||||
class Timer {
|
||||
public:
|
||||
Timer(std::chrono::nanoseconds refreshPeriod,
|
||||
std::chrono::nanoseconds appToSfDelay)
|
||||
: mRefreshPeriod(refreshPeriod), mAppToSfDelay(appToSfDelay) {}
|
||||
|
||||
// Returns false if we have detected that we have received the same
|
||||
// timestamp multiple times so that the caller can wait for fresh timestamps
|
||||
bool addTimestamp(time_point point) {
|
||||
// Keep track of the previous timestamp and how many times we've seen it
|
||||
// to determine if we've stopped receiving Choreographer callbacks,
|
||||
// which would indicate that we should probably stop until we see them
|
||||
// again (e.g., if the app has been moved to the background)
|
||||
if (point == mLastTimestamp) {
|
||||
if (mRepeatCount++ > 5) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
mRepeatCount = 0;
|
||||
}
|
||||
mLastTimestamp = point;
|
||||
|
||||
point += mAppToSfDelay;
|
||||
|
||||
while (mBaseTime + mRefreshPeriod * 1.5 < point) {
|
||||
mBaseTime += mRefreshPeriod;
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds delta = (point - (mBaseTime + mRefreshPeriod));
|
||||
if (delta < -mRefreshPeriod / 2 || delta > mRefreshPeriod / 2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: 0.2 weighting factor for exponential smoothing is completely
|
||||
// arbitrary
|
||||
mRefreshPeriod += delta * 2 / 10;
|
||||
mBaseTime += mRefreshPeriod;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void sleep(std::chrono::nanoseconds offset) {
|
||||
if (offset < -(mRefreshPeriod / 2) || offset > mRefreshPeriod / 2) {
|
||||
offset = 0ms;
|
||||
}
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
auto targetTime = mBaseTime + mRefreshPeriod + offset;
|
||||
while (targetTime < now) {
|
||||
targetTime += mRefreshPeriod;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_until(targetTime);
|
||||
}
|
||||
|
||||
private:
|
||||
std::chrono::nanoseconds mRefreshPeriod;
|
||||
const std::chrono::nanoseconds mAppToSfDelay;
|
||||
time_point mBaseTime = std::chrono::steady_clock::now();
|
||||
|
||||
time_point mLastTimestamp = std::chrono::steady_clock::now();
|
||||
int32_t mRepeatCount = 0;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
namespace swappy {
|
||||
|
||||
ChoreographerFilter::ChoreographerFilter(std::chrono::nanoseconds refreshPeriod,
|
||||
std::chrono::nanoseconds appToSfDelay,
|
||||
Worker doWork)
|
||||
: mRefreshPeriod(refreshPeriod),
|
||||
mAppToSfDelay(appToSfDelay),
|
||||
mDoWork(doWork) {
|
||||
Settings::getInstance()->addListener([this]() { onSettingsChanged(); });
|
||||
|
||||
std::lock_guard<std::mutex> lock(mThreadPoolMutex);
|
||||
mUseAffinity = Settings::getInstance()->getUseAffinity();
|
||||
launchThreadsLocked();
|
||||
}
|
||||
|
||||
ChoreographerFilter::~ChoreographerFilter() {
|
||||
std::lock_guard<std::mutex> lock(mThreadPoolMutex);
|
||||
terminateThreadsLocked();
|
||||
}
|
||||
|
||||
void ChoreographerFilter::onChoreographer() {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mLastTimestamp = std::chrono::steady_clock::now();
|
||||
++mSequenceNumber;
|
||||
mCondition.notify_all();
|
||||
}
|
||||
|
||||
void ChoreographerFilter::launchThreadsLocked() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mIsRunning = true;
|
||||
}
|
||||
|
||||
const int32_t numThreads = getNumCpus() > 2 ? 2 : 1;
|
||||
for (int32_t thread = 0; thread < numThreads; ++thread) {
|
||||
mThreadPool.push_back(
|
||||
Thread([this, thread]() { threadMain(mUseAffinity, thread); }));
|
||||
}
|
||||
}
|
||||
|
||||
void ChoreographerFilter::terminateThreadsLocked() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mIsRunning = false;
|
||||
mCondition.notify_all();
|
||||
}
|
||||
|
||||
for (auto& thread : mThreadPool) {
|
||||
thread.join();
|
||||
}
|
||||
mThreadPool.clear();
|
||||
}
|
||||
|
||||
void ChoreographerFilter::onSettingsChanged() {
|
||||
const bool useAffinity = Settings::getInstance()->getUseAffinity();
|
||||
const Settings::DisplayTimings& displayTimings =
|
||||
Settings::getInstance()->getDisplayTimings();
|
||||
std::lock_guard<std::mutex> lock(mThreadPoolMutex);
|
||||
if (useAffinity == mUseAffinity &&
|
||||
mRefreshPeriod == displayTimings.refreshPeriod) {
|
||||
return;
|
||||
}
|
||||
|
||||
terminateThreadsLocked();
|
||||
mUseAffinity = useAffinity;
|
||||
mRefreshPeriod = displayTimings.refreshPeriod;
|
||||
mAppToSfDelay = displayTimings.sfOffset - displayTimings.appOffset;
|
||||
ALOGV(
|
||||
"onSettingsChanged(): refreshPeriod=%lld, appOffset=%lld, "
|
||||
"sfOffset=%lld",
|
||||
(long long)displayTimings.refreshPeriod.count(),
|
||||
(long long)displayTimings.appOffset.count(),
|
||||
(long long)displayTimings.sfOffset.count());
|
||||
launchThreadsLocked();
|
||||
}
|
||||
|
||||
void ChoreographerFilter::threadMain(bool useAffinity, int32_t thread) {
|
||||
Timer timer(mRefreshPeriod, mAppToSfDelay);
|
||||
|
||||
{
|
||||
int cpu = getNumCpus() - 1 - thread;
|
||||
if (cpu >= 0) {
|
||||
setAffinity(cpu);
|
||||
}
|
||||
}
|
||||
|
||||
std::string threadName = "Filter";
|
||||
threadName += swappy::to_string(thread);
|
||||
pthread_setname_np(pthread_self(), threadName.c_str());
|
||||
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
while (true) {
|
||||
auto timestamp = mLastTimestamp;
|
||||
auto workDuration = mWorkDuration;
|
||||
lock.unlock();
|
||||
|
||||
// If we have received the same timestamp multiple times, it probably
|
||||
// means that the app has stopped sending them to us, which could
|
||||
// indicate that it's no longer running. If we detect that, we stop
|
||||
// until we see a fresh timestamp to avoid spinning forever in the
|
||||
// background.
|
||||
if (!timer.addTimestamp(timestamp)) {
|
||||
lock.lock();
|
||||
mCondition.wait(lock, [=]() {
|
||||
return !mIsRunning || (mLastTimestamp != timestamp);
|
||||
});
|
||||
timestamp = mLastTimestamp;
|
||||
lock.unlock();
|
||||
timer.addTimestamp(timestamp);
|
||||
}
|
||||
|
||||
if (!mIsRunning) break;
|
||||
|
||||
timer.sleep(-workDuration);
|
||||
{
|
||||
std::unique_lock<std::mutex> workLock(mWorkMutex);
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (now - mLastWorkRun > mRefreshPeriod / 2) {
|
||||
// Assume we got here first and there's work to do
|
||||
// gamesdk::ScopedTrace trace("doWork");
|
||||
mWorkDuration = mDoWork();
|
||||
mLastWorkRun = now;
|
||||
}
|
||||
}
|
||||
lock.lock();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace swappy
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "Settings.h"
|
||||
#include "Thread.h"
|
||||
|
||||
namespace swappy {
|
||||
|
||||
class ChoreographerFilter {
|
||||
public:
|
||||
using Worker = std::function<std::chrono::nanoseconds()>;
|
||||
|
||||
explicit ChoreographerFilter(std::chrono::nanoseconds refreshPeriod,
|
||||
std::chrono::nanoseconds appToSfDelay,
|
||||
Worker doWork);
|
||||
~ChoreographerFilter();
|
||||
|
||||
void onChoreographer();
|
||||
|
||||
private:
|
||||
void launchThreadsLocked();
|
||||
void terminateThreadsLocked();
|
||||
|
||||
void onSettingsChanged();
|
||||
|
||||
void threadMain(bool useAffinity, int32_t thread);
|
||||
|
||||
std::mutex mThreadPoolMutex;
|
||||
bool mUseAffinity = true;
|
||||
std::vector<Thread> mThreadPool;
|
||||
|
||||
std::mutex mMutex;
|
||||
std::condition_variable mCondition;
|
||||
bool mIsRunning = true;
|
||||
int64_t mSequenceNumber = 0;
|
||||
std::chrono::steady_clock::time_point mLastTimestamp;
|
||||
|
||||
std::mutex mWorkMutex;
|
||||
std::chrono::steady_clock::time_point mLastWorkRun;
|
||||
std::chrono::nanoseconds mWorkDuration;
|
||||
|
||||
std::chrono::nanoseconds mRefreshPeriod;
|
||||
std::chrono::nanoseconds mAppToSfDelay;
|
||||
const Worker mDoWork;
|
||||
};
|
||||
|
||||
} // namespace swappy
|
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
//#include <android/choreographer.h>
|
||||
|
||||
// Declare the types, even if we can't access the functions.
|
||||
#if __ANDROID_API__ < 24
|
||||
|
||||
struct AChoreographer;
|
||||
typedef struct AChoreographer AChoreographer;
|
||||
|
||||
/**
|
||||
* Prototype of the function that is called when a new frame is being rendered.
|
||||
* It's passed the time that the frame is being rendered as nanoseconds in the
|
||||
* CLOCK_MONOTONIC time base, as well as the data pointer provided by the
|
||||
* application that registered a callback. All callbacks that run as part of
|
||||
* rendering a frame will observe the same frame time, so it should be used
|
||||
* whenever events need to be synchronized (e.g. animations).
|
||||
*/
|
||||
typedef void (*AChoreographer_frameCallback)(long frameTimeNanos, void* data);
|
||||
|
||||
#endif // __ANDROID_API__ < 24
|
||||
|
||||
#if __ANDROID_API__ < 30
|
||||
|
||||
/**
|
||||
* Prototype of the function that is called when the display refresh rate
|
||||
* changes. It's passed the new vsync period in nanoseconds, as well as the data
|
||||
* pointer provided by the application that registered a callback.
|
||||
*/
|
||||
typedef void (*AChoreographer_refreshRateCallback)(int64_t vsyncPeriodNanos,
|
||||
void* data);
|
||||
|
||||
#endif // __ANDROID_API__ < 30
|
@ -0,0 +1,214 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "ChoreographerThread"
|
||||
|
||||
#include "ChoreographerThread.h"
|
||||
|
||||
// #include <android/looper.h>
|
||||
// #include <jni.h>
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <condition_variable>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
|
||||
#include "ChoreographerShim.h"
|
||||
#include "CpuInfo.h"
|
||||
// #include "JNIUtil.h"
|
||||
#include "Log.h"
|
||||
#include "Settings.h"
|
||||
#include "Thread.h"
|
||||
// #include "Trace.h"
|
||||
|
||||
namespace swappy {
|
||||
|
||||
// AChoreographer is supported from API 24. To allow compilation for minSDK < 24
|
||||
// and still use AChoreographer for SDK >= 24 we need runtime support to call
|
||||
// AChoreographer APIs.
|
||||
|
||||
using PFN_AChoreographer_getInstance = AChoreographer *(*)();
|
||||
|
||||
using PFN_AChoreographer_postFrameCallback =
|
||||
void (*)(AChoreographer *choreographer,
|
||||
AChoreographer_frameCallback callback, void *data);
|
||||
|
||||
using PFN_AChoreographer_postFrameCallbackDelayed = void (*)(
|
||||
AChoreographer *choreographer, AChoreographer_frameCallback callback,
|
||||
void *data, long delayMillis);
|
||||
|
||||
using PFN_AChoreographer_registerRefreshRateCallback =
|
||||
void (*)(AChoreographer *choreographer,
|
||||
AChoreographer_refreshRateCallback callback, void *data);
|
||||
|
||||
using PFN_AChoreographer_unregisterRefreshRateCallback =
|
||||
void (*)(AChoreographer *choreographer,
|
||||
AChoreographer_refreshRateCallback callback, void *data);
|
||||
|
||||
|
||||
class NoChoreographerThread : public ChoreographerThread {
|
||||
public:
|
||||
NoChoreographerThread(Callback onChoreographer);
|
||||
~NoChoreographerThread();
|
||||
|
||||
private:
|
||||
void postFrameCallbacks() override;
|
||||
void scheduleNextFrameCallback() override REQUIRES(mWaitingMutex);
|
||||
void looperThread();
|
||||
void onSettingsChanged();
|
||||
|
||||
Thread mThread;
|
||||
bool mThreadRunning GUARDED_BY(mWaitingMutex);
|
||||
std::condition_variable_any mWaitingCondition GUARDED_BY(mWaitingMutex);
|
||||
std::chrono::nanoseconds mRefreshPeriod GUARDED_BY(mWaitingMutex);
|
||||
};
|
||||
|
||||
NoChoreographerThread::NoChoreographerThread(Callback onChoreographer)
|
||||
: ChoreographerThread(onChoreographer) {
|
||||
std::lock_guard<std::mutex> lock(mWaitingMutex);
|
||||
Settings::getInstance()->addListener([this]() { onSettingsChanged(); });
|
||||
mThreadRunning = true;
|
||||
mThread = Thread([this]() { looperThread(); });
|
||||
mInitialized = true;
|
||||
}
|
||||
|
||||
NoChoreographerThread::~NoChoreographerThread() {
|
||||
ALOGI("Destroying NoChoreographerThread");
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mWaitingMutex);
|
||||
mThreadRunning = false;
|
||||
}
|
||||
mWaitingCondition.notify_all();
|
||||
mThread.join();
|
||||
}
|
||||
|
||||
void NoChoreographerThread::onSettingsChanged() {
|
||||
const Settings::DisplayTimings &displayTimings =
|
||||
Settings::getInstance()->getDisplayTimings();
|
||||
std::lock_guard<std::mutex> lock(mWaitingMutex);
|
||||
mRefreshPeriod = displayTimings.refreshPeriod;
|
||||
ALOGV("onSettingsChanged(): refreshPeriod=%lld",
|
||||
(long long)displayTimings.refreshPeriod.count());
|
||||
}
|
||||
|
||||
void NoChoreographerThread::looperThread() {
|
||||
const char *name = "SwappyChoreographer";
|
||||
|
||||
CpuInfo cpu;
|
||||
cpu_set_t cpu_set;
|
||||
CPU_ZERO(&cpu_set);
|
||||
CPU_SET(0, &cpu_set);
|
||||
|
||||
if (cpu.getNumberOfCpus() > 0) {
|
||||
ALOGI("Swappy found {} CPUs [{}].", cpu.getNumberOfCpus(),
|
||||
cpu.getHardware().c_str());
|
||||
if (cpu.getNumberOfLittleCores() > 0) {
|
||||
cpu_set = cpu.getLittleCoresMask();
|
||||
}
|
||||
}
|
||||
|
||||
const auto tid = gettid();
|
||||
ALOGI("Setting '{}' thread [{}-0x{:x}] affinity mask to 0x{:x}.", name, tid,
|
||||
tid, to_mask(cpu_set));
|
||||
sched_setaffinity(tid, sizeof(cpu_set), &cpu_set);
|
||||
|
||||
pthread_setname_np(pthread_self(), name);
|
||||
|
||||
auto wakeTime = std::chrono::steady_clock::now();
|
||||
|
||||
while (true) {
|
||||
{
|
||||
// mutex should be unlocked before sleeping
|
||||
std::lock_guard<std::mutex> lock(mWaitingMutex);
|
||||
if (!mThreadRunning) {
|
||||
break;
|
||||
}
|
||||
mWaitingCondition.wait(mWaitingMutex);
|
||||
if (!mThreadRunning) {
|
||||
break;
|
||||
}
|
||||
|
||||
const auto timePassed = std::chrono::steady_clock::now() - wakeTime;
|
||||
const int intervals = std::floor(timePassed / mRefreshPeriod);
|
||||
wakeTime += (intervals + 1) * mRefreshPeriod;
|
||||
}
|
||||
|
||||
SPDLOG_DEBUG("sleeping {}", wakeTime.count());
|
||||
std::this_thread::sleep_until(wakeTime);
|
||||
mCallback();
|
||||
}
|
||||
ALOGI("Terminating choreographer thread");
|
||||
}
|
||||
|
||||
void NoChoreographerThread::postFrameCallbacks() {
|
||||
std::lock_guard<std::mutex> lock(mWaitingMutex);
|
||||
mWaitingCondition.notify_one();
|
||||
}
|
||||
|
||||
void NoChoreographerThread::scheduleNextFrameCallback() {}
|
||||
|
||||
ChoreographerThread::ChoreographerThread(Callback onChoreographer)
|
||||
: mCallback(onChoreographer) {}
|
||||
|
||||
ChoreographerThread::~ChoreographerThread() = default;
|
||||
|
||||
void ChoreographerThread::postFrameCallbacks() {
|
||||
// TRACE_CALL();
|
||||
|
||||
// This method is called before calling to swap buffers
|
||||
// It registers to get MAX_CALLBACKS_BEFORE_IDLE frame callbacks before
|
||||
// going idle so if app goes to idle the thread will not get further frame
|
||||
// callbacks
|
||||
std::lock_guard<std::mutex> lock(mWaitingMutex);
|
||||
if (mCallbacksBeforeIdle == 0) {
|
||||
scheduleNextFrameCallback();
|
||||
}
|
||||
mCallbacksBeforeIdle = MAX_CALLBACKS_BEFORE_IDLE;
|
||||
}
|
||||
|
||||
void ChoreographerThread::onChoreographer() {
|
||||
// TRACE_CALL();
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mWaitingMutex);
|
||||
mCallbacksBeforeIdle--;
|
||||
|
||||
if (mCallbacksBeforeIdle > 0) {
|
||||
scheduleNextFrameCallback();
|
||||
}
|
||||
}
|
||||
mCallback();
|
||||
}
|
||||
|
||||
std::unique_ptr<ChoreographerThread>
|
||||
ChoreographerThread::createChoreographerThread(Type type,
|
||||
Callback onChoreographer,
|
||||
Callback onRefreshRateChanged,
|
||||
SdkVersion sdkVersion) {
|
||||
// if (type == Type::App) {
|
||||
// ALOGI("Using Application's Choreographer");
|
||||
// return std::make_unique<NoChoreographerThread>(onChoreographer);
|
||||
// }
|
||||
|
||||
ALOGI("Using no Choreographer (Best Effort)");
|
||||
return std::make_unique<NoChoreographerThread>(onChoreographer);
|
||||
}
|
||||
|
||||
} // namespace swappy
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// #include <jni.h>
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include "SwappyDisplayManager.h"
|
||||
#include "Thread.h"
|
||||
|
||||
namespace swappy {
|
||||
|
||||
class ChoreographerThread {
|
||||
public:
|
||||
enum class Type {
|
||||
// choreographer ticks are provided by application
|
||||
App,
|
||||
|
||||
// register internally with choreographer
|
||||
Swappy,
|
||||
};
|
||||
|
||||
using Callback = std::function<void()>;
|
||||
|
||||
static std::unique_ptr<ChoreographerThread> createChoreographerThread(
|
||||
Type type, Callback onChoreographer,
|
||||
Callback onRefreshRateChanged, SdkVersion sdkVersion);
|
||||
|
||||
virtual ~ChoreographerThread() = 0;
|
||||
|
||||
virtual void postFrameCallbacks();
|
||||
|
||||
bool isInitialized() { return mInitialized; }
|
||||
|
||||
protected:
|
||||
ChoreographerThread(Callback onChoreographer);
|
||||
virtual void scheduleNextFrameCallback() REQUIRES(mWaitingMutex) = 0;
|
||||
virtual void onChoreographer();
|
||||
|
||||
std::mutex mWaitingMutex;
|
||||
int mCallbacksBeforeIdle GUARDED_BY(mWaitingMutex) = 0;
|
||||
Callback mCallback;
|
||||
bool mInitialized = false;
|
||||
|
||||
static constexpr int MAX_CALLBACKS_BEFORE_IDLE = 10;
|
||||
};
|
||||
|
||||
} // namespace swappy
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <sched.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace swappy {
|
||||
|
||||
class CpuInfo {
|
||||
public:
|
||||
struct Cpu {
|
||||
enum class Type { Little, Big };
|
||||
|
||||
int id;
|
||||
int package_id;
|
||||
long frequency;
|
||||
|
||||
Type type;
|
||||
};
|
||||
|
||||
CpuInfo();
|
||||
|
||||
unsigned int getNumberOfCpus() const;
|
||||
|
||||
const std::vector<Cpu>& getCpus() const;
|
||||
const std::string getHardware() const;
|
||||
|
||||
unsigned int getNumberOfLittleCores() const;
|
||||
unsigned int getNumberOfBigCores() const;
|
||||
|
||||
cpu_set_t getLittleCoresMask() const;
|
||||
cpu_set_t getBigCoresMask() const;
|
||||
|
||||
private:
|
||||
std::vector<Cpu> mCpus;
|
||||
std::string mHardware;
|
||||
|
||||
unsigned int mNumberOfLittleCores = 0;
|
||||
unsigned int mNumberOfBigCores = 0;
|
||||
|
||||
cpu_set_t mLittleCoresMask;
|
||||
cpu_set_t mBigCoresMask;
|
||||
};
|
||||
|
||||
unsigned int to_mask(cpu_set_t cpu_set);
|
||||
|
||||
} // namespace swappy
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <swappy/swappyGL_extra.h>
|
||||
|
||||
namespace swappy {
|
||||
|
||||
class FrameStatistics {
|
||||
public:
|
||||
virtual ~FrameStatistics() {}
|
||||
virtual int32_t lastLatencyRecorded() const = 0;
|
||||
// Only the essential latency statistics, not full.
|
||||
virtual bool isEssential() const = 0;
|
||||
virtual SwappyStats getStats() = 0;
|
||||
};
|
||||
|
||||
} // namespace swappy
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#define ALOGE(...) SPDLOG_ERROR(__VA_ARGS__)
|
||||
#define ALOGW(...) SPDLOG_WARN(__VA_ARGS__)
|
||||
#define ALOGI(...) SPDLOG_INFO(__VA_ARGS__)
|
||||
#define ALOGW_ONCE_IF(cond, ...) \
|
||||
do { \
|
||||
static bool alogw_once##__FILE__##__LINE__##__ = true; \
|
||||
if (cond && alogw_once##__FILE__##__LINE__##__) { \
|
||||
alogw_once##__FILE__##__LINE__##__ = false; \
|
||||
ALOGW(__VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
#define ALOGE_ONCE(...) \
|
||||
do { \
|
||||
static bool aloge_once##__FILE__##__LINE__##__ = true; \
|
||||
if (aloge_once##__FILE__##__LINE__##__) { \
|
||||
aloge_once##__FILE__##__LINE__##__ = false; \
|
||||
ALOGE(__VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define ALOGV(...) \
|
||||
SPDLOG_TRACE(__VA_ARGS__)
|
||||
#else
|
||||
#define ALOGV(...)
|
||||
#endif
|
||||
|
||||
namespace swappy {
|
||||
|
||||
std::string to_string(int value);
|
||||
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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 "Settings.h"
|
||||
|
||||
#define LOG_TAG "Settings"
|
||||
|
||||
//#include "Log.h"
|
||||
|
||||
namespace swappy {
|
||||
|
||||
std::unique_ptr<Settings> Settings::instance;
|
||||
|
||||
Settings* Settings::getInstance() {
|
||||
if (!instance) {
|
||||
instance = std::make_unique<Settings>(ConstructorTag{});
|
||||
}
|
||||
return instance.get();
|
||||
}
|
||||
|
||||
void Settings::reset() { instance.reset(); }
|
||||
|
||||
void Settings::addListener(Listener listener) {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mListeners.emplace_back(std::move(listener));
|
||||
}
|
||||
|
||||
void Settings::setDisplayTimings(const DisplayTimings& displayTimings) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mDisplayTimings = displayTimings;
|
||||
}
|
||||
// Notify the listeners without the lock held
|
||||
notifyListeners();
|
||||
}
|
||||
void Settings::setSwapDuration(uint64_t swapNs) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mSwapDuration = std::chrono::nanoseconds(swapNs);
|
||||
}
|
||||
// Notify the listeners without the lock held
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void Settings::setUseAffinity(bool tf) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mUseAffinity = tf;
|
||||
}
|
||||
// Notify the listeners without the lock held
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
const Settings::DisplayTimings& Settings::getDisplayTimings() const {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return mDisplayTimings;
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds Settings::getSwapDuration() const {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return mSwapDuration;
|
||||
}
|
||||
|
||||
bool Settings::getUseAffinity() const {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
return mUseAffinity;
|
||||
}
|
||||
|
||||
void Settings::notifyListeners() {
|
||||
// Grab a local copy of the listeners
|
||||
std::vector<Listener> listeners;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
listeners = mListeners;
|
||||
}
|
||||
|
||||
// Call the listeners without the lock held
|
||||
for (const auto& listener : listeners) {
|
||||
listener();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace swappy
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Thread.h"
|
||||
|
||||
namespace swappy {
|
||||
|
||||
class Settings {
|
||||
private:
|
||||
// Allows construction with std::unique_ptr from a static method, but
|
||||
// disallows construction outside of the class since no one else can
|
||||
// construct a ConstructorTag
|
||||
struct ConstructorTag {};
|
||||
|
||||
public:
|
||||
struct DisplayTimings {
|
||||
std::chrono::nanoseconds refreshPeriod{0};
|
||||
std::chrono::nanoseconds appOffset{0};
|
||||
std::chrono::nanoseconds sfOffset{0};
|
||||
};
|
||||
|
||||
explicit Settings(ConstructorTag){};
|
||||
|
||||
static Settings* getInstance();
|
||||
|
||||
static void reset();
|
||||
|
||||
using Listener = std::function<void()>;
|
||||
void addListener(Listener listener);
|
||||
|
||||
void setDisplayTimings(const DisplayTimings& displayTimings);
|
||||
void setSwapDuration(uint64_t swapNs);
|
||||
void setUseAffinity(bool);
|
||||
|
||||
const DisplayTimings& getDisplayTimings() const;
|
||||
std::chrono::nanoseconds getSwapDuration() const;
|
||||
bool getUseAffinity() const;
|
||||
|
||||
private:
|
||||
void notifyListeners();
|
||||
|
||||
static std::unique_ptr<Settings> instance;
|
||||
|
||||
mutable std::mutex mMutex;
|
||||
std::vector<Listener> mListeners GUARDED_BY(mMutex);
|
||||
|
||||
DisplayTimings mDisplayTimings GUARDED_BY(mMutex);
|
||||
std::chrono::nanoseconds mSwapDuration GUARDED_BY(mMutex) =
|
||||
std::chrono::nanoseconds(16'666'667L);
|
||||
bool mUseAffinity GUARDED_BY(mMutex) = true;
|
||||
};
|
||||
|
||||
} // namespace swappy
|
@ -0,0 +1,941 @@
|
||||
/*
|
||||
* 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;
|
||||
auto vsyncPresentationDeadlineNanos = ONE_S_IN_NS / 100;
|
||||
const auto refreshRateHz = 100;
|
||||
|
||||
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
|
@ -0,0 +1,366 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
//#include <jni.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <deque>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include "CPUTracer.h"
|
||||
#include "Settings.h"
|
||||
#include "ChoreographerFilter.h"
|
||||
#include "ChoreographerThread.h"
|
||||
#include "FrameStatistics.h"
|
||||
#include "SwappyDisplayManager.h"
|
||||
#include "Thread.h"
|
||||
//#include "swappy/swappyGL.h"
|
||||
//#include "swappy/swappyGL_extra.h"
|
||||
|
||||
namespace swappy {
|
||||
|
||||
// ANativeWindow_setFrameRate is supported from API 30. To allow compilation for
|
||||
// minSDK < 30 we need runtime support to call this API.
|
||||
using PFN_ANativeWindow_setFrameRate = int32_t (*)(ANativeWindow* window,
|
||||
float frameRate,
|
||||
int8_t compatibility);
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
struct SwappyCommonSettings {
|
||||
SdkVersion sdkVersion;
|
||||
|
||||
std::chrono::nanoseconds refreshPeriod;
|
||||
std::chrono::nanoseconds appVsyncOffset;
|
||||
std::chrono::nanoseconds sfVsyncOffset;
|
||||
|
||||
static bool getFromApp(//JNIEnv* env, jobject jactivity,
|
||||
SwappyCommonSettings* out);
|
||||
static SdkVersion getSDKVersion(/*JNIEnv* env*/);
|
||||
static bool queryDisplayTimings(//JNIEnv* env, jobject jactivity,
|
||||
SwappyCommonSettings* out);
|
||||
};
|
||||
|
||||
// Common part between OpenGL and Vulkan implementations.
|
||||
class SwappyCommon {
|
||||
public:
|
||||
enum class PipelineMode { Off, On };
|
||||
|
||||
// callbacks to be called during pre/post swap
|
||||
struct SwapHandlers {
|
||||
std::function<bool()> lastFrameIsComplete;
|
||||
std::function<std::chrono::nanoseconds()> getPrevFrameGpuTime;
|
||||
};
|
||||
|
||||
SwappyCommon(/*JNIEnv* env, jobject jactivity*/);
|
||||
|
||||
~SwappyCommon();
|
||||
|
||||
std::chrono::nanoseconds getSwapDuration();
|
||||
|
||||
void onChoreographer(int64_t frameTimeNanos);
|
||||
|
||||
void onPreSwap(const SwapHandlers& h);
|
||||
|
||||
bool needToSetPresentationTime() { return mPresentationTimeNeeded; }
|
||||
|
||||
void onPostSwap(const SwapHandlers& h);
|
||||
|
||||
PipelineMode getCurrentPipelineMode() { return mPipelineMode; }
|
||||
|
||||
template <typename... T>
|
||||
using Tracer = std::function<void(T...)>;
|
||||
void addTracerCallbacks(SwappyTracer tracer);
|
||||
|
||||
void setAutoSwapInterval(bool enabled);
|
||||
void setAutoPipelineMode(bool enabled);
|
||||
|
||||
void setMaxAutoSwapDuration(std::chrono::nanoseconds swapDuration) {
|
||||
mAutoSwapIntervalThreshold = swapDuration;
|
||||
}
|
||||
|
||||
std::chrono::steady_clock::time_point getPresentationTime() {
|
||||
return mPresentationTime;
|
||||
}
|
||||
std::chrono::nanoseconds getRefreshPeriod() const {
|
||||
return mCommonSettings.refreshPeriod;
|
||||
}
|
||||
|
||||
bool isValid() { return mValid; }
|
||||
|
||||
std::chrono::nanoseconds getFenceTimeout() const { return mFenceTimeout; }
|
||||
void setFenceTimeout(std::chrono::nanoseconds t) { mFenceTimeout = t; }
|
||||
|
||||
bool isDeviceUnsupported();
|
||||
|
||||
void setANativeWindow(ANativeWindow* window);
|
||||
|
||||
void setFrameStatistics(
|
||||
const std::shared_ptr<FrameStatistics>& frameStats) {
|
||||
mFrameStatistics = frameStats;
|
||||
}
|
||||
|
||||
void setBufferStuffingFixWait(int32_t nFrames) {
|
||||
mBufferStuffingFixWait = std::max(0, nFrames);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Used for testing
|
||||
SwappyCommon(const SwappyCommonSettings& settings);
|
||||
|
||||
private:
|
||||
class FrameDuration {
|
||||
public:
|
||||
FrameDuration() = default;
|
||||
|
||||
FrameDuration(std::chrono::nanoseconds cpuTime,
|
||||
std::chrono::nanoseconds gpuTime,
|
||||
bool frameMissedDeadline)
|
||||
: mCpuTime(cpuTime),
|
||||
mGpuTime(gpuTime),
|
||||
mFrameMissedDeadline(frameMissedDeadline) {
|
||||
mCpuTime = std::min(mCpuTime, MAX_DURATION);
|
||||
mGpuTime = std::min(mGpuTime, MAX_DURATION);
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds getCpuTime() const { return mCpuTime; }
|
||||
std::chrono::nanoseconds getGpuTime() const { return mGpuTime; }
|
||||
|
||||
bool frameMiss() const { return mFrameMissedDeadline; }
|
||||
|
||||
std::chrono::nanoseconds getTime(PipelineMode pipeline) const {
|
||||
if (mCpuTime == 0ns && mGpuTime == 0ns) {
|
||||
return 0ns;
|
||||
}
|
||||
|
||||
if (pipeline == PipelineMode::On) {
|
||||
return std::max(mCpuTime, mGpuTime) + FRAME_MARGIN;
|
||||
}
|
||||
|
||||
return mCpuTime + mGpuTime + FRAME_MARGIN;
|
||||
}
|
||||
|
||||
FrameDuration& operator+=(const FrameDuration& other) {
|
||||
mCpuTime += other.mCpuTime;
|
||||
mGpuTime += other.mGpuTime;
|
||||
return *this;
|
||||
}
|
||||
|
||||
FrameDuration& operator-=(const FrameDuration& other) {
|
||||
mCpuTime -= other.mCpuTime;
|
||||
mGpuTime -= other.mGpuTime;
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend FrameDuration operator/(FrameDuration lhs, int rhs) {
|
||||
lhs.mCpuTime /= rhs;
|
||||
lhs.mGpuTime /= rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
private:
|
||||
std::chrono::nanoseconds mCpuTime = std::chrono::nanoseconds(0);
|
||||
std::chrono::nanoseconds mGpuTime = std::chrono::nanoseconds(0);
|
||||
bool mFrameMissedDeadline = false;
|
||||
|
||||
static constexpr std::chrono::nanoseconds MAX_DURATION =
|
||||
std::chrono::milliseconds(100);
|
||||
};
|
||||
|
||||
void addFrameDuration(FrameDuration duration);
|
||||
std::chrono::nanoseconds wakeClient();
|
||||
|
||||
bool swapFaster(int newSwapInterval) REQUIRES(mMutex);
|
||||
|
||||
bool swapSlower(const FrameDuration& averageFrameTime,
|
||||
const std::chrono::nanoseconds& upperBound,
|
||||
int newSwapInterval) REQUIRES(mMutex);
|
||||
bool updateSwapInterval();
|
||||
void preSwapBuffersCallbacks();
|
||||
void postSwapBuffersCallbacks();
|
||||
void preWaitCallbacks();
|
||||
void postWaitCallbacks(std::chrono::nanoseconds cpuTime,
|
||||
std::chrono::nanoseconds gpuTime);
|
||||
void startFrameCallbacks();
|
||||
void swapIntervalChangedCallbacks();
|
||||
void onSettingsChanged();
|
||||
void updateMeasuredSwapDuration(std::chrono::nanoseconds duration);
|
||||
void startFrame();
|
||||
void waitUntil(int32_t target);
|
||||
void waitUntilTargetFrame();
|
||||
void waitOneFrame();
|
||||
void setPreferredDisplayModeId(int index);
|
||||
void setPreferredRefreshPeriod(std::chrono::nanoseconds frameTime)
|
||||
REQUIRES(mMutex);
|
||||
int calculateSwapInterval(std::chrono::nanoseconds frameTime,
|
||||
std::chrono::nanoseconds refreshPeriod);
|
||||
void updateDisplayTimings();
|
||||
|
||||
// Waits for the next frame, considering both Choreographer and the prior
|
||||
// frame's completion
|
||||
bool waitForNextFrame(const SwapHandlers& h);
|
||||
|
||||
void onRefreshRateChanged();
|
||||
|
||||
inline bool swapFasterCondition() {
|
||||
return mSwapDuration <=
|
||||
mCommonSettings.refreshPeriod * (mAutoSwapInterval - 1) +
|
||||
DURATION_ROUNDING_MARGIN;
|
||||
}
|
||||
|
||||
// const jobject mJactivity;
|
||||
// void* mLibAndroid = nullptr;
|
||||
PFN_ANativeWindow_setFrameRate mANativeWindow_setFrameRate = nullptr;
|
||||
|
||||
// JavaVM* mJVM = nullptr;
|
||||
|
||||
SwappyCommonSettings mCommonSettings;
|
||||
|
||||
std::unique_ptr<ChoreographerFilter> mChoreographerFilter;
|
||||
|
||||
bool mUsingExternalChoreographer = false;
|
||||
std::unique_ptr<ChoreographerThread> mChoreographerThread;
|
||||
// std::thread mWakerThread;
|
||||
// bool mQuitThread = false;
|
||||
|
||||
std::mutex mWaitingMutex;
|
||||
std::condition_variable mWaitingCondition;
|
||||
std::chrono::steady_clock::time_point mCurrentFrameTimestamp =
|
||||
std::chrono::steady_clock::now();
|
||||
int32_t mCurrentFrame = 0;
|
||||
std::atomic<std::chrono::nanoseconds> mMeasuredSwapDuration;
|
||||
|
||||
std::chrono::steady_clock::time_point mSwapTime;
|
||||
|
||||
std::mutex mMutex;
|
||||
class FrameDurations {
|
||||
public:
|
||||
void add(FrameDuration frameDuration);
|
||||
bool hasEnoughSamples() const;
|
||||
FrameDuration getAverageFrameTime() const;
|
||||
int getMissedFramePercent() const;
|
||||
void clear();
|
||||
|
||||
private:
|
||||
static constexpr std::chrono::nanoseconds
|
||||
FRAME_DURATION_SAMPLE_SECONDS = 2s;
|
||||
|
||||
std::deque<std::pair<std::chrono::time_point<std::chrono::steady_clock>,
|
||||
FrameDuration>>
|
||||
mFrames;
|
||||
FrameDuration mFrameDurationsSum = {};
|
||||
int mMissedFrameCount = 0;
|
||||
};
|
||||
|
||||
FrameDurations mFrameDurations GUARDED_BY(mMutex);
|
||||
|
||||
bool mAutoSwapIntervalEnabled GUARDED_BY(mMutex) = true;
|
||||
bool mPipelineModeAutoMode GUARDED_BY(mMutex) = true;
|
||||
|
||||
static constexpr std::chrono::nanoseconds FRAME_MARGIN = 1ms;
|
||||
static constexpr std::chrono::nanoseconds DURATION_ROUNDING_MARGIN = 1us;
|
||||
static constexpr int NON_PIPELINE_PERCENT = 50; // 50%
|
||||
static constexpr int FRAME_DROP_THRESHOLD = 10; // 10%
|
||||
|
||||
std::chrono::nanoseconds mSwapDuration = 0ns;
|
||||
int32_t mAutoSwapInterval;
|
||||
std::atomic<std::chrono::nanoseconds> mAutoSwapIntervalThreshold = {
|
||||
50ms}; // 20FPS
|
||||
static constexpr std::chrono::nanoseconds REFRESH_RATE_MARGIN = 500ns;
|
||||
|
||||
std::chrono::steady_clock::time_point mStartFrameTime;
|
||||
|
||||
struct SwappyTracerCallbacks {
|
||||
std::list<Tracer<>> preWait;
|
||||
std::list<Tracer<int64_t, int64_t>> postWait;
|
||||
std::list<Tracer<>> preSwapBuffers;
|
||||
std::list<Tracer<int64_t>> postSwapBuffers;
|
||||
std::list<Tracer<int32_t, long>> startFrame;
|
||||
std::list<Tracer<>> swapIntervalChanged;
|
||||
};
|
||||
|
||||
SwappyTracerCallbacks mInjectedTracers;
|
||||
|
||||
int32_t mTargetFrame = 0;
|
||||
std::chrono::steady_clock::time_point mPresentationTime =
|
||||
std::chrono::steady_clock::now();
|
||||
bool mPresentationTimeNeeded;
|
||||
PipelineMode mPipelineMode = PipelineMode::On;
|
||||
|
||||
bool mValid;
|
||||
|
||||
std::chrono::nanoseconds mFenceTimeout = std::chrono::nanoseconds(50ms);
|
||||
|
||||
constexpr static bool USE_DISPLAY_MANAGER = true;
|
||||
std::unique_ptr<SwappyDisplayManager> mDisplayManager;
|
||||
int mNextModeId = -1;
|
||||
|
||||
std::shared_ptr<SwappyDisplayManager::RefreshPeriodMap>
|
||||
mSupportedRefreshPeriods;
|
||||
|
||||
struct TimingSettings {
|
||||
std::chrono::nanoseconds refreshPeriod = {};
|
||||
std::chrono::nanoseconds swapDuration = {};
|
||||
|
||||
static TimingSettings from(const Settings& settings) {
|
||||
TimingSettings timingSettings;
|
||||
|
||||
timingSettings.refreshPeriod =
|
||||
settings.getDisplayTimings().refreshPeriod;
|
||||
timingSettings.swapDuration = settings.getSwapDuration();
|
||||
return timingSettings;
|
||||
}
|
||||
|
||||
bool operator!=(const TimingSettings& other) const {
|
||||
return (refreshPeriod != other.refreshPeriod) ||
|
||||
(swapDuration != other.swapDuration);
|
||||
}
|
||||
|
||||
bool operator==(const TimingSettings& other) const {
|
||||
return !(*this != other);
|
||||
}
|
||||
};
|
||||
TimingSettings mNextTimingSettings GUARDED_BY(mMutex) = {};
|
||||
bool mTimingSettingsNeedUpdate GUARDED_BY(mMutex) = false;
|
||||
|
||||
// CPUTracer mCPUTracer;
|
||||
|
||||
ANativeWindow* mWindow GUARDED_BY(mMutex) = nullptr;
|
||||
bool mWindowChanged GUARDED_BY(mMutex) = false;
|
||||
float mLatestFrameRateVote GUARDED_BY(mMutex) = 0.f;
|
||||
static constexpr float FRAME_RATE_VOTE_MARGIN = 1.f; // 1Hz
|
||||
|
||||
// If zero, don't apply the double buffering fix. If non-zero, apply
|
||||
// the fix after this number of bad frames.
|
||||
int mBufferStuffingFixWait = 0;
|
||||
// When zero, buffer stuffing fixing may occur.
|
||||
// After a fix has been applied, this is non-zero and counts down to avoid
|
||||
// consecutive fixes.
|
||||
int mBufferStuffingFixCounter = 0;
|
||||
// Counts the number of consecutive missed frames (as judged by expected
|
||||
// latency).
|
||||
int mMissedFrameCounter = 0;
|
||||
|
||||
std::shared_ptr<FrameStatistics> mFrameStatistics;
|
||||
};
|
||||
|
||||
} // namespace swappy
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "SwappyDisplayManager"
|
||||
|
||||
#include "SwappyDisplayManager.h"
|
||||
|
||||
#include "Log.h"
|
||||
//#include <android/looper.h>
|
||||
//#include <jni.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
//#include "JNIUtil.h"
|
||||
#include "Settings.h"
|
||||
|
||||
namespace swappy {
|
||||
|
||||
bool SwappyDisplayManager::useSwappyDisplayManager(SdkVersion sdkVersion) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SwappyDisplayManager::SwappyDisplayManager()
|
||||
{
|
||||
}
|
||||
|
||||
SwappyDisplayManager::~SwappyDisplayManager() {
|
||||
}
|
||||
|
||||
std::shared_ptr<SwappyDisplayManager::RefreshPeriodMap>
|
||||
SwappyDisplayManager::getSupportedRefreshPeriods() {
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
|
||||
mCondition.wait(
|
||||
lock, [&]() { return mSupportedRefreshPeriods.get() != nullptr; });
|
||||
return mSupportedRefreshPeriods;
|
||||
}
|
||||
|
||||
void SwappyDisplayManager::setPreferredDisplayModeId(int index) {
|
||||
}
|
||||
|
||||
// // Helper class to wrap JNI entry points to SwappyDisplayManager
|
||||
// class SwappyDisplayManagerJNI {
|
||||
// public:
|
||||
// static void onSetSupportedRefreshPeriods(
|
||||
// long, std::shared_ptr<SwappyDisplayManager::RefreshPeriodMap>);
|
||||
// static void onRefreshPeriodChanged(long, long, long, long);
|
||||
// };
|
||||
//
|
||||
// void SwappyDisplayManagerJNI::onSetSupportedRefreshPeriods(
|
||||
// std::shared_ptr<SwappyDisplayManager::RefreshPeriodMap> refreshPeriods) {
|
||||
// auto *sDM = reinterpret_cast<SwappyDisplayManager *>(cookie);
|
||||
//
|
||||
// std::lock_guard<std::mutex> lock(sDM->mMutex);
|
||||
// sDM->mSupportedRefreshPeriods = std::move(refreshPeriods);
|
||||
// sDM->mCondition.notify_one();
|
||||
// }
|
||||
//
|
||||
// void SwappyDisplayManagerJNI::onRefreshPeriodChanged(//jlong /*cookie*/,
|
||||
// long refreshPeriod,
|
||||
// long appOffset,
|
||||
// long sfOffset) {
|
||||
// ALOGV("onRefreshPeriodChanged: refresh rate: %.0fHz", 1e9f / refreshPeriod);
|
||||
// using std::chrono::nanoseconds;
|
||||
// Settings::DisplayTimings displayTimings;
|
||||
// displayTimings.refreshPeriod = nanoseconds(refreshPeriod);
|
||||
// displayTimings.appOffset = nanoseconds(appOffset);
|
||||
// displayTimings.sfOffset = nanoseconds(sfOffset);
|
||||
// Settings::getInstance()->setDisplayTimings(displayTimings);
|
||||
// }
|
||||
|
||||
} // namespace swappy
|
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
//#include <jni.h>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
namespace swappy {
|
||||
|
||||
struct SdkVersion {
|
||||
int sdkInt; // Build.VERSION.SDK_INT
|
||||
int previewSdkInt; // Build.VERSION.PREVIEW_SDK_INT
|
||||
};
|
||||
|
||||
class SwappyDisplayManager {
|
||||
public:
|
||||
static bool useSwappyDisplayManager(SdkVersion sdkVersion);
|
||||
|
||||
SwappyDisplayManager(/*...*/);
|
||||
~SwappyDisplayManager();
|
||||
|
||||
bool isInitialized() { return mInitialized; }
|
||||
|
||||
// Map from refresh period to display mode id
|
||||
using RefreshPeriodMap = std::map<std::chrono::nanoseconds, int>;
|
||||
|
||||
std::shared_ptr<RefreshPeriodMap> getSupportedRefreshPeriods();
|
||||
|
||||
void setPreferredDisplayModeId(int index);
|
||||
|
||||
private:
|
||||
std::mutex mMutex;
|
||||
std::condition_variable mCondition;
|
||||
std::shared_ptr<RefreshPeriodMap> mSupportedRefreshPeriods;
|
||||
//jmethodID mSetPreferredDisplayModeId = nullptr;
|
||||
//jmethodID mTerminate = nullptr;
|
||||
bool mInitialized = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace swappy
|
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* 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 "Thread.h"
|
||||
|
||||
#include <sched.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include "swappy/swappy_common.h"
|
||||
|
||||
#define LOG_TAG "SwappyThread"
|
||||
#include "Log.h"
|
||||
|
||||
namespace swappy {
|
||||
|
||||
int32_t getNumCpus() {
|
||||
static int32_t sNumCpus = []() {
|
||||
pid_t pid = gettid();
|
||||
cpu_set_t cpuSet;
|
||||
CPU_ZERO(&cpuSet);
|
||||
sched_getaffinity(pid, sizeof(cpuSet), &cpuSet);
|
||||
|
||||
int32_t numCpus = 0;
|
||||
while (CPU_ISSET(numCpus, &cpuSet)) {
|
||||
++numCpus;
|
||||
}
|
||||
|
||||
return numCpus;
|
||||
}();
|
||||
|
||||
return sNumCpus;
|
||||
}
|
||||
|
||||
void setAffinity(int32_t cpu) {
|
||||
cpu_set_t cpuSet;
|
||||
CPU_ZERO(&cpuSet);
|
||||
CPU_SET(cpu, &cpuSet);
|
||||
sched_setaffinity(gettid(), sizeof(cpuSet), &cpuSet);
|
||||
}
|
||||
|
||||
void setAffinity(Affinity affinity) {
|
||||
const int32_t numCpus = getNumCpus();
|
||||
|
||||
cpu_set_t cpuSet;
|
||||
CPU_ZERO(&cpuSet);
|
||||
for (int32_t cpu = 0; cpu < numCpus; ++cpu) {
|
||||
switch (affinity) {
|
||||
case Affinity::None_:
|
||||
CPU_SET(cpu, &cpuSet);
|
||||
break;
|
||||
case Affinity::Even:
|
||||
if (cpu % 2 == 0) CPU_SET(cpu, &cpuSet);
|
||||
break;
|
||||
case Affinity::Odd:
|
||||
if (cpu % 2 == 1) CPU_SET(cpu, &cpuSet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sched_setaffinity(gettid(), sizeof(cpuSet), &cpuSet);
|
||||
}
|
||||
|
||||
static const SwappyThreadFunctions* s_ext_thread_manager = nullptr;
|
||||
|
||||
struct ThreadImpl {
|
||||
virtual ~ThreadImpl() {}
|
||||
virtual bool joinable() = 0;
|
||||
virtual void join() = 0;
|
||||
};
|
||||
|
||||
struct ExtThreadImpl : public ThreadImpl {
|
||||
std::function<void()> fn_;
|
||||
SwappyThreadId id_;
|
||||
|
||||
public:
|
||||
ExtThreadImpl(std::function<void()>&& fn) : fn_(std::move(fn)) {
|
||||
if (s_ext_thread_manager->start(&id_, startThread, this) != 0) {
|
||||
ALOGE("Couldn't create thread");
|
||||
}
|
||||
}
|
||||
void join() { s_ext_thread_manager->join(id_); }
|
||||
bool joinable() { return s_ext_thread_manager->joinable(id_); }
|
||||
static void* startThread(void* x) {
|
||||
ExtThreadImpl* impl = (ExtThreadImpl*)x;
|
||||
impl->fn_();
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
struct StlThreadImpl : public ThreadImpl {
|
||||
std::thread thread_;
|
||||
|
||||
public:
|
||||
StlThreadImpl(std::function<void()>&& fn) : thread_(std::move(fn)) {}
|
||||
void join() { thread_.join(); }
|
||||
bool joinable() { return thread_.joinable(); }
|
||||
};
|
||||
|
||||
Thread::Thread() noexcept {}
|
||||
|
||||
Thread::~Thread() {}
|
||||
|
||||
Thread::Thread(std::function<void()>&& fn) noexcept {
|
||||
if (s_ext_thread_manager != nullptr) {
|
||||
impl_ = std::make_unique<ExtThreadImpl>(std::move(fn));
|
||||
} else {
|
||||
impl_ = std::make_unique<StlThreadImpl>(std::move(fn));
|
||||
}
|
||||
}
|
||||
|
||||
Thread::Thread(Thread&& rhs) noexcept : impl_(std::move(rhs.impl_)) {}
|
||||
|
||||
Thread& Thread::operator=(Thread&& rhs) noexcept {
|
||||
if (&rhs != this) {
|
||||
impl_ = std::move(rhs.impl_);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Thread::join() {
|
||||
if (impl_.get()) {
|
||||
impl_->join();
|
||||
}
|
||||
}
|
||||
|
||||
bool Thread::joinable() {
|
||||
return (impl_.get() != nullptr && impl_->joinable());
|
||||
}
|
||||
|
||||
} // namespace swappy
|
||||
|
||||
extern "C" void Swappy_setThreadFunctions(const SwappyThreadFunctions* mgr) {
|
||||
swappy::s_ext_thread_manager = mgr;
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
// Enable thread safety attributes only with clang.
|
||||
// The attributes can be safely erased when compiling with other compilers.
|
||||
#if defined(__clang__) && (!defined(SWIG))
|
||||
#define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x))
|
||||
#else
|
||||
#define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op
|
||||
#endif
|
||||
|
||||
#if !defined GAMESDK_THREAD_CHECKS
|
||||
#define GAMESDK_THREAD_CHECKS 1
|
||||
#endif
|
||||
|
||||
#if GAMESDK_THREAD_CHECKS
|
||||
#define GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x))
|
||||
|
||||
#define REQUIRES(...) \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__))
|
||||
|
||||
#define NO_THREAD_SAFETY_ANALYSIS \
|
||||
THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)
|
||||
#else
|
||||
#define GUARDED_BY(x)
|
||||
#define REQUIRES(...)
|
||||
#define NO_THREAD_SAFETY_ANALYSIS
|
||||
#endif
|
||||
|
||||
namespace swappy {
|
||||
|
||||
enum class Affinity { None_, Even, Odd };
|
||||
|
||||
int32_t getNumCpus();
|
||||
void setAffinity(int32_t cpu);
|
||||
void setAffinity(Affinity affinity);
|
||||
|
||||
struct ThreadImpl;
|
||||
|
||||
class Thread {
|
||||
std::unique_ptr<ThreadImpl> impl_;
|
||||
|
||||
public:
|
||||
Thread() noexcept;
|
||||
Thread(std::function<void()>&& fn) noexcept;
|
||||
Thread(Thread&& rhs) noexcept;
|
||||
Thread(const Thread&) = delete;
|
||||
Thread& operator=(Thread&& rhs) noexcept;
|
||||
Thread& operator=(const Thread& rhs) = delete;
|
||||
~Thread();
|
||||
void join();
|
||||
bool joinable();
|
||||
};
|
||||
|
||||
} // namespace swappy
|
@ -0,0 +1 @@
|
||||
include ../../src/swappy/OWNERS
|
@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup swappyGL Swappy for OpenGL
|
||||
* OpenGL part of Swappy.
|
||||
* @{
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
#include <jni.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "swappy_common.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Initialize Swappy, getting the required Android parameters from the
|
||||
* display subsystem via JNI.
|
||||
* @param env The JNI environment where Swappy is used
|
||||
* @param jactivity The activity where Swappy is used
|
||||
* @return false if Swappy failed to initialize.
|
||||
* @see SwappyGL_destroy
|
||||
*/
|
||||
bool SwappyGL_init(JNIEnv *env, jobject jactivity);
|
||||
|
||||
/**
|
||||
* @brief Check if Swappy was successfully initialized.
|
||||
* @return false if either the `swappy.disable` system property is not `false`
|
||||
* or the required OpenGL extensions are not available for Swappy to work.
|
||||
*/
|
||||
bool SwappyGL_isEnabled();
|
||||
|
||||
/**
|
||||
* @brief Destroy resources and stop all threads that Swappy has created.
|
||||
* @see SwappyGL_init
|
||||
*/
|
||||
void SwappyGL_destroy();
|
||||
|
||||
/**
|
||||
* @brief Tell Swappy which ANativeWindow to use when calling to ANativeWindow_*
|
||||
* API.
|
||||
* @param window ANativeWindow that was used to create the EGLSurface.
|
||||
* @return true on success, false if Swappy was not initialized.
|
||||
*/
|
||||
bool SwappyGL_setWindow(ANativeWindow *window);
|
||||
|
||||
/**
|
||||
* @brief Replace calls to eglSwapBuffers with this. Swappy will wait for the
|
||||
* previous frame's buffer to be processed by the GPU before actually calling
|
||||
* eglSwapBuffers.
|
||||
* @return true on success or false if
|
||||
* 1) Swappy is not initialized or 2) eglSwapBuffers did not return EGL_TRUE.
|
||||
* In the latter case, eglGetError can be used to get the error code.
|
||||
*/
|
||||
bool SwappyGL_swap(EGLDisplay display, EGLSurface surface);
|
||||
|
||||
// Paramter setters:
|
||||
|
||||
void SwappyGL_setUseAffinity(bool tf);
|
||||
|
||||
/**
|
||||
* @brief Override the swap interval
|
||||
*
|
||||
* By default, Swappy will adjust the swap interval based on actual frame
|
||||
* rendering time.
|
||||
*
|
||||
* If an app wants to override the swap interval calculated by Swappy, it can
|
||||
* call this function:
|
||||
*
|
||||
* * This will temporarily override Swappy's frame timings but, unless
|
||||
* `SwappyGL_setAutoSwapInterval(false)` is called, the timings will continue
|
||||
* to be be updated dynamically, so the swap interval may change.
|
||||
*
|
||||
* * This set the **minimal** interval to run. For example,
|
||||
* `SwappyGL_setSwapIntervalNS(SWAPPY_SWAP_30FPS)` will not allow Swappy to swap
|
||||
* faster, even if auto mode decides that it can. But it can go slower if auto
|
||||
* mode is on.
|
||||
*
|
||||
* @param swap_ns The new swap interval value, in nanoseconds.
|
||||
*/
|
||||
void SwappyGL_setSwapIntervalNS(uint64_t swap_ns);
|
||||
|
||||
/**
|
||||
* @brief Set the fence timeout parameter, for devices with faulty
|
||||
* drivers. Its default value is 50,000,000ns (50ms).
|
||||
*/
|
||||
void SwappyGL_setFenceTimeoutNS(uint64_t fence_timeout_ns);
|
||||
|
||||
// Parameter getters:
|
||||
|
||||
/**
|
||||
* @brief Get the refresh period value, in nanoseconds.
|
||||
*/
|
||||
uint64_t SwappyGL_getRefreshPeriodNanos();
|
||||
|
||||
/**
|
||||
* @brief Get the swap interval value, in nanoseconds.
|
||||
*/
|
||||
uint64_t SwappyGL_getSwapIntervalNS();
|
||||
|
||||
bool SwappyGL_getUseAffinity();
|
||||
|
||||
/**
|
||||
* @brief Get the fence timeout value, in nanoseconds.
|
||||
*/
|
||||
uint64_t SwappyGL_getFenceTimeoutNS();
|
||||
|
||||
/**
|
||||
* @brief Set the number of bad frames to wait before applying a fix for buffer
|
||||
* stuffing. Set to zero in order to turn off this feature. Default value = 0.
|
||||
*/
|
||||
void SwappyGL_setBufferStuffingFixWait(int32_t n_frames);
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
/** @} */
|
@ -0,0 +1,167 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup swappyGL_extra Swappy for OpenGL extras
|
||||
* Extra utility functions to use Swappy with OpenGL.
|
||||
* @{
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
//#include <jni.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "swappy_common.h"
|
||||
|
||||
/**
|
||||
* The longest duration, in refresh periods, represented by the statistics.
|
||||
* @see SwappyStats
|
||||
*/
|
||||
#define MAX_FRAME_BUCKETS 6
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief If an app wishes to use the Android choreographer to provide ticks to
|
||||
* Swappy, it can call this function.
|
||||
*
|
||||
* @warning This function *must* be called before the first `Swappy_swap()`
|
||||
* call. Afterwards, call this function every choreographer tick.
|
||||
*/
|
||||
void SwappyGL_onChoreographer(int64_t frameTimeNanos);
|
||||
|
||||
/** @brief Pass callbacks to be called each frame to trace execution. */
|
||||
void SwappyGL_injectTracer(const SwappyTracer *t);
|
||||
|
||||
/**
|
||||
* @brief Toggle auto-swap interval detection on/off
|
||||
*
|
||||
* By default, Swappy will adjust the swap interval based on actual frame
|
||||
* rendering time. If an app wants to override the swap interval calculated by
|
||||
* Swappy, it can call `SwappyGL_setSwapIntervalNS`. This will temporarily
|
||||
* override Swappy's frame timings but, unless
|
||||
* `SwappyGL_setAutoSwapInterval(false)` is called, the timings will continue to
|
||||
* be be updated dynamically, so the swap interval may change.
|
||||
*/
|
||||
void SwappyGL_setAutoSwapInterval(bool enabled);
|
||||
|
||||
/**
|
||||
* @brief Sets the maximal duration for auto-swap interval in milliseconds.
|
||||
*
|
||||
* If Swappy is operating in auto-swap interval and the frame duration is longer
|
||||
* than `max_swap_ns`, Swappy will not do any pacing and just submit the frame
|
||||
* as soon as possible.
|
||||
*/
|
||||
void SwappyGL_setMaxAutoSwapIntervalNS(uint64_t max_swap_ns);
|
||||
|
||||
/**
|
||||
* @brief Toggle auto-pipeline mode on/off
|
||||
*
|
||||
* By default, if auto-swap interval is on, auto-pipelining is on and Swappy
|
||||
* will try to reduce latency by scheduling cpu and gpu work in the same
|
||||
* pipeline stage, if it fits.
|
||||
*/
|
||||
void SwappyGL_setAutoPipelineMode(bool enabled);
|
||||
|
||||
/**
|
||||
* @brief Toggle statistics collection on/off
|
||||
*
|
||||
* By default, stats collection is off and there is no overhead related to
|
||||
* stats. An app can turn on stats collection by calling
|
||||
* `SwappyGL_enableStats(true)`. Then, the app is expected to call
|
||||
* ::SwappyGL_recordFrameStart for each frame before starting to do any CPU
|
||||
* related work. Stats will be logged to logcat with a 'FrameStatistics' tag. An
|
||||
* app can get the stats by calling ::SwappyGL_getStats.
|
||||
*/
|
||||
void SwappyGL_enableStats(bool enabled);
|
||||
|
||||
/**
|
||||
* @brief Swappy statistics, collected if toggled on with
|
||||
* ::SwappyGL_enableStats.
|
||||
* @see SwappyGL_getStats
|
||||
*/
|
||||
struct SwappyStats {
|
||||
/** @brief Total frames swapped by swappy */
|
||||
uint64_t totalFrames;
|
||||
|
||||
/** @brief Histogram of the number of screen refreshes a frame waited in the
|
||||
* compositor queue after rendering was completed.
|
||||
*
|
||||
* For example:
|
||||
* if a frame waited 2 refresh periods in the compositor queue after
|
||||
* rendering was done, the frame will be counted in idleFrames[2]
|
||||
*/
|
||||
uint64_t idleFrames[MAX_FRAME_BUCKETS];
|
||||
|
||||
/** @brief Histogram of the number of screen refreshes passed between the
|
||||
* requested presentation time and the actual present time.
|
||||
*
|
||||
* For example:
|
||||
* if a frame was presented 2 refresh periods after the requested
|
||||
* timestamp swappy set, the frame will be counted in lateFrames[2]
|
||||
*/
|
||||
uint64_t lateFrames[MAX_FRAME_BUCKETS];
|
||||
|
||||
/** @brief Histogram of the number of screen refreshes passed between two
|
||||
* consecutive frames
|
||||
*
|
||||
* For example:
|
||||
* if frame N was presented 2 refresh periods after frame N-1
|
||||
* frame N will be counted in offsetFromPreviousFrame[2]
|
||||
*/
|
||||
uint64_t offsetFromPreviousFrame[MAX_FRAME_BUCKETS];
|
||||
|
||||
/** @brief Histogram of the number of screen refreshes passed between the
|
||||
* call to Swappy_recordFrameStart and the actual present time.
|
||||
*
|
||||
* For example:
|
||||
* if a frame was presented 2 refresh periods after the call to
|
||||
* `Swappy_recordFrameStart` the frame will be counted in latencyFrames[2]
|
||||
*/
|
||||
uint64_t latencyFrames[MAX_FRAME_BUCKETS];
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Should be called if stats have been enabled with SwappyGL_enableStats.
|
||||
*
|
||||
* When stats collection is enabled with SwappyGL_enableStats, the app is
|
||||
* expected to call this function for each frame before starting to do any CPU
|
||||
* related work.
|
||||
*
|
||||
* @see SwappyGL_enableStats.
|
||||
*/
|
||||
void SwappyGL_recordFrameStart(EGLDisplay display, EGLSurface surface);
|
||||
|
||||
/**
|
||||
* @brief Returns the stats collected, if statistics collection was toggled on.
|
||||
*
|
||||
* @param swappyStats Pointer to a SwappyStats that will be populated with
|
||||
* collected stats.
|
||||
* @see SwappyStats
|
||||
* @see SwappyGL_enableStats
|
||||
*/
|
||||
void SwappyGL_getStats(SwappyStats *swappyStats);
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
/** @} */
|
@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup swappy_common Swappy common tools
|
||||
* Tools to be used with Swappy for OpenGL or Swappy for Vulkan.
|
||||
* @{
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
//#include <android/native_window.h>
|
||||
#include <stdint.h>
|
||||
|
||||
struct ANativeWindow;
|
||||
|
||||
/** @brief Swap interval for 60fps, in nanoseconds. */
|
||||
#define SWAPPY_SWAP_60FPS (16666667L)
|
||||
|
||||
/** @brief Swap interval for 30fps, in nanoseconds. */
|
||||
#define SWAPPY_SWAP_30FPS (33333333L)
|
||||
|
||||
/** @brief Swap interval for 20fps, in nanoseconds. */
|
||||
#define SWAPPY_SWAP_20FPS (50000000L)
|
||||
|
||||
/** @cond INTERNAL */
|
||||
|
||||
#define SWAPPY_SYSTEM_PROP_KEY_DISABLE "swappy.disable"
|
||||
|
||||
// Internal macros to track Swappy version, do not use directly.
|
||||
#define SWAPPY_MAJOR_VERSION 1
|
||||
#define SWAPPY_MINOR_VERSION 9
|
||||
#define SWAPPY_BUGFIX_VERSION 0
|
||||
#define SWAPPY_PACKED_VERSION \
|
||||
ANDROID_GAMESDK_PACKED_VERSION(SWAPPY_MAJOR_VERSION, SWAPPY_MINOR_VERSION, \
|
||||
SWAPPY_BUGFIX_VERSION)
|
||||
|
||||
// Internal macros to generate a symbol to track Swappy version, do not use
|
||||
// directly.
|
||||
#define SWAPPY_VERSION_CONCAT_NX(PREFIX, MAJOR, MINOR, BUGFIX, GITCOMMIT) \
|
||||
PREFIX##_##MAJOR##_##MINOR##_##BUGFIX##_##GITCOMMIT
|
||||
#define SWAPPY_VERSION_CONCAT(PREFIX, MAJOR, MINOR, BUGFIX, GITCOMMIT) \
|
||||
SWAPPY_VERSION_CONCAT_NX(PREFIX, MAJOR, MINOR, BUGFIX, GITCOMMIT)
|
||||
#define SWAPPY_VERSION_SYMBOL \
|
||||
SWAPPY_VERSION_CONCAT(Swappy_version, SWAPPY_MAJOR_VERSION, \
|
||||
SWAPPY_MINOR_VERSION, SWAPPY_BUGFIX_VERSION, \
|
||||
AGDK_GIT_COMMIT)
|
||||
|
||||
/** @endcond */
|
||||
|
||||
/** @brief Id of a thread returned by an external thread manager. */
|
||||
typedef uint64_t SwappyThreadId;
|
||||
|
||||
/**
|
||||
* @brief A structure enabling you to set how Swappy starts and joins threads by
|
||||
* calling
|
||||
* ::Swappy_setThreadFunctions.
|
||||
*
|
||||
* Usage of this functionality is optional.
|
||||
*/
|
||||
typedef struct SwappyThreadFunctions {
|
||||
/** @brief Thread start callback.
|
||||
*
|
||||
* This function is called by Swappy to start thread_func on a new thread.
|
||||
* @param user_data A value to be passed the thread function.
|
||||
* If the thread was started, this function should set the thread_id and
|
||||
* return 0. If the thread was not started, this function should return a
|
||||
* non-zero value.
|
||||
*/
|
||||
int (*start)(SwappyThreadId* thread_id, void* (*thread_func)(void*),
|
||||
void* user_data);
|
||||
|
||||
/** @brief Thread join callback.
|
||||
*
|
||||
* This function is called by Swappy to join the thread with given id.
|
||||
*/
|
||||
void (*join)(SwappyThreadId thread_id);
|
||||
|
||||
/** @brief Thread joinable callback.
|
||||
*
|
||||
* This function is called by Swappy to discover whether the thread with the
|
||||
* given id is joinable.
|
||||
*/
|
||||
bool (*joinable)(SwappyThreadId thread_id);
|
||||
} SwappyThreadFunctions;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Return the version of the Swappy library at runtime.
|
||||
*/
|
||||
uint32_t Swappy_version();
|
||||
|
||||
/**
|
||||
* @brief Call this before any other functions in order to use a custom thread
|
||||
* manager.
|
||||
*
|
||||
* Usage of this function is entirely optional. Swappy uses std::thread by
|
||||
* default.
|
||||
*
|
||||
*/
|
||||
void Swappy_setThreadFunctions(const SwappyThreadFunctions* thread_functions);
|
||||
|
||||
/**
|
||||
* @brief Return the full version of the Swappy library at runtime, e.g.
|
||||
* "1.9.0_8a85ab7c46"
|
||||
*/
|
||||
const char* Swappy_versionString();
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Pointer to a function that can be attached to SwappyTracer::preWait
|
||||
* @param userData Pointer to arbitrary data, see SwappyTracer::userData.
|
||||
*/
|
||||
typedef void (*SwappyPreWaitCallback)(void*);
|
||||
|
||||
/**
|
||||
* Pointer to a function that can be attached to SwappyTracer::postWait.
|
||||
* @param userData Pointer to arbitrary data, see SwappyTracer::userData.
|
||||
* @param cpu_time_ns Time for CPU processing of this frame in nanoseconds.
|
||||
* @param gpu_time_ns Time for GPU processing of previous frame in nanoseconds.
|
||||
*/
|
||||
typedef void (*SwappyPostWaitCallback)(void*, int64_t cpu_time_ns,
|
||||
int64_t gpu_time_ns);
|
||||
|
||||
/**
|
||||
* Pointer to a function that can be attached to SwappyTracer::preSwapBuffers.
|
||||
* @param userData Pointer to arbitrary data, see SwappyTracer::userData.
|
||||
*/
|
||||
typedef void (*SwappyPreSwapBuffersCallback)(void*);
|
||||
|
||||
/**
|
||||
* Pointer to a function that can be attached to SwappyTracer::postSwapBuffers.
|
||||
* @param userData Pointer to arbitrary data, see SwappyTracer::userData.
|
||||
* @param desiredPresentationTimeMillis The target time, in milliseconds, at
|
||||
* which the frame would be presented on screen.
|
||||
*/
|
||||
typedef void (*SwappyPostSwapBuffersCallback)(
|
||||
void*, int64_t desiredPresentationTimeMillis);
|
||||
|
||||
/**
|
||||
* Pointer to a function that can be attached to SwappyTracer::startFrame.
|
||||
* @param userData Pointer to arbitrary data, see SwappyTracer::userData.
|
||||
* @param desiredPresentationTimeMillis The time, in milliseconds, at which the
|
||||
* frame is scheduled to be presented.
|
||||
*/
|
||||
typedef void (*SwappyStartFrameCallback)(void*, int currentFrame,
|
||||
int64_t desiredPresentationTimeMillis);
|
||||
|
||||
/**
|
||||
* Pointer to a function that can be attached to
|
||||
* SwappyTracer::swapIntervalChanged. Call ::SwappyGL_getSwapIntervalNS or
|
||||
* ::SwappyVk_getSwapIntervalNS to get the latest swapInterval.
|
||||
* @param userData Pointer to arbitrary data, see SwappyTracer::userData.
|
||||
*/
|
||||
typedef void (*SwappySwapIntervalChangedCallback)(void*);
|
||||
|
||||
/**
|
||||
* @brief Collection of callbacks to be called each frame to trace execution.
|
||||
*
|
||||
* Injection of these is optional.
|
||||
*/
|
||||
typedef struct SwappyTracer {
|
||||
/**
|
||||
* Callback called before waiting to queue the frame to the composer.
|
||||
*/
|
||||
SwappyPreWaitCallback preWait;
|
||||
|
||||
/**
|
||||
* Callback called after wait to queue the frame to the composer is done.
|
||||
*/
|
||||
SwappyPostWaitCallback postWait;
|
||||
|
||||
/**
|
||||
* Callback called before calling the function to queue the frame to the
|
||||
* composer.
|
||||
*/
|
||||
SwappyPreSwapBuffersCallback preSwapBuffers;
|
||||
|
||||
/**
|
||||
* Callback called after calling the function to queue the frame to the
|
||||
* composer.
|
||||
*/
|
||||
SwappyPostSwapBuffersCallback postSwapBuffers;
|
||||
|
||||
/**
|
||||
* Callback called at the start of a frame.
|
||||
*/
|
||||
SwappyStartFrameCallback startFrame;
|
||||
|
||||
/**
|
||||
* Pointer to some arbitrary data that will be passed as the first argument
|
||||
* of callbacks.
|
||||
*/
|
||||
void* userData;
|
||||
|
||||
/**
|
||||
* Callback called when the swap interval was changed.
|
||||
*/
|
||||
SwappySwapIntervalChangedCallback swapIntervalChanged;
|
||||
} SwappyTracer;
|
||||
|
||||
/** @} */
|
@ -0,0 +1,35 @@
|
||||
project('swappy', 'cpp',
|
||||
version: '1.0',
|
||||
license: 'Apache 2.0',
|
||||
)
|
||||
|
||||
include_dirs = include_directories('.', 'include', 'common', 'vulkan')
|
||||
sources = [
|
||||
'vulkan/SwappyVkBase.cpp',
|
||||
#'vulkan/swappyVk_c.cpp',
|
||||
'vulkan/SwappyVk.cpp',
|
||||
'vulkan/SwappyVkFallback.cpp',
|
||||
'vulkan/SwappyVkGoogleDisplayTiming.cpp',
|
||||
|
||||
'common/CpuInfo.cpp',
|
||||
# 'common/CPUTracer.cpp',
|
||||
'common/Settings.cpp',
|
||||
'common/SwappyCommon.cpp',
|
||||
'common/Thread.cpp',
|
||||
'common/SwappyDisplayManager.cpp',
|
||||
'common/ChoreographerFilter.cpp',
|
||||
'common/ChoreographerThread.cpp',
|
||||
]
|
||||
|
||||
cpp = meson.get_compiler('cpp')
|
||||
dependencies = [
|
||||
dependency('spdlog', required: true),
|
||||
]
|
||||
|
||||
swappy = library('swappy',
|
||||
sources,
|
||||
dependencies: dependencies,
|
||||
include_directories: include_dirs,
|
||||
)
|
||||
|
||||
swappy_dep = declare_dependency(include_directories: include_dirs, link_with: swappy)
|
@ -0,0 +1,294 @@
|
||||
/*
|
||||
* Copyright 2019 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 "SwappyVk.h"
|
||||
|
||||
namespace swappy {
|
||||
|
||||
class DefaultSwappyVkFunctionProvider {
|
||||
public:
|
||||
static bool Init() {
|
||||
if (!mLibVulkan) {
|
||||
// This is the first time we've been called
|
||||
mLibVulkan = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL);
|
||||
if (!mLibVulkan) {
|
||||
// If Vulkan doesn't exist, bail-out early:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static void* GetProcAddr(const char* name) {
|
||||
if (!mLibVulkan && !Init()) return nullptr;
|
||||
return dlsym(mLibVulkan, name);
|
||||
}
|
||||
static void Close() {
|
||||
if (mLibVulkan) {
|
||||
dlclose(mLibVulkan);
|
||||
mLibVulkan = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static void* mLibVulkan;
|
||||
};
|
||||
|
||||
void* DefaultSwappyVkFunctionProvider::mLibVulkan = nullptr;
|
||||
|
||||
bool SwappyVk::InitFunctions() {
|
||||
if (pFunctionProvider == nullptr) {
|
||||
static SwappyVkFunctionProvider c_provider;
|
||||
c_provider.init = &DefaultSwappyVkFunctionProvider::Init;
|
||||
c_provider.getProcAddr = &DefaultSwappyVkFunctionProvider::GetProcAddr;
|
||||
c_provider.close = &DefaultSwappyVkFunctionProvider::Close;
|
||||
pFunctionProvider = &c_provider;
|
||||
}
|
||||
if (pFunctionProvider->init()) {
|
||||
LoadVulkanFunctions(pFunctionProvider);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void SwappyVk::SetFunctionProvider(
|
||||
const SwappyVkFunctionProvider* functionProvider) {
|
||||
if (pFunctionProvider != nullptr) pFunctionProvider->close();
|
||||
pFunctionProvider = functionProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic/Singleton implementation of swappyVkDetermineDeviceExtensions.
|
||||
*/
|
||||
void SwappyVk::swappyVkDetermineDeviceExtensions(
|
||||
VkPhysicalDevice physicalDevice, uint32_t availableExtensionCount,
|
||||
VkExtensionProperties* pAvailableExtensions,
|
||||
uint32_t* pRequiredExtensionCount, char** pRequiredExtensions) {
|
||||
#if (not defined ANDROID_NDK_VERSION) || ANDROID_NDK_VERSION >= 15
|
||||
// TODO: Refactor this to be more concise:
|
||||
if (!pRequiredExtensions) {
|
||||
for (uint32_t i = 0; i < availableExtensionCount; i++) {
|
||||
if (!strcmp(VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME,
|
||||
pAvailableExtensions[i].extensionName)) {
|
||||
(*pRequiredExtensionCount)++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
doesPhysicalDeviceHaveGoogleDisplayTiming[physicalDevice] = false;
|
||||
for (uint32_t i = 0, j = 0; i < availableExtensionCount; i++) {
|
||||
if (!strcmp(VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME,
|
||||
pAvailableExtensions[i].extensionName)) {
|
||||
if (j < *pRequiredExtensionCount) {
|
||||
strcpy(pRequiredExtensions[j++],
|
||||
VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME);
|
||||
doesPhysicalDeviceHaveGoogleDisplayTiming[physicalDevice] =
|
||||
true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
doesPhysicalDeviceHaveGoogleDisplayTiming[physicalDevice] = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void SwappyVk::SetQueueFamilyIndex(VkDevice device, VkQueue queue,
|
||||
uint32_t queueFamilyIndex) {
|
||||
perQueueFamilyIndex[queue] = {device, queueFamilyIndex};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic/Singleton implementation of swappyVkGetRefreshCycleDuration.
|
||||
*/
|
||||
bool SwappyVk::GetRefreshCycleDuration(//JNIEnv* env, jobject jactivity,
|
||||
VkPhysicalDevice physicalDevice,
|
||||
VkDevice device,
|
||||
VkSwapchainKHR swapchain,
|
||||
uint64_t* pRefreshDuration) {
|
||||
auto& pImplementation = perSwapchainImplementation[swapchain];
|
||||
if (!pImplementation) {
|
||||
if (!InitFunctions()) {
|
||||
// If Vulkan doesn't exist, bail-out early
|
||||
return false;
|
||||
}
|
||||
|
||||
#if (not defined ANDROID_NDK_VERSION) || ANDROID_NDK_VERSION >= 15
|
||||
// First, based on whether VK_GOOGLE_display_timing is available
|
||||
// (determined and cached by swappyVkDetermineDeviceExtensions),
|
||||
// determine which derived class to use to implement the rest of the API
|
||||
if (doesPhysicalDeviceHaveGoogleDisplayTiming[physicalDevice]) {
|
||||
pImplementation = std::make_shared<SwappyVkGoogleDisplayTiming>(
|
||||
/*env, jactivity,*/ physicalDevice, device, pFunctionProvider);
|
||||
ALOGV(
|
||||
"SwappyVk initialized for VkDevice {} using "
|
||||
"VK_GOOGLE_display_timing on Android",
|
||||
(void*)device);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
pImplementation = std::make_shared<SwappyVkFallback>(
|
||||
/*env, jactivity,*/ physicalDevice, device, pFunctionProvider);
|
||||
ALOGV("SwappyVk initialized for VkDevice {} using Android fallback",
|
||||
(void*)device);
|
||||
}
|
||||
|
||||
if (!pImplementation) { // should never happen
|
||||
ALOGE(
|
||||
"SwappyVk could not find or create correct implementation for "
|
||||
"the current environment: "
|
||||
"{}, {}",
|
||||
(void*)physicalDevice, (void*)device);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Now, call that derived class to get the refresh duration to return
|
||||
return pImplementation->doGetRefreshCycleDuration(swapchain,
|
||||
pRefreshDuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic/Singleton implementation of swappyVkSetWindow.
|
||||
*/
|
||||
void SwappyVk::SetWindow(VkDevice device, VkSwapchainKHR swapchain,
|
||||
ANativeWindow* window) {
|
||||
auto& pImplementation = perSwapchainImplementation[swapchain];
|
||||
if (!pImplementation) {
|
||||
return;
|
||||
}
|
||||
pImplementation->doSetWindow(window);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic/Singleton implementation of swappyVkSetSwapInterval.
|
||||
*/
|
||||
void SwappyVk::SetSwapDuration(VkDevice device, VkSwapchainKHR swapchain,
|
||||
uint64_t swapNs) {
|
||||
auto& pImplementation = perSwapchainImplementation[swapchain];
|
||||
if (!pImplementation) {
|
||||
return;
|
||||
}
|
||||
pImplementation->doSetSwapInterval(swapchain, swapNs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic/Singleton implementation of swappyVkQueuePresent.
|
||||
*/
|
||||
VkResult SwappyVk::QueuePresent(VkQueue queue,
|
||||
const VkPresentInfoKHR* pPresentInfo) {
|
||||
if (perQueueFamilyIndex.find(queue) == perQueueFamilyIndex.end()) {
|
||||
ALOGE("Unknown queue {}. Did you call SwappyVkSetQueueFamilyIndex ?",
|
||||
(void*)queue);
|
||||
return VK_INCOMPLETE;
|
||||
}
|
||||
|
||||
// This command doesn't have a VkDevice. It should have at least one
|
||||
// VkSwapchainKHR's. For this command, all VkSwapchainKHR's will have the
|
||||
// same VkDevice and VkQueue.
|
||||
if ((pPresentInfo->swapchainCount == 0) || (!pPresentInfo->pSwapchains)) {
|
||||
// This shouldn't happen, but if it does, something is really wrong.
|
||||
return VK_ERROR_DEVICE_LOST;
|
||||
}
|
||||
auto& pImplementation =
|
||||
perSwapchainImplementation[*pPresentInfo->pSwapchains];
|
||||
if (pImplementation) {
|
||||
return pImplementation->doQueuePresent(
|
||||
queue, perQueueFamilyIndex[queue].queueFamilyIndex, pPresentInfo);
|
||||
} else {
|
||||
// This should only happen if the API was used wrong (e.g. they never
|
||||
// called swappyVkGetRefreshCycleDuration).
|
||||
// NOTE: Technically, a Vulkan library shouldn't protect a user from
|
||||
// themselves, but we'll be friendlier
|
||||
return VK_ERROR_DEVICE_LOST;
|
||||
}
|
||||
}
|
||||
|
||||
void SwappyVk::DestroySwapchain(VkDevice /*device*/, VkSwapchainKHR swapchain) {
|
||||
auto swapchain_it = perSwapchainImplementation.find(swapchain);
|
||||
if (swapchain_it == perSwapchainImplementation.end()) return;
|
||||
perSwapchainImplementation.erase(swapchain);
|
||||
}
|
||||
|
||||
void SwappyVk::DestroyDevice(VkDevice device) {
|
||||
{
|
||||
// Erase swapchains
|
||||
auto it = perSwapchainImplementation.begin();
|
||||
while (it != perSwapchainImplementation.end()) {
|
||||
if (it->second->getDevice() == device) {
|
||||
it = perSwapchainImplementation.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
// Erase the device
|
||||
auto it = perQueueFamilyIndex.begin();
|
||||
while (it != perQueueFamilyIndex.end()) {
|
||||
if (it->second.device == device) {
|
||||
it = perQueueFamilyIndex.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SwappyVk::SetAutoSwapInterval(bool enabled) {
|
||||
for (auto i : perSwapchainImplementation) {
|
||||
i.second->setAutoSwapInterval(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
void SwappyVk::SetAutoPipelineMode(bool enabled) {
|
||||
for (auto i : perSwapchainImplementation) {
|
||||
i.second->setAutoPipelineMode(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
void SwappyVk::SetMaxAutoSwapDuration(std::chrono::nanoseconds maxDuration) {
|
||||
for (auto i : perSwapchainImplementation) {
|
||||
i.second->setMaxAutoSwapDuration(maxDuration);
|
||||
}
|
||||
}
|
||||
|
||||
void SwappyVk::SetFenceTimeout(std::chrono::nanoseconds t) {
|
||||
for (auto i : perSwapchainImplementation) {
|
||||
i.second->setFenceTimeout(t);
|
||||
}
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds SwappyVk::GetFenceTimeout() const {
|
||||
auto it = perSwapchainImplementation.begin();
|
||||
if (it != perSwapchainImplementation.end()) {
|
||||
return it->second->getFenceTimeout();
|
||||
}
|
||||
return std::chrono::nanoseconds(0);
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds SwappyVk::GetSwapInterval(VkSwapchainKHR swapchain) {
|
||||
auto it = perSwapchainImplementation.find(swapchain);
|
||||
if (it != perSwapchainImplementation.end())
|
||||
return it->second->getSwapInterval();
|
||||
return std::chrono::nanoseconds(0);
|
||||
}
|
||||
|
||||
void SwappyVk::addTracer(const SwappyTracer* t) {
|
||||
for (auto i : perSwapchainImplementation) {
|
||||
i.second->addTracer(t);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace swappy
|
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "SwappyVkBase.h"
|
||||
#include "SwappyVkFallback.h"
|
||||
#include "SwappyVkGoogleDisplayTiming.h"
|
||||
|
||||
namespace swappy {
|
||||
|
||||
// The API functions call methods of the singleton SwappyVk class.
|
||||
// Those methods call virtual methods of the abstract SwappyVkBase class,
|
||||
// which is actually implemented by one of the derived/concrete classes:
|
||||
//
|
||||
// - SwappyVkGoogleDisplayTiming
|
||||
// - SwappyVkFallback
|
||||
|
||||
/***************************************************************************************************
|
||||
*
|
||||
* Singleton class that provides the high-level implementation of the Swappy
|
||||
*entrypoints.
|
||||
*
|
||||
***************************************************************************************************/
|
||||
/**
|
||||
* Singleton class that provides the high-level implementation of the Swappy
|
||||
* entrypoints.
|
||||
*
|
||||
* This class determines which low-level implementation to use for each physical
|
||||
* device, and then calls that class's do-method for the entrypoint.
|
||||
*/
|
||||
class SwappyVk {
|
||||
public:
|
||||
static SwappyVk& getInstance() {
|
||||
static SwappyVk instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
~SwappyVk() {
|
||||
if (pFunctionProvider) {
|
||||
pFunctionProvider->close();
|
||||
}
|
||||
}
|
||||
|
||||
void swappyVkDetermineDeviceExtensions(
|
||||
VkPhysicalDevice physicalDevice, uint32_t availableExtensionCount,
|
||||
VkExtensionProperties* pAvailableExtensions,
|
||||
uint32_t* pRequiredExtensionCount, char** pRequiredExtensions);
|
||||
void SetQueueFamilyIndex(VkDevice device, VkQueue queue,
|
||||
uint32_t queueFamilyIndex);
|
||||
bool GetRefreshCycleDuration(//JNIEnv* env, jobject jactivity,
|
||||
VkPhysicalDevice physicalDevice,
|
||||
VkDevice device, VkSwapchainKHR swapchain,
|
||||
uint64_t* pRefreshDuration);
|
||||
void SetWindow(VkDevice device, VkSwapchainKHR swapchain,
|
||||
ANativeWindow* window);
|
||||
void SetSwapDuration(VkDevice device, VkSwapchainKHR swapchain,
|
||||
uint64_t swapNs);
|
||||
VkResult QueuePresent(VkQueue queue, const VkPresentInfoKHR* pPresentInfo);
|
||||
void DestroySwapchain(VkDevice device, VkSwapchainKHR swapchain);
|
||||
void DestroyDevice(VkDevice device);
|
||||
|
||||
void SetAutoSwapInterval(bool enabled);
|
||||
void SetAutoPipelineMode(bool enabled);
|
||||
void SetMaxAutoSwapDuration(std::chrono::nanoseconds maxDuration);
|
||||
void SetFenceTimeout(std::chrono::nanoseconds duration);
|
||||
std::chrono::nanoseconds GetFenceTimeout() const;
|
||||
std::chrono::nanoseconds GetSwapInterval(VkSwapchainKHR swapchain);
|
||||
|
||||
void addTracer(const SwappyTracer* t);
|
||||
|
||||
void SetFunctionProvider(const SwappyVkFunctionProvider* pFunctionProvider);
|
||||
bool InitFunctions();
|
||||
|
||||
private:
|
||||
std::map<VkPhysicalDevice, bool> doesPhysicalDeviceHaveGoogleDisplayTiming;
|
||||
std::map<VkSwapchainKHR, std::shared_ptr<SwappyVkBase>>
|
||||
perSwapchainImplementation;
|
||||
|
||||
struct QueueFamilyIndex {
|
||||
VkDevice device;
|
||||
uint32_t queueFamilyIndex;
|
||||
};
|
||||
std::map<VkQueue, QueueFamilyIndex> perQueueFamilyIndex;
|
||||
|
||||
const SwappyVkFunctionProvider* pFunctionProvider = nullptr;
|
||||
|
||||
private:
|
||||
SwappyVk() {} // Need to implement this constructor
|
||||
|
||||
// Forbid copies.
|
||||
SwappyVk(SwappyVk const&) = delete;
|
||||
void operator=(SwappyVk const&) = delete;
|
||||
};
|
||||
|
||||
} // namespace swappy
|
@ -0,0 +1,429 @@
|
||||
/*
|
||||
* Copyright 2019 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 "SwappyVkBase.h"
|
||||
|
||||
//#include "system_utils.h"
|
||||
|
||||
#define LOG_TAG "SwappyVkBase"
|
||||
|
||||
namespace swappy {
|
||||
|
||||
PFN_vkCreateCommandPool vkCreateCommandPool = nullptr;
|
||||
PFN_vkDestroyCommandPool vkDestroyCommandPool = nullptr;
|
||||
PFN_vkCreateFence vkCreateFence = nullptr;
|
||||
PFN_vkDestroyFence vkDestroyFence = nullptr;
|
||||
PFN_vkWaitForFences vkWaitForFences = nullptr;
|
||||
PFN_vkGetFenceStatus vkGetFenceStatus = nullptr;
|
||||
PFN_vkResetFences vkResetFences = nullptr;
|
||||
PFN_vkCreateSemaphore vkCreateSemaphore = nullptr;
|
||||
PFN_vkDestroySemaphore vkDestroySemaphore = nullptr;
|
||||
PFN_vkCreateEvent vkCreateEvent = nullptr;
|
||||
PFN_vkDestroyEvent vkDestroyEvent = nullptr;
|
||||
PFN_vkCmdSetEvent vkCmdSetEvent = nullptr;
|
||||
PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers = nullptr;
|
||||
PFN_vkFreeCommandBuffers vkFreeCommandBuffers = nullptr;
|
||||
PFN_vkBeginCommandBuffer vkBeginCommandBuffer = nullptr;
|
||||
PFN_vkEndCommandBuffer vkEndCommandBuffer = nullptr;
|
||||
PFN_vkQueueSubmit vkQueueSubmit = nullptr;
|
||||
|
||||
void LoadVulkanFunctions(const SwappyVkFunctionProvider* pFunctionProvider) {
|
||||
if (vkCreateCommandPool == nullptr) {
|
||||
vkCreateCommandPool = reinterpret_cast<PFN_vkCreateCommandPool>(
|
||||
pFunctionProvider->getProcAddr("vkCreateCommandPool"));
|
||||
vkDestroyCommandPool = reinterpret_cast<PFN_vkDestroyCommandPool>(
|
||||
pFunctionProvider->getProcAddr("vkDestroyCommandPool"));
|
||||
vkCreateFence = reinterpret_cast<PFN_vkCreateFence>(
|
||||
pFunctionProvider->getProcAddr("vkCreateFence"));
|
||||
vkDestroyFence = reinterpret_cast<PFN_vkDestroyFence>(
|
||||
pFunctionProvider->getProcAddr("vkDestroyFence"));
|
||||
vkWaitForFences = reinterpret_cast<PFN_vkWaitForFences>(
|
||||
pFunctionProvider->getProcAddr("vkWaitForFences"));
|
||||
vkGetFenceStatus = reinterpret_cast<PFN_vkGetFenceStatus>(
|
||||
pFunctionProvider->getProcAddr("vkGetFenceStatus"));
|
||||
vkResetFences = reinterpret_cast<PFN_vkResetFences>(
|
||||
pFunctionProvider->getProcAddr("vkResetFences"));
|
||||
vkCreateSemaphore = reinterpret_cast<PFN_vkCreateSemaphore>(
|
||||
pFunctionProvider->getProcAddr("vkCreateSemaphore"));
|
||||
vkDestroySemaphore = reinterpret_cast<PFN_vkDestroySemaphore>(
|
||||
pFunctionProvider->getProcAddr("vkDestroySemaphore"));
|
||||
vkCreateEvent = reinterpret_cast<PFN_vkCreateEvent>(
|
||||
pFunctionProvider->getProcAddr("vkCreateEvent"));
|
||||
vkDestroyEvent = reinterpret_cast<PFN_vkDestroyEvent>(
|
||||
pFunctionProvider->getProcAddr("vkDestroyEvent"));
|
||||
vkCmdSetEvent = reinterpret_cast<PFN_vkCmdSetEvent>(
|
||||
pFunctionProvider->getProcAddr("vkCmdSetEvent"));
|
||||
vkAllocateCommandBuffers =
|
||||
reinterpret_cast<PFN_vkAllocateCommandBuffers>(
|
||||
pFunctionProvider->getProcAddr("vkAllocateCommandBuffers"));
|
||||
vkFreeCommandBuffers = reinterpret_cast<PFN_vkFreeCommandBuffers>(
|
||||
pFunctionProvider->getProcAddr("vkFreeCommandBuffers"));
|
||||
vkBeginCommandBuffer = reinterpret_cast<PFN_vkBeginCommandBuffer>(
|
||||
pFunctionProvider->getProcAddr("vkBeginCommandBuffer"));
|
||||
vkEndCommandBuffer = reinterpret_cast<PFN_vkEndCommandBuffer>(
|
||||
pFunctionProvider->getProcAddr("vkEndCommandBuffer"));
|
||||
vkQueueSubmit = reinterpret_cast<PFN_vkQueueSubmit>(
|
||||
pFunctionProvider->getProcAddr("vkQueueSubmit"));
|
||||
}
|
||||
}
|
||||
|
||||
SwappyVkBase::SwappyVkBase(/*JNIEnv* env, jobject jactivity,*/
|
||||
VkPhysicalDevice physicalDevice, VkDevice device,
|
||||
const SwappyVkFunctionProvider* pFunctionProvider)
|
||||
: mCommonBase(/*env, jactivity*/),
|
||||
mPhysicalDevice(physicalDevice),
|
||||
mDevice(device),
|
||||
mpFunctionProvider(pFunctionProvider),
|
||||
mInitialized(false),
|
||||
mEnabled(false) {
|
||||
if (!mCommonBase.isValid()) {
|
||||
ALOGE("SwappyCommon could not initialize correctly.");
|
||||
return;
|
||||
}
|
||||
|
||||
mpfnGetDeviceProcAddr = reinterpret_cast<PFN_vkGetDeviceProcAddr>(
|
||||
mpFunctionProvider->getProcAddr("vkGetDeviceProcAddr"));
|
||||
mpfnQueuePresentKHR = reinterpret_cast<PFN_vkQueuePresentKHR>(
|
||||
mpfnGetDeviceProcAddr(mDevice, "vkQueuePresentKHR"));
|
||||
|
||||
initGoogExtension();
|
||||
|
||||
mEnabled = true;
|
||||
}
|
||||
|
||||
void SwappyVkBase::initGoogExtension() {
|
||||
#if (not defined ANDROID_NDK_VERSION) || ANDROID_NDK_VERSION >= 15
|
||||
mpfnGetRefreshCycleDurationGOOGLE =
|
||||
reinterpret_cast<PFN_vkGetRefreshCycleDurationGOOGLE>(
|
||||
mpfnGetDeviceProcAddr(mDevice, "vkGetRefreshCycleDurationGOOGLE"));
|
||||
mpfnGetPastPresentationTimingGOOGLE =
|
||||
reinterpret_cast<PFN_vkGetPastPresentationTimingGOOGLE>(
|
||||
mpfnGetDeviceProcAddr(mDevice,
|
||||
"vkGetPastPresentationTimingGOOGLE"));
|
||||
#endif
|
||||
}
|
||||
|
||||
SwappyVkBase::~SwappyVkBase() { destroyVkSyncObjects(); }
|
||||
|
||||
void SwappyVkBase::doSetWindow(ANativeWindow* window) {
|
||||
mCommonBase.setANativeWindow(window);
|
||||
}
|
||||
|
||||
void SwappyVkBase::doSetSwapInterval(VkSwapchainKHR swapchain,
|
||||
uint64_t swapNs) {
|
||||
Settings::getInstance()->setSwapDuration(swapNs);
|
||||
}
|
||||
|
||||
VkResult SwappyVkBase::initializeVkSyncObjects(VkQueue queue,
|
||||
uint32_t queueFamilyIndex) {
|
||||
if (mCommandPool.find(queue) != mCommandPool.end()) {
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
VkSync sync;
|
||||
|
||||
const VkCommandPoolCreateInfo cmd_pool_info = {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
||||
.pNext = NULL,
|
||||
.flags = 0,
|
||||
.queueFamilyIndex = queueFamilyIndex,
|
||||
};
|
||||
|
||||
VkResult res = vkCreateCommandPool(mDevice, &cmd_pool_info, NULL,
|
||||
&mCommandPool[queue]);
|
||||
if (res) {
|
||||
ALOGE("vkCreateCommandPool failed %d", res);
|
||||
return res;
|
||||
}
|
||||
const VkCommandBufferAllocateInfo present_cmd_info = {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||
.pNext = NULL,
|
||||
.commandPool = mCommandPool[queue],
|
||||
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
.commandBufferCount = 1,
|
||||
};
|
||||
|
||||
for (int i = 0; i < MAX_PENDING_FENCES; i++) {
|
||||
VkFenceCreateInfo fence_ci = {
|
||||
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
||||
.pNext = NULL,
|
||||
.flags = VK_FENCE_CREATE_SIGNALED_BIT};
|
||||
res = vkCreateFence(mDevice, &fence_ci, NULL, &sync.fence);
|
||||
if (res) {
|
||||
ALOGE("failed to create fence: %d", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
VkSemaphoreCreateInfo semaphore_ci = {
|
||||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
|
||||
.pNext = NULL,
|
||||
.flags = 0};
|
||||
res = vkCreateSemaphore(mDevice, &semaphore_ci, NULL, &sync.semaphore);
|
||||
if (res) {
|
||||
ALOGE("failed to create semaphore: %d", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
res =
|
||||
vkAllocateCommandBuffers(mDevice, &present_cmd_info, &sync.command);
|
||||
if (res) {
|
||||
ALOGE("vkAllocateCommandBuffers failed %d", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
const VkCommandBufferBeginInfo cmd_buf_info = {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||
.pNext = NULL,
|
||||
.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT,
|
||||
.pInheritanceInfo = NULL,
|
||||
};
|
||||
res = vkBeginCommandBuffer(sync.command, &cmd_buf_info);
|
||||
if (res) {
|
||||
ALOGE("vkAllocateCommandBuffers failed %d", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
VkEventCreateInfo event_info = {
|
||||
.sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO,
|
||||
.pNext = NULL,
|
||||
.flags = 0,
|
||||
};
|
||||
res = vkCreateEvent(mDevice, &event_info, NULL, &sync.event);
|
||||
if (res) {
|
||||
ALOGE("vkCreateEvent failed %d", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
vkCmdSetEvent(sync.command, sync.event,
|
||||
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
|
||||
|
||||
res = vkEndCommandBuffer(sync.command);
|
||||
if (res) {
|
||||
ALOGE("vkCreateEvent failed %d", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
mFreeSyncPool[queue].push_back(sync);
|
||||
}
|
||||
|
||||
// Create a thread that will wait for the fences
|
||||
auto emplaceResult =
|
||||
mThreads.emplace(queue, std::make_unique<ThreadContext>(queue));
|
||||
auto& threadContext = emplaceResult.first->second;
|
||||
|
||||
// Start the thread
|
||||
std::lock_guard<std::mutex> lock(threadContext->lock);
|
||||
threadContext->thread =
|
||||
Thread([&]() { waitForFenceThreadMain(*threadContext); });
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
void SwappyVkBase::destroyVkSyncObjects() {
|
||||
// Stop all waiters threads
|
||||
for (auto it = mThreads.begin(); it != mThreads.end(); it++) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(it->second->lock);
|
||||
it->second->running = false;
|
||||
it->second->condition.notify_one();
|
||||
}
|
||||
it->second->thread.join();
|
||||
}
|
||||
|
||||
// Wait for all unsignaled fences to get signlaed
|
||||
for (auto it = mWaitingSyncs.begin(); it != mWaitingSyncs.end(); it++) {
|
||||
auto queue = it->first;
|
||||
auto syncList = it->second;
|
||||
while (syncList.size() > 0) {
|
||||
VkSync sync = syncList.front();
|
||||
syncList.pop_front();
|
||||
vkWaitForFences(mDevice, 1, &sync.fence, VK_TRUE, UINT64_MAX);
|
||||
mSignaledSyncs[queue].push_back(sync);
|
||||
}
|
||||
}
|
||||
|
||||
// Move all signaled fences to the free pool
|
||||
for (auto it = mSignaledSyncs.begin(); it != mSignaledSyncs.end(); it++) {
|
||||
auto queue = it->first;
|
||||
reclaimSignaledFences(queue);
|
||||
}
|
||||
|
||||
// Free all sync objects
|
||||
for (auto it = mFreeSyncPool.begin(); it != mFreeSyncPool.end(); it++) {
|
||||
auto syncList = it->second;
|
||||
while (syncList.size() > 0) {
|
||||
VkSync sync = syncList.front();
|
||||
syncList.pop_front();
|
||||
vkFreeCommandBuffers(mDevice, mCommandPool[it->first], 1,
|
||||
&sync.command);
|
||||
vkDestroyEvent(mDevice, sync.event, NULL);
|
||||
vkDestroySemaphore(mDevice, sync.semaphore, NULL);
|
||||
vkResetFences(mDevice, 1, &sync.fence);
|
||||
vkDestroyFence(mDevice, sync.fence, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// Free destroy the command pools
|
||||
for (auto it = mCommandPool.begin(); it != mCommandPool.end(); it++) {
|
||||
auto commandPool = it->second;
|
||||
vkDestroyCommandPool(mDevice, commandPool, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void SwappyVkBase::reclaimSignaledFences(VkQueue queue) {
|
||||
std::lock_guard<std::mutex> lock(mThreads[queue]->lock);
|
||||
while (!mSignaledSyncs[queue].empty()) {
|
||||
VkSync sync = mSignaledSyncs[queue].front();
|
||||
mSignaledSyncs[queue].pop_front();
|
||||
mFreeSyncPool[queue].push_back(sync);
|
||||
}
|
||||
}
|
||||
|
||||
bool SwappyVkBase::lastFrameIsCompleted(VkQueue queue) {
|
||||
auto pipelineMode = mCommonBase.getCurrentPipelineMode();
|
||||
std::lock_guard<std::mutex> lock(mThreads[queue]->lock);
|
||||
if (pipelineMode == SwappyCommon::PipelineMode::On) {
|
||||
// We are in pipeline mode so we need to check the fence of frame N-1
|
||||
return mWaitingSyncs[queue].size() < 2;
|
||||
}
|
||||
|
||||
// We are not in pipeline mode so we need to check the fence the current
|
||||
// frame. i.e. there are not unsignaled frames
|
||||
return mWaitingSyncs[queue].empty();
|
||||
}
|
||||
|
||||
VkResult SwappyVkBase::injectFence(VkQueue queue,
|
||||
const VkPresentInfoKHR* pPresentInfo,
|
||||
VkSemaphore* pSemaphore) {
|
||||
reclaimSignaledFences(queue);
|
||||
|
||||
// If we cross the swap interval threshold, we don't pace at all.
|
||||
// In this case we might not have a free fence, so just don't use the fence.
|
||||
if (mFreeSyncPool[queue].empty() ||
|
||||
vkGetFenceStatus(mDevice, mFreeSyncPool[queue].front().fence) !=
|
||||
VK_SUCCESS) {
|
||||
*pSemaphore = VK_NULL_HANDLE;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
VkSync sync = mFreeSyncPool[queue].front();
|
||||
mFreeSyncPool[queue].pop_front();
|
||||
|
||||
vkResetFences(mDevice, 1, &sync.fence);
|
||||
|
||||
VkPipelineStageFlags pipe_stage_flags;
|
||||
VkSubmitInfo submit_info;
|
||||
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
submit_info.pNext = NULL;
|
||||
submit_info.pWaitDstStageMask = &pipe_stage_flags;
|
||||
pipe_stage_flags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
submit_info.waitSemaphoreCount = pPresentInfo->waitSemaphoreCount;
|
||||
submit_info.pWaitSemaphores = pPresentInfo->pWaitSemaphores;
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &sync.command;
|
||||
submit_info.signalSemaphoreCount = 1;
|
||||
submit_info.pSignalSemaphores = &sync.semaphore;
|
||||
VkResult res = vkQueueSubmit(queue, 1, &submit_info, sync.fence);
|
||||
*pSemaphore = sync.semaphore;
|
||||
|
||||
std::lock_guard<std::mutex> lock(mThreads[queue]->lock);
|
||||
mWaitingSyncs[queue].push_back(sync);
|
||||
mThreads[queue]->hasPendingWork = true;
|
||||
mThreads[queue]->condition.notify_all();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void SwappyVkBase::setAutoSwapInterval(bool enabled) {
|
||||
mCommonBase.setAutoSwapInterval(enabled);
|
||||
}
|
||||
|
||||
void SwappyVkBase::setMaxAutoSwapDuration(std::chrono::nanoseconds swapMaxNS) {
|
||||
mCommonBase.setMaxAutoSwapDuration(swapMaxNS);
|
||||
}
|
||||
|
||||
void SwappyVkBase::setAutoPipelineMode(bool enabled) {
|
||||
mCommonBase.setAutoPipelineMode(enabled);
|
||||
}
|
||||
|
||||
void SwappyVkBase::waitForFenceThreadMain(ThreadContext& thread) {
|
||||
while (true) {
|
||||
bool waitingSyncsEmpty;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(thread.lock);
|
||||
// Wait for new fence object
|
||||
thread.condition.wait(thread.lock, [&]() REQUIRES(thread.lock) {
|
||||
return thread.hasPendingWork || !thread.running;
|
||||
});
|
||||
|
||||
thread.hasPendingWork = false;
|
||||
|
||||
if (!thread.running) {
|
||||
break;
|
||||
}
|
||||
|
||||
waitingSyncsEmpty = mWaitingSyncs[thread.queue].empty();
|
||||
}
|
||||
|
||||
while (!waitingSyncsEmpty) {
|
||||
VkSync sync;
|
||||
{ // Get the sync object with a lock
|
||||
std::lock_guard<std::mutex> lock(thread.lock);
|
||||
sync = mWaitingSyncs[thread.queue].front();
|
||||
}
|
||||
|
||||
// gamesdk::ScopedTrace tracer("Swappy: GPU frame time");
|
||||
const auto startTime = std::chrono::steady_clock::now();
|
||||
VkResult result =
|
||||
vkWaitForFences(mDevice, 1, &sync.fence, VK_TRUE,
|
||||
mCommonBase.getFenceTimeout().count());
|
||||
if (result) {
|
||||
ALOGE("Failed to wait for fence %d", result);
|
||||
}
|
||||
mLastFenceTime = std::chrono::steady_clock::now() - startTime;
|
||||
|
||||
// Move the sync object to the signaled list
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(thread.lock);
|
||||
mWaitingSyncs[thread.queue].pop_front();
|
||||
|
||||
mSignaledSyncs[thread.queue].push_back(sync);
|
||||
waitingSyncsEmpty = mWaitingSyncs[thread.queue].empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds SwappyVkBase::getLastFenceTime(VkQueue queue) {
|
||||
return mLastFenceTime;
|
||||
}
|
||||
|
||||
void SwappyVkBase::setFenceTimeout(std::chrono::nanoseconds duration) {
|
||||
mCommonBase.setFenceTimeout(duration);
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds SwappyVkBase::getFenceTimeout() const {
|
||||
return mCommonBase.getFenceTimeout();
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds SwappyVkBase::getSwapInterval() {
|
||||
return mCommonBase.getSwapDuration();
|
||||
}
|
||||
|
||||
void SwappyVkBase::addTracer(const SwappyTracer* tracer) {
|
||||
mCommonBase.addTracerCallbacks(*tracer);
|
||||
}
|
||||
|
||||
} // namespace swappy
|
@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
*
|
||||
* Per-Device abstract base class.
|
||||
*
|
||||
***************************************************************************************************/
|
||||
|
||||
/**
|
||||
* Abstract base class that calls the Vulkan API.
|
||||
*
|
||||
* It is expected that one concrete class will be instantiated per VkDevice, and
|
||||
* that all VkSwapchainKHR's for a given VkDevice will share the same instance.
|
||||
*
|
||||
* Base class members are used by the derived classes to unify the behavior
|
||||
* across implementations:
|
||||
* @mThread - Thread used for getting Choreographer events.
|
||||
* @mTreadRunning - Used to signal the tread to exit
|
||||
* @mNextPresentID - unique ID for frame presentation.
|
||||
* @mNextDesiredPresentTime - Holds the time in nanoseconds for the next frame
|
||||
* to be presented.
|
||||
* @mNextPresentIDToCheck - Used to determine whether presentation time needs
|
||||
* to be adjusted.
|
||||
* @mFrameID - Keeps track of how many Choreographer callbacks received.
|
||||
* @mLastframeTimeNanos - Holds the last frame time reported by Choreographer.
|
||||
* @mSumRefreshTime - Used together with @mSamples to calculate refresh rate
|
||||
* based on Choreographer.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <dlfcn.h>
|
||||
#include <inttypes.h>
|
||||
#include <pthread.h>
|
||||
#include <swappy/swappyVk.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
//#include "ChoreographerShim.h"
|
||||
#include "Settings.h"
|
||||
#include "SwappyCommon.h"
|
||||
//#include "Trace.h"
|
||||
|
||||
struct ANativeWindow;
|
||||
|
||||
namespace swappy {
|
||||
|
||||
#define ALOGE(...) \
|
||||
SPDLOG_ERROR(__VA_ARGS__)
|
||||
#define ALOGW(...) \
|
||||
SPDLOG_WARN(__VA_ARGS__)
|
||||
#define ALOGI(...) \
|
||||
SPDLOG_INFO(__VA_ARGS__)
|
||||
#define ALOGD(...) \
|
||||
SPDLOG_DEBUG(__VA_ARGS__)
|
||||
#define ALOGV(...) \
|
||||
SPDLOG_TRACE(__VA_ARGS__)
|
||||
|
||||
constexpr uint32_t kThousand = 1000;
|
||||
constexpr uint32_t kMillion = 1000000;
|
||||
constexpr uint32_t kBillion = 1000000000;
|
||||
constexpr uint32_t k16_6msec = 16666666;
|
||||
|
||||
constexpr uint32_t kTooCloseToVsyncBoundary = 3000000;
|
||||
constexpr uint32_t kTooFarAwayFromVsyncBoundary = 7000000;
|
||||
constexpr uint32_t kNudgeWithinVsyncBoundaries = 2000000;
|
||||
|
||||
// AChoreographer is supported from API 24. To allow compilation for minSDK < 24
|
||||
// and still use AChoreographer for SDK >= 24 we need runtime support to call
|
||||
// AChoreographer APIs.
|
||||
|
||||
// using PFN_AChoreographer_getInstance = AChoreographer* (*)();
|
||||
|
||||
// using PFN_AChoreographer_postFrameCallback =
|
||||
// void (*)(AChoreographer* choreographer,
|
||||
// AChoreographer_frameCallback callback, void* data);
|
||||
//
|
||||
// using PFN_AChoreographer_postFrameCallbackDelayed = void (*)(
|
||||
// AChoreographer* choreographer, AChoreographer_frameCallback callback,
|
||||
// void* data, long delayMillis);
|
||||
|
||||
extern PFN_vkCreateCommandPool vkCreateCommandPool;
|
||||
extern PFN_vkDestroyCommandPool vkDestroyCommandPool;
|
||||
extern PFN_vkCreateFence vkCreateFence;
|
||||
extern PFN_vkDestroyFence vkDestroyFence;
|
||||
extern PFN_vkWaitForFences vkWaitForFences;
|
||||
extern PFN_vkResetFences vkResetFences;
|
||||
extern PFN_vkCreateSemaphore vkCreateSemaphore;
|
||||
extern PFN_vkDestroySemaphore vkDestroySemaphore;
|
||||
extern PFN_vkCreateEvent vkCreateEvent;
|
||||
extern PFN_vkDestroyEvent vkDestroyEvent;
|
||||
extern PFN_vkCmdSetEvent vkCmdSetEvent;
|
||||
extern PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers;
|
||||
extern PFN_vkFreeCommandBuffers vkFreeCommandBuffers;
|
||||
extern PFN_vkBeginCommandBuffer vkBeginCommandBuffer;
|
||||
extern PFN_vkEndCommandBuffer vkEndCommandBuffer;
|
||||
extern PFN_vkQueueSubmit vkQueueSubmit;
|
||||
|
||||
void LoadVulkanFunctions(const SwappyVkFunctionProvider* pFunctionProvider);
|
||||
|
||||
class SwappyVkBase {
|
||||
public:
|
||||
SwappyVkBase(//JNIEnv* env, jobject jactivity,
|
||||
VkPhysicalDevice physicalDevice, VkDevice device,
|
||||
const SwappyVkFunctionProvider* pFunctionProvider);
|
||||
|
||||
virtual ~SwappyVkBase();
|
||||
|
||||
virtual bool doGetRefreshCycleDuration(VkSwapchainKHR swapchain,
|
||||
uint64_t* pRefreshDuration) = 0;
|
||||
|
||||
virtual VkResult doQueuePresent(VkQueue queue, uint32_t queueFamilyIndex,
|
||||
const VkPresentInfoKHR* pPresentInfo) = 0;
|
||||
|
||||
void doSetWindow(ANativeWindow* window);
|
||||
void doSetSwapInterval(VkSwapchainKHR swapchain, uint64_t swapNs);
|
||||
|
||||
VkResult injectFence(VkQueue queue, const VkPresentInfoKHR* pPresentInfo,
|
||||
VkSemaphore* pSemaphore);
|
||||
|
||||
bool isEnabled() { return mEnabled; }
|
||||
|
||||
void setAutoSwapInterval(bool enabled);
|
||||
void setAutoPipelineMode(bool enabled);
|
||||
|
||||
void setMaxAutoSwapDuration(std::chrono::nanoseconds swapMaxNS);
|
||||
|
||||
void setFenceTimeout(std::chrono::nanoseconds duration);
|
||||
std::chrono::nanoseconds getFenceTimeout() const;
|
||||
std::chrono::nanoseconds getSwapInterval();
|
||||
|
||||
void addTracer(const SwappyTracer* tracer);
|
||||
|
||||
VkDevice getDevice() const { return mDevice; }
|
||||
|
||||
protected:
|
||||
struct VkSync {
|
||||
VkFence fence;
|
||||
VkSemaphore semaphore;
|
||||
VkCommandBuffer command;
|
||||
VkEvent event;
|
||||
};
|
||||
|
||||
struct ThreadContext {
|
||||
ThreadContext(VkQueue queue) : queue(queue) {}
|
||||
|
||||
Thread thread;
|
||||
bool running GUARDED_BY(lock) = true;
|
||||
bool hasPendingWork GUARDED_BY(lock);
|
||||
std::mutex lock;
|
||||
std::condition_variable_any condition;
|
||||
VkQueue queue GUARDED_BY(lock);
|
||||
};
|
||||
|
||||
SwappyCommon mCommonBase;
|
||||
VkPhysicalDevice mPhysicalDevice;
|
||||
VkDevice mDevice;
|
||||
const SwappyVkFunctionProvider* mpFunctionProvider;
|
||||
bool mInitialized;
|
||||
bool mEnabled;
|
||||
|
||||
uint32_t mNextPresentID = 0;
|
||||
uint32_t mNextPresentIDToCheck = 2;
|
||||
|
||||
PFN_vkGetDeviceProcAddr mpfnGetDeviceProcAddr = nullptr;
|
||||
PFN_vkQueuePresentKHR mpfnQueuePresentKHR = nullptr;
|
||||
#if (not defined ANDROID_NDK_VERSION) || ANDROID_NDK_VERSION >= 15
|
||||
PFN_vkGetRefreshCycleDurationGOOGLE mpfnGetRefreshCycleDurationGOOGLE =
|
||||
nullptr;
|
||||
PFN_vkGetPastPresentationTimingGOOGLE mpfnGetPastPresentationTimingGOOGLE =
|
||||
nullptr;
|
||||
#endif
|
||||
// Holds VKSync objects ready to be used
|
||||
std::map<VkQueue, std::list<VkSync>> mFreeSyncPool;
|
||||
|
||||
// Holds VKSync objects queued and but signaled yet
|
||||
std::map<VkQueue, std::list<VkSync>> mWaitingSyncs;
|
||||
|
||||
// Holds VKSync objects that were signaled
|
||||
std::map<VkQueue, std::list<VkSync>> mSignaledSyncs;
|
||||
|
||||
std::map<VkQueue, VkCommandPool> mCommandPool;
|
||||
std::map<VkQueue, std::unique_ptr<ThreadContext>> mThreads;
|
||||
|
||||
static constexpr int MAX_PENDING_FENCES = 2;
|
||||
|
||||
std::atomic<std::chrono::nanoseconds> mLastFenceTime = {};
|
||||
|
||||
void initGoogExtension();
|
||||
VkResult initializeVkSyncObjects(VkQueue queue, uint32_t queueFamilyIndex);
|
||||
void destroyVkSyncObjects();
|
||||
void reclaimSignaledFences(VkQueue queue);
|
||||
bool lastFrameIsCompleted(VkQueue queue);
|
||||
std::chrono::nanoseconds getLastFenceTime(VkQueue queue);
|
||||
void waitForFenceThreadMain(ThreadContext& thread);
|
||||
};
|
||||
|
||||
} // namespace swappy
|
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2019 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 "SwappyVkFallback.h"
|
||||
|
||||
#define LOG_TAG "SwappyVkFallback"
|
||||
|
||||
namespace swappy {
|
||||
|
||||
SwappyVkFallback::SwappyVkFallback(//JNIEnv* env, jobject jactivity,
|
||||
VkPhysicalDevice physicalDevice,
|
||||
VkDevice device,
|
||||
const SwappyVkFunctionProvider* provider)
|
||||
: SwappyVkBase(/*env, jactivity,*/ physicalDevice, device, provider) {}
|
||||
|
||||
bool SwappyVkFallback::doGetRefreshCycleDuration(VkSwapchainKHR swapchain,
|
||||
uint64_t* pRefreshDuration) {
|
||||
if (!isEnabled()) {
|
||||
ALOGE("Swappy is disabled.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Since we don't have presentation timing, we cannot achieve pipelining.
|
||||
mCommonBase.setAutoPipelineMode(false);
|
||||
|
||||
*pRefreshDuration = mCommonBase.getRefreshPeriod().count();
|
||||
|
||||
double refreshRate = 1000000000.0 / *pRefreshDuration;
|
||||
ALOGI("Returning refresh duration of {} nsec (approx {} Hz)",
|
||||
*pRefreshDuration, refreshRate);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
VkResult SwappyVkFallback::doQueuePresent(
|
||||
VkQueue queue, uint32_t queueFamilyIndex,
|
||||
const VkPresentInfoKHR* pPresentInfo) {
|
||||
if (!isEnabled()) {
|
||||
ALOGE("Swappy is disabled.");
|
||||
return VK_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
|
||||
VkResult result = initializeVkSyncObjects(queue, queueFamilyIndex);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const SwappyCommon::SwapHandlers handlers = {
|
||||
.lastFrameIsComplete =
|
||||
std::bind(&SwappyVkFallback::lastFrameIsCompleted, this, queue),
|
||||
.getPrevFrameGpuTime =
|
||||
std::bind(&SwappyVkFallback::getLastFenceTime, this, queue),
|
||||
};
|
||||
|
||||
// Inject the fence first and wait for it in onPreSwap() as we don't want to
|
||||
// submit a frame before rendering is completed.
|
||||
VkSemaphore semaphore;
|
||||
result = injectFence(queue, pPresentInfo, &semaphore);
|
||||
if (result) {
|
||||
ALOGE("Failed to vkQueueSubmit %d", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t waitSemaphoreCount;
|
||||
const VkSemaphore* pWaitSemaphores;
|
||||
if (semaphore != VK_NULL_HANDLE) {
|
||||
waitSemaphoreCount = 1;
|
||||
pWaitSemaphores = &semaphore;
|
||||
} else {
|
||||
waitSemaphoreCount = pPresentInfo->waitSemaphoreCount;
|
||||
pWaitSemaphores = pPresentInfo->pWaitSemaphores;
|
||||
}
|
||||
|
||||
mCommonBase.onPreSwap(handlers);
|
||||
|
||||
VkPresentInfoKHR replacementPresentInfo = {
|
||||
pPresentInfo->sType, nullptr,
|
||||
waitSemaphoreCount, pWaitSemaphores,
|
||||
pPresentInfo->swapchainCount, pPresentInfo->pSwapchains,
|
||||
pPresentInfo->pImageIndices, pPresentInfo->pResults};
|
||||
|
||||
result = mpfnQueuePresentKHR(queue, &replacementPresentInfo);
|
||||
|
||||
mCommonBase.onPostSwap(handlers);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace swappy
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
*
|
||||
* Per-Device concrete/derived class for the "Android fallback" path (uses
|
||||
* Choreographer to try to get presents to occur at the desired time).
|
||||
*
|
||||
***************************************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "SwappyVkBase.h"
|
||||
|
||||
namespace swappy {
|
||||
|
||||
class SwappyVkFallback : public SwappyVkBase {
|
||||
public:
|
||||
SwappyVkFallback(//JNIEnv* env, jobject jactivity,
|
||||
VkPhysicalDevice physicalDevice, VkDevice device,
|
||||
const SwappyVkFunctionProvider* provider);
|
||||
|
||||
virtual bool doGetRefreshCycleDuration(VkSwapchainKHR swapchain,
|
||||
uint64_t* pRefreshDuration) override;
|
||||
|
||||
virtual VkResult doQueuePresent(
|
||||
VkQueue queue, uint32_t queueFamilyIndex,
|
||||
const VkPresentInfoKHR* pPresentInfo) override;
|
||||
};
|
||||
|
||||
} // namespace swappy
|
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
#if (not defined ANDROID_NDK_VERSION) || ANDROID_NDK_VERSION >= 15
|
||||
|
||||
#include "SwappyVkGoogleDisplayTiming.h"
|
||||
|
||||
#define LOG_TAG "SwappyVkGoogleDisplayTiming"
|
||||
|
||||
using std::chrono::nanoseconds;
|
||||
|
||||
namespace swappy {
|
||||
|
||||
SwappyVkGoogleDisplayTiming::SwappyVkGoogleDisplayTiming(
|
||||
/*JNIEnv* env, jobject jactivity,*/ VkPhysicalDevice physicalDevice,
|
||||
VkDevice device, const SwappyVkFunctionProvider* provider)
|
||||
: SwappyVkBase(/*env, jactivity,*/ physicalDevice, device, provider) {}
|
||||
|
||||
bool SwappyVkGoogleDisplayTiming::doGetRefreshCycleDuration(
|
||||
VkSwapchainKHR swapchain, uint64_t* pRefreshDuration) {
|
||||
if (!isEnabled()) {
|
||||
ALOGE("Swappy is disabled.");
|
||||
return false;
|
||||
}
|
||||
|
||||
VkRefreshCycleDurationGOOGLE refreshCycleDuration;
|
||||
VkResult res = mpfnGetRefreshCycleDurationGOOGLE(mDevice, swapchain,
|
||||
&refreshCycleDuration);
|
||||
if (res != VK_SUCCESS) {
|
||||
ALOGE("mpfnGetRefreshCycleDurationGOOGLE failed %d", res);
|
||||
return false;
|
||||
}
|
||||
|
||||
*pRefreshDuration = mCommonBase.getRefreshPeriod().count();
|
||||
|
||||
double refreshRate = 1000000000.0 / *pRefreshDuration;
|
||||
ALOGI("Returning refresh duration of %" PRIu64 " nsec (approx %f Hz)",
|
||||
*pRefreshDuration, refreshRate);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
VkResult SwappyVkGoogleDisplayTiming::doQueuePresent(
|
||||
VkQueue queue, uint32_t queueFamilyIndex,
|
||||
const VkPresentInfoKHR* pPresentInfo) {
|
||||
if (!isEnabled()) {
|
||||
ALOGE("Swappy is disabled.");
|
||||
return VK_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
|
||||
VkResult res = initializeVkSyncObjects(queue, queueFamilyIndex);
|
||||
if (res) {
|
||||
return res;
|
||||
}
|
||||
|
||||
const SwappyCommon::SwapHandlers handlers = {
|
||||
.lastFrameIsComplete = std::bind(
|
||||
&SwappyVkGoogleDisplayTiming::lastFrameIsCompleted, this, queue),
|
||||
.getPrevFrameGpuTime = std::bind(
|
||||
&SwappyVkGoogleDisplayTiming::getLastFenceTime, this, queue),
|
||||
};
|
||||
|
||||
VkSemaphore semaphore;
|
||||
res = injectFence(queue, pPresentInfo, &semaphore);
|
||||
if (res) {
|
||||
ALOGE("Failed to vkQueueSubmit %d", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
uint32_t waitSemaphoreCount;
|
||||
const VkSemaphore* pWaitSemaphores;
|
||||
if (semaphore != VK_NULL_HANDLE) {
|
||||
waitSemaphoreCount = 1;
|
||||
pWaitSemaphores = &semaphore;
|
||||
} else {
|
||||
waitSemaphoreCount = pPresentInfo->waitSemaphoreCount;
|
||||
pWaitSemaphores = pPresentInfo->pWaitSemaphores;
|
||||
}
|
||||
|
||||
mCommonBase.onPreSwap(handlers);
|
||||
|
||||
VkPresentTimeGOOGLE pPresentTimes[pPresentInfo->swapchainCount];
|
||||
VkPresentInfoKHR replacementPresentInfo;
|
||||
VkPresentTimesInfoGOOGLE presentTimesInfo;
|
||||
if (mCommonBase.needToSetPresentationTime()) {
|
||||
// Setup the new structures to pass:
|
||||
for (uint32_t i = 0; i < pPresentInfo->swapchainCount; i++) {
|
||||
pPresentTimes[i].presentID = mNextPresentID;
|
||||
pPresentTimes[i].desiredPresentTime =
|
||||
mCommonBase.getPresentationTime().time_since_epoch().count();
|
||||
}
|
||||
|
||||
presentTimesInfo = {VK_STRUCTURE_TYPE_PRESENT_TIMES_INFO_GOOGLE,
|
||||
pPresentInfo->pNext, pPresentInfo->swapchainCount,
|
||||
pPresentTimes};
|
||||
|
||||
replacementPresentInfo = {
|
||||
pPresentInfo->sType, &presentTimesInfo,
|
||||
waitSemaphoreCount, pWaitSemaphores,
|
||||
pPresentInfo->swapchainCount, pPresentInfo->pSwapchains,
|
||||
pPresentInfo->pImageIndices, pPresentInfo->pResults};
|
||||
|
||||
} else {
|
||||
replacementPresentInfo = {
|
||||
pPresentInfo->sType, nullptr,
|
||||
waitSemaphoreCount, pWaitSemaphores,
|
||||
pPresentInfo->swapchainCount, pPresentInfo->pSwapchains,
|
||||
pPresentInfo->pImageIndices, pPresentInfo->pResults};
|
||||
}
|
||||
mNextPresentID++;
|
||||
|
||||
res = mpfnQueuePresentKHR(queue, &replacementPresentInfo);
|
||||
mCommonBase.onPostSwap(handlers);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace swappy
|
||||
|
||||
#endif // #if (not defined ANDROID_NDK_VERSION) || ANDROID_NDK_VERSION>=15
|
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "SwappyVkBase.h"
|
||||
|
||||
namespace swappy {
|
||||
|
||||
/***************************************************************************************************
|
||||
*
|
||||
* Per-Device concrete/derived class for using VK_GOOGLE_display_timing.
|
||||
*
|
||||
* This class uses the VK_GOOGLE_display_timing in order to present frames at a
|
||||
*muliple (the "swap interval") of a fixed refresh-cycle duration (i.e. the time
|
||||
*between successive vsync's).
|
||||
*
|
||||
* In order to reduce complexity, some simplifying assumptions are made:
|
||||
*
|
||||
* - We assume a fixed refresh-rate (FRR) display that's between 60 Hz and 120
|
||||
*Hz.
|
||||
*
|
||||
* - While Vulkan allows applications to create and use multiple
|
||||
*VkSwapchainKHR's per VkDevice, and to re-create VkSwapchainKHR's, we assume
|
||||
*that the application uses a single VkSwapchainKHR, and never re-creates it.
|
||||
*
|
||||
* - The values reported back by the VK_GOOGLE_display_timing extension (which
|
||||
*comes from lower-level Android interfaces) are not precise, and that values
|
||||
*can drift over time. For example, the refresh-cycle duration for a 60 Hz
|
||||
*display should be 16,666,666 nsec; but the value reported back by the
|
||||
*extension won't be precisely this. Also, the differences betweeen the times
|
||||
*of two successive frames won't be an exact multiple of 16,666,666 nsec. This
|
||||
*can make it difficult to precisely predict when a future vsync will be (it can
|
||||
*appear to drift overtime). Therefore, we try to give a desiredPresentTime for
|
||||
*each image that is between 3 and 7 msec before vsync. We look at the
|
||||
*actualPresentTime for previously-presented images, and nudge the future
|
||||
*desiredPresentTime back within those 3-7 msec boundaries.
|
||||
*
|
||||
* - There can be a few frames of latency between when an image is presented and
|
||||
*when the actualPresentTime is available for that image. Therefore, we
|
||||
*initially just pick times based upon CLOCK_MONOTONIC (which is the time domain
|
||||
*for VK_GOOGLE_display_timing). After we get past-present times, we nudge the
|
||||
*desiredPresentTime, we wait for a few presents before looking again to see
|
||||
*whether we need to nudge again.
|
||||
*
|
||||
* - If, for some reason, an application can't keep up with its chosen swap
|
||||
*interval (e.g. it's designed for 30FPS on a premium device and is now running
|
||||
*on a slow device; or it's running on a 120Hz display), this algorithm may not
|
||||
*be able to make up for this (i.e. smooth rendering at a targeted frame rate
|
||||
*may not be possible with an application that can't render fast enough).
|
||||
*
|
||||
***************************************************************************************************/
|
||||
class SwappyVkGoogleDisplayTiming : public SwappyVkBase {
|
||||
public:
|
||||
SwappyVkGoogleDisplayTiming(//JNIEnv* env, jobject jactivity,
|
||||
VkPhysicalDevice physicalDevice,
|
||||
VkDevice device,
|
||||
const SwappyVkFunctionProvider* provider);
|
||||
|
||||
virtual bool doGetRefreshCycleDuration(VkSwapchainKHR swapchain,
|
||||
uint64_t* pRefreshDuration) override;
|
||||
|
||||
virtual VkResult doQueuePresent(
|
||||
VkQueue queue, uint32_t queueFamilyIndex,
|
||||
const VkPresentInfoKHR* pPresentInfo) override;
|
||||
};
|
||||
|
||||
} // namespace swappy
|
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
// API entry points
|
||||
|
||||
#include "SwappyVk.h"
|
||||
#include "swappy/swappyVk.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
// Internal function to track Swappy version bundled in a binary.
|
||||
void SWAPPY_VERSION_SYMBOL();
|
||||
|
||||
void SwappyVk_determineDeviceExtensions(
|
||||
VkPhysicalDevice physicalDevice, uint32_t availableExtensionCount,
|
||||
VkExtensionProperties* pAvailableExtensions,
|
||||
uint32_t* pRequiredExtensionCount, char** pRequiredExtensions) {
|
||||
TRACE_CALL();
|
||||
swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance();
|
||||
swappy.swappyVkDetermineDeviceExtensions(
|
||||
physicalDevice, availableExtensionCount, pAvailableExtensions,
|
||||
pRequiredExtensionCount, pRequiredExtensions);
|
||||
}
|
||||
|
||||
void SwappyVk_setQueueFamilyIndex(VkDevice device, VkQueue queue,
|
||||
uint32_t queueFamilyIndex) {
|
||||
TRACE_CALL();
|
||||
swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance();
|
||||
swappy.SetQueueFamilyIndex(device, queue, queueFamilyIndex);
|
||||
}
|
||||
|
||||
bool SwappyVk_initAndGetRefreshCycleDuration(JNIEnv* env, jobject jactivity,
|
||||
VkPhysicalDevice physicalDevice,
|
||||
VkDevice device,
|
||||
VkSwapchainKHR swapchain,
|
||||
uint64_t* pRefreshDuration) {
|
||||
SWAPPY_VERSION_SYMBOL();
|
||||
TRACE_CALL();
|
||||
swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance();
|
||||
return swappy.GetRefreshCycleDuration(env, jactivity, physicalDevice,
|
||||
device, swapchain, pRefreshDuration);
|
||||
}
|
||||
|
||||
void SwappyVk_setWindow(VkDevice device, VkSwapchainKHR swapchain,
|
||||
ANativeWindow* window) {
|
||||
TRACE_CALL();
|
||||
swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance();
|
||||
swappy.SetWindow(device, swapchain, window);
|
||||
}
|
||||
|
||||
void SwappyVk_setSwapIntervalNS(VkDevice device, VkSwapchainKHR swapchain,
|
||||
uint64_t swap_ns) {
|
||||
TRACE_CALL();
|
||||
swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance();
|
||||
swappy.SetSwapDuration(device, swapchain, swap_ns);
|
||||
}
|
||||
|
||||
VkResult SwappyVk_queuePresent(VkQueue queue,
|
||||
const VkPresentInfoKHR* pPresentInfo) {
|
||||
TRACE_CALL();
|
||||
swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance();
|
||||
return swappy.QueuePresent(queue, pPresentInfo);
|
||||
}
|
||||
|
||||
void SwappyVk_destroySwapchain(VkDevice device, VkSwapchainKHR swapchain) {
|
||||
TRACE_CALL();
|
||||
swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance();
|
||||
swappy.DestroySwapchain(device, swapchain);
|
||||
}
|
||||
|
||||
void SwappyVk_destroyDevice(VkDevice device) {
|
||||
TRACE_CALL();
|
||||
swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance();
|
||||
swappy.DestroyDevice(device);
|
||||
}
|
||||
|
||||
void SwappyVk_setAutoSwapInterval(bool enabled) {
|
||||
TRACE_CALL();
|
||||
swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance();
|
||||
swappy.SetAutoSwapInterval(enabled);
|
||||
}
|
||||
|
||||
void SwappyVk_setAutoPipelineMode(bool enabled) {
|
||||
TRACE_CALL();
|
||||
swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance();
|
||||
swappy.SetAutoPipelineMode(enabled);
|
||||
}
|
||||
|
||||
void SwappyVk_setFenceTimeoutNS(uint64_t fence_timeout_ns) {
|
||||
TRACE_CALL();
|
||||
swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance();
|
||||
swappy.SetFenceTimeout(std::chrono::nanoseconds(fence_timeout_ns));
|
||||
}
|
||||
|
||||
void SwappyVk_setMaxAutoSwapIntervalNS(uint64_t max_swap_ns) {
|
||||
TRACE_CALL();
|
||||
swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance();
|
||||
swappy.SetMaxAutoSwapDuration(std::chrono::nanoseconds(max_swap_ns));
|
||||
}
|
||||
|
||||
uint64_t SwappyVk_getFenceTimeoutNS() {
|
||||
TRACE_CALL();
|
||||
swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance();
|
||||
return swappy.GetFenceTimeout().count();
|
||||
}
|
||||
|
||||
void SwappyVk_injectTracer(const SwappyTracer* t) {
|
||||
TRACE_CALL();
|
||||
swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance();
|
||||
swappy.addTracer(t);
|
||||
}
|
||||
|
||||
void SwappyVk_setFunctionProvider(
|
||||
const SwappyVkFunctionProvider* pSwappyVkFunctionProvider) {
|
||||
TRACE_CALL();
|
||||
swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance();
|
||||
swappy.SetFunctionProvider(pSwappyVkFunctionProvider);
|
||||
}
|
||||
|
||||
uint64_t SwappyVk_getSwapIntervalNS(VkSwapchainKHR swapchain) {
|
||||
TRACE_CALL();
|
||||
swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance();
|
||||
return swappy.GetSwapInterval(swapchain).count();
|
||||
}
|
||||
|
||||
} // extern "C"
|
Loading…
Reference in New Issue