• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 
2 /*
3  * Copyright 2015 Google Inc.
4  *
5  * Use of this source code is governed by a BSD-style license that can be
6  * found in the LICENSE file.
7  */
8 
9 #include "tools/sk_app/VulkanWindowContext.h"
10 
11 #include "include/core/SkSurface.h"
12 #include "include/gpu/GrBackendSemaphore.h"
13 #include "include/gpu/GrBackendSurface.h"
14 #include "include/gpu/GrContext.h"
15 #include "src/core/SkAutoMalloc.h"
16 
17 #include "include/gpu/vk/GrVkExtensions.h"
18 #include "include/gpu/vk/GrVkTypes.h"
19 #include "src/gpu/vk/GrVkImage.h"
20 #include "src/gpu/vk/GrVkUtil.h"
21 
22 #ifdef VK_USE_PLATFORM_WIN32_KHR
23 // windows wants to define this as CreateSemaphoreA or CreateSemaphoreW
24 #undef CreateSemaphore
25 #endif
26 
27 #define GET_PROC(F) f ## F = (PFN_vk ## F) fGetInstanceProcAddr(fInstance, "vk" #F)
28 #define GET_DEV_PROC(F) f ## F = (PFN_vk ## F) fGetDeviceProcAddr(fDevice, "vk" #F)
29 
30 namespace sk_app {
31 
VulkanWindowContext(const DisplayParams & params,CreateVkSurfaceFn createVkSurface,CanPresentFn canPresent,PFN_vkGetInstanceProcAddr instProc,PFN_vkGetDeviceProcAddr devProc)32 VulkanWindowContext::VulkanWindowContext(const DisplayParams& params,
33                                          CreateVkSurfaceFn createVkSurface,
34                                          CanPresentFn canPresent,
35                                          PFN_vkGetInstanceProcAddr instProc,
36                                          PFN_vkGetDeviceProcAddr devProc)
37     : WindowContext(params)
38     , fCreateVkSurfaceFn(createVkSurface)
39     , fCanPresentFn(canPresent)
40     , fSurface(VK_NULL_HANDLE)
41     , fSwapchain(VK_NULL_HANDLE)
42     , fImages(nullptr)
43     , fImageLayouts(nullptr)
44     , fSurfaces(nullptr)
45     , fBackbuffers(nullptr) {
46     fGetInstanceProcAddr = instProc;
47     fGetDeviceProcAddr = devProc;
48     this->initializeContext();
49 }
50 
initializeContext()51 void VulkanWindowContext::initializeContext() {
52     // any config code here (particularly for msaa)?
53 
54     PFN_vkGetInstanceProcAddr getInstanceProc = fGetInstanceProcAddr;
55     PFN_vkGetDeviceProcAddr getDeviceProc = fGetDeviceProcAddr;
56     auto getProc = [getInstanceProc, getDeviceProc](const char* proc_name,
57                                                     VkInstance instance, VkDevice device) {
58         if (device != VK_NULL_HANDLE) {
59             return getDeviceProc(device, proc_name);
60         }
61         return getInstanceProc(instance, proc_name);
62     };
63     GrVkBackendContext backendContext;
64     GrVkExtensions extensions;
65     VkPhysicalDeviceFeatures2 features;
66     if (!sk_gpu_test::CreateVkBackendContext(getProc, &backendContext, &extensions, &features,
67                                              &fDebugCallback, &fPresentQueueIndex, fCanPresentFn)) {
68         sk_gpu_test::FreeVulkanFeaturesStructs(&features);
69         return;
70     }
71 
72     if (!extensions.hasExtension(VK_KHR_SURFACE_EXTENSION_NAME, 25) ||
73         !extensions.hasExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME, 68)) {
74         sk_gpu_test::FreeVulkanFeaturesStructs(&features);
75         return;
76     }
77 
78     fInstance = backendContext.fInstance;
79     fPhysicalDevice = backendContext.fPhysicalDevice;
80     fDevice = backendContext.fDevice;
81     fGraphicsQueueIndex = backendContext.fGraphicsQueueIndex;
82     fGraphicsQueue = backendContext.fQueue;
83 
84     PFN_vkGetPhysicalDeviceProperties localGetPhysicalDeviceProperties =
85             reinterpret_cast<PFN_vkGetPhysicalDeviceProperties>(
86                     backendContext.fGetProc("vkGetPhysicalDeviceProperties",
87                                             backendContext.fInstance,
88                                             VK_NULL_HANDLE));
89     if (!localGetPhysicalDeviceProperties) {
90         sk_gpu_test::FreeVulkanFeaturesStructs(&features);
91         return;
92     }
93     VkPhysicalDeviceProperties physDeviceProperties;
94     localGetPhysicalDeviceProperties(backendContext.fPhysicalDevice, &physDeviceProperties);
95     uint32_t physDevVersion = physDeviceProperties.apiVersion;
96 
97     fInterface.reset(new GrVkInterface(backendContext.fGetProc, fInstance, fDevice,
98                                        backendContext.fInstanceVersion, physDevVersion,
99                                        &extensions));
100 
101     GET_PROC(DestroyInstance);
102     if (fDebugCallback != VK_NULL_HANDLE) {
103         GET_PROC(DestroyDebugReportCallbackEXT);
104     }
105     GET_PROC(DestroySurfaceKHR);
106     GET_PROC(GetPhysicalDeviceSurfaceSupportKHR);
107     GET_PROC(GetPhysicalDeviceSurfaceCapabilitiesKHR);
108     GET_PROC(GetPhysicalDeviceSurfaceFormatsKHR);
109     GET_PROC(GetPhysicalDeviceSurfacePresentModesKHR);
110     GET_DEV_PROC(DeviceWaitIdle);
111     GET_DEV_PROC(QueueWaitIdle);
112     GET_DEV_PROC(DestroyDevice);
113     GET_DEV_PROC(CreateSwapchainKHR);
114     GET_DEV_PROC(DestroySwapchainKHR);
115     GET_DEV_PROC(GetSwapchainImagesKHR);
116     GET_DEV_PROC(AcquireNextImageKHR);
117     GET_DEV_PROC(QueuePresentKHR);
118     GET_DEV_PROC(GetDeviceQueue);
119 
120     fContext = GrContext::MakeVulkan(backendContext, fDisplayParams.fGrContextOptions);
121 
122     fSurface = fCreateVkSurfaceFn(fInstance);
123     if (VK_NULL_HANDLE == fSurface) {
124         this->destroyContext();
125         sk_gpu_test::FreeVulkanFeaturesStructs(&features);
126         return;
127     }
128 
129     VkBool32 supported;
130     VkResult res = fGetPhysicalDeviceSurfaceSupportKHR(fPhysicalDevice, fPresentQueueIndex,
131                                                        fSurface, &supported);
132     if (VK_SUCCESS != res) {
133         this->destroyContext();
134         sk_gpu_test::FreeVulkanFeaturesStructs(&features);
135         return;
136     }
137 
138     if (!this->createSwapchain(-1, -1, fDisplayParams)) {
139         this->destroyContext();
140         sk_gpu_test::FreeVulkanFeaturesStructs(&features);
141         return;
142     }
143 
144     // create presentQueue
145     fGetDeviceQueue(fDevice, fPresentQueueIndex, 0, &fPresentQueue);
146     sk_gpu_test::FreeVulkanFeaturesStructs(&features);
147 }
148 
createSwapchain(int width,int height,const DisplayParams & params)149 bool VulkanWindowContext::createSwapchain(int width, int height,
150                                           const DisplayParams& params) {
151     // check for capabilities
152     VkSurfaceCapabilitiesKHR caps;
153     VkResult res = fGetPhysicalDeviceSurfaceCapabilitiesKHR(fPhysicalDevice, fSurface, &caps);
154     if (VK_SUCCESS != res) {
155         return false;
156     }
157 
158     uint32_t surfaceFormatCount;
159     res = fGetPhysicalDeviceSurfaceFormatsKHR(fPhysicalDevice, fSurface, &surfaceFormatCount,
160                                               nullptr);
161     if (VK_SUCCESS != res) {
162         return false;
163     }
164 
165     SkAutoMalloc surfaceFormatAlloc(surfaceFormatCount * sizeof(VkSurfaceFormatKHR));
166     VkSurfaceFormatKHR* surfaceFormats = (VkSurfaceFormatKHR*)surfaceFormatAlloc.get();
167     res = fGetPhysicalDeviceSurfaceFormatsKHR(fPhysicalDevice, fSurface, &surfaceFormatCount,
168                                               surfaceFormats);
169     if (VK_SUCCESS != res) {
170         return false;
171     }
172 
173     uint32_t presentModeCount;
174     res = fGetPhysicalDeviceSurfacePresentModesKHR(fPhysicalDevice, fSurface, &presentModeCount,
175                                                    nullptr);
176     if (VK_SUCCESS != res) {
177         return false;
178     }
179 
180     SkAutoMalloc presentModeAlloc(presentModeCount * sizeof(VkPresentModeKHR));
181     VkPresentModeKHR* presentModes = (VkPresentModeKHR*)presentModeAlloc.get();
182     res = fGetPhysicalDeviceSurfacePresentModesKHR(fPhysicalDevice, fSurface, &presentModeCount,
183                                                    presentModes);
184     if (VK_SUCCESS != res) {
185         return false;
186     }
187 
188     VkExtent2D extent = caps.currentExtent;
189     // use the hints
190     if (extent.width == (uint32_t)-1) {
191         extent.width = width;
192         extent.height = height;
193     }
194 
195     // clamp width; to protect us from broken hints
196     if (extent.width < caps.minImageExtent.width) {
197         extent.width = caps.minImageExtent.width;
198     } else if (extent.width > caps.maxImageExtent.width) {
199         extent.width = caps.maxImageExtent.width;
200     }
201     // clamp height
202     if (extent.height < caps.minImageExtent.height) {
203         extent.height = caps.minImageExtent.height;
204     } else if (extent.height > caps.maxImageExtent.height) {
205         extent.height = caps.maxImageExtent.height;
206     }
207 
208     fWidth = (int)extent.width;
209     fHeight = (int)extent.height;
210 
211     uint32_t imageCount = caps.minImageCount + 2;
212     if (caps.maxImageCount > 0 && imageCount > caps.maxImageCount) {
213         // Application must settle for fewer images than desired:
214         imageCount = caps.maxImageCount;
215     }
216 
217     VkImageUsageFlags usageFlags = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
218                                    VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
219                                    VK_IMAGE_USAGE_TRANSFER_DST_BIT;
220     SkASSERT((caps.supportedUsageFlags & usageFlags) == usageFlags);
221     SkASSERT(caps.supportedTransforms & caps.currentTransform);
222     SkASSERT(caps.supportedCompositeAlpha & (VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR |
223                                              VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR));
224     VkCompositeAlphaFlagBitsKHR composite_alpha =
225         (caps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR) ?
226                                         VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR :
227                                         VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
228 
229     // Pick our surface format.
230     VkFormat surfaceFormat = VK_FORMAT_UNDEFINED;
231     VkColorSpaceKHR colorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR;
232     for (uint32_t i = 0; i < surfaceFormatCount; ++i) {
233         VkFormat localFormat = surfaceFormats[i].format;
234         if (GrVkFormatIsSupported(localFormat)) {
235             surfaceFormat = localFormat;
236             colorSpace = surfaceFormats[i].colorSpace;
237             break;
238         }
239     }
240     fDisplayParams = params;
241     fSampleCount = params.fMSAASampleCount;
242     fStencilBits = 8;
243 
244     if (VK_FORMAT_UNDEFINED == surfaceFormat) {
245         return false;
246     }
247 
248     SkColorType colorType;
249     switch (surfaceFormat) {
250         case VK_FORMAT_R8G8B8A8_UNORM: // fall through
251         case VK_FORMAT_R8G8B8A8_SRGB:
252             colorType = kRGBA_8888_SkColorType;
253             break;
254         case VK_FORMAT_B8G8R8A8_UNORM: // fall through
255             colorType = kBGRA_8888_SkColorType;
256             break;
257         default:
258             return false;
259     }
260 
261     // If mailbox mode is available, use it, as it is the lowest-latency non-
262     // tearing mode. If not, fall back to FIFO which is always available.
263     VkPresentModeKHR mode = VK_PRESENT_MODE_FIFO_KHR;
264     bool hasImmediate = false;
265     for (uint32_t i = 0; i < presentModeCount; ++i) {
266         // use mailbox
267         if (VK_PRESENT_MODE_MAILBOX_KHR == presentModes[i]) {
268             mode = VK_PRESENT_MODE_MAILBOX_KHR;
269         }
270         if (VK_PRESENT_MODE_IMMEDIATE_KHR == presentModes[i]) {
271             hasImmediate = true;
272         }
273     }
274     if (params.fDisableVsync && hasImmediate) {
275         mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
276     }
277 
278     VkSwapchainCreateInfoKHR swapchainCreateInfo;
279     memset(&swapchainCreateInfo, 0, sizeof(VkSwapchainCreateInfoKHR));
280     swapchainCreateInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
281     swapchainCreateInfo.surface = fSurface;
282     swapchainCreateInfo.minImageCount = imageCount;
283     swapchainCreateInfo.imageFormat = surfaceFormat;
284     swapchainCreateInfo.imageColorSpace = colorSpace;
285     swapchainCreateInfo.imageExtent = extent;
286     swapchainCreateInfo.imageArrayLayers = 1;
287     swapchainCreateInfo.imageUsage = usageFlags;
288 
289     uint32_t queueFamilies[] = { fGraphicsQueueIndex, fPresentQueueIndex };
290     if (fGraphicsQueueIndex != fPresentQueueIndex) {
291         swapchainCreateInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
292         swapchainCreateInfo.queueFamilyIndexCount = 2;
293         swapchainCreateInfo.pQueueFamilyIndices = queueFamilies;
294     } else {
295         swapchainCreateInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
296         swapchainCreateInfo.queueFamilyIndexCount = 0;
297         swapchainCreateInfo.pQueueFamilyIndices = nullptr;
298     }
299 
300     swapchainCreateInfo.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
301     swapchainCreateInfo.compositeAlpha = composite_alpha;
302     swapchainCreateInfo.presentMode = mode;
303     swapchainCreateInfo.clipped = true;
304     swapchainCreateInfo.oldSwapchain = fSwapchain;
305 
306     res = fCreateSwapchainKHR(fDevice, &swapchainCreateInfo, nullptr, &fSwapchain);
307     if (VK_SUCCESS != res) {
308         return false;
309     }
310 
311     // destroy the old swapchain
312     if (swapchainCreateInfo.oldSwapchain != VK_NULL_HANDLE) {
313         fDeviceWaitIdle(fDevice);
314 
315         this->destroyBuffers();
316 
317         fDestroySwapchainKHR(fDevice, swapchainCreateInfo.oldSwapchain, nullptr);
318     }
319 
320     this->createBuffers(swapchainCreateInfo.imageFormat, colorType);
321 
322     return true;
323 }
324 
createBuffers(VkFormat format,SkColorType colorType)325 void VulkanWindowContext::createBuffers(VkFormat format, SkColorType colorType) {
326     fGetSwapchainImagesKHR(fDevice, fSwapchain, &fImageCount, nullptr);
327     SkASSERT(fImageCount);
328     fImages = new VkImage[fImageCount];
329     fGetSwapchainImagesKHR(fDevice, fSwapchain, &fImageCount, fImages);
330 
331     // set up initial image layouts and create surfaces
332     fImageLayouts = new VkImageLayout[fImageCount];
333     fSurfaces = new sk_sp<SkSurface>[fImageCount];
334     for (uint32_t i = 0; i < fImageCount; ++i) {
335         fImageLayouts[i] = VK_IMAGE_LAYOUT_UNDEFINED;
336 
337         GrVkImageInfo info;
338         info.fImage = fImages[i];
339         info.fAlloc = GrVkAlloc();
340         info.fImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
341         info.fImageTiling = VK_IMAGE_TILING_OPTIMAL;
342         info.fFormat = format;
343         info.fLevelCount = 1;
344         info.fCurrentQueueFamily = fPresentQueueIndex;
345 
346         if (fSampleCount == 1) {
347             GrBackendRenderTarget backendRT(fWidth, fHeight, fSampleCount, info);
348 
349             fSurfaces[i] = SkSurface::MakeFromBackendRenderTarget(
350                     fContext.get(), backendRT, kTopLeft_GrSurfaceOrigin, colorType,
351                     fDisplayParams.fColorSpace, &fDisplayParams.fSurfaceProps);
352         } else {
353             GrBackendTexture backendTexture(fWidth, fHeight, info);
354 
355             fSurfaces[i] = SkSurface::MakeFromBackendTexture(
356                     fContext.get(), backendTexture, kTopLeft_GrSurfaceOrigin, fSampleCount,
357                     colorType, fDisplayParams.fColorSpace, &fDisplayParams.fSurfaceProps);
358 
359         }
360     }
361 
362     // set up the backbuffers
363     VkSemaphoreCreateInfo semaphoreInfo;
364     memset(&semaphoreInfo, 0, sizeof(VkSemaphoreCreateInfo));
365     semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
366     semaphoreInfo.pNext = nullptr;
367     semaphoreInfo.flags = 0;
368 
369     // we create one additional backbuffer structure here, because we want to
370     // give the command buffers they contain a chance to finish before we cycle back
371     fBackbuffers = new BackbufferInfo[fImageCount + 1];
372     for (uint32_t i = 0; i < fImageCount + 1; ++i) {
373         fBackbuffers[i].fImageIndex = -1;
374         GR_VK_CALL_ERRCHECK(fInterface,
375                             CreateSemaphore(fDevice, &semaphoreInfo,
376                                             nullptr, &fBackbuffers[i].fRenderSemaphore));
377     }
378     fCurrentBackbufferIndex = fImageCount;
379 }
380 
destroyBuffers()381 void VulkanWindowContext::destroyBuffers() {
382 
383     if (fBackbuffers) {
384         for (uint32_t i = 0; i < fImageCount + 1; ++i) {
385             fBackbuffers[i].fImageIndex = -1;
386             GR_VK_CALL(fInterface,
387                        DestroySemaphore(fDevice,
388                                         fBackbuffers[i].fRenderSemaphore,
389                                         nullptr));
390         }
391     }
392 
393     delete[] fBackbuffers;
394     fBackbuffers = nullptr;
395 
396     // Does this actually free the surfaces?
397     delete[] fSurfaces;
398     fSurfaces = nullptr;
399     delete[] fImageLayouts;
400     fImageLayouts = nullptr;
401     delete[] fImages;
402     fImages = nullptr;
403 }
404 
~VulkanWindowContext()405 VulkanWindowContext::~VulkanWindowContext() {
406     this->destroyContext();
407 }
408 
destroyContext()409 void VulkanWindowContext::destroyContext() {
410     if (this->isValid()) {
411         fQueueWaitIdle(fPresentQueue);
412         fDeviceWaitIdle(fDevice);
413 
414         this->destroyBuffers();
415 
416         if (VK_NULL_HANDLE != fSwapchain) {
417             fDestroySwapchainKHR(fDevice, fSwapchain, nullptr);
418             fSwapchain = VK_NULL_HANDLE;
419         }
420 
421         if (VK_NULL_HANDLE != fSurface) {
422             fDestroySurfaceKHR(fInstance, fSurface, nullptr);
423             fSurface = VK_NULL_HANDLE;
424         }
425     }
426 
427     fContext.reset();
428     fInterface.reset();
429 
430     if (VK_NULL_HANDLE != fDevice) {
431         fDestroyDevice(fDevice, nullptr);
432         fDevice = VK_NULL_HANDLE;
433     }
434 
435 #ifdef SK_ENABLE_VK_LAYERS
436     if (fDebugCallback != VK_NULL_HANDLE) {
437         fDestroyDebugReportCallbackEXT(fInstance, fDebugCallback, nullptr);
438     }
439 #endif
440 
441     fPhysicalDevice = VK_NULL_HANDLE;
442 
443     if (VK_NULL_HANDLE != fInstance) {
444         fDestroyInstance(fInstance, nullptr);
445         fInstance = VK_NULL_HANDLE;
446     }
447 }
448 
getAvailableBackbuffer()449 VulkanWindowContext::BackbufferInfo* VulkanWindowContext::getAvailableBackbuffer() {
450     SkASSERT(fBackbuffers);
451 
452     ++fCurrentBackbufferIndex;
453     if (fCurrentBackbufferIndex > fImageCount) {
454         fCurrentBackbufferIndex = 0;
455     }
456 
457     BackbufferInfo* backbuffer = fBackbuffers + fCurrentBackbufferIndex;
458     return backbuffer;
459 }
460 
getBackbufferSurface()461 sk_sp<SkSurface> VulkanWindowContext::getBackbufferSurface() {
462     BackbufferInfo* backbuffer = this->getAvailableBackbuffer();
463     SkASSERT(backbuffer);
464 
465     // semaphores should be in unsignaled state
466     VkSemaphoreCreateInfo semaphoreInfo;
467     memset(&semaphoreInfo, 0, sizeof(VkSemaphoreCreateInfo));
468     semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
469     semaphoreInfo.pNext = nullptr;
470     semaphoreInfo.flags = 0;
471     VkSemaphore semaphore;
472     GR_VK_CALL_ERRCHECK(fInterface, CreateSemaphore(fDevice, &semaphoreInfo,
473                                                     nullptr, &semaphore));
474 
475     // acquire the image
476     VkResult res = fAcquireNextImageKHR(fDevice, fSwapchain, UINT64_MAX,
477                                         semaphore, VK_NULL_HANDLE,
478                                         &backbuffer->fImageIndex);
479     if (VK_ERROR_SURFACE_LOST_KHR == res) {
480         // need to figure out how to create a new vkSurface without the platformData*
481         // maybe use attach somehow? but need a Window
482         GR_VK_CALL(fInterface, DestroySemaphore(fDevice, semaphore, nullptr));
483         return nullptr;
484     }
485     if (VK_ERROR_OUT_OF_DATE_KHR == res) {
486         // tear swapchain down and try again
487         if (!this->createSwapchain(-1, -1, fDisplayParams)) {
488             GR_VK_CALL(fInterface, DestroySemaphore(fDevice, semaphore, nullptr));
489             return nullptr;
490         }
491         backbuffer = this->getAvailableBackbuffer();
492 
493         // acquire the image
494         res = fAcquireNextImageKHR(fDevice, fSwapchain, UINT64_MAX,
495                                    semaphore, VK_NULL_HANDLE,
496                                    &backbuffer->fImageIndex);
497 
498         if (VK_SUCCESS != res) {
499             GR_VK_CALL(fInterface, DestroySemaphore(fDevice, semaphore, nullptr));
500             return nullptr;
501         }
502     }
503 
504     SkSurface* surface = fSurfaces[backbuffer->fImageIndex].get();
505 
506     GrBackendSemaphore beSemaphore;
507     beSemaphore.initVulkan(semaphore);
508 
509     surface->wait(1, &beSemaphore);
510 
511     return sk_ref_sp(surface);
512 }
513 
swapBuffers()514 void VulkanWindowContext::swapBuffers() {
515 
516     BackbufferInfo* backbuffer = fBackbuffers + fCurrentBackbufferIndex;
517     SkSurface* surface = fSurfaces[backbuffer->fImageIndex].get();
518 
519     GrBackendSemaphore beSemaphore;
520     beSemaphore.initVulkan(backbuffer->fRenderSemaphore);
521 
522     GrFlushInfo info;
523     info.fNumSemaphores = 1;
524     info.fSignalSemaphores = &beSemaphore;
525     surface->flush(SkSurface::BackendSurfaceAccess::kPresent, info);
526 
527     // Submit present operation to present queue
528     const VkPresentInfoKHR presentInfo =
529     {
530         VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, // sType
531         NULL, // pNext
532         1, // waitSemaphoreCount
533         &backbuffer->fRenderSemaphore, // pWaitSemaphores
534         1, // swapchainCount
535         &fSwapchain, // pSwapchains
536         &backbuffer->fImageIndex, // pImageIndices
537         NULL // pResults
538     };
539 
540     fQueuePresentKHR(fPresentQueue, &presentInfo);
541 }
542 
543 }   //namespace sk_app
544