diff --git a/CMakeLists.txt b/CMakeLists.txt index ad67792..8bd12b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 3.24) # options option(ENGINE_BUILD_TEST "Compile the test program" ON) -option(ENGINE_BUILD_VULKAN "Use Vulkan 1.3 for graphics" ON) project(engine LANGUAGES CXX VERSION "0.1.0" @@ -27,7 +26,15 @@ set(SRC_FILES src/scene.cpp src/gfx_device_vulkan.cpp - src/gfx_device_null.cpp + + src/vulkan/device.cpp + src/vulkan/device.h + src/vulkan/gpu_allocator.cpp + src/vulkan/gpu_allocator.h + src/vulkan/instance.cpp + src/vulkan/instance.h + src/vulkan/swapchain.cpp + src/vulkan/swapchain.h src/util/files.cpp src/util/model_loader.cpp @@ -90,6 +97,10 @@ source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}/include" PREFIX "Include" FILES $ target_compile_definitions(${PROJECT_NAME} PRIVATE DEFINITIONS "ENGINE_EXPORTS") +if (WIN32) + target_compile_definitions(${PROJECT_NAME} PRIVATE DEFINITIONS "NOMINMAX") # stop windows.h conflicting with 'std::max' +endif() + set_property(TARGET ${PROJECT_NAME} PROPERTY WINDOWS_EXPORT_ALL_SYMBOLS ON) set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 20) @@ -114,13 +125,6 @@ target_include_directories(${PROJECT_NAME} PRIVATE src) configure_file(config.h.in config.h) target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) -# figure out what graphics api to use -if (ENGINE_BUILD_VULKAN) - target_compile_definitions(${PROJECT_NAME} PRIVATE "ENGINE_BUILD_VULKAN") -else() - target_compile_definitions(${PROJECT_NAME} PRIVATE "ENGINE_BUILD_NULL") -endif() - # Build the test if (ENGINE_BUILD_TEST) add_subdirectory(test) @@ -134,24 +138,25 @@ if (MINGW) target_link_libraries(${PROJECT_NAME} PUBLIC mingw32) endif() -if(ENGINE_BUILD_VULKAN) - # Volk - set(VOLK_STATIC_DEFINES "") - set(VOLK_PULL_IN_VULKAN ON) - set(VOLK_INSTALL OFF) - set(VOLK_HEADERS_ONLY OFF) - add_subdirectory(dependencies/volk) - target_link_libraries(${PROJECT_NAME} PRIVATE volk::volk) - # Vulkan Memory Allocator - target_include_directories(${PROJECT_NAME} SYSTEM PRIVATE dependencies/VulkanMemoryAllocator/include) - # shaderc - if (MSVC) - include(FindVulkan) - find_package(Vulkan COMPONENTS shaderc_combined) - target_link_libraries(${PROJECT_NAME} PRIVATE Vulkan::shaderc_combined) - else() - target_link_libraries(${PROJECT_NAME} PRIVATE shaderc_shared) - endif() +# Volk +set(VOLK_STATIC_DEFINES "") +set(VOLK_PULL_IN_VULKAN ON) +set(VOLK_INSTALL OFF) +set(VOLK_HEADERS_ONLY OFF) +if (WIN32) + set(VOLK_STATIC_DEFINES VK_USE_PLATFORM_WIN32_KHR) +endif() +add_subdirectory(dependencies/volk) +target_link_libraries(${PROJECT_NAME} PRIVATE volk::volk) +# Vulkan Memory Allocator +target_include_directories(${PROJECT_NAME} SYSTEM PRIVATE dependencies/VulkanMemoryAllocator/include) +# shaderc +if (MSVC) + include(FindVulkan) + find_package(Vulkan COMPONENTS shaderc_combined) + target_link_libraries(${PROJECT_NAME} PRIVATE Vulkan::shaderc_combined) +else() + target_link_libraries(${PROJECT_NAME} PRIVATE shaderc_shared) endif() # SDL2: diff --git a/debug.sh b/debug.sh deleted file mode 100755 index 73ad8b3..0000000 --- a/debug.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh -set -e -cd Debug -cmake --build . -cd test -./enginetest $@ diff --git a/include/application.hpp b/include/application.hpp index 207087b..ea65a25 100644 --- a/include/application.hpp +++ b/include/application.hpp @@ -77,6 +77,8 @@ namespace engine { bool m_enableFrameLimiter = true; + gfx::CommandBuffer *m_drawCommandBuffer; + /* resource stuff */ std::unordered_map> m_resourceManagers{}; diff --git a/include/gfx.hpp b/include/gfx.hpp index c4d8e8c..4e233e9 100644 --- a/include/gfx.hpp +++ b/include/gfx.hpp @@ -90,5 +90,6 @@ namespace engine::gfx { struct Pipeline; struct Buffer; struct Texture; + struct CommandBuffer; } diff --git a/include/gfx_device.hpp b/include/gfx_device.hpp index 047121f..a446e02 100644 --- a/include/gfx_device.hpp +++ b/include/gfx_device.hpp @@ -19,12 +19,12 @@ namespace engine { void getViewportSize(uint32_t *w, uint32_t *h); - // adds a draw call to the queue - // vertexBuffer is required, indexBuffer can be NULL, uniformData is required - void draw(const gfx::Pipeline* pipeline, const gfx::Buffer* vertexBuffer, const gfx::Buffer* indexBuffer, uint32_t count, const void* pushConstantData, size_t pushConstantSize, const gfx::Texture* texture); + gfx::CommandBuffer* beginRender(); + void finishRender(gfx::CommandBuffer* commandBuffer); - // Call once per frame. Executes all queued draw calls and renders to the screen. - void renderFrame(); + void cmdBindPipeline(gfx::CommandBuffer* commandBuffer, const gfx::Pipeline* pipeline); + void cmdBindDescriptorSetTexture(gfx::CommandBuffer* commandBuffer, uint32_t set, uint32_t binding, const gfx::Texture* texture); + void cmdBindDescriptorSetBuffer(gfx::CommandBuffer* commandBuffer, uint32_t set, uint32_t binding, const gfx::Texture* texture); // creates the equivalent of an OpenGL shader program & vertex attrib configuration gfx::Pipeline* createPipeline(const char* vertShaderPath, const char* fragShaderPath, const gfx::VertexFormat& vertexFormat, uint64_t uniformBufferSize, bool alphaBlending, bool backfaceCulling); diff --git a/include/log.hpp b/include/log.hpp index c46c1a6..ca02844 100644 --- a/include/log.hpp +++ b/include/log.hpp @@ -8,9 +8,9 @@ #include -#define TRACE SPDLOG_TRACE -#define DEBUG SPDLOG_DEBUG -#define INFO SPDLOG_INFO -#define WARN SPDLOG_WARN -#define ERROR SPDLOG_ERROR -#define CRITICAL SPDLOG_CRITICAL \ No newline at end of file +#define LOG_TRACE SPDLOG_TRACE +#define LOG_DEBUG SPDLOG_DEBUG +#define LOG_INFO SPDLOG_INFO +#define LOG_WARN SPDLOG_WARN +#define LOG_ERROR SPDLOG_ERROR +#define LOG_CRITICAL SPDLOG_CRITICAL \ No newline at end of file diff --git a/include/logger.hpp b/include/logger.hpp index 96dcfab..ad82794 100644 --- a/include/logger.hpp +++ b/include/logger.hpp @@ -40,7 +40,7 @@ namespace engine { spdlog::set_default_logger(logger); spdlog::flush_every(std::chrono::seconds(60)); - INFO("Created log with path: {}", log_path.string()); + LOG_INFO("Created log with path: {}", log_path.string()); } diff --git a/prebuild.sh b/prebuild.sh deleted file mode 100755 index be2d831..0000000 --- a/prebuild.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh -set -e -mkdir -p Debug -cd Debug -cmake -G Ninja -D CMAKE_BUILD_TYPE=Debug -D CMAKE_EXPORT_COMPILE_COMMANDS=ON .. -cd .. -ln -sf Debug/compile_commands.json . -mkdir -p Release -cd Release -cmake -G Ninja -D CMAKE_BUILD_TYPE=Release -D CMAKE_EXPORT_COMPILE_COMMANDS=ON .. diff --git a/release.sh b/release.sh deleted file mode 100755 index 11c89a5..0000000 --- a/release.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh -set -e -cd Release -cmake --build . -cd test -./enginetest $@ diff --git a/res/engine/shaders/textured.frag b/res/engine/shaders/standard.frag similarity index 100% rename from res/engine/shaders/textured.frag rename to res/engine/shaders/standard.frag diff --git a/res/engine/shaders/textured.vert b/res/engine/shaders/standard.vert similarity index 100% rename from res/engine/shaders/textured.vert rename to res/engine/shaders/standard.vert diff --git a/src/application.cpp b/src/application.cpp index 55a319f..1238a2f 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -79,13 +79,13 @@ namespace engine { vertParams.hasUV0 = true; auto texturedShader = std::make_unique( gfx(), - getResourcePath("engine/shaders/textured.vert").c_str(), - getResourcePath("engine/shaders/textured.frag").c_str(), + getResourcePath("engine/shaders/standard.vert").c_str(), + getResourcePath("engine/shaders/standard.frag").c_str(), vertParams, false, true ); - getResourceManager()->addPersistent("engine.textured", std::move(texturedShader)); + getResourceManager()->addPersistent("builtin.standard", std::move(texturedShader)); } { resources::Shader::VertexParams vertParams{}; @@ -99,7 +99,7 @@ namespace engine { false, true ); - getResourceManager()->addPersistent("engine.skybox", std::move(texturedShader)); + getResourceManager()->addPersistent("builtin.skybox", std::move(texturedShader)); } { auto whiteTexture = std::make_unique( @@ -109,7 +109,7 @@ namespace engine { false, false ); - getResourceManager()->addPersistent("engine.white", std::move(whiteTexture)); + getResourceManager()->addPersistent("builtin.white", std::move(whiteTexture)); } } @@ -117,7 +117,7 @@ namespace engine { void Application::gameLoop() { - TRACE("Begin game loop..."); + LOG_TRACE("Begin game loop..."); constexpr int FPS_LIMIT = 240; constexpr auto FRAMETIME_LIMIT = std::chrono::nanoseconds(1000000000 / FPS_LIMIT); @@ -129,6 +129,9 @@ namespace engine { // single-threaded game loop while (m_window->isRunning()) { + /* begin rendering */ + m_drawCommandBuffer = m_gfx->beginRender(); + /* logic */ m_sceneManager->updateActiveScene(m_window->dt()); @@ -139,12 +142,12 @@ namespace engine { uint64_t now = m_window->getNanos(); if (now - lastTick >= 1000000000LL * 5LL) [[unlikely]] { lastTick = now; - INFO("fps: {}", m_window->getAvgFPS()); + LOG_INFO("fps: {}", m_window->getAvgFPS()); m_window->resetAvgFPS(); } /* draw */ - m_gfx->renderFrame(); + m_gfx->finishRender(m_drawCommandBuffer); /* poll events */ m_window->getInputAndEvents(); diff --git a/src/gfx_device_null.cpp b/src/gfx_device_null.cpp deleted file mode 100644 index 87ae741..0000000 --- a/src/gfx_device_null.cpp +++ /dev/null @@ -1,127 +0,0 @@ -// The implementation of the graphics layer using Vulkan 1.3. - -#ifdef ENGINE_BUILD_NULL - -#include "gfx_device.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace engine { - - struct GFXDevice::Impl { - int FRAMECOUNT = 0; - }; - - GFXDevice::GFXDevice(const char* appName, const char* appVersion, SDL_Window* window, bool vsync) - { - (void)appName; - (void)appVersion; - (void)window; - (void)vsync; - - pimpl = std::make_unique(); - - } - - GFXDevice::~GFXDevice() - { - } - - void GFXDevice::getViewportSize(uint32_t *w, uint32_t *h) - { - *w = 1024; - *h = 768; - } - - void GFXDevice::draw(const gfx::Pipeline* pipeline, const gfx::Buffer* vertexBuffer, const gfx::Buffer* indexBuffer, uint32_t count, const void* pushConstantData, size_t pushConstantSize, const gfx::Texture* texture) - { - (void)pipeline; - (void)vertexBuffer; - (void)indexBuffer; - (void)count; - (void)pushConstantData; - (void)pushConstantSize; - (void)texture; - } - - void GFXDevice::renderFrame() - { - pimpl->FRAMECOUNT++; - } - - gfx::Pipeline* GFXDevice::createPipeline(const char* vertShaderPath, const char* fragShaderPath, const gfx::VertexFormat& vertexFormat, uint64_t uniformBufferSize, bool alphaBlending, bool backfaceCulling) - { - (void)vertShaderPath; - (void)fragShaderPath; - (void)vertexFormat; - (void)uniformBufferSize; - (void)alphaBlending; - (void)backfaceCulling; - return nullptr; - } - - void GFXDevice::destroyPipeline(const gfx::Pipeline* pipeline) - { - (void)pipeline; - } - - void GFXDevice::updateUniformBuffer(const gfx::Pipeline* pipeline, const void* data, size_t size, uint32_t offset) - { - (void)pipeline; - (void)data; - (void)size; - (void)offset; - } - - gfx::Buffer* GFXDevice::createBuffer(gfx::BufferType type, uint64_t size, const void* data) - { - (void)type; - (void)size; - (void)data; - return nullptr; - } - - void GFXDevice::destroyBuffer(const gfx::Buffer* buffer) - { - (void)buffer; - } - - gfx::Texture* GFXDevice::createTexture( - const void* imageData, - uint32_t width, - uint32_t height, - gfx::TextureFilter minFilter, - gfx::TextureFilter magFilter, - gfx::MipmapSetting mipmapSetting, - bool useAnisotropy) - { - (void)imageData; - (void)width; - (void)height; - (void)minFilter; - (void)magFilter; - (void)mipmapSetting; - (void)useAnisotropy; - return nullptr; - } - - void GFXDevice::destroyTexture(const gfx::Texture* texture) - { - (void)texture; - } - - void GFXDevice::waitIdle() - { - } - -} - -#endif diff --git a/src/gfx_device_vulkan.cpp b/src/gfx_device_vulkan.cpp index 0dc7713..fe71b3d 100644 --- a/src/gfx_device_vulkan.cpp +++ b/src/gfx_device_vulkan.cpp @@ -1,25 +1,5 @@ // The implementation of the graphics layer using Vulkan 1.3. -#ifdef ENGINE_BUILD_VULKAN - -#include "gfx_device.hpp" -#include "util.hpp" -#include "config.h" -#include "log.hpp" -#include "util/files.hpp" - -#include - -#define VMA_STATIC_VULKAN_FUNCTIONS 0 -#define VMA_DYNAMIC_VULKAN_FUNCTIONS 0 -#define VMA_VULKAN_VERSION 1003000 -#define VMA_IMPLEMENTATION -#include - -#include - -#include - #include #include #include @@ -30,6 +10,22 @@ #include #include +#include + +#include + +#include + +#include "gfx_device.hpp" +#include "vulkan/instance.h" +#include "vulkan/device.h" +#include "vulkan/gpu_allocator.h" +#include "vulkan/swapchain.h" +#include "util.hpp" +#include "config.h" +#include "log.hpp" +#include "util/files.hpp" + namespace engine { static constexpr uint32_t FRAMES_IN_FLIGHT = 2; // This improved FPS by 5x! (on Intel IGPU) @@ -38,56 +34,12 @@ namespace engine { // structures and enums - struct LayerInfo { - std::vector layersAvailable{}; - std::optional::iterator> validationLayer; - }; - - struct Queue { - uint32_t familyIndex; - uint32_t queueIndex; - bool supportsGraphics; - bool supportsTransfer; - bool supportsCompute; - - VkQueue handle; - }; - struct DepthBuffer { VkImage image; VmaAllocation allocation; VkImageView view; }; - struct Swapchain { - VkSwapchainKHR swapchain = VK_NULL_HANDLE; - - VkExtent2D extent; - VkSurfaceFormatKHR surfaceFormat; - VkPresentModeKHR presentMode; - - std::vector images{}; - std::vector imageViews{}; - std::vector framebuffers{}; - - DepthBuffer depthBuffer{}; - - // multisampling - VkSampleCountFlagBits msaaSamples{}; - struct MSTarget { - VkImage colorImage{}; - VmaAllocation colorImageAllocation{}; - VkImageView colorImageView{}; - } msTarget{}; - - VkQueue activeQueue{}; - - VkRenderPass renderpass; - - std::array acquireSemaphores{}; // waits until the image is available - std::array releaseSemaphores{}; // waits until rendering finishes - }; - struct DrawCall { const gfx::Pipeline* pipeline = nullptr; // for performance, keep this the same for consecutive draw calls const gfx::Buffer* vertexBuffer = nullptr; @@ -97,12 +49,6 @@ namespace engine { const gfx::Texture* texture = nullptr; }; - enum class QueueFlags : uint32_t { - GRAPHICS = (1 << 0), - TRANSFER = (1 << 1), - COMPUTE = (1 << 2), - }; - // handles struct gfx::Buffer { @@ -130,7 +76,10 @@ namespace engine { uint32_t mipLevels; }; - + struct gfx::CommandBuffer { + uint32_t frameIndex; // needed to identify correct command buffer, fences and semaphores + uint32_t imageIndex; // for swapchain present + }; // enum converters @@ -199,6 +148,7 @@ namespace engine { // functions +#if 0 static VkShaderModule compileShader(VkDevice device, shaderc_shader_kind kind, const std::string& source, const char* filename) { @@ -245,165 +195,6 @@ namespace engine { } - static std::vector getRequiredVulkanExtensions(SDL_Window* window) - { - [[maybe_unused]] SDL_bool res; - - unsigned int sdlExtensionCount = 0; - res = SDL_Vulkan_GetInstanceExtensions(window, &sdlExtensionCount, nullptr); - assert(res == SDL_TRUE); - std::vector requiredExtensions(sdlExtensionCount); - res = SDL_Vulkan_GetInstanceExtensions(window, &sdlExtensionCount, requiredExtensions.data()); - assert(res == SDL_TRUE); - - return requiredExtensions; - } - - static LayerInfo getAvailableLayers(bool useValidation) - { - constexpr const char* VALIDATION_LAYER_NAME = "VK_LAYER_KHRONOS_validation"; - - LayerInfo info; - [[maybe_unused]] VkResult res; - - uint32_t layerCount; - res = vkEnumerateInstanceLayerProperties(&layerCount, nullptr); - assert(res == VK_SUCCESS); - info.layersAvailable.resize(layerCount); - res = vkEnumerateInstanceLayerProperties(&layerCount, info.layersAvailable.data()); - assert(res == VK_SUCCESS); - - if (useValidation == true) { - // find validation layer and print all layers to log - for (auto it = info.layersAvailable.begin(); it != info.layersAvailable.end(); it++) { - if (strncmp(it->layerName, VALIDATION_LAYER_NAME, 256) == 0) { - info.validationLayer = it; - } - } - if (info.validationLayer.has_value() == false) { - throw std::runtime_error("The validation layer was not found. Quitting."); - } - } - - return info; - } - - static VkBool32 messengerCallback( - VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, - VkDebugUtilsMessageTypeFlagsEXT messageTypes, - const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, - void* pUserData) - { - (void)pUserData; - - std::string msgType{}; - - if (messageTypes & VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) - msgType += " (GENERAL)"; - if (messageTypes & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT) - msgType += " (PERF.)"; - if (messageTypes & VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) - msgType += " (VALID.)"; - - switch (messageSeverity) { - case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: - DEBUG("VULKAN MESSAGE{}: ID: {} MSG: {}", msgType, pCallbackData->pMessageIdName, pCallbackData->pMessage); - break; - case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: - INFO("VULKAN MESSAGE{}: ID: {} MSG: {}", msgType, pCallbackData->pMessageIdName, pCallbackData->pMessage); - break; - case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: - WARN("VULKAN MESSAGE{}: ID: {} MSG: {}", msgType, pCallbackData->pMessageIdName, pCallbackData->pMessage); - break; - case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: - ERROR("VULKAN MESSAGE{}: ID: {} MSG: {}", msgType, pCallbackData->pMessageIdName, pCallbackData->pMessage); - break; - default: - break; - } - return VK_FALSE; - } - - static VkDebugUtilsMessengerCreateInfoEXT getDebugMessengerCreateInfo() - { - VkDebugUtilsMessengerCreateInfoEXT debugMessengerInfo{ - .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, - .pNext = nullptr, - .flags = 0, - .messageSeverity = 0, - .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, - .pfnUserCallback = messengerCallback, - .pUserData = nullptr, - }; - - enum class MessageSeverity { - VERBOSE, - INFO, - WARNING, - ERROR - }; - - constexpr MessageSeverity MESSAGE_LEVEL = MessageSeverity::WARNING; - switch (MESSAGE_LEVEL) { - case MessageSeverity::VERBOSE: - debugMessengerInfo.messageSeverity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT; - // fall-through - case MessageSeverity::INFO: - debugMessengerInfo.messageSeverity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT; - // fall-through - case MessageSeverity::WARNING: - debugMessengerInfo.messageSeverity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT; - // fall-through - case MessageSeverity::ERROR: - debugMessengerInfo.messageSeverity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; - // fall-through - default: - break; - } - - return debugMessengerInfo; - } - - static VkSurfaceKHR createSurface(SDL_Window* window, VkInstance instance) - { - VkSurfaceKHR surface; - - if (SDL_Vulkan_CreateSurface(window, instance, &surface) == false) { - throw std::runtime_error("Unable to create window surface"); - } - - return surface; - } - - // returns the queue supporting the requested flags - static Queue getQueueSupporting(const std::vector queues, QueueFlags flags) - { - uint32_t bitmask = static_cast(flags); - - for (size_t i = 0; i < queues.size(); i++) { - - if (bitmask & static_cast(QueueFlags::GRAPHICS)) { - if (queues[i].supportsGraphics == false) continue; - } - - if (bitmask & static_cast(QueueFlags::TRANSFER)) { - if (queues[i].supportsTransfer == false) continue; - } - - if (bitmask & static_cast(QueueFlags::COMPUTE)) { - if (queues[i].supportsCompute == false) continue; - } - - return queues[i]; - - } - - throw std::runtime_error("Unable to find the requested queue"); - } - static Swapchain::MSTarget createMSAATarget(VkSampleCountFlagBits msaaSamples, VkExtent2D extent, VkFormat colorFormat, VkDevice device, VmaAllocator allocator) { Swapchain::MSTarget target{}; @@ -525,333 +316,6 @@ namespace engine { throw std::runtime_error("MSAA is not supported"); } - // This is called not just on initialisation, but also when the window is resized. - static void createSwapchain(VkDevice device, VkPhysicalDevice physicalDevice, VmaAllocator allocator, std::vector queues, SDL_Window* window, VkSurfaceKHR surface, gfx::GraphicsSettings settings, Swapchain* swapchain) - { - [[maybe_unused]] VkResult res; - - // get surface capabilities - VkSurfaceCapabilitiesKHR caps; - res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, &caps); - assert(res == VK_SUCCESS); - - // check there is at least one supported surface format - uint32_t surfaceFormatCount = 0; - std::vector formats{}; - res = vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &surfaceFormatCount, nullptr); - assert(res == VK_SUCCESS); - formats.resize(surfaceFormatCount); - res = vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &surfaceFormatCount, formats.data()); - assert(res == VK_SUCCESS); - - // check there is at least one supported present mode - uint32_t surfacePresentModeCount = 0; - std::vector presentModes{}; - res = vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &surfacePresentModeCount, nullptr); - assert(res == VK_SUCCESS); - presentModes.resize(surfacePresentModeCount); - res = vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &surfacePresentModeCount, presentModes.data()); - assert(res == VK_SUCCESS); - - VkExtent2D oldExtent = swapchain->extent; - - if (caps.currentExtent.width != std::numeric_limits::max()) { - swapchain->extent = caps.currentExtent; - } - else { - // if fb size isn't already found, get it from SDL - int width, height; - SDL_Vulkan_GetDrawableSize(window, &width, &height); - - swapchain->extent.width = static_cast(width); - swapchain->extent.height = static_cast(height); - - swapchain->extent.width = std::clamp( - swapchain->extent.width, - caps.minImageExtent.width, caps.maxImageExtent.width); - swapchain->extent.height = std::clamp( - swapchain->extent.height, - caps.minImageExtent.height, caps.maxImageExtent.height); - } - - if (swapchain->extent.width == 0 || swapchain->extent.height == 0) { - swapchain->extent = oldExtent; - } - - // delete old framebuffers - for (VkFramebuffer fb : swapchain->framebuffers) { - vkDestroyFramebuffer(device, fb, nullptr); - } - - // delete old image views - for (VkImageView view : swapchain->imageViews) { - vkDestroyImageView(device, view, nullptr); - } - - swapchain->surfaceFormat = formats[0]; - for (const auto& format : formats) { - if (format.format == VK_FORMAT_B8G8R8A8_SRGB && - format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { - swapchain->surfaceFormat = format; // prefer using srgb non linear colors - } - } - - swapchain->presentMode = VK_PRESENT_MODE_FIFO_KHR; // This mode is always available - if (settings.vsync == true) { - if (settings.waitForPresent == false) { - for (const auto& presMode : presentModes) { - if (presMode == VK_PRESENT_MODE_MAILBOX_KHR) { - swapchain->presentMode = presMode; // this mode allows V-sync without fixing FPS to refresh rate - } - } - } - } else { - for (const auto& presMode : presentModes) { - if (presMode == VK_PRESENT_MODE_IMMEDIATE_KHR) { - swapchain->presentMode = presMode; // V-sync off - } - } - } - - INFO("V-sync: {}", swapchain->presentMode == VK_PRESENT_MODE_FIFO_KHR || swapchain->presentMode == VK_PRESENT_MODE_MAILBOX_KHR ? "ON" : "OFF"); - - uint32_t imageCount = caps.minImageCount + 1; - if (caps.maxImageCount > 0 && imageCount > caps.maxImageCount) { - imageCount = caps.maxImageCount; - } - - VkSwapchainCreateInfoKHR createInfo{ - .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, - .pNext = nullptr, - .flags = 0, - .surface = surface, - .minImageCount = imageCount, - .imageFormat = swapchain->surfaceFormat.format, - .imageColorSpace = swapchain->surfaceFormat.colorSpace, - .imageExtent = swapchain->extent, - .imageArrayLayers = 1, - .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, - .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, - .queueFamilyIndexCount = 0, - .pQueueFamilyIndices = nullptr, - .preTransform = caps.currentTransform, - .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, - .presentMode = swapchain->presentMode, - .clipped = VK_TRUE, - .oldSwapchain = swapchain->swapchain, - - }; - - std::array queueFamilyIndices{ - getQueueSupporting(queues, QueueFlags::GRAPHICS).familyIndex, - getQueueSupporting(queues, QueueFlags::TRANSFER).familyIndex - }; - - // if graphics and transfer queues aren't in the same family - if (queueFamilyIndices[0] != queueFamilyIndices[1]) { - throw std::runtime_error("Graphics and transfer queues must be in the same family"); - } - - res = vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapchain->swapchain); - assert(res == VK_SUCCESS); - - if (createInfo.oldSwapchain != VK_NULL_HANDLE) { - // if recreating swapchain, destroy old one - vkDestroySwapchainKHR(device, createInfo.oldSwapchain, nullptr); - } - - // get all the image handles - uint32_t swapchainImageCount = 0; - res = vkGetSwapchainImagesKHR(device, swapchain->swapchain, &swapchainImageCount, nullptr); - assert(res == VK_SUCCESS); - swapchain->images.resize(swapchainImageCount); - res = vkGetSwapchainImagesKHR(device, swapchain->swapchain, &swapchainImageCount, swapchain->images.data()); - assert(res == VK_SUCCESS); - - // Use multisample anti-aliasing - if (settings.msaaLevel != gfx::MSAALevel::MSAA_OFF) - swapchain->msaaSamples = getMaxSampleCount(physicalDevice, settings.msaaLevel); - else - swapchain->msaaSamples = VK_SAMPLE_COUNT_1_BIT; - - INFO("Multisampling mode: {}", swapchain->msaaSamples == VK_SAMPLE_COUNT_1_BIT ? "OFF" : std::to_string(swapchain->msaaSamples) + "x"); - - // create depth buffer if old depth buffer is wrong size. - // Also do the same for the MSAA buffer. - if (swapchain->swapchain == VK_NULL_HANDLE) { - swapchain->depthBuffer = createDepthBuffer(device, allocator, swapchain->extent, swapchain->msaaSamples); - swapchain->msTarget = createMSAATarget(swapchain->msaaSamples, swapchain->extent, swapchain->surfaceFormat.format, device, allocator); - } - else if (swapchain->extent.width != oldExtent.width || swapchain->extent.height != oldExtent.height) { - destroyDepthBuffer(swapchain->depthBuffer, device, allocator); - swapchain->depthBuffer = createDepthBuffer(device, allocator, swapchain->extent, swapchain->msaaSamples); - destroyMSAATarget(swapchain->msTarget, device, allocator); - swapchain->msTarget = createMSAATarget(swapchain->msaaSamples, swapchain->extent, swapchain->surfaceFormat.format, device, allocator); - } - - VkAttachmentReference colorAttachmentRef{}; - VkAttachmentReference depthAttachmentRef{}; - VkAttachmentReference colorAttachmentResolveRef{}; - - // create the render pass - if (swapchain->renderpass == VK_NULL_HANDLE) { - - /* - * render pass layout: - * 0: color attachment with msaa samples, - * 1: depth attachment with msaa samples, used for fragment shading - * 2: present src (resolve) attachment with 1 sample, used for swapchain present - * - * if msaa is disabled, 0 is used for swapchain present and 2 is ignored - */ - - VkAttachmentDescription colorAttachment{}; - colorAttachment.format = swapchain->surfaceFormat.format; - colorAttachment.samples = swapchain->msaaSamples; - 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; - if (swapchain->msaaSamples == VK_SAMPLE_COUNT_1_BIT) { - colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - } else { - colorAttachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - } - - colorAttachmentRef.attachment = 0; - colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - - VkAttachmentDescription depthAttachment{}; - depthAttachment.format = VK_FORMAT_D32_SFLOAT; - depthAttachment.samples = swapchain->msaaSamples; - depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - - depthAttachmentRef.attachment = 1; - depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - - VkAttachmentDescription colorAttachmentResolve{}; - colorAttachmentResolve.format = swapchain->surfaceFormat.format; - colorAttachmentResolve.samples = VK_SAMPLE_COUNT_1_BIT; - colorAttachmentResolve.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - colorAttachmentResolve.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - colorAttachmentResolve.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - colorAttachmentResolve.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - colorAttachmentResolve.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - colorAttachmentResolve.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - - colorAttachmentResolveRef.attachment = 2; - colorAttachmentResolveRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - - VkSubpassDescription subpass{}; - subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpass.colorAttachmentCount = 1; - subpass.pColorAttachments = &colorAttachmentRef; - subpass.pDepthStencilAttachment = &depthAttachmentRef; - if (swapchain->msaaSamples == VK_SAMPLE_COUNT_1_BIT) { - subpass.pResolveAttachments = nullptr; - } else { - subpass.pResolveAttachments = &colorAttachmentResolveRef; - } - - VkSubpassDependency dependency{}; - dependency.srcSubpass = VK_SUBPASS_EXTERNAL; - dependency.dstSubpass = 0; - dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; - dependency.srcAccessMask = 0; - dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; - dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - - std::array attachments = { colorAttachment, depthAttachment, colorAttachmentResolve }; - - VkRenderPassCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - if (swapchain->msaaSamples == VK_SAMPLE_COUNT_1_BIT) { - createInfo.attachmentCount = 2; - } else { - createInfo.attachmentCount = 3; - } - createInfo.pAttachments = attachments.data(); - createInfo.subpassCount = 1; - createInfo.pSubpasses = &subpass; - createInfo.dependencyCount = 1; - createInfo.pDependencies = &dependency; - - res = vkCreateRenderPass(device, &createInfo, nullptr, &swapchain->renderpass); - } - - // create image views and framebuffers - - swapchain->imageViews.resize(swapchain->images.size()); - swapchain->framebuffers.resize(swapchain->images.size()); - for (size_t i = 0; i < swapchain->images.size(); i++) { - - VkImageViewCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - createInfo.pNext = nullptr; - createInfo.image = swapchain->images[i]; - createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - createInfo.format = swapchain->surfaceFormat.format; - createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - createInfo.subresourceRange.baseMipLevel = 0; - createInfo.subresourceRange.levelCount = 1; - createInfo.subresourceRange.baseArrayLayer = 0; - createInfo.subresourceRange.layerCount = 1; - res = vkCreateImageView(device, &createInfo, nullptr, &swapchain->imageViews[i]); - assert(res == VK_SUCCESS); - - std::array attachments = { - swapchain->msTarget.colorImageView, - swapchain->depthBuffer.view, - swapchain->imageViews[i], - }; - - VkFramebufferCreateInfo framebufferInfo{}; - framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; - framebufferInfo.renderPass = swapchain->renderpass; - if (swapchain->msaaSamples == VK_SAMPLE_COUNT_1_BIT) { - attachments[0] = swapchain->imageViews[i]; - framebufferInfo.attachmentCount = 2; - } else { - framebufferInfo.attachmentCount = 3; - } - framebufferInfo.pAttachments = attachments.data(); - framebufferInfo.width = swapchain->extent.width; - framebufferInfo.height = swapchain->extent.height; - framebufferInfo.layers = 1; - - res = vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapchain->framebuffers[i]); - assert(res == VK_SUCCESS); - - } - - // create the swapchain semaphores - VkSemaphoreCreateInfo semaphoreInfo{}; - semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - for (auto& acquireSemaphore : swapchain->acquireSemaphores) { - if (acquireSemaphore == VK_NULL_HANDLE) { - res = vkCreateSemaphore(device, &semaphoreInfo, nullptr, &acquireSemaphore); - assert(res == VK_SUCCESS); - } - } - for (auto& releaseSemaphore : swapchain->releaseSemaphores) { - if (releaseSemaphore == VK_NULL_HANDLE) { - res = vkCreateSemaphore(device, &semaphoreInfo, nullptr, &releaseSemaphore); - assert(res == VK_SUCCESS); - } - } - - } - static void copyBuffer(VkDevice device, VkCommandPool commandPool, VkQueue queue, VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) { [[maybe_unused]] VkResult res; @@ -899,7 +363,7 @@ namespace engine { } - VkCommandBuffer beginOneTimeCommands(VkDevice device, VkCommandPool commandPool) + static VkCommandBuffer beginOneTimeCommands(VkDevice device, VkCommandPool commandPool) { [[maybe_unused]] VkResult res; @@ -1054,45 +518,31 @@ namespace engine { 0, nullptr, 1, &barrier); } +#endif // class definitions struct GFXDevice::Impl { - VkInstance instance = VK_NULL_HANDLE; - VkDebugUtilsMessengerEXT debugMessenger = VK_NULL_HANDLE; - - SDL_Window* window = nullptr; - - VkSurfaceKHR surface = VK_NULL_HANDLE; - - VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VkDevice device = VK_NULL_HANDLE; - - Swapchain swapchain{}; - - std::vector queues{}; - Queue gfxQueue{}; - Queue presentQueue{}; - VkCommandPool commandPool = VK_NULL_HANDLE; - - VmaAllocator allocator = nullptr; - // device settings gfx::GraphicsSettings graphicsSettings; - - // device properties/limits - float maxSamplerAnisotropy; - // render loop + SDL_Window* window = nullptr; + Instance instance{}; + VkSurfaceKHR surface = VK_NULL_HANDLE; + Device device{}; + VmaAllocator allocator{}; + SwapchainInfo swapchainInfo{}; + Swapchain swapchain{}; + uint64_t FRAMECOUNT = 0; - std::array commandBuffers{}; - std::array inFlightFences{}; - std::queue drawQueue{}; - VkDescriptorSetLayoutBinding uboLayoutBinding{}; - VkDescriptorSetLayout descriptorSetLayout{}; - VkDescriptorSetLayoutBinding samplerLayoutBinding{}; - VkDescriptorSetLayout samplerSetLayout{}; + + // temp + VkFence renderFence = VK_NULL_HANDLE; + VkSemaphore presentSemaphore = VK_NULL_HANDLE; + VkSemaphore renderSemaphore = VK_NULL_HANDLE; + VkCommandBuffer drawBuf = VK_NULL_HANDLE; + bool swapchainIsOutOfDate = false; }; @@ -1119,454 +569,73 @@ namespace engine { throw std::runtime_error("The loaded Vulkan version must be at least 1.3"); } - bool useValidation; -#ifdef NDEBUG - useValidation = false; // release mode -#else - useValidation = true; // debug mode -#endif + pimpl->instance = createVulkanInstance(pimpl->window, appName, appVersion); - // get the both the engine and application versions - int appVersionMajor = 0, appVersionMinor = 0, appVersionPatch = 0; - versionFromCharArray(appVersion, &appVersionMajor, &appVersionMinor, &appVersionPatch); - int engineVersionMajor = 0, engineVersionMinor = 0, engineVersionPatch = 0; - versionFromCharArray(ENGINE_VERSION, &engineVersionMajor, &engineVersionMinor, &engineVersionPatch); - - VkApplicationInfo applicationInfo{ - .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, - .pNext = nullptr, - .pApplicationName = appName, - .applicationVersion = VK_MAKE_VERSION(appVersionMajor, appVersionMinor, appVersionPatch), - .pEngineName = "engine", - .engineVersion = VK_MAKE_VERSION(engineVersionMajor, engineVersionMinor, engineVersionPatch), - .apiVersion = VK_API_VERSION_1_3, + if (SDL_Vulkan_CreateSurface(pimpl->window, pimpl->instance.instance, &pimpl->surface) == false) { + throw std::runtime_error("Unable to create window surface"); }; + DeviceRequirements deviceRequirements{}; + deviceRequirements.requiredExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; + deviceRequirements.requiredFeatures.samplerAnisotropy = VK_TRUE; + deviceRequirements.sampledImageLinearFilter = true; + pimpl->device = createDevice(pimpl->instance.instance, deviceRequirements, pimpl->surface); + pimpl->allocator = createAllocator(pimpl->instance.instance, pimpl->device.device, pimpl->device.physicalDevice); - // make a list of all extensions to use - std::vector extensions{}; + pimpl->swapchainInfo.device = pimpl->device.device; + pimpl->swapchainInfo.physicalDevice = pimpl->device.physicalDevice; + pimpl->swapchainInfo.surface = pimpl->surface; + pimpl->swapchainInfo.window = pimpl->window; + pimpl->swapchainInfo.vsync = pimpl->graphicsSettings.vsync; + pimpl->swapchainInfo.waitForPresent = pimpl->graphicsSettings.waitForPresent; + createSwapchain(&pimpl->swapchain, pimpl->swapchainInfo); - const std::vector windowExtensions = getRequiredVulkanExtensions(window); - extensions.insert(extensions.end(), windowExtensions.begin(), windowExtensions.end()); + /* the following is temporary setup */ - // also use debug utils extension - extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); - - - - // make a list of layers to use - std::vector layers{}; - - const LayerInfo layerInfo = getAvailableLayers(useValidation); - - if (layerInfo.validationLayer.has_value()) { - layers.push_back(layerInfo.validationLayer.value()->layerName); - } - - VkInstanceCreateInfo instanceInfo{ - .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + VkFenceCreateInfo fenceInfo{ + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .pNext = nullptr, - .flags = 0, - .pApplicationInfo = &applicationInfo, - .enabledLayerCount = (uint32_t)layers.size(), - .ppEnabledLayerNames = layers.data(), - .enabledExtensionCount = (uint32_t)extensions.size(), - .ppEnabledExtensionNames = extensions.data(), + .flags = VK_FENCE_CREATE_SIGNALED_BIT }; - - // add the debug messenger - VkDebugUtilsMessengerCreateInfoEXT debugMessengerInfo; - if (layerInfo.validationLayer.has_value()) { - debugMessengerInfo = getDebugMessengerCreateInfo(); - instanceInfo.pNext = &debugMessengerInfo; - } - else { - instanceInfo.pNext = nullptr; - } - - - - for ([[maybe_unused]] const char* ext : extensions) { - DEBUG("Using Vulkan instance extension: {}", ext); - } - - res = vkCreateInstance(&instanceInfo, nullptr, &pimpl->instance); - if (res == VK_ERROR_INCOMPATIBLE_DRIVER) { - throw std::runtime_error("The graphics driver is incompatible with vulkan"); - } else if (res != VK_SUCCESS) { - throw std::runtime_error("vkCreateInstance failed: " + std::to_string(res)); - } - - - - // load the instance functions - volkLoadInstanceOnly(pimpl->instance); - - - - // create the debug messenger - { - VkDebugUtilsMessengerCreateInfoEXT createInfo = getDebugMessengerCreateInfo(); - - VkResult res; - res = vkCreateDebugUtilsMessengerEXT(pimpl->instance, &createInfo, nullptr, &pimpl->debugMessenger); - if (res != VK_SUCCESS) { - throw std::runtime_error("vkCreateDebugUtilsMessengerExt failed: " + std::to_string(res)); - } - } - - - - // get the surface - pimpl->surface = createSurface(window, pimpl->instance); - - - - // Select a physical device and get capabilities, features, and display modes. - // Create a logical device and create the queues and their corresponding command buffers. - { - // enumerate physical devices - uint32_t physDeviceCount = 0; - VkResult res; - res = vkEnumeratePhysicalDevices(pimpl->instance, &physDeviceCount, nullptr); - assert(res == VK_SUCCESS); - if (physDeviceCount == 0) { - throw std::runtime_error("No GPU found with vulkan support!"); - } - std::vector physicalDevices(physDeviceCount); - res = vkEnumeratePhysicalDevices(pimpl->instance, &physDeviceCount, physicalDevices.data()); - assert(res == VK_SUCCESS); - - // find suitable device: - - const std::vector requiredDeviceExtensions{ - VK_KHR_SWAPCHAIN_EXTENSION_NAME, - VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, - }; - - for (const auto& dev : physicalDevices) { - - // first, check extension support - uint32_t extensionCount; - res = vkEnumerateDeviceExtensionProperties(dev, nullptr, &extensionCount, nullptr); - assert(res == VK_SUCCESS); - std::vector availableExtensions(extensionCount); - res = vkEnumerateDeviceExtensionProperties(dev, nullptr, &extensionCount, availableExtensions.data()); - assert(res == VK_SUCCESS); - - for (const char* extToFind : requiredDeviceExtensions) { - bool extFound = false; - for (const auto& ext : availableExtensions) { - if (strcmp(extToFind, ext.extensionName) == 0) { - extFound = true; - } - } - if (!extFound) { - continue; - } - } - - - - // check physical device properties - VkPhysicalDeviceProperties devProps; - vkGetPhysicalDeviceProperties(dev, &devProps); - - // check that the device supports vulkan 1.3 - if (devProps.apiVersion < VK_API_VERSION_1_3) { - continue; - } - - // check for some features: - VkPhysicalDeviceFeatures devFeatures; - vkGetPhysicalDeviceFeatures(dev, &devFeatures); - // anisotropic filtering is needed - if (devFeatures.samplerAnisotropy == VK_FALSE) continue; - pimpl->maxSamplerAnisotropy = devProps.limits.maxSamplerAnisotropy; - - // check for linear filtering for mipmaps - VkFormatProperties formatProperties{}; - vkGetPhysicalDeviceFormatProperties(dev, VK_FORMAT_R8G8B8A8_SRGB, &formatProperties); - if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) { - continue; - } - - pimpl->physicalDevice = dev; - break; - - } // end for() - - if (pimpl->physicalDevice == VK_NULL_HANDLE) { - throw std::runtime_error("No suitable Vulkan physical device found"); - } - - VkPhysicalDeviceProperties devProps; - vkGetPhysicalDeviceProperties(pimpl->physicalDevice, &devProps); - DEBUG("Selected physical device: {}", devProps.deviceName); - - - - // Get the queue families and find ones that support graphics, transfer, and compute - - uint32_t queueFamilyCount = 0; - vkGetPhysicalDeviceQueueFamilyProperties(pimpl->physicalDevice, &queueFamilyCount, nullptr); - std::vector queueFamilies(queueFamilyCount); - vkGetPhysicalDeviceQueueFamilyProperties(pimpl->physicalDevice, &queueFamilyCount, queueFamilies.data()); - - std::optional graphicsFamilyIndex; - std::optional transferFamilyIndex; - std::optional computeFamilyIndex; - - for (uint32_t i = 0; i < queueFamilyCount; i++) { - VkQueueFamilyProperties family = queueFamilies[i]; - if (family.queueCount > 0) { - if (graphicsFamilyIndex.has_value() == false && family.queueFlags & VK_QUEUE_GRAPHICS_BIT) { - graphicsFamilyIndex = i; - } - if (transferFamilyIndex.has_value() == false && family.queueFlags & VK_QUEUE_TRANSFER_BIT) { - transferFamilyIndex = i; - } - if (computeFamilyIndex.has_value() == false && family.queueFlags & VK_QUEUE_COMPUTE_BIT) { - computeFamilyIndex = i; - } - } - } - if (graphicsFamilyIndex.has_value() == false || - transferFamilyIndex.has_value() == false) { - throw std::runtime_error("Unable to find queues with the GRAPHICS or TRANSFER family flags"); - } - - // there is no guaranteed support for compute queues - - std::vector queueCreateInfos{}; - - // use a set to filter out duplicate indices - std::unordered_set uniqueQueueFamilies{}; - if (graphicsFamilyIndex.has_value()) uniqueQueueFamilies.insert(graphicsFamilyIndex.value()); - if (transferFamilyIndex.has_value()) uniqueQueueFamilies.insert(transferFamilyIndex.value()); - if (computeFamilyIndex.has_value()) uniqueQueueFamilies.insert(computeFamilyIndex.value()); - - float queuePriority = 1.0f; - - for (uint32_t family : uniqueQueueFamilies) { - // create a queue for each unique type to ensure that there are queues available for graphics, transfer, and compute - - Queue newQueue{}; - newQueue.familyIndex = family; - newQueue.queueIndex = 0; - newQueue.supportsGraphics = false; - newQueue.supportsTransfer = false; - newQueue.supportsCompute = false; - if (graphicsFamilyIndex == family) newQueue.supportsGraphics = true; - if (transferFamilyIndex == family) newQueue.supportsTransfer = true; - if (computeFamilyIndex == family) newQueue.supportsCompute = true; - - VkDeviceQueueCreateInfo queueCreateInfo{ - .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .queueFamilyIndex = family, - .queueCount = 1, - .pQueuePriorities = &queuePriority, - }; - queueCreateInfos.push_back(queueCreateInfo); - pimpl->queues.push_back(newQueue); - } - - // check the physical device is compatible with the surface - VkBool32 graphicsQueueCanPresent; - res = vkGetPhysicalDeviceSurfaceSupportKHR(pimpl->physicalDevice, graphicsFamilyIndex.value(), pimpl->surface, &graphicsQueueCanPresent); - assert(res == VK_SUCCESS); - if (graphicsQueueCanPresent != VK_TRUE) { - throw std::runtime_error("The selected queue family does not support this surface"); - } - - VkPhysicalDeviceFeatures deviceFeatures{}; - deviceFeatures.samplerAnisotropy = VK_TRUE; - - VkDeviceCreateInfo deviceCreateInfo{ - .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .queueCreateInfoCount = (uint32_t)queueCreateInfos.size(), - .pQueueCreateInfos = queueCreateInfos.data(), - .enabledLayerCount = 0, - .ppEnabledLayerNames = nullptr, - .enabledExtensionCount = (uint32_t)requiredDeviceExtensions.size(), - .ppEnabledExtensionNames = requiredDeviceExtensions.data(), - .pEnabledFeatures = &deviceFeatures, - }; - - for ([[maybe_unused]] const char* ext : requiredDeviceExtensions) { - DEBUG("Using Vulkan device extension: {}", ext); - } - - res = vkCreateDevice(pimpl->physicalDevice, &deviceCreateInfo, nullptr, &pimpl->device); - if (res != VK_SUCCESS) { - throw std::runtime_error("Unable to create Vulkan logical device, error code: " + std::to_string(res)); - } - - volkLoadDevice(pimpl->device); - - for (auto& q : pimpl->queues) { - vkGetDeviceQueue(pimpl->device, q.familyIndex, q.queueIndex, &q.handle); - } - - pimpl->presentQueue = getQueueSupporting(pimpl->queues, QueueFlags::TRANSFER); - pimpl->gfxQueue = getQueueSupporting(pimpl->queues, QueueFlags::GRAPHICS); - - VkCommandPoolCreateInfo gfxCmdPoolInfo{ - .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, - .pNext = nullptr, - .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, - .queueFamilyIndex = pimpl->gfxQueue.familyIndex - }; - - res = vkCreateCommandPool(pimpl->device, &gfxCmdPoolInfo, nullptr, &pimpl->commandPool); - assert(res == VK_SUCCESS); - - VkCommandBufferAllocateInfo gfxCmdBufInfo{ - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, - .pNext = nullptr, - .commandPool = pimpl->commandPool, - .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, - .commandBufferCount = 1 - }; - - for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; i++) { - res = vkAllocateCommandBuffers(pimpl->device, &gfxCmdBufInfo, &pimpl->commandBuffers[i]); - assert(res == VK_SUCCESS); - } - } - - - - // now make the memory allocator using vk_mem_alloc.h - { - VmaVulkanFunctions functions{ - .vkGetInstanceProcAddr = nullptr, - .vkGetDeviceProcAddr = nullptr, - .vkGetPhysicalDeviceProperties = vkGetPhysicalDeviceProperties, - .vkGetPhysicalDeviceMemoryProperties = vkGetPhysicalDeviceMemoryProperties, - .vkAllocateMemory = vkAllocateMemory, - .vkFreeMemory = vkFreeMemory, - .vkMapMemory = vkMapMemory, - .vkUnmapMemory = vkUnmapMemory, - .vkFlushMappedMemoryRanges = vkFlushMappedMemoryRanges, - .vkInvalidateMappedMemoryRanges = vkInvalidateMappedMemoryRanges, - .vkBindBufferMemory = vkBindBufferMemory, - .vkBindImageMemory = vkBindImageMemory, - .vkGetBufferMemoryRequirements = vkGetBufferMemoryRequirements, - .vkGetImageMemoryRequirements = vkGetImageMemoryRequirements, - .vkCreateBuffer = vkCreateBuffer, - .vkDestroyBuffer = vkDestroyBuffer, - .vkCreateImage = vkCreateImage, - .vkDestroyImage = vkDestroyImage, - .vkCmdCopyBuffer = vkCmdCopyBuffer, - .vkGetBufferMemoryRequirements2KHR = vkGetBufferMemoryRequirements2, - .vkGetImageMemoryRequirements2KHR = vkGetImageMemoryRequirements2, - .vkBindBufferMemory2KHR = vkBindBufferMemory2, - .vkBindImageMemory2KHR = vkBindImageMemory2, - .vkGetPhysicalDeviceMemoryProperties2KHR = vkGetPhysicalDeviceMemoryProperties2, - .vkGetDeviceBufferMemoryRequirements = vkGetDeviceBufferMemoryRequirements, - .vkGetDeviceImageMemoryRequirements = vkGetDeviceImageMemoryRequirements, - }; - - VmaAllocatorCreateInfo createInfo{ - .flags = 0, - .physicalDevice = pimpl->physicalDevice, - .device = pimpl->device, - .preferredLargeHeapBlockSize = 0, - .pAllocationCallbacks = nullptr, - .pDeviceMemoryCallbacks = nullptr, - .pHeapSizeLimit = nullptr, - .pVulkanFunctions = &functions, - .instance = pimpl->instance, - .vulkanApiVersion = VK_API_VERSION_1_3, - .pTypeExternalMemoryHandleTypes = nullptr - }; - - [[maybe_unused]] VkResult res; - res = vmaCreateAllocator(&createInfo, &pimpl->allocator); - assert(res == VK_SUCCESS); - } - - - - // Now make the swapchain - createSwapchain(pimpl->device, pimpl->physicalDevice, pimpl->allocator, pimpl->queues, window, pimpl->surface, pimpl->graphicsSettings, &pimpl->swapchain); - - - - VkFenceCreateInfo fenceInfo{}; - fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; - fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - fenceInfo.pNext = nullptr; - for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; i++) { - res = vkCreateFence(pimpl->device, &fenceInfo, nullptr, &pimpl->inFlightFences[i]); - assert(res == VK_SUCCESS); - } - - // create uniform buffer descriptor set layout - pimpl->uboLayoutBinding.binding = 0; - pimpl->uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - pimpl->uboLayoutBinding.descriptorCount = 1; - pimpl->uboLayoutBinding.stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS; - pimpl->uboLayoutBinding.pImmutableSamplers = nullptr; - - VkDescriptorSetLayoutCreateInfo descriptorSetLayoutInfo{}; - descriptorSetLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - descriptorSetLayoutInfo.bindingCount = 1; - descriptorSetLayoutInfo.pBindings = &pimpl->uboLayoutBinding; - res = vkCreateDescriptorSetLayout(pimpl->device, &descriptorSetLayoutInfo, nullptr, &pimpl->descriptorSetLayout); + res = vkCreateFence(pimpl->device.device, &fenceInfo, nullptr, &pimpl->renderFence); + if (res != VK_SUCCESS) throw std::runtime_error("Failed to create fence!"); + + VkSemaphoreCreateInfo smphInfo{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .pNext = nullptr, + .flags = 0 + }; + res = vkCreateSemaphore(pimpl->device.device, &smphInfo, nullptr, &pimpl->presentSemaphore); + if (res != VK_SUCCESS) throw std::runtime_error("Failed to create semaphore!"); + res = vkCreateSemaphore(pimpl->device.device, &smphInfo, nullptr, &pimpl->renderSemaphore); + if (res != VK_SUCCESS) throw std::runtime_error("Failed to create semaphore!"); + + // Create the command buffer for rendering: + VkCommandBufferAllocateInfo cmdAllocInfo{ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .pNext = nullptr, + .commandPool = pimpl->device.commandPools.draw, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1 + }; + res = vkAllocateCommandBuffers(pimpl->device.device, &cmdAllocInfo, &pimpl->drawBuf); assert(res == VK_SUCCESS); - // create texture sampler descriptor set layout - pimpl->samplerLayoutBinding.binding = 0; - pimpl->samplerLayoutBinding.descriptorCount = 1; - pimpl->samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - pimpl->samplerLayoutBinding.pImmutableSamplers = nullptr; - pimpl->samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; - - VkDescriptorSetLayoutCreateInfo samplerSetLayoutInfo{}; - samplerSetLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - samplerSetLayoutInfo.bindingCount = 1; - samplerSetLayoutInfo.pBindings = &pimpl->samplerLayoutBinding; - res = vkCreateDescriptorSetLayout(pimpl->device, &samplerSetLayoutInfo, nullptr, &pimpl->samplerSetLayout); - assert(res == VK_SUCCESS); - - } GFXDevice::~GFXDevice() { - vkDestroyDescriptorSetLayout(pimpl->device, pimpl->samplerSetLayout, nullptr); - vkDestroyDescriptorSetLayout(pimpl->device, pimpl->descriptorSetLayout, nullptr); + vkFreeCommandBuffers(pimpl->device.device, pimpl->device.commandPools.draw, 1, &pimpl->drawBuf); + vkDestroySemaphore(pimpl->device.device, pimpl->renderSemaphore, nullptr); + vkDestroySemaphore(pimpl->device.device, pimpl->presentSemaphore, nullptr); + vkDestroyFence(pimpl->device.device, pimpl->renderFence, nullptr); - for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; i++) { - vkDestroyFence(pimpl->device, pimpl->inFlightFences[i], nullptr); - vkDestroySemaphore(pimpl->device, pimpl->swapchain.releaseSemaphores[i], nullptr); - vkDestroySemaphore(pimpl->device, pimpl->swapchain.acquireSemaphores[i], nullptr); - } - for (VkImageView view : pimpl->swapchain.imageViews) { - vkDestroyImageView(pimpl->device, view, nullptr); - } - for (VkFramebuffer fb : pimpl->swapchain.framebuffers) { - vkDestroyFramebuffer(pimpl->device, fb, nullptr); - } - destroyMSAATarget(pimpl->swapchain.msTarget, pimpl->device, pimpl->allocator); - destroyDepthBuffer(pimpl->swapchain.depthBuffer, pimpl->device, pimpl->allocator); - vkDestroyRenderPass(pimpl->device, pimpl->swapchain.renderpass, nullptr); - vkDestroySwapchainKHR(pimpl->device, pimpl->swapchain.swapchain, nullptr); - - vmaDestroyAllocator(pimpl->allocator); - - vkDestroyCommandPool(pimpl->device, pimpl->commandPool, nullptr); - vkDestroyDevice(pimpl->device, nullptr); - vkDestroySurfaceKHR(pimpl->instance, pimpl->surface, nullptr); - vkDestroyDebugUtilsMessengerEXT(pimpl->instance, pimpl->debugMessenger, nullptr); - vkDestroyInstance(pimpl->instance, nullptr); + destroySwapchain(pimpl->swapchain); + destroyAllocator(pimpl->allocator); + destroyDevice(pimpl->device); + vkDestroySurfaceKHR(pimpl->instance.instance, pimpl->surface, nullptr); + destroyVulkanInstance(pimpl->instance); } void GFXDevice::getViewportSize(uint32_t *w, uint32_t *h) @@ -1581,82 +650,68 @@ namespace engine { *w = (uint32_t)width; *h = (uint32_t)height; } - } - void GFXDevice::draw(const gfx::Pipeline* pipeline, const gfx::Buffer* vertexBuffer, const gfx::Buffer* indexBuffer, uint32_t count, const void* pushConstantData, size_t pushConstantSize, const gfx::Texture* texture) - { - assert(pipeline != nullptr); - assert(vertexBuffer != nullptr); - assert(vertexBuffer->type == gfx::BufferType::VERTEX); - assert(indexBuffer == nullptr || indexBuffer->type == gfx::BufferType::INDEX); - assert(pushConstantSize <= PUSH_CONSTANT_MAX_SIZE); - - DrawCall call{ - .pipeline = pipeline, - .vertexBuffer = vertexBuffer, - .indexBuffer = indexBuffer, // will be ignored if nullptr - .count = count, - .pushConstantData{} - }; - - memcpy(call.pushConstantData, pushConstantData, pushConstantSize); - - call.texture = texture; // will be ignored if nullptr - - pimpl->drawQueue.push(call); - - } - - void GFXDevice::renderFrame() + gfx::CommandBuffer* GFXDevice::beginRender() { VkResult res; - const uint32_t frameIndex = pimpl->FRAMECOUNT % FRAMES_IN_FLIGHT; + uint32_t swapchainImageIndex; - res = vkWaitForFences(pimpl->device, 1, &pimpl->inFlightFences[frameIndex], VK_TRUE, UINT64_MAX); + do { + if (pimpl->swapchainIsOutOfDate) { + // re-create swapchain + vkQueueWaitIdle(pimpl->device.queues.drawQueues[0]); + vkQueueWaitIdle(pimpl->device.queues.presentQueue); + createSwapchain(&pimpl->swapchain, pimpl->swapchainInfo); + } + // THIS FUNCTION BLOCKS UNTIL AN IMAGE IS AVAILABLE (it waits for vsync) + res = vkAcquireNextImageKHR( + pimpl->device.device, pimpl->swapchain.swapchain, 1000000000LL, + pimpl->presentSemaphore, VK_NULL_HANDLE, &swapchainImageIndex); + assert(res == VK_SUCCESS || res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR); + if (res == VK_SUCCESS) pimpl->swapchainIsOutOfDate = false; + } while (pimpl->swapchainIsOutOfDate); + + /* wait until the previous frame RENDERING has finished */ + res = vkWaitForFences(pimpl->device.device, 1, &pimpl->renderFence, VK_TRUE, 1000000000LL); + if (res == VK_TIMEOUT) throw std::runtime_error("Render fence timed out!"); assert(res == VK_SUCCESS); - res = vkResetFences(pimpl->device, 1, &pimpl->inFlightFences[frameIndex]); + res = vkResetFences(pimpl->device.device, 1, &pimpl->renderFence); assert(res == VK_SUCCESS); - uint32_t imageIndex = 0; - res = vkAcquireNextImageKHR(pimpl->device, pimpl->swapchain.swapchain, UINT64_MAX, pimpl->swapchain.acquireSemaphores[frameIndex], VK_NULL_HANDLE, &imageIndex); - if (res == VK_ERROR_OUT_OF_DATE_KHR) { - // recreate swapchain - waitIdle(); - createSwapchain(pimpl->device, pimpl->physicalDevice, pimpl->allocator, pimpl->queues, pimpl->window, pimpl->surface, pimpl->graphicsSettings, &pimpl->swapchain); - return; - } - else { - assert(res == VK_SUCCESS || res == VK_SUBOPTIMAL_KHR); - } - - res = vkResetCommandBuffer(pimpl->commandBuffers[frameIndex], 0); + /* record command buffer */ + res = vkResetCommandBuffer(pimpl->drawBuf, 0); assert(res == VK_SUCCESS); - - // now record command buffer - { - VkCommandBufferBeginInfo beginInfo{}; - beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - beginInfo.flags = 0; - beginInfo.pInheritanceInfo = nullptr; - res = vkBeginCommandBuffer(pimpl->commandBuffers[frameIndex], &beginInfo); - assert(res == VK_SUCCESS); - VkRenderPassBeginInfo renderPassInfo{}; - renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; - renderPassInfo.renderPass = pimpl->swapchain.renderpass; - renderPassInfo.framebuffer = pimpl->swapchain.framebuffers[imageIndex]; - renderPassInfo.renderArea.offset = { 0, 0 }; - renderPassInfo.renderArea.extent = pimpl->swapchain.extent; + VkCommandBufferBeginInfo beginInfo{ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .pNext = nullptr, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + .pInheritanceInfo = nullptr // ignored + }; + res = vkBeginCommandBuffer(pimpl->drawBuf, &beginInfo); + assert(res == VK_SUCCESS); - std::array clearValues{}; - clearValues[0].color = { {1.0f, 1.0f, 1.0f, 1.0f} }; - clearValues[1].depthStencil = { 1.0f, 0 }; - renderPassInfo.clearValueCount = (uint32_t)clearValues.size(); - renderPassInfo.pClearValues = clearValues.data(); - - vkCmdBeginRenderPass(pimpl->commandBuffers[frameIndex], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + { // RECORDING + + VkClearValue clearValue{}; + clearValue.color.float32[0] = 1.0f; + clearValue.color.float32[1] = 0.0f; + clearValue.color.float32[2] = 0.0f; + clearValue.color.float32[3] = 1.0f; + + VkRenderPassBeginInfo passBegin{ + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + .pNext = nullptr + }; + passBegin.renderPass = pimpl->swapchain.renderpass; + passBegin.framebuffer = std::get<2>(pimpl->swapchain.images[swapchainImageIndex]); + passBegin.renderArea.extent = pimpl->swapchain.extent; + passBegin.renderArea.offset = { 0, 0 }; + passBegin.clearValueCount = 1; + passBegin.pClearValues = &clearValue; + vkCmdBeginRenderPass(pimpl->drawBuf, &passBegin, VK_SUBPASS_CONTENTS_INLINE); VkViewport viewport{}; viewport.x = 0.0f; @@ -1665,106 +720,77 @@ namespace engine { viewport.height = -(float)pimpl->swapchain.extent.height; viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; - vkCmdSetViewport(pimpl->commandBuffers[frameIndex], 0, 1, &viewport); + vkCmdSetViewport(pimpl->drawBuf, 0, 1, &viewport); VkRect2D scissor{}; scissor.offset = { 0, 0 }; scissor.extent = pimpl->swapchain.extent; - vkCmdSetScissor(pimpl->commandBuffers[frameIndex], 0, 1, &scissor); + vkCmdSetScissor(pimpl->drawBuf, 0, 1, &scissor); - // run queued draw calls - - VkDeviceSize offsets[] = { 0 }; - - const gfx::Pipeline* lastPipeline = nullptr; - const gfx::Texture* lastTexture = nullptr; - const gfx::Buffer* lastVertexBuffer = nullptr; - const gfx::Buffer* lastIndexBuffer = nullptr; - while (pimpl->drawQueue.empty() == false) { - - DrawCall call = pimpl->drawQueue.front(); - - if (call.pipeline != lastPipeline) { - vkCmdBindPipeline(pimpl->commandBuffers[frameIndex], VK_PIPELINE_BIND_POINT_GRAPHICS, call.pipeline->handle); - // bind pipeline uniform-buffer - vkCmdBindDescriptorSets(pimpl->commandBuffers[frameIndex], VK_PIPELINE_BIND_POINT_GRAPHICS, call.pipeline->layout, 0, 1, &call.pipeline->descriptorSets[frameIndex], 0, nullptr); - } - - if (call.texture != lastTexture) { - // set the texture - vkCmdBindDescriptorSets(pimpl->commandBuffers[frameIndex], VK_PIPELINE_BIND_POINT_GRAPHICS, call.pipeline->layout, 1, 1, &call.texture->descriptorSets[frameIndex], 0, nullptr); - } - - // like uniforms but faster - vkCmdPushConstants(pimpl->commandBuffers[frameIndex], call.pipeline->layout, VK_SHADER_STAGE_VERTEX_BIT, 0, PUSH_CONSTANT_MAX_SIZE, call.pushConstantData); - - if (call.vertexBuffer != lastVertexBuffer) { - vkCmdBindVertexBuffers(pimpl->commandBuffers[frameIndex], 0, 1, &call.vertexBuffer->buffer, offsets); - } - if (call.indexBuffer == nullptr) { - // no index buffer - vkCmdDraw(pimpl->commandBuffers[frameIndex], call.count, 1, 0, 0); - } else { - // use index buffer - if (call.indexBuffer != lastIndexBuffer) { - vkCmdBindIndexBuffer(pimpl->commandBuffers[frameIndex], call.indexBuffer->buffer, 0, VK_INDEX_TYPE_UINT32); - } - vkCmdDrawIndexed(pimpl->commandBuffers[frameIndex], call.count, 1, 0, 0, 0); - } - - lastPipeline = call.pipeline; - lastTexture = call.texture; - lastVertexBuffer = call.vertexBuffer; - lastIndexBuffer = call.indexBuffer; - - pimpl->drawQueue.pop(); - - } - - vkCmdEndRenderPass(pimpl->commandBuffers[frameIndex]); - - res = vkEndCommandBuffer(pimpl->commandBuffers[frameIndex]); - assert(res == VK_SUCCESS); } - VkSubmitInfo submitInfo{}; - submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + // hand command buffer over to caller + gfx::CommandBuffer* commandBuffer = new gfx::CommandBuffer; + commandBuffer->imageIndex = swapchainImageIndex; + return commandBuffer; - VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; - submitInfo.waitSemaphoreCount = 1; - submitInfo.pWaitSemaphores = &pimpl->swapchain.acquireSemaphores[frameIndex]; - submitInfo.pWaitDstStageMask = waitStages; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &pimpl->commandBuffers[frameIndex]; - submitInfo.signalSemaphoreCount = 1; - submitInfo.pSignalSemaphores = &pimpl->swapchain.releaseSemaphores[frameIndex]; + } - res = vkQueueSubmit(pimpl->gfxQueue.handle, 1, &submitInfo, pimpl->inFlightFences[frameIndex]); + void GFXDevice::finishRender(gfx::CommandBuffer* commandBuffer) + { + if (commandBuffer == nullptr) { + return; + } + uint32_t swapchainImageIndex = commandBuffer->imageIndex; + VkResult res; + + vkCmdEndRenderPass(pimpl->drawBuf); + + res = vkEndCommandBuffer(pimpl->drawBuf); assert(res == VK_SUCCESS); - VkPresentInfoKHR presentInfo{}; - presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; - presentInfo.waitSemaphoreCount = 1; - presentInfo.pWaitSemaphores = &pimpl->swapchain.releaseSemaphores[frameIndex]; + // SUBMIT - presentInfo.swapchainCount = 1; - presentInfo.pSwapchains = &pimpl->swapchain.swapchain; - presentInfo.pImageIndices = &imageIndex; - presentInfo.pResults = nullptr; + VkPipelineStageFlags waitStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - res = vkQueuePresentKHR(pimpl->presentQueue.handle, &presentInfo); + VkSubmitInfo submitInfo{ + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .pNext = nullptr, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &pimpl->presentSemaphore, + .pWaitDstStageMask = &waitStage, + .commandBufferCount = 1, + .pCommandBuffers = &pimpl->drawBuf, + .signalSemaphoreCount = 1, + .pSignalSemaphores = &pimpl->renderSemaphore, + }; + res = vkQueueSubmit(pimpl->device.queues.drawQueues[0], 1, &submitInfo, pimpl->renderFence); + assert(res == VK_SUCCESS); + + // PRESENT + + VkPresentInfoKHR presentInfo{ + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .pNext = nullptr, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &pimpl->renderSemaphore, + .swapchainCount = 1, + .pSwapchains = &pimpl->swapchain.swapchain, + .pImageIndices = &swapchainImageIndex, + .pResults = nullptr + }; + res = vkQueuePresentKHR(pimpl->device.queues.presentQueue, &presentInfo); if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR) { - // recreate swapchain - waitIdle(); - createSwapchain(pimpl->device, pimpl->physicalDevice, pimpl->allocator, pimpl->queues, pimpl->window, pimpl->surface, pimpl->graphicsSettings, &pimpl->swapchain); - } - else { - assert(res == VK_SUCCESS); + // flag to re-create the swapchain before next render + pimpl->swapchainIsOutOfDate = true; } + else if (res != VK_SUCCESS) throw std::runtime_error("Failed to queue present!"); pimpl->FRAMECOUNT++; + + delete commandBuffer; } - + gfx::Pipeline* GFXDevice::createPipeline(const char* vertShaderPath, const char* fragShaderPath, const gfx::VertexFormat& vertexFormat, uint64_t uniformBufferSize, bool alphaBlending, bool backfaceCulling) { @@ -1772,11 +798,12 @@ namespace engine { gfx::Pipeline* pipeline = new gfx::Pipeline; +#if 0 auto vertShaderCode = util::readTextFile(vertShaderPath); auto fragShaderCode = util::readTextFile(fragShaderPath); - VkShaderModule vertShaderModule = compileShader(pimpl->device, shaderc_vertex_shader, vertShaderCode->data(), vertShaderPath); - VkShaderModule fragShaderModule = compileShader(pimpl->device, shaderc_fragment_shader, fragShaderCode->data(), fragShaderPath); + VkShaderModule vertShaderModule = compileShader(pimpl->device.device, shaderc_vertex_shader, vertShaderCode->data(), vertShaderPath); + VkShaderModule fragShaderModule = compileShader(pimpl->device.device, shaderc_fragment_shader, fragShaderCode->data(), fragShaderPath); // create uniform buffers pipeline->uniformBuffers.resize(FRAMES_IN_FLIGHT); @@ -1813,7 +840,7 @@ namespace engine { poolInfo.poolSizeCount = 1; poolInfo.pPoolSizes = &poolSize; poolInfo.maxSets = FRAMES_IN_FLIGHT; - res = vkCreateDescriptorPool(pimpl->device, &poolInfo, nullptr, &pipeline->descriptorPool); + res = vkCreateDescriptorPool(pimpl->device.device, &poolInfo, nullptr, &pipeline->descriptorPool); assert(res == VK_SUCCESS); std::array layouts; @@ -2031,6 +1058,8 @@ namespace engine { vkDestroyShaderModule(pimpl->device, fragShaderModule, nullptr); vkDestroyShaderModule(pimpl->device, vertShaderModule, nullptr); +#endif + return pipeline; } @@ -2038,6 +1067,7 @@ namespace engine { void GFXDevice::destroyPipeline(const gfx::Pipeline* pipeline) { +#if 0 vkDestroyPipeline(pimpl->device, pipeline->handle, nullptr); vkDestroyPipelineLayout(pimpl->device, pipeline->layout, nullptr); @@ -2046,12 +1076,15 @@ namespace engine { for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; i++) { destroyBuffer(pipeline->uniformBuffers[i]); } +#endif delete pipeline; } void GFXDevice::updateUniformBuffer(const gfx::Pipeline* pipeline, const void* data, size_t size, uint32_t offset) { + +#if 0 assert(size <= pipeline->uniformBuffers[0]->size); [[maybe_unused]] VkResult res; @@ -2063,6 +1096,7 @@ namespace engine { memcpy((uint8_t*)uniformDest + offset, data, size); vmaUnmapMemory(pimpl->allocator, buffer->allocation); } +#endif } @@ -2075,6 +1109,8 @@ namespace engine { out->type = type; +#if 0 + VkBuffer stagingBuffer; VmaAllocation stagingAllocation; @@ -2124,13 +1160,16 @@ namespace engine { // destroy staging buffer vmaDestroyBuffer(pimpl->allocator, stagingBuffer, stagingAllocation); - +#endif return out; + } void GFXDevice::destroyBuffer(const gfx::Buffer* buffer) { +#if 0 vmaDestroyBuffer(pimpl->allocator, buffer->buffer, buffer->allocation); +#endif delete buffer; } @@ -2145,6 +1184,8 @@ namespace engine { { auto out = new gfx::Texture; +#if 0 + [[maybe_unused]] VkResult res; size_t imageSize = width * height * 4; @@ -2152,7 +1193,7 @@ namespace engine { if (mipmapSetting == gfx::MipmapSetting::OFF) { out->mipLevels = 1; } else { - out->mipLevels = static_cast(std::floor(std::log2(std::max(width, height)))) + 1; + out->mipLevels = static_cast(std::floor(std::log2( std::max(width, height) ))) + 1; } // first load image into staging buffer @@ -2332,22 +1373,23 @@ namespace engine { vkUpdateDescriptorSets(pimpl->device, 1, &descriptorWrite, 0, nullptr); } +#endif return out; } void GFXDevice::destroyTexture(const gfx::Texture* texture) { +#if 0 vkDestroyDescriptorPool(pimpl->device, texture->pool, nullptr); vkDestroySampler(pimpl->device, texture->sampler, nullptr); vkDestroyImageView(pimpl->device, texture->imageView, nullptr); vmaDestroyImage(pimpl->allocator, texture->image, texture->alloc); +#endif } void GFXDevice::waitIdle() { - vkDeviceWaitIdle(pimpl->device); + vkDeviceWaitIdle(pimpl->device.device); } -} - -#endif +} \ No newline at end of file diff --git a/src/resources/mesh.cpp b/src/resources/mesh.cpp index 8376518..1deb802 100644 --- a/src/resources/mesh.cpp +++ b/src/resources/mesh.cpp @@ -41,7 +41,7 @@ namespace engine::resources { m_vb = m_gfx->createBuffer(gfx::BufferType::VERTEX, vertices.size() * sizeof(Vertex), vertices.data()); m_ib = m_gfx->createBuffer(gfx::BufferType::INDEX, indices.size() * sizeof(uint32_t), indices.data()); m_count = (uint32_t)indices.size(); - INFO("Loaded mesh, vertices: {}, indices: {}", vertices.size(), indices.size()); + LOG_INFO("Loaded mesh, vertices: {}, indices: {}", vertices.size(), indices.size()); } } diff --git a/src/resources/shader.cpp b/src/resources/shader.cpp index 25c3f7e..6258d6e 100644 --- a/src/resources/shader.cpp +++ b/src/resources/shader.cpp @@ -38,7 +38,7 @@ namespace engine::resources { m_pipeline = m_gfx->createPipeline(vertPath, fragPath, vertFormat, sizeof(glm::mat4) * 2, alphaBlending, cullBackFace); - INFO("Loaded shader: {}, vertex attribs: {}", vertPath, vertFormat.attributeDescriptions.size()); + LOG_INFO("Loaded shader: {}, vertex attribs: {}", vertPath, vertFormat.attributeDescriptions.size()); } Shader::~Shader() diff --git a/src/resources/texture.cpp b/src/resources/texture.cpp index 16bf2c8..0d428ce 100644 --- a/src/resources/texture.cpp +++ b/src/resources/texture.cpp @@ -57,7 +57,7 @@ Texture::Texture(GFXDevice* gfxDevice, const std::string& path, Filtering filter mipmapSetting, anisotropyEnable); - INFO("Loaded texture: {}, width: {} height: {}", path, width, height); + LOG_INFO("Loaded texture: {}, width: {} height: {}", path, width, height); } diff --git a/src/systems/collisions.cpp b/src/systems/collisions.cpp index 917dc24..8c1b887 100644 --- a/src/systems/collisions.cpp +++ b/src/systems/collisions.cpp @@ -79,7 +79,7 @@ namespace engine { m_dynamicAABBs.reserve(size); m_possibleCollisions.reserve(size); m_collisionInfos.reserve(size); - TRACE("added entity {} to collider system", entity); + LOG_TRACE("added entity {} to collider system", entity); } void PhysicsSystem::onUpdate(float ts) diff --git a/src/systems/render.cpp b/src/systems/render.cpp index a201703..e993f44 100644 --- a/src/systems/render.cpp +++ b/src/systems/render.cpp @@ -76,6 +76,7 @@ namespace engine { pushConsts.model = t->worldMatrix; pushConsts.view = viewMatrix; + /* gfx->draw( r->material->getShader()->getPipeline(), r->mesh->getVB(), @@ -84,7 +85,7 @@ namespace engine { &pushConsts, sizeof(pushConsts), r->material->m_texture->getHandle() - ); + );*/ } diff --git a/src/util/model_loader.cpp b/src/util/model_loader.cpp index 0a50bdb..1177ef2 100644 --- a/src/util/model_loader.cpp +++ b/src/util/model_loader.cpp @@ -77,11 +77,11 @@ namespace engine::util { auto child = scene->createEntity("_mesh" + std::to_string(i), parentObj); auto childRenderer = scene->addComponent(child); childRenderer->mesh = meshes[parentNode->mMeshes[i]]; - childRenderer->material = std::make_shared(scene->app()->getResource("engine.textured")); + childRenderer->material = std::make_shared(scene->app()->getResource("builtin.standard")); if (textures.contains(meshTextureIndices[parentNode->mMeshes[i]])) { childRenderer->material->m_texture = textures.at(meshTextureIndices[parentNode->mMeshes[i]]); } else { - childRenderer->material->m_texture = scene->app()->getResource("engine.white"); + childRenderer->material->m_texture = scene->app()->getResource("builtin.white"); } } @@ -105,7 +105,7 @@ namespace engine::util { public: void write(const char* message) override { (void)message; - TRACE("ASSIMP: {}", message); + LOG_TRACE("ASSIMP: {}", message); } }; @@ -160,23 +160,23 @@ namespace engine::util { assert(scene->HasLights() == false); assert(scene->hasSkeletons() == false); - TRACE("material count: {}, mesh count: {}", scene->mNumMaterials, scene->mNumMeshes); + LOG_TRACE("material count: {}, mesh count: {}", scene->mNumMaterials, scene->mNumMeshes); std::map> textures{}; for (uint32_t i = 0; i < scene->mNumMaterials; i++) { const aiMaterial* m = scene->mMaterials[i]; - TRACE("Material {}:", i); - TRACE(" Name: {}", m->GetName().C_Str()); + LOG_TRACE("Material {}:", i); + LOG_TRACE(" Name: {}", m->GetName().C_Str()); for (uint32_t j = 0; j < m->mNumProperties; j++) { [[maybe_unused]] const aiMaterialProperty* p = m->mProperties[j]; - TRACE(" prop {}, key: {}", j, p->mKey.C_Str()); + LOG_TRACE(" prop {}, key: {}", j, p->mKey.C_Str()); } if (aiGetMaterialTextureCount(m, aiTextureType_DIFFUSE) >= 1) { aiString texPath{}; aiGetMaterialTexture(m, aiTextureType_DIFFUSE, 0, &texPath); - TRACE(" Diffuse tex: {}", texPath.C_Str()); + LOG_TRACE(" Diffuse tex: {}", texPath.C_Str()); std::filesystem::path absPath = path; absPath = absPath.parent_path(); absPath /= texPath.C_Str(); @@ -185,7 +185,7 @@ namespace engine::util { parent->app()->gfx(), absPath.string(), resources::Texture::Filtering::TRILINEAR, true, true); } catch (const std::runtime_error&) { - textures[i] = parent->app()->getResource("engine.white"); + textures[i] = parent->app()->getResource("builtin.white"); } } } @@ -197,8 +197,8 @@ namespace engine::util { meshMaterialIndices.push_back(m->mMaterialIndex); std::vector vertices(m->mNumVertices); std::vector indices(m->mNumFaces * 3); - TRACE("Mesh {}: vertex count {}", i, vertices.size()); - TRACE("Mesh {}: index count {}", i, indices.size()); + LOG_TRACE("Mesh {}: vertex count {}", i, vertices.size()); + LOG_TRACE("Mesh {}: index count {}", i, indices.size()); for (uint32_t j = 0; j < vertices.size(); j++) { Vertex v{}; @@ -230,7 +230,7 @@ namespace engine::util { buildGraph(textures, meshes, meshMaterialIndices, scene->mRootNode, parent, obj); - INFO("Loaded model: {}, meshes: {}, textures: {}", scene->GetShortFilename(path.c_str()), meshes.size(), textures.size()); + LOG_INFO("Loaded model: {}, meshes: {}, textures: {}", scene->GetShortFilename(path.c_str()), meshes.size(), textures.size()); Assimp::DefaultLogger::kill(); return obj; diff --git a/src/vulkan/device.cpp b/src/vulkan/device.cpp new file mode 100644 index 0000000..23e56a2 --- /dev/null +++ b/src/vulkan/device.cpp @@ -0,0 +1,333 @@ +#include +#include +#include +#include +#include + +#include + +#include "device.h" + +namespace engine { + + /* chooses a device, creates it, gets its function pointers, and creates command pools */ + Device createDevice(VkInstance instance, DeviceRequirements requirements, VkSurfaceKHR surface) + { + Device d{}; + + // enumerate physical devices + uint32_t physDeviceCount = 0; + VkResult res; + res = vkEnumeratePhysicalDevices(instance, &physDeviceCount, nullptr); + assert(res == VK_SUCCESS); + if (physDeviceCount == 0) { + throw std::runtime_error("No GPU found with vulkan support!"); + } + std::vector physicalDevices(physDeviceCount); + res = vkEnumeratePhysicalDevices(instance, &physDeviceCount, physicalDevices.data()); + assert(res == VK_SUCCESS); + + for (VkPhysicalDevice physDev : physicalDevices) { + + // first, check extension support + uint32_t extensionCount; + res = vkEnumerateDeviceExtensionProperties(physDev, nullptr, &extensionCount, nullptr); + assert(res == VK_SUCCESS); + std::vector availableExtensions(extensionCount); + res = vkEnumerateDeviceExtensionProperties(physDev, nullptr, &extensionCount, availableExtensions.data()); + assert(res == VK_SUCCESS); + + bool foundRequiredExtensions = true; + for (const char* extToFind : requirements.requiredExtensions) { + bool extFound = false; + for (const auto& ext : availableExtensions) { + if (strcmp(extToFind, ext.extensionName) == 0) { + extFound = true; + break; + } + } + if (!extFound) { + foundRequiredExtensions = false; + break; + } + } + if (!foundRequiredExtensions) continue; // NEXT! + + VkPhysicalDeviceProperties devProps; + vkGetPhysicalDeviceProperties(physDev, &devProps); + + // check that the device supports vulkan 1.3 + if (devProps.apiVersion < VK_API_VERSION_1_3) { + continue; + } + + /* check features */ + VkPhysicalDeviceFeatures devFeatures; + vkGetPhysicalDeviceFeatures(physDev, &devFeatures); + { + if (requirements.requiredFeatures.robustBufferAccess) + if (devFeatures.robustBufferAccess == VK_FALSE) continue; + if (requirements.requiredFeatures.fullDrawIndexUint32) + if (devFeatures.fullDrawIndexUint32 == VK_FALSE) continue; + if (requirements.requiredFeatures.imageCubeArray == VK_TRUE) + if (devFeatures.imageCubeArray == VK_FALSE) continue; + if (requirements.requiredFeatures.independentBlend == VK_TRUE) + if (devFeatures.independentBlend == VK_FALSE) continue; + if (requirements.requiredFeatures.geometryShader == VK_TRUE) + if (devFeatures.geometryShader == VK_FALSE) continue; + if (requirements.requiredFeatures.tessellationShader == VK_TRUE) + if (devFeatures.tessellationShader == VK_FALSE) continue; + if (requirements.requiredFeatures.sampleRateShading == VK_TRUE) + if (devFeatures.sampleRateShading == VK_FALSE) continue; + if (requirements.requiredFeatures.dualSrcBlend == VK_TRUE) + if (devFeatures.dualSrcBlend == VK_FALSE) continue; + if (requirements.requiredFeatures.logicOp == VK_TRUE) + if (devFeatures.logicOp == VK_FALSE) continue; + if (requirements.requiredFeatures.multiDrawIndirect == VK_TRUE) + if (devFeatures.multiDrawIndirect == VK_FALSE) continue; + if (requirements.requiredFeatures.drawIndirectFirstInstance == VK_TRUE) + if (devFeatures.drawIndirectFirstInstance == VK_FALSE) continue; + if (requirements.requiredFeatures.depthClamp == VK_TRUE) + if (devFeatures.depthClamp == VK_FALSE) continue; + if (requirements.requiredFeatures.depthBiasClamp == VK_TRUE) + if (devFeatures.depthBiasClamp == VK_FALSE) continue; + if (requirements.requiredFeatures.fillModeNonSolid == VK_TRUE) + if (devFeatures.fillModeNonSolid == VK_FALSE) continue; + if (requirements.requiredFeatures.depthBounds == VK_TRUE) + if (devFeatures.depthBounds == VK_FALSE) continue; + if (requirements.requiredFeatures.wideLines == VK_TRUE) + if (devFeatures.wideLines == VK_FALSE) continue; + if (requirements.requiredFeatures.largePoints == VK_TRUE) + if (devFeatures.largePoints == VK_FALSE) continue; + if (requirements.requiredFeatures.alphaToOne == VK_TRUE) + if (devFeatures.alphaToOne == VK_FALSE) continue; + if (requirements.requiredFeatures.multiViewport == VK_TRUE) + if (devFeatures.multiViewport == VK_FALSE) continue; + if (requirements.requiredFeatures.samplerAnisotropy == VK_TRUE) + if (devFeatures.samplerAnisotropy == VK_FALSE) continue; + if (requirements.requiredFeatures.textureCompressionETC2 == VK_TRUE) + if (devFeatures.textureCompressionETC2 == VK_FALSE) continue; + if (requirements.requiredFeatures.textureCompressionASTC_LDR == VK_TRUE) + if (devFeatures.textureCompressionASTC_LDR == VK_FALSE) continue; + if (requirements.requiredFeatures.textureCompressionBC == VK_TRUE) + if (devFeatures.textureCompressionBC == VK_FALSE) continue; + if (requirements.requiredFeatures.occlusionQueryPrecise == VK_TRUE) + if (devFeatures.occlusionQueryPrecise == VK_FALSE) continue; + if (requirements.requiredFeatures.pipelineStatisticsQuery == VK_TRUE) + if (devFeatures.pipelineStatisticsQuery == VK_FALSE) continue; + if (requirements.requiredFeatures.vertexPipelineStoresAndAtomics == VK_TRUE) + if (devFeatures.vertexPipelineStoresAndAtomics == VK_FALSE) continue; + if (requirements.requiredFeatures.fragmentStoresAndAtomics == VK_TRUE) + if (devFeatures.fragmentStoresAndAtomics == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderTessellationAndGeometryPointSize == VK_TRUE) + if (devFeatures.shaderTessellationAndGeometryPointSize == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderImageGatherExtended == VK_TRUE) + if (devFeatures.shaderImageGatherExtended == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderStorageImageExtendedFormats == VK_TRUE) + if (devFeatures.shaderStorageImageExtendedFormats == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderStorageImageMultisample == VK_TRUE) + if (devFeatures.shaderStorageImageMultisample == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderStorageImageReadWithoutFormat == VK_TRUE) + if (devFeatures.shaderStorageImageReadWithoutFormat == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderStorageImageWriteWithoutFormat == VK_TRUE) + if (devFeatures.shaderStorageImageWriteWithoutFormat == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderUniformBufferArrayDynamicIndexing == VK_TRUE) + if (devFeatures.shaderUniformBufferArrayDynamicIndexing == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderSampledImageArrayDynamicIndexing == VK_TRUE) + if (devFeatures.shaderSampledImageArrayDynamicIndexing == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderStorageBufferArrayDynamicIndexing == VK_TRUE) + if (devFeatures.shaderStorageBufferArrayDynamicIndexing == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderStorageImageArrayDynamicIndexing == VK_TRUE) + if (devFeatures.shaderStorageImageArrayDynamicIndexing == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderClipDistance == VK_TRUE) + if (devFeatures.shaderClipDistance == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderCullDistance == VK_TRUE) + if (devFeatures.shaderCullDistance == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderFloat64 == VK_TRUE) + if (devFeatures.shaderFloat64 == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderInt64 == VK_TRUE) + if (devFeatures.shaderInt64 == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderInt16 == VK_TRUE) + if (devFeatures.shaderInt16 == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderResourceResidency == VK_TRUE) + if (devFeatures.shaderResourceResidency == VK_FALSE) continue; + if (requirements.requiredFeatures.shaderResourceMinLod == VK_TRUE) + if (devFeatures.shaderResourceMinLod == VK_FALSE) continue; + if (requirements.requiredFeatures.sparseBinding == VK_TRUE) + if (devFeatures.sparseBinding == VK_FALSE) continue; + if (requirements.requiredFeatures.sparseResidencyBuffer == VK_TRUE) + if (devFeatures.sparseResidencyBuffer == VK_FALSE) continue; + if (requirements.requiredFeatures.sparseResidencyImage2D == VK_TRUE) + if (devFeatures.sparseResidencyImage2D == VK_FALSE) continue; + if (requirements.requiredFeatures.sparseResidencyImage3D == VK_TRUE) + if (devFeatures.sparseResidencyImage3D == VK_FALSE) continue; + if (requirements.requiredFeatures.sparseResidency2Samples == VK_TRUE) + if (devFeatures.sparseResidency2Samples == VK_FALSE) continue; + if (requirements.requiredFeatures.sparseResidency4Samples == VK_TRUE) + if (devFeatures.sparseResidency4Samples == VK_FALSE) continue; + if (requirements.requiredFeatures.sparseResidency8Samples == VK_TRUE) + if (devFeatures.sparseResidency8Samples == VK_FALSE) continue; + if (requirements.requiredFeatures.sparseResidency16Samples == VK_TRUE) + if (devFeatures.sparseResidency16Samples == VK_FALSE) continue; + if (requirements.requiredFeatures.sparseResidencyAliased == VK_TRUE) + if (devFeatures.sparseResidencyAliased == VK_FALSE) continue; + if (requirements.requiredFeatures.variableMultisampleRate == VK_TRUE) + if (devFeatures.variableMultisampleRate == VK_FALSE) continue; + if (requirements.requiredFeatures.inheritedQueries == VK_TRUE) + if (devFeatures.inheritedQueries == VK_FALSE) continue; + } + + if (requirements.sampledImageLinearFilter == true) { + // check for linear filtering for mipmaps + VkFormatProperties formatProperties{}; + vkGetPhysicalDeviceFormatProperties(physDev, VK_FORMAT_R8G8B8A8_SRGB, &formatProperties); + if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) { + continue; + } + } + + /* USE THIS PHYSICAL DEVICE */ + d.physicalDevice = physDev; + d.properties = devProps; + d.features = requirements.requiredFeatures; // to be safe, only advertise requested features + //deviceInfo->features = devFeatures; + + break; + } + + if (d.physicalDevice == VK_NULL_HANDLE) { + throw std::runtime_error("No suitable Vulkan physical device found"); + } + + /* queue families */ + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(d.physicalDevice, &queueFamilyCount, nullptr); + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(d.physicalDevice, &queueFamilyCount, queueFamilies.data()); + + // find a graphics/present family + uint32_t graphicsFamily = UINT32_MAX; + for (size_t i = 0; i < queueFamilies.size(); i++) { + VkQueueFamilyProperties p = queueFamilies[i]; + + if (p.queueCount < 2) break; // need one queue for presenting and one-or-more for rendering + + if (p.queueFlags & VK_QUEUE_GRAPHICS_BIT) { + VkBool32 supportsPresent; + res = vkGetPhysicalDeviceSurfaceSupportKHR(d.physicalDevice, static_cast(i), surface, &supportsPresent); + if (res != VK_SUCCESS) throw std::runtime_error("Failed to check for queue family present support!"); + if (supportsPresent) { + graphicsFamily = static_cast(i); + break; + } + } + } + if (graphicsFamily == UINT32_MAX) throw std::runtime_error("Unable to find a graphics/present queue family!"); + + // find a transfer queue family (image layout transitions, buffer upload) + uint32_t transferFamily = UINT32_MAX; + for (size_t i = 0; i < queueFamilies.size(); i++) { + VkQueueFamilyProperties p = queueFamilies[i]; + if (((p.queueFlags & VK_QUEUE_TRANSFER_BIT) != 0) && + ((p.queueFlags & VK_QUEUE_COMPUTE_BIT) == 0) && + ((p.queueFlags & VK_QUEUE_GRAPHICS_BIT) == 0)) { + transferFamily = static_cast(i); + break; + } + } + if (transferFamily == UINT32_MAX) throw std::runtime_error("Unable to find a transfer queue family!"); + + // queue priorities + std::vector graphicsQueuePriorities(queueFamilies[graphicsFamily].queueCount); + std::fill(graphicsQueuePriorities.begin(), graphicsQueuePriorities.end(), 1.0f); + std::vector transferQueuePriorities(queueFamilies[transferFamily].queueCount); + std::fill(transferQueuePriorities.begin(), transferQueuePriorities.end(), 1.0f); + + std::array queueCreateInfos{ + VkDeviceQueueCreateInfo{ + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .queueFamilyIndex = graphicsFamily, + .queueCount = queueFamilies[graphicsFamily].queueCount, + .pQueuePriorities = graphicsQueuePriorities.data(), + }, + VkDeviceQueueCreateInfo{ + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .queueFamilyIndex = transferFamily, + .queueCount = queueFamilies[transferFamily].queueCount, + .pQueuePriorities = transferQueuePriorities.data(), + } + }; + + /* create device now */ + VkDeviceCreateInfo deviceCreateInfo{ + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .queueCreateInfoCount = static_cast(queueCreateInfos.size()), + .pQueueCreateInfos = queueCreateInfos.data(), + .enabledLayerCount = 0, // deprecated and ignored + .ppEnabledLayerNames = nullptr, // deprecated and ignored + .enabledExtensionCount = static_cast(requirements.requiredExtensions.size()), + .ppEnabledExtensionNames = requirements.requiredExtensions.data(), + .pEnabledFeatures = &requirements.requiredFeatures + }; + + res = vkCreateDevice(d.physicalDevice, &deviceCreateInfo, nullptr, &d.device); + if (res != VK_SUCCESS) { + throw std::runtime_error("Unable to create Vulkan logical device, error code: " + std::to_string(res)); + } + + volkLoadDevice(d.device); + + vkGetDeviceQueue(d.device, graphicsFamily, 0, &d.queues.presentQueue); + d.queues.drawQueues.resize(queueFamilies[graphicsFamily].queueCount - 1); + for (uint32_t i = 0; i < d.queues.drawQueues.size(); i++) { + vkGetDeviceQueue(d.device, graphicsFamily, i + 1, &d.queues.drawQueues[i]); + } + d.queues.transferQueues.resize(queueFamilies[transferFamily].queueCount); + for (uint32_t i = 0; i < d.queues.transferQueues.size(); i++) { + vkGetDeviceQueue(d.device, transferFamily, i, &d.queues.transferQueues[i]); + } + + d.queues.presentAndDrawQueueFamily = graphicsFamily; + d.queues.transferQueueFamily = transferFamily; + + /* generate command pools */ + VkCommandPoolCreateInfo poolCreateInfo{ + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .pNext = nullptr, + .flags = 0, // set individually after + .queueFamilyIndex = 0, // set individually after + }; + + // present queue does not need a command pool as it does not use command buffers + + // draw command pools: + poolCreateInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolCreateInfo.queueFamilyIndex = graphicsFamily; + res = vkCreateCommandPool(d.device, &poolCreateInfo, nullptr, &d.commandPools.draw); + if (res != VK_SUCCESS) throw std::runtime_error("Failed to create command pool"); + + // transfer command pools: + poolCreateInfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT; // buffers from this pool are often short-lived, + // as is usually the case for transfer operations + poolCreateInfo.queueFamilyIndex = transferFamily; + res = vkCreateCommandPool(d.device, &poolCreateInfo, nullptr, &d.commandPools.transfer); + if (res != VK_SUCCESS) throw std::runtime_error("Failed to create command pool"); + + return d; + + } + + void destroyDevice(Device device) + { + vkDestroyCommandPool(device.device, device.commandPools.transfer, nullptr); + vkDestroyCommandPool(device.device, device.commandPools.draw, nullptr); + vkDestroyDevice(device.device, nullptr); + } + +} \ No newline at end of file diff --git a/src/vulkan/device.h b/src/vulkan/device.h new file mode 100644 index 0000000..3865d12 --- /dev/null +++ b/src/vulkan/device.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include + +namespace engine { + + struct DeviceRequirements { + std::vector requiredExtensions; + VkPhysicalDeviceFeatures requiredFeatures; + bool sampledImageLinearFilter; + }; + + struct Device { + VkDevice device = VK_NULL_HANDLE; + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkPhysicalDeviceProperties properties{}; + VkPhysicalDeviceFeatures features{}; + + struct DeviceQueues { + VkQueue presentQueue = VK_NULL_HANDLE; + std::vector drawQueues{}; + std::vector transferQueues{}; + + uint32_t presentAndDrawQueueFamily = UINT32_MAX; + uint32_t transferQueueFamily = UINT32_MAX; + } queues{}; + + struct CommandPools { + VkCommandPool draw = VK_NULL_HANDLE; // use with the drawQueues + VkCommandPool transfer = VK_NULL_HANDLE; // use with the transferQueues + } commandPools{}; + }; + + Device createDevice(VkInstance instance, DeviceRequirements requirements, VkSurfaceKHR surface); + void destroyDevice(Device device); + +} \ No newline at end of file diff --git a/src/vulkan/gpu_allocator.cpp b/src/vulkan/gpu_allocator.cpp new file mode 100644 index 0000000..f62cfe4 --- /dev/null +++ b/src/vulkan/gpu_allocator.cpp @@ -0,0 +1,74 @@ +#include + +#include + +#define VMA_STATIC_VULKAN_FUNCTIONS 0 +#define VMA_DYNAMIC_VULKAN_FUNCTIONS 0 +#define VMA_VULKAN_VERSION 1003000 +#define VMA_IMPLEMENTATION +#include + +#include "gpu_allocator.h" + +namespace engine { + + VmaAllocator createAllocator(VkInstance instance, VkDevice device, VkPhysicalDevice physicalDevice) + { + VmaVulkanFunctions functions{ + .vkGetInstanceProcAddr = nullptr, + .vkGetDeviceProcAddr = nullptr, + .vkGetPhysicalDeviceProperties = vkGetPhysicalDeviceProperties, + .vkGetPhysicalDeviceMemoryProperties = vkGetPhysicalDeviceMemoryProperties, + .vkAllocateMemory = vkAllocateMemory, + .vkFreeMemory = vkFreeMemory, + .vkMapMemory = vkMapMemory, + .vkUnmapMemory = vkUnmapMemory, + .vkFlushMappedMemoryRanges = vkFlushMappedMemoryRanges, + .vkInvalidateMappedMemoryRanges = vkInvalidateMappedMemoryRanges, + .vkBindBufferMemory = vkBindBufferMemory, + .vkBindImageMemory = vkBindImageMemory, + .vkGetBufferMemoryRequirements = vkGetBufferMemoryRequirements, + .vkGetImageMemoryRequirements = vkGetImageMemoryRequirements, + .vkCreateBuffer = vkCreateBuffer, + .vkDestroyBuffer = vkDestroyBuffer, + .vkCreateImage = vkCreateImage, + .vkDestroyImage = vkDestroyImage, + .vkCmdCopyBuffer = vkCmdCopyBuffer, + .vkGetBufferMemoryRequirements2KHR = vkGetBufferMemoryRequirements2, + .vkGetImageMemoryRequirements2KHR = vkGetImageMemoryRequirements2, + .vkBindBufferMemory2KHR = vkBindBufferMemory2, + .vkBindImageMemory2KHR = vkBindImageMemory2, + .vkGetPhysicalDeviceMemoryProperties2KHR = vkGetPhysicalDeviceMemoryProperties2, + .vkGetDeviceBufferMemoryRequirements = vkGetDeviceBufferMemoryRequirements, + .vkGetDeviceImageMemoryRequirements = vkGetDeviceImageMemoryRequirements, + }; + + VmaAllocatorCreateInfo createInfo{ + .flags = 0, + .physicalDevice = physicalDevice, + .device = device, + .preferredLargeHeapBlockSize = 0, + .pAllocationCallbacks = nullptr, + .pDeviceMemoryCallbacks = nullptr, + .pHeapSizeLimit = nullptr, + .pVulkanFunctions = &functions, + .instance = instance, + .vulkanApiVersion = VK_API_VERSION_1_3, + .pTypeExternalMemoryHandleTypes = nullptr + }; + + [[maybe_unused]] VkResult res; + VmaAllocator allocator; + res = vmaCreateAllocator(&createInfo, &allocator); + assert(res == VK_SUCCESS); + + return allocator; + + } + + void destroyAllocator(VmaAllocator allocator) + { + vmaDestroyAllocator(allocator); + } + +} \ No newline at end of file diff --git a/src/vulkan/gpu_allocator.h b/src/vulkan/gpu_allocator.h new file mode 100644 index 0000000..a2e6181 --- /dev/null +++ b/src/vulkan/gpu_allocator.h @@ -0,0 +1,9 @@ +#pragma once +#include + +namespace engine { + + VmaAllocator createAllocator(VkInstance instance, VkDevice device, VkPhysicalDevice physicalDevice); + void destroyAllocator(VmaAllocator allocator); + +} \ No newline at end of file diff --git a/src/vulkan/instance.cpp b/src/vulkan/instance.cpp new file mode 100644 index 0000000..5de047b --- /dev/null +++ b/src/vulkan/instance.cpp @@ -0,0 +1,200 @@ +#include +#include +#include +#include + +#include + +#include "util.hpp" +#include "config.h" +#include "log.hpp" + +#include "instance.h" + +namespace engine { + + static std::vector getWindowExtensions(SDL_Window* window) + { + [[maybe_unused]] SDL_bool res; + + unsigned int sdlExtensionCount = 0; + res = SDL_Vulkan_GetInstanceExtensions(window, &sdlExtensionCount, nullptr); + assert(res == SDL_TRUE); + std::vector requiredExtensions(sdlExtensionCount); + res = SDL_Vulkan_GetInstanceExtensions(window, &sdlExtensionCount, requiredExtensions.data()); + assert(res == SDL_TRUE); + + return requiredExtensions; + } + + static const char* getValidationLayer() + { + const char* const VALIDATION_LAYER_NAME = "VK_LAYER_KHRONOS_validation"; + + [[maybe_unused]] VkResult res; + + uint32_t layerCount; + res = vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + assert(res == VK_SUCCESS); + std::vector layers(layerCount); + res = vkEnumerateInstanceLayerProperties(&layerCount, layers.data()); + assert(res == VK_SUCCESS); + + // find validation layer and print all layers to log + for (const auto& layer : layers) { + if (strncmp(layer.layerName, VALIDATION_LAYER_NAME, 256) == 0) { + return VALIDATION_LAYER_NAME; + } + } + + throw std::runtime_error("Unable to find validation layer!"); + + } + + static VkBool32 messengerCallback( + VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageTypes, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData) + { + (void)pUserData; + + std::string msgType{}; + + if (messageTypes & VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) + msgType += " (GENERAL)"; + if (messageTypes & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT) + msgType += " (PERF.)"; + if (messageTypes & VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) + msgType += " (VALID.)"; + + switch (messageSeverity) { + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: + LOG_DEBUG("VULKAN MESSAGE{}: ID: {} MSG: {}", msgType, pCallbackData->pMessageIdName, pCallbackData->pMessage); + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: + LOG_INFO("VULKAN MESSAGE{}: ID: {} MSG: {}", msgType, pCallbackData->pMessageIdName, pCallbackData->pMessage); + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: + LOG_WARN("VULKAN MESSAGE{}: ID: {} MSG: {}", msgType, pCallbackData->pMessageIdName, pCallbackData->pMessage); + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: + LOG_ERROR("VULKAN MESSAGE{}: ID: {} MSG: {}", msgType, pCallbackData->pMessageIdName, pCallbackData->pMessage); + break; + default: + break; + } + return VK_FALSE; + } + + static VkDebugUtilsMessengerCreateInfoEXT getDebugMessengerCreateInfo() + { + VkDebugUtilsMessengerCreateInfoEXT debugMessengerInfo{ + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, + .pNext = nullptr, + .flags = 0, + .messageSeverity = 0, + .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, + .pfnUserCallback = messengerCallback, + .pUserData = nullptr, + }; + + enum class MessageSeverity { + SEV_VERBOSE, + SEV_INFO, + SEV_WARNING, + SEV_ERROR // windows.h defines ERROR annoyingly + }; + + constexpr MessageSeverity MESSAGE_LEVEL = MessageSeverity::SEV_WARNING; + switch (MESSAGE_LEVEL) { + case MessageSeverity::SEV_VERBOSE: + debugMessengerInfo.messageSeverity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT; + [[fallthrough]]; + case MessageSeverity::SEV_INFO: + debugMessengerInfo.messageSeverity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT; + [[fallthrough]]; + case MessageSeverity::SEV_WARNING: + debugMessengerInfo.messageSeverity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT; + [[fallthrough]]; + case MessageSeverity::SEV_ERROR: + debugMessengerInfo.messageSeverity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + [[fallthrough]]; + default: + break; + } + + return debugMessengerInfo; + } + + Instance createVulkanInstance(SDL_Window* window, const char* appName, const char* appVersion) + { + Instance instance; + + // get the both the engine and application versions + int appVersionMajor = 0, appVersionMinor = 0, appVersionPatch = 0; + versionFromCharArray(appVersion, &appVersionMajor, &appVersionMinor, &appVersionPatch); + int engineVersionMajor = 0, engineVersionMinor = 0, engineVersionPatch = 0; + versionFromCharArray(ENGINE_VERSION, &engineVersionMajor, &engineVersionMinor, &engineVersionPatch); + + VkApplicationInfo applicationInfo{ + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pNext = nullptr, + .pApplicationName = appName, + .applicationVersion = VK_MAKE_VERSION(appVersionMajor, appVersionMinor, appVersionPatch), + .pEngineName = "engine", + .engineVersion = VK_MAKE_VERSION(engineVersionMajor, engineVersionMinor, engineVersionPatch), + .apiVersion = VK_API_VERSION_1_3, + }; + + const std::vector windowExtensions = getWindowExtensions(window); + std::vector instanceExtensionsToUse = windowExtensions; + instanceExtensionsToUse.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + + const char* const validationLayer = getValidationLayer(); + + VkDebugUtilsMessengerCreateInfoEXT debugMessengerInfo = getDebugMessengerCreateInfo(); + + VkInstanceCreateInfo instanceInfo{ + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pNext = &debugMessengerInfo, + .flags = 0, + .pApplicationInfo = &applicationInfo, + .enabledLayerCount = 1, + .ppEnabledLayerNames = &validationLayer, + .enabledExtensionCount = (uint32_t)instanceExtensionsToUse.size(), + .ppEnabledExtensionNames = instanceExtensionsToUse.data(), + }; + + VkResult res; + res = vkCreateInstance(&instanceInfo, nullptr, &instance.instance); + if (res == VK_ERROR_INCOMPATIBLE_DRIVER) { + throw std::runtime_error("The graphics driver is incompatible with vulkan"); + } + else if (res != VK_SUCCESS) { + throw std::runtime_error("vkCreateInstance failed: " + std::to_string(res)); + } + + volkLoadInstanceOnly(instance.instance); + + // create the debug messenger + VkDebugUtilsMessengerCreateInfoEXT createInfo = getDebugMessengerCreateInfo(); + + res = vkCreateDebugUtilsMessengerEXT(instance.instance, &createInfo, nullptr, &instance.debugMessenger); + if (res != VK_SUCCESS) { + throw std::runtime_error("vkCreateDebugUtilsMessengerExt failed: " + std::to_string(res)); + } + + return instance; + } + + void destroyVulkanInstance(Instance instance) + { + vkDestroyDebugUtilsMessengerEXT(instance.instance, instance.debugMessenger, nullptr); + vkDestroyInstance(instance.instance, nullptr); + } + +} \ No newline at end of file diff --git a/src/vulkan/instance.h b/src/vulkan/instance.h new file mode 100644 index 0000000..b27ce50 --- /dev/null +++ b/src/vulkan/instance.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#include + +namespace engine { + + struct Instance { + VkInstance instance = VK_NULL_HANDLE; + VkDebugUtilsMessengerEXT debugMessenger = VK_NULL_HANDLE; + }; + + Instance createVulkanInstance(SDL_Window* window, const char* appName, const char* appVersion); + void destroyVulkanInstance(Instance instance); + +} \ No newline at end of file diff --git a/src/vulkan/swapchain.cpp b/src/vulkan/swapchain.cpp new file mode 100644 index 0000000..20f91b7 --- /dev/null +++ b/src/vulkan/swapchain.cpp @@ -0,0 +1,250 @@ +#include +#include +#include +#include + +#include + +#include "swapchain.h" + +namespace engine { + + void createSwapchain(Swapchain* sc, const SwapchainInfo& info) + { + sc->device = info.device; + + printf("Recreating swapchain!\n"); + + // get surface caps and features + VkResult res; + res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(info.physicalDevice, info.surface, &sc->surfaceCapabilities); + if (res != VK_SUCCESS) throw std::runtime_error("Unable to get surface capabilities!"); + + // check there is at least one supported surface format + uint32_t surfaceFormatCount = 0; + std::vector formats{}; + res = vkGetPhysicalDeviceSurfaceFormatsKHR(info.physicalDevice, info.surface, &surfaceFormatCount, nullptr); + assert(res == VK_SUCCESS); + formats.resize(surfaceFormatCount); + res = vkGetPhysicalDeviceSurfaceFormatsKHR(info.physicalDevice, info.surface, &surfaceFormatCount, formats.data()); + assert(res == VK_SUCCESS); + + sc->surfaceFormat = formats[0]; + for (const auto& format : formats) { + if (format.format == VK_FORMAT_B8G8R8A8_SRGB && + format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + sc->surfaceFormat = format; // prefer using srgb non linear colors + } + } + + // check there is at least one supported present mode + uint32_t surfacePresentModeCount = 0; + std::vector presentModes{}; + res = vkGetPhysicalDeviceSurfacePresentModesKHR(info.physicalDevice, info.surface, &surfacePresentModeCount, nullptr); + assert(res == VK_SUCCESS); + presentModes.resize(surfacePresentModeCount); + res = vkGetPhysicalDeviceSurfacePresentModesKHR(info.physicalDevice, info.surface, &surfacePresentModeCount, presentModes.data()); + assert(res == VK_SUCCESS); + + sc->presentMode = VK_PRESENT_MODE_FIFO_KHR; // This mode is always available + if (info.vsync == true) { + if (info.waitForPresent == false) { + for (const auto& presMode : presentModes) { + if (presMode == VK_PRESENT_MODE_MAILBOX_KHR) { + sc->presentMode = presMode; // this mode allows V-sync without fixing FPS to refresh rate + } + } + } + } + else { + for (const auto& presMode : presentModes) { + if (presMode == VK_PRESENT_MODE_IMMEDIATE_KHR) { + sc->presentMode = presMode; // V-sync off + } + } + } + + /* get min image count */ + uint32_t minImageCount = sc->surfaceCapabilities.minImageCount + 1; + if (sc->surfaceCapabilities.maxImageCount > 0 && minImageCount > sc->surfaceCapabilities.maxImageCount) { + minImageCount = sc->surfaceCapabilities.maxImageCount; + } + + /* get extent */ + VkExtent2D oldExtent = sc->extent; + if (sc->surfaceCapabilities.currentExtent.width != std::numeric_limits::max()) { + sc->extent = sc->surfaceCapabilities.currentExtent; + } + else { + // if fb size isn't already found, get it from SDL + int width, height; + SDL_Vulkan_GetDrawableSize(info.window, &width, &height); + + sc->extent.width = static_cast(width); + sc->extent.height = static_cast(height); + + sc->extent.width = std::clamp( + sc->extent.width, + sc->surfaceCapabilities.minImageExtent.width, sc->surfaceCapabilities.maxImageExtent.width); + sc->extent.height = std::clamp( + sc->extent.height, + sc->surfaceCapabilities.minImageExtent.height, sc->surfaceCapabilities.maxImageExtent.height); + } + if (sc->extent.width == 0 || sc->extent.height == 0) { // preserve extent if the window is minimised + sc->extent = oldExtent; + } + + /* TODO: delete old framebuffers and image views */ + + /* create swapchain */ + + VkSwapchainCreateInfoKHR scInfo{ + .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + .pNext = nullptr, + .flags = 0, + .surface = info.surface, + .minImageCount = minImageCount, + .imageFormat = sc->surfaceFormat.format, + .imageColorSpace = sc->surfaceFormat.colorSpace, + .imageExtent = sc->extent, + .imageArrayLayers = 1, + .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, // ignored + .pQueueFamilyIndices = nullptr, // ignored + .preTransform = sc->surfaceCapabilities.currentTransform, + .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + .presentMode = sc->presentMode, + .clipped = VK_TRUE, // discard parts of the screen not visible + .oldSwapchain = sc->swapchain + }; + + res = vkCreateSwapchainKHR(sc->device, &scInfo, nullptr, &sc->swapchain); + if (res != VK_SUCCESS) throw std::runtime_error("Failed to create swapchain!"); + + /* if it existed, destroy the old swapchain */ + if (scInfo.oldSwapchain != VK_NULL_HANDLE) { + vkDestroySwapchainKHR(info.device, scInfo.oldSwapchain, nullptr); + } + + /* create the render pass */ + if (sc->renderpass != VK_NULL_HANDLE) { + vkDestroyRenderPass(sc->device, sc->renderpass, nullptr); + } + VkAttachmentDescription colorAttachment{ + .flags = 0, + .format = sc->surfaceFormat.format, + .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, // ignored + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, // ignored + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR + }; + VkAttachmentReference colorAttachmentRef{ + .attachment = 0, + .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL + }; + VkSubpassDescription subpass{ + .flags = 0, + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .inputAttachmentCount = 0, + .pInputAttachments = nullptr, + .colorAttachmentCount = 1, + .pColorAttachments = &colorAttachmentRef, + .pResolveAttachments = nullptr, + .pDepthStencilAttachment = nullptr, + .preserveAttachmentCount = 0, + .pPreserveAttachments = nullptr, + }; + VkSubpassDependency dependency{ + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = 0, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dependencyFlags = 0 + }; + VkRenderPassCreateInfo renderPassInfo{ + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .attachmentCount = 1, + .pAttachments = &colorAttachment, + .subpassCount = 1, + .pSubpasses = &subpass, + .dependencyCount = 1, + .pDependencies = &dependency, + }; + res = vkCreateRenderPass(sc->device, &renderPassInfo, nullptr, &sc->renderpass); + if (res != EXIT_SUCCESS) throw std::runtime_error("Failed to create renderpass!"); + + // get all the image handles + uint32_t swapchainImageCount = 0; + res = vkGetSwapchainImagesKHR(info.device, sc->swapchain, &swapchainImageCount, nullptr); + assert(res == VK_SUCCESS); + std::vector swapchainImages(swapchainImageCount); + res = vkGetSwapchainImagesKHR(info.device, sc->swapchain, &swapchainImageCount, swapchainImages.data()); + assert(res == VK_SUCCESS); + + /* create image view and framebuffer for each image */ + sc->images.resize(swapchainImageCount); + for (uint32_t i = 0; i < swapchainImageCount; i++) { + auto& [image, view, framebuffer] = sc->images.at(i); + + if (view != VK_NULL_HANDLE) vkDestroyImageView(sc->device, view, nullptr); + if (framebuffer != VK_NULL_HANDLE) vkDestroyFramebuffer(sc->device, framebuffer, nullptr); + + image = swapchainImages[i]; + + /* make the image view */ + VkImageViewCreateInfo viewInfo{}; + viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + viewInfo.pNext = nullptr; + viewInfo.flags = 0; + viewInfo.image = image; + viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + viewInfo.format = sc->surfaceFormat.format; + viewInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + viewInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + viewInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + viewInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + viewInfo.subresourceRange.baseMipLevel = 0; + viewInfo.subresourceRange.levelCount = 1; + viewInfo.subresourceRange.baseArrayLayer = 0; + viewInfo.subresourceRange.layerCount = 1; + VkResult res = vkCreateImageView(sc->device, &viewInfo, nullptr, &view); + if (res != VK_SUCCESS) throw std::runtime_error("Failed to create image view from swapchain image!"); + + VkFramebufferCreateInfo fbInfo{}; + fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; + fbInfo.pNext = nullptr; + fbInfo.flags = 0; + fbInfo.renderPass = sc->renderpass; + fbInfo.attachmentCount = 1; + fbInfo.pAttachments = &view; + fbInfo.width = sc->extent.width; + fbInfo.height = sc->extent.height; + fbInfo.layers = 1; + + res = vkCreateFramebuffer(sc->device, &fbInfo, nullptr, &framebuffer); + if (res != VK_SUCCESS) throw std::runtime_error("Failed to create framebuffer for swapchain image!"); + + } + + } + + void destroySwapchain(const Swapchain& sc) + { + for (const auto& [image, view, framebuffer] : sc.images) { + vkDestroyFramebuffer(sc.device, framebuffer, nullptr); + vkDestroyImageView(sc.device, view, nullptr); + } + vkDestroyRenderPass(sc.device, sc.renderpass, nullptr); + vkDestroySwapchainKHR(sc.device, sc.swapchain, nullptr); + } + +} \ No newline at end of file diff --git a/src/vulkan/swapchain.h b/src/vulkan/swapchain.h new file mode 100644 index 0000000..ee40315 --- /dev/null +++ b/src/vulkan/swapchain.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include + +#include + +namespace engine { + + struct Swapchain { + VkSwapchainKHR swapchain = VK_NULL_HANDLE; + VkDevice device = VK_NULL_HANDLE; // the associated device + VkSurfaceFormatKHR surfaceFormat{}; + VkSurfaceCapabilitiesKHR surfaceCapabilities{}; + VkPresentModeKHR presentMode{}; + VkExtent2D extent{}; + VkRenderPass renderpass = VK_NULL_HANDLE; + std::vector> images{}; + }; + + struct SwapchainInfo { + VkDevice device; + VkPhysicalDevice physicalDevice; + VkSurfaceKHR surface; + SDL_Window* window; + bool vsync; + bool waitForPresent; + }; + + void createSwapchain(Swapchain* sc, const SwapchainInfo& info); + void destroySwapchain(const Swapchain& sc); + +} \ No newline at end of file diff --git a/src/window.cpp b/src/window.cpp index 8154a29..efcccc1 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -24,12 +24,8 @@ namespace engine { Uint32 windowFlags = SDL_WINDOW_SHOWN; -#ifdef ENGINE_BUILD_VULKAN + // use Vulkan 1.3 windowFlags |= SDL_WINDOW_VULKAN; -#endif -#ifdef ENGINE_BUILD_OPENGL - windowFlags |= SDL_WINDOW_OPENGL; -#endif if (m_resizable) { windowFlags |= SDL_WINDOW_RESIZABLE; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 79a82c9..871a6b1 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -19,7 +19,8 @@ set(GAME_SOURCES ) if (WIN32) - add_executable(${PROJECT_NAME} WIN32 ${GAME_SOURCES} "game.rc") + #add_executable(${PROJECT_NAME} WIN32 ${GAME_SOURCES} "game.rc") + add_executable(${PROJECT_NAME} ${GAME_SOURCES}) else() add_executable(${PROJECT_NAME} ${GAME_SOURCES}) endif() diff --git a/test/res/models/uvsphere.dae b/test/res/models/uvsphere.dae new file mode 100644 index 0000000..ad855b6 --- /dev/null +++ b/test/res/models/uvsphere.dae @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0f9facb4b00b2c0a67d1272cce5e6fb623732bad1eead0952b82d286f3ae64d9 +size 556098 diff --git a/test/res/textures/grass_pbr/Grass001_2K_AmbientOcclusion.png b/test/res/textures/grass_pbr/Grass001_2K_AmbientOcclusion.png new file mode 100644 index 0000000..9edc304 Binary files /dev/null and b/test/res/textures/grass_pbr/Grass001_2K_AmbientOcclusion.png differ diff --git a/test/res/textures/grass_pbr/Grass001_2K_Color.png b/test/res/textures/grass_pbr/Grass001_2K_Color.png new file mode 100644 index 0000000..4b9baeb Binary files /dev/null and b/test/res/textures/grass_pbr/Grass001_2K_Color.png differ diff --git a/test/res/textures/grass_pbr/Grass001_2K_Displacement.png b/test/res/textures/grass_pbr/Grass001_2K_Displacement.png new file mode 100644 index 0000000..92f1c3f Binary files /dev/null and b/test/res/textures/grass_pbr/Grass001_2K_Displacement.png differ diff --git a/test/res/textures/grass_pbr/Grass001_2K_NormalDX.png b/test/res/textures/grass_pbr/Grass001_2K_NormalDX.png new file mode 100644 index 0000000..7eed3f8 Binary files /dev/null and b/test/res/textures/grass_pbr/Grass001_2K_NormalDX.png differ diff --git a/test/res/textures/grass_pbr/Grass001_2K_NormalGL.png b/test/res/textures/grass_pbr/Grass001_2K_NormalGL.png new file mode 100644 index 0000000..984e8fa Binary files /dev/null and b/test/res/textures/grass_pbr/Grass001_2K_NormalGL.png differ diff --git a/test/res/textures/grass_pbr/Grass001_2K_Roughness.png b/test/res/textures/grass_pbr/Grass001_2K_Roughness.png new file mode 100644 index 0000000..1c0d236 Binary files /dev/null and b/test/res/textures/grass_pbr/Grass001_2K_Roughness.png differ diff --git a/test/src/camera_controller.cpp b/test/src/camera_controller.cpp index d1f7b62..db1a84c 100644 --- a/test/src/camera_controller.cpp +++ b/test/src/camera_controller.cpp @@ -154,7 +154,7 @@ void CameraControllerSystem::onUpdate(float ts) " z: " + std::to_string(t->position.z) }; m_scene->app()->window()->infoBox("POSITION", pos_string); - INFO("position: " + pos_string); + LOG_INFO("position: " + pos_string); } if (m_scene->app()->inputManager()->getButtonPress("fullscreen")) { diff --git a/test/src/game.cpp b/test/src/game.cpp index 2985efd..49062b3 100644 --- a/test/src/game.cpp +++ b/test/src/game.cpp @@ -40,7 +40,7 @@ static void configureInputs(engine::InputManager* inputManager) void playGame(bool enableFrameLimiter) { - INFO("FPS limiter: {}", enableFrameLimiter ? "ON" : "OFF"); + LOG_INFO("FPS limiter: {}", enableFrameLimiter ? "ON" : "OFF"); engine::gfx::GraphicsSettings graphicsSettings{}; graphicsSettings.vsync = true; @@ -56,6 +56,8 @@ void playGame(bool enableFrameLimiter) auto myScene = app.sceneManager()->createEmptyScene(); +#if 0 + /* create camera */ { myScene->registerComponent(); @@ -96,48 +98,19 @@ void playGame(bool enableFrameLimiter) { uint32_t skybox = myScene->createEntity("skybox"); auto skyboxRenderable = myScene->addComponent(skybox); - skyboxRenderable->material = std::make_unique(app.getResource("engine.skybox")); + skyboxRenderable->material = std::make_unique(app.getResource("builtin.skybox")); skyboxRenderable->material->m_texture = spaceTexture; // skyboxRenderable->mesh = genSphereMesh(app.gfx(), 1.0f, 50, true); skyboxRenderable->mesh = genCuboidMesh(app.gfx(), 2.0f, 2.0f, 2.0f, 1.0f, true); myScene->getComponent(skybox)->position = { -1.0f, -1.0f, -1.0f }; } - /* enemy cube */ - { - uint32_t enemy = myScene->createEntity("enemy"); - auto enemyRenderable = myScene->addComponent(enemy); - enemyRenderable->material = std::make_unique(app.getResource("engine.textured")); - enemyRenderable->material->m_texture = app.getResource("engine.white"); - enemyRenderable->mesh = genCuboidMesh(app.gfx(), 10.0f, 10.0f, 10.0f); - auto enemyTransform = myScene->getComponent(enemy); - enemyTransform->position.x = 10.0f; - enemyTransform->position.y = 0.0f; - enemyTransform->position.z = 14.0f; - auto enemyCollider = myScene->addComponent(enemy); - enemyCollider->isStatic = true; - enemyCollider->aabb = { { 0.0f, 0.0f, 0.0f }, { 10.0f, 10.0f, 10.0f } }; // A box enclosing the sphere - } - - /* sun */ - { - uint32_t sun = myScene->createEntity("sun"); - auto sunRenderable = myScene->addComponent(sun); - sunRenderable->material = std::make_unique(app.getResource("engine.textured")); - sunRenderable->material->m_texture = app.getResource("engine.white"); - sunRenderable->mesh = genSphereMesh(app.gfx(), 500.0f, 32, false, true); - auto sunTransform = myScene->getComponent(sun); - sunTransform->position.x = 2000.0f; - sunTransform->position.y = 2000.0f; - sunTransform->position.z = -2000.0f; - } - /* floor */ { uint32_t floor = myScene->createEntity("floor"); myScene->getComponent(floor)->position = glm::vec3{-5000.0f, -1.0f, -5000.0f}; auto floorRenderable = myScene->addComponent(floor); - floorRenderable->material = std::make_shared(app.getResource("engine.textured")); + floorRenderable->material = std::make_shared(app.getResource("builtin.standard")); floorRenderable->material->m_texture = grassTexture; floorRenderable->mesh = genCuboidMesh(app.gfx(), 10000.0f, 1.0f, 10000.0f, 5000.0f); auto floorCollider = myScene->addComponent(floor); @@ -145,44 +118,7 @@ void playGame(bool enableFrameLimiter) floorCollider->aabb = { { 0.0f, 0.0f, 0.0f }, { 10000.0f, 1.0f, 10000.0f } }; } - // cubes! - if (false) { // disabled - constexpr int SIZE = 10; - - const uint32_t cubeParent = myScene->createEntity("cubeParent"); - engine::TransformComponent* cubeParentTransform = myScene->getComponent(cubeParent); - cubeParentTransform->position = { 100.0f, 0.0f, 100.0f }; - cubeParentTransform->scale = { 100.0f, 100.0f, 100.0f }; - - std::shared_ptr cubeMesh = genCuboidMesh(app.gfx(), 0.1f, 0.1f, 0.1f); - const auto cubeMaterial = std::make_shared(app.getResource("engine.textured")); - cubeMaterial->m_texture = app.getResource("engine.white"); - - uint32_t cubes[SIZE][SIZE][SIZE]; - for (int x = 0; x < SIZE; x++) { - for (int y = 0; y < SIZE; y++) { - for (int z = 0; z < SIZE; z++) { - const uint32_t cube = myScene->createEntity("cube" + std::to_string(x * 100 + y * 10 + z), cubeParent); - - auto transform = myScene->getComponent(cube); - auto renderable = myScene->addComponent(cube); - auto collider = myScene->addComponent(cube); - collider->aabb.pos1 = { 0.0f, 0.0f, 0.0f }; - collider->aabb.pos2 = { 10.0f, 10.0f, 10.0f }; - collider->isStatic = true; - collider->isTrigger = false; - - transform->position = { (float)x, (float)y, (float)z }; - renderable->mesh = cubeMesh; - renderable->material = cubeMaterial; - - cubes[x][y][z] = cube; - } - } - } - - (void)cubes; - } +#endif app.gameLoop(); diff --git a/test/src/main.cpp b/test/src/main.cpp index 2656bd9..20efa54 100644 --- a/test/src/main.cpp +++ b/test/src/main.cpp @@ -1,39 +1,32 @@ #include "config.h" - #include "game.hpp" +// engine #include "logger.hpp" #include "window.hpp" +// standard library #include +#include int main(int argc, char* argv[]) { bool enableFrameLimiter = true; - if (argc == 2) { - const std::string arg { argv[1] }; - if (arg == "nofpslimit") enableFrameLimiter = false; + if (strcmp(argv[2], "nofpslimit") == 0) enableFrameLimiter = false; } engine::setupLog(PROJECT_NAME); - INFO("{} v{}", PROJECT_NAME, PROJECT_VERSION); + LOG_INFO("{} v{}", PROJECT_NAME, PROJECT_VERSION); try { playGame(enableFrameLimiter); } catch (const std::exception& e) { - - CRITICAL("{}", e.what()); - + LOG_CRITICAL("{}", e.what()); engine::Window::errorBox(e.what()); -#ifndef NDEBUG - fputs(e.what(), stderr); - fputc('\n', stderr); -#endif - return EXIT_FAILURE; }