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

430 lines
16 KiB
C++

/*
* 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 {}", 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: {}", 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: {}", res);
return res;
}
res =
vkAllocateCommandBuffers(mDevice, &present_cmd_info, &sync.command);
if (res) {
ALOGE("vkAllocateCommandBuffers failed {}", 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 {}", 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 {}", res);
return res;
}
vkCmdSetEvent(sync.command, sync.event,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
res = vkEndCommandBuffer(sync.command);
if (res) {
ALOGE("vkCreateEvent failed {}", 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 {}", 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