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.setMaxSize(width * 4, height * 4);
1726 app.view.setBackend(pugl::vulkanBackend());
1727 app.view.setHint(pugl::ViewHint::resizable, opts.resizable);
1728 const pugl::Status st = app.view.realize();
1729 if (st != pugl::Status::success) {
1730 return logError("Failed to create window (%s)\n", pugl::strerror(st));
1731 }
1732
1733 if (!app.loader) {
1734 return logError("Failed to load Vulkan library\n");
1735 }
1736
1737 // Load Vulkan for the view
1738 if ((r = app.vulkan.init(app.loader, opts))) {
1739 return logError("Failed to set up Vulkan API (%s)\n", sk::string(r));
1740 }
1741
1742 const auto& vk = app.vulkan.vk;
1743
1744 // Set up the graphics device
1745 if ((r = app.gpu.init(app.loader, app.vulkan, app.view, opts))) {
1746 return logError("Failed to set up device (%s)\n", sk::string(r));
1747 }
1748
1749 logInfo("Present mode", sk::string(app.gpu.presentMode));
1750 logInfo("Resize present mode", sk::string(app.gpu.resizePresentMode));
1751
1752 // Set up the rectangle data we will render every frame
1753 if ((r = app.rectData.init(vk, app.gpu, app.rects.size()))) {
1754 return logError("Failed to allocate render data (%s)\n", sk::string(r));
1755 }
1756
1757 // Load shader modules
1758 if ((r = app.rectShaders.init(vk, app.gpu, app.programPath))) {
1759 return logError("Failed to load shaders (%s)\n", sk::string(r));
1760 }
1761
1762 if ((r = app.renderer.init(app.vulkan.vk,
1763 app.gpu,
1764 app.rectData,
1765 app.rectShaders,
1766 app.extent,
1767 false))) {
1768 return logError("Failed to create renderer (%s)\n", sk::string(r));
1769 }
1770
1771 logInfo("Swapchain frames",
1772 std::to_string(app.renderer.swapchain.imageViews.size()));
1773 logInfo("Frames in flight",
1774 std::to_string(app.renderer.sync.inFlight.size()));
1775
1776 recordCommandBuffers(app.vulkan.vk,
1777 app.renderer.swapchain,
1778 app.renderer.renderPass,
1779 app.renderer.rectPipeline,
1780 app.rectData);
1781
1782 const int refreshRate = app.view.getHint(pugl::ViewHint::refreshRate);
1783 const double frameDuration = 1.0 / static_cast<double>(refreshRate);
1784 const double timeout = app.opts.sync ? frameDuration : 0.0;
1785
1786 PuglFpsPrinter fpsPrinter = {app.world.time()};
1787 app.view.show();
1788 while (!app.quit) {
1789 app.world.update(timeout);
1790 puglPrintFps(app.world.cobj(), &fpsPrinter, &app.framesDrawn);
1791 }
1792
1793 if ((r = app.vulkan.vk.deviceWaitIdle(app.gpu.device))) {
1794 return logError("Failed to wait for device idle (%s)\n", sk::string(r));
1795 }
1796
1797 return 0;
1798 }
1799
1800 } // namespace
1801
1802 int
main(int argc,char ** argv)1803 main(int argc, char** argv)
1804 {
1805 // Parse command line options
1806 const char* const programPath = argv[0];
1807 const PuglTestOptions opts = puglParseTestOptions(&argc, &argv);
1808 if (opts.help) {
1809 puglPrintTestUsage(programPath, "");
1810 return 0;
1811 }
1812
1813 // Parse number of rectangles argument, if given
1814 int64_t numRects = 1000;
1815 if (argc >= 1) {
1816 char* endptr = nullptr;
1817 numRects = strtol(argv[0], &endptr, 10);
1818 if (endptr != argv[0] + strlen(argv[0]) || numRects < 1) {
1819 logError("Invalid number of rectangles: %s\n", argv[0]);
1820 return 1;
1821 }
1822 }
1823
1824 // Run application
1825 return run(programPath, opts, static_cast<size_t>(numRects));
1826 }
1827