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 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 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 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 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 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 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 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 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 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 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 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> 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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: 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> 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> 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 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 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 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 1486 View::onEvent(const pugl::UpdateEvent&) 1487 { 1488 return postRedisplay(); 1489 } 1490 1491 VkResult 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 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 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 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 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 1655 View::onEvent(const pugl::TimerEvent&) 1656 { 1657 return postRedisplay(); 1658 } 1659 1660 pugl::Status 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 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 1683 View::onEvent(const pugl::CloseEvent&) 1684 { 1685 _app.quit = true; 1686 1687 return pugl::Status::success; 1688 } 1689 1690 VkResult 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 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 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