text: #include "vkengine.hpp"
  #include <vulkan/vulkan.hpp>
  #include <GLFW/glfw3.h>
  
  #include <iostream>
  #include <vector>
  #include <cstring>
  #include <stdexcept>
  #include <cstdlib>
  #include <algorithm>
  #include <iterator>
  #include <cassert>
  #include <limits>
  #include <set>
  #include <fstream>
  
  #ifdef NDEBUG
      bool enableValidationLayer = false;
  #else
      bool enableValidationLayer = true;
  #endif
  
  PFN_vkCreateDebugUtilsMessengerEXT pfnVkCreateDebugUtilsMessengerEXT;
  PFN_vkDestroyDebugUtilsMessengerEXT pfnVkDestroyDebugUtilsMessengerEXT;
  
  void VulkanEngine::initWindow() {
      glfwInit();
  
      glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
      glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
  
      window = glfwCreateWindow(800, 600, "Mono", nullptr, nullptr);
  }
  
  void VulkanEngine::initVulkan() {
     initInstance();
     createDevice();
     createSwapchain();
     createImageViews();
     createRenderPass();
     createGraphicsPipeline();
     createFramebuffers();
     createCommandPool();
     createCommandBuffer();
  }
  
  void VulkanEngine::mainloop() {
      while(!glfwWindowShouldClose(window)) {
          glfwPollEvents();
      }
  }
  
  void VulkanEngine::cleanup() {
      device.destroyCommandPool(commandPool);
  
      for(const auto& framebuffer : framebuffers) {
          device.destroyFramebuffer(framebuffer);
      }
      
      device.destroyPipeline(graphicsPipeline);
      device.destroyPipelineLayout(pipelineLayout);
  
      device.destroyRenderPass(renderPass);
  
      for(const auto& imageView : swapchainImageViews) {
          device.destroyImageView(imageView);
      }
      device.destroySwapchainKHR(swapchain);
      device.destroy();
  
      if(enableValidationLayer) {
          instance.destroyDebugUtilsMessengerEXT(debugUtilsMessenger, nullptr, vk::DispatchLoaderDynamic{ instance, vkGetInstanceProcAddr });
      }
  
      instance.destroySurfaceKHR();
      instance.destroy();
  
      glfwDestroyWindow(window);
      glfwTerminate();
  }
  
  VKAPI_ATTR VkResult VKAPI_CALL VulkanEngine::vkCreateDebugUtilsMessengerEXT(
      VkInstance instance, 
      const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, 
      const VkAllocationCallbacks* pAllocator, 
      VkDebugUtilsMessengerEXT* pMessenger)
  {
      return pfnVkCreateDebugUtilsMessengerEXT(instance, pCreateInfo, pAllocator, pMessenger);
  } 
  
  VKAPI_ATTR void VKAPI_CALL VulkanEngine::vkDestroyDebugUtilsMessengerEXT(
      VkInstance instance,
      VkDebugUtilsMessengerEXT pMessenger,
      const VkAllocationCallbacks* pAllocator)
  {
      return pfnVkDestroyDebugUtilsMessengerEXT(instance, pMessenger, pAllocator);
  }
  
  VKAPI_ATTR VkBool32 VKAPI_CALL VulkanEngine::debugCallback(
      VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, 
      VkDebugUtilsMessageTypeFlagsEXT messageType, 
      VkDebugUtilsMessengerCallbackDataEXT const* pCallback, 
      void* pUserData) 
  {
      std::cout << vk::to_string(static_cast<vk::DebugUtilsMessageSeverityFlagBitsEXT>(messageSeverity))  << '\t'
                << vk::to_string(static_cast<vk::DebugUtilsMessageTypeFlagsEXT>( messageType )) << '\t'
                << "MessageIDName: " << pCallback->pMessageIdName << '\n'
                << "Validation Layer: " << pCallback->pMessage << '\n'
                << "===========================================================================================" << '\n';
  
      return VK_FALSE;
  }
  
  void VulkanEngine::initInstance() {
      auto extensions = getRequiredExtensions();
      try {
          vk::ApplicationInfo appInfo(
              "Mono", 
              VK_API_VERSION_1_0, 
              "No engine", 
              VK_API_VERSION_1_0, 
              VK_API_VERSION_1_0
          );
  
          if(enableValidationLayer && !checkValidationLayerSupport()) {
              std::cout << "Validation layer requested but not found" << '\n';
              exit(1);
          }
  
          vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | 
                                                              vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning);
  
          vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral |
                                                              vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance |
                                                              vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation);
  
          vk::DebugUtilsMessengerCreateInfoEXT debugInfo({}, severityFlags, messageTypeFlags, &debugCallback);
  
          vk::InstanceCreateInfo instanceInfo(
              vk::InstanceCreateFlags(), 
              &appInfo, 
              static_cast<uint32_t>(validationLayers.size()), 
              validationLayers.data(), 
              static_cast<uint32_t>(extensions.size()), 
              extensions.data()
          );
          instanceInfo.setPNext(&debugInfo);
  
          instance = vk::createInstance(instanceInfo);
  
          pfnVkCreateDebugUtilsMessengerEXT = reinterpret_cast<PFN_vkCreateDebugUtilsMessengerEXT>(instance.getProcAddr("vkCreateDebugUtilsMessengerEXT"));
          pfnVkDestroyDebugUtilsMessengerEXT = reinterpret_cast<PFN_vkDestroyDebugUtilsMessengerEXT>(instance.getProcAddr("vkDestroyDebugUtilsMessengerEXT"));
      
          debugUtilsMessenger = instance.createDebugUtilsMessengerEXT(debugInfo, nullptr, vk::DispatchLoaderDynamic{ instance, vkGetInstanceProcAddr });
      } 
      catch (const std::exception& e) {
          std::cout << e.what() << '\n';
          exit(1);
      }
  }
  
  std::vector<const char*> VulkanEngine::getRequiredExtensions() {
      uint32_t glfwExtensionCount = 0;
      const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
  
      std::vector<const char*> extensions = {glfwExtensions, glfwExtensions + glfwExtensionCount};
  
      if(enableValidationLayer) {
          extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
      }
  
      return extensions;
  }
  
  bool VulkanEngine::checkValidationLayerSupport() {
      bool layerFound = false;
  
      std::vector<vk::LayerProperties> availableLayers = vk::enumerateInstanceLayerProperties();
  
      for(const auto& layerName : validationLayers) {
          layerFound = false;
  
          for(const auto& layerProperty : availableLayers) {
              if(strcmp(layerName , layerProperty.layerName) == 0) {
                  layerFound = true;
              }
          }
      }
  
      return layerFound;
  }
  
  void VulkanEngine::createDevice() {
      physicalDevice = instance.enumeratePhysicalDevices().front();
  
      std::vector<vk::QueueFamilyProperties> queueFamilyProperties = physicalDevice.getQueueFamilyProperties();
  
      // Get graphics queue index
      auto propertyIterator = std::find_if(queueFamilyProperties.begin(), queueFamilyProperties.end(), [](vk::QueueFamilyProperties const& queueFamily){
          return queueFamily.queueFlags& vk::QueueFlagBits::eGraphics;
      });
  
      graphicsQueueFamilyIndex = std::distance(queueFamilyProperties.begin(), propertyIterator);
      assert(queueFamilyProperties.size() > graphicsQueueFamilyIndex);
  
      VkSurfaceKHR _surface;
      glfwCreateWindowSurface((VkInstance)instance, window, nullptr, &_surface);
      surface = vk::SurfaceKHR(_surface);
  
      vk::Bool32 presentQueueSupport;
      uint32_t presentQueueFamilyIndex = 0;
      vk::Result isPresentQueueFamilySupported =  physicalDevice.getSurfaceSupportKHR(graphicsQueueFamilyIndex, surface, &presentQueueSupport);
      if(presentQueueSupport == VK_TRUE) {
          presentQueueFamilyIndex = graphicsQueueFamilyIndex;
      } else {
          throw std::runtime_error("couldn't find support for present queue");
      }
  
      std::set<uint32_t> uniqueQueueFamilies = { graphicsQueueFamilyIndex, presentQueueFamilyIndex };
      std::vector<vk::DeviceQueueCreateInfo> deviceQueueInfos;
  
      try {
          float queuePriority = 1.0f;
          for(const auto& queueFamily : uniqueQueueFamilies) {
              vk::DeviceQueueCreateInfo deviceQueueInfo(vk::DeviceQueueCreateFlags(), queueFamily, 1, &queuePriority);
              deviceQueueInfos.push_back(deviceQueueInfo);
          }
  
          vk::DeviceCreateInfo deviceInfo(vk::DeviceCreateFlags(), deviceQueueInfos, {} /*Layer Name*/, deviceExtensions);
  
          device = physicalDevice.createDevice(deviceInfo);
  
          device.getQueue(graphicsQueueFamilyIndex, 0, &graphicsQueue);
          device.getQueue(presentQueueFamilyIndex, 0, &presentQueue);
      }
      catch(std::exception const& e) {
          std::cout << "DeviceCreateInfoException: " << e.what() << '\n';
          exit(1);
      }
  }
  
  bool VulkanEngine::checkDeviceExtensionSupport() {
      bool extensionFound = false;
      std::vector<vk::ExtensionProperties> deviceExtensionsProperties = physicalDevice.enumerateDeviceExtensionProperties();
  
      for(const auto& extensionName : deviceExtensions) {
          for(const auto& extensionProperty: deviceExtensionsProperties) {
              if(strcmp(extensionName, extensionProperty.extensionName) == 0) {
                  extensionFound = true;
              }
          }
      }
  
      return extensionFound;
  }
  
  void VulkanEngine::createSwapchain() {
      if(enableValidationLayer && !checkDeviceExtensionSupport()) {
          throw std::runtime_error("can't find required device extension(s)");
      }
  
      VulkanEngine::SwapchainDetails swapchainDetails = getSwapchainDetails();
      uint32_t queueFamilyIndexes[] = {graphicsQueueFamilyIndex, presentQueueFamilyIndex};
  
      uint32_t imageCount = swapchainDetails.capabilities.minImageCount + 1;
      if(swapchainDetails.capabilities.maxImageCount > 0 && imageCount > swapchainDetails.capabilities.maxImageCount) {
          imageCount = swapchainDetails.capabilities.maxImageCount;
      }
  
      vk::SwapchainCreateInfoKHR swapchainInfo(
          vk::SwapchainCreateFlagsKHR(), 
          surface, 
          imageCount, 
          surfaceFormat.format, 
          surfaceFormat.colorSpace, 
          swapchainExtent, 
          1, 
          vk::ImageUsageFlagBits::eColorAttachment,
          vk::SharingMode::eExclusive,
          {},
          swapchainDetails.capabilities.currentTransform,
          vk::CompositeAlphaFlagBitsKHR::eOpaque,
          swapchainDetails.presentMode,
          true,
          nullptr
      );
      
      if(graphicsQueueFamilyIndex != presentQueueFamilyIndex) {
          swapchainInfo.imageSharingMode = vk::SharingMode::eConcurrent;
          swapchainInfo.queueFamilyIndexCount = 2;
          swapchainInfo.pQueueFamilyIndices = queueFamilyIndexes;
      }
      try{
          swapchain = device.createSwapchainKHR(swapchainInfo);
      } 
      catch(const std::exception& e) {
          throw std::runtime_error(e.what());
      }
  
      swapchainImages = device.getSwapchainImagesKHR(swapchain);
  }
  
  VulkanEngine::SwapchainDetails VulkanEngine::getSwapchainDetails() {
      VulkanEngine::SwapchainDetails swapchainDetails;
  
      swapchainDetails.capabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface);
      if(swapchainDetails.capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max()) {
          swapchainExtent = swapchainDetails.capabilities.currentExtent;
      } else {
          int width, height;
          glfwGetFramebufferSize(window, &width, &height);
  
          swapchainExtent.width = std::clamp((uint32_t)width, swapchainDetails.capabilities.minImageExtent.width, swapchainDetails.capabilities.maxImageExtent.height);
          swapchainExtent.height = std::clamp((uint32_t)height, swapchainDetails.capabilities.minImageExtent.height, swapchainDetails.capabilities.maxImageExtent.height);
      }
  
      swapchainDetails.surfaceFormats = physicalDevice.getSurfaceFormatsKHR(surface);
      assert(!swapchainDetails.surfaceFormats.empty());
  
      surfaceFormat = swapchainDetails.surfaceFormats[0];
      for(const auto& formatProperty : swapchainDetails.surfaceFormats) {
          if(formatProperty.format == vk::Format::eB8G8R8A8Srgb && formatProperty.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) {
              surfaceFormat = formatProperty;
          }
      }
  
      swapchainDetails.presentMode = vk::PresentModeKHR::eFifo;
  
      return swapchainDetails;
  }
  
  void VulkanEngine::createImageViews() {
      swapchainImageViews.reserve(swapchainImages.size());
  
      vk::ComponentMapping componentMapping(vk::ComponentSwizzle::eR, vk::ComponentSwizzle::eG, vk::ComponentSwizzle::eB, vk::ComponentSwizzle::eA);
      vk::ImageSubresourceRange subresourceRange(vk::ImageAspectFlagBits::eColor, 0 , 1, 0, 1);
  
      for(size_t i = 0; i < swapchainImages.size(); ++i) {
          try {
              vk::ImageViewCreateInfo imageViewInfo
                  (vk::ImageViewCreateFlags(), swapchainImages[i], vk::ImageViewType::e2D, surfaceFormat.format, componentMapping, subresourceRange);
  
              swapchainImageViews.push_back(device.createImageView(imageViewInfo));
          }
          catch(std::exception const& e) {
              throw std::runtime_error(e.what());
          }
      }
  }
  
  void VulkanEngine::createCommandPool() {
      vk::CommandPoolCreateInfo commandPoolInfo(vk::CommandPoolCreateFlags(), graphicsQueueFamilyIndex);
      commandPool = device.createCommandPool(commandPoolInfo);
  }
  
  void VulkanEngine::createCommandBuffer() {
      commandBuffers.resize(framebuffers.size());
      try {
          vk::CommandBufferAllocateInfo commandBufferInfo(commandPool, vk::CommandBufferLevel::ePrimary, static_cast<uint32_t>(commandBuffers.size()));
          commandBuffers = device.allocateCommandBuffers(commandBufferInfo);
  
          for(size_t i = 0; i < commandBuffers.size(); ++i) {
              vk::CommandBufferBeginInfo commandBufferBeginInfo(vk::CommandBufferUsageFlags(), nullptr);
              commandBuffers[i].begin(commandBufferBeginInfo);
  
              std::array<float, 4> clearColorValue = {0.0f, 0.0f, 0.0f, 1.0f};
              vk::ClearValue clearValue{vk::ClearColorValue(clearColorValue)};
              vk::RenderPassBeginInfo renderPassBeginInfo(
                  renderPass, 
                  framebuffers[i], 
                  vk::Rect2D(vk::Offset2D(0, 0), swapchainExtent), 
                  clearValue
              );
  
              commandBuffers[i].beginRenderPass(renderPassBeginInfo, vk::SubpassContents::eInline);
  
              commandBuffers[i].bindPipeline(vk::PipelineBindPoint::eGraphics, graphicsPipeline);
              commandBuffers[i].draw(3, 1, 0, 0);
  
              commandBuffers[i].endRenderPass();
              commandBuffers[i].end();
          }
      } 
      catch (const std::exception& e) {
          throw std::runtime_error(e.what());
      }
  }
  
  std::vector<char> VulkanEngine::readFile(const std::string& filename) {
      std::cout << "opening file " << filename << std::endl;
      std::ifstream file(filename, std::ios::ate | std::ios::binary);
  
      if(!file.is_open()) {
          throw std::runtime_error("failed to open file " + filename);
      }
  
      std::cout << filename << " successfully opened\n";
  
      size_t fileSize = (size_t)file.tellg();
      std::vector<char> buffer(fileSize);
  
      file.seekg(0);
      file.read(buffer.data(), fileSize);
  
      file.close();
  
      return buffer;
  }
  
  vk::ShaderModule VulkanEngine::createShaderModule(const std::vector<char> &code) {
      vk::ShaderModuleCreateInfo shaderModuleInfo(vk::ShaderModuleCreateFlags(), code.size(), reinterpret_cast<const uint32_t*>(code.data()));
      vk::ShaderModule shaderModule = device.createShaderModule(shaderModuleInfo);
  
      return shaderModule;
  }
  
  void VulkanEngine::createRenderPass() {
      vk::AttachmentDescription attachmentDescription(
          vk::AttachmentDescriptionFlags(), 
          surfaceFormat.format,               // format
          vk::SampleCountFlagBits::e1,        // samples
          vk::AttachmentLoadOp::eClear,       // LoadOp
          vk::AttachmentStoreOp::eStore,      // StoreOp
          vk::AttachmentLoadOp::eDontCare,    // StencilLoadOp
          vk::AttachmentStoreOp::eDontCare,   // StencilStoreOp
          vk::ImageLayout::eUndefined,        // InitialLayout
          vk::ImageLayout::ePresentSrcKHR     // finalLayout
      );
  
      vk::AttachmentReference colorAttachmentRef(0, vk::ImageLayout::eColorAttachmentOptimal);
  
      vk::SubpassDescription subpassDescription(
          vk::SubpassDescriptionFlags(),
          vk::PipelineBindPoint::eGraphics,
          nullptr,                            // inputAttachment
          colorAttachmentRef,                 // colorAttachment
          nullptr,                            // resolveAttachment
          nullptr,                            // depthStencilAttachment
          nullptr                             // preserveAttachment
      );
  
      try {
          vk::RenderPassCreateInfo renderPassInfo(vk::RenderPassCreateFlags(), attachmentDescription, subpassDescription, nullptr);
          renderPass = device.createRenderPass(renderPassInfo);
      }
      catch(const std::exception& e) {
          std::cout << "Render pass exception: " << e.what() << '\n';
          exit(1);
      }
  }
  
  void VulkanEngine::createGraphicsPipeline() { 
      std::string vertShaderPath = currentWorkingDir + "\\shaders\\vert.spv";
      std::string fragShaderPath = currentWorkingDir + "\\shaders\\frag.spv";
   
      auto vertShaderCode = readFile(vertShaderPath);
      auto fragShaderCode = readFile(fragShaderPath);
  
      vk::ShaderModule vertShaderModule = createShaderModule(vertShaderCode);
      vk::ShaderModule fragShaderModule = createShaderModule(fragShaderCode);
  
      try {
          std::array<vk::PipelineShaderStageCreateInfo, 2> shaderStageInfos = {
              vk::PipelineShaderStageCreateInfo(vk::PipelineShaderStageCreateFlags(), vk::ShaderStageFlagBits::eVertex, vertShaderModule, "main"),
              vk::PipelineShaderStageCreateInfo(vk::PipelineShaderStageCreateFlags(), vk::ShaderStageFlagBits::eFragment, fragShaderModule, "main")
          };
  
          vk::PipelineVertexInputStateCreateInfo vertexInputStateInfo(vk::PipelineVertexInputStateCreateFlags(), nullptr, nullptr);
  
          vk::PipelineInputAssemblyStateCreateInfo inputAssemblyStateInfo(
              vk::PipelineInputAssemblyStateCreateFlags(), 
              vk::PrimitiveTopology::eTriangleList, 
              false
          );
  
          vk::Viewport viewport{0.0f, 0.0f, (float)swapchainExtent.width, (float)swapchainExtent.height, 0.0f, 1.0f};
          vk::Rect2D scissor{vk::Offset2D(0, 0), swapchainExtent};
          vk::PipelineViewportStateCreateInfo viewportStateInfo(vk::PipelineViewportStateCreateFlags(), viewport, scissor);
  
          vk::PipelineRasterizationStateCreateInfo rasterizationStateInfo(
              vk::PipelineRasterizationStateCreateFlags(), 
              false, 
              false, 
              vk::PolygonMode::eFill, 
              vk::CullModeFlagBits::eBack, 
              vk::FrontFace::eClockwise, 
              false, 
              0.0f, 
              0.0f, 
              0.0f, 
              1.0f
          );
  
          vk::PipelineMultisampleStateCreateInfo multisampleStateInfo(
              vk::PipelineMultisampleStateCreateFlags(), 
              vk::SampleCountFlagBits::e1, 
              false, 
              0.0f, 
              nullptr, 
              false, 
              false
          );
  
          std::array<float, 4> blendConstantsValue = {0.0f, 0.0f, 0.0f, 0.0f};
          vk::ColorComponentFlags colorWriteMaskFlags(vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA);
          vk::PipelineColorBlendAttachmentState colorBlendAttachmentState(
              false,                  // blendEnable
              vk::BlendFactor::eOne,  // srcColorBlendFactor
              vk::BlendFactor::eZero, // dstColorBlendFactor
              vk::BlendOp::eAdd,      // colorBlendOp
              vk::BlendFactor::eOne,  // srcAlphaBlendFactor
              vk::BlendFactor::eZero, // dstAlphaBlendFactor
              vk::BlendOp::eAdd,      // alphaBlendOp
              colorWriteMaskFlags     // colorWriteMask
          );
          vk::PipelineColorBlendStateCreateInfo colorBlendStateInfo(
              vk::PipelineColorBlendStateCreateFlags(), 
              false, 
              vk::LogicOp::eCopy, 
              colorBlendAttachmentState, 
              blendConstantsValue
          );
  
          vk::PipelineLayoutCreateInfo pipelineLayoutInfo(vk::PipelineLayoutCreateFlags(), nullptr, nullptr);
          pipelineLayout = device.createPipelineLayout(pipelineLayoutInfo);
  
          vk::GraphicsPipelineCreateInfo graphicsPipelineInfo(
              vk::PipelineCreateFlags(), 
              shaderStageInfos, 
              &vertexInputStateInfo, 
              &inputAssemblyStateInfo, 
              nullptr,                    // TesselationStateCreateInfo
              &viewportStateInfo, 
              &rasterizationStateInfo, 
              &multisampleStateInfo, 
              nullptr,                    // DepthStencilCreateInfo
              &colorBlendStateInfo,
              nullptr,                    // DynamicStateCreateInfo
              pipelineLayout,
              renderPass,
              0,                          // subpass
              nullptr,                    // basePipelineHandle
              -1                          // basePipelineIndex
          );
  
          graphicsPipeline = device.createGraphicsPipeline(nullptr, graphicsPipelineInfo);
      }
      catch(const std::exception& e) {
          std::cout << "graphics pipeline creation exception: " << e.what() << '\n';
          exit(1);
      }
  
      device.destroyShaderModule(vertShaderModule);
      device.destroyShaderModule(fragShaderModule);
  }
  
  void VulkanEngine::createFramebuffers() {
      std::array<vk::ImageView, 1> attachments;
      framebuffers.reserve(swapchainImageViews.size());
  
      try {
          for(size_t i = 0; i < swapchainImageViews.size(); ++i) {
              attachments[0] = {swapchainImageViews[i]};
              vk::FramebufferCreateInfo framebufferInfo(
                  vk::FramebufferCreateFlags(), 
                  renderPass, 
                  attachments, 
                  swapchainExtent.width, 
                  swapchainExtent.height, 
                  1
              );
  
              framebuffers.push_back(device.createFramebuffer(framebufferInfo));
          }
      }
      catch(const std::exception& e) {
          throw std::runtime_error(e.what());
      }
  }
  
  