c++windowswinapivulkan

Window resizing does not cause VK_ERROR_OUT_OF_DATE_KHR


I use my own Win32 implementation together with Vulkan. I have the following test function that resizes my window.

void Window::Test()
{
    RECT rect = { 0, 0, 1200, 1200 };
    DWORD style = WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU | CS_OWNDC | WS_MAXIMIZEBOX;
    DWORD exStyle = WS_EX_APPWINDOW | WS_EX_TOPMOST;
    AdjustWindowRectExForDpi(&rect, style, false, exStyle, GetDpiForWindow(windowHandle));

    SetWindowPos(windowHandle, HWND_TOP, 0, 0, rect.right - rect.left, rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOZORDER);
}

Doing this does resize the window, but both acquireNextImageKHR and presentKHR return vk::eSuccess still. This causes the swap chain to be invalid but not recreated, leading to a crash when I call presentKHR the frame after the window resize.

Acquire image code:

    uint32_t swapChainImageIndex;
    {
        vk::Result acquireResult = device.acquireNextImageKHR(swapchain, UINT64_MAX, *imageAvailableSemaphores[currentFrameIndex], nullptr, &swapChainImageIndex);

        if(acquireResult == vk::Result::eErrorOutOfDateKHR)
        {
            //Recreate swapchain here
            // * DOES NOT GET HIT *
            return;
        }
        else if(acquireResult != vk::Result::eSuccess && acquireResult != vk::Result::eSuboptimalKHR)
        {
            //Crash
            // * DOES NOT GET HIT *
        }
    }

Present code:

    //Below line crashes after window is resized
    vk::Result presentResult = presentQueue.presentKHR(presentInfo);
    if(presentResult == vk::Result::eErrorOutOfDateKHR || presentResult == vk::Result::eSuboptimalKHR)
    {
        //Recreate swapchain here
        // * DOES NOT GET HIT *
    }
    else if(presentResult != vk::Result::eSuccess)
    {
        //Crash
        // * DOES NOT GET HIT *
    }

I have a control project that uses GLFW. When I resize my window in that project the result does become VK_ERROR_OUT_OF_DATE_KHR. I am able to set a bool that tracks if the window is resized manually, but I believe that should not be necessary since the control project does not require such a bool or callback.

I have tried returning 0 in what I believe to be the relevant window callback like this:

case WM_SIZE:
{
    return 0;
}
case WM_SIZING:
{
    return 0;
}

This doesn't seem to affect anything, and I still get the same crash.


