From a1ef6eb8bdacc3ba398aa87920e1ea642a083864 Mon Sep 17 00:00:00 2001 From: Emil Shanaty <65846814+emil28092005@users.noreply.github.com> Date: Sat, 8 Feb 2025 12:22:40 +0300 Subject: [PATCH] Initial commit --- .gitignore | 46 ++ .gitmodules | 18 + CMakeLists.txt | 13 + LICENSE | 21 + README.md | 57 +++ compile_shaders.bat | 2 + compile_shaders.sh | 6 + external/Vulkan-Headers | 1 + external/cxxopts | 1 + external/glfw | 1 + external/glm | 1 + external/tinygltf | 1 + models | 1 + shaders/shader.frag | 9 + shaders/shader.vert | 20 + src/main.cpp | 900 ++++++++++++++++++++++++++++++++++++++++ 16 files changed, 1098 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 README.md create mode 100755 compile_shaders.bat create mode 100755 compile_shaders.sh create mode 160000 external/Vulkan-Headers create mode 160000 external/cxxopts create mode 160000 external/glfw create mode 160000 external/glm create mode 160000 external/tinygltf create mode 160000 models create mode 100644 shaders/shader.frag create mode 100644 shaders/shader.vert create mode 100644 src/main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..05c6741 --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# CLion +.idea/ + +# Vscode +.vscode/ + +# CMake folders +cmake-build-debug/ +cmake-build-release/ +build/ + +# SPIR-v +*.spv \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..ebabb28 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,18 @@ +[submodule "external/Vulkan-Headers"] + path = external/Vulkan-Headers + url = https://github.com/KhronosGroup/Vulkan-Headers.git +[submodule "external/glfw"] + path = external/glfw + url = https://github.com/glfw/glfw.git +[submodule "external/glm"] + path = external/glm + url = https://github.com/g-truc/glm.git +[submodule "external/cxxopts"] + path = external/cxxopts + url = https://github.com/jarro2783/cxxopts.git +[submodule "external/tinygltf"] + path = external/tinygltf + url = https://github.com/syoyo/tinygltf +[submodule "models"] + path = models + url = https://github.com/KhronosGroup/glTF-Sample-Assets.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..cce7326 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.26) +project(acg_in_gd_lab) + +set(CMAKE_CXX_STANDARD 17) + +add_subdirectory(external/glfw) +add_subdirectory(external/glm) + +find_package(Vulkan REQUIRED) + +add_executable(acg_in_gd_lab src/main.cpp) +target_link_libraries(acg_in_gd_lab PRIVATE glfw glm::glm ${Vulkan_LIBRARIES}) +target_include_directories(acg_in_gd_lab PRIVATE ${Vulkan_INCLUDE_DIR}) \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..126c78f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Ivan Belyavtsev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f726707 --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# Advanced Computer Graphics in Game Development + +A template repository for Advanced Computer Graphics in Game Development course. + +## Pre-requirements + +- Version control: [Git](https://git-scm.com) +- Build system: [Cmake](https://cmake.org) +- C++17 compatible compiler: + - MSVC on Windows + - Clang on MacOS + - GCC on Linux +- Code editor: [VSCode](https://code.visualstudio.com/Download) +- [Vulkan SDK](https://www.lunarg.com/vulkan-sdk/) + +## How to build the project + +Use `git clone --recursive` to clone the repo with submodules or run `git submodule update --init --recursive` after the first clone. + +Go to the project folder and run the next command: + +```shell +mkdir build +cd build +cmake .. +cmake --build . +``` + +## How to compile shaders + +On Windows: + +```bat +compile_shaders.bat +``` + +On Linux or MacOS: + +```shell +./compile_shaders.sh +``` + +## How to run + +```shell +build/acg_in_gd_lab +``` + +## Third-party tools and data + +- [Vulkan-Headers](https://github.com/KhronosGroup/Vulkan-Headers) (Apache 2.0, MIT licenses) +- [GLFW](https://www.glfw.org) (Zlib license) +- [GLM](https://glm.g-truc.net) (Modified MIT license) +- [Cxxopts](https://github.com/jarro2783/cxxopts) (MIT license) +- [STB](https://github.com/nothings/stb) (Public domain, MIT licenses) +- [TinyGLTF](https://github.com/syoyo/tinygltf) (MIT license) +- [glTF Sample Assets](https://github.com/KhronosGroup/glTF-Sample-Assets) (CC-BY 4.0 International) diff --git a/compile_shaders.bat b/compile_shaders.bat new file mode 100755 index 0000000..8e534bd --- /dev/null +++ b/compile_shaders.bat @@ -0,0 +1,2 @@ +glslc.exe shaders\shader.vert -o shaders\vert.spv +glslc.exe shaders\shader.frag -o shaders\frag.spv diff --git a/compile_shaders.sh b/compile_shaders.sh new file mode 100755 index 0000000..e1c33a1 --- /dev/null +++ b/compile_shaders.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -ex + +glslc shaders/shader.vert -o shaders/vert.spv +glslc shaders/shader.frag -o shaders/frag.spv \ No newline at end of file diff --git a/external/Vulkan-Headers b/external/Vulkan-Headers new file mode 160000 index 0000000..39f924b --- /dev/null +++ b/external/Vulkan-Headers @@ -0,0 +1 @@ +Subproject commit 39f924b810e561fd86b2558b6711ca68d4363f68 diff --git a/external/cxxopts b/external/cxxopts new file mode 160000 index 0000000..7795b6a --- /dev/null +++ b/external/cxxopts @@ -0,0 +1 @@ +Subproject commit 7795b6a47b7af268ed64ce901fa1f2d9f1bee6d3 diff --git a/external/glfw b/external/glfw new file mode 160000 index 0000000..e7ea71b --- /dev/null +++ b/external/glfw @@ -0,0 +1 @@ +Subproject commit e7ea71be039836da3a98cea55ae5569cb5eb885c diff --git a/external/glm b/external/glm new file mode 160000 index 0000000..69b130c --- /dev/null +++ b/external/glm @@ -0,0 +1 @@ +Subproject commit 69b130c162e6266e07392741bd04feacc55dcda2 diff --git a/external/tinygltf b/external/tinygltf new file mode 160000 index 0000000..a5e653e --- /dev/null +++ b/external/tinygltf @@ -0,0 +1 @@ +Subproject commit a5e653e46cca64bbc0ecee7ede007347a94c39b0 diff --git a/models b/models new file mode 160000 index 0000000..b40880a --- /dev/null +++ b/models @@ -0,0 +1 @@ +Subproject commit b40880a4823deb041cce26fac4b55af662ceded3 diff --git a/shaders/shader.frag b/shaders/shader.frag new file mode 100644 index 0000000..13009da --- /dev/null +++ b/shaders/shader.frag @@ -0,0 +1,9 @@ +#version 450 + +layout(location = 0) in vec3 fragColor; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} \ No newline at end of file diff --git a/shaders/shader.vert b/shaders/shader.vert new file mode 100644 index 0000000..f4f4c7d --- /dev/null +++ b/shaders/shader.vert @@ -0,0 +1,20 @@ +#version 450 + +layout(location = 0) out vec3 fragColor; + +vec2 positions[3] = vec2[]( + vec2(0.0, -0.5), + vec2(0.5, 0.5), + vec2(-0.5, 0.5) +); + +vec3 colors[3] = vec3[] ( + vec3(1.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + vec3(0.0, 0.0, 1.0) +); + +void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + fragColor = colors[gl_VertexIndex]; +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..45e59dc --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,900 @@ +#define GLFW_INCLUDE_VULKAN + +#include + +#include +#include +#include +#include +#include + + +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT *pCreateInfo, + const VkAllocationCallbacks *pAllocator, + VkDebugUtilsMessengerEXT *pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, + const VkAllocationCallbacks *pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, + "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; + + bool isAdequate() const { + return !formats.empty() && !presentModes.empty(); + } + + VkSurfaceFormatKHR chooseSwapSurfaceFormat() { + for (const auto& format : formats) { + if (format.format == VK_FORMAT_B8G8R8A8_SRGB && + format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return format; + } + } + if (!formats.empty()) + return formats[0]; + return {VK_FORMAT_UNDEFINED, VK_COLOR_SPACE_MAX_ENUM_KHR}; + } + + VkPresentModeKHR chooseSwapPresentMode() { + for (const auto& presentMode : presentModes) { + if (presentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return presentMode; + } + } + + return VK_PRESENT_MODE_IMMEDIATE_KHR; + } + + VkExtent2D chooseSwapExtent(GLFWwindow *window) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } + + int width, height; + glfwGetFramebufferSize(window, &width, &height); + VkExtent2D actualExtent { + static_cast(width), + static_cast(height) + }; + actualExtent.width = std::clamp(actualExtent.width, + capabilities.minImageExtent.width, + capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(actualExtent.height, + capabilities.minImageExtent.height, + capabilities.maxImageExtent.height); + return actualExtent; + } + + uint32_t chooseImageCount() { + uint32_t imageCount = capabilities.minImageCount + 1; + if (capabilities.maxImageCount > 0 && imageCount > capabilities.maxImageCount) { + imageCount = capabilities.maxImageCount; + } + return imageCount; + } + +}; + +static std::vector readFile(const std::filesystem::path & filepath) { + std::ifstream file(filepath, std::ios::ate | std::ios::binary); + if (!file.is_open()) { + throw std::runtime_error(std::string("failed to open a file: ") + filepath.string()); + } + + auto fileSize = file.tellg(); + std::vector buffer(fileSize); + file.seekg(0); + file.read(buffer.data(), fileSize); + file.close(); + + return buffer; +} + +class HelloTriangleApplication { +private: + const int WIDTH = 800; + const int HEIGHT = 600; + const std::string appName = "Vulkan on MacOS"; + const std::string engineName = "The Best Engine"; + const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + "VK_KHR_portability_subset", + }; + const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" + }; + +#ifdef NDEBUG + const bool enableValidationLayers = false; +#else + const bool enableValidationLayers = true; +#endif + + GLFWwindow *window = nullptr; + + VkInstance instance = VK_NULL_HANDLE; + VkDebugUtilsMessengerEXT debugMessenger = VK_NULL_HANDLE; + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device = VK_NULL_HANDLE; + VkQueue graphicsQueue = VK_NULL_HANDLE; + VkQueue presentQueue = VK_NULL_HANDLE; + VkSurfaceKHR surface = VK_NULL_HANDLE; + VkSwapchainKHR swapChain = VK_NULL_HANDLE; + std::vector swapChainImages; + VkFormat swapChainImageFormat = VK_FORMAT_UNDEFINED; + VkExtent2D swapChainExtent = {}; + std::vector swapChainImageViews; + VkRenderPass renderPass = VK_NULL_HANDLE; + VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; + VkPipeline graphicsPipeline = VK_NULL_HANDLE; + std::vector swapChainFramebuffers; + VkCommandPool commandPool = VK_NULL_HANDLE; + VkCommandBuffer commandBuffer = VK_NULL_HANDLE; + VkSemaphore imageAvailableSemaphore = VK_NULL_HANDLE; + VkSemaphore renderFinishedSemaphore = VK_NULL_HANDLE; + VkFence inFlightFence = VK_NULL_HANDLE; + +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + void initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow( + WIDTH, HEIGHT, + appName.c_str(), + nullptr, nullptr); + } + + void initVulkan() { + createInstance(); + setupDebugMessenger(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createCommandBuffer(); + createSyncObjects(); + } + + bool checkValidationLayerSupport() { + uint32_t layerCount = 0; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const auto &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; + } + + std::vector getRequiredExtensions() const { + uint32_t glfwExtensionCount = 0; + const char **glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if (enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); + + return extensions; + } + + static void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT &createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.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; + createInfo.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; + createInfo.pfnUserCallback = debugCallback; + } + + void createInstance() { + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pNext = nullptr; + appInfo.pApplicationName = appName.c_str(); + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = engineName.c_str(); + 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; + + auto requiredExtensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = requiredExtensions.size(); + createInfo.ppEnabledExtensionNames = requiredExtensions.data(); + createInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; + + createInfo.enabledLayerCount = 0; + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if (enableValidationLayers) { + if (!checkValidationLayerSupport()) { + throw std::runtime_error("validation layers are requested, but not available"); + } + createInfo.enabledLayerCount = validationLayers.size(); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + populateDebugMessengerCreateInfo(debugCreateInfo); + createInfo.pNext = &debugCreateInfo; + } + + if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { + throw std::runtime_error("failed to create an instance"); + } + + uint32_t extensionCount = 0; + vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); + std::vector extensions(extensionCount); + vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data()); + std::cout << "available extensions:\n"; + + for (const auto &extension: extensions) { + std::cout << '\t' << extension.extensionName << '\n'; + } + + } + + 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; + } + + void setupDebugMessenger() { + if (!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger"); + } + } + + void createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { + throw std::runtime_error("failed to create window surface"); + } + } + + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice dev) { + SwapChainSupportDetails details{}; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(dev, surface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(dev, surface, &formatCount, nullptr); + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(dev, surface, &formatCount, details.formats.data()); + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(dev, surface, &presentModeCount, nullptr); + details.presentModes.resize(formatCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(dev, surface, &presentModeCount, details.presentModes.data()); + + return details; + } + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice dev) { + QueueFamilyIndices indices; + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(dev, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(dev, &queueFamilyCount, queueFamilies.data()); + + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + VkBool32 presentSupport = false; + vkGetPhysicalDeviceSurfaceSupportKHR(dev, i, surface, &presentSupport); + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.isComplete()) { + break; + } + + i++; + } + + return indices; + } + + bool checkDeviceExtensionSupport(VkPhysicalDevice dev) { + uint32_t extensionCount; + vkEnumerateDeviceExtensionProperties(dev, nullptr, + &extensionCount, nullptr); + std::vector availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(dev, nullptr, + &extensionCount, availableExtensions.data()); + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); + } + + void pickPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); + if (deviceCount == 0) { + throw std::runtime_error("failed to find a Vulkan-supporting GPU"); + } + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); + + auto isDeviceSuitable = [&](VkPhysicalDevice dev) { + VkPhysicalDeviceProperties deviceProperties; + vkGetPhysicalDeviceProperties(dev, &deviceProperties); + std::cout << "device: " << deviceProperties.deviceName; + + bool hasQueues = findQueueFamilies(dev).isComplete(); + std::cout << " has queues: " << hasQueues; + bool isSupportExtensions = checkDeviceExtensionSupport(dev); + std::cout << ", supports extensions: " << isSupportExtensions; + bool swapChainAdequate = false; + if (isSupportExtensions) { + swapChainAdequate = querySwapChainSupport(dev).isAdequate(); + } + std::cout << ", swapchain is not empty: " << swapChainAdequate << "\n"; + return hasQueues && isSupportExtensions && swapChainAdequate; + }; + + for (const auto &dev: devices) { + if (isDeviceSuitable(dev)) { + physicalDevice = dev; + break; + } + } + + if (physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("can't find a suitable physical device"); + } + } + + void createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + + const std::set uniqueQueueFamilies = { + indices.graphicsFamily.value(), + indices.presentFamily.value() + }; + std::vector queueCreateInfos; + float queuePriority = 1.0f; + + for (uint32_t queueFamily : uniqueQueueFamilies){ + VkDeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back(queueCreateInfo); + } + + VkPhysicalDeviceFeatures deviceFeatures{}; + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + createInfo.queueCreateInfoCount = queueCreateInfos.size(); + createInfo.pEnabledFeatures = &deviceFeatures; + createInfo.enabledExtensionCount = deviceExtensions.size(); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + createInfo.enabledLayerCount = 0; + + if (enableValidationLayers) { + createInfo.enabledLayerCount = validationLayers.size(); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } + if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { + throw std::runtime_error("failed to create logical device!"); + } + vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); + vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); + } + + void createSwapChain() { + auto swapChinSupport = querySwapChainSupport(physicalDevice); + + auto surfaceFormat = swapChinSupport.chooseSwapSurfaceFormat(); + auto presentMode = swapChinSupport.chooseSwapPresentMode(); + auto extent = swapChinSupport.chooseSwapExtent(window); + uint32_t imageCount = swapChinSupport.chooseImageCount(); + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surface; + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + createInfo.preTransform = swapChinSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + createInfo.oldSwapchain = VK_NULL_HANDLE; + + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } + + if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { + throw std::runtime_error("failed to create a swap chain"); + } + + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); + swapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); + + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; + } + + void createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + + for (uint32_t i = 0; i < swapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = swapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = swapChainImageFormat; + 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 = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create na image view"); + } + } + } + + VkShaderModule createShaderModule(const std::vector& code) { + VkShaderModuleCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + + VkShaderModule shaderModule; + if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("failed to create a shader module"); + } + return shaderModule; + } + + void createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_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_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + 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; + + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; + + if(vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { + throw std::runtime_error("failed to create a render pass"); + } + } + + void createGraphicsPipeline() { + auto vertShaderCode = readFile("shaders/vert.spv"); + auto fragShaderCode = readFile("shaders/frag.spv"); + + VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); + VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + VkPipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + + VkPipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + + std::vector shaderStages = { + vertShaderStageInfo, + fragShaderStageInfo + }; + + VkPipelineVertexInputStateCreateInfo vertexInputState{}; + vertexInputState.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputState.vertexBindingDescriptionCount = 0; + vertexInputState.vertexAttributeDescriptionCount = 0; + + VkPipelineInputAssemblyStateCreateInfo inputAssemblyState{}; + inputAssemblyState.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssemblyState.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssemblyState.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizerState{}; + rasterizerState.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizerState.depthClampEnable = VK_FALSE; + rasterizerState.rasterizerDiscardEnable = VK_FALSE; + rasterizerState.polygonMode = VK_POLYGON_MODE_FILL; + rasterizerState.lineWidth = 1.0f; + rasterizerState.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizerState.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizerState.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisamplingState{}; + multisamplingState.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisamplingState.sampleShadingEnable = VK_FALSE; + multisamplingState.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + 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 colorBlendState{}; + colorBlendState.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlendState.logicOpEnable = VK_FALSE; + colorBlendState.logicOp = VK_LOGIC_OP_COPY; + colorBlendState.attachmentCount = 1; + colorBlendState.pAttachments = &colorBlendAttachment; + colorBlendState.blendConstants[0] = 0.0f; + colorBlendState.blendConstants[1] = 0.0f; + colorBlendState.blendConstants[2] = 0.0f; + colorBlendState.blendConstants[3] = 0.0f; + + std::vector dynamicStates = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipelineLayoutInfo.setLayoutCount = 0; + pipelineLayoutInfo.pushConstantRangeCount = 0; + + if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &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 = shaderStages.size(); + pipelineInfo.pStages = shaderStages.data(); + pipelineInfo.pVertexInputState = &vertexInputState; + pipelineInfo.pInputAssemblyState = &inputAssemblyState; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizerState; + pipelineInfo.pMultisampleState = &multisamplingState; + pipelineInfo.pDepthStencilState = nullptr; + pipelineInfo.pColorBlendState = &colorBlendState; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + pipelineInfo.basePipelineIndex = -1; + + if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("failed to create a graphics pipeline"); + } + + vkDestroyShaderModule(device, fragShaderModule, nullptr); + vkDestroyShaderModule(device, vertShaderModule, nullptr); + } + + void createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); + for (uint8_t i = 0; i < swapChainImageViews.size(); i++) { + std::vector attachments = { + swapChainImageViews[i] + }; + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = attachments.size(); + framebufferInfo.pAttachments = attachments.data(); + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("failed to create a framebuffer"); + } + } + } + + void createCommandPool() { + auto queueFamilyIndices = findQueueFamilies(physicalDevice); + + VkCommandPoolCreateInfo poolInfo{}; + poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { + throw std::runtime_error("failed to create a command pool"); + } + } + + void createCommandBuffer() { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = 1; + + if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to allocate a command buffer"); + } + } + + void 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; + + if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphore) != VK_SUCCESS || + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphore) != VK_SUCCESS || + vkCreateFence(device, &fenceInfo, nullptr, &inFlightFence) != VK_SUCCESS) { + throw std::runtime_error("failed to create semaphores!"); + } + } + + void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(commandBuffer, &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 = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = swapChainExtent; + VkClearValue clearColor = {{{0.0f, 0.0f, 0.4f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; + + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = static_cast(swapChainExtent.width); + viewport.height = static_cast(swapChainExtent.height); + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = swapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + vkCmdDraw(commandBuffer, 3, 1, 0, 0); + + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("failed to record a command buffer"); + } + } + + void drawFrame() { + vkWaitForFences(device, 1, &inFlightFence, VK_TRUE, UINT64_MAX); + vkResetFences(device, 1, &inFlightFence); + + uint32_t imageIndex; + vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); + + vkResetCommandBuffer(commandBuffer, 0); + recordCommandBuffer(commandBuffer, imageIndex); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + std::vector waitSemaphores = {imageAvailableSemaphore}; + std::vector waitStages = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + std::vector commandBuffers = {commandBuffer}; + std::vector signalSemaphores = {renderFinishedSemaphore}; + submitInfo.waitSemaphoreCount = waitSemaphores.size(); + submitInfo.pWaitSemaphores = waitSemaphores.data(); + submitInfo.pWaitDstStageMask = waitStages.data(); + submitInfo.commandBufferCount = commandBuffers.size(); + submitInfo.pCommandBuffers = commandBuffers.data(); + submitInfo.signalSemaphoreCount = signalSemaphores.size(); + submitInfo.pSignalSemaphores = signalSemaphores.data(); + + if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFence) != VK_SUCCESS) { + throw std::runtime_error("failed to submit draw command buffer!"); + } + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = signalSemaphores.size(); + presentInfo.pWaitSemaphores = signalSemaphores.data(); + + std::vector swapChains = {swapChain}; + presentInfo.swapchainCount = swapChains.size(); + presentInfo.pSwapchains = swapChains.data(); + presentInfo.pImageIndices = &imageIndex; + + vkQueuePresentKHR(presentQueue, &presentInfo); + } + + void mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + drawFrame(); + } + } + + void cleanup() { + vkDeviceWaitIdle(device); + vkDestroySemaphore(device, imageAvailableSemaphore, nullptr); + vkDestroySemaphore(device, renderFinishedSemaphore, nullptr); + vkDestroyFence(device, inFlightFence, nullptr); + vkDestroyCommandPool(device, commandPool, nullptr); + for (auto framebuffer : swapChainFramebuffers) { + vkDestroyFramebuffer(device, framebuffer, nullptr); + } + vkDestroyPipeline(device, graphicsPipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyRenderPass(device, renderPass, nullptr); + for (auto imageView : swapChainImageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + vkDestroySwapchainKHR(device, swapChain, nullptr); + vkDestroyDevice(device, nullptr); + + if (enableValidationLayers) { + DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); + } + + vkDestroySurfaceKHR(instance, surface, nullptr); + vkDestroyInstance(instance, nullptr); + + glfwDestroyWindow(window); + + glfwTerminate(); + } +}; + +int main() { + HelloTriangleApplication app; + + try { + app.run(); + } catch (const std::exception &e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} \ No newline at end of file