219 lines
8.1 KiB
C++
219 lines
8.1 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.
|
|
*/
|
|
|
|
#if (not defined ANDROID_NDK_VERSION) || ANDROID_NDK_VERSION >= 15
|
|
|
|
#include "SwappyVkGoogleDisplayTiming.h"
|
|
|
|
#define LOG_TAG "SwappyVkGoogleDisplayTiming"
|
|
#include "SwappyLog.h"
|
|
|
|
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) {
|
|
mPendingFrames.reserve(MAX_FRAME_LAG + 1);
|
|
}
|
|
|
|
bool SwappyVkGoogleDisplayTiming::doGetRefreshCycleDuration(
|
|
VkSwapchainKHR swapchain, uint64_t* pRefreshDuration) {
|
|
if (!isEnabled()) {
|
|
SWAPPY_LOGE("Swappy is disabled.");
|
|
return false;
|
|
}
|
|
|
|
VkRefreshCycleDurationGOOGLE refreshCycleDuration;
|
|
VkResult res = mpfnGetRefreshCycleDurationGOOGLE(mDevice, swapchain,
|
|
&refreshCycleDuration);
|
|
if (res != VK_SUCCESS) {
|
|
SWAPPY_LOGE("mpfnGetRefreshCycleDurationGOOGLE failed %d", res);
|
|
return false;
|
|
}
|
|
|
|
*pRefreshDuration = mCommonBase.getRefreshPeriod().count();
|
|
|
|
// refreshRate is only used for logging, which maybe disabled.
|
|
[[maybe_unused]] double refreshRate = 1000000000.0 / *pRefreshDuration;
|
|
SWAPPY_LOGI("Returning refresh duration of %" PRIu64 " nsec (approx %f Hz)",
|
|
*pRefreshDuration, refreshRate);
|
|
|
|
mSwapchain = swapchain;
|
|
return true;
|
|
}
|
|
|
|
VkResult SwappyVkGoogleDisplayTiming::doQueuePresent(
|
|
VkQueue queue, uint32_t queueFamilyIndex,
|
|
const VkPresentInfoKHR* pPresentInfo) {
|
|
if (!isEnabled()) {
|
|
SWAPPY_LOGE("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) {
|
|
SWAPPY_LOGE("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;
|
|
// Set up the new structures to pass:
|
|
// if 0 is passed as desired present time, it is ignored by the loader.
|
|
uint64_t desiredPresentTime =
|
|
mCommonBase.needToSetPresentationTime()
|
|
? mCommonBase.getPresentationTime().time_since_epoch().count()
|
|
: 0;
|
|
for (uint32_t i = 0; i < pPresentInfo->swapchainCount; i++) {
|
|
pPresentTimes[i].presentID = mPresentID;
|
|
pPresentTimes[i].desiredPresentTime = desiredPresentTime;
|
|
}
|
|
|
|
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};
|
|
|
|
mPresentID++;
|
|
|
|
res = mpfnQueuePresentKHR(queue, &replacementPresentInfo);
|
|
mCommonBase.onPostSwap(handlers);
|
|
|
|
return res;
|
|
}
|
|
|
|
void SwappyVkGoogleDisplayTiming::enableStats(bool enabled) {
|
|
mFrameStatisticsCommon.enableStats(enabled);
|
|
}
|
|
|
|
void SwappyVkGoogleDisplayTiming::recordFrameStart(VkQueue queue,
|
|
uint32_t image) {
|
|
uint64_t frameStartTime = static_cast<uint64_t>(
|
|
std::chrono::steady_clock::now().time_since_epoch().count());
|
|
mPendingFrames.push_back({mPresentID, frameStartTime});
|
|
|
|
// No point in querying if the history is too short, as vulkan loader does
|
|
// not return any history newer than 5 frames.
|
|
// See MIN_NUM_FRAMES_AGO in
|
|
// https://android.googlesource.com/platform/frameworks/native/+/refs/heads/master/vulkan/libvulkan/swapchain.cpp
|
|
if (mPendingFrames.size() < MIN_FRAME_LAG) return;
|
|
|
|
// The query for vulkan past presentation timings does not point to any
|
|
// specific id. Instead, the loader just returns whatever timings are
|
|
// available to the user. Query all the available timings, which is a max
|
|
// of 10 in the vulkan loader currently.
|
|
//
|
|
// Check through each of the timing if we have a pending frame id, if we do
|
|
// then populate the histogram with the available timings. There are a
|
|
// couple of assumptions made here.
|
|
// * The maximum size of the vectors here is 10, so simplicity is
|
|
// prioritized.
|
|
// * The frames are in order.
|
|
// * If any of the presentTimings ids are not present in mPendingFrames,
|
|
// those are frames that must have been cleared and we do not care about
|
|
// the timings anymore.
|
|
// * [Performance] Under normal smooth circumstances, this should be 1
|
|
// frame handled at a time, if there is a situation where several frames
|
|
// are pending, it implies that the gpu & presentation engine are not
|
|
// keeping up. So spending a few CPU cycles here to go through a few extra
|
|
// frames is not going to impact overall performance.
|
|
uint32_t pastTimingsCount = MAX_FRAME_LAG;
|
|
VkResult result = mpfnGetPastPresentationTimingGOOGLE(
|
|
mDevice, mSwapchain, &pastTimingsCount, &mPastTimes[0]);
|
|
|
|
if (result == VK_INCOMPLETE) {
|
|
SWAPPY_LOGI(
|
|
"More past presentation times available. Consider increasing "
|
|
"MAX_FRAME_LAG");
|
|
}
|
|
if (result != VK_SUCCESS && result != VK_INCOMPLETE) {
|
|
SWAPPY_LOGE("Error collecting past presentation times with result %d",
|
|
result);
|
|
return;
|
|
}
|
|
|
|
int i = 0;
|
|
while (i < pastTimingsCount && mPendingFrames.size() > 1) {
|
|
auto frame = mPendingFrames.front();
|
|
|
|
if (frame.id == mPastTimes[i].presentID) {
|
|
FrameTimings current = {
|
|
frame.startFrameTime, mPastTimes[i].desiredPresentTime,
|
|
mPastTimes[i].actualPresentTime, mPastTimes[i].presentMargin};
|
|
|
|
mFrameStatisticsCommon.updateFrameStats(
|
|
current, mCommonBase.getRefreshPeriod().count());
|
|
i++;
|
|
}
|
|
// If the past timings returned do not match, then the pending frame is
|
|
// too old. So remove it from the list.
|
|
mPendingFrames.erase(mPendingFrames.begin());
|
|
}
|
|
|
|
// Clear the pending frames if we are lagging too much.
|
|
if (mPendingFrames.size() > MAX_FRAME_LAG) {
|
|
while (mPendingFrames.size() > MIN_FRAME_LAG) {
|
|
mPendingFrames.erase(mPendingFrames.begin());
|
|
}
|
|
mFrameStatisticsCommon.invalidateLastFrame();
|
|
}
|
|
}
|
|
|
|
void SwappyVkGoogleDisplayTiming::getStats(SwappyStats* swappyStats) {
|
|
*swappyStats = mFrameStatisticsCommon.getStats();
|
|
}
|
|
|
|
void SwappyVkGoogleDisplayTiming::clearStats() {
|
|
mFrameStatisticsCommon.clearStats();
|
|
}
|
|
} // namespace swappy
|
|
|
|
#endif // #if (not defined ANDROID_NDK_VERSION) || ANDROID_NDK_VERSION>=15
|