#ifndef ENGINE_H #define ENGINE_H // Normal header #include "Common/types.h" struct FrameData { VkCommandPool _commandPool; VkCommandBuffer _mainCommandBuffer; VkSemaphore _swapchainSemaphore; VkSemaphore _renderSemaphore; VkFence _renderFence; DeletionQueue _deletionQueue; }; constexpr uint32_t FRAME_OVERLAP = 2; class Engine { public: //========== Class Members ========== bool _isInitialized { false }; int _frameNumber { 0 }; VkExtent2D _windowExtent { 1700, 900 }; GLFWwindow *window { nullptr }; // Vulkan specific class members VkInstance _instance; VkDebugUtilsMessengerEXT _debugMessenger; VkPhysicalDevice _choosenGPU; VkDevice _device; VkSurfaceKHR _surface; VkSwapchainKHR _swapchain; VkFormat _swapchainImageFormat; std::vector _swapchainImages; std::vector _swapchainImageViews; VkExtent2D _swapchainExtent; FrameData _frames[FRAME_OVERLAP]; FrameData& getCurrentFrame() { return _frames[_frameNumber % FRAME_OVERLAP]; }; VkQueue _graphicsQueue; uint32_t _graphicsQueueFamily; // Deletion queue DeletionQueue _mainDeletionQueue; VmaAllocator _allocator; // Draw resources AllocatedImage _drawImage; VkExtent2D _drawExtent; void init(); void cleanup(); void draw(); void run(); private: void initRender(); void initVulkan(); void initSwapchain(); void initCommands(); void initSyncStructures(); void createSwapchain( uint32_t width, uint32_t height ); void destroySwapchain(); void drawBackground( VkCommandBuffer cmd ); }; #endif #ifdef ENGINE_IMPL #ifndef ENGINE_IMPL_H #define ENGINE_IMPL_H //========== Implementation ========== // Implementation defines #define IMAGES_IMPL #define INITIALIZERS_IMPL #define CALLBACK_IMPL #define TIMER_IMPL #define VMA_IMPLEMENTATION // Custom includes #include "images.h" #include "initializers.h" #include "callbacks.h" #include "Common/timer.h" #include "Common/types.h" #include "VkBootstrap.h" #include "vk_mem_alloc.h" #ifndef DEBUG constexpr bool bUseValidationLayers = false; #else constexpr bool bUseValidationLayers = true; #endif // Abort when there is an error // Todo : Give an error message or crash dump using namespace std; #define VK_CHECK(x) \ do { \ VkResult err = x; \ if (err) { \ fmt::println("Vulkan error : {}", string_VkResult(err)); \ abort(); \ } \ } while (0) \ void Engine::init() { #ifdef DEBUG fmt::println("Initializing the engine..."); fmt::println("Current engine version {}", BUILD_ID); #endif glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); window = glfwCreateWindow( static_cast(_windowExtent.width), static_cast(_windowExtent.height), "Engine", nullptr, nullptr ); glfwSetKeyCallback(window, Callback::keyboardCallback); _isInitialized = true; initRender(); #ifdef DEBUG fmt::println("Engine Initialized !"); #endif } void Engine::cleanup() { #ifdef DEBUG fmt::println("Cleaning up..."); #endif if (_isInitialized) { // Wait for the GPU to stop working vkDeviceWaitIdle(_device); for (int i = 0; i < FRAME_OVERLAP; i++) { vkDestroyCommandPool(_device, _frames[i]._commandPool, nullptr); // Destroy sync objects vkDestroyFence(_device, _frames[i]._renderFence, nullptr); vkDestroySemaphore(_device, _frames[i]._renderSemaphore, nullptr); vkDestroySemaphore(_device, _frames[i]._swapchainSemaphore, nullptr); // Uses the deletion queue to clear ressources _frames[i]._deletionQueue.flush(); } // Flush the global deletion queue _mainDeletionQueue.flush(); destroySwapchain(); vkDestroySurfaceKHR(_instance, _surface, nullptr); vkDestroyDevice(_device, nullptr); vkb::destroy_debug_utils_messenger(_instance, _debugMessenger); vkDestroyInstance(_instance, nullptr); /* vkDestroyCommandPool(_device, _commandPool, nullptr); vkDestroySwapchainKHR(_device, _swapchain, nullptr); // Change to use dynamic rendering vkDestroyRenderPass(_device, _renderPass, nullptr); // Destroy swapchain ressources for (uint32_t i = 0; i < _framebuffers.size(); i++) { vkDestroyFramebuffer(_device, _framebuffers[i], nullptr); vkDestroyImageView(_device, _swapchainImageViews[i], nullptr); } */ // Destroy the window (at the end) glfwDestroyWindow(window); glfwTerminate(); } #ifdef DEBUG fmt::println("Engine cleaned up !"); #endif } void Engine::draw() { // wait until the gpu has finished rendering the last frame. Timeout of 1s VK_CHECK(vkWaitForFences(_device, 1, &getCurrentFrame()._renderFence, true, UINT64_MAX)); // Flush frame data getCurrentFrame()._deletionQueue.flush(); // Request image from the swapchain uint32_t swapchainImageIndex; VK_CHECK(vkAcquireNextImageKHR(_device, _swapchain, UINT64_MAX, getCurrentFrame()._swapchainSemaphore, nullptr, &swapchainImageIndex)); // Reset fences to redo sync VK_CHECK(vkResetFences(_device, 1, &getCurrentFrame()._renderFence)); VkCommandBuffer cmd = getCurrentFrame()._mainCommandBuffer; _drawExtent.width = _drawImage.imageExtent.width; _drawExtent.height = _drawImage.imageExtent.height; VK_CHECK(vkResetCommandBuffer(cmd, 0)); VkCommandBufferBeginInfo cmdBeginInfo = { vkinit::commandBufferBeginInfo( VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT ) }; VK_CHECK(vkBeginCommandBuffer(cmd, &cmdBeginInfo)); vkutil::transitionImage( cmd, _drawImage.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL ); drawBackground(cmd); // Transfer the draw image and swapchain image to the correct layouts vkutil::transitionImage( cmd, _drawImage.image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL ); vkutil::transitionImage( cmd, _swapchainImages[swapchainImageIndex], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL ); // Copy image -> swapchain vkutil::copyImageToImage( cmd, _drawImage.image, _swapchainImages[swapchainImageIndex], _drawExtent, _swapchainExtent ); // Uses the present layout to display into the screen vkutil::transitionImage( cmd, _swapchainImages[swapchainImageIndex], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR ); // Locks the buffer, allowing it to be executed VK_CHECK(vkEndCommandBuffer(cmd)); VkCommandBufferSubmitInfo cmdinfo = vkinit::commandBufferSubmitInfo(cmd); VkSemaphoreSubmitInfo waitInfo = vkinit::semaphoreSubmitInfo(VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR, getCurrentFrame()._swapchainSemaphore); VkSemaphoreSubmitInfo signalInfo = vkinit::semaphoreSubmitInfo(VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT, getCurrentFrame()._renderSemaphore); VkSubmitInfo2 submit = vkinit::submitInfo(&cmdinfo, &signalInfo, &waitInfo); VK_CHECK(vkQueueSubmit2(_graphicsQueue, 1, &submit, getCurrentFrame()._renderFence)); VkPresentInfoKHR presentInfo = {}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.pNext = nullptr; presentInfo.pSwapchains = &_swapchain; presentInfo.swapchainCount = 1; presentInfo.pWaitSemaphores = &getCurrentFrame()._renderSemaphore; presentInfo.waitSemaphoreCount = 1; presentInfo.pImageIndices = &swapchainImageIndex; VK_CHECK(vkQueuePresentKHR(_graphicsQueue, &presentInfo)); _frameNumber++; } void Engine::run() { #ifdef DEBUG fmt::println("Running..."); RunTimer timer; timer.startTimer(); #endif bool stopRendering = false; while (!glfwWindowShouldClose(window)) { glfwPollEvents(); if (glfwGetWindowAttrib(window, GLFW_ICONIFIED)) { stopRendering = true; } else { stopRendering = false; } if (stopRendering) { // Minimized, pause std::this_thread::sleep_for(std::chrono::milliseconds(100)); continue; } draw(); } #ifdef DEBUG fmt::println("Engine has finished running after {} seconds!", timer.endTimer()); #endif } void Engine::initRender() { initVulkan(); initSwapchain(); //initDefaultRenderpass(); //initFramebuffers(); initCommands(); initSyncStructures(); } void Engine::initVulkan() { #ifdef DEBUG fmt::println("Initializing Vulkan..."); #endif vkb::InstanceBuilder builder; // Create a basic VK instance, with debuging features auto instRet = builder.set_app_name("Engine") .request_validation_layers(bUseValidationLayers) .use_default_debug_messenger() .require_api_version(1, 3, 0) .build(); vkb::Instance vkbInst = instRet.value(); // Grab the instance _instance = vkbInst.instance; _debugMessenger = vkbInst.debug_messenger; // Create the surface if (glfwCreateWindowSurface(_instance, window, nullptr, & _surface) != VK_SUCCESS) { throw std::runtime_error("Failed to create window surface !\nAborting..."); } // Features // Vulkan 1.3 VkPhysicalDeviceVulkan13Features features13 { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES, .synchronization2 = true, .dynamicRendering = true }; VkPhysicalDeviceVulkan12Features features12 { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, .descriptorIndexing = true, .bufferDeviceAddress = true }; // Use vkBootstrap to select the GPU vkb::PhysicalDeviceSelector selector { vkbInst }; vkb::PhysicalDevice physicalDevice = selector .set_minimum_version(1, 3) .set_required_features_13(features13) .set_required_features_12(features12) .set_surface(_surface) .select() .value(); vkb::DeviceBuilder deviceBuilder { physicalDevice }; #ifdef DEBUG fmt::println("Device {}", physicalDevice.name); #endif vkb::Device vkbDevice { deviceBuilder.build().value() }; _device = { vkbDevice.device }; _choosenGPU = { physicalDevice.physical_device }; _graphicsQueue = { vkbDevice.get_queue(vkb::QueueType::graphics).value() }; _graphicsQueueFamily = { vkbDevice.get_queue_index(vkb::QueueType::graphics).value() }; VmaAllocatorCreateInfo allocatorInfo = {}; allocatorInfo.physicalDevice = _choosenGPU; allocatorInfo.device = _device; allocatorInfo.instance = _instance; allocatorInfo.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; vmaCreateAllocator(&allocatorInfo, &_allocator); _mainDeletionQueue.pushFunction([&]() { vmaDestroyAllocator(_allocator); }); #ifdef DEBUG fmt::println("Vulkan initialized !"); #endif } void Engine::initSwapchain() { #ifdef DEBUG fmt::println("Initializing the swapchain..."); #endif createSwapchain(_windowExtent.width, _windowExtent.height); VkExtent3D drawImageExtent = { _windowExtent.width, _windowExtent.height, 1 }; // Hardcoding draw format to a 32 bit float _drawImage.imageFormat = VK_FORMAT_R16G16B16A16_SFLOAT; _drawImage.imageExtent = drawImageExtent; VkImageUsageFlags drawImageUsages { VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT }; VkImageCreateInfo rImgInfo = { vkinit::imageCreateInfo( _drawImage.imageFormat, drawImageUsages, drawImageExtent ) }; // Use the GPU local memory for the draw image VmaAllocationCreateInfo rImgAllocInfo = { .usage = VMA_MEMORY_USAGE_GPU_ONLY, .requiredFlags = VkMemoryPropertyFlags(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) }; vmaCreateImage( _allocator, &rImgInfo, &rImgAllocInfo, &_drawImage.image, &_drawImage.allocation, nullptr ); VkImageViewCreateInfo rViewInfo = { vkinit::imageViewCreateInfo( _drawImage.imageFormat, _drawImage.image, VK_IMAGE_ASPECT_COLOR_BIT ) }; VK_CHECK( vkCreateImageView( _device, &rViewInfo, nullptr, &_drawImage.imageView ) ); // Add to deletion queues _mainDeletionQueue.pushFunction([this]() { vkDestroyImageView( _device, _drawImage.imageView, nullptr ); vmaDestroyImage( _allocator, _drawImage.image, _drawImage.allocation ); }); #ifdef DEBUG fmt::println("Swapchain initialized !"); #endif } void Engine::initCommands() { #ifdef DEBUG fmt::println("Initializing vulkan commands.."); #endif VkCommandPoolCreateInfo commandPoolInfo = { vkinit::commandPoolCreateInfo( _graphicsQueueFamily, VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT ) }; for ( uint32_t i = 0; i < FRAME_OVERLAP; i++ ) { VK_CHECK( vkCreateCommandPool( _device, &commandPoolInfo, nullptr, &_frames[i]._commandPool ) ); VkCommandBufferAllocateInfo cmdAllocInfo = vkinit::commandBufferAllocateInfo(_frames[i]._commandPool, 1); VK_CHECK( vkAllocateCommandBuffers( _device, &cmdAllocInfo, &_frames[i]._mainCommandBuffer ) ); } #ifdef DEBUG fmt::println("Vulan commands initialized !"); #endif } void Engine::initSyncStructures() { #ifdef DEBUG fmt::println("Creating sync structures..."); #endif VkFenceCreateInfo fenceCreateInfo = vkinit::fenceCreateInfo(VK_FENCE_CREATE_SIGNALED_BIT); VkSemaphoreCreateInfo semaphoreCreateInfo = vkinit::semaphoreCreateInfo(); for (int i = 0; i < FRAME_OVERLAP; i++) { VK_CHECK(vkCreateFence(_device, &fenceCreateInfo, nullptr, &_frames[i]._renderFence)); VK_CHECK(vkCreateSemaphore(_device, &semaphoreCreateInfo, nullptr, &_frames[i]._swapchainSemaphore)); VK_CHECK(vkCreateSemaphore(_device, &semaphoreCreateInfo, nullptr, &_frames[i]._renderSemaphore)); } #ifdef DEBUG fmt::println("Sync structures created !"); #endif } void Engine::createSwapchain( uint32_t width, uint32_t height ) { #ifdef DEBUG fmt::println("Creating the Swapchain..."); #endif vkb::SwapchainBuilder swapchainBuilder { _choosenGPU, _device, _surface }; _swapchainImageFormat = VK_FORMAT_B8G8R8A8_UNORM; vkb::Swapchain vkbSwapchain = swapchainBuilder .set_desired_format( VkSurfaceFormatKHR { .format = _swapchainImageFormat, .colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR } ) .set_desired_present_mode(VK_PRESENT_MODE_FIFO_KHR) // VSync .set_desired_extent(width, height) .add_image_usage_flags(VK_IMAGE_USAGE_TRANSFER_DST_BIT) .build() .value(); _swapchainExtent = { vkbSwapchain.extent }; // Store swapchain and it's related images _swapchain = { vkbSwapchain.swapchain }; _swapchainImages = { vkbSwapchain.get_images().value() }; _swapchainImageViews = { vkbSwapchain.get_image_views().value() }; #ifdef DEBUG fmt::println("Swapchain created with dimentions {} by {}", vkbSwapchain.extent.width, vkbSwapchain.extent.height); #endif } void Engine::destroySwapchain() { #ifdef DEBUG fmt::println("Destroying the swapchain..."); #endif vkDestroySwapchainKHR(_device, _swapchain, nullptr); for (uint32_t i = 0; i < _swapchainImageViews.size(); i++) { vkDestroyImageView(_device, _swapchainImageViews[i], nullptr); } #ifdef DEBUG fmt::println("Swapchain destroyed !"); #endif } void Engine::drawBackground( VkCommandBuffer cmd ) { VkClearColorValue clearValue; float flash = { std::abs(std::sin(_frameNumber / 120.0f)) }; clearValue = { { 0.0f, 0.0f, flash, 1.0f } }; VkImageSubresourceRange clearRange = vkinit::imageSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT); // Clear Image vkCmdClearColorImage( cmd, _drawImage.image, VK_IMAGE_LAYOUT_GENERAL, &clearValue, 1, &clearRange ); } #endif #endif