From eeb0dd8024431af19c90216b941b86580d3df464 Mon Sep 17 00:00:00 2001 From: azraptor <154612819+azraptor@users.noreply.github.com> Date: Thu, 3 Jul 2025 01:59:44 -0400 Subject: [PATCH] Submoduled added, SDL2 code added and rewritten to be runnable --- .gitignore | 4 + .gitmodules | 6 + CMakeLists.txt | 72 ++++ README.md | 6 +- include/vulkanapp.hh | 141 ++++++ shaders/triangle.frag | 9 + shaders/triangle.vert | 20 + src/main.cc | 19 + src/vulkanapp.cc | 927 ++++++++++++++++++++++++++++++++++++++++ thirdparty/SDL | 1 + thirdparty/vk-bootstrap | 1 + 11 files changed, 1205 insertions(+), 1 deletion(-) create mode 100644 .gitmodules create mode 100644 CMakeLists.txt create mode 100644 include/vulkanapp.hh create mode 100644 shaders/triangle.frag create mode 100644 shaders/triangle.vert create mode 100644 src/main.cc create mode 100644 src/vulkanapp.cc create mode 160000 thirdparty/SDL create mode 160000 thirdparty/vk-bootstrap diff --git a/.gitignore b/.gitignore index 226ada1..26837de 100644 --- a/.gitignore +++ b/.gitignore @@ -100,3 +100,7 @@ CTestTestfile.cmake _deps CMakeUserPresets.json +# Other +.cache/ +.vscode/ +build/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..9b32fd7 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "thirdparty/vk-bootstrap"] + path = thirdparty/vk-bootstrap + url = https://github.com/charles-lunarg/vk-bootstrap.git +[submodule "thirdparty/SDL"] + path = thirdparty/SDL + url = https://github.com/libsdl-org/SDL.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..54c2c86 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,72 @@ +cmake_minimum_required(VERSION 3.10) +project(vksdlproj VERSION 0.1.0 LANGUAGES C CXX) + +SET(CMAKE_BUILD_RPATH_USE_ORIGIN TRUE) +set(EXE_NAME project) + +set(PROGRAM_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/vulkanapp.cc) + +option(SDL3_NONSYSTEM "Use SDL3 from folder in source tree" ON) + +# Get our GLSL shaders +set(SHADER_DIR ${CMAKE_CURRENT_SOURCE_DIR}/shaders) +set(SHADER_BIN_DIR ${CMAKE_CURRENT_BINARY_DIR}) +file(GLOB SHADERS ${SHADER_DIR}/*.vert + ${SHADER_DIR}/*.frag + ${SHADER_DIR}/*.comp + ${SHADER_DIR}/*.geom + ${SHADER_DIR}/*.tesc + ${SHADER_DIR}/*.tese + ${SHADER_DIR}/*.mesh + ${SHADER_DIR}/*.task + ${SHADER_DIR}/*.rgen + ${SHADER_DIR}/*.rchit + ${SHADER_DIR}/*.rmiss + ) + +# Find SDL3 +if(SDL3_NONSYSTEM) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/SDL ${CMAKE_CURRENT_BINARY_DIR}/SDL EXCLUDE_FROM_ALL) +else() + find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3-shared) +endif() + +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/vk-bootstrap) + +find_package(Vulkan REQUIRED COMPONENTS glslc) + +# Compile each shader +foreach(SHADER IN LISTS SHADERS) + get_filename_component(FILENAME ${SHADER} NAME) + add_custom_command(OUTPUT ${SHADER_BIN_DIR}/${FILENAME}.spv + COMMAND ${Vulkan_GLSLC_EXECUTABLE} ${SHADER} -o ${SHADER_BIN_DIR}/${FILENAME}.spv + DEPENDS ${SHADER} + COMMENT "Compiling Shader ${FILENAME}") + list(APPEND SPV_SHADERS ${SHADER_BIN_DIR}/${FILENAME}.spv) +endForeach() + +# Shader target +add_custom_target(shaders ALL DEPENDS ${SPV_SHADERS}) + +add_executable(${EXE_NAME} ${CMAKE_SOURCE_DIR}/src/main.cc ${PROGRAM_SOURCES} ${SHADERS}) + +set_target_properties(${EXE_NAME} + PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS ON +) + +target_include_directories(${EXE_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/include) +add_dependencies(${EXE_NAME} shaders) + +if(CMAKE_COMPILER_IS_GNUCC) + target_compile_options(${EXE_NAME} PRIVATE -Wall -Wextra) +elseif(MSVC) + target_compile_options(${EXE_NAME} PRIVATE /W4) +endif() + +# Linking +target_link_libraries(${EXE_NAME} PRIVATE SDL3::SDL3) +target_link_libraries(${EXE_NAME} PRIVATE Vulkan::Vulkan) +target_link_libraries(${EXE_NAME} PRIVATE vk-bootstrap::vk-bootstrap) \ No newline at end of file diff --git a/README.md b/README.md index e63d05c..50e1cd3 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ # VulkanSDL3 -Vulkan 1.3 + SDL3 \ No newline at end of file +Learning how to use SDL3 and vulkan to create graphics programs that can run on multiple different places. + +SDL3 is structed a bit differently than SDL2, some new faculties such as the "App" API among other changes. + +Also incorporating some more libraries such as vk-bootstrap to aid in making vulkan less painful. \ No newline at end of file diff --git a/include/vulkanapp.hh b/include/vulkanapp.hh new file mode 100644 index 0000000..76d0c52 --- /dev/null +++ b/include/vulkanapp.hh @@ -0,0 +1,141 @@ +#pragma once + +#include +#include + +#include +#include +#include + +#include + +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; + +const std::vector deviceExtensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME +}; + +const int MAX_FRAMES_IN_FLIGHT = 2; + +#ifdef NDEBUG +const bool enableValidationLayers = false; +#else +const bool enableValidationLayers = true; +#endif + +VkResult CreateDebugUtilsMessengerEXT(VkInstance, const VkDebugUtilsMessengerCreateInfoEXT*, const VkAllocationCallbacks*, VkDebugUtilsMessengerEXT*); +void DestroyDebugUtilsMessengerEXT(VkInstance, VkDebugUtilsMessengerEXT, const VkAllocationCallbacks*); + +struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool complete() { + return graphicsFamily.has_value() && presentFamily.has_value(); + } +}; + +struct SwapChainSupportDetails { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; +}; + +class VulkanApp { +public: + VulkanApp(const uint32_t& _w, const uint32_t& _h) : + mWin(nullptr), mWidth(_w), mHeight(_h) {} + + void init(); + void loop(); + void cleanup(); +private: + // SDL2 + SDL_Window *mWin; + SDL_Event mEvent; + + // Not tied to library + uint32_t mWidth; + uint32_t mHeight; + bool mActive = false; + bool mResized = false; + bool mMinimized = false; + + // Vulkan + VkInstance mInstance; + VkPhysicalDevice mPhysicalDevice = VK_NULL_HANDLE; + VkDevice mLogicalDevice; + VkSurfaceKHR mSurface; + + VkSwapchainKHR mSwapChain; + std::vector mSwapChainImages; + std::vector mSwapChainImageViews; + VkFormat mSwapChainImageFormat; + VkExtent2D mSwapChainExtent; + std::vector mSwapChainFramebuffers; + + VkRenderPass mRenderPass; + VkPipelineLayout mPipelineLayout; + VkPipeline mGraphicsPipeline; + + VkCommandPool mCommandPool; + std::vector mCommandBuffers; + + VkQueue mGraphicsQueue; + VkQueue mPresentQueue; + + std::vector mImageAvailableSemaphores; + std::vector mRenderFinishedSemaphores; + std::vector mInFlightFences; + + + VkDebugUtilsMessengerEXT mDebugMessenger; + + void createInstance(); + void selectPhysicalDevice(); + void createLogicalDevice(); + + void createSwapChain(); + void recreateSwapChain(); + void cleanupSwapChain(); + + void createImageViews(); + void createRenderPass(); + void createGraphicsPipeline(); + void createFramebuffers(); + void createCommandPool(); + void createCommandBuffer(); + void createSyncObjects(); + + void recordCommandBuffer(VkCommandBuffer, uint32_t); + void drawFrame(); + + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice); + bool isDeviceSuitable(VkPhysicalDevice); + bool checkDeviceExtensionSupport(VkPhysicalDevice); + VkShaderModule createShaderModule(const std::vector&); + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice); + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector&); + VkPresentModeKHR chooseSwapPresentMode(const std::vector&); + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR&); + // Validation layer stuff + void setupDebugMessenger(); + bool checkValidationLayerSupport(); + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) { + (void)pUserData; + + if(messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { + fprintf(stderr, "Validation Layer [Error/Warning]: %s \n", pCallbackData->pMessage); + } else if(messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) { + fprintf(stderr, "Validation Layer [General]: %s \n", pCallbackData->pMessage); + } else { + fprintf(stderr, "Validation Layer: %s \n", pCallbackData->pMessage); + } + + return VK_FALSE; + } +}; \ No newline at end of file diff --git a/shaders/triangle.frag b/shaders/triangle.frag new file mode 100644 index 0000000..13009da --- /dev/null +++ b/shaders/triangle.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/triangle.vert b/shaders/triangle.vert new file mode 100644 index 0000000..af2dac5 --- /dev/null +++ b/shaders/triangle.vert @@ -0,0 +1,20 @@ +#version 450 + +layout(location = 0) out vec3 fragColor; + +const vec2 positions[3] = vec2[]( + vec2(0.0, -0.5), + vec2(0.5, 0.5), + vec2(-0.5, 0.5) +); + +const 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]; +} diff --git a/src/main.cc b/src/main.cc new file mode 100644 index 0000000..68402ec --- /dev/null +++ b/src/main.cc @@ -0,0 +1,19 @@ +#include "vulkanapp.hh" + +#include + +int main() { + VulkanApp app(1024, 1024); + + try { + app.init(); + app.loop(); + app.cleanup(); + } catch(const std::exception& except) { + fprintf(stderr, "Error! %s\n", except.what()); + + return 1; + } + + return 0; +} \ No newline at end of file diff --git a/src/vulkanapp.cc b/src/vulkanapp.cc new file mode 100644 index 0000000..e5977de --- /dev/null +++ b/src/vulkanapp.cc @@ -0,0 +1,927 @@ +#include "vulkanapp.hh" + +#include +#include + +#include +#include +#include +#include +#include + +// Validator stuff +VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, + const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, + const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { + PFN_vkCreateDebugUtilsMessengerEXT func = + reinterpret_cast( + 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) { + PFN_vkDestroyDebugUtilsMessengerEXT func = + reinterpret_cast( + vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT") + ); + + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +const std::vector readFile(const std::string& path) { + FILE *fd = nullptr; +#if _MSC_VER + fopen_s(&fd, path.c_str(), "rb"); +#else + fd = fopen(path.c_str(), "rb"); +#endif + + long fileSize = 0; + std::vector buf; + + if(fd == nullptr) { + std::string err = "Could not open "; + throw std::runtime_error(err + path + "\n"); + } + + fseek(fd, 0L, SEEK_END); + fileSize = ftell(fd); + fseek(fd, 0L, SEEK_SET); + + buf.resize(fileSize); + + fread(buf.data(), sizeof(uint8_t), fileSize, fd); + fclose(fd); + + return buf; +} + +VkShaderModule VulkanApp::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(mLogicalDevice, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { + throw std::runtime_error("Could not create shader module!"); + } + + return shaderModule; +} + +QueueFamilyIndices VulkanApp::findQueueFamilies(VkPhysicalDevice device) { + QueueFamilyIndices indices; + + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + // Get queue families and check them + for(size_t i=0;i(i), mSurface, &presentSupport); + + if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + indices.graphicsFamily = i; + } + + if (presentSupport) { + indices.presentFamily = i; + } + + if (indices.complete()) break; + } + + return indices; +} + +SwapChainSupportDetails VulkanApp::querySwapChainSupport(VkPhysicalDevice device) { + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, mSurface, &details.capabilities); + + uint32_t formatCount; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, mSurface, &formatCount, nullptr); + + if (formatCount != 0) { + details.formats.resize(formatCount); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, mSurface, &formatCount, details.formats.data()); + } + + uint32_t presentModeCount; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, mSurface, &presentModeCount, nullptr); + + if (presentModeCount != 0) { + details.presentModes.resize(presentModeCount); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, mSurface, &presentModeCount, details.presentModes.data()); + } + + return details; +} + +bool VulkanApp::isDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensionsSupported = checkDeviceExtensionSupport(device); + + bool swapChainAdequate = false; + if (extensionsSupported) { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); + } + + return indices.complete() && extensionsSupported && swapChainAdequate; +} + +VkSurfaceFormatKHR VulkanApp::chooseSwapSurfaceFormat(const std::vector& availableFormats) { + for (const VkSurfaceFormatKHR& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && + availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + return availableFormats[0]; +} + +VkPresentModeKHR VulkanApp::chooseSwapPresentMode(const std::vector& availablePresentModes) { + for (const VkPresentModeKHR& availablePresentMode : availablePresentModes) { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { + return availablePresentMode; + } + } + return VK_PRESENT_MODE_FIFO_KHR; +} + +VkExtent2D VulkanApp::chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } else { + int width, height; + SDL_GetWindowSizeInPixels(mWin, &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; + } +} + +void VulkanApp::recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = 0; // Optional + beginInfo.pInheritanceInfo = nullptr; // Optional + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + throw std::runtime_error("Could not start recording Vulkan command buffer!"); + } + + VkRenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; + renderPassInfo.renderPass = mRenderPass; + renderPassInfo.framebuffer = mSwapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = {0, 0}; + renderPassInfo.renderArea.extent = mSwapChainExtent; + + VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; + + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, mGraphicsPipeline); + + VkViewport viewport{}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = static_cast(mSwapChainExtent.width); + viewport.height = static_cast(mSwapChainExtent.height); + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); + + VkRect2D scissor{}; + scissor.offset = {0, 0}; + scissor.extent = mSwapChainExtent; + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); + + // Actually draw the vertices + vkCmdDraw(commandBuffer, 3, 1, 0, 0); + vkCmdEndRenderPass(commandBuffer); + + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + throw std::runtime_error("Could not record Vulkan command buffer!"); + } +} + +void VulkanApp::drawFrame() { + uint32_t currentFrame = 0; + VkResult result = VK_SUCCESS; + uint32_t imageIndex = 0; + + vkWaitForFences(mLogicalDevice, 1, &mInFlightFences[currentFrame], VK_TRUE, UINT64_MAX); + vkResetFences(mLogicalDevice, 1, &mInFlightFences[currentFrame]); + + + result = vkAcquireNextImageKHR(mLogicalDevice, + mSwapChain, + UINT64_MAX, + mImageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) { + recreateSwapChain(); + return; + } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + throw std::runtime_error("Could not get Vulkan swap chain image!"); + } + + vkResetFences(mLogicalDevice, 1, &mInFlightFences[currentFrame]); + + vkResetCommandBuffer(mCommandBuffers[currentFrame], /*VkCommandBufferResetFlagBits*/ 0); + recordCommandBuffer(mCommandBuffers[currentFrame], imageIndex); + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + + VkSemaphore waitSemaphores[] = {mImageAvailableSemaphores[currentFrame]}; + VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &mCommandBuffers[currentFrame]; + + VkSemaphore signalSemaphores[] = {mRenderFinishedSemaphores[currentFrame]}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + + if (vkQueueSubmit(mGraphicsQueue, 1, &submitInfo, mInFlightFences[currentFrame]) != VK_SUCCESS) { + throw std::runtime_error("Could not submit Vulkan command buffer!"); + } + + VkPresentInfoKHR presentInfo{}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = signalSemaphores; + + VkSwapchainKHR swapChains[] = {mSwapChain}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + + presentInfo.pImageIndices = &imageIndex; + + result = vkQueuePresentKHR(mPresentQueue, &presentInfo); + + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || mResized) { + mResized = false; + recreateSwapChain(); + return; + } else if (result != VK_SUCCESS) { + throw std::runtime_error("Could not present Vulkan swap chain image!"); + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; +} + +void VulkanApp::init() { + // Initialize SDL2 + SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS); + //SDL_Vulkan_LoadLibrary(nullptr); + + // Create the window + mWin = SDL_CreateWindow("Vulkan+SDL2 Application", + mWidth, + mHeight, + SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE + ); + + if(mWin == nullptr) { + std::string err = "Could not create SDL2 window! " + std::string(SDL_GetError()) + "\n"; + throw std::runtime_error(err); + } + + // Vulkan stuff from here + createInstance(); + setupDebugMessenger(); + + // Surface (must be done before physical device) + if(SDL_Vulkan_CreateSurface(mWin, mInstance, nullptr, &mSurface) != true) { + throw std::runtime_error("Could not create Vulkan surface!"); + } + + selectPhysicalDevice(); + createLogicalDevice(); + createSwapChain(); + createImageViews(); + createRenderPass(); + createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createCommandBuffer(); + createSyncObjects(); +} + +void VulkanApp::createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("Validation layers requested, but not available!"); + } + + // Get # of extensions then get extensions + uint32_t extensionCount = 0; + const char * const * extensionArr = SDL_Vulkan_GetInstanceExtensions(&extensionCount); + std::vector extensionNames(extensionArr, extensionArr + extensionCount); + + if(enableValidationLayers) + extensionNames.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + + // App info + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Simple Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "RAPT2"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + // Extensions + VkInstanceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + createInfo.pApplicationInfo = &appInfo; + + createInfo.enabledExtensionCount = static_cast(extensionNames.size()); + createInfo.ppEnabledExtensionNames = extensionNames.data(); + + // Debug handling + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + if(enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + + debugCreateInfo = {}; + debugCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + debugCreateInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + debugCreateInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + debugCreateInfo.pfnUserCallback = debugCallback; + + createInfo.pNext = reinterpret_cast(&debugCreateInfo); + } else { + createInfo.enabledLayerCount = 0; + + createInfo.pNext = nullptr; + } + + if(vkCreateInstance(&createInfo, nullptr, &mInstance) != VK_SUCCESS) { + throw std::runtime_error("Could not create Vulkan instance!"); + } +} + +void VulkanApp::selectPhysicalDevice() { + uint32_t deviceCount = 0; + vkEnumeratePhysicalDevices(mInstance, &deviceCount, nullptr); + + if(deviceCount == 0) { + throw std::runtime_error("Could not find a Vulkan compatible GPU!"); + } + + std::vector devices(deviceCount); + vkEnumeratePhysicalDevices(mInstance, &deviceCount, devices.data()); + + for(const VkPhysicalDevice& device : devices) { + QueueFamilyIndices indices = findQueueFamilies(device); + if(indices.complete()) { + mPhysicalDevice = device; + break; + } + } + + if(mPhysicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("Could not find any suitable GPU!"); + } +} + +void VulkanApp::createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(mPhysicalDevice); + + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = { + indices.graphicsFamily.value(), + indices.presentFamily.value() + }; + + 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{}; + + vkGetPhysicalDeviceFeatures(mPhysicalDevice, &deviceFeatures); + + VkDeviceCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + + createInfo.pEnabledFeatures = &deviceFeatures; + + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); + + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } + + if (vkCreateDevice(mPhysicalDevice, &createInfo, nullptr, &mLogicalDevice) != VK_SUCCESS) { + throw std::runtime_error("Could not create logical device!"); + } + + vkGetDeviceQueue(mLogicalDevice, indices.graphicsFamily.value(), 0, &mGraphicsQueue); + vkGetDeviceQueue(mLogicalDevice, indices.presentFamily.value(), 0, &mPresentQueue); +} + +void VulkanApp::createSwapChain() { + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(mPhysicalDevice); + + VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); + VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); + VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); + + uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; + + if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { + imageCount = swapChainSupport.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = mSurface; + 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; + + QueueFamilyIndices indices = findQueueFamilies(mPhysicalDevice); + 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; + } else { + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + createInfo.queueFamilyIndexCount = 0; // Optional + createInfo.pQueueFamilyIndices = nullptr; // Optional + } + + createInfo.preTransform = swapChainSupport.capabilities.currentTransform; + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + createInfo.presentMode = presentMode; + createInfo.clipped = VK_TRUE; + createInfo.oldSwapchain = VK_NULL_HANDLE; + + if (vkCreateSwapchainKHR(mLogicalDevice, &createInfo, nullptr, &mSwapChain) != VK_SUCCESS) { + throw std::runtime_error("Could not create swap chain!"); + } + + vkGetSwapchainImagesKHR(mLogicalDevice, mSwapChain, &imageCount, nullptr); + mSwapChainImages.resize(imageCount); + vkGetSwapchainImagesKHR(mLogicalDevice, mSwapChain, &imageCount, mSwapChainImages.data()); + + mSwapChainImageFormat = surfaceFormat.format; + mSwapChainExtent = extent; +} + +void VulkanApp::recreateSwapChain() { + vkDeviceWaitIdle(mLogicalDevice); + + cleanupSwapChain(); + + createSwapChain(); + createImageViews(); + createFramebuffers(); +} + +void VulkanApp::cleanupSwapChain() { + for (VkFramebuffer framebuffer : mSwapChainFramebuffers) { + vkDestroyFramebuffer(mLogicalDevice, framebuffer, nullptr); + } + + for (VkImageView imageView : mSwapChainImageViews) { + vkDestroyImageView(mLogicalDevice, imageView, nullptr); + } + + vkDestroySwapchainKHR(mLogicalDevice, mSwapChain, nullptr); +} + +void VulkanApp::createImageViews() { + mSwapChainImageViews.resize(mSwapChainImages.size()); + + for (size_t i = 0; i < mSwapChainImages.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = mSwapChainImages[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = mSwapChainImageFormat; + + 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(mLogicalDevice, &createInfo, nullptr, &mSwapChainImageViews[i]) != VK_SUCCESS) { + throw std::runtime_error("Could not create image views!"); + } + } +} + +void VulkanApp::createRenderPass() { + VkAttachmentDescription colorAttachment{}; + colorAttachment.format = mSwapChainImageFormat; + 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.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + + VkAttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkRenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + + if (vkCreateRenderPass(mLogicalDevice, &renderPassInfo, nullptr, &mRenderPass) != VK_SUCCESS) { + throw std::runtime_error("Could not create Vulkan render pass!"); + } +} + +void VulkanApp::createGraphicsPipeline() { + const std::vector vertShaderCode = readFile("triangle.vert.spv"); + const std::vector fragShaderCode = readFile("triangle.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"; + + VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInputInfo.vertexBindingDescriptionCount = 0; + vertexInputInfo.vertexAttributeDescriptionCount = 0; + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + + VkPipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_FALSE; + + VkPipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = VK_LOGIC_OP_COPY; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + 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(mLogicalDevice, &pipelineLayoutInfo, nullptr, &mPipelineLayout) != VK_SUCCESS) { + throw std::runtime_error("Could not create Vulkan pipeline layout!"); + } + + VkGraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = mPipelineLayout; + pipelineInfo.renderPass = mRenderPass; + pipelineInfo.subpass = 0; + pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; + + if (vkCreateGraphicsPipelines(mLogicalDevice, + VK_NULL_HANDLE, + 1, + &pipelineInfo, + nullptr, + &mGraphicsPipeline) != VK_SUCCESS) { + throw std::runtime_error("Could not create Vulkan graphics pipeline!"); + } + + vkDestroyShaderModule(mLogicalDevice, fragShaderModule, nullptr); + vkDestroyShaderModule(mLogicalDevice, vertShaderModule, nullptr); +} + +void VulkanApp::createFramebuffers() { + mSwapChainFramebuffers.resize(mSwapChainImageViews.size()); + + + + for (size_t i = 0; i < mSwapChainImageViews.size(); i++) { + VkImageView attachments[] = { + mSwapChainImageViews[i] + }; + + VkFramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + framebufferInfo.renderPass = mRenderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = mSwapChainExtent.width; + framebufferInfo.height = mSwapChainExtent.height; + framebufferInfo.layers = 1; + + if (vkCreateFramebuffer(mLogicalDevice, &framebufferInfo, nullptr, &mSwapChainFramebuffers[i]) != VK_SUCCESS) { + throw std::runtime_error("Could not create Vulkan framebuffer!"); + } + } +} + +void VulkanApp::createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(mPhysicalDevice); + + 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(mLogicalDevice, &poolInfo, nullptr, &mCommandPool) != VK_SUCCESS) { + throw std::runtime_error("Could not create Vulkan command pool!"); + } +} + +void VulkanApp::createCommandBuffer() { + mCommandBuffers.resize(MAX_FRAMES_IN_FLIGHT); + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = mCommandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = static_cast(mCommandBuffers.size()); + + if (vkAllocateCommandBuffers(mLogicalDevice, &allocInfo, mCommandBuffers.data()) != VK_SUCCESS) { + throw std::runtime_error("Could not allocate Vulkan command buffers!"); + } +} + +void VulkanApp::createSyncObjects() { + mImageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + mRenderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + mInFlightFences.resize(MAX_FRAMES_IN_FLIGHT); + + 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; + + for(size_t i = 0;i availableExtensions(extensionCount); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); + + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + + for (const VkExtensionProperties& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + + return requiredExtensions.empty(); +} + +bool VulkanApp::checkValidationLayerSupport() { + uint32_t layerCount = 0; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const VkLayerProperties& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; +} + +void VulkanApp::setupDebugMessenger() { + if (!enableValidationLayers) return; + + 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; + + if (CreateDebugUtilsMessengerEXT(mInstance, &createInfo, nullptr, &mDebugMessenger) != VK_SUCCESS) { + throw std::runtime_error("failed to set up debug messenger!"); + } +} + +void VulkanApp::loop() { + if(mWin == nullptr) { + std::string err = "Could not find window " + std::string(SDL_GetError()) + "\n"; + throw std::runtime_error(err); + } + + mActive = true; + + // Main loop + while(mActive) { + while(SDL_PollEvent(&mEvent)) { + switch (mEvent.type) { + case SDL_EVENT_QUIT: + mActive = false; + break; + case SDL_EVENT_WINDOW_RESIZED: + mResized = true; + break; + case SDL_EVENT_WINDOW_MINIMIZED: + mMinimized = true; + break; + case SDL_EVENT_WINDOW_RESTORED: + mMinimized = false; + break; + default: + break; + } + } + + if(!mMinimized) drawFrame(); + } + + vkDeviceWaitIdle(mLogicalDevice); +} + +void VulkanApp::cleanup() { + cleanupSwapChain(); + + vkDestroyPipeline(mLogicalDevice, mGraphicsPipeline, nullptr); + vkDestroyPipelineLayout(mLogicalDevice, mPipelineLayout, nullptr); + + vkDestroyRenderPass(mLogicalDevice, mRenderPass, nullptr); + + for(size_t i=0;i