Solution

  • Found the issue! When the result is not a success and exceptions are enabled in Vulkan-hpp it seems to silently ignore the error and give a success instead. Seems like very weird behavior, but possible to fix by disabling exceptions.

    Code to demo for those curious. Scroll to the bottom for the most interesting parts.

    #include <chrono>
    #include <fstream>
    #include <iostream>
    #include <optional>
    #include <set>
    #include <vulkan/vulkan.hpp>
    #include <Windows.h>
    
    const char* requestedPhysicalExtension = VK_KHR_SURFACE_EXTENSION_NAME;
    const char* requestedLogicalDeviceExtension = VK_KHR_SWAPCHAIN_EXTENSION_NAME;
    
    HINSTANCE hInstance;
    HWND windowHandle;
    bool running = true;
    vk::UniqueInstance vulkanInstance;
    vk::SurfaceKHR vulkanSurface;
    vk::PhysicalDevice physicalDevice;
    vk::UniqueDevice logicalDevice;
    std::optional<uint32_t> graphicsFamily;
    std::optional<uint32_t> presentFamily;
    vk::Queue graphicsQueue;
    vk::Queue presentQueue;
    vk::UniqueHandle<vk::DebugUtilsMessengerEXT, vk::detail::DispatchLoaderDynamic> debugMessenger;
    vk::detail::DispatchLoaderDynamic vulkanLoaderDynamic;
    PFN_vkGetInstanceProcAddr getInstanceProcAddr = nullptr;
    
    vk::UniqueSwapchainKHR swapChain;
    std::vector<vk::Image> swapChainImages;
    std::vector<vk::UniqueImageView> swapChainImageViews;
    vk::Extent2D swapChainExtent;
    vk::Format swapChainImageFormat;
    
    vk::UniqueRenderPass renderPass;
    std::vector<vk::UniqueFramebuffer> framebuffers;
    
    vk::UniqueCommandPool commandPool;
    std::vector<vk::UniqueCommandBuffer> commandBuffers;
    
    vk::UniquePipeline graphicsPipeline;
    
    vk::UniqueSemaphore imageAvailableSemaphore;
    vk::UniqueSemaphore renderFinishedSemaphore;
    vk::UniqueFence inFlightFence;
    
    const char* enabledLayerName = "VK_LAYER_KHRONOS_validation";
    
    struct SwapChainSupportDetails
    {
        vk::SurfaceCapabilitiesKHR capabilities;
        std::vector<vk::SurfaceFormatKHR> formats;
        std::vector<vk::PresentModeKHR> presentModes;
    };
    
    vk::Bool32 DebugMessengerCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT severity,
                                      vk::DebugUtilsMessageTypeFlagsEXT types, vk::DebugUtilsMessengerCallbackDataEXT const* callbackData,
                                      void* userData)
    {
        switch(severity)
        {
            using SeverityBits = vk::DebugUtilsMessageSeverityFlagBitsEXT;
            case SeverityBits::eVerbose:
                std::cout << "Vulkan Validation Layer" << callbackData->pMessage << std::endl;
            break;
            case SeverityBits::eInfo:
                std::cout << "Vulkan Validation Layer" << callbackData->pMessage << std::endl;
            break;
            case SeverityBits::eWarning:
                std::cerr << "Vulkan Validation Layer" << callbackData->pMessage << std::endl;
            break;
            case SeverityBits::eError:
                std::cerr << "Vulkan Validation Layer" << callbackData->pMessage << std::endl;
            break;
        }
    
        return VK_TRUE;
    }
    
    LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        switch (message)
        {
            case WM_CLOSE:
            {
                DestroyWindow(hWnd);
                break;
            }
            case WM_DESTROY:
            {
                PostQuitMessage(0);
                return 0;
            }
            case WM_CANCELMODE:
            {
                return 0;
            }
        }
    
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    
    void InitWindow()
    {
        WNDCLASS wndClass {};
        wndClass.lpszClassName = L"Test";
        wndClass.hInstance = hInstance;
        wndClass.hIcon = LoadIcon(nullptr, IDI_WINLOGO);
        wndClass.hCursor = LoadCursor(nullptr, IDC_ARROW);
        wndClass.lpfnWndProc = WindowProcedure;
        wndClass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    
        RegisterClass(&wndClass);
    
        DWORD style = WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU | WS_MAXIMIZEBOX | WS_VISIBLE;
    
        RECT rect;
        rect.left = 250;
        rect.top= 250;
        rect.right = rect.left + 800;
        rect.bottom = rect.top + 600;
    
        AdjustWindowRect(&rect, style, false);
    
        windowHandle = CreateWindowEx(0,
            L"Test",
            L"Test Window",
            style,
            rect.left,
            rect.top,
            rect.right - rect.left,
            rect.bottom - rect.top,
            nullptr,
            nullptr,
            hInstance,
            nullptr);
    
        ShowWindow(windowHandle, SW_SHOW);
    }
    
    bool ProcessMessages()
    {
        MSG message;
    
        while (PeekMessage(&message, nullptr, 0u, 0u, PM_REMOVE))
        {
            if (message.message == WM_QUIT)
            {
                return false;
            }
    
            TranslateMessage(&message);
            DispatchMessage(&message);
        }
    
        return true;
    }
    
    void InitVulkan()
    {
        HMODULE platformHandle = LoadLibraryA("vulkan-1.dll");
        if(!platformHandle)
        {
            std::cerr << "Failed to load vulkan." << std::endl;
            exit(-1);
        }
    
        getInstanceProcAddr = reinterpret_cast<PFN_vkGetInstanceProcAddr>(GetProcAddress(platformHandle, "vkGetInstanceProcAddr"));
        if(!getInstanceProcAddr)
        {
            std::cerr << "Failed to load vulkan." << std::endl;
            exit(-1);
        }
        vulkanLoaderDynamic.init(getInstanceProcAddr);
        
        vk::ApplicationInfo appInfo("Test", VK_MAKE_API_VERSION(0, 1, 0, 0), "No Engine", VK_MAKE_API_VERSION(0, 1, 0, 0), VK_API_VERSION_1_0);
    
        std::vector<const char*> requiredExtensionNames = { VK_KHR_SURFACE_EXTENSION_NAME, "VK_KHR_win32_surface", VK_EXT_DEBUG_UTILS_EXTENSION_NAME };
        std::vector<vk::ExtensionProperties> availableExtensions = vk::enumerateInstanceExtensionProperties();
    
        size_t requiredExtensionsFound = 0;
        for(const char* requiredExtensionName : requiredExtensionNames)
        {
            for(const vk::ExtensionProperties& extension : availableExtensions)
            {
                if(strcmp(requiredExtensionName, extension.extensionName) == 0)
                {
                    requiredExtensionsFound++;
                }
            }
        }
    
        if(requiredExtensionsFound < requiredExtensionNames.size())
        {
            throw std::runtime_error("Not enough extensions found");
        }
    
        vk::DebugUtilsMessengerCreateInfoEXT debugLayerCreateInfo {};
        debugLayerCreateInfo.messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError;
        debugLayerCreateInfo.messageType =  vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation;
        debugLayerCreateInfo.pfnUserCallback = DebugMessengerCallback;
        vk::InstanceCreateInfo instanceCreateInfo({}, &appInfo, 1, &enabledLayerName, requiredExtensionNames.size(), requiredExtensionNames.data(), &debugLayerCreateInfo);
    
        vulkanInstance = vk::createInstanceUnique(instanceCreateInfo);
        vulkanLoaderDynamic.init(*vulkanInstance, getInstanceProcAddr);
    }
    
    void SetupDebugMessenger()
    {
        vk::DebugUtilsMessengerCreateInfoEXT createInfo {};
        createInfo.messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError;
        createInfo.messageType =  vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation;
        createInfo.pfnUserCallback = DebugMessengerCallback;
    
        debugMessenger = vulkanInstance->createDebugUtilsMessengerEXTUnique(createInfo, nullptr, vulkanLoaderDynamic);
    }
    
    void CreateSurface()
    {
        vk::Win32SurfaceCreateInfoKHR surfaceCreateInfo({}, hInstance, windowHandle);
        vulkanSurface = vulkanInstance->createWin32SurfaceKHR(surfaceCreateInfo);
    }
    
    void InitPhysicalDevice()
    {
        std::vector<vk::PhysicalDevice> vulkanPhysicalDevices = vulkanInstance->enumeratePhysicalDevices();
    
        for(const vk::PhysicalDevice& vulkanDevice : vulkanPhysicalDevices)
        {
            //Pick first dedicated GPU
            if(vulkanDevice.getProperties().deviceType == vk::PhysicalDeviceType::eDiscreteGpu)
            {
                physicalDevice = vulkanDevice;
                break;
            }
        }
    
        std::vector<vk::QueueFamilyProperties> queueFamilyProperties = physicalDevice.getQueueFamilyProperties();
        uint32_t i = 0;
        for(const vk::QueueFamilyProperties& queueFamily : queueFamilyProperties)
        {
            if(queueFamily.queueFlags & vk::QueueFlagBits::eGraphics)
                graphicsFamily = i;
    
            vk::Bool32 hasPresentSupport = physicalDevice.getSurfaceSupportKHR(i, vulkanSurface);
            
            if(hasPresentSupport)
                presentFamily = i;
    
            if(graphicsFamily && presentFamily)
                return;
            
            i++;
        }
    }
    
    void CreateSyncObjects()
    {
        vk::SemaphoreCreateInfo semaphoreCreateInfo;
        imageAvailableSemaphore = logicalDevice->createSemaphoreUnique(semaphoreCreateInfo);
        renderFinishedSemaphore = logicalDevice->createSemaphoreUnique(semaphoreCreateInfo);
        vk::FenceCreateInfo fenceCreateInfo(vk::FenceCreateFlagBits::eSignaled);
        inFlightFence = logicalDevice->createFenceUnique(fenceCreateInfo);
    }
    
    void CreateLogicalDevice()
    {
        std::set<uint32_t> uniqueQueueFamilies = {*graphicsFamily, *presentFamily};
        std::vector<vk::DeviceQueueCreateInfo> deviceQueueCreateInfos(uniqueQueueFamilies.size());
        float queuePriority = 1.0f;
        size_t i = 0;
        for(uint32_t queueFamily : uniqueQueueFamilies)
        {
            deviceQueueCreateInfos[i] = vk::DeviceQueueCreateInfo({}, queueFamily, 1, &queuePriority);
            i++;
        }
    
        vk::PhysicalDeviceFeatures deviceFeatures {};
    
        vk::DeviceCreateInfo deviceCreateInfo({}, static_cast<uint32_t>(deviceQueueCreateInfos.size()), deviceQueueCreateInfos.data(), 0, nullptr, 1, &requestedLogicalDeviceExtension, &deviceFeatures);
    
        logicalDevice = physicalDevice.createDeviceUnique(deviceCreateInfo);
    
        graphicsQueue = logicalDevice->getQueue(*graphicsFamily, 0);
        presentQueue = logicalDevice->getQueue(*presentFamily, 0);
    }
    
    vk::SurfaceFormatKHR PickSurfaceFormat(const std::vector<vk::SurfaceFormatKHR>& availableFormats)
    {
        for(const vk::SurfaceFormatKHR& availableFormat : availableFormats)
        {
            if(availableFormat.format == vk::Format::eB8G8R8A8Srgb && availableFormat.colorSpace == vk::ColorSpaceKHR::eVkColorspaceSrgbNonlinear)
                return availableFormat;
        }
    
        return availableFormats[0];
    }
    vk::PresentModeKHR PickPresentMode(const std::vector<vk::PresentModeKHR>& availablePresentModes)
    {
        for(const vk::PresentModeKHR& availablePresentMode : availablePresentModes)
        {
            if(availablePresentMode == vk::PresentModeKHR::eMailbox)
                return availablePresentMode;
        }
    
        return vk::PresentModeKHR::eFifo;
    }
    vk::Extent2D PickExtent(const vk::SurfaceCapabilitiesKHR& capabilities)
    {
        if(capabilities.currentExtent.width != UINT_MAX)
        {
            return capabilities.currentExtent;
        }
        
        vk::Extent2D actualExtent(800, 600);
    
        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;
    }
    
    vk::UniqueImageView CreateImageView(const vk::UniqueDevice& logicalDevice, const vk::Image& image, vk::Format format, vk::ImageAspectFlagBits aspectFlags, uint32_t mipLevels)
    {
        vk::ImageSubresourceRange subresourceRange(aspectFlags, 0, mipLevels, 0, 1);
        vk::ComponentMapping componentMapping;
        
        vk::ImageViewCreateInfo createInfo({}, image, vk::ImageViewType::e2D, format, componentMapping, subresourceRange);
    
        return logicalDevice->createImageViewUnique(createInfo);
    }
    
    void CreateSwapChain()
    {
        SwapChainSupportDetails details;
        details.capabilities = physicalDevice.getSurfaceCapabilitiesKHR(vulkanSurface);
        details.formats = physicalDevice.getSurfaceFormatsKHR(vulkanSurface);
        details.presentModes = physicalDevice.getSurfacePresentModesKHR(vulkanSurface);
    
        uint32_t imageCount = details.capabilities.minImageCount + 1;
    
        if(details.capabilities.maxImageCount > 0 && imageCount > details.capabilities.maxImageCount)
            imageCount = details.capabilities.maxImageCount;
        
        const vk::SurfaceFormatKHR surfaceFormat = PickSurfaceFormat(details.formats);
        swapChainImageFormat = surfaceFormat.format;
    
        swapChainExtent = PickExtent(details.capabilities);
        vk::PresentModeKHR presentMode = PickPresentMode(details.presentModes);
    
        const bool isGraphicsFamilyAlsoPresent = *graphicsFamily == *presentFamily;
        vk::SharingMode sharingMode;
        std::vector<uint32_t> queueFamilyIndices;
        if(isGraphicsFamilyAlsoPresent)
        {
            sharingMode = vk::SharingMode::eExclusive;
            queueFamilyIndices = { *graphicsFamily };
        }
        else
        {
            sharingMode = vk::SharingMode::eConcurrent;
            queueFamilyIndices = { *graphicsFamily, *presentFamily };
        }
    
        vk::SwapchainCreateInfoKHR createInfo({}, vulkanSurface, imageCount, surfaceFormat.format, surfaceFormat.colorSpace, swapChainExtent, 1, vk::ImageUsageFlagBits::eColorAttachment,
                                              sharingMode, static_cast<uint32_t>(queueFamilyIndices.size()), queueFamilyIndices.data(), details.capabilities.currentTransform, vk::CompositeAlphaFlagBitsKHR::eOpaque,
                                              presentMode, vk::True, nullptr);
    
        swapChain = logicalDevice->createSwapchainKHRUnique(createInfo);
    
        swapChainImages = logicalDevice->getSwapchainImagesKHR(*swapChain);
    
        swapChainImageViews.reserve(swapChainImages.size());
        for(const vk::Image& image : swapChainImages)
        {
            swapChainImageViews.emplace_back(CreateImageView(logicalDevice, image, surfaceFormat.format, vk::ImageAspectFlagBits::eColor, 1));
        }
    }
    
    void  CreateRenderPass()
    {
        //Color
        vk::AttachmentDescription colorAttachment({}, swapChainImageFormat, vk::SampleCountFlagBits::e1, vk::AttachmentLoadOp::eClear, vk::AttachmentStoreOp::eStore,
                                                  vk::AttachmentLoadOp::eDontCare, vk::AttachmentStoreOp::eDontCare, vk::ImageLayout::eUndefined, vk::ImageLayout::ePresentSrcKHR);
        vk::AttachmentReference colorAttachmentRef(0, vk::ImageLayout::eColorAttachmentOptimal);
    
        //Subpasses
        vk::SubpassDescription subpass({}, vk::PipelineBindPoint::eGraphics, 0, nullptr, 1, &colorAttachmentRef,
                                       nullptr, nullptr, 0, nullptr);
    
        std::vector<vk::AttachmentDescription> attachments = { colorAttachment };
        vk::RenderPassCreateInfo renderPassCreateInfo({}, static_cast<uint32_t>(attachments.size()), attachments.data(), 1, &subpass, 0, nullptr);
    
        renderPass = logicalDevice->createRenderPassUnique(renderPassCreateInfo);
    }
    
    void CreateCommandPool()
    {
        vk::CommandPoolCreateInfo createInfo(vk::CommandPoolCreateFlagBits::eResetCommandBuffer, *graphicsFamily);
        commandPool = logicalDevice->createCommandPoolUnique(createInfo);
    }
    
    void CreateCommandBuffer()
    {
        vk::CommandBufferAllocateInfo allocInfo(*commandPool, vk::CommandBufferLevel::ePrimary, 1);
    
        commandBuffers = logicalDevice->allocateCommandBuffersUnique(allocInfo);
    }
    
    void CreateFramebuffers()
    {
        framebuffers.reserve(swapChainImageViews.size());
        for(const vk::UniqueImageView& imageView : swapChainImageViews)
        {
            vk::ImageView attachments[] = { *imageView };
    
            vk::FramebufferCreateInfo framebufferCreateInfo({}, *renderPass, 1, attachments, swapChainExtent.width, swapChainExtent.height, 1);
            framebuffers.emplace_back(logicalDevice->createFramebufferUnique(framebufferCreateInfo));
        }
    }
    
    std::vector<char> LoadShaderFile(const std::string& fileName)
    {
        std::ifstream file(fileName, std::ios::ate | std::ios::binary);
    
        if(!file.is_open())
        {
            char errorBuffer[256];
            strerror_s(errorBuffer, sizeof(errorBuffer), errno);
            std::cerr << "Vulkan Shaders" << std::format("Failed trying to open shader file \"{}\"! Reason: {}", fileName, errorBuffer) << std::endl;
        }
    
        size_t fileSize = static_cast<size_t>(file.tellg());
        std::vector<char> fileBuffer(fileSize);
    
        file.seekg(0);
        file.read(fileBuffer.data(), fileSize);
    
        file.close();
    
        return fileBuffer;
    }
    
    vk::UniqueShaderModule CreateShaderStage(const std::vector<char>& code)
    {
        vk::ShaderModuleCreateInfo createInfo({}, code.size(), reinterpret_cast<const uint32_t*>(code.data()));
        return logicalDevice->createShaderModuleUnique(createInfo);
    }
    
    void CreateGraphicsPipeline()
    {
        std::vector<char> vertexShaderCode = LoadShaderFile("triangle_vertex.spv");
        std::vector<char> fragmentShaderCode = LoadShaderFile("triangle_fragment.spv");
    
        vk::UniqueShaderModule vertexShaderModule = CreateShaderStage(vertexShaderCode);
        vk::UniqueShaderModule fragmentShaderModule = CreateShaderStage(fragmentShaderCode);
    
        vk::PipelineShaderStageCreateInfo vertexShaderStage({}, vk::ShaderStageFlagBits::eVertex, *vertexShaderModule, "main");
        vk::PipelineShaderStageCreateInfo fragmentShaderStage({}, vk::ShaderStageFlagBits::eFragment, *fragmentShaderModule, "main");
    
        vk::PipelineShaderStageCreateInfo shaderStages[] = { vertexShaderStage, fragmentShaderStage };
    
        vk::PipelineVertexInputStateCreateInfo vertexInputCreateInfo({}, 0, nullptr, 0, nullptr);
        
        std::vector<vk::DynamicState> dynamicStates = { vk::DynamicState::eViewport, vk::DynamicState::eScissor };
        vk::PipelineDynamicStateCreateInfo dynamicStateCreateInfo({}, static_cast<uint32_t>(dynamicStates.size()), dynamicStates.data());
    
        vk::PipelineInputAssemblyStateCreateInfo inputAssemblyCreateInfo({}, vk::PrimitiveTopology::eTriangleList, vk::False);
    
        vk::Viewport viewport(0.0f, 0.0f, static_cast<float>(swapChainExtent.width), static_cast<float>(swapChainExtent.height), 0.0f, 1.0f);
        vk::Rect2D scissor(vk::Offset2D(0, 0), swapChainExtent);
        vk::PipelineViewportStateCreateInfo viewportStateCreateInfo({}, 1, &viewport, 1, &scissor);
    
        //Fixed state
        vk::PipelineRasterizationStateCreateInfo rasterizerCreateInfo({}, vk::False, vk::False, vk::PolygonMode::eFill, vk::CullModeFlagBits::eBack,
                                                                      vk::FrontFace::eClockwise, vk::False, 0.0f, 0.0f, 0.0f, 1.0f);
        vk::PipelineMultisampleStateCreateInfo multisampleCreateInfo({}, vk::SampleCountFlagBits::e1, vk::False, 1.0f, nullptr, vk::False, vk::False);
        vk::PipelineDepthStencilStateCreateInfo depthStencilCreateInfo {};
    
        //Color blending
        vk::PipelineColorBlendAttachmentState colorBlendAttachment(vk::False, vk::BlendFactor::eOne, vk::BlendFactor::eZero, vk::BlendOp::eAdd,
                                                                    vk::BlendFactor::eOne, vk::BlendFactor::eZero, vk::BlendOp::eAdd, 
                                                                    vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA);
        vk::PipelineColorBlendStateCreateInfo colorBlendingCreateInfo({}, vk::False, vk::LogicOp::eClear, 1, &colorBlendAttachment, {0, 0, 0, 0});
    
        vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo({}, 0, nullptr, 0, nullptr);
        auto pipelineLayout = logicalDevice->createPipelineLayoutUnique(pipelineLayoutCreateInfo);
    
        vk::GraphicsPipelineCreateInfo graphicsPipelineCreateInfo({}, 2, shaderStages, &vertexInputCreateInfo, &inputAssemblyCreateInfo, nullptr, &viewportStateCreateInfo, &rasterizerCreateInfo,
                                                                  &multisampleCreateInfo, &depthStencilCreateInfo, &colorBlendingCreateInfo, &dynamicStateCreateInfo, *pipelineLayout, *renderPass, 0, nullptr, -1);
    
        auto [result, pipelines] = logicalDevice->createGraphicsPipelinesUnique(nullptr, graphicsPipelineCreateInfo);
        if(result != vk::Result::eSuccess)
            std::cerr << "Failed to create graphics pipeline!" << std::endl;
    
        graphicsPipeline = std::move(pipelines.front());
    }
    
    void Render()
    {
        logicalDevice->waitForFences(1, &*inFlightFence, vk::True, UINT64_MAX);
        logicalDevice->resetFences(1, &*inFlightFence);
    
        uint32_t swapChainImageIndex;
        {
            //Always returns success if exceptions are enabled
            vk::Result acquireResult = logicalDevice->acquireNextImageKHR(*swapChain, UINT64_MAX, *imageAvailableSemaphore, nullptr, &swapChainImageIndex);
    
            if(acquireResult == vk::Result::eErrorOutOfDateKHR)
            {
                std::cout << "Time to recreate swapchain!" << std::endl;
                //Recreate swapchain here
                return;
            }
            else if(acquireResult != vk::Result::eSuccess && acquireResult != vk::Result::eSuboptimalKHR)
            {
                //Something went wrong, crash
            }
        }
    
        vk::CommandBuffer commandBuffer = *commandBuffers[0];
        commandBuffer.reset();
        vk::CommandBufferBeginInfo beginInfo;
    
        commandBuffer.begin(beginInfo);
    
        vk::Rect2D renderArea(vk::Offset2D(0, 0), swapChainExtent);
        vk::ClearValue clearValue(vk::ClearColorValue(0, 0, 0, 1));
        vk::RenderPassBeginInfo renderPassBeginInfo(*renderPass, *framebuffers[swapChainImageIndex], renderArea, 1, &clearValue);
    
        commandBuffer.beginRenderPass(&renderPassBeginInfo, vk::SubpassContents::eInline);
        commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, *graphicsPipeline);
        
        vk::Viewport viewport(0.0f, 0.0f, static_cast<float>(swapChainExtent.width), static_cast<float>(swapChainExtent.height), 0.0f, 1.0f);
        commandBuffer.setViewport(0, 1, &viewport);
    
        vk::Rect2D scissor(vk::Offset2D(0, 0), swapChainExtent);
        commandBuffer.setScissor(0, 1, &scissor);
    
        commandBuffer.draw(3, 1, 0, 0);
    
        commandBuffer.endRenderPass();
        commandBuffer.end();
    
        std::array<vk::PipelineStageFlags, 1> waitStages = { vk::PipelineStageFlagBits::eColorAttachmentOutput };
        vk::Semaphore waitSemaphores[] = { *imageAvailableSemaphore };
        vk::Semaphore signalSemaphores[] = { *renderFinishedSemaphore };
        
        vk::SubmitInfo submitInfo(1, waitSemaphores, waitStages.data(), 1, &commandBuffer, 1, signalSemaphores);
        if(graphicsQueue.submit(1, &submitInfo, *inFlightFence) != vk::Result::eSuccess)
            std::cerr << "Failed to submit draw command buffer!" << std::endl;
    
        vk::SwapchainKHR swapChains[] = { *swapChain };
        vk::Result results[] = { vk::Result::eSuccess, vk::Result::eSuboptimalKHR, vk::Result::eErrorOutOfDateKHR };
        vk::PresentInfoKHR presentInfo(1, signalSemaphores, 1, swapChains, &swapChainImageIndex, nullptr);
        vk::Result presentResult = presentQueue.presentKHR(presentInfo);
        if(presentResult == vk::Result::eErrorOutOfDateKHR || presentResult == vk::Result::eSuboptimalKHR)
        {
            std::cout << "Time to recreate swapchain!" << std::endl;
            //Recreate swapchain here
        }
        else if(presentResult != vk::Result::eSuccess)
        {
            //Something went wrong, crash
        }
    }
    
    int main(int argc, char* argv[])
    {
        InitWindow();
        InitVulkan();
        SetupDebugMessenger();
        CreateSurface();
        InitPhysicalDevice();
        CreateLogicalDevice();
        CreateSyncObjects();
        CreateSwapChain();
        CreateRenderPass();
        CreateFramebuffers();
        CreateCommandPool();
        CreateCommandBuffer();
        CreateGraphicsPipeline();
    
        //Uncommenting the try/catch will print the exception
        try
        {
        std::chrono::time_point previousTime = std::chrono::high_resolution_clock::now();
        double resizeTimer = 5.f;
            while (running)
            {
                if (!ProcessMessages())
                    running = false;
    
                std::chrono::time_point currentTime = std::chrono::high_resolution_clock::now();
                float deltaTime = std::chrono::duration(currentTime - previousTime).count();
                resizeTimer -= deltaTime;
    
                if(resizeTimer <= 0)
                {
                    SetWindowPos(windowHandle, nullptr, 0, 0, 1200, 1200, SWP_NOMOVE | SWP_NOZORDER);
                    UpdateWindow(windowHandle);
                }
    
                Render();
            }
        }
        catch(std::exception e)
        {
            //Prints "vk::Queue::presentKHR: ErrorOutOfDateKHR" when window is resized
            std::cout << e.what();
        }
    
        logicalDevice->waitIdle();
        vulkanInstance.release();
        return 0;
    }