Home | History | Annotate | Download | only in swappyVk
      1 /*
      2  * Copyright 2018 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #ifdef ANDROID
     18 #define SWAPPYVK_USE_WRAPPER
     19 #endif
     20 #include <swappyVk/SwappyVk.h>
     21 
     22 #include <map>
     23 #include <condition_variable>
     24 #include <cstring>
     25 #include <unistd.h>
     26 
     27 #include <dlfcn.h>
     28 #include <cstdlib>
     29 
     30 #include <inttypes.h>
     31 
     32 #ifdef ANDROID
     33 #include <mutex>
     34 #include <pthread.h>
     35 #include <list>
     36 #include <android/looper.h>
     37 #include <android/log.h>
     38 #include "Trace.h"
     39 #include "ChoreographerShim.h"
     40 
     41 #define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, "SwappyVk", __VA_ARGS__)
     42 #define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, "SwappyVk", __VA_ARGS__)
     43 #define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, "SwappyVk", __VA_ARGS__)
     44 #define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "SwappyVk", __VA_ARGS__)
     45 #define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, "SwappyVk", __VA_ARGS__)
     46 #else
     47 #define ATRACE_CALL() ((void)0)
     48 #define ALOGE(...)    ((void)0)
     49 #define ALOGW(...)    ((void)0)
     50 #define ALOGD(...)    ((void)0)
     51 #define ALOGV(...)    ((void)0)
     52 #endif
     53 
     54 
     55 constexpr uint32_t kThousand = 1000;
     56 constexpr uint32_t kMillion  = 1000000;
     57 constexpr uint32_t kBillion  = 1000000000;
     58 constexpr uint32_t k16_6msec = 16666666;
     59 
     60 constexpr uint32_t kTooCloseToVsyncBoundary     = 3000000;
     61 constexpr uint32_t kTooFarAwayFromVsyncBoundary = 7000000;
     62 constexpr uint32_t kNudgeWithinVsyncBoundaries  = 2000000;
     63 
     64 // Note: The API functions is at the botton of the file.  Those functions call methods of the
     65 // singleton SwappyVk class.  Those methods call virtual methods of the abstract SwappyVkBase
     66 // class, which is actually implemented by one of the derived/concrete classes:
     67 //
     68 // - SwappyVkGoogleDisplayTiming
     69 // - SwappyVkVulkanFallback
     70 // - SwappyVkAndroidFallback
     71 
     72 // Forward declarations:
     73 class SwappyVk;
     74 
     75 // AChoreographer is supported from API 24. To allow compilation for minSDK < 24
     76 // and still use AChoreographer for SDK >= 24 we need runtime support to call
     77 // AChoreographer APIs.
     78 
     79 using PFN_AChoreographer_getInstance = AChoreographer* (*)();
     80 
     81 using PFN_AChoreographer_postFrameCallback = void (*)(AChoreographer* choreographer,
     82                                                   AChoreographer_frameCallback callback,
     83                                                   void* data);
     84 
     85 using PFN_AChoreographer_postFrameCallbackDelayed = void (*)(AChoreographer* choreographer,
     86                                                         AChoreographer_frameCallback callback,
     87                                                         void* data,
     88                                                         long delayMillis);
     89 
     90 /***************************************************************************************************
     91  *
     92  * Per-Device abstract base class.
     93  *
     94  ***************************************************************************************************/
     95 
     96 /**
     97  * Abstract base class that calls the Vulkan API.
     98  *
     99  * It is expected that one concrete class will be instantiated per VkDevice, and that all
    100  * VkSwapchainKHR's for a given VkDevice will share the same instance.
    101  *
    102  * Base class members are used by the derived classes to unify the behavior across implementaitons:
    103  *  @mThread - Thread used for getting Choreographer events.
    104  *  @mTreadRunning - Used to signal the tread to exit
    105  *  @mNextPresentID - unique ID for frame presentation.
    106  *  @mNextDesiredPresentTime - Holds the time in nanoseconds for the next frame to be presented.
    107  *  @mNextPresentIDToCheck - Used to determine whether presentation time needs to be adjusted.
    108  *  @mFrameID - Keeps track of how many Choreographer callbacks received.
    109  *  @mLastframeTimeNanos - Holds the last frame time reported by Choreographer.
    110  *  @mSumRefreshTime - Used together with @mSamples to calculate refresh rate based on Choreographer.
    111  */
    112 class SwappyVkBase
    113 {
    114 public:
    115     SwappyVkBase(VkPhysicalDevice physicalDevice,
    116                  VkDevice         device,
    117                  uint64_t         refreshDur,
    118                  uint32_t         interval,
    119                  SwappyVk         &swappyVk,
    120                  void             *libVulkan) :
    121             mPhysicalDevice(physicalDevice), mDevice(device), mRefreshDur(refreshDur),
    122             mInterval(interval), mSwappyVk(swappyVk), mLibVulkan(libVulkan),
    123             mInitialized(false)
    124     {
    125 #ifdef ANDROID
    126         InitVulkan();
    127 #endif
    128         mpfnGetDeviceProcAddr =
    129                 reinterpret_cast<PFN_vkGetDeviceProcAddr>(
    130                     dlsym(mLibVulkan, "vkGetDeviceProcAddr"));
    131         mpfnQueuePresentKHR =
    132                 reinterpret_cast<PFN_vkQueuePresentKHR>(
    133                     mpfnGetDeviceProcAddr(mDevice, "vkQueuePresentKHR"));
    134 
    135         mLibAndroid = dlopen("libandroid.so", RTLD_NOW | RTLD_LOCAL);
    136         if (mLibAndroid == nullptr) {
    137             ALOGE("FATAL: cannot open libandroid.so: %s", strerror(errno));
    138             abort();
    139         }
    140 
    141         mAChoreographer_getInstance =
    142                 reinterpret_cast<PFN_AChoreographer_getInstance >(
    143                     dlsym(mLibAndroid, "AChoreographer_getInstance"));
    144 
    145         mAChoreographer_postFrameCallback =
    146                 reinterpret_cast<PFN_AChoreographer_postFrameCallback >(
    147                         dlsym(mLibAndroid, "AChoreographer_postFrameCallback"));
    148 
    149         mAChoreographer_postFrameCallbackDelayed =
    150                 reinterpret_cast<PFN_AChoreographer_postFrameCallbackDelayed >(
    151                         dlsym(mLibAndroid, "AChoreographer_postFrameCallbackDelayed"));
    152         if (!mAChoreographer_getInstance ||
    153             !mAChoreographer_postFrameCallback ||
    154             !mAChoreographer_postFrameCallbackDelayed) {
    155             ALOGE("FATAL: cannot get AChoreographer symbols");
    156             abort();
    157         }
    158     }
    159     virtual ~SwappyVkBase() {
    160         if(mLibAndroid)
    161             dlclose(mLibAndroid);
    162     }
    163     virtual bool doGetRefreshCycleDuration(VkSwapchainKHR swapchain,
    164                                            uint64_t*      pRefreshDuration) = 0;
    165     void doSetSwapInterval(VkSwapchainKHR swapchain,
    166                            uint32_t       interval)
    167     {
    168         mInterval = interval;
    169     }
    170     virtual VkResult doQueuePresent(VkQueue                 queue,
    171                                     uint32_t                queueFamilyIndex,
    172                                     const VkPresentInfoKHR* pPresentInfo) = 0;
    173 protected:
    174     VkPhysicalDevice mPhysicalDevice;
    175     VkDevice         mDevice;
    176     uint64_t         mRefreshDur;
    177     uint32_t         mInterval;
    178     SwappyVk         &mSwappyVk;
    179     void             *mLibVulkan;
    180     bool             mInitialized;
    181     pthread_t mThread = 0;
    182     ALooper *mLooper = nullptr;
    183     bool mTreadRunning = false;
    184     AChoreographer *mChoreographer = nullptr;
    185     std::mutex mWaitingMutex;
    186     std::condition_variable mWaitingCondition;
    187     uint32_t mNextPresentID = 0;
    188     uint64_t mNextDesiredPresentTime = 0;
    189     uint32_t mNextPresentIDToCheck = 2;
    190 
    191     PFN_vkGetDeviceProcAddr mpfnGetDeviceProcAddr = nullptr;
    192     PFN_vkQueuePresentKHR   mpfnQueuePresentKHR = nullptr;
    193     PFN_vkGetRefreshCycleDurationGOOGLE mpfnGetRefreshCycleDurationGOOGLE = nullptr;
    194     PFN_vkGetPastPresentationTimingGOOGLE mpfnGetPastPresentationTimingGOOGLE = nullptr;
    195 
    196     void *mLibAndroid = nullptr;
    197     PFN_AChoreographer_getInstance mAChoreographer_getInstance = nullptr;
    198     PFN_AChoreographer_postFrameCallback mAChoreographer_postFrameCallback = nullptr;
    199     PFN_AChoreographer_postFrameCallbackDelayed mAChoreographer_postFrameCallbackDelayed = nullptr;
    200 
    201     long mFrameID = 0;
    202     long mTargetFrameID = 0;
    203     uint64_t mLastframeTimeNanos = 0;
    204     long mSumRefreshTime = 0;
    205     long mSamples = 0;
    206     long mCallbacksBeforeIdle = 0;
    207 
    208     static constexpr int MAX_SAMPLES = 5;
    209     static constexpr int MAX_CALLBACKS_BEFORE_IDLE = 10;
    210 
    211     void initGoogExtention()
    212     {
    213         mpfnGetRefreshCycleDurationGOOGLE =
    214                 reinterpret_cast<PFN_vkGetRefreshCycleDurationGOOGLE>(
    215                         mpfnGetDeviceProcAddr(mDevice, "vkGetRefreshCycleDurationGOOGLE"));
    216         mpfnGetPastPresentationTimingGOOGLE =
    217                 reinterpret_cast<PFN_vkGetPastPresentationTimingGOOGLE>(
    218                         mpfnGetDeviceProcAddr(mDevice, "vkGetPastPresentationTimingGOOGLE"));
    219     }
    220 
    221     void startChoreographerThread();
    222     void stopChoreographerThread();
    223     static void *looperThreadWrapper(void *data);
    224     void *looperThread();
    225     static void frameCallback(long frameTimeNanos, void *data);
    226     void onDisplayRefresh(long frameTimeNanos);
    227     void calcRefreshRate(uint64_t currentTime);
    228     void postChoreographerCallback();
    229 };
    230 
    231 void SwappyVkBase::startChoreographerThread() {
    232     std::unique_lock<std::mutex> lock(mWaitingMutex);
    233     // create a new ALooper thread to get Choreographer events
    234     mTreadRunning = true;
    235     pthread_create(&mThread, NULL, looperThreadWrapper, this);
    236     mWaitingCondition.wait(lock, [&]() { return mChoreographer != nullptr; });
    237 }
    238 
    239 void SwappyVkBase::stopChoreographerThread() {
    240     if (mLooper) {
    241         ALooper_acquire(mLooper);
    242         mTreadRunning = false;
    243         ALooper_wake(mLooper);
    244         ALooper_release(mLooper);
    245         pthread_join(mThread, NULL);
    246     }
    247 }
    248 
    249 void *SwappyVkBase::looperThreadWrapper(void *data) {
    250     SwappyVkBase *me = reinterpret_cast<SwappyVkBase *>(data);
    251     return me->looperThread();
    252 }
    253 
    254 void *SwappyVkBase::looperThread() {
    255     int outFd, outEvents;
    256     void *outData;
    257 
    258     mLooper = ALooper_prepare(0);
    259     if (!mLooper) {
    260         ALOGE("ALooper_prepare failed");
    261         return NULL;
    262     }
    263 
    264     mChoreographer = mAChoreographer_getInstance();
    265     if (!mChoreographer) {
    266         ALOGE("AChoreographer_getInstance failed");
    267         return NULL;
    268     }
    269     mWaitingCondition.notify_all();
    270 
    271     while (mTreadRunning) {
    272         ALooper_pollAll(-1, &outFd, &outEvents, &outData);
    273     }
    274 
    275     return NULL;
    276 }
    277 
    278 void SwappyVkBase::frameCallback(long frameTimeNanos, void *data) {
    279     SwappyVkBase *me = reinterpret_cast<SwappyVkBase *>(data);
    280     me->onDisplayRefresh(frameTimeNanos);
    281 }
    282 
    283 void SwappyVkBase::onDisplayRefresh(long frameTimeNanos) {
    284     std::lock_guard<std::mutex> lock(mWaitingMutex);
    285     struct timespec currTime;
    286     clock_gettime(CLOCK_MONOTONIC, &currTime);
    287     uint64_t currentTime =
    288             ((uint64_t) currTime.tv_sec * kBillion) + (uint64_t) currTime.tv_nsec;
    289 
    290     calcRefreshRate(currentTime);
    291     mLastframeTimeNanos = currentTime;
    292     mFrameID++;
    293     mWaitingCondition.notify_all();
    294 
    295     // queue the next frame callback
    296     if (mCallbacksBeforeIdle > 0) {
    297         mCallbacksBeforeIdle--;
    298         mAChoreographer_postFrameCallbackDelayed(mChoreographer, frameCallback, this, 1);
    299     }
    300 }
    301 
    302 void SwappyVkBase::postChoreographerCallback() {
    303     if (mCallbacksBeforeIdle == 0) {
    304         mAChoreographer_postFrameCallbackDelayed(mChoreographer, frameCallback, this, 1);
    305     }
    306     mCallbacksBeforeIdle = MAX_CALLBACKS_BEFORE_IDLE;
    307 }
    308 
    309 void SwappyVkBase::calcRefreshRate(uint64_t currentTime) {
    310     long refresh_nano = currentTime - mLastframeTimeNanos;
    311 
    312     if (mRefreshDur != 0 || mLastframeTimeNanos == 0) {
    313         return;
    314     }
    315 
    316     mSumRefreshTime += refresh_nano;
    317     mSamples++;
    318 
    319     if (mSamples == MAX_SAMPLES) {
    320         mRefreshDur = mSumRefreshTime / mSamples;
    321     }
    322 }
    323 
    324 
    325 /***************************************************************************************************
    326  *
    327  * Per-Device concrete/derived class for using VK_GOOGLE_display_timing.
    328  *
    329  * This class uses the VK_GOOGLE_display_timing in order to present frames at a muliple (the "swap
    330  * interval") of a fixed refresh-cycle duration (i.e. the time between successive vsync's).
    331  *
    332  * In order to reduce complexity, some simplifying assumptions are made:
    333  *
    334  * - We assume a fixed refresh-rate (FRR) display that's between 60 Hz and 120 Hz.
    335  *
    336  * - While Vulkan allows applications to create and use multiple VkSwapchainKHR's per VkDevice, and
    337  *   to re-create VkSwapchainKHR's, we assume that the application uses a single VkSwapchainKHR,
    338  *   and never re-creates it.
    339  *
    340  * - The values reported back by the VK_GOOGLE_display_timing extension (which comes from
    341  *   lower-level Android interfaces) are not precise, and that values can drift over time.  For
    342  *   example, the refresh-cycle duration for a 60 Hz display should be 16,666,666 nsec; but the
    343  *   value reported back by the extension won't be precisely this.  Also, the differences betweeen
    344  *   the times of two successive frames won't be an exact multiple of 16,666,666 nsec.  This can
    345  *   make it difficult to precisely predict when a future vsync will be (it can appear to drift
    346  *   overtime).  Therefore, we try to give a desiredPresentTime for each image that is between 3
    347  *   and 7 msec before vsync.  We look at the actualPresentTime for previously-presented images,
    348  *   and nudge the future desiredPresentTime back within those 3-7 msec boundaries.
    349  *
    350  * - There can be a few frames of latency between when an image is presented and when the
    351  *   actualPresentTime is available for that image.  Therefore, we initially just pick times based
    352  *   upon CLOCK_MONOTONIC (which is the time domain for VK_GOOGLE_display_timing).  After we get
    353  *   past-present times, we nudge the desiredPresentTime, we wait for a few presents before looking
    354  *   again to see whether we need to nudge again.
    355  *
    356  * - If, for some reason, an application can't keep up with its chosen swap interval (e.g. it's
    357  *   designed for 30FPS on a premium device and is now running on a slow device; or it's running on
    358  *   a 120Hz display), this algorithm may not be able to make up for this (i.e. smooth rendering at
    359  *   a targetted frame rate may not be possible with an application that can't render fast enough).
    360  *
    361  ***************************************************************************************************/
    362 
    363 /**
    364  * Concrete/derived class that sits on top of VK_GOOGLE_display_timing
    365  */
    366 class SwappyVkGoogleDisplayTiming : public SwappyVkBase
    367 {
    368 public:
    369     SwappyVkGoogleDisplayTiming(VkPhysicalDevice physicalDevice,
    370                                 VkDevice         device,
    371                                 SwappyVk         &swappyVk,
    372                                 void             *libVulkan) :
    373             SwappyVkBase(physicalDevice, device, k16_6msec, 1, swappyVk, libVulkan)
    374     {
    375         initGoogExtention();
    376     }
    377     virtual bool doGetRefreshCycleDuration(VkSwapchainKHR swapchain,
    378                                            uint64_t*      pRefreshDuration) override
    379     {
    380         VkRefreshCycleDurationGOOGLE refreshCycleDuration;
    381         VkResult res = mpfnGetRefreshCycleDurationGOOGLE(mDevice, swapchain, &refreshCycleDuration);
    382         if (res != VK_SUCCESS) {
    383             // This should never occur, but in case it does, return 16,666,666ns:
    384             mRefreshDur = k16_6msec;
    385         } else {
    386             mRefreshDur = refreshCycleDuration.refreshDuration;
    387         }
    388 
    389         // TEMP CODE: LOG REFRESH DURATION AND RATE:
    390         double refreshRate = mRefreshDur;
    391         refreshRate = 1.0 / (refreshRate / 1000000000.0);
    392 
    393         ALOGD("Returning refresh duration of %" PRIu64 " nsec (approx %f Hz)", mRefreshDur, refreshRate);
    394 
    395         *pRefreshDuration = mRefreshDur;
    396         return true;
    397     }
    398     virtual VkResult doQueuePresent(VkQueue                 queue,
    399                                     uint32_t                queueFamilyIndex,
    400                                     const VkPresentInfoKHR* pPresentInfo) override;
    401 
    402 private:
    403     void calculateNextDesiredPresentTime(VkSwapchainKHR swapchain);
    404     void checkPastPresentTiming(VkSwapchainKHR swapchain);
    405 };
    406 
    407 VkResult SwappyVkGoogleDisplayTiming::doQueuePresent(VkQueue                 queue,
    408                                                      uint32_t                queueFamilyIndex,
    409                                                      const VkPresentInfoKHR* pPresentInfo)
    410 {
    411     VkResult ret = VK_SUCCESS;
    412 
    413     calculateNextDesiredPresentTime(pPresentInfo->pSwapchains[0]);
    414 
    415     // Setup the new structures to pass:
    416     VkPresentTimeGOOGLE *pPresentTimes =
    417             reinterpret_cast<VkPresentTimeGOOGLE*>(malloc(sizeof(VkPresentTimeGOOGLE) *
    418                                                           pPresentInfo->swapchainCount));
    419     for (uint32_t i = 0 ; i < pPresentInfo->swapchainCount ; i++) {
    420         pPresentTimes[i].presentID = mNextPresentID;
    421         pPresentTimes[i].desiredPresentTime = mNextDesiredPresentTime;
    422     }
    423     mNextPresentID++;
    424 
    425     VkPresentTimesInfoGOOGLE presentTimesInfo = {VK_STRUCTURE_TYPE_PRESENT_TIMES_INFO_GOOGLE,
    426                                                  pPresentInfo->pNext, pPresentInfo->swapchainCount,
    427                                                  pPresentTimes};
    428     VkPresentInfoKHR replacementPresentInfo = {pPresentInfo->sType, &presentTimesInfo,
    429                                                pPresentInfo->waitSemaphoreCount,
    430                                                pPresentInfo->pWaitSemaphores,
    431                                                pPresentInfo->swapchainCount,
    432                                                pPresentInfo->pSwapchains,
    433                                                pPresentInfo->pImageIndices, pPresentInfo->pResults};
    434     ret = mpfnQueuePresentKHR(queue, &replacementPresentInfo);
    435     free(pPresentTimes);
    436     return ret;
    437 }
    438 
    439 void SwappyVkGoogleDisplayTiming::calculateNextDesiredPresentTime(VkSwapchainKHR swapchain)
    440 {
    441     struct timespec currTime;
    442     clock_gettime(CLOCK_MONOTONIC, &currTime);
    443     uint64_t currentTime =
    444             ((uint64_t) currTime.tv_sec * kBillion) + (uint64_t) currTime.tv_nsec;
    445 
    446 
    447     // Determine the desiredPresentTime:
    448     if (!mNextDesiredPresentTime) {
    449         mNextDesiredPresentTime = currentTime + mRefreshDur;
    450     } else {
    451         // Look at the timing of past presents, and potentially adjust mNextDesiredPresentTime:
    452         checkPastPresentTiming(swapchain);
    453         mNextDesiredPresentTime += mRefreshDur * mInterval;
    454 
    455         // Make sure the calculated time is not before the current time to present
    456         if (mNextDesiredPresentTime < currentTime) {
    457             mNextDesiredPresentTime = currentTime + mRefreshDur;
    458         }
    459     }
    460 }
    461 
    462 void SwappyVkGoogleDisplayTiming::checkPastPresentTiming(VkSwapchainKHR swapchain)
    463 {
    464     VkResult ret = VK_SUCCESS;
    465 
    466     if (mNextPresentID <= mNextPresentIDToCheck) {
    467         return;
    468     }
    469     // Check the timing of past presents to see if we need to adjust mNextDesiredPresentTime:
    470     uint32_t pastPresentationTimingCount = 0;
    471     VkResult err = mpfnGetPastPresentationTimingGOOGLE(mDevice, swapchain,
    472                                                        &pastPresentationTimingCount, NULL);
    473     if (!pastPresentationTimingCount) {
    474         return;
    475     }
    476     // TODO: don't allocate memory for the timestamps every time.
    477     VkPastPresentationTimingGOOGLE *past =
    478             reinterpret_cast<VkPastPresentationTimingGOOGLE*>(
    479                     malloc(sizeof(VkPastPresentationTimingGOOGLE) *
    480                            pastPresentationTimingCount));
    481     err = mpfnGetPastPresentationTimingGOOGLE(mDevice, swapchain,
    482                                               &pastPresentationTimingCount, past);
    483     for (uint32_t i = 0; i < pastPresentationTimingCount; i++) {
    484         // Note: On Android, actualPresentTime can actually be before desiredPresentTime
    485         // (which shouldn't be possible.  Therefore, this must be a signed integer.
    486         int64_t amountEarlyBy =
    487                 (int64_t) past[i].actualPresentTime - (int64_t)past[i].desiredPresentTime;
    488         if (amountEarlyBy < kTooCloseToVsyncBoundary) {
    489             // We're getting too close to vsync.  Nudge the next present back
    490             // towards/in the boundaries, and check back after a few more presents:
    491             mNextDesiredPresentTime -= kNudgeWithinVsyncBoundaries;
    492             mNextPresentIDToCheck = mNextPresentID + 7;
    493             break;
    494         }
    495         if (amountEarlyBy > kTooFarAwayFromVsyncBoundary) {
    496             // We're getting too far away from vsync.  Nudge the next present back
    497             // towards/in the boundaries, and check back after a few more presents:
    498             mNextDesiredPresentTime += kNudgeWithinVsyncBoundaries;
    499             mNextPresentIDToCheck = mNextPresentID + 7;
    500             break;
    501         }
    502     }
    503     free(past);
    504 }
    505 
    506 /**
    507  * Concrete/derived class that sits on top of VK_GOOGLE_display_timing
    508  */
    509 class SwappyVkGoogleDisplayTimingAndroid : public SwappyVkGoogleDisplayTiming
    510 {
    511 public:
    512     SwappyVkGoogleDisplayTimingAndroid(VkPhysicalDevice physicalDevice,
    513                                 VkDevice         device,
    514                                 SwappyVk         &swappyVk,
    515                                 void             *libVulkan) :
    516             SwappyVkGoogleDisplayTiming(physicalDevice, device, swappyVk,libVulkan) {
    517         startChoreographerThread();
    518     }
    519 
    520     ~SwappyVkGoogleDisplayTimingAndroid() {
    521         stopChoreographerThread();
    522         destroyVkSyncObjects();
    523     }
    524 
    525     virtual bool doGetRefreshCycleDuration(VkSwapchainKHR swapchain,
    526                                           uint64_t*      pRefreshDuration) override {
    527         bool res = SwappyVkGoogleDisplayTiming::doGetRefreshCycleDuration(swapchain, pRefreshDuration);
    528         return res;
    529     }
    530 
    531 
    532 
    533     virtual VkResult doQueuePresent(VkQueue queue,
    534                                     uint32_t queueFamilyIndex,
    535                                     const VkPresentInfoKHR *pPresentInfo) override;
    536 
    537 private:
    538     VkResult initializeVkSyncObjects(VkQueue queue, uint32_t queueFamilyIndex);
    539     void destroyVkSyncObjects();
    540 
    541     void waitForFenceChoreographer(VkQueue queue);
    542 
    543     struct VkSync {
    544         VkFence fence;
    545         VkSemaphore semaphore;
    546         VkCommandBuffer command;
    547         VkEvent event;
    548     };
    549 
    550     std::map<VkQueue, std::list<VkSync>> mFreeSync;
    551     std::map<VkQueue, std::list<VkSync>> mPendingSync;
    552     std::map<VkQueue, VkCommandPool> mCommandPool;
    553 
    554     static constexpr int MAX_PENDING_FENCES = 1;
    555 };
    556 
    557 VkResult SwappyVkGoogleDisplayTimingAndroid::initializeVkSyncObjects(VkQueue   queue,
    558                                                                      uint32_t  queueFamilyIndex)
    559 {
    560     if (mCommandPool.find(queue) != mCommandPool.end()) {
    561         return VK_SUCCESS;
    562     }
    563 
    564     VkSync sync;
    565 
    566     const VkCommandPoolCreateInfo cmd_pool_info = {
    567             .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
    568             .pNext = NULL,
    569             .queueFamilyIndex = queueFamilyIndex,
    570             .flags = 0,
    571     };
    572 
    573     VkResult res = vkCreateCommandPool(mDevice, &cmd_pool_info, NULL, &mCommandPool[queue]);
    574     if (res) {
    575         ALOGE("vkCreateCommandPool failed %d", res);
    576         return res;
    577     }
    578     const VkCommandBufferAllocateInfo present_cmd_info = {
    579             .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
    580             .pNext = NULL,
    581             .commandPool = mCommandPool[queue],
    582             .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
    583             .commandBufferCount = 1,
    584     };
    585 
    586     for(int i = 0; i < MAX_PENDING_FENCES; i++) {
    587         VkFenceCreateInfo fence_ci =
    588                 {.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .pNext = NULL, .flags = 0};
    589 
    590         res = vkCreateFence(mDevice, &fence_ci, NULL, &sync.fence);
    591         if (res) {
    592             ALOGE("failed to create fence: %d", res);
    593             return res;
    594         }
    595 
    596         VkSemaphoreCreateInfo semaphore_ci =
    597                 {.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, .pNext = NULL, .flags = 0};
    598 
    599         res = vkCreateSemaphore(mDevice, &semaphore_ci, NULL, &sync.semaphore);
    600         if (res) {
    601             ALOGE("failed to create semaphore: %d", res);
    602             return res;
    603         }
    604 
    605 
    606         res = vkAllocateCommandBuffers(mDevice, &present_cmd_info, &sync.command);
    607         if (res) {
    608             ALOGE("vkAllocateCommandBuffers failed %d", res);
    609             return res;
    610         }
    611 
    612         const VkCommandBufferBeginInfo cmd_buf_info = {
    613                 .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
    614                 .pNext = NULL,
    615                 .flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT,
    616                 .pInheritanceInfo = NULL,
    617         };
    618 
    619         res = vkBeginCommandBuffer(sync.command, &cmd_buf_info);
    620         if (res) {
    621             ALOGE("vkAllocateCommandBuffers failed %d", res);
    622             return res;
    623         }
    624 
    625         VkEventCreateInfo event_info = {
    626                 .sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO,
    627                 .pNext = NULL,
    628                 .flags = 0,
    629         };
    630 
    631         res = vkCreateEvent(mDevice, &event_info, NULL, &sync.event);
    632         if (res) {
    633             ALOGE("vkCreateEvent failed %d", res);
    634             return res;
    635         }
    636 
    637         vkCmdSetEvent(sync.command, sync.event, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
    638 
    639         res = vkEndCommandBuffer(sync.command);
    640         if (res) {
    641             ALOGE("vkCreateEvent failed %d", res);
    642             return res;
    643         }
    644 
    645         mFreeSync[queue].push_back(sync);
    646     }
    647 
    648     return VK_SUCCESS;
    649 }
    650 
    651 void SwappyVkGoogleDisplayTimingAndroid::destroyVkSyncObjects() {
    652     for (auto it = mPendingSync.begin(); it != mPendingSync.end(); it++) {
    653         while (mPendingSync[it->first].size() > 0) {
    654             VkSync sync = mPendingSync[it->first].front();
    655             mPendingSync[it->first].pop_front();
    656             vkWaitForFences(mDevice, 1, &sync.fence, VK_TRUE, UINT64_MAX);
    657             vkResetFences(mDevice, 1, &sync.fence);
    658             mFreeSync[it->first].push_back(sync);
    659         }
    660 
    661         while (mFreeSync[it->first].size() > 0) {
    662             VkSync sync = mFreeSync[it->first].front();
    663             mFreeSync[it->first].pop_front();
    664             vkFreeCommandBuffers(mDevice, mCommandPool[it->first], 1, &sync.command);
    665             vkDestroyEvent(mDevice, sync.event, NULL);
    666             vkDestroySemaphore(mDevice, sync.semaphore, NULL);
    667             vkDestroyFence(mDevice, sync.fence, NULL);
    668         }
    669 
    670         vkDestroyCommandPool(mDevice, mCommandPool[it->first], NULL);
    671     }
    672 }
    673 
    674 void SwappyVkGoogleDisplayTimingAndroid::waitForFenceChoreographer(VkQueue queue)
    675 {
    676     std::unique_lock<std::mutex> lock(mWaitingMutex);
    677     VkSync sync = mPendingSync[queue].front();
    678     mPendingSync[queue].pop_front();
    679     mWaitingCondition.wait(lock, [&]() {
    680         if (vkWaitForFences(mDevice, 1, &sync.fence, VK_TRUE, 0) == VK_TIMEOUT) {
    681             postChoreographerCallback();
    682 
    683             // adjust the target frame here as we are waiting additional frame for the fence
    684             mTargetFrameID++;
    685             return false;
    686         }
    687         return true;
    688     });
    689 
    690     vkResetFences(mDevice, 1, &sync.fence);
    691     mFreeSync[queue].push_back(sync);
    692 }
    693 
    694 VkResult SwappyVkGoogleDisplayTimingAndroid::doQueuePresent(VkQueue                 queue,
    695                                                      uint32_t                queueFamilyIndex,
    696                                                      const VkPresentInfoKHR* pPresentInfo)
    697 {
    698     VkResult ret = initializeVkSyncObjects(queue, queueFamilyIndex);
    699     if (ret) {
    700         return ret;
    701     }
    702 
    703     {
    704         std::unique_lock<std::mutex> lock(mWaitingMutex);
    705         mWaitingCondition.wait(lock, [&]() {
    706             if (mFrameID < mTargetFrameID) {
    707                 postChoreographerCallback();
    708                 return false;
    709             }
    710             return true;
    711         });
    712     }
    713 
    714     if (mPendingSync[queue].size() >= MAX_PENDING_FENCES) {
    715         waitForFenceChoreographer(queue);
    716     }
    717 
    718     // Adjust the presentation time based on the current frameID we are at.
    719     if(mFrameID < mTargetFrameID) {
    720         ALOGE("Bad frame ID %ld < target %ld", mFrameID, mTargetFrameID);
    721         mTargetFrameID = mFrameID;
    722     }
    723     mNextDesiredPresentTime += (mFrameID - mTargetFrameID) * mRefreshDur;
    724 
    725     // Setup the new structures to pass:
    726     VkPresentTimeGOOGLE pPresentTimes[pPresentInfo->swapchainCount];
    727     for (uint32_t i = 0 ; i < pPresentInfo->swapchainCount ; i++) {
    728         pPresentTimes[i].presentID = mNextPresentID;
    729         pPresentTimes[i].desiredPresentTime = mNextDesiredPresentTime;
    730     }
    731     mNextPresentID++;
    732 
    733     VkSync sync = mFreeSync[queue].front();
    734     mFreeSync[queue].pop_front();
    735     mPendingSync[queue].push_back(sync);
    736 
    737     VkPipelineStageFlags pipe_stage_flags;
    738     VkSubmitInfo submit_info;
    739     submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
    740     submit_info.pNext = NULL;
    741     submit_info.pWaitDstStageMask = &pipe_stage_flags;
    742     pipe_stage_flags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    743     submit_info.waitSemaphoreCount = pPresentInfo->waitSemaphoreCount;
    744     submit_info.pWaitSemaphores = pPresentInfo->pWaitSemaphores;
    745     submit_info.commandBufferCount = 1;
    746     submit_info.pCommandBuffers = &sync.command;
    747     submit_info.signalSemaphoreCount = 1;
    748     submit_info.pSignalSemaphores = &sync.semaphore;
    749     ret = vkQueueSubmit(queue, 1, &submit_info, sync.fence);
    750     if (ret) {
    751         ALOGE("Failed to vkQueueSubmit %d", ret);
    752         return ret;
    753     }
    754 
    755     VkPresentTimesInfoGOOGLE presentTimesInfo = {VK_STRUCTURE_TYPE_PRESENT_TIMES_INFO_GOOGLE,
    756                                                  pPresentInfo->pNext, pPresentInfo->swapchainCount,
    757                                                  pPresentTimes};
    758     VkPresentInfoKHR replacementPresentInfo = {pPresentInfo->sType, &presentTimesInfo,
    759                                                1,
    760                                                &sync.semaphore,
    761                                                pPresentInfo->swapchainCount,
    762                                                pPresentInfo->pSwapchains,
    763                                                pPresentInfo->pImageIndices, pPresentInfo->pResults};
    764     ret = mpfnQueuePresentKHR(queue, &replacementPresentInfo);
    765 
    766     // next present time is going to be 2 intervals from now, leaving 1 interval for cpu work
    767     // and 1 interval for gpu work
    768     mNextDesiredPresentTime = mLastframeTimeNanos + 2 * mRefreshDur * mInterval;
    769     mTargetFrameID = mFrameID + mInterval;
    770 
    771     return ret;
    772 }
    773 
    774 /***************************************************************************************************
    775  *
    776  * Per-Device concrete/derived class for the "Android fallback" path (uses
    777  * Choreographer to try to get presents to occur at the desired time).
    778  *
    779  ***************************************************************************************************/
    780 
    781 /**
    782  * Concrete/derived class that sits on top of the Vulkan API
    783  */
    784 #ifdef ANDROID
    785 class SwappyVkAndroidFallback : public SwappyVkBase
    786 {
    787 public:
    788     SwappyVkAndroidFallback(VkPhysicalDevice physicalDevice,
    789                             VkDevice         device,
    790                             SwappyVk         &swappyVk,
    791                             void             *libVulkan) :
    792             SwappyVkBase(physicalDevice, device, 0, 1, swappyVk, libVulkan) {
    793         startChoreographerThread();
    794     }
    795 
    796         ~SwappyVkAndroidFallback() {
    797             stopChoreographerThread();
    798     }
    799 
    800     virtual bool doGetRefreshCycleDuration(VkSwapchainKHR swapchain,
    801                                                uint64_t*      pRefreshDuration) override
    802     {
    803         std::unique_lock<std::mutex> lock(mWaitingMutex);
    804         mWaitingCondition.wait(lock, [&]() {
    805             if (mRefreshDur == 0) {
    806                 postChoreographerCallback();
    807                 return false;
    808             }
    809             return true;
    810         });
    811 
    812         *pRefreshDuration = mRefreshDur;
    813 
    814         double refreshRate = mRefreshDur;
    815         refreshRate = 1.0 / (refreshRate / 1000000000.0);
    816         ALOGI("Returning refresh duration of %" PRIu64 " nsec (approx %f Hz)", mRefreshDur, refreshRate);
    817         return true;
    818     }
    819 
    820     virtual VkResult doQueuePresent(VkQueue                 queue,
    821                                     uint32_t                queueFamilyIndex,
    822                                     const VkPresentInfoKHR* pPresentInfo) override
    823     {
    824         {
    825             std::unique_lock<std::mutex> lock(mWaitingMutex);
    826 
    827             mWaitingCondition.wait(lock, [&]() {
    828                 if (mFrameID < mTargetFrameID) {
    829                     postChoreographerCallback();
    830                     return false;
    831                 }
    832                 return true;
    833             });
    834         }
    835         mTargetFrameID = mFrameID + mInterval;
    836         return mpfnQueuePresentKHR(queue, pPresentInfo);
    837     }
    838 };
    839 #endif
    840 
    841 /***************************************************************************************************
    842  *
    843  * Per-Device concrete/derived class for the "Vulkan fallback" path (i.e. no API/OS timing support;
    844  * just generic Vulkan)
    845  *
    846  ***************************************************************************************************/
    847 
    848 /**
    849  * Concrete/derived class that sits on top of the Vulkan API
    850  */
    851 class SwappyVkVulkanFallback : public SwappyVkBase
    852 {
    853 public:
    854     SwappyVkVulkanFallback(VkPhysicalDevice physicalDevice,
    855                             VkDevice         device,
    856                             SwappyVk         &swappyVk,
    857                             void             *libVulkan) :
    858             SwappyVkBase(physicalDevice, device, k16_6msec, 1, swappyVk, libVulkan) {}
    859     virtual bool doGetRefreshCycleDuration(VkSwapchainKHR swapchain,
    860                                            uint64_t*      pRefreshDuration) override
    861     {
    862         *pRefreshDuration = mRefreshDur;
    863         return true;
    864     }
    865     virtual VkResult doQueuePresent(VkQueue                 queue,
    866                                     uint32_t                queueFamilyIndex,
    867                                     const VkPresentInfoKHR* pPresentInfo) override
    868     {
    869         return mpfnQueuePresentKHR(queue, pPresentInfo);
    870     }
    871 };
    872 
    873 
    874 
    875 
    876 /***************************************************************************************************
    877  *
    878  * Singleton class that provides the high-level implementation of the Swappy entrypoints.
    879  *
    880  ***************************************************************************************************/
    881 /**
    882  * Singleton class that provides the high-level implementation of the Swappy entrypoints.
    883  *
    884  * This class determines which low-level implementation to use for each physical
    885  * device, and then calls that class's do-method for the entrypoint.
    886  */
    887 class SwappyVk
    888 {
    889 public:
    890     static SwappyVk& getInstance()
    891     {
    892         static SwappyVk instance;
    893         return instance;
    894     }
    895     ~SwappyVk() {
    896         if(mLibVulkan)
    897             dlclose(mLibVulkan);
    898     }
    899 
    900     void swappyVkDetermineDeviceExtensions(VkPhysicalDevice       physicalDevice,
    901                                            uint32_t               availableExtensionCount,
    902                                            VkExtensionProperties* pAvailableExtensions,
    903                                            uint32_t*              pRequiredExtensionCount,
    904                                            char**                 pRequiredExtensions);
    905     void SetQueueFamilyIndex(VkDevice   device,
    906                              VkQueue    queue,
    907                              uint32_t   queueFamilyIndex);
    908     bool GetRefreshCycleDuration(VkPhysicalDevice physicalDevice,
    909                                  VkDevice         device,
    910                                  VkSwapchainKHR   swapchain,
    911                                  uint64_t*        pRefreshDuration);
    912     void SetSwapInterval(VkDevice       device,
    913                          VkSwapchainKHR swapchain,
    914                          uint32_t       interval);
    915     VkResult QueuePresent(VkQueue                 queue,
    916                           const VkPresentInfoKHR* pPresentInfo);
    917     void DestroySwapchain(VkDevice                device,
    918                           VkSwapchainKHR          swapchain);
    919 
    920 private:
    921     std::map<VkPhysicalDevice, bool> doesPhysicalDeviceHaveGoogleDisplayTiming;
    922     std::map<VkDevice, std::shared_ptr<SwappyVkBase>> perDeviceImplementation;
    923     std::map<VkSwapchainKHR, std::shared_ptr<SwappyVkBase>> perSwapchainImplementation;
    924 
    925     struct QueueFamilyIndex {
    926         VkDevice device;
    927         uint32_t queueFamilyIndex;
    928     };
    929     std::map<VkQueue, QueueFamilyIndex> perQueueFamilyIndex;
    930 
    931     void *mLibVulkan     = nullptr;
    932 
    933 private:
    934     SwappyVk() {} // Need to implement this constructor
    935     SwappyVk(SwappyVk const&); // Don't implement a copy constructor--no copies
    936     void operator=(SwappyVk const&); // Don't implement--no copies
    937 };
    938 
    939 /**
    940  * Generic/Singleton implementation of swappyVkDetermineDeviceExtensions.
    941  */
    942 void SwappyVk::swappyVkDetermineDeviceExtensions(
    943     VkPhysicalDevice       physicalDevice,
    944     uint32_t               availableExtensionCount,
    945     VkExtensionProperties* pAvailableExtensions,
    946     uint32_t*              pRequiredExtensionCount,
    947     char**                 pRequiredExtensions)
    948 {
    949     // TODO: Refactor this to be more concise:
    950     if (!pRequiredExtensions) {
    951         for (uint32_t i = 0; i < availableExtensionCount; i++) {
    952             if (!strcmp(VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME,
    953                         pAvailableExtensions[i].extensionName)) {
    954                 (*pRequiredExtensionCount)++;
    955             }
    956         }
    957     } else {
    958         doesPhysicalDeviceHaveGoogleDisplayTiming[physicalDevice] = false;
    959         for (uint32_t i = 0, j = 0; i < availableExtensionCount; i++) {
    960             if (!strcmp(VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME,
    961                         pAvailableExtensions[i].extensionName)) {
    962                 if (j < *pRequiredExtensionCount) {
    963                     strcpy(pRequiredExtensions[j++], VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME);
    964                     doesPhysicalDeviceHaveGoogleDisplayTiming[physicalDevice] = true;
    965                 }
    966             }
    967         }
    968     }
    969 }
    970 
    971 void SwappyVk::SetQueueFamilyIndex(VkDevice   device,
    972                                     VkQueue    queue,
    973                                     uint32_t   queueFamilyIndex)
    974 {
    975     perQueueFamilyIndex[queue] = {device, queueFamilyIndex};
    976 }
    977 
    978 
    979 /**
    980  * Generic/Singleton implementation of swappyVkGetRefreshCycleDuration.
    981  */
    982 bool SwappyVk::GetRefreshCycleDuration(VkPhysicalDevice physicalDevice,
    983                                        VkDevice         device,
    984                                        VkSwapchainKHR   swapchain,
    985                                        uint64_t*        pRefreshDuration)
    986 {
    987     auto& pImplementation = perDeviceImplementation[device];
    988     if (!pImplementation) {
    989         // We have not seen this device yet.
    990         if (!mLibVulkan) {
    991             // This is the first time we've been called--initialize function pointers:
    992             mLibVulkan = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL);
    993             if (!mLibVulkan)
    994             {
    995                 // If Vulkan doesn't exist, bail-out early:
    996                 return false;
    997             }
    998         }
    999 
   1000         // First, based on whether VK_GOOGLE_display_timing is available
   1001         // (determined and cached by swappyVkDetermineDeviceExtensions),
   1002         // determine which derived class to use to implement the rest of the API
   1003         if (doesPhysicalDeviceHaveGoogleDisplayTiming[physicalDevice]) {
   1004 #ifdef ANDROID
   1005             pImplementation = std::make_shared<SwappyVkGoogleDisplayTimingAndroid>
   1006                     (physicalDevice, device, getInstance(), mLibVulkan);
   1007             ALOGV("SwappyVk initialized for VkDevice %p using VK_GOOGLE_display_timing on Android", device);
   1008 #else
   1009             // Instantiate the class that sits on top of VK_GOOGLE_display_timing
   1010             pImplementation = std::make_shared<SwappyVkGoogleDisplayTiming>
   1011                     (physicalDevice, device, getInstance(), mLibVulkan);
   1012             ALOGV("SwappyVk initialized for VkDevice %p using VK_GOOGLE_display_timing", device);
   1013 #endif
   1014         } else {
   1015             // Instantiate the class that sits on top of the basic Vulkan APIs
   1016 #ifdef ANDROID
   1017             pImplementation = std::make_shared<SwappyVkAndroidFallback>
   1018                     (physicalDevice, device, getInstance(), mLibVulkan);
   1019             ALOGV("SwappyVk initialized for VkDevice %p using Android fallback", device);
   1020 #else  // ANDROID
   1021             pImplementation = std::make_shared<SwappyVkVulkanFallback>
   1022                     (physicalDevice, device, getInstance(), mLibVulkan);
   1023             ALOGV("SwappyVk initialized for VkDevice %p using Vulkan-only fallback", device);
   1024 #endif // ANDROID
   1025         }
   1026 
   1027         if (!pImplementation) {
   1028             // This shouldn't happen, but if it does, something is really wrong.
   1029             return false;
   1030         }
   1031     }
   1032 
   1033     // Cache the per-swapchain pointer to the derived class:
   1034     perSwapchainImplementation[swapchain] = pImplementation;
   1035 
   1036     // Now, call that derived class to get the refresh duration to return
   1037     return pImplementation->doGetRefreshCycleDuration(swapchain, pRefreshDuration);
   1038 }
   1039 
   1040 
   1041 /**
   1042  * Generic/Singleton implementation of swappyVkSetSwapInterval.
   1043  */
   1044 void SwappyVk::SetSwapInterval(VkDevice       device,
   1045                                VkSwapchainKHR swapchain,
   1046                                uint32_t       interval)
   1047 {
   1048     auto& pImplementation = perDeviceImplementation[device];
   1049     if (!pImplementation) {
   1050         return;
   1051     }
   1052     pImplementation->doSetSwapInterval(swapchain, interval);
   1053 }
   1054 
   1055 
   1056 /**
   1057  * Generic/Singleton implementation of swappyVkQueuePresent.
   1058  */
   1059 VkResult SwappyVk::QueuePresent(VkQueue                 queue,
   1060                                 const VkPresentInfoKHR* pPresentInfo)
   1061 {
   1062     if (perQueueFamilyIndex.find(queue) == perQueueFamilyIndex.end()) {
   1063         ALOGE("Unknown queue %p. Did you call SwappyVkSetQueueFamilyIndex ?", queue);
   1064         return VK_INCOMPLETE;
   1065     }
   1066 
   1067     // This command doesn't have a VkDevice.  It should have at least one VkSwapchainKHR's.  For
   1068     // this command, all VkSwapchainKHR's will have the same VkDevice and VkQueue.
   1069     if ((pPresentInfo->swapchainCount == 0) || (!pPresentInfo->pSwapchains)) {
   1070         // This shouldn't happen, but if it does, something is really wrong.
   1071         return VK_ERROR_DEVICE_LOST;
   1072     }
   1073     auto& pImplementation = perSwapchainImplementation[*pPresentInfo->pSwapchains];
   1074     if (pImplementation) {
   1075         return pImplementation->doQueuePresent(queue,
   1076                                                perQueueFamilyIndex[queue].queueFamilyIndex,
   1077                                                pPresentInfo);
   1078     } else {
   1079         // This should only happen if the API was used wrong (e.g. they never
   1080         // called swappyVkGetRefreshCycleDuration).
   1081         // NOTE: Technically, a Vulkan library shouldn't protect a user from
   1082         // themselves, but we'll be friendlier
   1083         return VK_ERROR_DEVICE_LOST;
   1084     }
   1085 }
   1086 
   1087 void SwappyVk::DestroySwapchain(VkDevice                device,
   1088                                         VkSwapchainKHR          swapchain) {
   1089     auto it = perQueueFamilyIndex.begin();
   1090     while (it != perQueueFamilyIndex.end()) {
   1091         if (it->second.device == device) {
   1092             it = perQueueFamilyIndex.erase(it);
   1093         } else {
   1094             ++it;
   1095         }
   1096     }
   1097 
   1098     perDeviceImplementation[device] = nullptr;
   1099     perSwapchainImplementation[swapchain] = nullptr;
   1100 }
   1101 
   1102 
   1103 /***************************************************************************************************
   1104  *
   1105  * API ENTRYPOINTS
   1106  *
   1107  ***************************************************************************************************/
   1108 
   1109 extern "C" {
   1110 
   1111 void SwappyVk_determineDeviceExtensions(
   1112     VkPhysicalDevice       physicalDevice,
   1113     uint32_t               availableExtensionCount,
   1114     VkExtensionProperties* pAvailableExtensions,
   1115     uint32_t*              pRequiredExtensionCount,
   1116     char**                 pRequiredExtensions)
   1117 {
   1118     TRACE_CALL();
   1119     SwappyVk& swappy = SwappyVk::getInstance();
   1120     swappy.swappyVkDetermineDeviceExtensions(physicalDevice,
   1121                                              availableExtensionCount, pAvailableExtensions,
   1122                                              pRequiredExtensionCount, pRequiredExtensions);
   1123 }
   1124 
   1125 void SwappyVk_setQueueFamilyIndex(
   1126         VkDevice    device,
   1127         VkQueue     queue,
   1128         uint32_t    queueFamilyIndex)
   1129 {
   1130     TRACE_CALL();
   1131     SwappyVk& swappy = SwappyVk::getInstance();
   1132     swappy.SetQueueFamilyIndex(device, queue, queueFamilyIndex);
   1133 }
   1134 
   1135 bool SwappyVk_initAndGetRefreshCycleDuration(
   1136         VkPhysicalDevice physicalDevice,
   1137         VkDevice         device,
   1138         VkSwapchainKHR   swapchain,
   1139         uint64_t*        pRefreshDuration)
   1140 {
   1141     TRACE_CALL();
   1142     SwappyVk& swappy = SwappyVk::getInstance();
   1143     return swappy.GetRefreshCycleDuration(physicalDevice, device, swapchain, pRefreshDuration);
   1144 }
   1145 
   1146 void SwappyVk_setSwapInterval(
   1147         VkDevice       device,
   1148         VkSwapchainKHR swapchain,
   1149         uint32_t       interval)
   1150 {
   1151     TRACE_CALL();
   1152     SwappyVk& swappy = SwappyVk::getInstance();
   1153     swappy.SetSwapInterval(device, swapchain, interval);
   1154 }
   1155 
   1156 VkResult SwappyVk_queuePresent(
   1157         VkQueue                 queue,
   1158         const VkPresentInfoKHR* pPresentInfo)
   1159 {
   1160     TRACE_CALL();
   1161     SwappyVk& swappy = SwappyVk::getInstance();
   1162     return swappy.QueuePresent(queue, pPresentInfo);
   1163 }
   1164 
   1165 void SwappyVk_destroySwapchain(
   1166         VkDevice                device,
   1167         VkSwapchainKHR          swapchain)
   1168 {
   1169     TRACE_CALL();
   1170     SwappyVk& swappy = SwappyVk::getInstance();
   1171     swappy.DestroySwapchain(device, swapchain);
   1172 }
   1173 
   1174 }  // extern "C"
   1175