Files
UnrealEngine/Engine/Source/ThirdParty/GoogleGameSDK/gamesdk/games-frame-pacing/vulkan/SwappyVk.cpp
2025-05-18 13:04:45 +08:00

412 lines
15 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 "SwappyVk.h"
#define LOG_TAG "SwappyVk"
#include "SwappyLog.h"
/* For tracking tracers internally within vulkan instance, we cannot store the
* pointers as there is no requirement for the life of the object once the
* SwappyVk_injectTracer(*t) call is returned. So we store the struct objects by
* value. In turn, this implies that we have to compare the whole struct for
* managing it. So we define a local "==" operator to this file in order to
* handle the C struct API in the std::list.
*
* In addition, there is a risk that updates to the SwappyTracer struct could go
* unnoticed silently. So we have a copy here and we check at compile time that
* the sizes are the same, it is a weak check, but it is better than nothing.
*/
typedef struct SwappyTracerLocalStruct {
SwappyPreWaitCallback preWait;
SwappyPostWaitCallback postWait;
SwappyPreSwapBuffersCallback preSwapBuffers;
SwappyPostSwapBuffersCallback postSwapBuffers;
SwappyStartFrameCallback startFrame;
void* userData;
SwappySwapIntervalChangedCallback swapIntervalChanged;
} SwappyTracerLocalStruct;
static bool operator==(const SwappyTracer& t1, const SwappyTracer& t2) {
static_assert(sizeof(SwappyTracer) == sizeof(SwappyTracerLocalStruct),
"SwappyTracer struct appears to have changed, please "
"consider updating locally.");
return (t1.preWait == t2.preWait) && (t1.postWait == t2.postWait) &&
(t1.preSwapBuffers == t2.preSwapBuffers) &&
(t1.postSwapBuffers == t2.postSwapBuffers) &&
(t1.startFrame == t2.startFrame) && (t1.userData == t2.userData) &&
(t1.swapIntervalChanged == t2.swapIntervalChanged);
}
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);
SWAPPY_LOGV(
"SwappyVk initialized for VkDevice %p using "
"VK_GOOGLE_display_timing on Android",
device);
} else
#endif
{
pImplementation = std::make_shared<SwappyVkFallback>(
env, jactivity, physicalDevice, device, pFunctionProvider);
SWAPPY_LOGV(
"SwappyVk initialized for VkDevice %p using Android fallback",
device);
}
if (!pImplementation) { // should never happen
SWAPPY_LOGE(
"SwappyVk could not find or create correct implementation for "
"the current environment: "
"%p, %p",
physicalDevice, device);
return false;
}
}
// SwappyBase is constructed by this point, so we can add the tracers we
// have so far.
{
std::lock_guard<std::mutex> lock(tracer_list_lock);
for (const auto& tracer : tracer_list) {
pImplementation->addTracer(&tracer);
}
}
// 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()) {
SWAPPY_LOGE(
"Unknown queue %p. Did you call SwappyVkSetQueueFamilyIndex ?",
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) {
if (t != nullptr) {
std::lock_guard<std::mutex> lock(tracer_list_lock);
tracer_list.push_back(*t);
for (const auto& i : perSwapchainImplementation) {
i.second->addTracer(t);
}
}
}
void SwappyVk::removeTracer(const SwappyTracer* t) {
if (t != nullptr) {
std::lock_guard<std::mutex> lock(tracer_list_lock);
tracer_list.remove(*t);
for (const auto& i : perSwapchainImplementation) {
i.second->removeTracer(t);
}
}
}
int SwappyVk::GetSupportedRefreshPeriodsNS(uint64_t* out_refreshrates,
int allocated_entries,
VkSwapchainKHR swapchain) {
return (*perSwapchainImplementation[swapchain])
.getSupportedRefreshPeriodsNS(out_refreshrates, allocated_entries);
}
bool SwappyVk::IsEnabled(VkSwapchainKHR swapchain, bool* isEnabled) {
auto& pImplementation = perSwapchainImplementation[swapchain];
if (!pImplementation || !isEnabled) return false;
*isEnabled = pImplementation->isEnabled();
return true;
}
void SwappyVk::enableStats(VkSwapchainKHR swapchain, bool enabled) {
auto it = perSwapchainImplementation.find(swapchain);
if (it != perSwapchainImplementation.end())
it->second->enableStats(enabled);
}
void SwappyVk::getStats(VkSwapchainKHR swapchain, SwappyStats* swappyStats) {
auto it = perSwapchainImplementation.find(swapchain);
if (it != perSwapchainImplementation.end())
it->second->getStats(swappyStats);
}
void SwappyVk::recordFrameStart(VkQueue queue, VkSwapchainKHR swapchain,
uint32_t image) {
auto it = perSwapchainImplementation.find(swapchain);
if (it != perSwapchainImplementation.end())
it->second->recordFrameStart(queue, image);
}
void SwappyVk::clearStats(VkSwapchainKHR swapchain) {
auto it = perSwapchainImplementation.find(swapchain);
if (it != perSwapchainImplementation.end()) it->second->clearStats();
}
void SwappyVk::resetFramePacing(VkSwapchainKHR swapchain) {
auto it = perSwapchainImplementation.find(swapchain);
if (it != perSwapchainImplementation.end()) it->second->resetFramePacing();
}
void SwappyVk::enableFramePacing(VkSwapchainKHR swapchain, bool enable) {
auto it = perSwapchainImplementation.find(swapchain);
if (it != perSwapchainImplementation.end())
it->second->enableFramePacing(enable);
}
void SwappyVk::enableBlockingWait(VkSwapchainKHR swapchain, bool enable) {
auto it = perSwapchainImplementation.find(swapchain);
if (it != perSwapchainImplementation.end())
it->second->enableBlockingWait(enable);
}
} // namespace swappy