| /* |
| * Copyright 2018 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/SwappyVk.h> |
| |
| #include <map> |
| #include <condition_variable> |
| #include <cstring> |
| #include <unistd.h> |
| |
| #include <dlfcn.h> |
| #include <cstdlib> |
| |
| #include <inttypes.h> |
| |
| #ifdef ANDROID |
| #include <mutex> |
| #include <pthread.h> |
| #include <list> |
| #include <android/looper.h> |
| #include <android/choreographer.h> |
| #include <android/log.h> |
| #include <android/trace.h> |
| |
| #define ATRACE_NAME(name) ScopedTrace ___tracer(name) |
| |
| // ATRACE_CALL is an ATRACE_NAME that uses the current function name. |
| #define ATRACE_CALL() ATRACE_NAME(__FUNCTION__) |
| |
| class ScopedTrace { |
| public: |
| inline ScopedTrace(const char *name) { |
| ATrace_beginSection(name); |
| } |
| |
| inline ~ScopedTrace() { |
| ATrace_endSection(); |
| } |
| }; |
| |
| |
| #define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, "SwappyVk", __VA_ARGS__) |
| #define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, "SwappyVk", __VA_ARGS__) |
| #define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, "SwappyVk", __VA_ARGS__) |
| #define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "SwappyVk", __VA_ARGS__) |
| #define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, "SwappyVk", __VA_ARGS__) |
| #else |
| #define ATRACE_CALL() ((void)0) |
| #define ALOGE(...) ((void)0) |
| #define ALOGW(...) ((void)0) |
| #define ALOGD(...) ((void)0) |
| #define ALOGV(...) ((void)0) |
| #endif |
| |
| |
| constexpr uint32_t kThousand = 1000; |
| constexpr uint32_t kMillion = 1000000; |
| constexpr uint32_t kBillion = 1000000000; |
| constexpr uint32_t k16_6msec = 16666666; |
| |
| constexpr uint32_t kTooCloseToVsyncBoundary = 3000000; |
| constexpr uint32_t kTooFarAwayFromVsyncBoundary = 7000000; |
| constexpr uint32_t kNudgeWithinVsyncBoundaries = 2000000; |
| |
| // Note: The API functions is at the botton of the file. Those functions call methods of the |
| // singleton SwappyVk class. Those methods call virtual methods of the abstract SwappyVkBase |
| // class, which is actually implemented by one of the derived/concrete classes: |
| // |
| // - SwappyVkGoogleDisplayTiming |
| // - SwappyVkVulkanFallback |
| // - SwappyVkAndroidFallback |
| |
| // Forward declarations: |
| class SwappyVk; |
| |
| /*************************************************************************************************** |
| * |
| * Per-Device abstract base class. |
| * |
| ***************************************************************************************************/ |
| |
| /** |
| * Abstract base class that calls the Vulkan API. |
| * |
| * It is expected that one concrete class will be instantiated per VkDevice, and that all |
| * VkSwapchainKHR's for a given VkDevice will share the same instance. |
| * |
| * Base class members are used by the derived classes to unify the behavior across implementaitons: |
| * @mThread - Thread used for getting Choreographer events. |
| * @mTreadRunning - Used to signal the tread to exit |
| * @mNextPresentID - unique ID for frame presentation. |
| * @mNextDesiredPresentTime - Holds the time in nanoseconds for the next frame to be presented. |
| * @mNextPresentIDToCheck - Used to determine whether presentation time needs to be adjusted. |
| * @mFrameID - Keeps track of how many Choreographer callbacks received. |
| * @mLastframeTimeNanos - Holds the last frame time reported by Choreographer. |
| * @mSumRefreshTime - Used together with @mSamples to calculate refresh rate based on Choreographer. |
| */ |
| class SwappyVkBase |
| { |
| public: |
| SwappyVkBase(VkPhysicalDevice physicalDevice, |
| VkDevice device, |
| uint64_t refreshDur, |
| uint32_t interval, |
| SwappyVk &swappyVk, |
| void *libVulkan) : |
| mPhysicalDevice(physicalDevice), mDevice(device), mRefreshDur(refreshDur), |
| mInterval(interval), mSwappyVk(swappyVk), mLibVulkan(libVulkan), |
| mInitialized(false) |
| { |
| #ifdef ANDROID |
| InitVulkan(); |
| #endif |
| mpfnGetDeviceProcAddr = |
| reinterpret_cast<PFN_vkGetDeviceProcAddr>( |
| dlsym(mLibVulkan, "vkGetDeviceProcAddr")); |
| mpfnQueuePresentKHR = |
| reinterpret_cast<PFN_vkQueuePresentKHR>( |
| mpfnGetDeviceProcAddr(mDevice, "vkQueuePresentKHR")); |
| } |
| virtual bool doGetRefreshCycleDuration(VkSwapchainKHR swapchain, |
| uint64_t* pRefreshDuration) = 0; |
| void doSetSwapInterval(VkSwapchainKHR swapchain, |
| uint32_t interval) |
| { |
| mInterval = interval; |
| } |
| virtual VkResult doQueuePresent(VkQueue queue, |
| uint32_t queueFamilyIndex, |
| const VkPresentInfoKHR* pPresentInfo) = 0; |
| protected: |
| VkPhysicalDevice mPhysicalDevice; |
| VkDevice mDevice; |
| uint64_t mRefreshDur; |
| uint32_t mInterval; |
| SwappyVk &mSwappyVk; |
| void *mLibVulkan; |
| bool mInitialized; |
| pthread_t mThread = 0; |
| ALooper *mLooper = nullptr; |
| bool mTreadRunning = false; |
| AChoreographer *mChoreographer = nullptr; |
| std::mutex mWaitingMutex; |
| std::condition_variable mWaitingCondition; |
| uint32_t mNextPresentID = 0; |
| uint64_t mNextDesiredPresentTime = 0; |
| uint32_t mNextPresentIDToCheck = 2; |
| |
| PFN_vkGetDeviceProcAddr mpfnGetDeviceProcAddr = nullptr; |
| PFN_vkQueuePresentKHR mpfnQueuePresentKHR = nullptr; |
| PFN_vkGetRefreshCycleDurationGOOGLE mpfnGetRefreshCycleDurationGOOGLE = nullptr; |
| PFN_vkGetPastPresentationTimingGOOGLE mpfnGetPastPresentationTimingGOOGLE = nullptr; |
| |
| long mFrameID = 0; |
| long mLastframeTimeNanos = 0; |
| long mSumRefreshTime = 0; |
| long mSamples = 0; |
| |
| static constexpr int CHOREOGRAPHER_THRESH = 1000; |
| static constexpr int MAX_SAMPLES = 5; |
| |
| void initGoogExtention() |
| { |
| mpfnGetRefreshCycleDurationGOOGLE = |
| reinterpret_cast<PFN_vkGetRefreshCycleDurationGOOGLE>( |
| mpfnGetDeviceProcAddr(mDevice, "vkGetRefreshCycleDurationGOOGLE")); |
| mpfnGetPastPresentationTimingGOOGLE = |
| reinterpret_cast<PFN_vkGetPastPresentationTimingGOOGLE>( |
| mpfnGetDeviceProcAddr(mDevice, "vkGetPastPresentationTimingGOOGLE")); |
| } |
| |
| void startChoreographerThread(); |
| void stopChoreographerThread(); |
| static void *looperThreadWrapper(void *data); |
| void *looperThread(); |
| static void frameCallback(long frameTimeNanos, void *data); |
| void onDisplayRefresh(long frameTimeNanos); |
| void calcRefreshRate(long frameTimeNanos); |
| }; |
| |
| void SwappyVkBase::startChoreographerThread() { |
| std::unique_lock<std::mutex> lock(mWaitingMutex); |
| // create a new ALooper thread to get Choreographer events |
| mTreadRunning = true; |
| pthread_create(&mThread, NULL, looperThreadWrapper, this); |
| mWaitingCondition.wait(lock, [&]() { return mChoreographer != nullptr; }); |
| } |
| |
| void SwappyVkBase::stopChoreographerThread() { |
| if (mLooper) { |
| ALooper_acquire(mLooper); |
| mTreadRunning = false; |
| ALooper_wake(mLooper); |
| ALooper_release(mLooper); |
| pthread_join(mThread, NULL); |
| } |
| } |
| |
| void *SwappyVkBase::looperThreadWrapper(void *data) { |
| SwappyVkBase *me = reinterpret_cast<SwappyVkBase *>(data); |
| return me->looperThread(); |
| } |
| |
| void *SwappyVkBase::looperThread() { |
| int outFd, outEvents; |
| void *outData; |
| |
| mLooper = ALooper_prepare(0); |
| if (!mLooper) { |
| ALOGE("ALooper_prepare failed"); |
| return NULL; |
| } |
| |
| mChoreographer = AChoreographer_getInstance(); |
| if (!mChoreographer) { |
| ALOGE("AChoreographer_getInstance failed"); |
| return NULL; |
| } |
| mWaitingCondition.notify_all(); |
| |
| while (mTreadRunning) { |
| ALooper_pollAll(-1, &outFd, &outEvents, &outData); |
| } |
| |
| return NULL; |
| } |
| |
| void SwappyVkBase::frameCallback(long frameTimeNanos, void *data) { |
| SwappyVkBase *me = reinterpret_cast<SwappyVkBase *>(data); |
| me->onDisplayRefresh(frameTimeNanos); |
| } |
| |
| void SwappyVkBase::onDisplayRefresh(long frameTimeNanos) { |
| std::lock_guard<std::mutex> lock(mWaitingMutex); |
| calcRefreshRate(frameTimeNanos); |
| mLastframeTimeNanos = frameTimeNanos; |
| mFrameID++; |
| mWaitingCondition.notify_all(); |
| } |
| |
| void SwappyVkBase::calcRefreshRate(long frameTimeNanos) { |
| long refresh_nano = std::abs(frameTimeNanos - mLastframeTimeNanos); |
| |
| if (mRefreshDur != 0 || mLastframeTimeNanos == 0) { |
| return; |
| } |
| |
| // ignore wrap around |
| if (mLastframeTimeNanos > frameTimeNanos) { |
| return; |
| } |
| |
| mSumRefreshTime += refresh_nano; |
| mSamples++; |
| |
| if (mSamples == MAX_SAMPLES) { |
| mRefreshDur = mSumRefreshTime / mSamples; |
| } |
| } |
| |
| |
| /*************************************************************************************************** |
| * |
| * Per-Device concrete/derived class for using VK_GOOGLE_display_timing. |
| * |
| * This class uses the VK_GOOGLE_display_timing in order to present frames at a muliple (the "swap |
| * interval") of a fixed refresh-cycle duration (i.e. the time between successive vsync's). |
| * |
| * In order to reduce complexity, some simplifying assumptions are made: |
| * |
| * - We assume a fixed refresh-rate (FRR) display that's between 60 Hz and 120 Hz. |
| * |
| * - While Vulkan allows applications to create and use multiple VkSwapchainKHR's per VkDevice, and |
| * to re-create VkSwapchainKHR's, we assume that the application uses a single VkSwapchainKHR, |
| * and never re-creates it. |
| * |
| * - The values reported back by the VK_GOOGLE_display_timing extension (which comes from |
| * lower-level Android interfaces) are not precise, and that values can drift over time. For |
| * example, the refresh-cycle duration for a 60 Hz display should be 16,666,666 nsec; but the |
| * value reported back by the extension won't be precisely this. Also, the differences betweeen |
| * the times of two successive frames won't be an exact multiple of 16,666,666 nsec. This can |
| * make it difficult to precisely predict when a future vsync will be (it can appear to drift |
| * overtime). Therefore, we try to give a desiredPresentTime for each image that is between 3 |
| * and 7 msec before vsync. We look at the actualPresentTime for previously-presented images, |
| * and nudge the future desiredPresentTime back within those 3-7 msec boundaries. |
| * |
| * - There can be a few frames of latency between when an image is presented and when the |
| * actualPresentTime is available for that image. Therefore, we initially just pick times based |
| * upon CLOCK_MONOTONIC (which is the time domain for VK_GOOGLE_display_timing). After we get |
| * past-present times, we nudge the desiredPresentTime, we wait for a few presents before looking |
| * again to see whether we need to nudge again. |
| * |
| * - If, for some reason, an application can't keep up with its chosen swap interval (e.g. it's |
| * designed for 30FPS on a premium device and is now running on a slow device; or it's running on |
| * a 120Hz display), this algorithm may not be able to make up for this (i.e. smooth rendering at |
| * a targetted frame rate may not be possible with an application that can't render fast enough). |
| * |
| ***************************************************************************************************/ |
| |
| /** |
| * Concrete/derived class that sits on top of VK_GOOGLE_display_timing |
| */ |
| class SwappyVkGoogleDisplayTiming : public SwappyVkBase |
| { |
| public: |
| SwappyVkGoogleDisplayTiming(VkPhysicalDevice physicalDevice, |
| VkDevice device, |
| SwappyVk &swappyVk, |
| void *libVulkan) : |
| SwappyVkBase(physicalDevice, device, k16_6msec, 1, swappyVk, libVulkan) |
| { |
| initGoogExtention(); |
| } |
| virtual bool doGetRefreshCycleDuration(VkSwapchainKHR swapchain, |
| uint64_t* pRefreshDuration) override |
| { |
| VkRefreshCycleDurationGOOGLE refreshCycleDuration; |
| VkResult res = mpfnGetRefreshCycleDurationGOOGLE(mDevice, swapchain, &refreshCycleDuration); |
| if (res != VK_SUCCESS) { |
| // This should never occur, but in case it does, return 16,666,666ns: |
| mRefreshDur = k16_6msec; |
| } else { |
| mRefreshDur = refreshCycleDuration.refreshDuration; |
| } |
| |
| // TEMP CODE: LOG REFRESH DURATION AND RATE: |
| double refreshRate = mRefreshDur; |
| refreshRate = 1.0 / (refreshRate / 1000000000.0); |
| |
| ALOGD("Returning refresh duration of %" PRIu64 " nsec (approx %f Hz)", mRefreshDur, refreshRate); |
| |
| *pRefreshDuration = mRefreshDur; |
| return true; |
| } |
| virtual VkResult doQueuePresent(VkQueue queue, |
| uint32_t queueFamilyIndex, |
| const VkPresentInfoKHR* pPresentInfo) override; |
| |
| private: |
| void calculateNextDesiredPresentTime(VkSwapchainKHR swapchain); |
| void checkPastPresentTiming(VkSwapchainKHR swapchain); |
| }; |
| |
| VkResult SwappyVkGoogleDisplayTiming::doQueuePresent(VkQueue queue, |
| uint32_t queueFamilyIndex, |
| const VkPresentInfoKHR* pPresentInfo) |
| { |
| VkResult ret = VK_SUCCESS; |
| |
| calculateNextDesiredPresentTime(pPresentInfo->pSwapchains[0]); |
| |
| // Setup the new structures to pass: |
| VkPresentTimeGOOGLE *pPresentTimes = |
| reinterpret_cast<VkPresentTimeGOOGLE*>(malloc(sizeof(VkPresentTimeGOOGLE) * |
| pPresentInfo->swapchainCount)); |
| for (uint32_t i = 0 ; i < pPresentInfo->swapchainCount ; i++) { |
| pPresentTimes[i].presentID = mNextPresentID; |
| pPresentTimes[i].desiredPresentTime = mNextDesiredPresentTime; |
| } |
| mNextPresentID++; |
| |
| VkPresentTimesInfoGOOGLE presentTimesInfo = {VK_STRUCTURE_TYPE_PRESENT_TIMES_INFO_GOOGLE, |
| pPresentInfo->pNext, pPresentInfo->swapchainCount, |
| pPresentTimes}; |
| VkPresentInfoKHR replacementPresentInfo = {pPresentInfo->sType, &presentTimesInfo, |
| pPresentInfo->waitSemaphoreCount, |
| pPresentInfo->pWaitSemaphores, |
| pPresentInfo->swapchainCount, |
| pPresentInfo->pSwapchains, |
| pPresentInfo->pImageIndices, pPresentInfo->pResults}; |
| ret = mpfnQueuePresentKHR(queue, &replacementPresentInfo); |
| free(pPresentTimes); |
| return ret; |
| } |
| |
| void SwappyVkGoogleDisplayTiming::calculateNextDesiredPresentTime(VkSwapchainKHR swapchain) |
| { |
| struct timespec currTime; |
| clock_gettime(CLOCK_MONOTONIC, &currTime); |
| uint64_t currentTime = |
| ((uint64_t) currTime.tv_sec * kBillion) + (uint64_t) currTime.tv_nsec; |
| |
| |
| // Determine the desiredPresentTime: |
| if (!mNextDesiredPresentTime) { |
| mNextDesiredPresentTime = currentTime + mRefreshDur; |
| } else { |
| // Look at the timing of past presents, and potentially adjust mNextDesiredPresentTime: |
| checkPastPresentTiming(swapchain); |
| mNextDesiredPresentTime += mRefreshDur * mInterval; |
| |
| // Make sure the calculated time is not before the current time to present |
| if (mNextDesiredPresentTime < currentTime) { |
| mNextDesiredPresentTime = currentTime + mRefreshDur; |
| } |
| } |
| } |
| |
| void SwappyVkGoogleDisplayTiming::checkPastPresentTiming(VkSwapchainKHR swapchain) |
| { |
| VkResult ret = VK_SUCCESS; |
| |
| if (mNextPresentID <= mNextPresentIDToCheck) { |
| return; |
| } |
| // Check the timing of past presents to see if we need to adjust mNextDesiredPresentTime: |
| uint32_t pastPresentationTimingCount = 0; |
| VkResult err = mpfnGetPastPresentationTimingGOOGLE(mDevice, swapchain, |
| &pastPresentationTimingCount, NULL); |
| if (!pastPresentationTimingCount) { |
| return; |
| } |
| // TODO: don't allocate memory for the timestamps every time. |
| VkPastPresentationTimingGOOGLE *past = |
| reinterpret_cast<VkPastPresentationTimingGOOGLE*>( |
| malloc(sizeof(VkPastPresentationTimingGOOGLE) * |
| pastPresentationTimingCount)); |
| err = mpfnGetPastPresentationTimingGOOGLE(mDevice, swapchain, |
| &pastPresentationTimingCount, past); |
| for (uint32_t i = 0; i < pastPresentationTimingCount; i++) { |
| // Note: On Android, actualPresentTime can actually be before desiredPresentTime |
| // (which shouldn't be possible. Therefore, this must be a signed integer. |
| int64_t amountEarlyBy = |
| (int64_t) past[i].actualPresentTime - (int64_t)past[i].desiredPresentTime; |
| if (amountEarlyBy < kTooCloseToVsyncBoundary) { |
| // We're getting too close to vsync. Nudge the next present back |
| // towards/in the boundaries, and check back after a few more presents: |
| mNextDesiredPresentTime -= kNudgeWithinVsyncBoundaries; |
| mNextPresentIDToCheck = mNextPresentID + 7; |
| break; |
| } |
| if (amountEarlyBy > kTooFarAwayFromVsyncBoundary) { |
| // We're getting too far away from vsync. Nudge the next present back |
| // towards/in the boundaries, and check back after a few more presents: |
| mNextDesiredPresentTime += kNudgeWithinVsyncBoundaries; |
| mNextPresentIDToCheck = mNextPresentID + 7; |
| break; |
| } |
| } |
| free(past); |
| } |
| |
| /** |
| * Concrete/derived class that sits on top of VK_GOOGLE_display_timing |
| */ |
| class SwappyVkGoogleDisplayTimingAndroid : public SwappyVkGoogleDisplayTiming |
| { |
| public: |
| SwappyVkGoogleDisplayTimingAndroid(VkPhysicalDevice physicalDevice, |
| VkDevice device, |
| SwappyVk &swappyVk, |
| void *libVulkan) : |
| SwappyVkGoogleDisplayTiming(physicalDevice, device, swappyVk,libVulkan) { |
| startChoreographerThread(); |
| } |
| |
| ~SwappyVkGoogleDisplayTimingAndroid() { |
| stopChoreographerThread(); |
| destroyVkSyncObjects(); |
| } |
| |
| virtual bool doGetRefreshCycleDuration(VkSwapchainKHR swapchain, |
| uint64_t* pRefreshDuration) override { |
| bool res = SwappyVkGoogleDisplayTiming::doGetRefreshCycleDuration(swapchain, pRefreshDuration); |
| return res; |
| } |
| |
| |
| |
| virtual VkResult doQueuePresent(VkQueue queue, |
| uint32_t queueFamilyIndex, |
| const VkPresentInfoKHR *pPresentInfo) override; |
| |
| private: |
| VkResult initializeVkSyncObjects(VkQueue queue, uint32_t queueFamilyIndex); |
| void destroyVkSyncObjects(); |
| |
| void waitForFenceChoreographer(VkQueue queue); |
| |
| struct VkSync { |
| VkFence fence; |
| VkSemaphore semaphore; |
| VkCommandBuffer command; |
| VkEvent event; |
| }; |
| |
| std::map<VkQueue, std::list<VkSync>> mFreeSync; |
| std::map<VkQueue, std::list<VkSync>> mPendingSync; |
| std::map<VkQueue, VkCommandPool> mCommandPool; |
| |
| static constexpr int MAX_PENDING_FENCES = 1; |
| }; |
| |
| VkResult SwappyVkGoogleDisplayTimingAndroid::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, |
| .queueFamilyIndex = queueFamilyIndex, |
| .flags = 0, |
| }; |
| |
| VkResult res = vkCreateCommandPool(mDevice, &cmd_pool_info, NULL, &mCommandPool[queue]); |
| if (res) { |
| ALOGE("vkCreateCommandPool failed %d", 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 = 0}; |
| |
| res = vkCreateFence(mDevice, &fence_ci, NULL, &sync.fence); |
| if (res) { |
| ALOGE("failed to create fence: %d", 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: %d", res); |
| return res; |
| } |
| |
| |
| res = vkAllocateCommandBuffers(mDevice, &present_cmd_info, &sync.command); |
| if (res) { |
| ALOGE("vkAllocateCommandBuffers failed %d", 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 %d", 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 %d", res); |
| return res; |
| } |
| |
| vkCmdSetEvent(sync.command, sync.event, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT); |
| |
| res = vkEndCommandBuffer(sync.command); |
| if (res) { |
| ALOGE("vkCreateEvent failed %d", res); |
| return res; |
| } |
| |
| mFreeSync[queue].push_back(sync); |
| } |
| |
| return VK_SUCCESS; |
| } |
| |
| void SwappyVkGoogleDisplayTimingAndroid::destroyVkSyncObjects() { |
| for (auto it = mPendingSync.begin(); it != mPendingSync.end(); it++) { |
| while (mPendingSync[it->first].size() > 0) { |
| VkSync sync = mPendingSync[it->first].front(); |
| mPendingSync[it->first].pop_front(); |
| vkWaitForFences(mDevice, 1, &sync.fence, VK_TRUE, UINT64_MAX); |
| vkResetFences(mDevice, 1, &sync.fence); |
| mFreeSync[it->first].push_back(sync); |
| } |
| |
| while (mFreeSync[it->first].size() > 0) { |
| VkSync sync = mFreeSync[it->first].front(); |
| mFreeSync[it->first].pop_front(); |
| vkFreeCommandBuffers(mDevice, mCommandPool[it->first], 1, &sync.command); |
| vkDestroyEvent(mDevice, sync.event, NULL); |
| vkDestroySemaphore(mDevice, sync.semaphore, NULL); |
| vkDestroyFence(mDevice, sync.fence, NULL); |
| } |
| |
| vkDestroyCommandPool(mDevice, mCommandPool[it->first], NULL); |
| } |
| } |
| |
| void SwappyVkGoogleDisplayTimingAndroid::waitForFenceChoreographer(VkQueue queue) |
| { |
| std::unique_lock<std::mutex> lock(mWaitingMutex); |
| VkSync sync = mPendingSync[queue].front(); |
| mPendingSync[queue].pop_front(); |
| mWaitingCondition.wait(lock, [&]() { |
| if (vkWaitForFences(mDevice, 1, &sync.fence, VK_TRUE, 0) == VK_TIMEOUT) { |
| AChoreographer_postFrameCallbackDelayed(mChoreographer, frameCallback, this, 1); |
| return false; |
| } |
| return true; |
| }); |
| |
| vkResetFences(mDevice, 1, &sync.fence); |
| mFreeSync[queue].push_back(sync); |
| } |
| |
| VkResult SwappyVkGoogleDisplayTimingAndroid::doQueuePresent(VkQueue queue, |
| uint32_t queueFamilyIndex, |
| const VkPresentInfoKHR* pPresentInfo) |
| { |
| VkResult ret = initializeVkSyncObjects(queue, queueFamilyIndex); |
| if (ret) { |
| return ret; |
| } |
| |
| struct timespec currTime; |
| clock_gettime(CLOCK_MONOTONIC, &currTime); |
| uint64_t currentTime = |
| ((uint64_t) currTime.tv_sec * kBillion) + (uint64_t) currTime.tv_nsec; |
| |
| // do we have something in the queue ? |
| if (mNextDesiredPresentTime > currentTime) { |
| std::unique_lock<std::mutex> lock(mWaitingMutex); |
| long target = mFrameID + mInterval; |
| mWaitingCondition.wait(lock, [&]() { |
| if (mFrameID < target) { |
| // wait for the next frame as this frame is too soon |
| AChoreographer_postFrameCallbackDelayed(mChoreographer, frameCallback, this, 1); |
| return false; |
| } |
| return true; |
| }); |
| } |
| |
| if (mPendingSync[queue].size() >= MAX_PENDING_FENCES) { |
| waitForFenceChoreographer(queue); |
| } |
| |
| clock_gettime(CLOCK_MONOTONIC, &currTime); |
| currentTime = |
| ((uint64_t) currTime.tv_sec * kBillion) + (uint64_t) currTime.tv_nsec; |
| mNextDesiredPresentTime = currentTime + mRefreshDur * mInterval; |
| |
| // Setup the new structures to pass: |
| VkPresentTimeGOOGLE pPresentTimes[pPresentInfo->swapchainCount]; |
| for (uint32_t i = 0 ; i < pPresentInfo->swapchainCount ; i++) { |
| pPresentTimes[i].presentID = mNextPresentID; |
| pPresentTimes[i].desiredPresentTime = mNextDesiredPresentTime; |
| } |
| mNextPresentID++; |
| |
| VkSync sync = mFreeSync[queue].front(); |
| mFreeSync[queue].pop_front(); |
| mPendingSync[queue].push_back(sync); |
| |
| 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; |
| ret = vkQueueSubmit(queue, 1, &submit_info, sync.fence); |
| if (ret) { |
| ALOGE("Failed to vkQueueSubmit %d", ret); |
| return ret; |
| } |
| |
| VkPresentTimesInfoGOOGLE presentTimesInfo = {VK_STRUCTURE_TYPE_PRESENT_TIMES_INFO_GOOGLE, |
| pPresentInfo->pNext, pPresentInfo->swapchainCount, |
| pPresentTimes}; |
| VkPresentInfoKHR replacementPresentInfo = {pPresentInfo->sType, &presentTimesInfo, |
| 1, |
| &sync.semaphore, |
| pPresentInfo->swapchainCount, |
| pPresentInfo->pSwapchains, |
| pPresentInfo->pImageIndices, pPresentInfo->pResults}; |
| ret = mpfnQueuePresentKHR(queue, &replacementPresentInfo); |
| |
| return ret; |
| } |
| |
| /*************************************************************************************************** |
| * |
| * Per-Device concrete/derived class for the "Android fallback" path (uses |
| * Choreographer to try to get presents to occur at the desired time). |
| * |
| ***************************************************************************************************/ |
| |
| /** |
| * Concrete/derived class that sits on top of the Vulkan API |
| */ |
| #ifdef ANDROID |
| class SwappyVkAndroidFallback : public SwappyVkBase |
| { |
| public: |
| SwappyVkAndroidFallback(VkPhysicalDevice physicalDevice, |
| VkDevice device, |
| SwappyVk &swappyVk, |
| void *libVulkan) : |
| SwappyVkBase(physicalDevice, device, 0, 1, swappyVk, libVulkan) { |
| startChoreographerThread(); |
| } |
| |
| ~SwappyVkAndroidFallback() { |
| stopChoreographerThread(); |
| } |
| |
| virtual bool doGetRefreshCycleDuration(VkSwapchainKHR swapchain, |
| uint64_t* pRefreshDuration) override |
| { |
| std::unique_lock<std::mutex> lock(mWaitingMutex); |
| mWaitingCondition.wait(lock, [&]() { |
| if (mRefreshDur == 0) { |
| AChoreographer_postFrameCallbackDelayed(mChoreographer, frameCallback, this, 1); |
| return false; |
| } |
| return true; |
| }); |
| |
| *pRefreshDuration = mRefreshDur; |
| |
| double refreshRate = mRefreshDur; |
| refreshRate = 1.0 / (refreshRate / 1000000000.0); |
| ALOGI("Returning refresh duration of %" PRIu64 " nsec (approx %f Hz)", mRefreshDur, refreshRate); |
| return true; |
| } |
| |
| virtual VkResult doQueuePresent(VkQueue queue, |
| uint32_t queueFamilyIndex, |
| const VkPresentInfoKHR* pPresentInfo) override |
| { |
| { |
| const long target = mFrameID + mInterval; |
| std::unique_lock<std::mutex> lock(mWaitingMutex); |
| |
| |
| mWaitingCondition.wait(lock, [&]() { |
| if (mFrameID < target) { |
| // wait for the next frame as this frame is too soon |
| AChoreographer_postFrameCallbackDelayed(mChoreographer, frameCallback, this, 1); |
| return false; |
| } |
| return true; |
| }); |
| } |
| return mpfnQueuePresentKHR(queue, pPresentInfo); |
| } |
| }; |
| #endif |
| |
| /*************************************************************************************************** |
| * |
| * Per-Device concrete/derived class for the "Vulkan fallback" path (i.e. no API/OS timing support; |
| * just generic Vulkan) |
| * |
| ***************************************************************************************************/ |
| |
| /** |
| * Concrete/derived class that sits on top of the Vulkan API |
| */ |
| class SwappyVkVulkanFallback : public SwappyVkBase |
| { |
| public: |
| SwappyVkVulkanFallback(VkPhysicalDevice physicalDevice, |
| VkDevice device, |
| SwappyVk &swappyVk, |
| void *libVulkan) : |
| SwappyVkBase(physicalDevice, device, k16_6msec, 1, swappyVk, libVulkan) {} |
| virtual bool doGetRefreshCycleDuration(VkSwapchainKHR swapchain, |
| uint64_t* pRefreshDuration) override |
| { |
| *pRefreshDuration = mRefreshDur; |
| return true; |
| } |
| virtual VkResult doQueuePresent(VkQueue queue, |
| uint32_t queueFamilyIndex, |
| const VkPresentInfoKHR* pPresentInfo) override |
| { |
| return mpfnQueuePresentKHR(queue, pPresentInfo); |
| } |
| }; |
| |
| |
| |
| |
| /*************************************************************************************************** |
| * |
| * Singleton class that provides the high-level implementation of the Swappy entrypoints. |
| * |
| ***************************************************************************************************/ |
| /** |
| * Singleton class that provides the high-level implementation of the Swappy entrypoints. |
| * |
| * This class determines which low-level implementation to use for each physical |
| * device, and then calls that class's do-method for the entrypoint. |
| */ |
| class SwappyVk |
| { |
| public: |
| static SwappyVk& getInstance() |
| { |
| static SwappyVk instance; |
| return instance; |
| } |
| ~SwappyVk() {} |
| |
| void swappyVkDetermineDeviceExtensions(VkPhysicalDevice physicalDevice, |
| uint32_t availableExtensionCount, |
| VkExtensionProperties* pAvailableExtensions, |
| uint32_t* pRequiredExtensionCount, |
| char** pRequiredExtensions); |
| void SetQueueFamiliyIndex(VkDevice device, |
| VkQueue queue, |
| uint32_t queueFamilyIndex); |
| bool GetRefreshCycleDuration(VkPhysicalDevice physicalDevice, |
| VkDevice device, |
| VkSwapchainKHR swapchain, |
| uint64_t* pRefreshDuration); |
| void SetSwapInterval(VkDevice device, |
| VkSwapchainKHR swapchain, |
| uint32_t interval); |
| VkResult QueuePresent(VkQueue queue, |
| const VkPresentInfoKHR* pPresentInfo); |
| void DestroySwapchain(VkDevice device, |
| VkSwapchainKHR swapchain); |
| |
| private: |
| std::map<VkPhysicalDevice, bool> doesPhysicalDeviceHaveGoogleDisplayTiming; |
| std::map<VkDevice, std::shared_ptr<SwappyVkBase>> perDeviceImplementation; |
| std::map<VkSwapchainKHR, std::shared_ptr<SwappyVkBase>> perSwapchainImplementation; |
| |
| struct QueueFamiliyIndex { |
| VkDevice device; |
| uint32_t queueFamiliyIndex; |
| }; |
| std::map<VkQueue, QueueFamiliyIndex> perQueueFamiliyIndex; |
| |
| void *mLibVulkan = nullptr; |
| |
| private: |
| SwappyVk() {} // Need to implement this constructor |
| SwappyVk(SwappyVk const&); // Don't implement a copy constructor--no copies |
| void operator=(SwappyVk const&); // Don't implement--no copies |
| }; |
| |
| /** |
| * Generic/Singleton implementation of swappyVkDetermineDeviceExtensions. |
| */ |
| void SwappyVk::swappyVkDetermineDeviceExtensions( |
| VkPhysicalDevice physicalDevice, |
| uint32_t availableExtensionCount, |
| VkExtensionProperties* pAvailableExtensions, |
| uint32_t* pRequiredExtensionCount, |
| char** pRequiredExtensions) |
| { |
| // 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; |
| } |
| } |
| } |
| } |
| } |
| |
| void SwappyVk::SetQueueFamiliyIndex(VkDevice device, |
| VkQueue queue, |
| uint32_t queueFamilyIndex) |
| { |
| perQueueFamiliyIndex[queue] = {device, queueFamilyIndex}; |
| } |
| |
| |
| /** |
| * Generic/Singleton implementation of swappyVkGetRefreshCycleDuration. |
| */ |
| bool SwappyVk::GetRefreshCycleDuration(VkPhysicalDevice physicalDevice, |
| VkDevice device, |
| VkSwapchainKHR swapchain, |
| uint64_t* pRefreshDuration) |
| { |
| auto& pImplementation = perDeviceImplementation[device]; |
| if (!pImplementation) { |
| // We have not seen this device yet. |
| if (!mLibVulkan) { |
| // This is the first time we've been called--initialize function pointers: |
| mLibVulkan = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL); |
| if (!mLibVulkan) |
| { |
| // If Vulkan doesn't exist, bail-out early: |
| return false; |
| } |
| } |
| |
| // 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]) { |
| #ifdef ANDROID |
| pImplementation = std::make_shared<SwappyVkGoogleDisplayTimingAndroid> |
| (physicalDevice, device, getInstance(), mLibVulkan); |
| ALOGV("SwappyVk initialized for VkDevice %p using VK_GOOGLE_display_timing on Android", device); |
| #else |
| // Instantiate the class that sits on top of VK_GOOGLE_display_timing |
| pImplementation = std::make_shared<SwappyVkGoogleDisplayTiming> |
| (physicalDevice, device, getInstance(), mLibVulkan); |
| ALOGV("SwappyVk initialized for VkDevice %p using VK_GOOGLE_display_timing", device); |
| #endif |
| } else { |
| // Instantiate the class that sits on top of the basic Vulkan APIs |
| #ifdef ANDROID |
| pImplementation = std::make_shared<SwappyVkAndroidFallback> |
| (physicalDevice, device, getInstance(), mLibVulkan); |
| ALOGV("SwappyVk initialized for VkDevice %p using Android fallback", device); |
| #else // ANDROID |
| pImplementation = std::make_shared<SwappyVkVulkanFallback> |
| (physicalDevice, device, getInstance(), mLibVulkan); |
| ALOGV("SwappyVk initialized for VkDevice %p using Vulkan-only fallback", device); |
| #endif // ANDROID |
| } |
| |
| if (!pImplementation) { |
| // This shouldn't happen, but if it does, something is really wrong. |
| return false; |
| } |
| } |
| |
| // Cache the per-swapchain pointer to the derived class: |
| perSwapchainImplementation[swapchain] = pImplementation; |
| |
| // Now, call that derived class to get the refresh duration to return |
| return pImplementation->doGetRefreshCycleDuration(swapchain, pRefreshDuration); |
| } |
| |
| |
| /** |
| * Generic/Singleton implementation of swappyVkSetSwapInterval. |
| */ |
| void SwappyVk::SetSwapInterval(VkDevice device, |
| VkSwapchainKHR swapchain, |
| uint32_t interval) |
| { |
| auto& pImplementation = perDeviceImplementation[device]; |
| if (!pImplementation) { |
| return; |
| } |
| pImplementation->doSetSwapInterval(swapchain, interval); |
| } |
| |
| |
| /** |
| * Generic/Singleton implementation of swappyVkQueuePresent. |
| */ |
| VkResult SwappyVk::QueuePresent(VkQueue queue, |
| const VkPresentInfoKHR* pPresentInfo) |
| { |
| if (perQueueFamiliyIndex.find(queue) == perQueueFamiliyIndex.end()) { |
| ALOGE("Unknown queue %p. Did you call SwappyVkSetQueueFamiliyIndex ?", 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, |
| perQueueFamiliyIndex[queue].queueFamiliyIndex, |
| 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 it = perQueueFamiliyIndex.begin(); |
| while (it != perQueueFamiliyIndex.end()) { |
| if (it->second.device == device) { |
| it = perQueueFamiliyIndex.erase(it); |
| } else { |
| ++it; |
| } |
| } |
| |
| perDeviceImplementation[device] = nullptr; |
| perSwapchainImplementation[swapchain] = nullptr; |
| } |
| |
| |
| /*************************************************************************************************** |
| * |
| * API ENTRYPOINTS |
| * |
| ***************************************************************************************************/ |
| |
| extern "C" { |
| |
| void swappyVkDetermineDeviceExtensions( |
| VkPhysicalDevice physicalDevice, |
| uint32_t availableExtensionCount, |
| VkExtensionProperties* pAvailableExtensions, |
| uint32_t* pRequiredExtensionCount, |
| char** pRequiredExtensions) |
| { |
| ATRACE_CALL(); |
| SwappyVk& swappy = SwappyVk::getInstance(); |
| swappy.swappyVkDetermineDeviceExtensions(physicalDevice, |
| availableExtensionCount, pAvailableExtensions, |
| pRequiredExtensionCount, pRequiredExtensions); |
| } |
| |
| void SwappyVkSetQueueFamiliyIndex( |
| VkDevice device, |
| VkQueue queue, |
| uint32_t queueFamilyIndex) |
| { |
| ATRACE_CALL(); |
| SwappyVk& swappy = SwappyVk::getInstance(); |
| swappy.SetQueueFamiliyIndex(device, queue, queueFamilyIndex); |
| } |
| |
| bool swappyVkGetRefreshCycleDuration( |
| VkPhysicalDevice physicalDevice, |
| VkDevice device, |
| VkSwapchainKHR swapchain, |
| uint64_t* pRefreshDuration) |
| { |
| ATRACE_CALL(); |
| SwappyVk& swappy = SwappyVk::getInstance(); |
| return swappy.GetRefreshCycleDuration(physicalDevice, device, swapchain, pRefreshDuration); |
| } |
| |
| void swappyVkSetSwapInterval( |
| VkDevice device, |
| VkSwapchainKHR swapchain, |
| uint32_t interval) |
| { |
| ATRACE_CALL(); |
| SwappyVk& swappy = SwappyVk::getInstance(); |
| swappy.SetSwapInterval(device, swapchain, interval); |
| } |
| |
| VkResult swappyVkQueuePresent( |
| VkQueue queue, |
| const VkPresentInfoKHR* pPresentInfo) |
| { |
| ATRACE_CALL(); |
| SwappyVk& swappy = SwappyVk::getInstance(); |
| return swappy.QueuePresent(queue, pPresentInfo); |
| } |
| |
| void SwappyVkDestroySwapchain( |
| VkDevice device, |
| VkSwapchainKHR swapchain) |
| { |
| ATRACE_CALL(); |
| SwappyVk& swappy = SwappyVk::getInstance(); |
| swappy.DestroySwapchain(device, swapchain); |
| } |
| |
| } // extern "C" |