1 /*
2   Copyright 2019-2020 David Robillard <d@drobilla.net>
3   Copyright 2019 Jordan Halase <jordan@halase.me>
4 
5   Permission to use, copy, modify, and/or distribute this software for any
6   purpose with or without fee is hereby granted, provided that the above
7   copyright notice and this permission notice appear in all copies.
8 
9   THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10   WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11   MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12   ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13   WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14   ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17 
18 /*
19   A simple example of drawing with Vulkan.
20 
21   For a more advanced demo that actually draws something interesting, see
22   pugl_vulkan_cxx_demo.cpp.
23 */
24 
25 #include "demo_utils.h"
26 #include "test/test_utils.h"
27 
28 #include "pugl/pugl.h"
29 #include "pugl/vulkan.h"
30 
31 #include <vulkan/vk_platform.h>
32 #include <vulkan/vulkan_core.h>
33 
34 #include <stdbool.h>
35 #include <stdint.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 
40 #define CLAMP(x, l, h) ((x) <= (l) ? (l) : (x) >= (h) ? (h) : (x))
41 
42 // Vulkan allocation callbacks which can be used for debugging
43 #define ALLOC_VK NULL
44 
45 // Helper macro for allocating arrays by type, with C++ compatible cast
46 #define AALLOC(size, Type) ((Type*)calloc(size, sizeof(Type)))
47 
48 // Helper macro for counted array arguments to make clang-format behave
49 #define COUNTED(count, ...) count, __VA_ARGS__
50 
51 /// Dynamically loaded Vulkan API functions
52 typedef struct {
53   PFN_vkCreateDebugReportCallbackEXT  vkCreateDebugReportCallbackEXT;
54   PFN_vkDestroyDebugReportCallbackEXT vkDestroyDebugReportCallbackEXT;
55 } InstanceAPI;
56 
57 /// Vulkan swapchain and everything that depends on it
58 typedef struct {
59   VkSwapchainKHR   rawSwapchain;
60   uint32_t         nImages;
61   VkExtent2D       extent;
62   VkImage*         images;
63   VkImageView*     imageViews;
64   VkFence*         fences;
65   VkCommandBuffer* commandBuffers;
66 } Swapchain;
67 
68 /// Synchronization semaphores
69 typedef struct {
70   VkSemaphore presentComplete;
71   VkSemaphore renderFinished;
72 } Sync;
73 
74 /// Vulkan state, purely Vulkan functions can depend on only this
75 typedef struct {
76   InstanceAPI                api;
77   VkInstance                 instance;
78   VkDebugReportCallbackEXT   debugCallback;
79   VkSurfaceKHR               surface;
80   VkSurfaceFormatKHR         surfaceFormat;
81   VkPresentModeKHR           presentMode;
82   VkPhysicalDeviceProperties deviceProperties;
83   VkPhysicalDevice           physicalDevice;
84   uint32_t                   graphicsIndex;
85   VkDevice                   device;
86   VkQueue                    graphicsQueue;
87   VkCommandPool              commandPool;
88   Swapchain*                 swapchain;
89   Sync                       sync;
90 } VulkanState;
91 
92 /// Complete application
93 typedef struct {
94   PuglTestOptions opts;
95   PuglWorld*      world;
96   PuglView*       view;
97   VulkanState     vk;
98   uint32_t        framesDrawn;
99   uint32_t        width;
100   uint32_t        height;
101   bool            quit;
102 } VulkanApp;
103 
104 static VKAPI_ATTR VkBool32 VKAPI_CALL
debugCallback(VkDebugReportFlagsEXT flags,VkDebugReportObjectTypeEXT objType,uint64_t obj,size_t location,int32_t code,const char * layerPrefix,const char * msg,void * userData)105 debugCallback(VkDebugReportFlagsEXT      flags,
106               VkDebugReportObjectTypeEXT objType,
107               uint64_t                   obj,
108               size_t                     location,
109               int32_t                    code,
110               const char*                layerPrefix,
111               const char*                msg,
112               void*                      userData)
113 {
114   (void)userData;
115   (void)objType;
116   (void)obj;
117   (void)location;
118   (void)code;
119 
120   if (flags & VK_DEBUG_REPORT_INFORMATION_BIT_EXT) {
121     fprintf(stderr, "note: ");
122   } else if (flags & VK_DEBUG_REPORT_WARNING_BIT_EXT) {
123     fprintf(stderr, "warning: ");
124   } else if (flags & VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT) {
125     fprintf(stderr, "performance warning: ");
126   } else if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) {
127     fprintf(stderr, "error: ");
128   } else if (flags & VK_DEBUG_REPORT_DEBUG_BIT_EXT) {
129     fprintf(stderr, "debug: ");
130   }
131 
132   fprintf(stderr, "%s: ", layerPrefix);
133   fprintf(stderr, "%s\n", msg);
134   return VK_FALSE;
135 }
136 
137 static bool
hasExtension(const char * const name,const VkExtensionProperties * const properties,const uint32_t count)138 hasExtension(const char* const                  name,
139              const VkExtensionProperties* const properties,
140              const uint32_t                     count)
141 {
142   for (uint32_t i = 0; i < count; ++i) {
143     if (!strcmp(properties[i].extensionName, name)) {
144       return true;
145     }
146   }
147 
148   return false;
149 }
150 
151 static bool
hasLayer(const char * const name,const VkLayerProperties * const properties,const uint32_t count)152 hasLayer(const char* const              name,
153          const VkLayerProperties* const properties,
154          const uint32_t                 count)
155 {
156   for (uint32_t i = 0; i < count; ++i) {
157     if (!strcmp(properties[i].layerName, name)) {
158       return true;
159     }
160   }
161 
162   return false;
163 }
164 
165 static void
pushString(const char *** const array,uint32_t * const count,const char * const string)166 pushString(const char*** const array,
167            uint32_t* const     count,
168            const char* const   string)
169 {
170   *array = (const char**)realloc(*array, (*count + 1) * sizeof(const char*));
171   (*array)[*count] = string;
172   ++*count;
173 }
174 
175 static VkResult
createInstance(VulkanApp * const app)176 createInstance(VulkanApp* const app)
177 {
178   const VkApplicationInfo appInfo = {
179     VK_STRUCTURE_TYPE_APPLICATION_INFO,
180     NULL,
181     "Pugl Vulkan Test",
182     VK_MAKE_VERSION(0, 1, 0),
183     "Pugl Vulkan Test Engine",
184     VK_MAKE_VERSION(0, 1, 0),
185     VK_MAKE_VERSION(1, 0, 0),
186   };
187 
188   // Get the number of supported extensions and layers
189   VkResult vr          = VK_SUCCESS;
190   uint32_t nExtProps   = 0;
191   uint32_t nLayerProps = 0;
192   if ((vr = vkEnumerateInstanceLayerProperties(&nLayerProps, NULL)) ||
193       (vr = vkEnumerateInstanceExtensionProperties(NULL, &nExtProps, NULL))) {
194     return vr;
195   }
196 
197   // Get properties of supported extensions
198   VkExtensionProperties* extProps = AALLOC(nExtProps, VkExtensionProperties);
199   vkEnumerateInstanceExtensionProperties(NULL, &nExtProps, extProps);
200 
201   uint32_t     nExtensions = 0;
202   const char** extensions  = NULL;
203 
204   // Add extensions required by pugl
205   uint32_t           nPuglExts = 0;
206   const char* const* puglExts  = puglGetInstanceExtensions(&nPuglExts);
207   for (uint32_t i = 0; i < nPuglExts; ++i) {
208     pushString(&extensions, &nExtensions, puglExts[i]);
209   }
210 
211   // Add extra extensions we want to use if they are supported
212   if (hasExtension("VK_EXT_debug_report", extProps, nExtProps)) {
213     pushString(&extensions, &nExtensions, "VK_EXT_debug_report");
214   }
215 
216   // Get properties of supported layers
217   VkLayerProperties* layerProps = AALLOC(nLayerProps, VkLayerProperties);
218   vkEnumerateInstanceLayerProperties(&nLayerProps, layerProps);
219 
220   // Add validation layers if error checking is enabled
221   uint32_t     nLayers = 0;
222   const char** layers  = NULL;
223   if (app->opts.errorChecking) {
224     const char* debugLayers[] = {"VK_LAYER_KHRONOS_validation",
225                                  "VK_LAYER_LUNARG_standard_validation",
226                                  NULL};
227 
228     for (const char** l = debugLayers; *l; ++l) {
229       if (hasLayer(*l, layerProps, nLayerProps)) {
230         pushString(&layers, &nLayers, *l);
231       }
232     }
233   }
234 
235   for (uint32_t i = 0; i < nExtensions; ++i) {
236     printf("Using instance extension:    %s\n", extensions[i]);
237   }
238 
239   for (uint32_t i = 0; i < nLayers; ++i) {
240     printf("Using instance layer:        %s\n", layers[i]);
241   }
242 
243   const VkInstanceCreateInfo createInfo = {
244     VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
245     NULL,
246     0,
247     &appInfo,
248     COUNTED(nLayers, layers),
249     COUNTED(nExtensions, extensions),
250   };
251 
252   if ((vr = vkCreateInstance(&createInfo, ALLOC_VK, &app->vk.instance))) {
253     logError("Could not create Vulkan Instance: %d\n", vr);
254   }
255 
256   free(layers);
257   free(extensions);
258   free(layerProps);
259   free(extProps);
260 
261   return vr;
262 }
263 
264 static VkResult
enableDebugging(VulkanState * const vk)265 enableDebugging(VulkanState* const vk)
266 {
267   vk->api.vkCreateDebugReportCallbackEXT =
268     (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(
269       vk->instance, "vkCreateDebugReportCallbackEXT");
270 
271   vk->api.vkDestroyDebugReportCallbackEXT =
272     (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(
273       vk->instance, "vkDestroyDebugReportCallbackEXT");
274 
275   if (vk->api.vkCreateDebugReportCallbackEXT) {
276     const VkDebugReportCallbackCreateInfoEXT info = {
277       VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT,
278       NULL,
279       VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT,
280       debugCallback,
281       NULL,
282     };
283 
284     VkResult vr = VK_SUCCESS;
285     if ((vr = vk->api.vkCreateDebugReportCallbackEXT(
286            vk->instance, &info, ALLOC_VK, &vk->debugCallback))) {
287       logError("Could not create debug reporter: %d\n", vr);
288       return vr;
289     }
290   }
291 
292   return VK_SUCCESS;
293 }
294 
295 static VkResult
getGraphicsQueueIndex(VkSurfaceKHR surface,VkPhysicalDevice device,uint32_t * graphicsIndex)296 getGraphicsQueueIndex(VkSurfaceKHR     surface,
297                       VkPhysicalDevice device,
298                       uint32_t*        graphicsIndex)
299 {
300   VkResult r = VK_SUCCESS;
301 
302   uint32_t nProps = 0;
303   vkGetPhysicalDeviceQueueFamilyProperties(device, &nProps, NULL);
304 
305   VkQueueFamilyProperties* props = AALLOC(nProps, VkQueueFamilyProperties);
306   vkGetPhysicalDeviceQueueFamilyProperties(device, &nProps, props);
307 
308   for (uint32_t q = 0; q < nProps; ++q) {
309     if (props[q].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
310       VkBool32 supported = false;
311       if ((r = vkGetPhysicalDeviceSurfaceSupportKHR(
312              device, q, surface, &supported))) {
313         free(props);
314         return r;
315       }
316 
317       if (supported) {
318         *graphicsIndex = q;
319         free(props);
320         return VK_SUCCESS;
321       }
322     }
323   }
324 
325   free(props);
326   return VK_ERROR_FEATURE_NOT_PRESENT;
327 }
328 
329 static bool
supportsRequiredExtensions(const VkPhysicalDevice device)330 supportsRequiredExtensions(const VkPhysicalDevice device)
331 {
332   uint32_t nExtProps = 0;
333   vkEnumerateDeviceExtensionProperties(device, NULL, &nExtProps, NULL);
334 
335   VkExtensionProperties* extProps = AALLOC(nExtProps, VkExtensionProperties);
336   vkEnumerateDeviceExtensionProperties(device, NULL, &nExtProps, extProps);
337 
338   for (uint32_t i = 0; i < nExtProps; ++i) {
339     if (!strcmp(extProps[i].extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME)) {
340       free(extProps);
341       return true;
342     }
343   }
344 
345   free(extProps);
346   return false;
347 }
348 
349 static bool
isDeviceSuitable(const VulkanState * const vk,const VkPhysicalDevice device,uint32_t * const graphicsIndex)350 isDeviceSuitable(const VulkanState* const vk,
351                  const VkPhysicalDevice   device,
352                  uint32_t* const          graphicsIndex)
353 {
354   if (!supportsRequiredExtensions(device) ||
355       getGraphicsQueueIndex(vk->surface, device, graphicsIndex)) {
356     return false;
357   }
358 
359   return true;
360 }
361 
362 /**
363    Selects a physical graphics device.
364 
365    This doesn't try to be clever, and just selects the first suitable device.
366 */
367 static VkResult
selectPhysicalDevice(VulkanState * const vk)368 selectPhysicalDevice(VulkanState* const vk)
369 {
370   VkResult vr = VK_SUCCESS;
371   if (!vk->surface) {
372     logError("Cannot select a physical device without a surface\n");
373     return VK_ERROR_SURFACE_LOST_KHR;
374   }
375 
376   uint32_t nDevices = 0;
377   if ((vr = vkEnumeratePhysicalDevices(vk->instance, &nDevices, NULL))) {
378     logError("Failed to get count of physical devices: %d\n", vr);
379     return vr;
380   }
381 
382   if (!nDevices) {
383     logError("No physical devices found\n");
384     return VK_ERROR_DEVICE_LOST;
385   }
386 
387   VkPhysicalDevice* devices = AALLOC(nDevices, VkPhysicalDevice);
388   if ((vr = vkEnumeratePhysicalDevices(vk->instance, &nDevices, devices))) {
389     logError("Failed to enumerate physical devices: %d\n", vr);
390     free(devices);
391     return vr;
392   }
393 
394   uint32_t i = 0;
395   for (i = 0; i < nDevices; ++i) {
396     VkPhysicalDeviceProperties deviceProps = {0};
397     vkGetPhysicalDeviceProperties(devices[i], &deviceProps);
398 
399     uint32_t graphicsIndex = 0;
400     if (isDeviceSuitable(vk, devices[i], &graphicsIndex)) {
401       printf("Using device %u/%u:            \"%s\"\n",
402              i + 1,
403              nDevices,
404              deviceProps.deviceName);
405       vk->deviceProperties = deviceProps;
406       vk->physicalDevice   = devices[i];
407       vk->graphicsIndex    = graphicsIndex;
408       printf("Using graphics queue family: %u\n", vk->graphicsIndex);
409       break;
410     }
411 
412     printf("Device \"%s\" not suitable\n", deviceProps.deviceName);
413   }
414 
415   if (i >= nDevices) {
416     logError("No suitable devices found\n");
417     vr = VK_ERROR_DEVICE_LOST;
418   }
419 
420   free(devices);
421   return vr;
422 }
423 
424 /// Opens the logical device and sets up the queue and command pool
425 static VkResult
openDevice(VulkanState * const vk)426 openDevice(VulkanState* const vk)
427 {
428   if (vk->device) {
429     logError("Renderer already has an opened device\n");
430     return VK_NOT_READY;
431   }
432 
433   const float       graphicsQueuePriority = 1.0f;
434   const char* const swapchainName         = VK_KHR_SWAPCHAIN_EXTENSION_NAME;
435 
436   const VkDeviceQueueCreateInfo queueCreateInfo = {
437     VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
438     NULL,
439     0,
440     vk->graphicsIndex,
441     COUNTED(1, &graphicsQueuePriority),
442   };
443 
444   const VkDeviceCreateInfo createInfo = {
445     VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
446     NULL,
447     0,
448     COUNTED(1, &queueCreateInfo),
449     COUNTED(0, NULL),
450     COUNTED(1, &swapchainName),
451     NULL,
452   };
453 
454   VkDevice device = NULL;
455   VkResult vr     = VK_SUCCESS;
456   if ((vr =
457          vkCreateDevice(vk->physicalDevice, &createInfo, ALLOC_VK, &device))) {
458     logError("Could not open device \"%s\": %d\n",
459              vk->deviceProperties.deviceName,
460              vr);
461     return vr;
462   }
463 
464   vk->device = device;
465   vkGetDeviceQueue(vk->device, vk->graphicsIndex, 0, &vk->graphicsQueue);
466 
467   const VkCommandPoolCreateInfo commandInfo = {
468     VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
469     NULL,
470     0,
471     vk->graphicsIndex,
472   };
473 
474   if ((vr = vkCreateCommandPool(
475          vk->device, &commandInfo, ALLOC_VK, &vk->commandPool))) {
476     logError("Could not create command pool: %d\n", vr);
477     return vr;
478   }
479 
480   return VK_SUCCESS;
481 }
482 
483 static const char*
presentModeString(const VkPresentModeKHR presentMode)484 presentModeString(const VkPresentModeKHR presentMode)
485 {
486   switch (presentMode) {
487   case VK_PRESENT_MODE_IMMEDIATE_KHR:
488     return "Immediate";
489   case VK_PRESENT_MODE_MAILBOX_KHR:
490     return "Mailbox";
491   case VK_PRESENT_MODE_FIFO_KHR:
492     return "FIFO";
493   case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
494     return "FIFO relaxed";
495   default:
496     return "Other";
497   }
498 }
499 
500 static bool
hasPresentMode(const VkPresentModeKHR mode,const VkPresentModeKHR * const presentModes,const uint32_t nPresentModes)501 hasPresentMode(const VkPresentModeKHR        mode,
502                const VkPresentModeKHR* const presentModes,
503                const uint32_t                nPresentModes)
504 {
505   for (uint32_t i = 0; i < nPresentModes; ++i) {
506     if (presentModes[i] == mode) {
507       return true;
508     }
509   }
510 
511   return false;
512 }
513 
514 /// Configure the surface for the currently opened device
515 static VkResult
configureSurface(VulkanState * const vk)516 configureSurface(VulkanState* const vk)
517 {
518   uint32_t nFormats = 0;
519   vkGetPhysicalDeviceSurfaceFormatsKHR(
520     vk->physicalDevice, vk->surface, &nFormats, NULL);
521   if (!nFormats) {
522     logError("No surface formats available\n");
523     return VK_ERROR_FORMAT_NOT_SUPPORTED;
524   }
525 
526   VkSurfaceFormatKHR* surfaceFormats = AALLOC(nFormats, VkSurfaceFormatKHR);
527   vkGetPhysicalDeviceSurfaceFormatsKHR(
528     vk->physicalDevice, vk->surface, &nFormats, surfaceFormats);
529 
530   const VkSurfaceFormatKHR want = {VK_FORMAT_B8G8R8A8_UNORM,
531                                    VK_COLOR_SPACE_SRGB_NONLINEAR_KHR};
532 
533   uint32_t formatIndex = 0;
534   for (formatIndex = 0; formatIndex < nFormats; ++formatIndex) {
535     if (surfaceFormats[formatIndex].format == want.format &&
536         surfaceFormats[formatIndex].colorSpace == want.colorSpace) {
537       vk->surfaceFormat = want;
538       break;
539     }
540   }
541   free(surfaceFormats);
542   if (formatIndex >= nFormats) {
543     logError("Could not find a suitable surface format\n");
544     return VK_ERROR_FORMAT_NOT_SUPPORTED;
545   }
546 
547   uint32_t nPresentModes = 0;
548   vkGetPhysicalDeviceSurfacePresentModesKHR(
549     vk->physicalDevice, vk->surface, &nPresentModes, NULL);
550   if (!nPresentModes) {
551     logError("No present modes available\n");
552     return VK_ERROR_FORMAT_NOT_SUPPORTED;
553   }
554 
555   VkPresentModeKHR* presentModes = AALLOC(nPresentModes, VkPresentModeKHR);
556   vkGetPhysicalDeviceSurfacePresentModesKHR(
557     vk->physicalDevice, vk->surface, &nPresentModes, presentModes);
558 
559   const VkPresentModeKHR tryModes[] = {
560     VK_PRESENT_MODE_MAILBOX_KHR,
561     VK_PRESENT_MODE_FIFO_RELAXED_KHR,
562     VK_PRESENT_MODE_FIFO_KHR,
563     VK_PRESENT_MODE_IMMEDIATE_KHR,
564   };
565 
566   VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR;
567   for (uint32_t i = 0; i < sizeof(tryModes) / sizeof(VkPresentModeKHR); ++i) {
568     if (hasPresentMode(tryModes[i], presentModes, nPresentModes)) {
569       presentMode = tryModes[i];
570       break;
571     }
572   }
573 
574   free(presentModes);
575   vk->presentMode = presentMode;
576   printf("Using present mode:          \"%s\" (%u)\n",
577          presentModeString(presentMode),
578          presentMode);
579 
580   return VK_SUCCESS;
581 }
582 
583 static VkResult
createRawSwapchain(VulkanState * const vk,const uint32_t width,const uint32_t height)584 createRawSwapchain(VulkanState* const vk,
585                    const uint32_t     width,
586                    const uint32_t     height)
587 {
588   VkSurfaceCapabilitiesKHR surfaceCapabilities;
589   VkResult                 vr = VK_SUCCESS;
590   if ((vr = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
591          vk->physicalDevice, vk->surface, &surfaceCapabilities))) {
592     logError("Could not get surface capabilities: %d\n", vr);
593     return vr;
594   }
595 
596   /* There is a known race condition with window/surface sizes, so we clamp
597      to what Vulkan reports and hope for the best. */
598 
599   vk->swapchain->extent.width = CLAMP(width,
600                                       surfaceCapabilities.minImageExtent.width,
601                                       surfaceCapabilities.maxImageExtent.width);
602 
603   vk->swapchain->extent.height =
604     CLAMP(height,
605           surfaceCapabilities.minImageExtent.height,
606           surfaceCapabilities.maxImageExtent.height);
607 
608   vk->swapchain->nImages = surfaceCapabilities.minImageCount;
609 
610   const VkSwapchainCreateInfoKHR createInfo = {
611     VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
612     NULL,
613     0,
614     vk->surface,
615     vk->swapchain->nImages,
616     vk->surfaceFormat.format,
617     VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,
618     vk->swapchain->extent,
619     1,
620     (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT),
621     VK_SHARING_MODE_EXCLUSIVE,
622     COUNTED(0, NULL),
623     surfaceCapabilities.currentTransform,
624     VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
625     vk->presentMode,
626     VK_TRUE,
627     0,
628   };
629 
630   if ((vr = vkCreateSwapchainKHR(
631          vk->device, &createInfo, ALLOC_VK, &vk->swapchain->rawSwapchain))) {
632     logError("Could not create swapchain: %d\n", vr);
633     return vr;
634   }
635 
636   return VK_SUCCESS;
637 }
638 
639 static VkResult
recordCommandBuffers(VulkanState * const vk)640 recordCommandBuffers(VulkanState* const vk)
641 {
642   const VkClearColorValue clearValue = {{
643     0xA4 / (float)0x100, // R
644     0x1E / (float)0x100, // G
645     0x22 / (float)0x100, // B
646     0xFF / (float)0x100, // A
647   }};
648 
649   const VkImageSubresourceRange range = {
650     VK_IMAGE_ASPECT_COLOR_BIT,
651     0,
652     1,
653     0,
654     1,
655   };
656 
657   const VkCommandBufferBeginInfo beginInfo = {
658     VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
659     NULL,
660     VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT,
661     NULL,
662   };
663 
664   for (uint32_t i = 0; i < vk->swapchain->nImages; ++i) {
665     const VkImageMemoryBarrier toClearBarrier = {
666       VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
667       NULL,
668       VK_ACCESS_MEMORY_READ_BIT,
669       VK_ACCESS_TRANSFER_WRITE_BIT,
670       VK_IMAGE_LAYOUT_UNDEFINED,
671       VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
672       vk->graphicsIndex,
673       vk->graphicsIndex,
674       vk->swapchain->images[i],
675       range,
676     };
677 
678     const VkImageMemoryBarrier toPresentBarrier = {
679       VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
680       NULL,
681       VK_ACCESS_TRANSFER_WRITE_BIT,
682       VK_ACCESS_MEMORY_READ_BIT,
683       VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
684       VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
685       vk->graphicsIndex,
686       vk->graphicsIndex,
687       vk->swapchain->images[i],
688       range,
689     };
690 
691     vkBeginCommandBuffer(vk->swapchain->commandBuffers[i], &beginInfo);
692 
693     vkCmdPipelineBarrier(vk->swapchain->commandBuffers[i],
694                          VK_PIPELINE_STAGE_TRANSFER_BIT,
695                          VK_PIPELINE_STAGE_TRANSFER_BIT,
696                          0,
697                          COUNTED(0, NULL),
698                          COUNTED(0, NULL),
699                          COUNTED(1, &toClearBarrier));
700 
701     vkCmdClearColorImage(vk->swapchain->commandBuffers[i],
702                          vk->swapchain->images[i],
703                          VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
704                          &clearValue,
705                          COUNTED(1, &range));
706 
707     vkCmdPipelineBarrier(vk->swapchain->commandBuffers[i],
708                          VK_PIPELINE_STAGE_TRANSFER_BIT,
709                          VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
710                          0,
711                          COUNTED(0, NULL),
712                          COUNTED(0, NULL),
713                          COUNTED(1, &toPresentBarrier));
714 
715     vkEndCommandBuffer(vk->swapchain->commandBuffers[i]);
716   }
717 
718   return VK_SUCCESS;
719 }
720 
721 static VkResult
createSwapchain(VulkanState * const vk,const uint32_t width,const uint32_t height)722 createSwapchain(VulkanState* const vk,
723                 const uint32_t     width,
724                 const uint32_t     height)
725 {
726   VkResult vr = VK_SUCCESS;
727 
728   vk->swapchain = AALLOC(1, Swapchain);
729   if ((vr = createRawSwapchain(vk, width, height))) {
730     return vr;
731   }
732 
733   if ((vr = vkGetSwapchainImagesKHR(vk->device,
734                                     vk->swapchain->rawSwapchain,
735                                     &vk->swapchain->nImages,
736                                     NULL))) {
737     logError("Failed to query swapchain images: %d\n", vr);
738     return vr;
739   }
740 
741   vk->swapchain->images = AALLOC(vk->swapchain->nImages, VkImage);
742   if ((vr = vkGetSwapchainImagesKHR(vk->device,
743                                     vk->swapchain->rawSwapchain,
744                                     &vk->swapchain->nImages,
745                                     vk->swapchain->images))) {
746     logError("Failed to get swapchain images: %d\n", vr);
747     return vr;
748   }
749 
750   const VkCommandBufferAllocateInfo allocInfo = {
751     VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
752     NULL,
753     vk->commandPool,
754     VK_COMMAND_BUFFER_LEVEL_PRIMARY,
755     vk->swapchain->nImages,
756   };
757 
758   vk->swapchain->commandBuffers =
759     AALLOC(vk->swapchain->nImages, VkCommandBuffer);
760 
761   if ((vr = vkAllocateCommandBuffers(
762          vk->device, &allocInfo, vk->swapchain->commandBuffers))) {
763     logError("Could not allocate command buffers: %d\n", vr);
764     return vr;
765   }
766 
767   const VkFenceCreateInfo fenceInfo = {
768     VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
769     NULL,
770     VK_FENCE_CREATE_SIGNALED_BIT,
771   };
772   vk->swapchain->fences = AALLOC(vk->swapchain->nImages, VkFence);
773 
774   for (uint32_t i = 0; i < vk->swapchain->nImages; ++i) {
775     if ((vr = vkCreateFence(
776            vk->device, &fenceInfo, ALLOC_VK, &vk->swapchain->fences[i]))) {
777       logError("Could not create render finished fence: %d\n", vr);
778       return vr;
779     }
780   }
781 
782   if ((vr = recordCommandBuffers(vk))) {
783     logError("Failed to record command buffers\n");
784     return vr;
785   }
786 
787   return VK_SUCCESS;
788 }
789 
790 static void
destroySwapchain(VulkanState * const vk,Swapchain * const swapchain)791 destroySwapchain(VulkanState* const vk, Swapchain* const swapchain)
792 {
793   if (!swapchain) {
794     return;
795   }
796 
797   for (uint32_t i = 0; i < swapchain->nImages; ++i) {
798     if (swapchain->fences[i]) {
799       vkDestroyFence(vk->device, swapchain->fences[i], ALLOC_VK);
800     }
801 
802     if (swapchain->imageViews && swapchain->imageViews[i]) {
803       vkDestroyImageView(vk->device, swapchain->imageViews[i], ALLOC_VK);
804     }
805   }
806 
807   free(swapchain->fences);
808   swapchain->fences = NULL;
809   free(swapchain->imageViews);
810   swapchain->imageViews = NULL;
811 
812   if (swapchain->images) {
813     free(swapchain->images);
814     swapchain->images = NULL;
815   }
816 
817   if (swapchain->commandBuffers) {
818     vkFreeCommandBuffers(vk->device,
819                          vk->commandPool,
820                          swapchain->nImages,
821                          swapchain->commandBuffers);
822     free(swapchain->commandBuffers);
823   }
824 
825   if (swapchain->rawSwapchain) {
826     vkDestroySwapchainKHR(vk->device, swapchain->rawSwapchain, ALLOC_VK);
827   }
828 
829   free(swapchain);
830 }
831 
832 static VkResult
createSyncObjects(VulkanState * const vk)833 createSyncObjects(VulkanState* const vk)
834 {
835   const VkSemaphoreCreateInfo info = {
836     VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
837     NULL,
838     0,
839   };
840 
841   vkCreateSemaphore(vk->device, &info, ALLOC_VK, &vk->sync.presentComplete);
842   vkCreateSemaphore(vk->device, &info, ALLOC_VK, &vk->sync.renderFinished);
843   return VK_SUCCESS;
844 }
845 
846 static void
destroySyncObjects(VulkanState * const vk)847 destroySyncObjects(VulkanState* const vk)
848 {
849   if (vk->sync.renderFinished) {
850     vkDestroySemaphore(vk->device, vk->sync.renderFinished, ALLOC_VK);
851     vk->sync.renderFinished = VK_NULL_HANDLE;
852   }
853   if (vk->sync.presentComplete) {
854     vkDestroySemaphore(vk->device, vk->sync.presentComplete, ALLOC_VK);
855     vk->sync.presentComplete = VK_NULL_HANDLE;
856   }
857 }
858 
859 static void
closeDevice(VulkanState * const vk)860 closeDevice(VulkanState* const vk)
861 {
862   if (vk->device) {
863     vkDeviceWaitIdle(vk->device);
864     destroySyncObjects(vk);
865     destroySwapchain(vk, vk->swapchain);
866     if (vk->commandPool) {
867       vkDestroyCommandPool(vk->device, vk->commandPool, ALLOC_VK);
868       vk->commandPool = VK_NULL_HANDLE;
869     }
870     vk->graphicsQueue = VK_NULL_HANDLE;
871     vkDestroyDevice(vk->device, ALLOC_VK);
872     vk->device = VK_NULL_HANDLE;
873   }
874 }
875 
876 static void
destroyWorld(VulkanApp * const app)877 destroyWorld(VulkanApp* const app)
878 {
879   VulkanState* vk = &app->vk;
880 
881   if (vk) {
882     closeDevice(vk);
883 
884     if (app->view) {
885       puglHide(app->view);
886       puglFreeView(app->view);
887       app->view = NULL;
888     }
889     if (vk->debugCallback && vk->api.vkDestroyDebugReportCallbackEXT) {
890       vk->api.vkDestroyDebugReportCallbackEXT(
891         vk->instance, vk->debugCallback, ALLOC_VK);
892       vk->debugCallback = VK_NULL_HANDLE;
893     }
894     if (vk->surface) {
895       vkDestroySurfaceKHR(vk->instance, vk->surface, ALLOC_VK);
896       vk->surface = VK_NULL_HANDLE;
897     }
898     if (vk->instance) {
899       fflush(stderr);
900       vkDestroyInstance(vk->instance, ALLOC_VK);
901       vk->instance = VK_NULL_HANDLE;
902     }
903     if (app->world) {
904       puglFreeWorld(app->world);
905       app->world = NULL;
906     }
907   }
908 }
909 
910 static PuglStatus
onConfigure(PuglView * const view,const double width,const double height)911 onConfigure(PuglView* const view, const double width, const double height)
912 {
913   VulkanApp* const app = (VulkanApp*)puglGetHandle(view);
914 
915   // We just record the size here and lazily resize the surface when exposed
916   app->width  = (uint32_t)width;
917   app->height = (uint32_t)height;
918 
919   return PUGL_SUCCESS;
920 }
921 
922 static PuglStatus
recreateSwapchain(VulkanState * const vk,const uint32_t width,const uint32_t height)923 recreateSwapchain(VulkanState* const vk,
924                   const uint32_t     width,
925                   const uint32_t     height)
926 {
927   vkDeviceWaitIdle(vk->device);
928   destroySwapchain(vk, vk->swapchain);
929 
930   if (createSwapchain(vk, width, height)) {
931     logError("Failed to recreate swapchain\n");
932     return PUGL_UNKNOWN_ERROR;
933   }
934 
935   return PUGL_SUCCESS;
936 }
937 
938 static PuglStatus
onExpose(PuglView * const view)939 onExpose(PuglView* const view)
940 {
941   VulkanApp*   app        = (VulkanApp*)puglGetHandle(view);
942   VulkanState* vk         = &app->vk;
943   uint32_t     imageIndex = 0;
944   VkResult     result     = VK_SUCCESS;
945 
946   // Recreate swapchain if the window size has changed
947   const Swapchain* swapchain = vk->swapchain;
948   if (swapchain->extent.width != app->width ||
949       swapchain->extent.height != app->height) {
950     recreateSwapchain(vk, app->width, app->height);
951   }
952 
953   // Acquire the next image to render, rebuilding if necessary
954   while ((result = vkAcquireNextImageKHR(vk->device,
955                                          vk->swapchain->rawSwapchain,
956                                          UINT64_MAX,
957                                          vk->sync.presentComplete,
958                                          VK_NULL_HANDLE,
959                                          &imageIndex))) {
960     switch (result) {
961     case VK_SUCCESS:
962       break;
963     case VK_SUBOPTIMAL_KHR:
964     case VK_ERROR_OUT_OF_DATE_KHR:
965       recreateSwapchain(vk, app->width, app->height);
966       continue;
967     default:
968       logError("Could not acquire swapchain image: %d\n", result);
969       return PUGL_UNKNOWN_ERROR;
970     }
971   }
972 
973   // Wait until we can start rendering this frame
974   vkWaitForFences(vk->device,
975                   COUNTED(1, &vk->swapchain->fences[imageIndex]),
976                   VK_TRUE,
977                   UINT64_MAX);
978   vkResetFences(vk->device, 1, &vk->swapchain->fences[imageIndex]);
979 
980   const VkPipelineStageFlags waitStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
981 
982   // Submit command buffer to render this frame
983   const VkSubmitInfo submitInfo = {
984     VK_STRUCTURE_TYPE_SUBMIT_INFO,
985     NULL,
986     COUNTED(1, &vk->sync.presentComplete),
987     &waitStage,
988     COUNTED(1, &vk->swapchain->commandBuffers[imageIndex]),
989     COUNTED(1, &vk->sync.renderFinished)};
990   if ((result = vkQueueSubmit(vk->graphicsQueue,
991                               1,
992                               &submitInfo,
993                               vk->swapchain->fences[imageIndex]))) {
994     logError("Could not submit to queue: %d\n", result);
995     return PUGL_FAILURE;
996   }
997 
998   // Present this frame
999   const VkPresentInfoKHR presentInfo = {
1000     VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
1001     NULL,
1002     COUNTED(1, &vk->sync.renderFinished),
1003     COUNTED(1, &vk->swapchain->rawSwapchain, &imageIndex, NULL),
1004   };
1005   if ((result = vkQueuePresentKHR(vk->graphicsQueue, &presentInfo))) {
1006     logError("Could not present image: %d\n", result);
1007   }
1008 
1009   if (app->opts.continuous) {
1010     ++app->framesDrawn;
1011   }
1012 
1013   return PUGL_SUCCESS;
1014 }
1015 
1016 static PuglStatus
onEvent(PuglView * const view,const PuglEvent * const e)1017 onEvent(PuglView* const view, const PuglEvent* const e)
1018 {
1019   VulkanApp* const app = (VulkanApp*)puglGetHandle(view);
1020 
1021   printEvent(e, "Event: ", app->opts.verbose);
1022 
1023   switch (e->type) {
1024   case PUGL_EXPOSE:
1025     return onExpose(view);
1026   case PUGL_CONFIGURE:
1027     return onConfigure(view, e->configure.width, e->configure.height);
1028   case PUGL_CLOSE:
1029     app->quit = 1;
1030     break;
1031   case PUGL_KEY_PRESS:
1032     switch (e->key.key) {
1033     case PUGL_KEY_ESCAPE:
1034     case 'q':
1035       app->quit = 1;
1036       break;
1037     }
1038     break;
1039   default:
1040     break;
1041   }
1042   return PUGL_SUCCESS;
1043 }
1044 
1045 int
main(int argc,char ** argv)1046 main(int argc, char** argv)
1047 {
1048   VulkanApp      app           = {0};
1049   VulkanState*   vk            = &app.vk;
1050   const uint32_t defaultWidth  = 640;
1051   const uint32_t defaultHeight = 360;
1052   const PuglRect frame         = {0, 0, defaultWidth, defaultHeight};
1053 
1054   // Parse command line options
1055   app.opts = puglParseTestOptions(&argc, &argv);
1056   if (app.opts.help) {
1057     puglPrintTestUsage(argv[0], "");
1058     return 0;
1059   }
1060 
1061   // Create world and view
1062   if (!(app.world = puglNewWorld(PUGL_PROGRAM, PUGL_WORLD_THREADS))) {
1063     return logError("Failed to create world\n");
1064   }
1065 
1066   if (!(app.view = puglNewView(app.world))) {
1067     puglFreeWorld(app.world);
1068     return logError("Failed to create Pugl World and View\n");
1069   }
1070 
1071   // Create Vulkan instance
1072   if (createInstance(&app)) {
1073     puglFreeWorld(app.world);
1074     return logError("Failed to create instance\n");
1075   }
1076 
1077   // Create window
1078   puglSetWindowTitle(app.view, "Pugl Vulkan");
1079   puglSetFrame(app.view, frame);
1080   puglSetHandle(app.view, &app);
1081   puglSetBackend(app.view, puglVulkanBackend());
1082   puglSetViewHint(app.view, PUGL_RESIZABLE, app.opts.resizable);
1083   puglSetEventFunc(app.view, onEvent);
1084   const PuglStatus st = puglRealize(app.view);
1085   if (st) {
1086     puglFreeWorld(app.world);
1087     puglFreeView(app.view);
1088     return logError("Failed to create window (%s)\n", puglStrerror(st));
1089   }
1090 
1091   // Create Vulkan surface for Window
1092   PuglVulkanLoader* loader = puglNewVulkanLoader(app.world);
1093   if (puglCreateSurface(puglGetInstanceProcAddrFunc(loader),
1094                         app.view,
1095                         vk->instance,
1096                         ALLOC_VK,
1097                         &vk->surface)) {
1098     return logError("Failed to create surface\n");
1099   }
1100 
1101   // Set up Vulkan
1102   VkResult vr = VK_SUCCESS;
1103   if ((vr = enableDebugging(vk)) ||                              //
1104       (vr = selectPhysicalDevice(vk)) ||                         //
1105       (vr = openDevice(vk)) ||                                   //
1106       (vr = configureSurface(vk)) ||                             //
1107       (vr = createSwapchain(vk, defaultWidth, defaultHeight)) || //
1108       (vr = createSyncObjects(vk))) {
1109     destroyWorld(&app);
1110     return logError("Failed to set up graphics (%d)\n", vr);
1111   }
1112 
1113   printf("Swapchain images:            %u\n", app.vk.swapchain->nImages);
1114 
1115   PuglFpsPrinter fpsPrinter = {puglGetTime(app.world)};
1116   puglShow(app.view);
1117   while (!app.quit) {
1118     puglUpdate(app.world, -1.0);
1119 
1120     if (app.opts.continuous) {
1121       puglPrintFps(app.world, &fpsPrinter, &app.framesDrawn);
1122     }
1123   }
1124 
1125   destroyWorld(&app);
1126   return 0;
1127 }
1128