1 /*
2   Copyright 2019-2020 David Robillard <d@drobilla.net>
3 
4   Permission to use, copy, modify, and/or distribute this software for any
5   purpose with or without fee is hereby granted, provided that the above
6   copyright notice and this permission notice appear in all copies.
7 
8   THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9   WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10   MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11   ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12   WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13   ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16 
17 /*
18   An example of drawing with Vulkan.
19 
20   This is an example of using Vulkan for pixel-perfect 2D drawing.  It uses
21   the same data and shaders as pugl_shader_demo.c and attempts to draw the
22   same thing, except using Vulkan.
23 
24   Since Vulkan is a complicated and very verbose API, this example is
25   unfortunately much larger than the others.  You should not use this as a
26   resource to learn Vulkan, but it provides a decent demo of using Vulkan with
27   Pugl that works nicely on all supported platforms.
28 */
29 
30 #include "demo_utils.h"
31 #include "file_utils.h"
32 #include "rects.h"
33 #include "test/test_utils.h"
34 
35 #include "sybok.hpp"
36 
37 #include "pugl/pugl.h"
38 #include "pugl/pugl.hpp"
39 #include "pugl/vulkan.hpp"
40 
41 #include <vulkan/vk_platform.h>
42 
43 #include <algorithm>
44 #include <array>
45 #include <cassert>
46 #include <cstddef>
47 #include <cstdint>
48 #include <cstdio>
49 #include <cstdlib>
50 #include <cstring>
51 #include <initializer_list>
52 #include <iomanip>
53 #include <iostream>
54 #include <memory>
55 #include <string>
56 #include <vector>
57 
58 namespace {
59 
60 constexpr uintptr_t resizeTimerId = 1u;
61 
62 struct PhysicalDeviceSelection {
63   sk::PhysicalDevice physicalDevice;
64   uint32_t           graphicsFamilyIndex;
65 };
66 
67 /// Basic Vulkan context associated with the window
68 struct VulkanContext {
69   VkResult init(pugl::VulkanLoader& loader, const PuglTestOptions& opts);
70 
71   sk::VulkanApi              vk;
72   sk::Instance               instance;
73   sk::DebugReportCallbackEXT debugCallback;
74 };
75 
76 /// Basic setup of graphics device
77 struct GraphicsDevice {
78   VkResult init(const pugl::VulkanLoader& loader,
79                 const VulkanContext&      context,
80                 pugl::View&               view,
81                 const PuglTestOptions&    opts);
82 
83   sk::SurfaceKHR     surface;
84   sk::PhysicalDevice physicalDevice{};
85   uint32_t           graphicsIndex{};
86   VkSurfaceFormatKHR surfaceFormat{};
87   VkPresentModeKHR   presentMode{};
88   VkPresentModeKHR   resizePresentMode{};
89   sk::Device         device{};
90   sk::Queue          graphicsQueue{};
91   sk::CommandPool    commandPool{};
92 };
93 
94 /// Buffer allocated on the GPU
95 struct Buffer {
96   VkResult init(const sk::VulkanApi&  vk,
97                 const GraphicsDevice& gpu,
98                 VkDeviceSize          size,
99                 VkBufferUsageFlags    usage,
100                 VkMemoryPropertyFlags properties);
101 
102   sk::Buffer       buffer;
103   sk::DeviceMemory deviceMemory;
104 };
105 
106 /// A set of frames that can be rendered concurrently
107 struct Swapchain {
108   VkResult init(const sk::VulkanApi&     vk,
109                 const GraphicsDevice&    gpu,
110                 VkSurfaceCapabilitiesKHR capabilities,
111                 VkExtent2D               extent,
112                 VkSwapchainKHR           oldSwapchain,
113                 bool                     resizing);
114 
115   VkSurfaceCapabilitiesKHR   capabilities{};
116   VkExtent2D                 extent{};
117   sk::SwapchainKHR           swapchain{};
118   std::vector<sk::ImageView> imageViews{};
119 };
120 
121 /// A pass that renders to a target
122 struct RenderPass {
123   VkResult init(const sk::VulkanApi&  vk,
124                 const GraphicsDevice& gpu,
125                 const Swapchain&      swapchain);
126 
127   sk::RenderPass                                   renderPass;
128   std::vector<sk::Framebuffer>                     framebuffers;
129   sk::CommandBuffers<std::vector<VkCommandBuffer>> commandBuffers;
130 };
131 
132 /// Uniform buffer for constant data used in shaders
133 struct UniformBufferObject {
134   mat4 projection;
135 };
136 
137 /// Rectangle data that does not depend on renderer configuration
138 struct RectData {
139   VkResult init(const sk::VulkanApi&  vk,
140                 const GraphicsDevice& gpu,
141                 size_t                nRects);
142 
143   sk::DescriptorSetLayout descriptorSetLayout{};
144   Buffer                  uniformBuffer{};
145   sk::MappedMemory        uniformData{};
146   Buffer                  modelBuffer{};
147   Buffer                  instanceBuffer{};
148   sk::MappedMemory        vertexData{};
149   size_t                  numRects{};
150 };
151 
152 /// Shader modules for drawing rectangles
153 struct RectShaders {
154   VkResult init(const sk::VulkanApi&  vk,
155                 const GraphicsDevice& gpu,
156                 const std::string&    programPath);
157 
158   sk::ShaderModule vert{};
159   sk::ShaderModule frag{};
160 };
161 
162 /// A pipeline to render rectangles with our shaders
163 struct RectPipeline {
164   VkResult init(const sk::VulkanApi&  vk,
165                 const GraphicsDevice& gpu,
166                 const RectData&       rectData,
167                 const RectShaders&    shaders,
168                 const Swapchain&      swapchain,
169                 const RenderPass&     renderPass);
170 
171   sk::DescriptorPool                               descriptorPool{};
172   sk::DescriptorSets<std::vector<VkDescriptorSet>> descriptorSets{};
173   sk::PipelineLayout                               pipelineLayout{};
174   std::array<sk::Pipeline, 1>                      pipelines{};
175   uint32_t                                         numImages{};
176 };
177 
178 /// Synchronization primitives used to coordinate drawing frames
179 struct RenderSync {
180   VkResult init(const sk::VulkanApi& vk,
181                 const sk::Device&    device,
182                 uint32_t             numImages);
183 
184   std::vector<sk::Semaphore> imageAvailable{};
185   std::vector<sk::Semaphore> renderFinished{};
186   std::vector<sk::Fence>     inFlight{};
187   size_t                     currentFrame{};
188 };
189 
190 /// Renderer that owns the above and everything required to draw
191 struct Renderer {
192   VkResult init(const sk::VulkanApi&  vk,
193                 const GraphicsDevice& gpu,
194                 const RectData&       rectData,
195                 const RectShaders&    rectShaders,
196                 VkExtent2D            extent,
197                 bool                  resizing);
198 
199   VkResult recreate(const sk::VulkanApi&  vk,
200                     const sk::SurfaceKHR& surface,
201                     const GraphicsDevice& gpu,
202                     const RectData&       rectData,
203                     const RectShaders&    rectShaders,
204                     VkExtent2D            extent,
205                     bool                  resizing);
206 
207   Swapchain    swapchain;
208   RenderPass   renderPass;
209   RectPipeline rectPipeline;
210   RenderSync   sync;
211 };
212 
213 VkResult
selectSurfaceFormat(const sk::VulkanApi & vk,const sk::PhysicalDevice & physicalDevice,const sk::SurfaceKHR & surface,VkSurfaceFormatKHR & surfaceFormat)214 selectSurfaceFormat(const sk::VulkanApi&      vk,
215                     const sk::PhysicalDevice& physicalDevice,
216                     const sk::SurfaceKHR&     surface,
217                     VkSurfaceFormatKHR&       surfaceFormat)
218 {
219   std::vector<VkSurfaceFormatKHR> formats;
220   if (VkResult r = vk.getPhysicalDeviceSurfaceFormatsKHR(
221         physicalDevice, surface, formats)) {
222     return r;
223   }
224 
225   for (const auto& format : formats) {
226     if (format.format == VK_FORMAT_B8G8R8A8_UNORM &&
227         format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
228       surfaceFormat = format;
229       return VK_SUCCESS;
230     }
231   }
232 
233   return VK_ERROR_FORMAT_NOT_SUPPORTED;
234 }
235 
236 VkResult
selectPresentMode(const sk::VulkanApi & vk,const sk::PhysicalDevice & physicalDevice,const sk::SurfaceKHR & surface,const bool multiBuffer,const bool sync,VkPresentModeKHR & presentMode)237 selectPresentMode(const sk::VulkanApi&      vk,
238                   const sk::PhysicalDevice& physicalDevice,
239                   const sk::SurfaceKHR&     surface,
240                   const bool                multiBuffer,
241                   const bool                sync,
242                   VkPresentModeKHR&         presentMode)
243 {
244   // Map command line options to mode priorities
245   static constexpr VkPresentModeKHR priorities[][2][4] = {
246     {
247       // No double buffer, no sync
248       {VK_PRESENT_MODE_IMMEDIATE_KHR,
249        VK_PRESENT_MODE_MAILBOX_KHR,
250        VK_PRESENT_MODE_FIFO_RELAXED_KHR,
251        VK_PRESENT_MODE_FIFO_KHR},
252 
253       // No double buffer, sync (nonsense, map to FIFO relaxed)
254       {VK_PRESENT_MODE_FIFO_RELAXED_KHR,
255        VK_PRESENT_MODE_FIFO_KHR,
256        VK_PRESENT_MODE_MAILBOX_KHR,
257        VK_PRESENT_MODE_IMMEDIATE_KHR},
258     },
259     {
260       // Double buffer, no sync
261       {
262         VK_PRESENT_MODE_MAILBOX_KHR,
263         VK_PRESENT_MODE_IMMEDIATE_KHR,
264         VK_PRESENT_MODE_FIFO_RELAXED_KHR,
265         VK_PRESENT_MODE_FIFO_KHR,
266       },
267 
268       // Double buffer, sync
269       {VK_PRESENT_MODE_FIFO_KHR,
270        VK_PRESENT_MODE_FIFO_RELAXED_KHR,
271        VK_PRESENT_MODE_MAILBOX_KHR,
272        VK_PRESENT_MODE_IMMEDIATE_KHR},
273     },
274   };
275 
276   std::vector<VkPresentModeKHR> modes;
277   if (VkResult r = vk.getPhysicalDeviceSurfacePresentModesKHR(
278         physicalDevice, surface, modes)) {
279     return r;
280   }
281 
282   const auto& tryModes = priorities[bool(multiBuffer)][bool(sync)];
283   for (const auto m : tryModes) {
284     if (std::find(modes.begin(), modes.end(), m) != modes.end()) {
285       presentMode = m;
286       return VK_SUCCESS;
287     }
288   }
289 
290   return VK_ERROR_INCOMPATIBLE_DRIVER;
291 }
292 
293 VkResult
openDevice(const sk::VulkanApi & vk,const sk::PhysicalDevice & physicalDevice,const uint32_t graphicsFamilyIndex,sk::Device & device)294 openDevice(const sk::VulkanApi&      vk,
295            const sk::PhysicalDevice& physicalDevice,
296            const uint32_t            graphicsFamilyIndex,
297            sk::Device&               device)
298 {
299   const float       graphicsQueuePriority = 1.0f;
300   const char* const swapchainName         = "VK_KHR_swapchain";
301 
302   const VkDeviceQueueCreateInfo queueCreateInfo{
303     VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
304     nullptr,
305     0u,
306     graphicsFamilyIndex,
307     SK_COUNTED(1u, &graphicsQueuePriority),
308   };
309 
310   const VkDeviceCreateInfo createInfo{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
311                                       nullptr,
312                                       0u,
313                                       SK_COUNTED(1u, &queueCreateInfo),
314                                       SK_COUNTED(0u, nullptr), // Deprecated
315                                       SK_COUNTED(1u, &swapchainName),
316                                       nullptr};
317 
318   return vk.createDevice(physicalDevice, createInfo, device);
319 }
320 
321 /// Return whether the physical device supports the extensions we require
322 VkResult
deviceSupportsRequiredExtensions(const sk::VulkanApi & vk,const sk::PhysicalDevice & device,bool & supported)323 deviceSupportsRequiredExtensions(const sk::VulkanApi&      vk,
324                                  const sk::PhysicalDevice& device,
325                                  bool&                     supported)
326 {
327   VkResult r = VK_SUCCESS;
328 
329   std::vector<VkExtensionProperties> props;
330   if ((r = vk.enumerateDeviceExtensionProperties(device, props))) {
331     return r;
332   }
333 
334   supported = std::any_of(
335     props.begin(), props.end(), [&](const VkExtensionProperties& e) {
336       return !strcmp(e.extensionName, "VK_KHR_swapchain");
337     });
338 
339   return VK_SUCCESS;
340 }
341 
342 /// Return the index of the graphics queue, if there is one
343 VkResult
findGraphicsQueue(const sk::VulkanApi & vk,const sk::SurfaceKHR & surface,const sk::PhysicalDevice & device,uint32_t & queueIndex)344 findGraphicsQueue(const sk::VulkanApi&      vk,
345                   const sk::SurfaceKHR&     surface,
346                   const sk::PhysicalDevice& device,
347                   uint32_t&                 queueIndex)
348 {
349   VkResult r = VK_SUCCESS;
350 
351   std::vector<VkQueueFamilyProperties> queueProps;
352   if ((r = vk.getPhysicalDeviceQueueFamilyProperties(device, queueProps))) {
353     return r;
354   }
355 
356   for (uint32_t q = 0u; q < queueProps.size(); ++q) {
357     if (queueProps[q].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
358       bool supported = false;
359       if ((r = vk.getPhysicalDeviceSurfaceSupportKHR(
360              device, q, surface, supported))) {
361         return r;
362       }
363 
364       if (supported) {
365         queueIndex = q;
366         return VK_SUCCESS;
367       }
368     }
369   }
370 
371   return VK_ERROR_FEATURE_NOT_PRESENT;
372 }
373 
374 /// Select a physical graphics device to use (simply the first found)
375 VkResult
selectPhysicalDevice(const sk::VulkanApi & vk,const sk::Instance & instance,const sk::SurfaceKHR & surface,PhysicalDeviceSelection & selection)376 selectPhysicalDevice(const sk::VulkanApi&     vk,
377                      const sk::Instance&      instance,
378                      const sk::SurfaceKHR&    surface,
379                      PhysicalDeviceSelection& selection)
380 {
381   VkResult r = VK_SUCCESS;
382 
383   std::vector<sk::PhysicalDevice> devices;
384   if ((r = vk.enumeratePhysicalDevices(instance, devices))) {
385     return r;
386   }
387 
388   for (const auto& device : devices) {
389     auto supported = false;
390     if ((r = deviceSupportsRequiredExtensions(vk, device, supported))) {
391       return r;
392     }
393 
394     if (supported) {
395       auto queueIndex = 0u;
396       if ((r = findGraphicsQueue(vk, surface, device, queueIndex))) {
397         return r;
398       }
399 
400       selection = PhysicalDeviceSelection{device, queueIndex};
401       return VK_SUCCESS;
402     }
403   }
404 
405   return VK_ERROR_INCOMPATIBLE_DISPLAY_KHR;
406 }
407 
408 VkResult
init(const pugl::VulkanLoader & loader,const VulkanContext & context,pugl::View & view,const PuglTestOptions & opts)409 GraphicsDevice::init(const pugl::VulkanLoader& loader,
410                      const VulkanContext&      context,
411                      pugl::View&               view,
412                      const PuglTestOptions&    opts)
413 {
414   const auto& vk = context.vk;
415   VkResult    r  = VK_SUCCESS;
416 
417   // Create a Vulkan surface for the window using the Pugl API
418   VkSurfaceKHR surfaceHandle = {};
419   if ((r = pugl::createSurface(loader.getInstanceProcAddrFunc(),
420                                view,
421                                context.instance,
422                                nullptr,
423                                &surfaceHandle))) {
424     return r;
425   }
426 
427   // Wrap surface in a safe RAII handle
428   surface =
429     sk::SurfaceKHR{surfaceHandle, {context.instance, vk.vkDestroySurfaceKHR}};
430 
431   PhysicalDeviceSelection physicalDeviceSelection = {};
432   // Select a physical device to use
433   if ((r = selectPhysicalDevice(
434          vk, context.instance, surface, physicalDeviceSelection))) {
435     return r;
436   }
437 
438   physicalDevice = physicalDeviceSelection.physicalDevice;
439   graphicsIndex  = physicalDeviceSelection.graphicsFamilyIndex;
440 
441   if ((r = selectSurfaceFormat(vk, physicalDevice, surface, surfaceFormat)) ||
442       (r = selectPresentMode(vk,
443                              physicalDevice,
444                              surface,
445                              opts.doubleBuffer,
446                              opts.sync,
447                              presentMode)) ||
448       (r = selectPresentMode(
449          vk, physicalDevice, surface, true, false, resizePresentMode)) ||
450       (r = openDevice(vk, physicalDevice, graphicsIndex, device))) {
451     return r;
452   }
453 
454   const VkCommandPoolCreateInfo commandPoolInfo{
455     VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, nullptr, {}, graphicsIndex};
456 
457   if ((r = vk.createCommandPool(device, commandPoolInfo, commandPool))) {
458     return r;
459   }
460 
461   graphicsQueue = vk.getDeviceQueue(device, graphicsIndex, 0);
462   return VK_SUCCESS;
463 }
464 
465 uint32_t
findMemoryType(const sk::VulkanApi & vk,const sk::PhysicalDevice & physicalDevice,const uint32_t typeFilter,const VkMemoryPropertyFlags & properties)466 findMemoryType(const sk::VulkanApi&         vk,
467                const sk::PhysicalDevice&    physicalDevice,
468                const uint32_t               typeFilter,
469                const VkMemoryPropertyFlags& properties)
470 {
471   VkPhysicalDeviceMemoryProperties memProperties =
472     vk.getPhysicalDeviceMemoryProperties(physicalDevice);
473 
474   for (uint32_t i = 0; i < memProperties.memoryTypeCount; ++i) {
475     if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags &
476                                     properties) == properties) {
477       return i;
478     }
479   }
480 
481   return UINT32_MAX;
482 }
483 
484 VkResult
init(const sk::VulkanApi & vk,const GraphicsDevice & gpu,const VkDeviceSize size,const VkBufferUsageFlags usage,const VkMemoryPropertyFlags properties)485 Buffer::init(const sk::VulkanApi&        vk,
486              const GraphicsDevice&       gpu,
487              const VkDeviceSize          size,
488              const VkBufferUsageFlags    usage,
489              const VkMemoryPropertyFlags properties)
490 {
491   const VkBufferCreateInfo bufferInfo{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
492                                       nullptr,
493                                       {},
494                                       size,
495                                       usage,
496                                       VK_SHARING_MODE_EXCLUSIVE,
497                                       SK_COUNTED(0, nullptr)};
498 
499   const auto& device = gpu.device;
500 
501   VkResult r = VK_SUCCESS;
502   if ((r = vk.createBuffer(device, bufferInfo, buffer))) {
503     return r;
504   }
505 
506   const auto requirements    = vk.getBufferMemoryRequirements(device, buffer);
507   const auto memoryTypeIndex = findMemoryType(
508     vk, gpu.physicalDevice, requirements.memoryTypeBits, properties);
509 
510   if (memoryTypeIndex == UINT32_MAX) {
511     return VK_ERROR_FEATURE_NOT_PRESENT;
512   }
513 
514   const VkMemoryAllocateInfo allocInfo{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
515                                        nullptr,
516                                        requirements.size,
517                                        memoryTypeIndex};
518 
519   if ((r = vk.allocateMemory(device, allocInfo, deviceMemory)) ||
520       (r = vk.bindBufferMemory(device, buffer, deviceMemory, 0))) {
521     return r;
522   }
523 
524   return VK_SUCCESS;
525 }
526 
527 VkResult
init(const sk::VulkanApi & vk,const GraphicsDevice & gpu,const VkSurfaceCapabilitiesKHR surfaceCapabilities,const VkExtent2D surfaceExtent,VkSwapchainKHR oldSwapchain,bool resizing)528 Swapchain::init(const sk::VulkanApi&           vk,
529                 const GraphicsDevice&          gpu,
530                 const VkSurfaceCapabilitiesKHR surfaceCapabilities,
531                 const VkExtent2D               surfaceExtent,
532                 VkSwapchainKHR                 oldSwapchain,
533                 bool                           resizing)
534 {
535   capabilities = surfaceCapabilities;
536   extent       = surfaceExtent;
537 
538   const auto minNumImages =
539     (!capabilities.maxImageCount || capabilities.maxImageCount >= 3u)
540       ? 3u
541       : capabilities.maxImageCount;
542 
543   const VkSwapchainCreateInfoKHR swapchainCreateInfo{
544     VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
545     nullptr,
546     {},
547     gpu.surface,
548     minNumImages,
549     gpu.surfaceFormat.format,
550     gpu.surfaceFormat.colorSpace,
551     surfaceExtent,
552     1,
553     (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT),
554     VK_SHARING_MODE_EXCLUSIVE,
555     SK_COUNTED(0, nullptr),
556     capabilities.currentTransform,
557     VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR,
558     resizing ? gpu.resizePresentMode : gpu.presentMode,
559     VK_TRUE,
560     oldSwapchain};
561 
562   VkResult             r = VK_SUCCESS;
563   std::vector<VkImage> images;
564   if ((r = vk.createSwapchainKHR(gpu.device, swapchainCreateInfo, swapchain)) ||
565       (r = vk.getSwapchainImagesKHR(gpu.device, swapchain, images))) {
566     return r;
567   }
568 
569   imageViews = std::vector<sk::ImageView>(images.size());
570   for (size_t i = 0; i < images.size(); ++i) {
571     const VkImageViewCreateInfo imageViewCreateInfo{
572       VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
573       nullptr,
574       {},
575       images[i],
576       VK_IMAGE_VIEW_TYPE_2D,
577       gpu.surfaceFormat.format,
578       {},
579       {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
580 
581     if ((r = vk.createImageView(
582            gpu.device, imageViewCreateInfo, imageViews[i]))) {
583       return r;
584     }
585   }
586 
587   return VK_SUCCESS;
588 }
589 
590 VkResult
init(const sk::VulkanApi & vk,const GraphicsDevice & gpu,const Swapchain & swapchain)591 RenderPass::init(const sk::VulkanApi&  vk,
592                  const GraphicsDevice& gpu,
593                  const Swapchain&      swapchain)
594 {
595   const auto numImages = static_cast<uint32_t>(swapchain.imageViews.size());
596 
597   assert(numImages > 0);
598 
599   // Create command buffers
600   const VkCommandBufferAllocateInfo commandBufferAllocateInfo{
601     VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
602     nullptr,
603     gpu.commandPool,
604     VK_COMMAND_BUFFER_LEVEL_PRIMARY,
605     numImages};
606 
607   VkResult r = VK_SUCCESS;
608   if ((r = vk.allocateCommandBuffers(
609          gpu.device, commandBufferAllocateInfo, commandBuffers))) {
610     return r;
611   }
612 
613   static constexpr VkAttachmentReference colorAttachmentRef{
614     0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
615 
616   static constexpr VkSubpassDescription subpass{
617     {},
618     VK_PIPELINE_BIND_POINT_GRAPHICS,
619     SK_COUNTED(0, nullptr),
620     SK_COUNTED(1, &colorAttachmentRef, nullptr, nullptr),
621     SK_COUNTED(0u, nullptr)};
622 
623   static constexpr VkSubpassDependency dependency{
624     VK_SUBPASS_EXTERNAL,
625     0,
626     VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
627     VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
628     (VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
629      VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT),
630     {},
631     {}};
632 
633   const VkAttachmentDescription colorAttachment{
634     {},
635     gpu.surfaceFormat.format,
636     VK_SAMPLE_COUNT_1_BIT,
637     VK_ATTACHMENT_LOAD_OP_CLEAR,
638     VK_ATTACHMENT_STORE_OP_STORE,
639     VK_ATTACHMENT_LOAD_OP_DONT_CARE,
640     VK_ATTACHMENT_STORE_OP_DONT_CARE,
641     VK_IMAGE_LAYOUT_UNDEFINED,
642     VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
643   };
644 
645   const VkRenderPassCreateInfo renderPassCreateInfo{
646     VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
647     nullptr,
648     {},
649     SK_COUNTED(1, &colorAttachment),
650     SK_COUNTED(1, &subpass),
651     SK_COUNTED(1, &dependency)};
652 
653   if ((r = vk.createRenderPass(gpu.device, renderPassCreateInfo, renderPass))) {
654     return r;
655   }
656 
657   // Create framebuffers
658   framebuffers = std::vector<sk::Framebuffer>(numImages);
659   for (uint32_t i = 0; i < numImages; ++i) {
660     const VkFramebufferCreateInfo framebufferCreateInfo{
661       VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
662       nullptr,
663       {},
664       renderPass,
665       SK_COUNTED(1, &swapchain.imageViews[i].get()),
666       swapchain.extent.width,
667       swapchain.extent.height,
668       1};
669 
670     if ((r = vk.createFramebuffer(
671            gpu.device, framebufferCreateInfo, framebuffers[i]))) {
672       return r;
673     }
674   }
675 
676   return VK_SUCCESS;
677 }
678 
679 std::vector<uint32_t>
readFile(const char * const programPath,const std::string & filename)680 readFile(const char* const programPath, const std::string& filename)
681 {
682   std::unique_ptr<char, decltype(&free)> path{
683     resourcePath(programPath, filename.c_str()), &free};
684 
685   std::cerr << "Loading shader:           " << path.get() << std::endl;
686 
687   std::unique_ptr<FILE, decltype(&fclose)> file{fopen(path.get(), "rb"),
688                                                 &fclose};
689 
690   if (!file) {
691     std::cerr << "Failed to open file '" << filename << "'\n";
692     return {};
693   }
694 
695   fseek(file.get(), 0, SEEK_END);
696   const auto fileSize = static_cast<size_t>(ftell(file.get()));
697   fseek(file.get(), 0, SEEK_SET);
698 
699   const auto            numWords = fileSize / sizeof(uint32_t);
700   std::vector<uint32_t> buffer(numWords);
701 
702   fread(buffer.data(), sizeof(uint32_t), numWords, file.get());
703 
704   return buffer;
705 }
706 
707 VkResult
createShaderModule(const sk::VulkanApi & vk,const GraphicsDevice & gpu,const std::vector<uint32_t> & code,sk::ShaderModule & shaderModule)708 createShaderModule(const sk::VulkanApi&         vk,
709                    const GraphicsDevice&        gpu,
710                    const std::vector<uint32_t>& code,
711                    sk::ShaderModule&            shaderModule)
712 {
713   const VkShaderModuleCreateInfo createInfo{
714     VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
715     nullptr,
716     {},
717     code.size() * sizeof(uint32_t),
718     code.data()};
719 
720   return vk.createShaderModule(gpu.device, createInfo, shaderModule);
721 }
722 
723 VkResult
init(const sk::VulkanApi & vk,const GraphicsDevice & gpu,const std::string & programPath)724 RectShaders::init(const sk::VulkanApi&  vk,
725                   const GraphicsDevice& gpu,
726                   const std::string&    programPath)
727 {
728   auto vertShaderCode = readFile(programPath.c_str(), "shaders/rect.vert.spv");
729 
730   auto fragShaderCode = readFile(programPath.c_str(), "shaders/rect.frag.spv");
731 
732   if (vertShaderCode.empty() || fragShaderCode.empty()) {
733     return VK_ERROR_INITIALIZATION_FAILED;
734   }
735 
736   VkResult r = VK_SUCCESS;
737   if ((r = createShaderModule(vk, gpu, vertShaderCode, vert)) ||
738       (r = createShaderModule(vk, gpu, fragShaderCode, frag))) {
739     return r;
740   }
741 
742   return VK_SUCCESS;
743 }
744 
745 VkResult
init(const sk::VulkanApi & vk,const GraphicsDevice & gpu,const RectData & rectData,const RectShaders & shaders,const Swapchain & swapchain,const RenderPass & renderPass)746 RectPipeline::init(const sk::VulkanApi&  vk,
747                    const GraphicsDevice& gpu,
748                    const RectData&       rectData,
749                    const RectShaders&    shaders,
750                    const Swapchain&      swapchain,
751                    const RenderPass&     renderPass)
752 {
753   const auto oldNumImages = numImages;
754   VkResult   r            = VK_SUCCESS;
755 
756   numImages      = static_cast<uint32_t>(swapchain.imageViews.size());
757   pipelines      = {};
758   pipelineLayout = {};
759   descriptorSets = {};
760 
761   if (numImages != oldNumImages) {
762     // Create layout descriptor pool
763 
764     const VkDescriptorPoolSize poolSize{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
765                                         numImages};
766 
767     const VkDescriptorPoolCreateInfo descriptorPoolCreateInfo{
768       VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
769       nullptr,
770       VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
771       numImages,
772       1u,
773       &poolSize};
774     if ((r = vk.createDescriptorPool(
775            gpu.device, descriptorPoolCreateInfo, descriptorPool))) {
776       return r;
777     }
778   }
779 
780   const std::vector<VkDescriptorSetLayout> layouts(
781     numImages, rectData.descriptorSetLayout.get());
782 
783   const VkDescriptorSetAllocateInfo descriptorSetAllocateInfo{
784     VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
785     nullptr,
786     descriptorPool,
787     numImages,
788     layouts.data()};
789   if ((r = vk.allocateDescriptorSets(
790          gpu.device, descriptorSetAllocateInfo, descriptorSets))) {
791     return r;
792   }
793 
794   const VkDescriptorBufferInfo bufferInfo{
795     rectData.uniformBuffer.buffer, 0, sizeof(UniformBufferObject)};
796 
797   const std::array<VkWriteDescriptorSet, 1> descriptorWrites{
798     {{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
799       nullptr,
800       descriptorSets[0],
801       0,
802       0,
803       1,
804       VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
805       nullptr,
806       &bufferInfo,
807       nullptr}}};
808 
809   const std::array<VkCopyDescriptorSet, 0> descriptorCopies{};
810 
811   vk.updateDescriptorSets(gpu.device, descriptorWrites, descriptorCopies);
812 
813   static constexpr std::array<VkVertexInputAttributeDescription, 4>
814     vertexAttributeDescriptions{
815       {// Model
816        {0u, 0u, VK_FORMAT_R32G32_SFLOAT, 0},
817 
818        // Rect instance attributes
819        {1u, 1u, VK_FORMAT_R32G32_SFLOAT, offsetof(Rect, pos)},
820        {2u, 1u, VK_FORMAT_R32G32_SFLOAT, offsetof(Rect, size)},
821        {3u, 1u, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Rect, fillColor)}}};
822 
823   static constexpr std::array<VkVertexInputBindingDescription, 2>
824     vertexBindingDescriptions{
825       VkVertexInputBindingDescription{
826         0, sizeof(vec2), VK_VERTEX_INPUT_RATE_VERTEX},
827       VkVertexInputBindingDescription{
828         1u, sizeof(Rect), VK_VERTEX_INPUT_RATE_INSTANCE}};
829 
830   static constexpr VkPipelineInputAssemblyStateCreateInfo inputAssembly{
831     VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
832     nullptr,
833     {},
834     VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP,
835     false};
836 
837   static constexpr VkPipelineRasterizationStateCreateInfo rasterizer{
838     VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
839     nullptr,
840     {},
841     0,
842     0,
843     VK_POLYGON_MODE_FILL,
844     VK_CULL_MODE_BACK_BIT,
845     VK_FRONT_FACE_CLOCKWISE,
846     0,
847     0,
848     0,
849     0,
850     1.0f};
851 
852   static constexpr VkPipelineMultisampleStateCreateInfo multisampling{
853     VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
854     nullptr,
855     {},
856     VK_SAMPLE_COUNT_1_BIT,
857     false,
858     0.0f,
859     nullptr,
860     false,
861     false};
862 
863   static constexpr VkPipelineColorBlendAttachmentState colorBlendAttachment{
864     true,
865     VK_BLEND_FACTOR_SRC_ALPHA,
866     VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
867     VK_BLEND_OP_ADD,
868     VK_BLEND_FACTOR_ONE,
869     VK_BLEND_FACTOR_ZERO,
870     VK_BLEND_OP_ADD,
871     (VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
872      VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT)};
873 
874   const VkPipelineShaderStageCreateInfo shaderStages[] = {
875     {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
876      nullptr,
877      {},
878      VK_SHADER_STAGE_VERTEX_BIT,
879      shaders.vert.get(),
880      "main",
881      nullptr},
882     {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
883      nullptr,
884      {},
885      VK_SHADER_STAGE_FRAGMENT_BIT,
886      shaders.frag.get(),
887      "main",
888      nullptr}};
889 
890   const VkPipelineVertexInputStateCreateInfo vertexInputInfo{
891     VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
892     nullptr,
893     {},
894     SK_COUNTED(static_cast<uint32_t>(vertexBindingDescriptions.size()),
895                vertexBindingDescriptions.data()),
896     SK_COUNTED(static_cast<uint32_t>(vertexAttributeDescriptions.size()),
897                vertexAttributeDescriptions.data())};
898 
899   const VkViewport viewport{0.0f,
900                             0.0f,
901                             float(swapchain.extent.width),
902                             float(swapchain.extent.height),
903                             0.0f,
904                             1.0f};
905 
906   const VkRect2D scissor{{0, 0}, swapchain.extent};
907 
908   const VkPipelineViewportStateCreateInfo viewportState{
909     VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
910     nullptr,
911     {},
912     SK_COUNTED(1, &viewport),
913     SK_COUNTED(1, &scissor)};
914 
915   const VkPipelineColorBlendStateCreateInfo colorBlending{
916     VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
917     nullptr,
918     {},
919     false,
920     VK_LOGIC_OP_COPY,
921     SK_COUNTED(1, &colorBlendAttachment),
922     {1.0f, 0.0f, 0.0f, 0.0f}};
923 
924   const VkPipelineLayoutCreateInfo layoutInfo{
925     VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
926     nullptr,
927     {},
928     SK_COUNTED(1, &rectData.descriptorSetLayout.get()),
929     SK_COUNTED(0, nullptr)};
930 
931   if ((r = vk.createPipelineLayout(gpu.device, layoutInfo, pipelineLayout))) {
932     return r;
933   }
934 
935   const std::array<VkGraphicsPipelineCreateInfo, 1> pipelineInfos{
936     {{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
937       nullptr,
938       {},
939       SK_COUNTED(2, shaderStages),
940       &vertexInputInfo,
941       &inputAssembly,
942       nullptr,
943       &viewportState,
944       &rasterizer,
945       &multisampling,
946       nullptr,
947       &colorBlending,
948       nullptr,
949       pipelineLayout,
950       renderPass.renderPass,
951       0u,
952       {},
953       0}}};
954 
955   if ((r = vk.createGraphicsPipelines(
956          gpu.device, {}, pipelineInfos, pipelines))) {
957     return r;
958   }
959 
960   return VK_SUCCESS;
961 }
962 
963 VkResult
init(const sk::VulkanApi & vk,const GraphicsDevice & gpu,const size_t nRects)964 RectData::init(const sk::VulkanApi&  vk,
965                const GraphicsDevice& gpu,
966                const size_t          nRects)
967 {
968   numRects = nRects;
969 
970   static constexpr VkDescriptorSetLayoutBinding uboLayoutBinding{
971     0,
972     VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
973     1,
974     VK_SHADER_STAGE_VERTEX_BIT,
975     nullptr};
976 
977   const VkDescriptorSetLayoutCreateInfo descriptorSetLayoutInfo{
978     VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
979     nullptr,
980     {},
981     1,
982     &uboLayoutBinding};
983 
984   VkResult r = VK_SUCCESS;
985   if ((r = vk.createDescriptorSetLayout(
986          gpu.device, descriptorSetLayoutInfo, descriptorSetLayout)) ||
987       (r = uniformBuffer.init(vk,
988                               gpu,
989                               sizeof(UniformBufferObject),
990                               VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
991                               VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
992                                 VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) ||
993       (r = vk.mapMemory(gpu.device,
994                         uniformBuffer.deviceMemory,
995                         0,
996                         sizeof(UniformBufferObject),
997                         {},
998                         uniformData))) {
999     return r;
1000   }
1001 
1002   const VkBufferUsageFlags usageFlags =
1003     (VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
1004      VK_BUFFER_USAGE_TRANSFER_DST_BIT);
1005 
1006   const VkMemoryPropertyFlags propertyFlags =
1007     (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
1008      VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
1009 
1010   if ((r = modelBuffer.init(
1011          vk, gpu, sizeof(rectVertices), usageFlags, propertyFlags))) {
1012     return r;
1013   }
1014 
1015   {
1016     // Copy model vertices (directly, we do this only once)
1017     sk::MappedMemory modelData;
1018     if ((r = vk.mapMemory(gpu.device,
1019                           modelBuffer.deviceMemory,
1020                           0,
1021                           static_cast<VkDeviceSize>(sizeof(rectVertices)),
1022                           {},
1023                           modelData))) {
1024       return r;
1025     }
1026 
1027     memcpy(modelData.get(), rectVertices, sizeof(rectVertices));
1028   }
1029 
1030   if ((r = instanceBuffer.init(
1031          vk, gpu, sizeof(Rect) * numRects, usageFlags, propertyFlags))) {
1032     return r;
1033   }
1034 
1035   // Map attribute vertices (we will update them every frame)
1036   const auto rectsSize = static_cast<VkDeviceSize>(sizeof(Rect) * numRects);
1037   if ((r = vk.mapMemory(gpu.device,
1038                         instanceBuffer.deviceMemory,
1039                         0,
1040                         rectsSize,
1041                         {},
1042                         vertexData))) {
1043     return r;
1044   }
1045 
1046   return VK_SUCCESS;
1047 }
1048 
1049 VkResult
init(const sk::VulkanApi & vk,const sk::Device & device,const uint32_t numImages)1050 RenderSync::init(const sk::VulkanApi& vk,
1051                  const sk::Device&    device,
1052                  const uint32_t       numImages)
1053 {
1054   const auto maxInFlight = std::max(1u, numImages - 1u);
1055   VkResult   r           = VK_SUCCESS;
1056 
1057   imageAvailable = std::vector<sk::Semaphore>(numImages);
1058   renderFinished = std::vector<sk::Semaphore>(numImages);
1059   for (uint32_t i = 0; i < numImages; ++i) {
1060     static constexpr VkSemaphoreCreateInfo semaphoreInfo{
1061       VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, nullptr, {}};
1062 
1063     if ((r = vk.createSemaphore(device, semaphoreInfo, imageAvailable[i])) ||
1064         (r = vk.createSemaphore(device, semaphoreInfo, renderFinished[i]))) {
1065       return r;
1066     }
1067   }
1068 
1069   inFlight = std::vector<sk::Fence>(maxInFlight);
1070   for (uint32_t i = 0; i < maxInFlight; ++i) {
1071     static constexpr VkFenceCreateInfo fenceInfo{
1072       VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
1073       nullptr,
1074       VK_FENCE_CREATE_SIGNALED_BIT};
1075 
1076     if ((r = vk.createFence(device, fenceInfo, inFlight[i]))) {
1077       return r;
1078     }
1079   }
1080 
1081   return VK_SUCCESS;
1082 }
1083 
1084 VkResult
init(const sk::VulkanApi & vk,const GraphicsDevice & gpu,const RectData & rectData,const RectShaders & rectShaders,const VkExtent2D extent,bool resizing)1085 Renderer::init(const sk::VulkanApi&  vk,
1086                const GraphicsDevice& gpu,
1087                const RectData&       rectData,
1088                const RectShaders&    rectShaders,
1089                const VkExtent2D      extent,
1090                bool                  resizing)
1091 {
1092   VkResult                 r            = VK_SUCCESS;
1093   VkSurfaceCapabilitiesKHR capabilities = {};
1094 
1095   if ((r = vk.getPhysicalDeviceSurfaceCapabilitiesKHR(
1096          gpu.physicalDevice, gpu.surface, capabilities)) ||
1097       (r = swapchain.init(vk, gpu, capabilities, extent, {}, resizing)) ||
1098       (r = renderPass.init(vk, gpu, swapchain)) ||
1099       (r = rectPipeline.init(
1100          vk, gpu, rectData, rectShaders, swapchain, renderPass))) {
1101     return r;
1102   }
1103 
1104   const auto numFrames = static_cast<uint32_t>(swapchain.imageViews.size());
1105   return sync.init(vk, gpu.device, numFrames);
1106 }
1107 
1108 VkResult
recreate(const sk::VulkanApi & vk,const sk::SurfaceKHR & surface,const GraphicsDevice & gpu,const RectData & rectData,const RectShaders & rectShaders,const VkExtent2D extent,bool resizing)1109 Renderer::recreate(const sk::VulkanApi&  vk,
1110                    const sk::SurfaceKHR& surface,
1111                    const GraphicsDevice& gpu,
1112                    const RectData&       rectData,
1113                    const RectShaders&    rectShaders,
1114                    const VkExtent2D      extent,
1115                    bool                  resizing)
1116 {
1117   VkResult   r            = VK_SUCCESS;
1118   const auto oldNumImages = swapchain.imageViews.size();
1119 
1120   VkSurfaceCapabilitiesKHR capabilities = {};
1121   if ((r = vk.getPhysicalDeviceSurfaceCapabilitiesKHR(
1122          gpu.physicalDevice, surface, capabilities)) ||
1123       (r = swapchain.init(
1124          vk, gpu, capabilities, extent, swapchain.swapchain, resizing)) ||
1125       (r = renderPass.init(vk, gpu, swapchain)) ||
1126       (r = rectPipeline.init(
1127          vk, gpu, rectData, rectShaders, swapchain, renderPass))) {
1128     return r;
1129   }
1130 
1131   const auto numFrames = static_cast<uint32_t>(swapchain.imageViews.size());
1132   if (swapchain.imageViews.size() != oldNumImages) {
1133     return sync.init(vk, gpu.device, numFrames);
1134   }
1135 
1136   return VK_SUCCESS;
1137 }
1138 
1139 VKAPI_ATTR
1140 VkBool32 VKAPI_CALL
debugCallback(VkDebugReportFlagsEXT flags,VkDebugReportObjectTypeEXT,uint64_t,size_t,int32_t,const char * layerPrefix,const char * msg,void *)1141 debugCallback(VkDebugReportFlagsEXT flags,
1142               VkDebugReportObjectTypeEXT,
1143               uint64_t,
1144               size_t,
1145               int32_t,
1146               const char* layerPrefix,
1147               const char* msg,
1148               void*)
1149 {
1150   std::cerr << sk::string(static_cast<VkDebugReportFlagBitsEXT>(flags)) << ": "
1151             << layerPrefix << ": " << msg << std::endl;
1152 
1153   return VK_FALSE;
1154 }
1155 
1156 bool
hasExtension(const char * name,const std::vector<VkExtensionProperties> & properties)1157 hasExtension(const char*                               name,
1158              const std::vector<VkExtensionProperties>& properties)
1159 {
1160   for (const auto& p : properties) {
1161     if (!strcmp(p.extensionName, name)) {
1162       return true;
1163     }
1164   }
1165 
1166   return false;
1167 }
1168 
1169 bool
hasLayer(const char * name,const std::vector<VkLayerProperties> & properties)1170 hasLayer(const char* name, const std::vector<VkLayerProperties>& properties)
1171 {
1172   for (const auto& p : properties) {
1173     if (!strcmp(p.layerName, name)) {
1174       return true;
1175     }
1176   }
1177 
1178   return false;
1179 }
1180 
1181 template<class Value>
1182 void
logInfo(const char * heading,const Value & value)1183 logInfo(const char* heading, const Value& value)
1184 {
1185   std::cout << std::setw(26) << std::left << (std::string(heading) + ":")
1186             << value << std::endl;
1187 }
1188 
1189 VkResult
createInstance(sk::VulkanInitApi & initApi,const PuglTestOptions & opts,sk::Instance & instance)1190 createInstance(sk::VulkanInitApi&     initApi,
1191                const PuglTestOptions& opts,
1192                sk::Instance&          instance)
1193 {
1194   VkResult r = VK_SUCCESS;
1195 
1196   std::vector<VkLayerProperties>     layerProps;
1197   std::vector<VkExtensionProperties> extProps;
1198   if ((r = initApi.enumerateInstanceLayerProperties(layerProps)) ||
1199       (r = initApi.enumerateInstanceExtensionProperties(extProps))) {
1200     return r;
1201   }
1202 
1203   const auto puglExtensions = pugl::getInstanceExtensions();
1204   auto       extensions =
1205     std::vector<const char*>(puglExtensions.begin(), puglExtensions.end());
1206 
1207   // Add extra extensions we want to use if they are supported
1208   if (hasExtension("VK_EXT_debug_report", extProps)) {
1209     extensions.push_back("VK_EXT_debug_report");
1210   }
1211 
1212   // Add validation layers if error checking is enabled
1213   std::vector<const char*> layers;
1214   if (opts.errorChecking) {
1215     for (const char* l : {"VK_LAYER_KHRONOS_validation",
1216                           "VK_LAYER_LUNARG_standard_validation"}) {
1217       if (hasLayer(l, layerProps)) {
1218         layers.push_back(l);
1219       }
1220     }
1221   }
1222 
1223   for (const auto& e : extensions) {
1224     logInfo("Using instance extension", e);
1225   }
1226 
1227   for (const auto& l : layers) {
1228     logInfo("Using instance layer", l);
1229   }
1230 
1231   static constexpr VkApplicationInfo appInfo{
1232     VK_STRUCTURE_TYPE_APPLICATION_INFO,
1233     nullptr,
1234     "Pugl Vulkan Demo",
1235     0,
1236     nullptr,
1237     0,
1238     VK_MAKE_VERSION(1, 0, 0),
1239   };
1240 
1241   const VkInstanceCreateInfo createInfo{
1242     VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
1243     nullptr,
1244     VkInstanceCreateFlags{},
1245     &appInfo,
1246     SK_COUNTED(uint32_t(layers.size()), layers.data()),
1247     SK_COUNTED(uint32_t(extensions.size()), extensions.data())};
1248 
1249   return initApi.createInstance(createInfo, instance);
1250 }
1251 
1252 VkResult
getDebugReportCallback(sk::VulkanApi & api,sk::Instance & instance,const bool verbose,sk::DebugReportCallbackEXT & callback)1253 getDebugReportCallback(sk::VulkanApi&              api,
1254                        sk::Instance&               instance,
1255                        const bool                  verbose,
1256                        sk::DebugReportCallbackEXT& callback)
1257 {
1258   if (api.vkCreateDebugReportCallbackEXT) {
1259     VkDebugReportFlagsEXT flags = (VK_DEBUG_REPORT_WARNING_BIT_EXT |
1260                                    VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT |
1261                                    VK_DEBUG_REPORT_ERROR_BIT_EXT);
1262 
1263     if (verbose) {
1264       flags |= VK_DEBUG_REPORT_INFORMATION_BIT_EXT;
1265       flags |= VK_DEBUG_REPORT_DEBUG_BIT_EXT;
1266     }
1267 
1268     const VkDebugReportCallbackCreateInfoEXT createInfo{
1269       VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT,
1270       nullptr,
1271       flags,
1272       debugCallback,
1273       nullptr};
1274 
1275     return api.createDebugReportCallbackEXT(instance, createInfo, callback);
1276   }
1277 
1278   return VK_ERROR_FEATURE_NOT_PRESENT;
1279 }
1280 
1281 void
recordCommandBuffer(sk::CommandScope & cmd,const Swapchain & swapchain,const RenderPass & renderPass,const RectPipeline & rectPipeline,const RectData & rectData,const size_t imageIndex)1282 recordCommandBuffer(sk::CommandScope&   cmd,
1283                     const Swapchain&    swapchain,
1284                     const RenderPass&   renderPass,
1285                     const RectPipeline& rectPipeline,
1286                     const RectData&     rectData,
1287                     const size_t        imageIndex)
1288 {
1289   const VkClearColorValue clearColorValue{{0.0f, 0.0f, 0.0f, 1.0f}};
1290   const VkClearValue      clearValue{clearColorValue};
1291 
1292   const VkRenderPassBeginInfo renderPassBegin{
1293     VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
1294     nullptr,
1295     renderPass.renderPass,
1296     renderPass.framebuffers[imageIndex],
1297     VkRect2D{{0, 0}, swapchain.extent},
1298     SK_COUNTED(1, &clearValue)};
1299 
1300   auto pass = cmd.beginRenderPass(renderPassBegin, VK_SUBPASS_CONTENTS_INLINE);
1301 
1302   pass.bindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, rectPipeline.pipelines[0]);
1303 
1304   const std::array<VkDeviceSize, 1> offsets{0};
1305   pass.bindVertexBuffers(
1306     0u, SK_COUNTED(1u, &rectData.modelBuffer.buffer.get(), offsets.data()));
1307 
1308   pass.bindVertexBuffers(
1309     1u, SK_COUNTED(1u, &rectData.instanceBuffer.buffer.get(), offsets.data()));
1310 
1311   pass.bindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS,
1312                           rectPipeline.pipelineLayout,
1313                           0u,
1314                           SK_COUNTED(1u, rectPipeline.descriptorSets.get()),
1315                           0u,
1316                           nullptr);
1317 
1318   pass.draw(4u, static_cast<uint32_t>(rectData.numRects), 0u, 0u);
1319 }
1320 
1321 VkResult
recordCommandBuffers(const sk::VulkanApi & vk,const Swapchain & swapchain,const RenderPass & renderPass,const RectPipeline & rectPipeline,const RectData & rectData)1322 recordCommandBuffers(const sk::VulkanApi& vk,
1323                      const Swapchain&     swapchain,
1324                      const RenderPass&    renderPass,
1325                      const RectPipeline&  rectPipeline,
1326                      const RectData&      rectData)
1327 {
1328   VkResult r = VK_SUCCESS;
1329 
1330   for (size_t i = 0; i < swapchain.imageViews.size(); ++i) {
1331     const VkCommandBufferBeginInfo beginInfo{
1332       VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
1333       nullptr,
1334       VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT,
1335       nullptr};
1336 
1337     auto* const commandBuffer = renderPass.commandBuffers[i];
1338     auto        cmd           = vk.beginCommandBuffer(commandBuffer, beginInfo);
1339     if (!cmd) {
1340       return cmd.error();
1341     }
1342 
1343     recordCommandBuffer(cmd, swapchain, renderPass, rectPipeline, rectData, i);
1344 
1345     if ((r = cmd.end())) {
1346       return r;
1347     }
1348   }
1349 
1350   return VK_SUCCESS;
1351 }
1352 
1353 class PuglVulkanDemo;
1354 
1355 class View : public pugl::View
1356 {
1357 public:
View(pugl::World & world,PuglVulkanDemo & app)1358   View(pugl::World& world, PuglVulkanDemo& app)
1359     : pugl::View{world}
1360     , _app{app}
1361   {
1362     setEventHandler(*this);
1363   }
1364 
1365   template<PuglEventType t, class Base>
onEvent(const pugl::Event<t,Base> &)1366   pugl::Status onEvent(const pugl::Event<t, Base>&) noexcept
1367   {
1368     return pugl::Status::success;
1369   }
1370 
1371   pugl::Status onEvent(const pugl::ConfigureEvent& event);
1372   pugl::Status onEvent(const pugl::UpdateEvent& event);
1373   pugl::Status onEvent(const pugl::ExposeEvent& event);
1374   pugl::Status onEvent(const pugl::LoopEnterEvent& event);
1375   pugl::Status onEvent(const pugl::TimerEvent& event);
1376   pugl::Status onEvent(const pugl::LoopLeaveEvent& event);
1377   pugl::Status onEvent(const pugl::KeyPressEvent& event);
1378   pugl::Status onEvent(const pugl::CloseEvent& event);
1379 
1380 private:
1381   PuglVulkanDemo& _app;
1382 };
1383 
1384 class PuglVulkanDemo
1385 {
1386 public:
1387   PuglVulkanDemo(const char*            executablePath,
1388                  const PuglTestOptions& o,
1389                  size_t                 numRects);
1390 
1391   const char*        programPath;
1392   PuglTestOptions    opts;
1393   pugl::World        world;
1394   pugl::VulkanLoader loader;
1395   View               view;
1396   VulkanContext      vulkan;
1397   GraphicsDevice     gpu;
1398   Renderer           renderer;
1399   RectData           rectData;
1400   RectShaders        rectShaders;
1401   uint32_t           framesDrawn{0};
1402   VkExtent2D         extent{512u, 512u};
1403   std::vector<Rect>  rects;
1404   bool               resizing{false};
1405   bool               quit{false};
1406 };
1407 
1408 std::vector<Rect>
makeRects(const size_t numRects,const uint32_t windowWidth)1409 makeRects(const size_t numRects, const uint32_t windowWidth)
1410 {
1411   std::vector<Rect> rects(numRects);
1412   for (size_t i = 0; i < numRects; ++i) {
1413     rects[i] = makeRect(i, static_cast<float>(windowWidth));
1414   }
1415 
1416   return rects;
1417 }
1418 
PuglVulkanDemo(const char * const executablePath,const PuglTestOptions & o,const size_t numRects)1419 PuglVulkanDemo::PuglVulkanDemo(const char* const      executablePath,
1420                                const PuglTestOptions& o,
1421                                const size_t           numRects)
1422   : programPath{executablePath}
1423   , opts{o}
1424   , world{pugl::WorldType::program, pugl::WorldFlag::threads}
1425   , loader{world}
1426   , view{world, *this}
1427   , rects{makeRects(numRects, extent.width)}
1428 {}
1429 
1430 VkResult
recreateRenderer(PuglVulkanDemo & app,const sk::VulkanApi & vk,const GraphicsDevice & gpu,const VkExtent2D extent,const RectData & rectData,const RectShaders & rectShaders)1431 recreateRenderer(PuglVulkanDemo&       app,
1432                  const sk::VulkanApi&  vk,
1433                  const GraphicsDevice& gpu,
1434                  const VkExtent2D      extent,
1435                  const RectData&       rectData,
1436                  const RectShaders&    rectShaders)
1437 {
1438   VkResult                 r            = VK_SUCCESS;
1439   VkSurfaceCapabilitiesKHR capabilities = {};
1440   if ((r = vk.getPhysicalDeviceSurfaceCapabilitiesKHR(
1441          gpu.physicalDevice, gpu.surface, capabilities))) {
1442     return r;
1443   }
1444 
1445   // There is a known race issue here, so we clamp and hope for the best
1446   const VkExtent2D clampedExtent{
1447     std::min(capabilities.maxImageExtent.width,
1448              std::max(capabilities.minImageExtent.width, extent.width)),
1449     std::min(capabilities.maxImageExtent.height,
1450              std::max(capabilities.minImageExtent.height, extent.height))};
1451 
1452   if ((r = vk.deviceWaitIdle(gpu.device)) ||
1453       (r = app.renderer.recreate(vk,
1454                                  gpu.surface,
1455                                  gpu,
1456                                  rectData,
1457                                  rectShaders,
1458                                  clampedExtent,
1459                                  app.resizing))) {
1460     return r;
1461   }
1462 
1463   // Reset current (initially signaled) fence because we already waited
1464   vk.resetFence(gpu.device,
1465                 app.renderer.sync.inFlight[app.renderer.sync.currentFrame]);
1466 
1467   // Record new command buffers
1468   return recordCommandBuffers(vk,
1469                               app.renderer.swapchain,
1470                               app.renderer.renderPass,
1471                               app.renderer.rectPipeline,
1472                               rectData);
1473 }
1474 
1475 pugl::Status
onEvent(const pugl::ConfigureEvent & event)1476 View::onEvent(const pugl::ConfigureEvent& event)
1477 {
1478   // We just record the size here and lazily resize the surface when exposed
1479   _app.extent = {static_cast<uint32_t>(event.width),
1480                  static_cast<uint32_t>(event.height)};
1481 
1482   return pugl::Status::success;
1483 }
1484 
1485 pugl::Status
onEvent(const pugl::UpdateEvent &)1486 View::onEvent(const pugl::UpdateEvent&)
1487 {
1488   return postRedisplay();
1489 }
1490 
1491 VkResult
beginFrame(PuglVulkanDemo & app,const sk::Device & device,uint32_t & imageIndex)1492 beginFrame(PuglVulkanDemo& app, const sk::Device& device, uint32_t& imageIndex)
1493 {
1494   const auto& vk = app.vulkan.vk;
1495 
1496   VkResult r = VK_SUCCESS;
1497 
1498   // Wait until we can start rendering the next frame
1499   if ((r = vk.waitForFence(
1500          device, app.renderer.sync.inFlight[app.renderer.sync.currentFrame])) ||
1501       (r = vk.resetFence(
1502          device, app.renderer.sync.inFlight[app.renderer.sync.currentFrame]))) {
1503     return r;
1504   }
1505 
1506   // Rebuild the renderer first if the window size has changed
1507   if (app.extent.width != app.renderer.swapchain.extent.width ||
1508       app.extent.height != app.renderer.swapchain.extent.height) {
1509     if ((r = recreateRenderer(
1510            app, vk, app.gpu, app.extent, app.rectData, app.rectShaders))) {
1511       return r;
1512     }
1513   }
1514 
1515   // Acquire the next image to render, rebuilding if necessary
1516   while ((r = vk.acquireNextImageKHR(
1517             device,
1518             app.renderer.swapchain.swapchain,
1519             UINT64_MAX,
1520             app.renderer.sync.imageAvailable[app.renderer.sync.currentFrame],
1521             {},
1522             &imageIndex))) {
1523     switch (r) {
1524     case VK_SUBOPTIMAL_KHR:
1525     case VK_ERROR_OUT_OF_DATE_KHR:
1526       if ((r = recreateRenderer(app,
1527                                 vk,
1528                                 app.gpu,
1529                                 app.renderer.swapchain.extent,
1530                                 app.rectData,
1531                                 app.rectShaders))) {
1532         return r;
1533       }
1534       continue;
1535     default:
1536       return r;
1537     }
1538   }
1539 
1540   return VK_SUCCESS;
1541 }
1542 
1543 void
update(PuglVulkanDemo & app,const double time)1544 update(PuglVulkanDemo& app, const double time)
1545 {
1546   // Animate rectangles
1547   for (size_t i = 0; i < app.rects.size(); ++i) {
1548     moveRect(&app.rects[i],
1549              i,
1550              app.rects.size(),
1551              static_cast<float>(app.extent.width),
1552              static_cast<float>(app.extent.height),
1553              time);
1554   }
1555 
1556   // Update vertex buffer
1557   memcpy(app.rectData.vertexData.get(),
1558          app.rects.data(),
1559          sizeof(Rect) * app.rects.size());
1560 
1561   // Update uniform buffer
1562   UniformBufferObject ubo = {{}};
1563   mat4Ortho(ubo.projection,
1564             0.0f,
1565             float(app.renderer.swapchain.extent.width),
1566             0.0f,
1567             float(app.renderer.swapchain.extent.height),
1568             -1.0f,
1569             1.0f);
1570 
1571   memcpy(app.rectData.uniformData.get(), &ubo, sizeof(ubo));
1572 }
1573 
1574 VkResult
endFrame(const sk::VulkanApi & vk,const GraphicsDevice & gpu,const Renderer & renderer,const uint32_t imageIndex)1575 endFrame(const sk::VulkanApi&  vk,
1576          const GraphicsDevice& gpu,
1577          const Renderer&       renderer,
1578          const uint32_t        imageIndex)
1579 {
1580   const auto currentFrame = renderer.sync.currentFrame;
1581   VkResult   r            = VK_SUCCESS;
1582 
1583   static constexpr VkPipelineStageFlags waitStage =
1584     VK_PIPELINE_STAGE_TRANSFER_BIT;
1585 
1586   const VkSubmitInfo submitInfo{
1587     VK_STRUCTURE_TYPE_SUBMIT_INFO,
1588     nullptr,
1589     SK_COUNTED(1, &renderer.sync.imageAvailable[currentFrame].get()),
1590     &waitStage,
1591     SK_COUNTED(1, &renderer.renderPass.commandBuffers[imageIndex]),
1592     SK_COUNTED(1, &renderer.sync.renderFinished[imageIndex].get())};
1593 
1594   if ((r = vk.queueSubmit(gpu.graphicsQueue,
1595                           submitInfo,
1596                           renderer.sync.inFlight[currentFrame]))) {
1597     return r;
1598   }
1599 
1600   const VkPresentInfoKHR presentInfo{
1601     VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
1602     nullptr,
1603     SK_COUNTED(1, &renderer.sync.renderFinished[imageIndex].get()),
1604     SK_COUNTED(1, &renderer.swapchain.swapchain.get(), &imageIndex),
1605     nullptr};
1606 
1607   switch ((r = vk.queuePresentKHR(gpu.graphicsQueue, presentInfo))) {
1608   case VK_SUCCESS:               // Sucessfully presented
1609   case VK_SUBOPTIMAL_KHR:        // Probably a resize race, ignore
1610   case VK_ERROR_OUT_OF_DATE_KHR: // Probably a resize race, ignore
1611     break;
1612   default:
1613     return r;
1614   }
1615 
1616   return VK_SUCCESS;
1617 }
1618 
1619 pugl::Status
onEvent(const pugl::ExposeEvent &)1620 View::onEvent(const pugl::ExposeEvent&)
1621 {
1622   const auto& vk  = _app.vulkan.vk;
1623   const auto& gpu = _app.gpu;
1624 
1625   // Acquire the next image, waiting and/or rebuilding if necessary
1626   auto nextImageIndex = 0u;
1627   if (beginFrame(_app, gpu.device, nextImageIndex)) {
1628     return pugl::Status::unknownError;
1629   }
1630 
1631   // Ready to go, update the data to the current time
1632   update(_app, world().time());
1633 
1634   // Submit the frame to the queue and present it
1635   endFrame(vk, gpu, _app.renderer, nextImageIndex);
1636 
1637   ++_app.framesDrawn;
1638   ++_app.renderer.sync.currentFrame;
1639   _app.renderer.sync.currentFrame %= _app.renderer.sync.inFlight.size();
1640 
1641   return pugl::Status::success;
1642 }
1643 
1644 pugl::Status
onEvent(const pugl::LoopEnterEvent &)1645 View::onEvent(const pugl::LoopEnterEvent&)
1646 {
1647   _app.resizing = true;
1648   startTimer(resizeTimerId,
1649              1.0 / static_cast<double>(getHint(pugl::ViewHint::refreshRate)));
1650 
1651   return pugl::Status::success;
1652 }
1653 
1654 pugl::Status
onEvent(const pugl::TimerEvent &)1655 View::onEvent(const pugl::TimerEvent&)
1656 {
1657   return postRedisplay();
1658 }
1659 
1660 pugl::Status
onEvent(const pugl::LoopLeaveEvent &)1661 View::onEvent(const pugl::LoopLeaveEvent&)
1662 {
1663   stopTimer(resizeTimerId);
1664 
1665   // Trigger a swapchain recreation with the normal present mode
1666   _app.renderer.swapchain.extent = {};
1667   _app.resizing                  = false;
1668 
1669   return pugl::Status::success;
1670 }
1671 
1672 pugl::Status
onEvent(const pugl::KeyPressEvent & event)1673 View::onEvent(const pugl::KeyPressEvent& event)
1674 {
1675   if (event.key == PUGL_KEY_ESCAPE || event.key == 'q') {
1676     _app.quit = true;
1677   }
1678 
1679   return pugl::Status::success;
1680 }
1681 
1682 pugl::Status
onEvent(const pugl::CloseEvent &)1683 View::onEvent(const pugl::CloseEvent&)
1684 {
1685   _app.quit = true;
1686 
1687   return pugl::Status::success;
1688 }
1689 
1690 VkResult
init(pugl::VulkanLoader & loader,const PuglTestOptions & opts)1691 VulkanContext::init(pugl::VulkanLoader& loader, const PuglTestOptions& opts)
1692 {
1693   VkResult r = VK_SUCCESS;
1694 
1695   sk::VulkanInitApi initApi{};
1696 
1697   // Load Vulkan API and set up the fundamentals
1698   if ((r = initApi.init(loader.getInstanceProcAddrFunc())) ||
1699       (r = createInstance(initApi, opts, instance)) ||
1700       (r = vk.init(initApi, instance)) ||
1701       (r = getDebugReportCallback(vk, instance, opts.verbose, debugCallback))) {
1702     return r;
1703   }
1704 
1705   return VK_SUCCESS;
1706 }
1707 
1708 int
run(const char * const programPath,const PuglTestOptions opts,const size_t numRects)1709 run(const char* const     programPath,
1710     const PuglTestOptions opts,
1711     const size_t          numRects)
1712 {
1713   PuglVulkanDemo app{programPath, opts, numRects};
1714 
1715   VkResult   r      = VK_SUCCESS;
1716   const auto width  = static_cast<int>(app.extent.width);
1717   const auto height = static_cast<int>(app.extent.height);
1718 
1719   // Realize window so we can set up Vulkan
1720   app.world.setClassName("PuglVulkanDemo");
1721   app.view.setWindowTitle("Pugl Vulkan Demo");
1722   app.view.setAspectRatio(1, 1, 16, 9);
1723   app.view.setDefaultSize(width, height);
1724   app.view.setMinSize(width / 4, height / 4);
1725   app.view.setBackend(pugl::vulkanBackend());
1726   app.view.setHint(pugl::ViewHint::resizable, opts.resizable);
1727   const pugl::Status st = app.view.realize();
1728   if (st != pugl::Status::success) {
1729     return logError("Failed to create window (%s)\n", pugl::strerror(st));
1730   }
1731 
1732   if (!app.loader) {
1733     return logError("Failed to load Vulkan library\n");
1734   }
1735 
1736   // Load Vulkan for the view
1737   if ((r = app.vulkan.init(app.loader, opts))) {
1738     return logError("Failed to set up Vulkan API (%s)\n", sk::string(r));
1739   }
1740 
1741   const auto& vk = app.vulkan.vk;
1742 
1743   // Set up the graphics device
1744   if ((r = app.gpu.init(app.loader, app.vulkan, app.view, opts))) {
1745     return logError("Failed to set up device (%s)\n", sk::string(r));
1746   }
1747 
1748   logInfo("Present mode", sk::string(app.gpu.presentMode));
1749   logInfo("Resize present mode", sk::string(app.gpu.resizePresentMode));
1750 
1751   // Set up the rectangle data we will render every frame
1752   if ((r = app.rectData.init(vk, app.gpu, app.rects.size()))) {
1753     return logError("Failed to allocate render data (%s)\n", sk::string(r));
1754   }
1755 
1756   // Load shader modules
1757   if ((r = app.rectShaders.init(vk, app.gpu, app.programPath))) {
1758     return logError("Failed to load shaders (%s)\n", sk::string(r));
1759   }
1760 
1761   if ((r = app.renderer.init(app.vulkan.vk,
1762                              app.gpu,
1763                              app.rectData,
1764                              app.rectShaders,
1765                              app.extent,
1766                              false))) {
1767     return logError("Failed to create renderer (%s)\n", sk::string(r));
1768   }
1769 
1770   logInfo("Swapchain frames",
1771           std::to_string(app.renderer.swapchain.imageViews.size()));
1772   logInfo("Frames in flight",
1773           std::to_string(app.renderer.sync.inFlight.size()));
1774 
1775   recordCommandBuffers(app.vulkan.vk,
1776                        app.renderer.swapchain,
1777                        app.renderer.renderPass,
1778                        app.renderer.rectPipeline,
1779                        app.rectData);
1780 
1781   const int    refreshRate   = app.view.getHint(pugl::ViewHint::refreshRate);
1782   const double frameDuration = 1.0 / static_cast<double>(refreshRate);
1783   const double timeout       = app.opts.sync ? frameDuration : 0.0;
1784 
1785   PuglFpsPrinter fpsPrinter = {app.world.time()};
1786   app.view.show();
1787   while (!app.quit) {
1788     app.world.update(timeout);
1789     puglPrintFps(app.world.cobj(), &fpsPrinter, &app.framesDrawn);
1790   }
1791 
1792   if ((r = app.vulkan.vk.deviceWaitIdle(app.gpu.device))) {
1793     return logError("Failed to wait for device idle (%s)\n", sk::string(r));
1794   }
1795 
1796   return 0;
1797 }
1798 
1799 } // namespace
1800 
1801 int
main(int argc,char ** argv)1802 main(int argc, char** argv)
1803 {
1804   // Parse command line options
1805   const char* const     programPath = argv[0];
1806   const PuglTestOptions opts        = puglParseTestOptions(&argc, &argv);
1807   if (opts.help) {
1808     puglPrintTestUsage(programPath, "");
1809     return 0;
1810   }
1811 
1812   // Parse number of rectangles argument, if given
1813   int64_t numRects = 1000;
1814   if (argc >= 1) {
1815     char* endptr = nullptr;
1816     numRects     = strtol(argv[0], &endptr, 10);
1817     if (endptr != argv[0] + strlen(argv[0]) || numRects < 1) {
1818       logError("Invalid number of rectangles: %s\n", argv[0]);
1819       return 1;
1820     }
1821   }
1822 
1823   // Run application
1824   return run(programPath, opts, static_cast<size_t>(numRects));
1825 }
1826