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