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:
SwappyVkBase(VkPhysicalDevice physicalDevice,VkDevice device,uint64_t refreshDur,uint32_t interval,SwappyVk & swappyVk,void * libVulkan)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 }
~SwappyVkBase()159 virtual ~SwappyVkBase() {
160 if(mLibAndroid)
161 dlclose(mLibAndroid);
162 }
163 virtual bool doGetRefreshCycleDuration(VkSwapchainKHR swapchain,
164 uint64_t* pRefreshDuration) = 0;
doSetSwapInterval(VkSwapchainKHR swapchain,uint32_t interval)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
initGoogExtention()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
startChoreographerThread()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
stopChoreographerThread()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
looperThreadWrapper(void * data)249 void *SwappyVkBase::looperThreadWrapper(void *data) {
250 SwappyVkBase *me = reinterpret_cast<SwappyVkBase *>(data);
251 return me->looperThread();
252 }
253
looperThread()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
frameCallback(long frameTimeNanos,void * data)278 void SwappyVkBase::frameCallback(long frameTimeNanos, void *data) {
279 SwappyVkBase *me = reinterpret_cast<SwappyVkBase *>(data);
280 me->onDisplayRefresh(frameTimeNanos);
281 }
282
onDisplayRefresh(long frameTimeNanos)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
postChoreographerCallback()302 void SwappyVkBase::postChoreographerCallback() {
303 if (mCallbacksBeforeIdle == 0) {
304 mAChoreographer_postFrameCallbackDelayed(mChoreographer, frameCallback, this, 1);
305 }
306 mCallbacksBeforeIdle = MAX_CALLBACKS_BEFORE_IDLE;
307 }
308
calcRefreshRate(uint64_t currentTime)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:
SwappyVkGoogleDisplayTiming(VkPhysicalDevice physicalDevice,VkDevice device,SwappyVk & swappyVk,void * libVulkan)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 }
doGetRefreshCycleDuration(VkSwapchainKHR swapchain,uint64_t * pRefreshDuration)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
doQueuePresent(VkQueue queue,uint32_t queueFamilyIndex,const VkPresentInfoKHR * pPresentInfo)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
calculateNextDesiredPresentTime(VkSwapchainKHR swapchain)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
checkPastPresentTiming(VkSwapchainKHR swapchain)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:
SwappyVkGoogleDisplayTimingAndroid(VkPhysicalDevice physicalDevice,VkDevice device,SwappyVk & swappyVk,void * libVulkan)512 SwappyVkGoogleDisplayTimingAndroid(VkPhysicalDevice physicalDevice,
513 VkDevice device,
514 SwappyVk &swappyVk,
515 void *libVulkan) :
516 SwappyVkGoogleDisplayTiming(physicalDevice, device, swappyVk,libVulkan) {
517 startChoreographerThread();
518 }
519
~SwappyVkGoogleDisplayTimingAndroid()520 ~SwappyVkGoogleDisplayTimingAndroid() {
521 stopChoreographerThread();
522 destroyVkSyncObjects();
523 }
524
doGetRefreshCycleDuration(VkSwapchainKHR swapchain,uint64_t * pRefreshDuration)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
initializeVkSyncObjects(VkQueue queue,uint32_t queueFamilyIndex)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
destroyVkSyncObjects()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
waitForFenceChoreographer(VkQueue queue)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
doQueuePresent(VkQueue queue,uint32_t queueFamilyIndex,const VkPresentInfoKHR * pPresentInfo)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:
SwappyVkAndroidFallback(VkPhysicalDevice physicalDevice,VkDevice device,SwappyVk & swappyVk,void * libVulkan)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
~SwappyVkAndroidFallback()796 ~SwappyVkAndroidFallback() {
797 stopChoreographerThread();
798 }
799
doGetRefreshCycleDuration(VkSwapchainKHR swapchain,uint64_t * pRefreshDuration)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
doQueuePresent(VkQueue queue,uint32_t queueFamilyIndex,const VkPresentInfoKHR * pPresentInfo)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:
SwappyVkVulkanFallback(VkPhysicalDevice physicalDevice,VkDevice device,SwappyVk & swappyVk,void * libVulkan)854 SwappyVkVulkanFallback(VkPhysicalDevice physicalDevice,
855 VkDevice device,
856 SwappyVk &swappyVk,
857 void *libVulkan) :
858 SwappyVkBase(physicalDevice, device, k16_6msec, 1, swappyVk, libVulkan) {}
doGetRefreshCycleDuration(VkSwapchainKHR swapchain,uint64_t * pRefreshDuration)859 virtual bool doGetRefreshCycleDuration(VkSwapchainKHR swapchain,
860 uint64_t* pRefreshDuration) override
861 {
862 *pRefreshDuration = mRefreshDur;
863 return true;
864 }
doQueuePresent(VkQueue queue,uint32_t queueFamilyIndex,const VkPresentInfoKHR * pPresentInfo)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:
getInstance()890 static SwappyVk& getInstance()
891 {
892 static SwappyVk instance;
893 return instance;
894 }
~SwappyVk()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:
SwappyVk()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 */
swappyVkDetermineDeviceExtensions(VkPhysicalDevice physicalDevice,uint32_t availableExtensionCount,VkExtensionProperties * pAvailableExtensions,uint32_t * pRequiredExtensionCount,char ** pRequiredExtensions)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
SetQueueFamilyIndex(VkDevice device,VkQueue queue,uint32_t queueFamilyIndex)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 */
GetRefreshCycleDuration(VkPhysicalDevice physicalDevice,VkDevice device,VkSwapchainKHR swapchain,uint64_t * pRefreshDuration)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 */
SetSwapInterval(VkDevice device,VkSwapchainKHR swapchain,uint32_t interval)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 */
QueuePresent(VkQueue queue,const VkPresentInfoKHR * pPresentInfo)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
DestroySwapchain(VkDevice device,VkSwapchainKHR swapchain)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
SwappyVk_determineDeviceExtensions(VkPhysicalDevice physicalDevice,uint32_t availableExtensionCount,VkExtensionProperties * pAvailableExtensions,uint32_t * pRequiredExtensionCount,char ** pRequiredExtensions)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
SwappyVk_setQueueFamilyIndex(VkDevice device,VkQueue queue,uint32_t queueFamilyIndex)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
SwappyVk_initAndGetRefreshCycleDuration(VkPhysicalDevice physicalDevice,VkDevice device,VkSwapchainKHR swapchain,uint64_t * pRefreshDuration)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
SwappyVk_setSwapInterval(VkDevice device,VkSwapchainKHR swapchain,uint32_t interval)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
SwappyVk_queuePresent(VkQueue queue,const VkPresentInfoKHR * pPresentInfo)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
SwappyVk_destroySwapchain(VkDevice device,VkSwapchainKHR swapchain)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