aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVamsidhar reddy Gaddam <gvamsi@google.com>2022-11-11 13:35:08 +0000
committerVamsidhar reddy Gaddam <gvamsi@google.com>2022-11-28 09:05:16 +0000
commit6b90ca5ea7587a4b8aeeb52ecce6a618750080b5 (patch)
tree7a6922e68041c819e8f09beb488460d9a1ee7a30
parentd60955290dfbce5654b0887b8924a429f3a8adcb (diff)
downloadgamesdk-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.cpp24
-rw-r--r--games-frame-pacing/vulkan/SwappyVk.h7
-rw-r--r--games-frame-pacing/vulkan/SwappyVkBase.h5
-rw-r--r--games-frame-pacing/vulkan/SwappyVkFallback.cpp16
-rw-r--r--games-frame-pacing/vulkan/SwappyVkFallback.h13
-rw-r--r--games-frame-pacing/vulkan/SwappyVkGoogleDisplayTiming.cpp127
-rw-r--r--games-frame-pacing/vulkan/SwappyVkGoogleDisplayTiming.h34
-rw-r--r--games-frame-pacing/vulkan/swappyVk_c.cpp19
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"