text: /* Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *  * Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *  * Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in the
   *    documentation and/or other materials provided with the distribution.
   *  * Neither the name of NVIDIA CORPORATION nor the names of its
   *    contributors may be used to endorse or promote products derived
   *    from this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
   * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
   * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
   * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
   * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
   * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  /*
   * This file contains basic cross-platform setup paths in working with Vulkan
   * and rendering window.  It is largely based off of tutorials provided here:
   * https://vulkan-tutorial.com/
  */
  
  #include <stdexcept>
  #include <iostream>
  #include <fstream>
  #include <algorithm>
  #include <functional>
  #include <set>
  #include <string.h>
  
  #include "VulkanBaseApp.h"
  #include "VulkanCudaInterop.h"
  
  #define GLFW_INCLUDE_VULKAN
  #define GLM_FORCE_DEPTH_ZERO_TO_ONE
  #include <GLFW/glfw3.h>
  
  #ifdef _WIN64
  #include <VersionHelpers.h>
  #include <dxgi1_2.h>
  #include <aclapi.h>
  #endif /* _WIN64 */
  
  #ifndef countof
  #define countof(x) (sizeof(x) / sizeof(*(x)))
  #endif
  
  static const char *validationLayers[] = {"VK_LAYER_KHRONOS_validation"};
  static const size_t MAX_FRAMES_IN_FLIGHT = 5;
  
  void VulkanBaseApp::resizeCallback(GLFWwindow *window, int width, int height) {
    VulkanBaseApp *app =
        reinterpret_cast<VulkanBaseApp *>(glfwGetWindowUserPointer(window));
    app->m_framebufferResized = true;
  }
  
  static VKAPI_ATTR VkBool32 VKAPI_CALL
  debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
                VkDebugUtilsMessageTypeFlagsEXT messageType,
                const VkDebugUtilsMessengerCallbackDataEXT *pCallbackData,
                void *pUserData) {
    std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl;
  
    return VK_FALSE;
  }
  
  VulkanBaseApp::VulkanBaseApp(const std::string &appName, bool enableValidation)
      : m_appName(appName),
        m_enableValidation(enableValidation),
        m_instance(VK_NULL_HANDLE),
        m_window(nullptr),
        m_debugMessenger(VK_NULL_HANDLE),
        m_surface(VK_NULL_HANDLE),
        m_physicalDevice(VK_NULL_HANDLE),
        m_device(VK_NULL_HANDLE),
        m_graphicsQueue(VK_NULL_HANDLE),
        m_presentQueue(VK_NULL_HANDLE),
        m_swapChain(VK_NULL_HANDLE),
        m_swapChainImages(),
        m_swapChainFormat(),
        m_swapChainExtent(),
        m_swapChainImageViews(),
        m_shaderFiles(),
        m_renderPass(),
        m_pipelineLayout(VK_NULL_HANDLE),
        m_graphicsPipeline(VK_NULL_HANDLE),
        m_swapChainFramebuffers(),
        m_commandPool(VK_NULL_HANDLE),
        m_commandBuffers(),
        m_imageAvailableSemaphores(),
        m_renderFinishedSemaphores(),
        m_inFlightFences(),
        m_uniformBuffers(),
        m_uniformMemory(),
        m_descriptorSetLayout(VK_NULL_HANDLE),
        m_descriptorPool(VK_NULL_HANDLE),
        m_descriptorSets(),
        m_depthImage(VK_NULL_HANDLE),
        m_depthImageMemory(VK_NULL_HANDLE),
        m_depthImageView(VK_NULL_HANDLE),
        m_currentFrame(0),
        m_framebufferResized(false) {}
  
  VkExternalSemaphoreHandleTypeFlagBits
  VulkanBaseApp::getDefaultSemaphoreHandleType() {
  #ifdef _WIN64
    return IsWindows8OrGreater()
               ? VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT
               : VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT;
  #else
    return VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT;
  #endif
  }
  
  VkExternalMemoryHandleTypeFlagBits VulkanBaseApp::getDefaultMemHandleType() {
  #ifdef _WIN64
    return IsWindows8Point1OrGreater()
               ? VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT
               : VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT;
  #else
    return VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
  #endif
  }
  
  VulkanBaseApp::~VulkanBaseApp() {
    cleanupSwapChain();
  
    if (m_descriptorSetLayout != VK_NULL_HANDLE) {
      vkDestroyDescriptorSetLayout(m_device, m_descriptorSetLayout, nullptr);
    }
  
    for (size_t i = 0; i < m_renderFinishedSemaphores.size(); i++) {
      vkDestroySemaphore(m_device, m_renderFinishedSemaphores[i], nullptr);
      vkDestroySemaphore(m_device, m_imageAvailableSemaphores[i], nullptr);
      vkDestroyFence(m_device, m_inFlightFences[i], nullptr);
    }
    if (m_commandPool != VK_NULL_HANDLE) {
      vkDestroyCommandPool(m_device, m_commandPool, nullptr);
    }
  
    if (m_device != VK_NULL_HANDLE) {
      vkDestroyDevice(m_device, nullptr);
    }
  
    if (m_enableValidation) {
      PFN_vkDestroyDebugUtilsMessengerEXT func =
          (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
              m_instance, "vkDestroyDebugUtilsMessengerEXT");
      if (func != nullptr) {
        func(m_instance, m_debugMessenger, nullptr);
      }
    }
  
    if (m_surface != VK_NULL_HANDLE) {
      vkDestroySurfaceKHR(m_instance, m_surface, nullptr);
    }
  
    if (m_instance != VK_NULL_HANDLE) {
      vkDestroyInstance(m_instance, nullptr);
    }
  
    if (m_window) {
      glfwDestroyWindow(m_window);
    }
  
    glfwTerminate();
  }
  
  void VulkanBaseApp::init() {
    initWindow();
    initVulkan();
  }
  
  VkCommandBuffer VulkanBaseApp::beginSingleTimeCommands() {
    VkCommandBufferAllocateInfo allocInfo = {};
    allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
    allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
    allocInfo.commandPool = m_commandPool;
    allocInfo.commandBufferCount = 1;
  
    VkCommandBuffer commandBuffer;
    vkAllocateCommandBuffers(m_device, &allocInfo, &commandBuffer);
  
    VkCommandBufferBeginInfo beginInfo = {};
    beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
    beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
  
    vkBeginCommandBuffer(commandBuffer, &beginInfo);
  
    return commandBuffer;
  }
  
  void VulkanBaseApp::endSingleTimeCommands(VkCommandBuffer commandBuffer) {
    vkEndCommandBuffer(commandBuffer);
  
    VkSubmitInfo submitInfo = {};
    submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
    submitInfo.commandBufferCount = 1;
    submitInfo.pCommandBuffers = &commandBuffer;
  
    vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
    vkQueueWaitIdle(m_graphicsQueue);
  
    vkFreeCommandBuffers(m_device, m_commandPool, 1, &commandBuffer);
  }
  
  void VulkanBaseApp::initWindow() {
    glfwInit();
  
    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
    glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
  
    m_window = glfwCreateWindow(1280, 800, m_appName.c_str(), nullptr, nullptr);
    glfwSetWindowUserPointer(m_window, this);
    glfwSetFramebufferSizeCallback(m_window, resizeCallback);
  }
  
  std::vector<const char *> VulkanBaseApp::getRequiredExtensions() const {
    return std::vector<const char *>();
  }
  
  std::vector<const char *> VulkanBaseApp::getRequiredDeviceExtensions() const {
    return std::vector<const char *>();
  }
  
  void VulkanBaseApp::initVulkan() {
    createInstance();
    createSurface();
    createDevice();
    createSwapChain();
    createImageViews();
    createRenderPass();
    createDescriptorSetLayout();
    createGraphicsPipeline();
    createCommandPool();
    createDepthResources();
    createFramebuffers();
    initVulkanApp();
    createUniformBuffers();
    createDescriptorPool();
    createDescriptorSets();
    createCommandBuffers();
    createSyncObjects();
  }
  
  #ifdef _WIN64
  class WindowsSecurityAttributes {
   protected:
    SECURITY_ATTRIBUTES m_winSecurityAttributes;
    PSECURITY_DESCRIPTOR m_winPSecurityDescriptor;
  
   public:
    WindowsSecurityAttributes();
    SECURITY_ATTRIBUTES *operator&();
    ~WindowsSecurityAttributes();
  };
  
  WindowsSecurityAttributes::WindowsSecurityAttributes() {
    m_winPSecurityDescriptor = (PSECURITY_DESCRIPTOR)calloc(
        1, SECURITY_DESCRIPTOR_MIN_LENGTH + 2 * sizeof(void **));
    if (!m_winPSecurityDescriptor) {
      throw std::runtime_error(
          "Failed to allocate memory for security descriptor");
    }
  
    PSID *ppSID = (PSID *)((PBYTE)m_winPSecurityDescriptor +
                           SECURITY_DESCRIPTOR_MIN_LENGTH);
    PACL *ppACL = (PACL *)((PBYTE)ppSID + sizeof(PSID *));
  
    InitializeSecurityDescriptor(m_winPSecurityDescriptor,
                                 SECURITY_DESCRIPTOR_REVISION);
  
    SID_IDENTIFIER_AUTHORITY sidIdentifierAuthority =
        SECURITY_WORLD_SID_AUTHORITY;
    AllocateAndInitializeSid(&sidIdentifierAuthority, 1, SECURITY_WORLD_RID, 0, 0,
                             0, 0, 0, 0, 0, ppSID);
  
    EXPLICIT_ACCESS explicitAccess;
    ZeroMemory(&explicitAccess, sizeof(EXPLICIT_ACCESS));
    explicitAccess.grfAccessPermissions =
        STANDARD_RIGHTS_ALL | SPECIFIC_RIGHTS_ALL;
    explicitAccess.grfAccessMode = SET_ACCESS;
    explicitAccess.grfInheritance = INHERIT_ONLY;
    explicitAccess.Trustee.TrusteeForm = TRUSTEE_IS_SID;
    explicitAccess.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
    explicitAccess.Trustee.ptstrName = (LPTSTR)*ppSID;
  
    SetEntriesInAcl(1, &explicitAccess, NULL, ppACL);
  
    SetSecurityDescriptorDacl(m_winPSecurityDescriptor, TRUE, *ppACL, FALSE);
  
    m_winSecurityAttributes.nLength = sizeof(m_winSecurityAttributes);
    m_winSecurityAttributes.lpSecurityDescriptor = m_winPSecurityDescriptor;
    m_winSecurityAttributes.bInheritHandle = TRUE;
  }
  
  SECURITY_ATTRIBUTES *WindowsSecurityAttributes::operator&() {
    return &m_winSecurityAttributes;
  }
  
  WindowsSecurityAttributes::~WindowsSecurityAttributes() {
    PSID *ppSID = (PSID *)((PBYTE)m_winPSecurityDescriptor +
                           SECURITY_DESCRIPTOR_MIN_LENGTH);
    PACL *ppACL = (PACL *)((PBYTE)ppSID + sizeof(PSID *));
  
    if (*ppSID) {
      FreeSid(*ppSID);
    }
    if (*ppACL) {
      LocalFree(*ppACL);
    }
    free(m_winPSecurityDescriptor);
  }
  #endif /* _WIN64 */
  
  static VkFormat findSupportedFormat(VkPhysicalDevice physicalDevice,
                                      const std::vector<VkFormat> &candidates,
                                      VkImageTiling tiling,
                                      VkFormatFeatureFlags features) {
    for (VkFormat format : candidates) {
      VkFormatProperties props;
      vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props);
      if (tiling == VK_IMAGE_TILING_LINEAR &&
          (props.linearTilingFeatures & features) == features) {
        return format;
      } else if (tiling == VK_IMAGE_TILING_OPTIMAL &&
                 (props.optimalTilingFeatures & features) == features) {
        return format;
      }
    }
    throw std::runtime_error("Failed to find supported format!");
  }
  
  static uint32_t findMemoryType(VkPhysicalDevice physicalDevice,
                                 uint32_t typeFilter,
                                 VkMemoryPropertyFlags properties) {
    VkPhysicalDeviceMemoryProperties memProperties;
    vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
    for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
      if (typeFilter & (1 << i) &&
          (memProperties.memoryTypes[i].propertyFlags & properties) ==
              properties) {
        return i;
      }
    }
    return ~0;
  }
  
  static bool supportsValidationLayers() {
    std::vector<VkLayerProperties> availableLayers;
    uint32_t layerCount;
  
    vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
    availableLayers.resize(layerCount);
    vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());
  
    for (const char *layerName : validationLayers) {
      bool layerFound = false;
  
      for (const auto &layerProperties : availableLayers) {
        if (strcmp(layerName, layerProperties.layerName) == 0) {
          layerFound = true;
          break;
        }
      }
  
      if (!layerFound) {
        return false;
      }
    }
  
    return true;
  }
  
  void VulkanBaseApp::createInstance() {
    if (m_enableValidation && !supportsValidationLayers()) {
      throw std::runtime_error("Validation requested, but not supported!");
    }
  
    VkApplicationInfo appInfo = {};
    appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
    appInfo.pApplicationName = m_appName.c_str();
    appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
    appInfo.pEngineName = "No Engine";
    appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
    appInfo.apiVersion = VK_API_VERSION_1_0;
  
    VkInstanceCreateInfo createInfo = {};
    createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
    createInfo.pApplicationInfo = &appInfo;
  
    std::vector<const char *> exts = getRequiredExtensions();
  
    {
      uint32_t glfwExtensionCount = 0;
      const char **glfwExtensions;
  
      glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
  
      exts.insert(exts.begin(), glfwExtensions,
                  glfwExtensions + glfwExtensionCount);
  
      if (m_enableValidation) {
        exts.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
      }
    }
  
    createInfo.enabledExtensionCount = static_cast<uint32_t>(exts.size());
    createInfo.ppEnabledExtensionNames = exts.data();
    VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo = {};
    if (m_enableValidation) {
      createInfo.enabledLayerCount =
          static_cast<uint32_t>(countof(validationLayers));
      createInfo.ppEnabledLayerNames = validationLayers;
  
      debugCreateInfo.sType =
          VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
      debugCreateInfo.messageSeverity =
          VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
          VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
          VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
      debugCreateInfo.messageType =
          VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
          VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
          VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
      debugCreateInfo.pfnUserCallback = debugCallback;
  
      createInfo.pNext = &debugCreateInfo;
    } else {
      createInfo.enabledLayerCount = 0;
      createInfo.pNext = nullptr;
    }
  
    if (vkCreateInstance(&createInfo, nullptr, &m_instance) != VK_SUCCESS) {
      throw std::runtime_error("Failed to create Vulkan instance!");
    }
  
    if (m_enableValidation) {
      PFN_vkCreateDebugUtilsMessengerEXT func =
          (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
              m_instance, "vkCreateDebugUtilsMessengerEXT");
      if (func == nullptr ||
          func(m_instance, &debugCreateInfo, nullptr, &m_debugMessenger) !=
              VK_SUCCESS) {
        throw std::runtime_error("Failed to set up debug messenger!");
      }
    }
  }
  
  void VulkanBaseApp::createSurface() {
    if (glfwCreateWindowSurface(m_instance, m_window, nullptr, &m_surface) !=
        VK_SUCCESS) {
      throw std::runtime_error("failed to create window surface!");
    }
  }
  
  static bool findGraphicsQueueIndicies(VkPhysicalDevice device,
                                        VkSurfaceKHR surface,
                                        uint32_t &graphicsFamily,
                                        uint32_t &presentFamily) {
    uint32_t queueFamilyCount = 0;
  
    vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
  
    std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
    vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount,
                                             queueFamilies.data());
  
    graphicsFamily = presentFamily = ~0;
  
    for (uint32_t i = 0; i < queueFamilyCount; i++) {
      if (queueFamilies[i].queueCount > 0) {
        if (graphicsFamily == ~0 &&
            queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
          graphicsFamily = i;
        }
        uint32_t presentSupport = 0;
        vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);
        if (presentFamily == ~0 && presentSupport) {
          presentFamily = i;
        }
        if (presentFamily != ~0 && graphicsFamily != ~0) {
          break;
        }
      }
    }
  
    return graphicsFamily != ~0 && presentFamily != ~0;
  }
  
  static bool hasAllExtensions(
      VkPhysicalDevice device,
      const std::vector<const char *> &deviceExtensions) {
    uint32_t extensionCount;
    vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount,
                                         nullptr);
    std::vector<VkExtensionProperties> availableExtensions(extensionCount);
    vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount,
                                         availableExtensions.data());
  
    std::set<std::string> requiredExtensions(deviceExtensions.begin(),
                                             deviceExtensions.end());
  
    for (const auto &extension : availableExtensions) {
      requiredExtensions.erase(extension.extensionName);
    }
  
    return requiredExtensions.empty();
  }
  
  static void getSwapChainProperties(
      VkPhysicalDevice device, VkSurfaceKHR surface,
      VkSurfaceCapabilitiesKHR &capabilities,
      std::vector<VkSurfaceFormatKHR> &formats,
      std::vector<VkPresentModeKHR> &presentModes) {
    vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &capabilities);
    uint32_t formatCount;
    vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr);
    if (formatCount != 0) {
      formats.resize(formatCount);
      vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount,
                                           formats.data());
    }
    uint32_t presentModeCount;
    vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount,
                                              nullptr);
    if (presentModeCount != 0) {
      presentModes.resize(presentModeCount);
      vkGetPhysicalDeviceSurfacePresentModesKHR(
          device, surface, &presentModeCount, presentModes.data());
    }
  }
  
  bool VulkanBaseApp::isSuitableDevice(VkPhysicalDevice dev) const {
    bool isSuitable = false;
    uint32_t graphicsQueueIndex, presentQueueIndex;
    std::vector<const char *> deviceExtensions = getRequiredDeviceExtensions();
    VkSurfaceCapabilitiesKHR caps;
    std::vector<VkSurfaceFormatKHR> formats;
    std::vector<VkPresentModeKHR> presentModes;
    deviceExtensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
    getSwapChainProperties(dev, m_surface, caps, formats, presentModes);
  
    VkPhysicalDeviceIDPropertiesKHR vkPhysicalDeviceIDPropertiesKHR = {};
    vkPhysicalDeviceIDPropertiesKHR.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES_KHR;
    vkPhysicalDeviceIDPropertiesKHR.pNext = NULL;
  
    VkPhysicalDeviceProperties2KHR vkPhysicalDeviceProperties2KHR = {};
    vkPhysicalDeviceProperties2KHR.sType =
        VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR;
    vkPhysicalDeviceProperties2KHR.pNext = &vkPhysicalDeviceIDPropertiesKHR;
  
    vkGetPhysicalDeviceProperties2(dev, &vkPhysicalDeviceProperties2KHR);
  
    isSuitable = hasAllExtensions(dev, deviceExtensions) &&
                 isDeviceCompatible(vkPhysicalDeviceIDPropertiesKHR.deviceUUID,
                                    (size_t)VK_UUID_SIZE) &&
                 !formats.empty() && !presentModes.empty() &&
                 findGraphicsQueueIndicies(dev, m_surface, graphicsQueueIndex,
                                           presentQueueIndex);
  
    if (isSuitable) {
      memcpy((void *)m_deviceUUID, vkPhysicalDeviceIDPropertiesKHR.deviceUUID,
             sizeof(m_deviceUUID));
    }
  
    return isSuitable;
  }
  
  bool VulkanBaseApp::isVkPhysicalDeviceUuid(void *Uuid) {
    return !memcmp((void *)m_deviceUUID, Uuid, (size_t)VK_UUID_SIZE);
  }
  
  void VulkanBaseApp::createDevice() {
    {
      uint32_t deviceCount = 0;
      vkEnumeratePhysicalDevices(m_instance, &deviceCount, nullptr);
      if (deviceCount == 0) {
        throw std::runtime_error("Failed to find Vulkan capable GPUs!");
      }
      std::vector<VkPhysicalDevice> phyDevs(deviceCount);
      vkEnumeratePhysicalDevices(m_instance, &deviceCount, phyDevs.data());
      std::vector<VkPhysicalDevice>::iterator it =
          std::find_if(phyDevs.begin(), phyDevs.end(),
                       std::bind(&VulkanBaseApp::isSuitableDevice, this,
                                 std::placeholders::_1));
      if (it == phyDevs.end()) {
        printf("\nNo suitable device found! Waiving Execution\n");
        exit(EXIT_WAIVED);
      }
      m_physicalDevice = *it;
    }
  
    uint32_t graphicsQueueIndex, presentQueueIndex;
    findGraphicsQueueIndicies(m_physicalDevice, m_surface, graphicsQueueIndex,
                              presentQueueIndex);
  
    std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
    std::set<uint32_t> uniqueFamilyIndices = {graphicsQueueIndex,
                                              presentQueueIndex};
  
    float queuePriority = 1.0f;
  
    for (uint32_t queueFamily : uniqueFamilyIndices) {
      VkDeviceQueueCreateInfo queueCreateInfo = {};
      queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
      queueCreateInfo.queueFamilyIndex = graphicsQueueIndex;
      queueCreateInfo.queueCount = 1;
      queueCreateInfo.pQueuePriorities = &queuePriority;
      queueCreateInfos.push_back(queueCreateInfo);
    }
  
    VkPhysicalDeviceFeatures deviceFeatures = {};
    deviceFeatures.fillModeNonSolid = true;
  
    VkDeviceCreateInfo createInfo = {};
    createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
  
    createInfo.pQueueCreateInfos = queueCreateInfos.data();
    createInfo.queueCreateInfoCount =
        static_cast<uint32_t>(queueCreateInfos.size());
  
    createInfo.pEnabledFeatures = &deviceFeatures;
  
    std::vector<const char *> deviceExtensions = getRequiredDeviceExtensions();
    deviceExtensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
  
    createInfo.enabledExtensionCount =
        static_cast<uint32_t>(deviceExtensions.size());
    createInfo.ppEnabledExtensionNames = deviceExtensions.data();
  
    if (m_enableValidation) {
      createInfo.enabledLayerCount =
          static_cast<uint32_t>(countof(validationLayers));
      createInfo.ppEnabledLayerNames = validationLayers;
    } else {
      createInfo.enabledLayerCount = 0;
    }
  
    if (vkCreateDevice(m_physicalDevice, &createInfo, nullptr, &m_device) !=
        VK_SUCCESS) {
      throw std::runtime_error("failed to create logical device!");
    }
  
    vkGetDeviceQueue(m_device, graphicsQueueIndex, 0, &m_graphicsQueue);
    vkGetDeviceQueue(m_device, presentQueueIndex, 0, &m_presentQueue);
  }
  
  static VkSurfaceFormatKHR chooseSwapSurfaceFormat(
      const std::vector<VkSurfaceFormatKHR> &availableFormats) {
    if (availableFormats.size() == 1 &&
        availableFormats[0].format == VK_FORMAT_UNDEFINED) {
      return {VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR};
    }
  
    for (const auto &availableFormat : availableFormats) {
      if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM &&
          availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
        return availableFormat;
      }
    }
  
    return availableFormats[0];
  }
  
  static VkPresentModeKHR chooseSwapPresentMode(
      const std::vector<VkPresentModeKHR> &availablePresentModes) {
    VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR;
  
    for (const auto &availablePresentMode : availablePresentModes) {
      if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
        return availablePresentMode;
      } else if (availablePresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) {
        bestMode = availablePresentMode;
      }
    }
  
    return bestMode;
  }
  
  static VkExtent2D chooseSwapExtent(
      GLFWwindow *window, const VkSurfaceCapabilitiesKHR &capabilities) {
    if (capabilities.currentExtent.width !=
        std::numeric_limits<uint32_t>::max()) {
      return capabilities.currentExtent;
    } else {
      int width, height;
      glfwGetFramebufferSize(window, &width, &height);
      VkExtent2D actualExtent = {static_cast<uint32_t>(width),
                                 static_cast<uint32_t>(height)};
  
      actualExtent.width = std::max(
          capabilities.minImageExtent.width,
          std::min(capabilities.maxImageExtent.width, actualExtent.width));
      actualExtent.height = std::max(
          capabilities.minImageExtent.height,
          std::min(capabilities.maxImageExtent.height, actualExtent.height));
  
      return actualExtent;
    }
  }
  
  void VulkanBaseApp::createSwapChain() {
    VkSurfaceCapabilitiesKHR capabilities;
    VkSurfaceFormatKHR format;
    VkPresentModeKHR presentMode;
    VkExtent2D extent;
    uint32_t imageCount;
  
    {
      std::vector<VkSurfaceFormatKHR> formats;
      std::vector<VkPresentModeKHR> presentModes;
  
      getSwapChainProperties(m_physicalDevice, m_surface, capabilities, formats,
                             presentModes);
      format = chooseSwapSurfaceFormat(formats);
      presentMode = chooseSwapPresentMode(presentModes);
      extent = chooseSwapExtent(m_window, capabilities);
      imageCount = capabilities.minImageCount + 1;
      if (capabilities.maxImageCount > 0 &&
          imageCount > capabilities.maxImageCount) {
        imageCount = capabilities.maxImageCount;
      }
    }
  
    VkSwapchainCreateInfoKHR createInfo = {};
    createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
    createInfo.surface = m_surface;
  
    createInfo.minImageCount = imageCount;
    createInfo.imageFormat = format.format;
    createInfo.imageColorSpace = format.colorSpace;
    createInfo.imageExtent = extent;
    createInfo.imageArrayLayers = 1;
    createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
  
    uint32_t queueFamilyIndices[2];
    findGraphicsQueueIndicies(m_physicalDevice, m_surface, queueFamilyIndices[0],
                              queueFamilyIndices[1]);
  
    if (queueFamilyIndices[0] != queueFamilyIndices[1]) {
      createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
      createInfo.queueFamilyIndexCount = countof(queueFamilyIndices);
      createInfo.pQueueFamilyIndices = queueFamilyIndices;
    } else {
      createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
    }
  
    createInfo.preTransform = capabilities.currentTransform;
    createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
    createInfo.presentMode = presentMode;
    createInfo.clipped = VK_TRUE;
  
    createInfo.oldSwapchain = VK_NULL_HANDLE;
  
    if (vkCreateSwapchainKHR(m_device, &createInfo, nullptr, &m_swapChain) !=
        VK_SUCCESS) {
      throw std::runtime_error("failed to create swap chain!");
    }
  
    vkGetSwapchainImagesKHR(m_device, m_swapChain, &imageCount, nullptr);
    m_swapChainImages.resize(imageCount);
    vkGetSwapchainImagesKHR(m_device, m_swapChain, &imageCount,
                            m_swapChainImages.data());
  
    m_swapChainFormat = format.format;
    m_swapChainExtent = extent;
  }
  
  static VkImageView createImageView(VkDevice dev, VkImage image, VkFormat format,
                                     VkImageAspectFlags aspectFlags) {
    VkImageView imageView;
    VkImageViewCreateInfo createInfo = {};
    createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
    createInfo.image = image;
    createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
    createInfo.format = format;
    createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
    createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
    createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
    createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
    createInfo.subresourceRange.aspectMask = aspectFlags;
    createInfo.subresourceRange.baseMipLevel = 0;
    createInfo.subresourceRange.levelCount = 1;
    createInfo.subresourceRange.baseArrayLayer = 0;
    createInfo.subresourceRange.layerCount = 1;
    if (vkCreateImageView(dev, &createInfo, nullptr, &imageView) != VK_SUCCESS) {
      throw std::runtime_error("Failed to create image views!");
    }
  
    return imageView;
  }
  
  static void createImage(VkPhysicalDevice physicalDevice, VkDevice device,
                          uint32_t width, uint32_t height, VkFormat format,
                          VkImageTiling tiling, VkImageUsageFlags usage,
                          VkMemoryPropertyFlags properties, VkImage &image,
                          VkDeviceMemory &imageMemory) {
    VkImageCreateInfo imageInfo = {};
    imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
    imageInfo.imageType = VK_IMAGE_TYPE_2D;
    imageInfo.extent.width = width;
    imageInfo.extent.height = height;
    imageInfo.extent.depth = 1;
    imageInfo.mipLevels = 1;
    imageInfo.arrayLayers = 1;
    imageInfo.format = format;
    imageInfo.tiling = tiling;
    imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    imageInfo.usage = usage;
    imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
    imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
  
    if (vkCreateImage(device, &imageInfo, nullptr, &image) != VK_SUCCESS) {
      throw std::runtime_error("failed to create image!");
    }
  
    VkMemoryRequirements memRequirements;
    vkGetImageMemoryRequirements(device, image, &memRequirements);
  
    VkMemoryAllocateInfo allocInfo = {};
    allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
    allocInfo.allocationSize = memRequirements.size;
    allocInfo.memoryTypeIndex = findMemoryType(
        physicalDevice, memRequirements.memoryTypeBits, properties);
  
    if (vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory) !=
        VK_SUCCESS) {
      throw std::runtime_error("failed to allocate image memory!");
    }
  
    vkBindImageMemory(device, image, imageMemory, 0);
  }
  
  void VulkanBaseApp::createImageViews() {
    m_swapChainImageViews.resize(m_swapChainImages.size());
  
    for (uint32_t i = 0; i < m_swapChainImages.size(); i++) {
      m_swapChainImageViews[i] =
          createImageView(m_device, m_swapChainImages[i], m_swapChainFormat,
                          VK_IMAGE_ASPECT_COLOR_BIT);
    }
  }
  
  void VulkanBaseApp::createRenderPass() {
    VkAttachmentDescription colorAttachment = {};
    colorAttachment.format = m_swapChainFormat;
    colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
    // Set up the render pass to preserve the contents of the attachment while
    // rendering. By doing this the points already rendered are not cleared and
    // thus displays growing number of points with time eventhough the number of
    // points rendered per frame is constant
    colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
    colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
    colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
    colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
    colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
  
    VkAttachmentReference colorAttachmentRef = {};
    colorAttachmentRef.attachment = 0;
    colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
  
    VkAttachmentDescription depthAttachment = {};
    depthAttachment.format = findSupportedFormat(
        m_physicalDevice, {VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT,
                           VK_FORMAT_D24_UNORM_S8_UINT},
        VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT);
    depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
    depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
    depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
    depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
    depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
    depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    depthAttachment.finalLayout =
        VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
  
    VkAttachmentReference depthAttachmentRef = {};
    depthAttachmentRef.attachment = 1;
    depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
  
    VkSubpassDescription subpass = {};
    subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
    subpass.colorAttachmentCount = 1;
    subpass.pColorAttachments = &colorAttachmentRef;
    subpass.pDepthStencilAttachment = &depthAttachmentRef;
  
    VkSubpassDependency dependency = {};
    dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
    dependency.dstSubpass = 0;
    dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    dependency.srcAccessMask = 0;
    dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
                               VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
  
    VkAttachmentDescription attachments[] = {colorAttachment, depthAttachment};
    VkRenderPassCreateInfo renderPassInfo = {};
    renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
    renderPassInfo.attachmentCount = countof(attachments);
    renderPassInfo.pAttachments = attachments;
    renderPassInfo.subpassCount = 1;
    renderPassInfo.pSubpasses = &subpass;
    renderPassInfo.dependencyCount = 1;
    renderPassInfo.pDependencies = &dependency;
  
    if (vkCreateRenderPass(m_device, &renderPassInfo, nullptr, &m_renderPass) !=
        VK_SUCCESS) {
      throw std::runtime_error("failed to create render pass!");
    }
  }
  
  void VulkanBaseApp::createDescriptorSetLayout() {
    VkDescriptorSetLayoutBinding uboLayoutBinding = {};
    uboLayoutBinding.binding = 0;
    uboLayoutBinding.descriptorCount = 1;
    uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
    uboLayoutBinding.pImmutableSamplers = nullptr;
    uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
  
    VkDescriptorSetLayoutCreateInfo layoutInfo = {};
    layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
    layoutInfo.bindingCount = 1;
    layoutInfo.pBindings = &uboLayoutBinding;
  
    if (vkCreateDescriptorSetLayout(m_device, &layoutInfo, nullptr,
                                    &m_descriptorSetLayout) != VK_SUCCESS) {
      throw std::runtime_error("failed to create descriptor set layout!");
    }
  }
  
  VkShaderModule createShaderModule(VkDevice device, const char *filename) {
    std::vector<char> shaderContents;
    std::ifstream shaderFile(filename, std::ios_base::in | std::ios_base::binary);
    VkShaderModuleCreateInfo createInfo = {};
    VkShaderModule shaderModule;
  
    if (!shaderFile.good()) {
      throw std::runtime_error("Failed to load shader contents");
    }
    readFile(shaderFile, shaderContents);
  
    createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
    createInfo.codeSize = shaderContents.size();
    createInfo.pCode = reinterpret_cast<const uint32_t *>(shaderContents.data());
  
    if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) !=
        VK_SUCCESS) {
      throw std::runtime_error("Failed to create shader module!");
    }
  
    return shaderModule;
  }
  
  void VulkanBaseApp::getVertexDescriptions(
      std::vector<VkVertexInputBindingDescription> &bindingDesc,
      std::vector<VkVertexInputAttributeDescription> &attribDesc) {}
  
  void VulkanBaseApp::getAssemblyStateInfo(
      VkPipelineInputAssemblyStateCreateInfo &info) {}
  
  void VulkanBaseApp::createGraphicsPipeline() {
    std::vector<VkPipelineShaderStageCreateInfo> shaderStageInfos(
        m_shaderFiles.size());
    for (size_t i = 0; i < m_shaderFiles.size(); i++) {
      shaderStageInfos[i] = {};
      shaderStageInfos[i].sType =
          VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
      shaderStageInfos[i].stage = m_shaderFiles[i].first;
      shaderStageInfos[i].module =
          createShaderModule(m_device, m_shaderFiles[i].second.c_str());
      shaderStageInfos[i].pName = "main";
    }
  
    VkPipelineVertexInputStateCreateInfo vertexInputInfo = {};
  
    std::vector<VkVertexInputBindingDescription> vertexBindingDescriptions;
    std::vector<VkVertexInputAttributeDescription> vertexAttributeDescriptions;
  
    getVertexDescriptions(vertexBindingDescriptions, vertexAttributeDescriptions);
  
    vertexInputInfo.sType =
        VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
    vertexInputInfo.vertexBindingDescriptionCount =
        static_cast<uint32_t>(vertexBindingDescriptions.size());
    vertexInputInfo.pVertexBindingDescriptions = vertexBindingDescriptions.data();
    vertexInputInfo.vertexAttributeDescriptionCount =
        static_cast<uint32_t>(vertexAttributeDescriptions.size());
    vertexInputInfo.pVertexAttributeDescriptions =
        vertexAttributeDescriptions.data();
  
    VkPipelineInputAssemblyStateCreateInfo inputAssembly = {};
    getAssemblyStateInfo(inputAssembly);
  
    VkViewport viewport = {};
    viewport.x = 0.0f;
    viewport.y = 0.0f;
    viewport.width = (float)m_swapChainExtent.width;
    viewport.height = (float)m_swapChainExtent.height;
    viewport.minDepth = 0.0f;
    viewport.maxDepth = 1.0f;
  
    VkRect2D scissor = {};
    scissor.offset = {0, 0};
    scissor.extent = m_swapChainExtent;
  
    VkPipelineViewportStateCreateInfo viewportState = {};
    viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
    viewportState.viewportCount = 1;
    viewportState.pViewports = &viewport;
    viewportState.scissorCount = 1;
    viewportState.pScissors = &scissor;
  
    VkPipelineRasterizationStateCreateInfo rasterizer = {};
    rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
    rasterizer.depthClampEnable = VK_FALSE;
    rasterizer.rasterizerDiscardEnable = VK_FALSE;
    rasterizer.polygonMode = VK_POLYGON_MODE_POINT;
    rasterizer.lineWidth = 1.0f;
    rasterizer.cullMode = VK_CULL_MODE_NONE;
    rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
    rasterizer.depthBiasEnable = VK_FALSE;
  
    VkPipelineMultisampleStateCreateInfo multisampling = {};
    multisampling.sType =
        VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
    multisampling.sampleShadingEnable = VK_FALSE;
    multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
    multisampling.minSampleShading = 1.0f;           // Optional
    multisampling.pSampleMask = nullptr;             // Optional
    multisampling.alphaToCoverageEnable = VK_FALSE;  // Optional
    multisampling.alphaToOneEnable = VK_FALSE;       // Optional
  
    VkPipelineDepthStencilStateCreateInfo depthStencil = {};
    depthStencil.sType =
        VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
    depthStencil.depthTestEnable = VK_TRUE;
    depthStencil.depthWriteEnable = VK_TRUE;
    depthStencil.depthCompareOp = VK_COMPARE_OP_LESS;
    depthStencil.depthBoundsTestEnable = VK_FALSE;
    depthStencil.stencilTestEnable = VK_FALSE;
  
    VkPipelineColorBlendAttachmentState colorBlendAttachment = {};
    colorBlendAttachment.colorWriteMask =
        VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
        VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
    colorBlendAttachment.blendEnable = VK_FALSE;
  
    VkPipelineColorBlendStateCreateInfo colorBlending = {};
    colorBlending.sType =
        VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
    colorBlending.logicOpEnable = VK_FALSE;
    colorBlending.logicOp = VK_LOGIC_OP_COPY;
    colorBlending.attachmentCount = 1;
    colorBlending.pAttachments = &colorBlendAttachment;
    colorBlending.blendConstants[0] = 0.0f;
    colorBlending.blendConstants[1] = 0.0f;
    colorBlending.blendConstants[2] = 0.0f;
    colorBlending.blendConstants[3] = 0.0f;
  
    VkPipelineLayoutCreateInfo pipelineLayoutInfo = {};
    pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
    pipelineLayoutInfo.setLayoutCount = 1;                    // Optional
    pipelineLayoutInfo.pSetLayouts = &m_descriptorSetLayout;  // Optional
    pipelineLayoutInfo.pushConstantRangeCount = 0;            // Optional
    pipelineLayoutInfo.pPushConstantRanges = nullptr;         // Optional
  
    if (vkCreatePipelineLayout(m_device, &pipelineLayoutInfo, nullptr,
                               &m_pipelineLayout) != VK_SUCCESS) {
      throw std::runtime_error("failed to create pipeline layout!");
    }
  
    VkGraphicsPipelineCreateInfo pipelineInfo = {};
    pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
    pipelineInfo.stageCount = static_cast<uint32_t>(shaderStageInfos.size());
    pipelineInfo.pStages = shaderStageInfos.data();
  
    pipelineInfo.pVertexInputState = &vertexInputInfo;
    pipelineInfo.pInputAssemblyState = &inputAssembly;
    pipelineInfo.pViewportState = &viewportState;
    pipelineInfo.pRasterizationState = &rasterizer;
    pipelineInfo.pMultisampleState = &multisampling;
    pipelineInfo.pDepthStencilState = &depthStencil;  // Optional
    pipelineInfo.pColorBlendState = &colorBlending;
    pipelineInfo.pDynamicState = nullptr;  // Optional
  
    pipelineInfo.layout = m_pipelineLayout;
  
    pipelineInfo.renderPass = m_renderPass;
    pipelineInfo.subpass = 0;
  
    pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;  // Optional
    pipelineInfo.basePipelineIndex = -1;               // Optional
  
    if (vkCreateGraphicsPipelines(m_device, VK_NULL_HANDLE, 1, &pipelineInfo,
                                  nullptr, &m_graphicsPipeline) != VK_SUCCESS) {
      throw std::runtime_error("failed to create graphics pipeline!");
    }
  
    for (size_t i = 0; i < shaderStageInfos.size(); i++) {
      vkDestroyShaderModule(m_device, shaderStageInfos[i].module, nullptr);
    }
  }
  
  void VulkanBaseApp::createFramebuffers() {
    m_swapChainFramebuffers.resize(m_swapChainImageViews.size());
    for (size_t i = 0; i < m_swapChainImageViews.size(); i++) {
      VkImageView attachments[] = {m_swapChainImageViews[i], m_depthImageView};
  
      VkFramebufferCreateInfo framebufferInfo = {};
      framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
      framebufferInfo.renderPass = m_renderPass;
      framebufferInfo.attachmentCount = countof(attachments);
      framebufferInfo.pAttachments = attachments;
      framebufferInfo.width = m_swapChainExtent.width;
      framebufferInfo.height = m_swapChainExtent.height;
      framebufferInfo.layers = 1;
  
      if (vkCreateFramebuffer(m_device, &framebufferInfo, nullptr,
                              &m_swapChainFramebuffers[i]) != VK_SUCCESS) {
        throw std::runtime_error("failed to create framebuffer!");
      }
    }
  }
  
  void VulkanBaseApp::createCommandPool() {
    VkCommandPoolCreateInfo poolInfo = {};
    uint32_t graphicsIndex, presentIndex;
  
    findGraphicsQueueIndicies(m_physicalDevice, m_surface, graphicsIndex,
                              presentIndex);
  
    poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
    poolInfo.queueFamilyIndex = graphicsIndex;
    poolInfo.flags = 0;  // Optional
  
    if (vkCreateCommandPool(m_device, &poolInfo, nullptr, &m_commandPool) !=
        VK_SUCCESS) {
      throw std::runtime_error("Failed to create command pool!");
    }
  }
  
  static void transitionImageLayout(VulkanBaseApp *app, VkImage image,
                                    VkFormat format, VkImageLayout oldLayout,
                                    VkImageLayout newLayout) {
    VkCommandBuffer commandBuffer = app->beginSingleTimeCommands();
  
    VkImageMemoryBarrier barrier = {};
    barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
    barrier.oldLayout = oldLayout;
    barrier.newLayout = newLayout;
    barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrier.image = image;
  
    if (newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) {
      barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
  
      if (format == VK_FORMAT_D32_SFLOAT_S8_UINT ||
          format == VK_FORMAT_D24_UNORM_S8_UINT) {
        barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
      }
    } else {
      barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    }
  
    barrier.subresourceRange.baseMipLevel = 0;
    barrier.subresourceRange.levelCount = 1;
    barrier.subresourceRange.baseArrayLayer = 0;
    barrier.subresourceRange.layerCount = 1;
  
    VkPipelineStageFlags sourceStage;
    VkPipelineStageFlags destinationStage;
  
    if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED &&
        newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
      barrier.srcAccessMask = 0;
      barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
  
      sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
      destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
    } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL &&
               newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
      barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
      barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
  
      sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
      destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
    } else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED &&
               newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) {
      barrier.srcAccessMask = 0;
      barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
                              VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
  
      sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
      destinationStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
    } else {
      throw std::invalid_argument("unsupported layout transition!");
    }
  
    vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0, 0,
                         nullptr, 0, nullptr, 1, &barrier);
  
    app->endSingleTimeCommands(commandBuffer);
  }
  
  void VulkanBaseApp::createDepthResources() {
    VkFormat depthFormat = findSupportedFormat(
        m_physicalDevice, {VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT,
                           VK_FORMAT_D24_UNORM_S8_UINT},
        VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT);
    createImage(m_physicalDevice, m_device, m_swapChainExtent.width,
                m_swapChainExtent.height, depthFormat, VK_IMAGE_TILING_OPTIMAL,
                VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
                VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_depthImage,
                m_depthImageMemory);
    m_depthImageView = createImageView(m_device, m_depthImage, depthFormat,
                                       VK_IMAGE_ASPECT_DEPTH_BIT);
    transitionImageLayout(this, m_depthImage, depthFormat,
                          VK_IMAGE_LAYOUT_UNDEFINED,
                          VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
  }
  
  void VulkanBaseApp::createUniformBuffers() {
    VkDeviceSize size = getUniformSize();
    if (size > 0) {
      m_uniformBuffers.resize(m_swapChainImages.size());
      m_uniformMemory.resize(m_swapChainImages.size());
      for (size_t i = 0; i < m_uniformBuffers.size(); i++) {
        createBuffer(getUniformSize(), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
                     VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
                         VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
                     m_uniformBuffers[i], m_uniformMemory[i]);
      }
    }
  }
  
  void VulkanBaseApp::createDescriptorPool() {
    VkDescriptorPoolSize poolSize = {};
    poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
    poolSize.descriptorCount = static_cast<uint32_t>(m_swapChainImages.size());
    VkDescriptorPoolCreateInfo poolInfo = {};
    poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
    poolInfo.poolSizeCount = 1;
    poolInfo.pPoolSizes = &poolSize;
    poolInfo.maxSets = static_cast<uint32_t>(m_swapChainImages.size());
    ;
    if (vkCreateDescriptorPool(m_device, &poolInfo, nullptr, &m_descriptorPool) !=
        VK_SUCCESS) {
      throw std::runtime_error("failed to create descriptor pool!");
    }
  }
  
  void VulkanBaseApp::createDescriptorSets() {
    std::vector<VkDescriptorSetLayout> layouts(m_swapChainImages.size(),
                                               m_descriptorSetLayout);
    VkDescriptorSetAllocateInfo allocInfo = {};
    allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
    allocInfo.descriptorPool = m_descriptorPool;
    allocInfo.descriptorSetCount =
        static_cast<uint32_t>(m_swapChainImages.size());
    allocInfo.pSetLayouts = layouts.data();
    m_descriptorSets.resize(m_swapChainImages.size());
  
    if (vkAllocateDescriptorSets(m_device, &allocInfo, m_descriptorSets.data()) !=
        VK_SUCCESS) {
      throw std::runtime_error("failed to allocate descriptor sets!");
    }
  
    VkDescriptorBufferInfo bufferInfo = {};
    bufferInfo.offset = 0;
    bufferInfo.range = VK_WHOLE_SIZE;
    VkWriteDescriptorSet descriptorWrite = {};
    descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
    descriptorWrite.dstBinding = 0;
    descriptorWrite.dstArrayElement = 0;
    descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
    descriptorWrite.descriptorCount = 1;
    descriptorWrite.pBufferInfo = &bufferInfo;
    descriptorWrite.pImageInfo = nullptr;        // Optional
    descriptorWrite.pTexelBufferView = nullptr;  // Optional
  
    for (size_t i = 0; i < m_swapChainImages.size(); i++) {
      bufferInfo.buffer = m_uniformBuffers[i];
      descriptorWrite.dstSet = m_descriptorSets[i];
      vkUpdateDescriptorSets(m_device, 1, &descriptorWrite, 0, nullptr);
    }
  }
  
  void VulkanBaseApp::createCommandBuffers() {
    m_commandBuffers.resize(m_swapChainFramebuffers.size());
    VkCommandBufferAllocateInfo allocInfo = {};
    allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
    allocInfo.commandPool = m_commandPool;
    allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
    allocInfo.commandBufferCount = (uint32_t)m_commandBuffers.size();
  
    if (vkAllocateCommandBuffers(m_device, &allocInfo, m_commandBuffers.data()) !=
        VK_SUCCESS) {
      throw std::runtime_error("failed to allocate command buffers!");
    }
  
    for (size_t i = 0; i < m_commandBuffers.size(); i++) {
      VkCommandBufferBeginInfo beginInfo = {};
      beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
      beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT;
      beginInfo.pInheritanceInfo = nullptr;  // Optional
  
      if (vkBeginCommandBuffer(m_commandBuffers[i], &beginInfo) != VK_SUCCESS) {
        throw std::runtime_error("failed to begin recording command buffer!");
      }
  
      VkRenderPassBeginInfo renderPassInfo = {};
      renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
      renderPassInfo.renderPass = m_renderPass;
      renderPassInfo.framebuffer = m_swapChainFramebuffers[i];
  
      renderPassInfo.renderArea.offset = {0, 0};
      renderPassInfo.renderArea.extent = m_swapChainExtent;
  
      VkClearValue clearColors[2];
      clearColors[0].color = {0.0f, 0.0f, 0.0f, 1.0f};
      clearColors[1].depthStencil = {1.0f, 0};
      renderPassInfo.clearValueCount = countof(clearColors);
      renderPassInfo.pClearValues = clearColors;
  
      vkCmdBeginRenderPass(m_commandBuffers[i], &renderPassInfo,
                           VK_SUBPASS_CONTENTS_INLINE);
  
      vkCmdBindPipeline(m_commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS,
                        m_graphicsPipeline);
  
      vkCmdBindDescriptorSets(m_commandBuffers[i],
                              VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout,
                              0, 1, &m_descriptorSets[i], 0, nullptr);
  
      fillRenderingCommandBuffer(m_commandBuffers[i]);
  
      vkCmdEndRenderPass(m_commandBuffers[i]);
  
      if (vkEndCommandBuffer(m_commandBuffers[i]) != VK_SUCCESS) {
        throw std::runtime_error("failed to record command buffer!");
      }
    }
  }
  
  void VulkanBaseApp::createSyncObjects() {
    VkSemaphoreCreateInfo semaphoreInfo = {};
    semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
    VkFenceCreateInfo fenceInfo = {};
    fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
    fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
  
    m_inFlightFences.resize(MAX_FRAMES_IN_FLIGHT);
    m_imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
    m_renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
  
    for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
      if (vkCreateSemaphore(m_device, &semaphoreInfo, nullptr,
                            &m_imageAvailableSemaphores[i]) != VK_SUCCESS) {
        throw std::runtime_error("Failed to create image available semaphore!");
      }
      if (vkCreateSemaphore(m_device, &semaphoreInfo, nullptr,
                            &m_renderFinishedSemaphores[i]) != VK_SUCCESS) {
        throw std::runtime_error("Failed to create image available semaphore!");
      }
      if (vkCreateFence(m_device, &fenceInfo, nullptr, &m_inFlightFences[i]) !=
          VK_SUCCESS) {
        throw std::runtime_error("Failed to create image available semaphore!");
      }
    }
  }
  
  void VulkanBaseApp::getWaitFrameSemaphores(
      std::vector<VkSemaphore> &wait,
      std::vector<VkPipelineStageFlags> &waitStages) const {}
  
  void VulkanBaseApp::getSignalFrameSemaphores(
      std::vector<VkSemaphore> &signal) const {}
  
  VkDeviceSize VulkanBaseApp::getUniformSize() const { return VkDeviceSize(0); }
  
  void VulkanBaseApp::updateUniformBuffer(uint32_t imageIndex, size_t frame) {}
  
  void VulkanBaseApp::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage,
                                   VkMemoryPropertyFlags properties,
                                   VkBuffer &buffer,
                                   VkDeviceMemory &bufferMemory) {
    VkBufferCreateInfo bufferInfo = {};
    bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
    bufferInfo.size = size;
    bufferInfo.usage = usage;
    bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
  
    if (vkCreateBuffer(m_device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) {
      throw std::runtime_error("failed to create buffer!");
    }
  
    VkMemoryRequirements memRequirements;
    vkGetBufferMemoryRequirements(m_device, buffer, &memRequirements);
  
    VkMemoryAllocateInfo allocInfo = {};
    allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
    allocInfo.allocationSize = memRequirements.size;
    allocInfo.memoryTypeIndex = findMemoryType(
        m_physicalDevice, memRequirements.memoryTypeBits, properties);
  
    if (vkAllocateMemory(m_device, &allocInfo, nullptr, &bufferMemory) !=
        VK_SUCCESS) {
      throw std::runtime_error("failed to allocate buffer memory!");
    }
  
    vkBindBufferMemory(m_device, buffer, bufferMemory, 0);
  }
  
  void VulkanBaseApp::createExternalBuffer(
      VkDeviceSize size, VkBufferUsageFlags usage,
      VkMemoryPropertyFlags properties,
      VkExternalMemoryHandleTypeFlagsKHR extMemHandleType, VkBuffer &buffer,
      VkDeviceMemory &bufferMemory) {
    VkBufferCreateInfo bufferInfo = {};
    bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
    bufferInfo.size = size;
    bufferInfo.usage = usage;
    bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
  
    VkExternalMemoryBufferCreateInfo externalMemoryBufferInfo = {};
    externalMemoryBufferInfo.sType =
        VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO;
    externalMemoryBufferInfo.handleTypes = extMemHandleType;
    bufferInfo.pNext = &externalMemoryBufferInfo;
  
    if (vkCreateBuffer(m_device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) {
      throw std::runtime_error("failed to create buffer!");
    }
  
    VkMemoryRequirements memRequirements;
    vkGetBufferMemoryRequirements(m_device, buffer, &memRequirements);
  
  #ifdef _WIN64
    WindowsSecurityAttributes winSecurityAttributes;
  
    VkExportMemoryWin32HandleInfoKHR vulkanExportMemoryWin32HandleInfoKHR = {};
    vulkanExportMemoryWin32HandleInfoKHR.sType =
        VK_STRUCTURE_TYPE_EXPORT_MEMORY_WIN32_HANDLE_INFO_KHR;
    vulkanExportMemoryWin32HandleInfoKHR.pNext = NULL;
    vulkanExportMemoryWin32HandleInfoKHR.pAttributes = &winSecurityAttributes;
    vulkanExportMemoryWin32HandleInfoKHR.dwAccess =
        DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE;
    vulkanExportMemoryWin32HandleInfoKHR.name = (LPCWSTR)NULL;
  #endif
    VkExportMemoryAllocateInfoKHR vulkanExportMemoryAllocateInfoKHR = {};
    vulkanExportMemoryAllocateInfoKHR.sType =
        VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO_KHR;
  #ifdef _WIN64
    vulkanExportMemoryAllocateInfoKHR.pNext =
        extMemHandleType & VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT_KHR
            ? &vulkanExportMemoryWin32HandleInfoKHR
            : NULL;
    vulkanExportMemoryAllocateInfoKHR.handleTypes = extMemHandleType;
  #else
    vulkanExportMemoryAllocateInfoKHR.pNext = NULL;
    vulkanExportMemoryAllocateInfoKHR.handleTypes =
        VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
  #endif
    VkMemoryAllocateInfo allocInfo = {};
    allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
    allocInfo.pNext = &vulkanExportMemoryAllocateInfoKHR;
    allocInfo.allocationSize = memRequirements.size;
    allocInfo.memoryTypeIndex = findMemoryType(
        m_physicalDevice, memRequirements.memoryTypeBits, properties);
  
    if (vkAllocateMemory(m_device, &allocInfo, nullptr, &bufferMemory) !=
        VK_SUCCESS) {
      throw std::runtime_error("failed to allocate external buffer memory!");
    }
  
    vkBindBufferMemory(m_device, buffer, bufferMemory, 0);
  }
  
  void *VulkanBaseApp::getMemHandle(
      VkDeviceMemory memory, VkExternalMemoryHandleTypeFlagBits handleType) {
  #ifdef _WIN64
    HANDLE handle = 0;
  
    VkMemoryGetWin32HandleInfoKHR vkMemoryGetWin32HandleInfoKHR = {};
    vkMemoryGetWin32HandleInfoKHR.sType =
        VK_STRUCTURE_TYPE_MEMORY_GET_WIN32_HANDLE_INFO_KHR;
    vkMemoryGetWin32HandleInfoKHR.pNext = NULL;
    vkMemoryGetWin32HandleInfoKHR.memory = memory;
    vkMemoryGetWin32HandleInfoKHR.handleType = handleType;
  
    PFN_vkGetMemoryWin32HandleKHR fpGetMemoryWin32HandleKHR;
    fpGetMemoryWin32HandleKHR =
        (PFN_vkGetMemoryWin32HandleKHR)vkGetDeviceProcAddr(
            m_device, "vkGetMemoryWin32HandleKHR");
    if (!fpGetMemoryWin32HandleKHR) {
      throw std::runtime_error("Failed to retrieve vkGetMemoryWin32HandleKHR!");
    }
    if (fpGetMemoryWin32HandleKHR(m_device, &vkMemoryGetWin32HandleInfoKHR,
                                  &handle) != VK_SUCCESS) {
      throw std::runtime_error("Failed to retrieve handle for buffer!");
    }
    return (void *)handle;
  #else
    int fd = -1;
  
    VkMemoryGetFdInfoKHR vkMemoryGetFdInfoKHR = {};
    vkMemoryGetFdInfoKHR.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR;
    vkMemoryGetFdInfoKHR.pNext = NULL;
    vkMemoryGetFdInfoKHR.memory = memory;
    vkMemoryGetFdInfoKHR.handleType = handleType;
  
    PFN_vkGetMemoryFdKHR fpGetMemoryFdKHR;
    fpGetMemoryFdKHR =
        (PFN_vkGetMemoryFdKHR)vkGetDeviceProcAddr(m_device, "vkGetMemoryFdKHR");
    if (!fpGetMemoryFdKHR) {
      throw std::runtime_error("Failed to retrieve vkGetMemoryWin32HandleKHR!");
    }
    if (fpGetMemoryFdKHR(m_device, &vkMemoryGetFdInfoKHR, &fd) != VK_SUCCESS) {
      throw std::runtime_error("Failed to retrieve handle for buffer!");
    }
    return (void *)(uintptr_t)fd;
  #endif /* _WIN64 */
  }
  
  void *VulkanBaseApp::getSemaphoreHandle(
      VkSemaphore semaphore, VkExternalSemaphoreHandleTypeFlagBits handleType) {
  #ifdef _WIN64
    HANDLE handle;
  
    VkSemaphoreGetWin32HandleInfoKHR semaphoreGetWin32HandleInfoKHR = {};
    semaphoreGetWin32HandleInfoKHR.sType =
        VK_STRUCTURE_TYPE_SEMAPHORE_GET_WIN32_HANDLE_INFO_KHR;
    semaphoreGetWin32HandleInfoKHR.pNext = NULL;
    semaphoreGetWin32HandleInfoKHR.semaphore = semaphore;
    semaphoreGetWin32HandleInfoKHR.handleType = handleType;
  
    PFN_vkGetSemaphoreWin32HandleKHR fpGetSemaphoreWin32HandleKHR;
    fpGetSemaphoreWin32HandleKHR =
        (PFN_vkGetSemaphoreWin32HandleKHR)vkGetDeviceProcAddr(
            m_device, "vkGetSemaphoreWin32HandleKHR");
    if (!fpGetSemaphoreWin32HandleKHR) {
      throw std::runtime_error("Failed to retrieve vkGetMemoryWin32HandleKHR!");
    }
    if (fpGetSemaphoreWin32HandleKHR(m_device, &semaphoreGetWin32HandleInfoKHR,
                                     &handle) != VK_SUCCESS) {
      throw std::runtime_error("Failed to retrieve handle for buffer!");
    }
  
    return (void *)handle;
  #else
    int fd;
  
    VkSemaphoreGetFdInfoKHR semaphoreGetFdInfoKHR = {};
    semaphoreGetFdInfoKHR.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
    semaphoreGetFdInfoKHR.pNext = NULL;
    semaphoreGetFdInfoKHR.semaphore = semaphore;
    semaphoreGetFdInfoKHR.handleType = handleType;
  
    PFN_vkGetSemaphoreFdKHR fpGetSemaphoreFdKHR;
    fpGetSemaphoreFdKHR = (PFN_vkGetSemaphoreFdKHR)vkGetDeviceProcAddr(
        m_device, "vkGetSemaphoreFdKHR");
    if (!fpGetSemaphoreFdKHR) {
      throw std::runtime_error("Failed to retrieve vkGetMemoryWin32HandleKHR!");
    }
    if (fpGetSemaphoreFdKHR(m_device, &semaphoreGetFdInfoKHR, &fd) !=
        VK_SUCCESS) {
      throw std::runtime_error("Failed to retrieve handle for buffer!");
    }
  
    return (void *)(uintptr_t)fd;
  #endif
  }
  
  void VulkanBaseApp::createExternalSemaphore(
      VkSemaphore &semaphore, VkExternalSemaphoreHandleTypeFlagBits handleType) {
    VkSemaphoreCreateInfo semaphoreInfo = {};
    semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
    VkExportSemaphoreCreateInfoKHR exportSemaphoreCreateInfo = {};
    exportSemaphoreCreateInfo.sType =
        VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO_KHR;
  
  #ifdef _WIN64
    WindowsSecurityAttributes winSecurityAttributes;
  
    VkExportSemaphoreWin32HandleInfoKHR exportSemaphoreWin32HandleInfoKHR = {};
    exportSemaphoreWin32HandleInfoKHR.sType =
        VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_WIN32_HANDLE_INFO_KHR;
    exportSemaphoreWin32HandleInfoKHR.pNext = NULL;
    exportSemaphoreWin32HandleInfoKHR.pAttributes = &winSecurityAttributes;
    exportSemaphoreWin32HandleInfoKHR.dwAccess =
        DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE;
    exportSemaphoreWin32HandleInfoKHR.name = (LPCWSTR)NULL;
    exportSemaphoreCreateInfo.pNext =
        (handleType & VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT)
            ? &exportSemaphoreWin32HandleInfoKHR
            : NULL;
  #else
    exportSemaphoreCreateInfo.pNext = NULL;
  #endif
    exportSemaphoreCreateInfo.handleTypes = handleType;
    semaphoreInfo.pNext = &exportSemaphoreCreateInfo;
  
    if (vkCreateSemaphore(m_device, &semaphoreInfo, nullptr, &semaphore) !=
        VK_SUCCESS) {
      throw std::runtime_error(
          "failed to create synchronization objects for a CUDA-Vulkan!");
    }
  }
  
  void VulkanBaseApp::importExternalBuffer(
      void *handle, VkExternalMemoryHandleTypeFlagBits handleType, size_t size,
      VkBufferUsageFlags usage, VkMemoryPropertyFlags properties,
      VkBuffer &buffer, VkDeviceMemory &memory) {
    VkBufferCreateInfo bufferInfo = {};
    bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
    bufferInfo.size = size;
    bufferInfo.usage = usage;
    bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
  
    VkExternalMemoryBufferCreateInfo externalMemoryBufferInfo = {};
    externalMemoryBufferInfo.sType =
        VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO;
    externalMemoryBufferInfo.handleTypes =
        VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT;
    bufferInfo.pNext = &externalMemoryBufferInfo;
  
    if (vkCreateBuffer(m_device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) {
      throw std::runtime_error("failed to create buffer!");
    }
  
    VkMemoryRequirements memRequirements;
    vkGetBufferMemoryRequirements(m_device, buffer, &memRequirements);
  
  #ifdef _WIN64
    VkImportMemoryWin32HandleInfoKHR handleInfo = {};
    handleInfo.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_WIN32_HANDLE_INFO_KHR;
    handleInfo.pNext = NULL;
    handleInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT;
    handleInfo.handle = handle;
    handleInfo.name = NULL;
  #else
    VkImportMemoryFdInfoKHR handleInfo = {};
    handleInfo.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR;
    handleInfo.pNext = NULL;
    handleInfo.fd = (int)(uintptr_t)handle;
    handleInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT;
  #endif /* _WIN64 */
  
    VkMemoryAllocateInfo memAllocation = {};
    memAllocation.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
    memAllocation.pNext = (void *)&handleInfo;
    memAllocation.allocationSize = memRequirements.size;
    memAllocation.memoryTypeIndex = findMemoryType(
        m_physicalDevice, memRequirements.memoryTypeBits, properties);
  
    if (vkAllocateMemory(m_device, &memAllocation, nullptr, &memory) !=
        VK_SUCCESS) {
      throw std::runtime_error("Failed to import allocation!");
    }
  
    vkBindBufferMemory(m_device, buffer, memory, 0);
  }
  
  void VulkanBaseApp::copyBuffer(VkBuffer dst, VkBuffer src, VkDeviceSize size) {
    VkCommandBuffer commandBuffer = beginSingleTimeCommands();
  
    VkBufferCopy copyRegion = {};
    copyRegion.size = size;
    vkCmdCopyBuffer(commandBuffer, src, dst, 1, &copyRegion);
  
    endSingleTimeCommands(commandBuffer);
  }
  
  void VulkanBaseApp::drawFrame() {
    size_t currentFrameIdx = m_currentFrame % MAX_FRAMES_IN_FLIGHT;
    vkWaitForFences(m_device, 1, &m_inFlightFences[currentFrameIdx], VK_TRUE,
                    std::numeric_limits<uint64_t>::max());
  
    uint32_t imageIndex;
    VkResult result = vkAcquireNextImageKHR(
        m_device, m_swapChain, std::numeric_limits<uint64_t>::max(),
        m_imageAvailableSemaphores[currentFrameIdx], VK_NULL_HANDLE, &imageIndex);
    if (result == VK_ERROR_OUT_OF_DATE_KHR) {
      recreateSwapChain();
    } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
      throw std::runtime_error("Failed to acquire swap chain image!");
    }
  
    updateUniformBuffer(imageIndex, m_currentFrame);
  
    VkSubmitInfo submitInfo = {};
    submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
  
    std::vector<VkSemaphore> waitSemaphores;
    std::vector<VkPipelineStageFlags> waitStages;
  
    waitSemaphores.push_back(m_imageAvailableSemaphores[currentFrameIdx]);
    waitStages.push_back(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT);
    getWaitFrameSemaphores(waitSemaphores, waitStages);
  
    submitInfo.waitSemaphoreCount = (uint32_t)waitSemaphores.size();
    submitInfo.pWaitSemaphores = waitSemaphores.data();
    submitInfo.pWaitDstStageMask = waitStages.data();
  
    submitInfo.commandBufferCount = 1;
    submitInfo.pCommandBuffers = &m_commandBuffers[imageIndex];
  
    std::vector<VkSemaphore> signalSemaphores;
    getSignalFrameSemaphores(signalSemaphores);
    signalSemaphores.push_back(m_renderFinishedSemaphores[currentFrameIdx]);
    submitInfo.signalSemaphoreCount = (uint32_t)signalSemaphores.size();
    submitInfo.pSignalSemaphores = signalSemaphores.data();
  
    vkResetFences(m_device, 1, &m_inFlightFences[currentFrameIdx]);
  
    if (vkQueueSubmit(m_graphicsQueue, 1, &submitInfo,
                      m_inFlightFences[currentFrameIdx]) != VK_SUCCESS) {
      throw std::runtime_error("failed to submit draw command buffer!");
    }
  
    VkPresentInfoKHR presentInfo = {};
    presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
    presentInfo.waitSemaphoreCount = 1;
    presentInfo.pWaitSemaphores = &m_renderFinishedSemaphores[currentFrameIdx];
  
    VkSwapchainKHR swapChains[] = {m_swapChain};
    presentInfo.swapchainCount = 1;
    presentInfo.pSwapchains = swapChains;
    presentInfo.pImageIndices = &imageIndex;
  
    result = vkQueuePresentKHR(m_presentQueue, &presentInfo);
    if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR ||
        m_framebufferResized) {
      recreateSwapChain();
      m_framebufferResized = false;
    } else if (result != VK_SUCCESS) {
      throw std::runtime_error("Failed to acquire swap chain image!");
    }
  
    m_currentFrame++;
  }
  
  void VulkanBaseApp::cleanupSwapChain() {
    if (m_depthImageView != VK_NULL_HANDLE) {
      vkDestroyImageView(m_device, m_depthImageView, nullptr);
    }
    if (m_depthImage != VK_NULL_HANDLE) {
      vkDestroyImage(m_device, m_depthImage, nullptr);
    }
    if (m_depthImageMemory != VK_NULL_HANDLE) {
      vkFreeMemory(m_device, m_depthImageMemory, nullptr);
    }
  
    for (size_t i = 0; i < m_uniformBuffers.size(); i++) {
      vkDestroyBuffer(m_device, m_uniformBuffers[i], nullptr);
      vkFreeMemory(m_device, m_uniformMemory[i], nullptr);
    }
  
    if (m_descriptorPool != VK_NULL_HANDLE) {
      vkDestroyDescriptorPool(m_device, m_descriptorPool, nullptr);
    }
  
    for (size_t i = 0; i < m_swapChainFramebuffers.size(); i++) {
      vkDestroyFramebuffer(m_device, m_swapChainFramebuffers[i], nullptr);
    }
  
    if (m_graphicsPipeline != VK_NULL_HANDLE) {
      vkDestroyPipeline(m_device, m_graphicsPipeline, nullptr);
    }
  
    if (m_pipelineLayout != VK_NULL_HANDLE) {
      vkDestroyPipelineLayout(m_device, m_pipelineLayout, nullptr);
    }
  
    if (m_renderPass != VK_NULL_HANDLE) {
      vkDestroyRenderPass(m_device, m_renderPass, nullptr);
    }
  
    for (size_t i = 0; i < m_swapChainImageViews.size(); i++) {
      vkDestroyImageView(m_device, m_swapChainImageViews[i], nullptr);
    }
  
    if (m_swapChain != VK_NULL_HANDLE) {
      vkDestroySwapchainKHR(m_device, m_swapChain, nullptr);
    }
  }
  
  void VulkanBaseApp::recreateSwapChain() {
    int width, height;
  
    glfwGetFramebufferSize(m_window, &width, &height);
    while (width == 0 || height == 0) {
      glfwWaitEvents();
      glfwGetFramebufferSize(m_window, &width, &height);
    }
  
    vkDeviceWaitIdle(m_device);
  
    cleanupSwapChain();
  
    createSwapChain();
    createImageViews();
    createRenderPass();
    createGraphicsPipeline();
    createDepthResources();
    createFramebuffers();
    createUniformBuffers();
    createDescriptorPool();
    createDescriptorSets();
    createCommandBuffers();
  }
  
  void VulkanBaseApp::mainLoop() {
    while (!glfwWindowShouldClose(m_window)) {
      glfwPollEvents();
      drawFrame();
    }
    vkDeviceWaitIdle(m_device);
  }
  
  void readFile(std::istream &s, std::vector<char> &data) {
    s.seekg(0, std::ios_base::end);
    data.resize(s.tellg());
    s.clear();
    s.seekg(0, std::ios_base::beg);
    s.read(data.data(), data.size());
  }
  