aboutsummaryrefslogtreecommitdiffstats
path: root/examples/pugl_vulkan_cxx_demo.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'examples/pugl_vulkan_cxx_demo.cpp')
-rw-r--r--examples/pugl_vulkan_cxx_demo.cpp1826
1 files changed, 0 insertions, 1826 deletions
diff --git a/examples/pugl_vulkan_cxx_demo.cpp b/examples/pugl_vulkan_cxx_demo.cpp
deleted file mode 100644
index d92e652..0000000
--- a/examples/pugl_vulkan_cxx_demo.cpp
+++ /dev/null
@@ -1,1826 +0,0 @@
-/*
- Copyright 2019-2020 David Robillard <d@drobilla.net>
-
- Permission to use, copy, modify, and/or distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
- copyright notice and this permission notice appear in all copies.
-
- THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-*/
-
-/*
- An example of drawing with Vulkan.
-
- This is an example of using Vulkan for pixel-perfect 2D drawing. It uses
- the same data and shaders as pugl_shader_demo.c and attempts to draw the
- same thing, except using Vulkan.
-
- Since Vulkan is a complicated and very verbose API, this example is
- unfortunately much larger than the others. You should not use this as a
- resource to learn Vulkan, but it provides a decent demo of using Vulkan with
- Pugl that works nicely on all supported platforms.
-*/
-
-#include "demo_utils.h"
-#include "file_utils.h"
-#include "rects.h"
-#include "test/test_utils.h"
-
-#include "sybok.hpp"
-
-#include "pugl/pugl.h"
-#include "pugl/pugl.hpp"
-#include "pugl/vulkan.hpp"
-
-#include <vulkan/vk_platform.h>
-
-#include <algorithm>
-#include <array>
-#include <cassert>
-#include <cstddef>
-#include <cstdint>
-#include <cstdio>
-#include <cstdlib>
-#include <cstring>
-#include <initializer_list>
-#include <iomanip>
-#include <iostream>
-#include <memory>
-#include <string>
-#include <vector>
-
-namespace {
-
-constexpr uintptr_t resizeTimerId = 1u;
-
-struct PhysicalDeviceSelection {
- sk::PhysicalDevice physicalDevice;
- uint32_t graphicsFamilyIndex;
-};
-
-/// Basic Vulkan context associated with the window
-struct VulkanContext {
- VkResult init(pugl::VulkanLoader& loader, const PuglTestOptions& opts);
-
- sk::VulkanApi vk;
- sk::Instance instance;
- sk::DebugReportCallbackEXT debugCallback;
-};
-
-/// Basic setup of graphics device
-struct GraphicsDevice {
- VkResult init(const pugl::VulkanLoader& loader,
- const VulkanContext& context,
- pugl::View& view,
- const PuglTestOptions& opts);
-
- sk::SurfaceKHR surface;
- sk::PhysicalDevice physicalDevice{};
- uint32_t graphicsIndex{};
- VkSurfaceFormatKHR surfaceFormat{};
- VkPresentModeKHR presentMode{};
- VkPresentModeKHR resizePresentMode{};
- sk::Device device{};
- sk::Queue graphicsQueue{};
- sk::CommandPool commandPool{};
-};
-
-/// Buffer allocated on the GPU
-struct Buffer {
- VkResult init(const sk::VulkanApi& vk,
- const GraphicsDevice& gpu,
- VkDeviceSize size,
- VkBufferUsageFlags usage,
- VkMemoryPropertyFlags properties);
-
- sk::Buffer buffer;
- sk::DeviceMemory deviceMemory;
-};
-
-/// A set of frames that can be rendered concurrently
-struct Swapchain {
- VkResult init(const sk::VulkanApi& vk,
- const GraphicsDevice& gpu,
- VkSurfaceCapabilitiesKHR capabilities,
- VkExtent2D extent,
- VkSwapchainKHR oldSwapchain,
- bool resizing);
-
- VkSurfaceCapabilitiesKHR capabilities{};
- VkExtent2D extent{};
- sk::SwapchainKHR swapchain{};
- std::vector<sk::ImageView> imageViews{};
-};
-
-/// A pass that renders to a target
-struct RenderPass {
- VkResult init(const sk::VulkanApi& vk,
- const GraphicsDevice& gpu,
- const Swapchain& swapchain);
-
- sk::RenderPass renderPass;
- std::vector<sk::Framebuffer> framebuffers;
- sk::CommandBuffers<std::vector<VkCommandBuffer>> commandBuffers;
-};
-
-/// Uniform buffer for constant data used in shaders
-struct UniformBufferObject {
- mat4 projection;
-};
-
-/// Rectangle data that does not depend on renderer configuration
-struct RectData {
- VkResult init(const sk::VulkanApi& vk,
- const GraphicsDevice& gpu,
- size_t nRects);
-
- sk::DescriptorSetLayout descriptorSetLayout{};
- Buffer uniformBuffer{};
- sk::MappedMemory uniformData{};
- Buffer modelBuffer{};
- Buffer instanceBuffer{};
- sk::MappedMemory vertexData{};
- size_t numRects{};
-};
-
-/// Shader modules for drawing rectangles
-struct RectShaders {
- VkResult init(const sk::VulkanApi& vk,
- const GraphicsDevice& gpu,
- const std::string& programPath);
-
- sk::ShaderModule vert{};
- sk::ShaderModule frag{};
-};
-
-/// A pipeline to render rectangles with our shaders
-struct RectPipeline {
- VkResult init(const sk::VulkanApi& vk,
- const GraphicsDevice& gpu,
- const RectData& rectData,
- const RectShaders& shaders,
- const Swapchain& swapchain,
- const RenderPass& renderPass);
-
- sk::DescriptorPool descriptorPool{};
- sk::DescriptorSets<std::vector<VkDescriptorSet>> descriptorSets{};
- sk::PipelineLayout pipelineLayout{};
- std::array<sk::Pipeline, 1> pipelines{};
- uint32_t numImages{};
-};
-
-/// Synchronization primitives used to coordinate drawing frames
-struct RenderSync {
- VkResult init(const sk::VulkanApi& vk,
- const sk::Device& device,
- uint32_t numImages);
-
- std::vector<sk::Semaphore> imageAvailable{};
- std::vector<sk::Semaphore> renderFinished{};
- std::vector<sk::Fence> inFlight{};
- size_t currentFrame{};
-};
-
-/// Renderer that owns the above and everything required to draw
-struct Renderer {
- VkResult init(const sk::VulkanApi& vk,
- const GraphicsDevice& gpu,
- const RectData& rectData,
- const RectShaders& rectShaders,
- VkExtent2D extent,
- bool resizing);
-
- VkResult recreate(const sk::VulkanApi& vk,
- const sk::SurfaceKHR& surface,
- const GraphicsDevice& gpu,
- const RectData& rectData,
- const RectShaders& rectShaders,
- VkExtent2D extent,
- bool resizing);
-
- Swapchain swapchain;
- RenderPass renderPass;
- RectPipeline rectPipeline;
- RenderSync sync;
-};
-
-VkResult
-selectSurfaceFormat(const sk::VulkanApi& vk,
- const sk::PhysicalDevice& physicalDevice,
- const sk::SurfaceKHR& surface,
- VkSurfaceFormatKHR& surfaceFormat)
-{
- std::vector<VkSurfaceFormatKHR> formats;
- if (VkResult r = vk.getPhysicalDeviceSurfaceFormatsKHR(
- physicalDevice, surface, formats)) {
- return r;
- }
-
- for (const auto& format : formats) {
- if (format.format == VK_FORMAT_B8G8R8A8_UNORM &&
- format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
- surfaceFormat = format;
- return VK_SUCCESS;
- }
- }
-
- return VK_ERROR_FORMAT_NOT_SUPPORTED;
-}
-
-VkResult
-selectPresentMode(const sk::VulkanApi& vk,
- const sk::PhysicalDevice& physicalDevice,
- const sk::SurfaceKHR& surface,
- const bool multiBuffer,
- const bool sync,
- VkPresentModeKHR& presentMode)
-{
- // Map command line options to mode priorities
- static constexpr VkPresentModeKHR priorities[][2][4] = {
- {
- // No double buffer, no sync
- {VK_PRESENT_MODE_IMMEDIATE_KHR,
- VK_PRESENT_MODE_MAILBOX_KHR,
- VK_PRESENT_MODE_FIFO_RELAXED_KHR,
- VK_PRESENT_MODE_FIFO_KHR},
-
- // No double buffer, sync (nonsense, map to FIFO relaxed)
- {VK_PRESENT_MODE_FIFO_RELAXED_KHR,
- VK_PRESENT_MODE_FIFO_KHR,
- VK_PRESENT_MODE_MAILBOX_KHR,
- VK_PRESENT_MODE_IMMEDIATE_KHR},
- },
- {
- // Double buffer, no sync
- {
- VK_PRESENT_MODE_MAILBOX_KHR,
- VK_PRESENT_MODE_IMMEDIATE_KHR,
- VK_PRESENT_MODE_FIFO_RELAXED_KHR,
- VK_PRESENT_MODE_FIFO_KHR,
- },
-
- // Double buffer, sync
- {VK_PRESENT_MODE_FIFO_KHR,
- VK_PRESENT_MODE_FIFO_RELAXED_KHR,
- VK_PRESENT_MODE_MAILBOX_KHR,
- VK_PRESENT_MODE_IMMEDIATE_KHR},
- },
- };
-
- std::vector<VkPresentModeKHR> modes;
- if (VkResult r = vk.getPhysicalDeviceSurfacePresentModesKHR(
- physicalDevice, surface, modes)) {
- return r;
- }
-
- const auto& tryModes = priorities[bool(multiBuffer)][bool(sync)];
- for (const auto m : tryModes) {
- if (std::find(modes.begin(), modes.end(), m) != modes.end()) {
- presentMode = m;
- return VK_SUCCESS;
- }
- }
-
- return VK_ERROR_INCOMPATIBLE_DRIVER;
-}
-
-VkResult
-openDevice(const sk::VulkanApi& vk,
- const sk::PhysicalDevice& physicalDevice,
- const uint32_t graphicsFamilyIndex,
- sk::Device& device)
-{
- const float graphicsQueuePriority = 1.0f;
- const char* const swapchainName = "VK_KHR_swapchain";
-
- const VkDeviceQueueCreateInfo queueCreateInfo{
- VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
- nullptr,
- 0u,
- graphicsFamilyIndex,
- SK_COUNTED(1u, &graphicsQueuePriority),
- };
-
- const VkDeviceCreateInfo createInfo{VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
- nullptr,
- 0u,
- SK_COUNTED(1u, &queueCreateInfo),
- SK_COUNTED(0u, nullptr), // Deprecated
- SK_COUNTED(1u, &swapchainName),
- nullptr};
-
- return vk.createDevice(physicalDevice, createInfo, device);
-}
-
-/// Return whether the physical device supports the extensions we require
-VkResult
-deviceSupportsRequiredExtensions(const sk::VulkanApi& vk,
- const sk::PhysicalDevice& device,
- bool& supported)
-{
- VkResult r = VK_SUCCESS;
-
- std::vector<VkExtensionProperties> props;
- if ((r = vk.enumerateDeviceExtensionProperties(device, props))) {
- return r;
- }
-
- supported = std::any_of(
- props.begin(), props.end(), [&](const VkExtensionProperties& e) {
- return !strcmp(e.extensionName, "VK_KHR_swapchain");
- });
-
- return VK_SUCCESS;
-}
-
-/// Return the index of the graphics queue, if there is one
-VkResult
-findGraphicsQueue(const sk::VulkanApi& vk,
- const sk::SurfaceKHR& surface,
- const sk::PhysicalDevice& device,
- uint32_t& queueIndex)
-{
- VkResult r = VK_SUCCESS;
-
- std::vector<VkQueueFamilyProperties> queueProps;
- if ((r = vk.getPhysicalDeviceQueueFamilyProperties(device, queueProps))) {
- return r;
- }
-
- for (uint32_t q = 0u; q < queueProps.size(); ++q) {
- if (queueProps[q].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
- bool supported = false;
- if ((r = vk.getPhysicalDeviceSurfaceSupportKHR(
- device, q, surface, supported))) {
- return r;
- }
-
- if (supported) {
- queueIndex = q;
- return VK_SUCCESS;
- }
- }
- }
-
- return VK_ERROR_FEATURE_NOT_PRESENT;
-}
-
-/// Select a physical graphics device to use (simply the first found)
-VkResult
-selectPhysicalDevice(const sk::VulkanApi& vk,
- const sk::Instance& instance,
- const sk::SurfaceKHR& surface,
- PhysicalDeviceSelection& selection)
-{
- VkResult r = VK_SUCCESS;
-
- std::vector<sk::PhysicalDevice> devices;
- if ((r = vk.enumeratePhysicalDevices(instance, devices))) {
- return r;
- }
-
- for (const auto& device : devices) {
- auto supported = false;
- if ((r = deviceSupportsRequiredExtensions(vk, device, supported))) {
- return r;
- }
-
- if (supported) {
- auto queueIndex = 0u;
- if ((r = findGraphicsQueue(vk, surface, device, queueIndex))) {
- return r;
- }
-
- selection = PhysicalDeviceSelection{device, queueIndex};
- return VK_SUCCESS;
- }
- }
-
- return VK_ERROR_INCOMPATIBLE_DISPLAY_KHR;
-}
-
-VkResult
-GraphicsDevice::init(const pugl::VulkanLoader& loader,
- const VulkanContext& context,
- pugl::View& view,
- const PuglTestOptions& opts)
-{
- const auto& vk = context.vk;
- VkResult r = VK_SUCCESS;
-
- // Create a Vulkan surface for the window using the Pugl API
- VkSurfaceKHR surfaceHandle = {};
- if ((r = pugl::createSurface(loader.getInstanceProcAddrFunc(),
- view,
- context.instance,
- nullptr,
- &surfaceHandle))) {
- return r;
- }
-
- // Wrap surface in a safe RAII handle
- surface =
- sk::SurfaceKHR{surfaceHandle, {context.instance, vk.vkDestroySurfaceKHR}};
-
- PhysicalDeviceSelection physicalDeviceSelection = {};
- // Select a physical device to use
- if ((r = selectPhysicalDevice(
- vk, context.instance, surface, physicalDeviceSelection))) {
- return r;
- }
-
- physicalDevice = physicalDeviceSelection.physicalDevice;
- graphicsIndex = physicalDeviceSelection.graphicsFamilyIndex;
-
- if ((r = selectSurfaceFormat(vk, physicalDevice, surface, surfaceFormat)) ||
- (r = selectPresentMode(vk,
- physicalDevice,
- surface,
- opts.doubleBuffer,
- opts.sync,
- presentMode)) ||
- (r = selectPresentMode(
- vk, physicalDevice, surface, true, false, resizePresentMode)) ||
- (r = openDevice(vk, physicalDevice, graphicsIndex, device))) {
- return r;
- }
-
- const VkCommandPoolCreateInfo commandPoolInfo{
- VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, nullptr, {}, graphicsIndex};
-
- if ((r = vk.createCommandPool(device, commandPoolInfo, commandPool))) {
- return r;
- }
-
- graphicsQueue = vk.getDeviceQueue(device, graphicsIndex, 0);
- return VK_SUCCESS;
-}
-
-uint32_t
-findMemoryType(const sk::VulkanApi& vk,
- const sk::PhysicalDevice& physicalDevice,
- const uint32_t typeFilter,
- const VkMemoryPropertyFlags& properties)
-{
- VkPhysicalDeviceMemoryProperties memProperties =
- vk.getPhysicalDeviceMemoryProperties(physicalDevice);
-
- for (uint32_t i = 0; i < memProperties.memoryTypeCount; ++i) {
- if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags &
- properties) == properties) {
- return i;
- }
- }
-
- return UINT32_MAX;
-}
-
-VkResult
-Buffer::init(const sk::VulkanApi& vk,
- const GraphicsDevice& gpu,
- const VkDeviceSize size,
- const VkBufferUsageFlags usage,
- const VkMemoryPropertyFlags properties)
-{
- const VkBufferCreateInfo bufferInfo{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
- nullptr,
- {},
- size,
- usage,
- VK_SHARING_MODE_EXCLUSIVE,
- SK_COUNTED(0, nullptr)};
-
- const auto& device = gpu.device;
-
- VkResult r = VK_SUCCESS;
- if ((r = vk.createBuffer(device, bufferInfo, buffer))) {
- return r;
- }
-
- const auto requirements = vk.getBufferMemoryRequirements(device, buffer);
- const auto memoryTypeIndex = findMemoryType(
- vk, gpu.physicalDevice, requirements.memoryTypeBits, properties);
-
- if (memoryTypeIndex == UINT32_MAX) {
- return VK_ERROR_FEATURE_NOT_PRESENT;
- }
-
- const VkMemoryAllocateInfo allocInfo{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
- nullptr,
- requirements.size,
- memoryTypeIndex};
-
- if ((r = vk.allocateMemory(device, allocInfo, deviceMemory)) ||
- (r = vk.bindBufferMemory(device, buffer, deviceMemory, 0))) {
- return r;
- }
-
- return VK_SUCCESS;
-}
-
-VkResult
-Swapchain::init(const sk::VulkanApi& vk,
- const GraphicsDevice& gpu,
- const VkSurfaceCapabilitiesKHR surfaceCapabilities,
- const VkExtent2D surfaceExtent,
- VkSwapchainKHR oldSwapchain,
- bool resizing)
-{
- capabilities = surfaceCapabilities;
- extent = surfaceExtent;
-
- const auto minNumImages =
- (!capabilities.maxImageCount || capabilities.maxImageCount >= 3u)
- ? 3u
- : capabilities.maxImageCount;
-
- const VkSwapchainCreateInfoKHR swapchainCreateInfo{
- VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
- nullptr,
- {},
- gpu.surface,
- minNumImages,
- gpu.surfaceFormat.format,
- gpu.surfaceFormat.colorSpace,
- surfaceExtent,
- 1,
- (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT),
- VK_SHARING_MODE_EXCLUSIVE,
- SK_COUNTED(0, nullptr),
- capabilities.currentTransform,
- VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR,
- resizing ? gpu.resizePresentMode : gpu.presentMode,
- VK_TRUE,
- oldSwapchain};
-
- VkResult r = VK_SUCCESS;
- std::vector<VkImage> images;
- if ((r = vk.createSwapchainKHR(gpu.device, swapchainCreateInfo, swapchain)) ||
- (r = vk.getSwapchainImagesKHR(gpu.device, swapchain, images))) {
- return r;
- }
-
- imageViews = std::vector<sk::ImageView>(images.size());
- for (size_t i = 0; i < images.size(); ++i) {
- const VkImageViewCreateInfo imageViewCreateInfo{
- VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
- nullptr,
- {},
- images[i],
- VK_IMAGE_VIEW_TYPE_2D,
- gpu.surfaceFormat.format,
- {},
- {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
-
- if ((r = vk.createImageView(
- gpu.device, imageViewCreateInfo, imageViews[i]))) {
- return r;
- }
- }
-
- return VK_SUCCESS;
-}
-
-VkResult
-RenderPass::init(const sk::VulkanApi& vk,
- const GraphicsDevice& gpu,
- const Swapchain& swapchain)
-{
- const auto numImages = static_cast<uint32_t>(swapchain.imageViews.size());
-
- assert(numImages > 0);
-
- // Create command buffers
- const VkCommandBufferAllocateInfo commandBufferAllocateInfo{
- VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
- nullptr,
- gpu.commandPool,
- VK_COMMAND_BUFFER_LEVEL_PRIMARY,
- numImages};
-
- VkResult r = VK_SUCCESS;
- if ((r = vk.allocateCommandBuffers(
- gpu.device, commandBufferAllocateInfo, commandBuffers))) {
- return r;
- }
-
- static constexpr VkAttachmentReference colorAttachmentRef{
- 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
-
- static constexpr VkSubpassDescription subpass{
- {},
- VK_PIPELINE_BIND_POINT_GRAPHICS,
- SK_COUNTED(0, nullptr),
- SK_COUNTED(1, &colorAttachmentRef, nullptr, nullptr),
- SK_COUNTED(0u, nullptr)};
-
- static constexpr VkSubpassDependency dependency{
- VK_SUBPASS_EXTERNAL,
- 0,
- VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
- VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
- (VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
- VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT),
- {},
- {}};
-
- const VkAttachmentDescription colorAttachment{
- {},
- gpu.surfaceFormat.format,
- VK_SAMPLE_COUNT_1_BIT,
- VK_ATTACHMENT_LOAD_OP_CLEAR,
- VK_ATTACHMENT_STORE_OP_STORE,
- VK_ATTACHMENT_LOAD_OP_DONT_CARE,
- VK_ATTACHMENT_STORE_OP_DONT_CARE,
- VK_IMAGE_LAYOUT_UNDEFINED,
- VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
- };
-
- const VkRenderPassCreateInfo renderPassCreateInfo{
- VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
- nullptr,
- {},
- SK_COUNTED(1, &colorAttachment),
- SK_COUNTED(1, &subpass),
- SK_COUNTED(1, &dependency)};
-
- if ((r = vk.createRenderPass(gpu.device, renderPassCreateInfo, renderPass))) {
- return r;
- }
-
- // Create framebuffers
- framebuffers = std::vector<sk::Framebuffer>(numImages);
- for (uint32_t i = 0; i < numImages; ++i) {
- const VkFramebufferCreateInfo framebufferCreateInfo{
- VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
- nullptr,
- {},
- renderPass,
- SK_COUNTED(1, &swapchain.imageViews[i].get()),
- swapchain.extent.width,
- swapchain.extent.height,
- 1};
-
- if ((r = vk.createFramebuffer(
- gpu.device, framebufferCreateInfo, framebuffers[i]))) {
- return r;
- }
- }
-
- return VK_SUCCESS;
-}
-
-std::vector<uint32_t>
-readFile(const char* const programPath, const std::string& filename)
-{
- std::unique_ptr<char, decltype(&free)> path{
- resourcePath(programPath, filename.c_str()), &free};
-
- std::cerr << "Loading shader: " << path.get() << std::endl;
-
- std::unique_ptr<FILE, decltype(&fclose)> file{fopen(path.get(), "rb"),
- &fclose};
-
- if (!file) {
- std::cerr << "Failed to open file '" << filename << "'\n";
- return {};
- }
-
- fseek(file.get(), 0, SEEK_END);
- const auto fileSize = static_cast<size_t>(ftell(file.get()));
- fseek(file.get(), 0, SEEK_SET);
-
- const auto numWords = fileSize / sizeof(uint32_t);
- std::vector<uint32_t> buffer(numWords);
-
- fread(buffer.data(), sizeof(uint32_t), numWords, file.get());
-
- return buffer;
-}
-
-VkResult
-createShaderModule(const sk::VulkanApi& vk,
- const GraphicsDevice& gpu,
- const std::vector<uint32_t>& code,
- sk::ShaderModule& shaderModule)
-{
- const VkShaderModuleCreateInfo createInfo{
- VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
- nullptr,
- {},
- code.size() * sizeof(uint32_t),
- code.data()};
-
- return vk.createShaderModule(gpu.device, createInfo, shaderModule);
-}
-
-VkResult
-RectShaders::init(const sk::VulkanApi& vk,
- const GraphicsDevice& gpu,
- const std::string& programPath)
-{
- auto vertShaderCode = readFile(programPath.c_str(), "shaders/rect.vert.spv");
-
- auto fragShaderCode = readFile(programPath.c_str(), "shaders/rect.frag.spv");
-
- if (vertShaderCode.empty() || fragShaderCode.empty()) {
- return VK_ERROR_INITIALIZATION_FAILED;
- }
-
- VkResult r = VK_SUCCESS;
- if ((r = createShaderModule(vk, gpu, vertShaderCode, vert)) ||
- (r = createShaderModule(vk, gpu, fragShaderCode, frag))) {
- return r;
- }
-
- return VK_SUCCESS;
-}
-
-VkResult
-RectPipeline::init(const sk::VulkanApi& vk,
- const GraphicsDevice& gpu,
- const RectData& rectData,
- const RectShaders& shaders,
- const Swapchain& swapchain,
- const RenderPass& renderPass)
-{
- const auto oldNumImages = numImages;
- VkResult r = VK_SUCCESS;
-
- numImages = static_cast<uint32_t>(swapchain.imageViews.size());
- pipelines = {};
- pipelineLayout = {};
- descriptorSets = {};
-
- if (numImages != oldNumImages) {
- // Create layout descriptor pool
-
- const VkDescriptorPoolSize poolSize{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
- numImages};
-
- const VkDescriptorPoolCreateInfo descriptorPoolCreateInfo{
- VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
- nullptr,
- VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
- numImages,
- 1u,
- &poolSize};
- if ((r = vk.createDescriptorPool(
- gpu.device, descriptorPoolCreateInfo, descriptorPool))) {
- return r;
- }
- }
-
- const std::vector<VkDescriptorSetLayout> layouts(
- numImages, rectData.descriptorSetLayout.get());
-
- const VkDescriptorSetAllocateInfo descriptorSetAllocateInfo{
- VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
- nullptr,
- descriptorPool,
- numImages,
- layouts.data()};
- if ((r = vk.allocateDescriptorSets(
- gpu.device, descriptorSetAllocateInfo, descriptorSets))) {
- return r;
- }
-
- const VkDescriptorBufferInfo bufferInfo{
- rectData.uniformBuffer.buffer, 0, sizeof(UniformBufferObject)};
-
- const std::array<VkWriteDescriptorSet, 1> descriptorWrites{
- {{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
- nullptr,
- descriptorSets[0],
- 0,
- 0,
- 1,
- VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
- nullptr,
- &bufferInfo,
- nullptr}}};
-
- const std::array<VkCopyDescriptorSet, 0> descriptorCopies{};
-
- vk.updateDescriptorSets(gpu.device, descriptorWrites, descriptorCopies);
-
- static constexpr std::array<VkVertexInputAttributeDescription, 4>
- vertexAttributeDescriptions{
- {// Model
- {0u, 0u, VK_FORMAT_R32G32_SFLOAT, 0},
-
- // Rect instance attributes
- {1u, 1u, VK_FORMAT_R32G32_SFLOAT, offsetof(Rect, pos)},
- {2u, 1u, VK_FORMAT_R32G32_SFLOAT, offsetof(Rect, size)},
- {3u, 1u, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(Rect, fillColor)}}};
-
- static constexpr std::array<VkVertexInputBindingDescription, 2>
- vertexBindingDescriptions{
- VkVertexInputBindingDescription{
- 0, sizeof(vec2), VK_VERTEX_INPUT_RATE_VERTEX},
- VkVertexInputBindingDescription{
- 1u, sizeof(Rect), VK_VERTEX_INPUT_RATE_INSTANCE}};
-
- static constexpr VkPipelineInputAssemblyStateCreateInfo inputAssembly{
- VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
- nullptr,
- {},
- VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP,
- false};
-
- static constexpr VkPipelineRasterizationStateCreateInfo rasterizer{
- VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
- nullptr,
- {},
- 0,
- 0,
- VK_POLYGON_MODE_FILL,
- VK_CULL_MODE_BACK_BIT,
- VK_FRONT_FACE_CLOCKWISE,
- 0,
- 0,
- 0,
- 0,
- 1.0f};
-
- static constexpr VkPipelineMultisampleStateCreateInfo multisampling{
- VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
- nullptr,
- {},
- VK_SAMPLE_COUNT_1_BIT,
- false,
- 0.0f,
- nullptr,
- false,
- false};
-
- static constexpr VkPipelineColorBlendAttachmentState colorBlendAttachment{
- true,
- VK_BLEND_FACTOR_SRC_ALPHA,
- VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
- VK_BLEND_OP_ADD,
- VK_BLEND_FACTOR_ONE,
- VK_BLEND_FACTOR_ZERO,
- VK_BLEND_OP_ADD,
- (VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
- VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT)};
-
- const VkPipelineShaderStageCreateInfo shaderStages[] = {
- {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
- nullptr,
- {},
- VK_SHADER_STAGE_VERTEX_BIT,
- shaders.vert.get(),
- "main",
- nullptr},
- {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
- nullptr,
- {},
- VK_SHADER_STAGE_FRAGMENT_BIT,
- shaders.frag.get(),
- "main",
- nullptr}};
-
- const VkPipelineVertexInputStateCreateInfo vertexInputInfo{
- VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
- nullptr,
- {},
- SK_COUNTED(static_cast<uint32_t>(vertexBindingDescriptions.size()),
- vertexBindingDescriptions.data()),
- SK_COUNTED(static_cast<uint32_t>(vertexAttributeDescriptions.size()),
- vertexAttributeDescriptions.data())};
-
- const VkViewport viewport{0.0f,
- 0.0f,
- float(swapchain.extent.width),
- float(swapchain.extent.height),
- 0.0f,
- 1.0f};
-
- const VkRect2D scissor{{0, 0}, swapchain.extent};
-
- const VkPipelineViewportStateCreateInfo viewportState{
- VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
- nullptr,
- {},
- SK_COUNTED(1, &viewport),
- SK_COUNTED(1, &scissor)};
-
- const VkPipelineColorBlendStateCreateInfo colorBlending{
- VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
- nullptr,
- {},
- false,
- VK_LOGIC_OP_COPY,
- SK_COUNTED(1, &colorBlendAttachment),
- {1.0f, 0.0f, 0.0f, 0.0f}};
-
- const VkPipelineLayoutCreateInfo layoutInfo{
- VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
- nullptr,
- {},
- SK_COUNTED(1, &rectData.descriptorSetLayout.get()),
- SK_COUNTED(0, nullptr)};
-
- if ((r = vk.createPipelineLayout(gpu.device, layoutInfo, pipelineLayout))) {
- return r;
- }
-
- const std::array<VkGraphicsPipelineCreateInfo, 1> pipelineInfos{
- {{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
- nullptr,
- {},
- SK_COUNTED(2, shaderStages),
- &vertexInputInfo,
- &inputAssembly,
- nullptr,
- &viewportState,
- &rasterizer,
- &multisampling,
- nullptr,
- &colorBlending,
- nullptr,
- pipelineLayout,
- renderPass.renderPass,
- 0u,
- {},
- 0}}};
-
- if ((r = vk.createGraphicsPipelines(
- gpu.device, {}, pipelineInfos, pipelines))) {
- return r;
- }
-
- return VK_SUCCESS;
-}
-
-VkResult
-RectData::init(const sk::VulkanApi& vk,
- const GraphicsDevice& gpu,
- const size_t nRects)
-{
- numRects = nRects;
-
- static constexpr VkDescriptorSetLayoutBinding uboLayoutBinding{
- 0,
- VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
- 1,
- VK_SHADER_STAGE_VERTEX_BIT,
- nullptr};
-
- const VkDescriptorSetLayoutCreateInfo descriptorSetLayoutInfo{
- VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
- nullptr,
- {},
- 1,
- &uboLayoutBinding};
-
- VkResult r = VK_SUCCESS;
- if ((r = vk.createDescriptorSetLayout(
- gpu.device, descriptorSetLayoutInfo, descriptorSetLayout)) ||
- (r = uniformBuffer.init(vk,
- gpu,
- sizeof(UniformBufferObject),
- VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
- VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
- VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) ||
- (r = vk.mapMemory(gpu.device,
- uniformBuffer.deviceMemory,
- 0,
- sizeof(UniformBufferObject),
- {},
- uniformData))) {
- return r;
- }
-
- const VkBufferUsageFlags usageFlags =
- (VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
- VK_BUFFER_USAGE_TRANSFER_DST_BIT);
-
- const VkMemoryPropertyFlags propertyFlags =
- (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
- VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
-
- if ((r = modelBuffer.init(
- vk, gpu, sizeof(rectVertices), usageFlags, propertyFlags))) {
- return r;
- }
-
- {
- // Copy model vertices (directly, we do this only once)
- sk::MappedMemory modelData;
- if ((r = vk.mapMemory(gpu.device,
- modelBuffer.deviceMemory,
- 0,
- static_cast<VkDeviceSize>(sizeof(rectVertices)),
- {},
- modelData))) {
- return r;
- }
-
- memcpy(modelData.get(), rectVertices, sizeof(rectVertices));
- }
-
- if ((r = instanceBuffer.init(
- vk, gpu, sizeof(Rect) * numRects, usageFlags, propertyFlags))) {
- return r;
- }
-
- // Map attribute vertices (we will update them every frame)
- const auto rectsSize = static_cast<VkDeviceSize>(sizeof(Rect) * numRects);
- if ((r = vk.mapMemory(gpu.device,
- instanceBuffer.deviceMemory,
- 0,
- rectsSize,
- {},
- vertexData))) {
- return r;
- }
-
- return VK_SUCCESS;
-}
-
-VkResult
-RenderSync::init(const sk::VulkanApi& vk,
- const sk::Device& device,
- const uint32_t numImages)
-{
- const auto maxInFlight = std::max(1u, numImages - 1u);
- VkResult r = VK_SUCCESS;
-
- imageAvailable = std::vector<sk::Semaphore>(numImages);
- renderFinished = std::vector<sk::Semaphore>(numImages);
- for (uint32_t i = 0; i < numImages; ++i) {
- static constexpr VkSemaphoreCreateInfo semaphoreInfo{
- VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, nullptr, {}};
-
- if ((r = vk.createSemaphore(device, semaphoreInfo, imageAvailable[i])) ||
- (r = vk.createSemaphore(device, semaphoreInfo, renderFinished[i]))) {
- return r;
- }
- }
-
- inFlight = std::vector<sk::Fence>(maxInFlight);
- for (uint32_t i = 0; i < maxInFlight; ++i) {
- static constexpr VkFenceCreateInfo fenceInfo{
- VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
- nullptr,
- VK_FENCE_CREATE_SIGNALED_BIT};
-
- if ((r = vk.createFence(device, fenceInfo, inFlight[i]))) {
- return r;
- }
- }
-
- return VK_SUCCESS;
-}
-
-VkResult
-Renderer::init(const sk::VulkanApi& vk,
- const GraphicsDevice& gpu,
- const RectData& rectData,
- const RectShaders& rectShaders,
- const VkExtent2D extent,
- bool resizing)
-{
- VkResult r = VK_SUCCESS;
- VkSurfaceCapabilitiesKHR capabilities = {};
-
- if ((r = vk.getPhysicalDeviceSurfaceCapabilitiesKHR(
- gpu.physicalDevice, gpu.surface, capabilities)) ||
- (r = swapchain.init(vk, gpu, capabilities, extent, {}, resizing)) ||
- (r = renderPass.init(vk, gpu, swapchain)) ||
- (r = rectPipeline.init(
- vk, gpu, rectData, rectShaders, swapchain, renderPass))) {
- return r;
- }
-
- const auto numFrames = static_cast<uint32_t>(swapchain.imageViews.size());
- return sync.init(vk, gpu.device, numFrames);
-}
-
-VkResult
-Renderer::recreate(const sk::VulkanApi& vk,
- const sk::SurfaceKHR& surface,
- const GraphicsDevice& gpu,
- const RectData& rectData,
- const RectShaders& rectShaders,
- const VkExtent2D extent,
- bool resizing)
-{
- VkResult r = VK_SUCCESS;
- const auto oldNumImages = swapchain.imageViews.size();
-
- VkSurfaceCapabilitiesKHR capabilities = {};
- if ((r = vk.getPhysicalDeviceSurfaceCapabilitiesKHR(
- gpu.physicalDevice, surface, capabilities)) ||
- (r = swapchain.init(
- vk, gpu, capabilities, extent, swapchain.swapchain, resizing)) ||
- (r = renderPass.init(vk, gpu, swapchain)) ||
- (r = rectPipeline.init(
- vk, gpu, rectData, rectShaders, swapchain, renderPass))) {
- return r;
- }
-
- const auto numFrames = static_cast<uint32_t>(swapchain.imageViews.size());
- if (swapchain.imageViews.size() != oldNumImages) {
- return sync.init(vk, gpu.device, numFrames);
- }
-
- return VK_SUCCESS;
-}
-
-VKAPI_ATTR
-VkBool32 VKAPI_CALL
-debugCallback(VkDebugReportFlagsEXT flags,
- VkDebugReportObjectTypeEXT,
- uint64_t,
- size_t,
- int32_t,
- const char* layerPrefix,
- const char* msg,
- void*)
-{
- std::cerr << sk::string(static_cast<VkDebugReportFlagBitsEXT>(flags)) << ": "
- << layerPrefix << ": " << msg << std::endl;
-
- return VK_FALSE;
-}
-
-bool
-hasExtension(const char* name,
- const std::vector<VkExtensionProperties>& properties)
-{
- for (const auto& p : properties) {
- if (!strcmp(p.extensionName, name)) {
- return true;
- }
- }
-
- return false;
-}
-
-bool
-hasLayer(const char* name, const std::vector<VkLayerProperties>& properties)
-{
- for (const auto& p : properties) {
- if (!strcmp(p.layerName, name)) {
- return true;
- }
- }
-
- return false;
-}
-
-template<class Value>
-void
-logInfo(const char* heading, const Value& value)
-{
- std::cout << std::setw(26) << std::left << (std::string(heading) + ":")
- << value << std::endl;
-}
-
-VkResult
-createInstance(sk::VulkanInitApi& initApi,
- const PuglTestOptions& opts,
- sk::Instance& instance)
-{
- VkResult r = VK_SUCCESS;
-
- std::vector<VkLayerProperties> layerProps;
- std::vector<VkExtensionProperties> extProps;
- if ((r = initApi.enumerateInstanceLayerProperties(layerProps)) ||
- (r = initApi.enumerateInstanceExtensionProperties(extProps))) {
- return r;
- }
-
- const auto puglExtensions = pugl::getInstanceExtensions();
- auto extensions =
- std::vector<const char*>(puglExtensions.begin(), puglExtensions.end());
-
- // Add extra extensions we want to use if they are supported
- if (hasExtension("VK_EXT_debug_report", extProps)) {
- extensions.push_back("VK_EXT_debug_report");
- }
-
- // Add validation layers if error checking is enabled
- std::vector<const char*> layers;
- if (opts.errorChecking) {
- for (const char* l : {"VK_LAYER_KHRONOS_validation",
- "VK_LAYER_LUNARG_standard_validation"}) {
- if (hasLayer(l, layerProps)) {
- layers.push_back(l);
- }
- }
- }
-
- for (const auto& e : extensions) {
- logInfo("Using instance extension", e);
- }
-
- for (const auto& l : layers) {
- logInfo("Using instance layer", l);
- }
-
- static constexpr VkApplicationInfo appInfo{
- VK_STRUCTURE_TYPE_APPLICATION_INFO,
- nullptr,
- "Pugl Vulkan Demo",
- 0,
- nullptr,
- 0,
- VK_MAKE_VERSION(1, 0, 0),
- };
-
- const VkInstanceCreateInfo createInfo{
- VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
- nullptr,
- VkInstanceCreateFlags{},
- &appInfo,
- SK_COUNTED(uint32_t(layers.size()), layers.data()),
- SK_COUNTED(uint32_t(extensions.size()), extensions.data())};
-
- return initApi.createInstance(createInfo, instance);
-}
-
-VkResult
-getDebugReportCallback(sk::VulkanApi& api,
- sk::Instance& instance,
- const bool verbose,
- sk::DebugReportCallbackEXT& callback)
-{
- if (api.vkCreateDebugReportCallbackEXT) {
- VkDebugReportFlagsEXT flags = (VK_DEBUG_REPORT_WARNING_BIT_EXT |
- VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT |
- VK_DEBUG_REPORT_ERROR_BIT_EXT);
-
- if (verbose) {
- flags |= VK_DEBUG_REPORT_INFORMATION_BIT_EXT;
- flags |= VK_DEBUG_REPORT_DEBUG_BIT_EXT;
- }
-
- const VkDebugReportCallbackCreateInfoEXT createInfo{
- VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT,
- nullptr,
- flags,
- debugCallback,
- nullptr};
-
- return api.createDebugReportCallbackEXT(instance, createInfo, callback);
- }
-
- return VK_ERROR_FEATURE_NOT_PRESENT;
-}
-
-void
-recordCommandBuffer(sk::CommandScope& cmd,
- const Swapchain& swapchain,
- const RenderPass& renderPass,
- const RectPipeline& rectPipeline,
- const RectData& rectData,
- const size_t imageIndex)
-{
- const VkClearColorValue clearColorValue{{0.0f, 0.0f, 0.0f, 1.0f}};
- const VkClearValue clearValue{clearColorValue};
-
- const VkRenderPassBeginInfo renderPassBegin{
- VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
- nullptr,
- renderPass.renderPass,
- renderPass.framebuffers[imageIndex],
- VkRect2D{{0, 0}, swapchain.extent},
- SK_COUNTED(1, &clearValue)};
-
- auto pass = cmd.beginRenderPass(renderPassBegin, VK_SUBPASS_CONTENTS_INLINE);
-
- pass.bindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, rectPipeline.pipelines[0]);
-
- const std::array<VkDeviceSize, 1> offsets{0};
- pass.bindVertexBuffers(
- 0u, SK_COUNTED(1u, &rectData.modelBuffer.buffer.get(), offsets.data()));
-
- pass.bindVertexBuffers(
- 1u, SK_COUNTED(1u, &rectData.instanceBuffer.buffer.get(), offsets.data()));
-
- pass.bindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS,
- rectPipeline.pipelineLayout,
- 0u,
- SK_COUNTED(1u, rectPipeline.descriptorSets.get()),
- 0u,
- nullptr);
-
- pass.draw(4u, static_cast<uint32_t>(rectData.numRects), 0u, 0u);
-}
-
-VkResult
-recordCommandBuffers(const sk::VulkanApi& vk,
- const Swapchain& swapchain,
- const RenderPass& renderPass,
- const RectPipeline& rectPipeline,
- const RectData& rectData)
-{
- VkResult r = VK_SUCCESS;
-
- for (size_t i = 0; i < swapchain.imageViews.size(); ++i) {
- const VkCommandBufferBeginInfo beginInfo{
- VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
- nullptr,
- VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT,
- nullptr};
-
- auto* const commandBuffer = renderPass.commandBuffers[i];
- auto cmd = vk.beginCommandBuffer(commandBuffer, beginInfo);
- if (!cmd) {
- return cmd.error();
- }
-
- recordCommandBuffer(cmd, swapchain, renderPass, rectPipeline, rectData, i);
-
- if ((r = cmd.end())) {
- return r;
- }
- }
-
- return VK_SUCCESS;
-}
-
-class PuglVulkanDemo;
-
-class View : public pugl::View
-{
-public:
- View(pugl::World& world, PuglVulkanDemo& app)
- : pugl::View{world}
- , _app{app}
- {
- setEventHandler(*this);
- }
-
- template<PuglEventType t, class Base>
- pugl::Status onEvent(const pugl::Event<t, Base>&) noexcept
- {
- return pugl::Status::success;
- }
-
- pugl::Status onEvent(const pugl::ConfigureEvent& event);
- pugl::Status onEvent(const pugl::UpdateEvent& event);
- pugl::Status onEvent(const pugl::ExposeEvent& event);
- pugl::Status onEvent(const pugl::LoopEnterEvent& event);
- pugl::Status onEvent(const pugl::TimerEvent& event);
- pugl::Status onEvent(const pugl::LoopLeaveEvent& event);
- pugl::Status onEvent(const pugl::KeyPressEvent& event);
- pugl::Status onEvent(const pugl::CloseEvent& event);
-
-private:
- PuglVulkanDemo& _app;
-};
-
-class PuglVulkanDemo
-{
-public:
- PuglVulkanDemo(const char* executablePath,
- const PuglTestOptions& o,
- size_t numRects);
-
- const char* programPath;
- PuglTestOptions opts;
- pugl::World world;
- pugl::VulkanLoader loader;
- View view;
- VulkanContext vulkan;
- GraphicsDevice gpu;
- Renderer renderer;
- RectData rectData;
- RectShaders rectShaders;
- uint32_t framesDrawn{0};
- VkExtent2D extent{512u, 512u};
- std::vector<Rect> rects;
- bool resizing{false};
- bool quit{false};
-};
-
-std::vector<Rect>
-makeRects(const size_t numRects, const uint32_t windowWidth)
-{
- std::vector<Rect> rects(numRects);
- for (size_t i = 0; i < numRects; ++i) {
- rects[i] = makeRect(i, static_cast<float>(windowWidth));
- }
-
- return rects;
-}
-
-PuglVulkanDemo::PuglVulkanDemo(const char* const executablePath,
- const PuglTestOptions& o,
- const size_t numRects)
- : programPath{executablePath}
- , opts{o}
- , world{pugl::WorldType::program, pugl::WorldFlag::threads}
- , loader{world}
- , view{world, *this}
- , rects{makeRects(numRects, extent.width)}
-{}
-
-VkResult
-recreateRenderer(PuglVulkanDemo& app,
- const sk::VulkanApi& vk,
- const GraphicsDevice& gpu,
- const VkExtent2D extent,
- const RectData& rectData,
- const RectShaders& rectShaders)
-{
- VkResult r = VK_SUCCESS;
- VkSurfaceCapabilitiesKHR capabilities = {};
- if ((r = vk.getPhysicalDeviceSurfaceCapabilitiesKHR(
- gpu.physicalDevice, gpu.surface, capabilities))) {
- return r;
- }
-
- // There is a known race issue here, so we clamp and hope for the best
- const VkExtent2D clampedExtent{
- std::min(capabilities.maxImageExtent.width,
- std::max(capabilities.minImageExtent.width, extent.width)),
- std::min(capabilities.maxImageExtent.height,
- std::max(capabilities.minImageExtent.height, extent.height))};
-
- if ((r = vk.deviceWaitIdle(gpu.device)) ||
- (r = app.renderer.recreate(vk,
- gpu.surface,
- gpu,
- rectData,
- rectShaders,
- clampedExtent,
- app.resizing))) {
- return r;
- }
-
- // Reset current (initially signaled) fence because we already waited
- vk.resetFence(gpu.device,
- app.renderer.sync.inFlight[app.renderer.sync.currentFrame]);
-
- // Record new command buffers
- return recordCommandBuffers(vk,
- app.renderer.swapchain,
- app.renderer.renderPass,
- app.renderer.rectPipeline,
- rectData);
-}
-
-pugl::Status
-View::onEvent(const pugl::ConfigureEvent& event)
-{
- // We just record the size here and lazily resize the surface when exposed
- _app.extent = {static_cast<uint32_t>(event.width),
- static_cast<uint32_t>(event.height)};
-
- return pugl::Status::success;
-}
-
-pugl::Status
-View::onEvent(const pugl::UpdateEvent&)
-{
- return postRedisplay();
-}
-
-VkResult
-beginFrame(PuglVulkanDemo& app, const sk::Device& device, uint32_t& imageIndex)
-{
- const auto& vk = app.vulkan.vk;
-
- VkResult r = VK_SUCCESS;
-
- // Wait until we can start rendering the next frame
- if ((r = vk.waitForFence(
- device, app.renderer.sync.inFlight[app.renderer.sync.currentFrame])) ||
- (r = vk.resetFence(
- device, app.renderer.sync.inFlight[app.renderer.sync.currentFrame]))) {
- return r;
- }
-
- // Rebuild the renderer first if the window size has changed
- if (app.extent.width != app.renderer.swapchain.extent.width ||
- app.extent.height != app.renderer.swapchain.extent.height) {
- if ((r = recreateRenderer(
- app, vk, app.gpu, app.extent, app.rectData, app.rectShaders))) {
- return r;
- }
- }
-
- // Acquire the next image to render, rebuilding if necessary
- while ((r = vk.acquireNextImageKHR(
- device,
- app.renderer.swapchain.swapchain,
- UINT64_MAX,
- app.renderer.sync.imageAvailable[app.renderer.sync.currentFrame],
- {},
- &imageIndex))) {
- switch (r) {
- case VK_SUBOPTIMAL_KHR:
- case VK_ERROR_OUT_OF_DATE_KHR:
- if ((r = recreateRenderer(app,
- vk,
- app.gpu,
- app.renderer.swapchain.extent,
- app.rectData,
- app.rectShaders))) {
- return r;
- }
- continue;
- default:
- return r;
- }
- }
-
- return VK_SUCCESS;
-}
-
-void
-update(PuglVulkanDemo& app, const double time)
-{
- // Animate rectangles
- for (size_t i = 0; i < app.rects.size(); ++i) {
- moveRect(&app.rects[i],
- i,
- app.rects.size(),
- static_cast<float>(app.extent.width),
- static_cast<float>(app.extent.height),
- time);
- }
-
- // Update vertex buffer
- memcpy(app.rectData.vertexData.get(),
- app.rects.data(),
- sizeof(Rect) * app.rects.size());
-
- // Update uniform buffer
- UniformBufferObject ubo = {{}};
- mat4Ortho(ubo.projection,
- 0.0f,
- float(app.renderer.swapchain.extent.width),
- 0.0f,
- float(app.renderer.swapchain.extent.height),
- -1.0f,
- 1.0f);
-
- memcpy(app.rectData.uniformData.get(), &ubo, sizeof(ubo));
-}
-
-VkResult
-endFrame(const sk::VulkanApi& vk,
- const GraphicsDevice& gpu,
- const Renderer& renderer,
- const uint32_t imageIndex)
-{
- const auto currentFrame = renderer.sync.currentFrame;
- VkResult r = VK_SUCCESS;
-
- static constexpr VkPipelineStageFlags waitStage =
- VK_PIPELINE_STAGE_TRANSFER_BIT;
-
- const VkSubmitInfo submitInfo{
- VK_STRUCTURE_TYPE_SUBMIT_INFO,
- nullptr,
- SK_COUNTED(1, &renderer.sync.imageAvailable[currentFrame].get()),
- &waitStage,
- SK_COUNTED(1, &renderer.renderPass.commandBuffers[imageIndex]),
- SK_COUNTED(1, &renderer.sync.renderFinished[imageIndex].get())};
-
- if ((r = vk.queueSubmit(gpu.graphicsQueue,
- submitInfo,
- renderer.sync.inFlight[currentFrame]))) {
- return r;
- }
-
- const VkPresentInfoKHR presentInfo{
- VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
- nullptr,
- SK_COUNTED(1, &renderer.sync.renderFinished[imageIndex].get()),
- SK_COUNTED(1, &renderer.swapchain.swapchain.get(), &imageIndex),
- nullptr};
-
- switch ((r = vk.queuePresentKHR(gpu.graphicsQueue, presentInfo))) {
- case VK_SUCCESS: // Sucessfully presented
- case VK_SUBOPTIMAL_KHR: // Probably a resize race, ignore
- case VK_ERROR_OUT_OF_DATE_KHR: // Probably a resize race, ignore
- break;
- default:
- return r;
- }
-
- return VK_SUCCESS;
-}
-
-pugl::Status
-View::onEvent(const pugl::ExposeEvent&)
-{
- const auto& vk = _app.vulkan.vk;
- const auto& gpu = _app.gpu;
-
- // Acquire the next image, waiting and/or rebuilding if necessary
- auto nextImageIndex = 0u;
- if (beginFrame(_app, gpu.device, nextImageIndex)) {
- return pugl::Status::unknownError;
- }
-
- // Ready to go, update the data to the current time
- update(_app, world().time());
-
- // Submit the frame to the queue and present it
- endFrame(vk, gpu, _app.renderer, nextImageIndex);
-
- ++_app.framesDrawn;
- ++_app.renderer.sync.currentFrame;
- _app.renderer.sync.currentFrame %= _app.renderer.sync.inFlight.size();
-
- return pugl::Status::success;
-}
-
-pugl::Status
-View::onEvent(const pugl::LoopEnterEvent&)
-{
- _app.resizing = true;
- startTimer(resizeTimerId,
- 1.0 / static_cast<double>(getHint(pugl::ViewHint::refreshRate)));
-
- return pugl::Status::success;
-}
-
-pugl::Status
-View::onEvent(const pugl::TimerEvent&)
-{
- return postRedisplay();
-}
-
-pugl::Status
-View::onEvent(const pugl::LoopLeaveEvent&)
-{
- stopTimer(resizeTimerId);
-
- // Trigger a swapchain recreation with the normal present mode
- _app.renderer.swapchain.extent = {};
- _app.resizing = false;
-
- return pugl::Status::success;
-}
-
-pugl::Status
-View::onEvent(const pugl::KeyPressEvent& event)
-{
- if (event.key == PUGL_KEY_ESCAPE || event.key == 'q') {
- _app.quit = true;
- }
-
- return pugl::Status::success;
-}
-
-pugl::Status
-View::onEvent(const pugl::CloseEvent&)
-{
- _app.quit = true;
-
- return pugl::Status::success;
-}
-
-VkResult
-VulkanContext::init(pugl::VulkanLoader& loader, const PuglTestOptions& opts)
-{
- VkResult r = VK_SUCCESS;
-
- sk::VulkanInitApi initApi{};
-
- // Load Vulkan API and set up the fundamentals
- if ((r = initApi.init(loader.getInstanceProcAddrFunc())) ||
- (r = createInstance(initApi, opts, instance)) ||
- (r = vk.init(initApi, instance)) ||
- (r = getDebugReportCallback(vk, instance, opts.verbose, debugCallback))) {
- return r;
- }
-
- return VK_SUCCESS;
-}
-
-int
-run(const char* const programPath,
- const PuglTestOptions opts,
- const size_t numRects)
-{
- PuglVulkanDemo app{programPath, opts, numRects};
-
- VkResult r = VK_SUCCESS;
- const auto width = static_cast<int>(app.extent.width);
- const auto height = static_cast<int>(app.extent.height);
-
- // Realize window so we can set up Vulkan
- app.world.setClassName("PuglVulkanDemo");
- app.view.setWindowTitle("Pugl Vulkan Demo");
- app.view.setAspectRatio(1, 1, 16, 9);
- app.view.setDefaultSize(width, height);
- app.view.setMinSize(width / 4, height / 4);
- app.view.setMaxSize(width * 4, height * 4);
- app.view.setBackend(pugl::vulkanBackend());
- app.view.setHint(pugl::ViewHint::resizable, opts.resizable);
- const pugl::Status st = app.view.realize();
- if (st != pugl::Status::success) {
- return logError("Failed to create window (%s)\n", pugl::strerror(st));
- }
-
- if (!app.loader) {
- return logError("Failed to load Vulkan library\n");
- }
-
- // Load Vulkan for the view
- if ((r = app.vulkan.init(app.loader, opts))) {
- return logError("Failed to set up Vulkan API (%s)\n", sk::string(r));
- }
-
- const auto& vk = app.vulkan.vk;
-
- // Set up the graphics device
- if ((r = app.gpu.init(app.loader, app.vulkan, app.view, opts))) {
- return logError("Failed to set up device (%s)\n", sk::string(r));
- }
-
- logInfo("Present mode", sk::string(app.gpu.presentMode));
- logInfo("Resize present mode", sk::string(app.gpu.resizePresentMode));
-
- // Set up the rectangle data we will render every frame
- if ((r = app.rectData.init(vk, app.gpu, app.rects.size()))) {
- return logError("Failed to allocate render data (%s)\n", sk::string(r));
- }
-
- // Load shader modules
- if ((r = app.rectShaders.init(vk, app.gpu, app.programPath))) {
- return logError("Failed to load shaders (%s)\n", sk::string(r));
- }
-
- if ((r = app.renderer.init(app.vulkan.vk,
- app.gpu,
- app.rectData,
- app.rectShaders,
- app.extent,
- false))) {
- return logError("Failed to create renderer (%s)\n", sk::string(r));
- }
-
- logInfo("Swapchain frames",
- std::to_string(app.renderer.swapchain.imageViews.size()));
- logInfo("Frames in flight",
- std::to_string(app.renderer.sync.inFlight.size()));
-
- recordCommandBuffers(app.vulkan.vk,
- app.renderer.swapchain,
- app.renderer.renderPass,
- app.renderer.rectPipeline,
- app.rectData);
-
- const int refreshRate = app.view.getHint(pugl::ViewHint::refreshRate);
- const double frameDuration = 1.0 / static_cast<double>(refreshRate);
- const double timeout = app.opts.sync ? frameDuration : 0.0;
-
- PuglFpsPrinter fpsPrinter = {app.world.time()};
- app.view.show();
- while (!app.quit) {
- app.world.update(timeout);
- puglPrintFps(app.world.cobj(), &fpsPrinter, &app.framesDrawn);
- }
-
- if ((r = app.vulkan.vk.deviceWaitIdle(app.gpu.device))) {
- return logError("Failed to wait for device idle (%s)\n", sk::string(r));
- }
-
- return 0;
-}
-
-} // namespace
-
-int
-main(int argc, char** argv)
-{
- // Parse command line options
- const char* const programPath = argv[0];
- const PuglTestOptions opts = puglParseTestOptions(&argc, &argv);
- if (opts.help) {
- puglPrintTestUsage(programPath, "");
- return 0;
- }
-
- // Parse number of rectangles argument, if given
- int64_t numRects = 1000;
- if (argc >= 1) {
- char* endptr = nullptr;
- numRects = strtol(argv[0], &endptr, 10);
- if (endptr != argv[0] + strlen(argv[0]) || numRects < 1) {
- logError("Invalid number of rectangles: %s\n", argv[0]);
- return 1;
- }
- }
-
- // Run application
- return run(programPath, opts, static_cast<size_t>(numRects));
-}