diff options
author | Vamsidhar reddy Gaddam <gvamsi@google.com> | 2022-11-11 13:35:08 +0000 |
---|---|---|
committer | Vamsidhar reddy Gaddam <gvamsi@google.com> | 2022-11-28 09:05:16 +0000 |
commit | 6b90ca5ea7587a4b8aeeb52ecce6a618750080b5 (patch) | |
tree | 7a6922e68041c819e8f09beb488460d9a1ee7a30 | |
parent | d60955290dfbce5654b0887b8924a429f3a8adcb (diff) | |
download | gamesdk-6b90ca5ea7587a4b8aeeb52ecce6a618750080b5.tar.gz |
Add Vulkan frame stats to Swappy
In this commit, Vulkan frame statistics are added to Swappy. The
implementation is built on top of the common frame statistics module.
* Implement the entry points.
* When VK_GOOGLE_display_timing is available, fetch frame data,
otherwise just log an error message.
Bug: 123727506
Test: Ran the cube sample app and saw frame statistics
Change-Id: I8210d86e8aeff570b6c54952cd60d4d795ef268c
-rw-r--r-- | games-frame-pacing/vulkan/SwappyVk.cpp | 24 | ||||
-rw-r--r-- | games-frame-pacing/vulkan/SwappyVk.h | 7 | ||||
-rw-r--r-- | games-frame-pacing/vulkan/SwappyVkBase.h | 5 | ||||
-rw-r--r-- | games-frame-pacing/vulkan/SwappyVkFallback.cpp | 16 | ||||
-rw-r--r-- | games-frame-pacing/vulkan/SwappyVkFallback.h | 13 | ||||
-rw-r--r-- | games-frame-pacing/vulkan/SwappyVkGoogleDisplayTiming.cpp | 127 | ||||
-rw-r--r-- | games-frame-pacing/vulkan/SwappyVkGoogleDisplayTiming.h | 34 | ||||
-rw-r--r-- | games-frame-pacing/vulkan/swappyVk_c.cpp | 19 |
8 files changed, 211 insertions, 34 deletions
diff --git a/games-frame-pacing/vulkan/SwappyVk.cpp b/games-frame-pacing/vulkan/SwappyVk.cpp index 70bb4cd3..5f940dce 100644 --- a/games-frame-pacing/vulkan/SwappyVk.cpp +++ b/games-frame-pacing/vulkan/SwappyVk.cpp @@ -313,4 +313,28 @@ bool SwappyVk::IsEnabled(VkSwapchainKHR swapchain, bool* 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(); +} + } // namespace swappy diff --git a/games-frame-pacing/vulkan/SwappyVk.h b/games-frame-pacing/vulkan/SwappyVk.h index 379f9d69..16d91715 100644 --- a/games-frame-pacing/vulkan/SwappyVk.h +++ b/games-frame-pacing/vulkan/SwappyVk.h @@ -91,6 +91,13 @@ class SwappyVk { bool IsEnabled(VkSwapchainKHR swapchain, bool* isEnabled); + // Frame statistics. + void enableStats(VkSwapchainKHR swapchain, bool enabled); + void getStats(VkSwapchainKHR swapchain, SwappyStats* swappyStats); + void recordFrameStart(VkQueue queue, VkSwapchainKHR swapchain, + uint32_t image); + void clearStats(VkSwapchainKHR swapchain); + private: std::map<VkPhysicalDevice, bool> doesPhysicalDeviceHaveGoogleDisplayTiming; std::map<VkSwapchainKHR, std::shared_ptr<SwappyVkBase>> diff --git a/games-frame-pacing/vulkan/SwappyVkBase.h b/games-frame-pacing/vulkan/SwappyVkBase.h index dc0af223..5705c8af 100644 --- a/games-frame-pacing/vulkan/SwappyVkBase.h +++ b/games-frame-pacing/vulkan/SwappyVkBase.h @@ -145,6 +145,11 @@ class SwappyVkBase { int getSupportedRefreshPeriodsNS(uint64_t* out_refreshrates, int allocated_entries); + virtual void enableStats(bool enabled) = 0; + virtual void getStats(SwappyStats* swappyStats) = 0; + virtual void recordFrameStart(VkQueue queue, uint32_t image) = 0; + virtual void clearStats() = 0; + protected: struct VkSync { VkFence fence; diff --git a/games-frame-pacing/vulkan/SwappyVkFallback.cpp b/games-frame-pacing/vulkan/SwappyVkFallback.cpp index abf0f8bb..741bd901 100644 --- a/games-frame-pacing/vulkan/SwappyVkFallback.cpp +++ b/games-frame-pacing/vulkan/SwappyVkFallback.cpp @@ -99,4 +99,20 @@ VkResult SwappyVkFallback::doQueuePresent( return result; } +void SwappyVkFallback::enableStats(bool enabled) { + ALOGE("Frame Statistics Unsupported - API ignored"); +} + +void SwappyVkFallback::getStats(SwappyStats* swappyStats) { + ALOGE("Frame Statistics Unsupported - API ignored"); +} + +void SwappyVkFallback::recordFrameStart(VkQueue queue, uint32_t image) { + ALOGE("Frame Statistics Unsupported - API ignored"); +} + +void SwappyVkFallback::clearStats() { + ALOGE("Frame Statistics Unsupported - API ignored"); +} + } // namespace swappy diff --git a/games-frame-pacing/vulkan/SwappyVkFallback.h b/games-frame-pacing/vulkan/SwappyVkFallback.h index 5906fee2..64518b55 100644 --- a/games-frame-pacing/vulkan/SwappyVkFallback.h +++ b/games-frame-pacing/vulkan/SwappyVkFallback.h @@ -33,12 +33,17 @@ class SwappyVkFallback : public SwappyVkBase { VkPhysicalDevice physicalDevice, VkDevice device, const SwappyVkFunctionProvider* provider); - virtual bool doGetRefreshCycleDuration(VkSwapchainKHR swapchain, - uint64_t* pRefreshDuration) override; + bool doGetRefreshCycleDuration(VkSwapchainKHR swapchain, + uint64_t* pRefreshDuration) override final; - virtual VkResult doQueuePresent( + VkResult doQueuePresent( VkQueue queue, uint32_t queueFamilyIndex, - const VkPresentInfoKHR* pPresentInfo) override; + const VkPresentInfoKHR* pPresentInfo) override final; + + void enableStats(bool enabled) override final; + void recordFrameStart(VkQueue queue, uint32_t image) override final; + void getStats(SwappyStats* swappyStats) override final; + void clearStats() override final; }; } // namespace swappy diff --git a/games-frame-pacing/vulkan/SwappyVkGoogleDisplayTiming.cpp b/games-frame-pacing/vulkan/SwappyVkGoogleDisplayTiming.cpp index 69e268ec..c450631c 100644 --- a/games-frame-pacing/vulkan/SwappyVkGoogleDisplayTiming.cpp +++ b/games-frame-pacing/vulkan/SwappyVkGoogleDisplayTiming.cpp @@ -50,6 +50,7 @@ bool SwappyVkGoogleDisplayTiming::doGetRefreshCycleDuration( ALOGI("Returning refresh duration of %" PRIu64 " nsec (approx %f Hz)", *pRefreshDuration, refreshRate); + mSwapchain = swapchain; return true; } @@ -95,32 +96,28 @@ VkResult SwappyVkGoogleDisplayTiming::doQueuePresent( 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(); - } + // 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}; + 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}; + 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++; + mPresentID++; res = mpfnQueuePresentKHR(queue, &replacementPresentInfo); mCommonBase.onPostSwap(handlers); @@ -128,6 +125,90 @@ VkResult SwappyVkGoogleDisplayTiming::doQueuePresent( 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) { + ALOGI( + "More past presentation times available. Consider increasing " + "MAX_FRAME_LAG"); + } + if (result != VK_SUCCESS && result != VK_INCOMPLETE) { + ALOGE("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 diff --git a/games-frame-pacing/vulkan/SwappyVkGoogleDisplayTiming.h b/games-frame-pacing/vulkan/SwappyVkGoogleDisplayTiming.h index 06c91eb3..6f6d1536 100644 --- a/games-frame-pacing/vulkan/SwappyVkGoogleDisplayTiming.h +++ b/games-frame-pacing/vulkan/SwappyVkGoogleDisplayTiming.h @@ -16,6 +16,7 @@ #pragma once +#include "FrameStatistics.h" #include "SwappyVkBase.h" namespace swappy { @@ -70,12 +71,37 @@ class SwappyVkGoogleDisplayTiming : public SwappyVkBase { VkDevice device, const SwappyVkFunctionProvider* provider); - virtual bool doGetRefreshCycleDuration(VkSwapchainKHR swapchain, - uint64_t* pRefreshDuration) override; + bool doGetRefreshCycleDuration(VkSwapchainKHR swapchain, + uint64_t* pRefreshDuration) override final; - virtual VkResult doQueuePresent( + VkResult doQueuePresent( VkQueue queue, uint32_t queueFamilyIndex, - const VkPresentInfoKHR* pPresentInfo) override; + const VkPresentInfoKHR* pPresentInfo) override final; + + void enableStats(bool enabled) override final; + void recordFrameStart(VkQueue queue, uint32_t image) override final; + void getStats(SwappyStats* swappyStats) override final; + void clearStats() override final; + + private: + static constexpr int MAX_FRAME_LAG = 10; + // Vulkan loader does not give any frame timings unless they are 5 frames + // old. We use this internally to not waste calls. + static constexpr int MIN_FRAME_LAG = 5; + uint32_t mPresentID = 0; + + VkSwapchainKHR mSwapchain; + + struct VKFrame { + uint32_t id; + uint64_t startFrameTime; + int pastTimingIndex; + }; + + std::vector<VKFrame> mPendingFrames; + // Storage for querying past presentation frames, allocated upfront. + VkPastPresentationTimingGOOGLE mPastTimes[MAX_FRAME_LAG]; + FrameStatistics mFrameStatisticsCommon; }; } // namespace swappy diff --git a/games-frame-pacing/vulkan/swappyVk_c.cpp b/games-frame-pacing/vulkan/swappyVk_c.cpp index 2449a033..d025afc0 100644 --- a/games-frame-pacing/vulkan/swappyVk_c.cpp +++ b/games-frame-pacing/vulkan/swappyVk_c.cpp @@ -158,15 +158,28 @@ bool SwappyVk_isEnabled(VkSwapchainKHR swapchain, bool* isEnabled) { } void SwappyVk_enableStats(VkSwapchainKHR swapchain, bool enabled) { - // stub for new API + TRACE_CALL(); + swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance(); + swappy.enableStats(swapchain, enabled); } void SwappyVk_getStats(VkSwapchainKHR swapchain, SwappyStats* swappyStats) { - // stub for new API + TRACE_CALL(); + swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance(); + swappy.getStats(swapchain, swappyStats); } void SwappyVk_recordFrameStart(VkQueue queue, VkSwapchainKHR swapchain, uint32_t image) { - // stub for new API + TRACE_CALL(); + swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance(); + swappy.recordFrameStart(queue, swapchain, image); } + +void SwappyVk_clearStats(VkSwapchainKHR swapchain) { + TRACE_CALL(); + swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance(); + swappy.clearStats(swapchain); +} + } // extern "C" |