Add franken-Swappy. Look away.

swappy
jackun 3 years ago
parent 019de913ca
commit 8551fac97c
No known key found for this signature in database
GPG Key ID: 119DB3F1D05A9ED3

@ -261,6 +261,11 @@ else
spdlog_dep = dependency('spdlog', required: true)
endif
swappy_sp = subproject('swappy', default_options: [
'default_library=static',
])
swappy_dep = swappy_sp.get_variable('swappy_dep')
if ['windows', 'mingw'].contains(host_machine.system())
subdir('modules/minhook')
inc_common += ( include_directories('modules/minhook/include'))

@ -190,6 +190,7 @@ vklayer_mesa_overlay = shared_library(
vulkan_wsi_deps,
dearimgui_dep,
spdlog_dep,
swappy_dep,
dep_libdrm,
dep_libdrm_amdgpu,
dep_xcb,

@ -39,6 +39,9 @@
#include <vulkan/vulkan.h>
#include <vulkan/vk_layer.h>
#include <SwappyVk.h>
#include <swappy/swappyVk.h>
#include "imgui.h"
#include "overlay.h"
@ -1550,7 +1553,13 @@ static VkResult overlay_CreateSwapchainKHR(
#ifdef __gnu_linux__
get_device_name(prop.vendorID, prop.deviceID, swapchain_data->sw_stats);
#endif
swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance();
uint64_t refreshDuration = std::chrono::duration_cast<std::chrono::nanoseconds> (1s / 100).count();
swappy.GetRefreshCycleDuration(device_data->physical_device,
device_data->device, *pSwapchain, &refreshDuration);
swappy.SetSwapDuration(device_data->device, *pSwapchain, 10000000L);//SWAPPY_SWAP_30FPS);
}
if(driverProps.driverID == VK_DRIVER_ID_NVIDIA_PROPRIETARY){
swapchain_data->sw_stats.driverName = "NVIDIA";
}
@ -1588,6 +1597,9 @@ static void overlay_DestroySwapchainKHR(
struct swapchain_data *swapchain_data =
FIND(struct swapchain_data, swapchain);
swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance();
swappy.DestroySwapchain(swapchain_data->device->device, swapchain);
shutdown_swapchain_data(swapchain_data);
swapchain_data->device->vtable.DestroySwapchainKHR(device, swapchain, pAllocator);
destroy_swapchain_data(swapchain_data);
@ -1632,18 +1644,21 @@ static VkResult overlay_QueuePresentKHR(
present_info.waitSemaphoreCount = 1;
}
VkResult chain_result = queue_data->device->vtable.QueuePresentKHR(queue, &present_info);
swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance();
VkResult chain_result = swappy.QueuePresent(queue, &present_info);
// VkResult chain_result = queue_data->device->vtable.QueuePresentKHR(queue, &present_info);
if (pPresentInfo->pResults)
pPresentInfo->pResults[i] = chain_result;
if (chain_result != VK_SUCCESS && result == VK_SUCCESS)
result = chain_result;
}
#if 0
using namespace std::chrono_literals;
if (fps_limit_stats.targetFrameTime > 0s){
FpsLimiter(fps_limit_stats);
}
#endif
return result;
}
@ -1747,6 +1762,30 @@ static VkResult overlay_QueueSubmit(
return device_data->vtable.QueueSubmit(queue, submitCount, pSubmits, fence);
}
// fugly
struct VkProvider
{
//VkProvider(instance_data * inst):instance(inst) {}
instance_data * instance;
device_data * device;
static VkProvider& inst()
{
static VkProvider inst;
return inst;
}
static bool init() { return true; }
static void * getProcAddr(const char* name)
{
SPDLOG_DEBUG("{}, {}", (void*)VkProvider::inst().instance->instance, name);
if (!strcmp(name, "vkGetDeviceProcAddr"))
return (void*)VkProvider::inst().device->vtable.GetDeviceProcAddr;
return (void*)VkProvider::inst().device->vtable.GetDeviceProcAddr(VkProvider::inst().device->device, name);
// return (void*)VkProvider::inst().instance->vtable.GetInstanceProcAddr(VkProvider::inst().instance->instance, name);
};
static void close(){};
};
static VkResult overlay_CreateDevice(
VkPhysicalDevice physicalDevice,
const VkDeviceCreateInfo* pCreateInfo,
@ -1782,6 +1821,27 @@ static VkResult overlay_CreateDevice(
std::vector<VkExtensionProperties> available_extensions(extension_count);
instance_data->vtable.EnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extension_count, available_extensions.data());
uint32_t swappy_required_extension_count = 0;
char** swappy_required_extension_names;
swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance();
swappy.swappyVkDetermineDeviceExtensions(
physicalDevice, extension_count, available_extensions.data(),
&swappy_required_extension_count, nullptr);
swappy_required_extension_names = (char**)malloc(swappy_required_extension_count * sizeof(char*));
for (uint32_t i = 0; i < swappy_required_extension_count; i++) {
swappy_required_extension_names[i] = (char*)malloc(VK_MAX_EXTENSION_NAME_SIZE + 1);
}
swappy.swappyVkDetermineDeviceExtensions(
physicalDevice, extension_count, available_extensions.data(),
&swappy_required_extension_count, swappy_required_extension_names);
for (uint32_t i = 0; i < swappy_required_extension_count; i++) {
SPDLOG_DEBUG("SwappyVk requires the following extension: {}", swappy_required_extension_names[i]);
free(swappy_required_extension_names[i]);
}
free(swappy_required_extension_names);
bool can_get_driver_info = instance_data->api_version < VK_API_VERSION_1_1 ? false : true;
@ -1831,9 +1891,13 @@ static VkResult overlay_CreateDevice(
instance_data->vtable.GetPhysicalDeviceProperties2(device_data->physical_device, &deviceProps);
}
VkProvider::inst().device = device_data;
if (!is_blacklisted()) {
device_map_queues(device_data, pCreateInfo);
init_gpu_stats(device_data->properties.vendorID, device_data->properties.deviceID, device_data->instance->params);
swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance();
swappy.SetQueueFamilyIndex(device_data->device, device_data->graphic_queue->queue, device_data->graphic_queue->family_index);
}
return result;
@ -1850,6 +1914,8 @@ static void overlay_DestroyDevice(
destroy_device_data(device_data);
}
//std::unique_ptr<VkProvider> vk_provider;
static VkResult overlay_CreateInstance(
const VkInstanceCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
@ -1944,6 +2010,16 @@ static VkResult overlay_CreateInstance(
instance_data->engine = engine;
instance_data->engineName = engineName;
instance_data->engineVersion = engineVersion;
//vk_provider = std::make_unique<VkProvider>(instance_data);
VkProvider::inst().instance = instance_data;
static SwappyVkFunctionProvider c;
c.init = &VkProvider::init;
c.close = &VkProvider::close;
c.getProcAddr = &VkProvider::getProcAddr;
swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance();
swappy.SetFunctionProvider(&c);
}
instance_data->api_version = pCreateInfo->pApplicationInfo ? pCreateInfo->pApplicationInfo->apiVersion : VK_API_VERSION_1_0;

@ -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,155 @@
/*
* 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 "Swappy"
#include "CpuInfo.h"
#include <bitset>
#include <cstdlib>
#include <cstring>
#include <limits>
//#include "Log.h"
namespace {
bool startsWith(std::string &mainStr, const char *toMatch) {
// std::string::find returns 0 if toMatch is found at beginning
return mainStr.find(toMatch) == 0;
}
std::vector<std::string> split(const std::string &s, char c) {
std::vector<std::string> v;
std::string::size_type i = 0;
std::string::size_type j = s.find(c);
while (j != std::string::npos) {
v.push_back(s.substr(i, j - i));
i = ++j;
j = s.find(c, j);
if (j == std::string::npos) {
v.push_back(s.substr(i, s.length()));
}
}
return v;
}
std::string ReadFile(const std::string &path) {
char buf[10240];
FILE *fp = fopen(path.c_str(), "r");
if (fp == nullptr) return std::string();
fgets(buf, 10240, fp);
fclose(fp);
return std::string(buf);
}
} // anonymous namespace
namespace swappy {
std::string to_string(int n) {
constexpr int kBufSize = 12; // strlen("2147483648")+1
static char buf[kBufSize];
snprintf(buf, kBufSize, "%d", n);
return buf;
}
CpuInfo::CpuInfo() {
const auto BUFFER_LENGTH = 10240;
char buf[BUFFER_LENGTH];
FILE *fp = fopen("/proc/cpuinfo", "r");
if (!fp) {
return;
}
long mMaxFrequency = 0;
long mMinFrequency = std::numeric_limits<long>::max();
while (fgets(buf, BUFFER_LENGTH, fp) != NULL) {
buf[strlen(buf) - 1] = '\0'; // eat the newline fgets() stores
std::string line = buf;
if (startsWith(line, "processor")) {
Cpu core;
core.id = mCpus.size();
auto core_path =
std::string("/sys/devices/system/cpu/cpu") + to_string(core.id);
auto package_id =
ReadFile(core_path + "/topology/physical_package_id");
auto frequency = ReadFile(core_path + "/cpufreq/cpuinfo_max_freq");
core.package_id = atol(package_id.c_str());
core.frequency = atol(frequency.c_str());
mMinFrequency = std::min(mMinFrequency, core.frequency);
mMaxFrequency = std::max(mMaxFrequency, core.frequency);
mCpus.push_back(core);
} else if (startsWith(line, "Hardware")) {
mHardware = split(line, ':')[1];
}
}
fclose(fp);
CPU_ZERO(&mLittleCoresMask);
CPU_ZERO(&mBigCoresMask);
for (auto cpu : mCpus) {
if (cpu.frequency == mMinFrequency) {
++mNumberOfLittleCores;
cpu.type = Cpu::Type::Little;
CPU_SET(cpu.id, &mLittleCoresMask);
} else {
++mNumberOfBigCores;
cpu.type = Cpu::Type::Big;
CPU_SET(cpu.id, &mBigCoresMask);
}
}
}
unsigned int CpuInfo::getNumberOfCpus() const { return mCpus.size(); }
const std::vector<CpuInfo::Cpu> &CpuInfo::getCpus() const { return mCpus; }
const std::string CpuInfo::getHardware() const { return mHardware; }
unsigned int CpuInfo::getNumberOfLittleCores() const {
return mNumberOfLittleCores;
}
unsigned int CpuInfo::getNumberOfBigCores() const { return mNumberOfBigCores; }
cpu_set_t CpuInfo::getLittleCoresMask() const { return mLittleCoresMask; }
cpu_set_t CpuInfo::getBigCoresMask() const { return mBigCoresMask; }
unsigned int to_mask(cpu_set_t cpu_set) {
std::bitset<32> mask;
for (int i = 0; i < CPU_SETSIZE; ++i) {
if (CPU_ISSET(i, &cpu_set)) mask[i] = 1;
}
return (int)mask.to_ulong();
}
} // 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,323 @@
/*
* 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 swappyVk Swappy for Vulkan
* Vulkan part of Swappy.
* @{
*/
#pragma once
//#include "jni.h"
#include "swappy_common.h"
#define VK_NO_PROTOTYPES 1
#include <vulkan/vulkan.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Determine any Vulkan device extensions that must be enabled for a new
* VkDevice.
*
* Swappy-for-Vulkan (SwappyVk) benefits from certain Vulkan device extensions
* (e.g. VK_GOOGLE_display_timing). Before the application calls
* vkCreateDevice, SwappyVk needs to look at the list of available extensions
* (returned by vkEnumerateDeviceExtensionProperties) and potentially identify
* one or more extensions that the application must add to:
*
* - VkDeviceCreateInfo::enabledExtensionCount
* - VkDeviceCreateInfo::ppEnabledExtensionNames
*
* before the application calls vkCreateDevice. For each VkPhysicalDevice that
* the application will call vkCreateDevice for, the application must call this
* function, and then must add the identified extension(s) to the list that are
* enabled for the VkDevice. Similar to many Vulkan functions, this function
* can be called twice, once to identify the number of required extensions, and
* again with application-allocated memory that the function can write into.
*
* @param[in] physicalDevice - The VkPhysicalDevice associated with
* the available extensions.
* @param[in] availableExtensionCount - This is the returned value of
* pPropertyCount from vkEnumerateDeviceExtensionProperties.
* @param[in] pAvailableExtensions - This is the returned value of
* pProperties from vkEnumerateDeviceExtensionProperties.
* @param[inout] pRequiredExtensionCount - If pRequiredExtensions is nullptr,
* the function sets this to the number of extensions that are required. If
* pRequiredExtensions is non-nullptr, this is the number of required extensions
* that the function should write into pRequiredExtensions.
* @param[inout] pRequiredExtensions - If non-nullptr, this is
* application-allocated memory into which the function will write the names of
* required extensions. It is a pointer to an array of
* char* strings (i.e. the same as
* VkDeviceCreateInfo::ppEnabledExtensionNames).
*/
void SwappyVk_determineDeviceExtensions(
VkPhysicalDevice physicalDevice, uint32_t availableExtensionCount,
VkExtensionProperties* pAvailableExtensions,
uint32_t* pRequiredExtensionCount, char** pRequiredExtensions);
/**
* @brief Tell Swappy the queueFamilyIndex used to create a specific VkQueue
*
* Swappy needs to know the queueFamilyIndex used for creating a specific
* VkQueue so it can use it when presenting.
*
* @param[in] device - The VkDevice associated with the queue
* @param[in] queue - A device queue.
* @param[in] queueFamilyIndex - The queue family index used to create the
* VkQueue.
*
*/
void SwappyVk_setQueueFamilyIndex(VkDevice device, VkQueue queue,
uint32_t queueFamilyIndex);
// TBD: For now, SwappyVk assumes only one VkSwapchainKHR per VkDevice, and that
// applications don't re-create swapchains. Is this long-term sufficient?
/**
* Internal init function. Do not call directly.
* See SwappyVk_initAndGetRefreshCycleDuration instead.
* @private
*/
bool SwappyVk_initAndGetRefreshCycleDuration_internal(
/*JNIEnv* env, jobject jactivity,*/ VkPhysicalDevice physicalDevice,
VkDevice device, VkSwapchainKHR swapchain, uint64_t* pRefreshDuration);
/**
* @brief Initialize SwappyVk for a given device and swapchain, and obtain the
* approximate time duration between vertical-blanking periods.
*
* Uses JNI to query AppVsyncOffset and PresentationDeadline.
*
* If your application presents to more than one swapchain at a time, you must
* call this for each swapchain before calling swappyVkSetSwapInterval() for it.
*
* The duration between vertical-blanking periods (an interval) is expressed as
* the approximate number of nanoseconds between vertical-blanking periods of
* the swapchains physical display.
*
* If the application converts this number to a fraction (e.g. 16,666,666 nsec
* to 0.016666666) and divides one by that fraction, it will be the approximate
* refresh rate of the display (e.g. 16,666,666 nanoseconds corresponds to a
* 60Hz display, 11,111,111 nsec corresponds to a 90Hz display).
*
* @param[in] env - JNIEnv that is assumed to be from AttachCurrentThread
* function
* @param[in] jactivity - NativeActivity object handle, used for JNI
* @param[in] physicalDevice - The VkPhysicalDevice associated with the
* swapchain
* @param[in] device - The VkDevice associated with the swapchain
* @param[in] swapchain - The VkSwapchainKHR the application wants Swappy to
* swap
* @param[out] pRefreshDuration - The returned refresh cycle duration
*
* @return bool - true if the value returned by pRefreshDuration is
* valid, otherwise false if an error.
*/
bool SwappyVk_initAndGetRefreshCycleDuration(//JNIEnv* env, jobject jactivity,
VkPhysicalDevice physicalDevice,
VkDevice device,
VkSwapchainKHR swapchain,
uint64_t* pRefreshDuration);
/**
* @brief Tell Swappy which ANativeWindow to use when calling to ANativeWindow_*
* API.
* @param[in] device - The VkDevice associated with the swapchain
* @param[in] swapchain - The VkSwapchainKHR the application wants Swappy to
* swap
* @param[in] window - The ANativeWindow that was used to create the
* VkSwapchainKHR
*/
void SwappyVk_setWindow(VkDevice device, VkSwapchainKHR swapchain,
ANativeWindow* window);
/**
* @brief Tell Swappy the duration of that each presented image should be
* visible.
*
* If your application presents to more than one swapchain at a time, you must
* call this for each swapchain before presenting to it.
*
* @param[in] device - The VkDevice associated with the swapchain
* @param[in] swapchain - The VkSwapchainKHR the application wants Swappy to
* swap
* @param[in] swap_ns - The duration of that each presented image should be
* visible in nanoseconds
*/
void SwappyVk_setSwapIntervalNS(VkDevice device, VkSwapchainKHR swapchain,
uint64_t swap_ns);
/**
* @brief Tell Swappy to present one or more images to corresponding swapchains.
*
* Swappy will call vkQueuePresentKHR for your application. Swappy may insert a
* struct to the pNext-chain of VkPresentInfoKHR, or it may insert other Vulkan
* commands in order to attempt to honor the desired swap interval.
*
* @note If your application presents to more than one swapchain at a time, and
* if you use a different swap interval for each swapchain, Swappy will attempt
* to honor the swap interval for each swapchain (being more successful on
* devices that support an underlying presentation-timing extension, such as
* VK_GOOGLE_display_timing).
*
* @param[in] queue - The VkQueue associated with the device and swapchain
* @param[in] pPresentInfo - A pointer to the VkPresentInfoKHR containing the
* information about what image(s) to present on which
* swapchain(s).
*/
VkResult SwappyVk_queuePresent(VkQueue queue,
const VkPresentInfoKHR* pPresentInfo);
/**
* @brief Destroy the SwappyVk instance associated with a swapchain.
*
* This API is expected to be called before calling vkDestroySwapchainKHR()
* so Swappy can cleanup its internal state.
*
* @param[in] device - The VkDevice associated with SwappyVk
* @param[in] swapchain - The VkSwapchainKHR the application wants Swappy to
* destroy
*/
void SwappyVk_destroySwapchain(VkDevice device, VkSwapchainKHR swapchain);
/**
* @brief Destroy any swapchains associated with the device and clean up the
* device's resources
*
* This function should be called after SwappyVk_destroySwapchain if you no
* longer need the device.
*
* @param[in] device - The VkDevice associated with SwappyVk
*/
void SwappyVk_destroyDevice(VkDevice device);
/**
* @brief Enables Auto-Swap-Interval feature for all instances.
*
* By default this feature is enabled. Changing it is completely
* optional for fine-tuning swappy behaviour.
*
* @param[in] enabled - True means enable, false means disable
*/
void SwappyVk_setAutoSwapInterval(bool enabled);
/**
* @brief Enables Auto-Pipeline-Mode feature for all instances.
*
* By default this feature is enabled. Changing it is completely
* optional for fine-tuning swappy behaviour.
*
* @param[in] enabled - True means enable, false means disable
*/
void SwappyVk_setAutoPipelineMode(bool enabled);
/**
* @brief Sets the maximal swap duration for all instances.
*
* Sets the maximal duration for Auto-Swap-Interval in milliseconds.
* If SwappyVk is operating in Auto-Swap-Interval and the frame duration is
* longer than the provided duration, SwappyVk will not do any pacing and just
* submit the frame as soon as possible.
*
* @param[in] max_swap_ns - maximal swap duration in milliseconds.
*/
void SwappyVk_setMaxAutoSwapIntervalNS(uint64_t max_swap_ns);
/**
* @brief The fence timeout parameter can be set for devices with faulty
* drivers. Its default value is 50,000,000.
*/
void SwappyVk_setFenceTimeoutNS(uint64_t fence_timeout_ns);
/**
* @brief Get the fence timeout parameter, for devices with faulty
* drivers. Its default value is 50,000,000.
*/
uint64_t SwappyVk_getFenceTimeoutNS();
/**
* @brief Inject callback functions to be called each frame.
*
* @param[in] tracer - Collection of callback functions
*/
void SwappyVk_injectTracer(const SwappyTracer* tracer);
/**
* @brief A structure enabling you to provide your own Vulkan function wrappers
* by calling ::SwappyVk_setFunctionProvider.
*
* Usage of this functionality is optional.
*/
typedef struct SwappyVkFunctionProvider {
/**
* @brief Callback to initialize the function provider.
*
* This function is called by Swappy before any functions are requested.
* E.g. so you can call dlopen on the Vulkan library.
*/
bool (*init)();
/**
* @brief Callback to get the address of a function.
*
* This function is called by Swappy to get the address of a Vulkan
* function.
* @param name The null-terminated name of the function.
*/
void* (*getProcAddr)(const char* name);
/**
* @brief Callback to close any resources owned by the function provider.
*
* This function is called by Swappy when no more functions will be
* requested, e.g. so you can call dlclose on the Vulkan library.
*/
void (*close)();
} SwappyVkFunctionProvider;
/**
* @brief Set the Vulkan function provider.
*
* This enables you to provide an object that will be used to look up Vulkan
* functions, e.g. to hook usage of these functions.
*
* To use this functionality, you *must* call this function before any others.
*
* Usage of this function is entirely optional. If you do not use it, the Vulkan
* functions required by Swappy will be dynamically loaded from libvulkan.so.
*
* @param[in] provider - provider object
*/
void SwappyVk_setFunctionProvider(
const SwappyVkFunctionProvider* pSwappyVkFunctionProvider);
/**
* @brief Get the swap interval value, in nanoseconds, for a given swapchain.
*
* @param[in] swapchain - the swapchain to query
*/
uint64_t SwappyVk_getSwapIntervalNS(VkSwapchainKHR swapchain);
#ifdef __cplusplus
} // extern "C"
#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…
Cancel
Save