#ifndef ENGINE_H #define ENGINE_H // Normal header // Current progress // 3 - Graphics Pipeline - Blending // Currently rewriting render images // Todo: // Switch C style types to C++ ones (vk::) // Switch fs paths from relative to being able to be launched from anywhere #include "descriptors.h" // Required for public interface custom allocator #include "Common/Types.h" #include #include #include #include #include #include "imgui.h" // For types #include "imgui_memory_editor.h" // For mem edit #include "loader.h" // For MeshAsset struct FrameData { VkCommandPool _commandPool; VkCommandBuffer _mainCommandBuffer; VkSemaphore _swapchainSemaphore; VkFence _renderFence; DeletionQueue _deletionQueue; }; struct ComputePushConstants { glm::vec4 data1; glm::vec4 data2; glm::vec4 data3; glm::vec4 data4; }; struct ComputeEffect { const char* name; VkPipeline pipeline; VkPipelineLayout layout; ComputePushConstants data; }; constexpr uint32_t FRAME_OVERLAP = 2; class Engine { public: //========== Class Members ========== // Config EngineConfig _conf; bool _isInitialized { false }; int _frameNumber { 0 }; VkExtent2D _viewportPanelSize { 1, 1 }; VkExtent2D _windowExtent; GLFWwindow *window { nullptr }; uint32_t _gpuMemusage; // 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]; }; // Sync std::vector _renderSemaphores; // It's size should be the number of swapchain images VkQueue _graphicsQueue; uint32_t _graphicsQueueFamily; // Deletion queue DeletionQueue _mainDeletionQueue; DeletionQueue _syncDeletionQueue; VmaAllocator _allocator; // Draw resources AllocatedImage _drawImage; AllocatedImage _depthImage; // Depth testing VkExtent2D _drawExtent; float renderScale = 1.0f; // Descriptor Sets DescriptorAllocator globalDescriptorAllocator; VkDescriptorSet _drawImageDescriptors; VkDescriptorSetLayout _drawImageDescriptorLayout; // Pipelines VkPipeline _gradientPipeline; VkPipelineLayout _gradientPipelineLayout; // Immediate commands VkFence _immFence; // Immediate mode fence VkCommandBuffer _immCommandBuffer; VkCommandPool _immCommandPool; // Resize bool _requestResize; // Push constants std::vector backgroundEffects; int currentBackgroundEffect{0}; // Already initialized // ImGUI texture sampler VkSampler _viewportSampler; VkDescriptorSet _viewportTextureID; // ImGUI memory editor MemoryEditor _memEdit; // Logger Logger _logger; // Sum things int prevID = 0; int meshIndex = 0; // Mesh VkPipelineLayout _meshPipelineLayout; VkPipeline _meshPipeline; GPUMeshBuffers rectangle; // Model std::string _modelPath { "models/basicmesh.glb" }; std::vector> testMeshes; void parseConfig( EngineConfig conf ); void init(); void cleanup(); void draw(); void run(); void immediateSubmit( std::function && function ); // Public functions (for CLI args) void getAvailableGPU(); // Meshes GPUMeshBuffers uploadMesh( std::span indices, std::span vertices ); private: using enum LogModule; using enum LogLevel; // Init functions void initRender(); void initVulkan(); void initSwapchain(); void initCommands(); void initSyncStructures(); void initDescriptors(); void initPipelines(); void initBackgroundPipelines(); void initMeshPipeline(); void initImgui(); // Default data void initDefaultData(); // Allocations AllocatedBuffer createBuffer( size_t allocSize, VkBufferUsageFlags usage, VmaMemoryUsage memoryUsage ); void destroyBuffer( const AllocatedBuffer& buffer ); // Random functions void createSwapchain( uint32_t width, uint32_t height ); void buildImgui(); void destroySwapchain(); void drawBackground( VkCommandBuffer cmd ); void drawGeometry( VkCommandBuffer cmd ); void drawImgui( VkCommandBuffer cmd, VkImageView targetImageView ); void resizeSwapchain(); }; #endif #ifdef ENGINE_IMPL #ifndef ENGINE_IMPL_H #define ENGINE_IMPL_H //========== Implementation ========== // Implementation defines #define LOADER_IMPL #define IMAGES_IMPL #define INITIALIZERS_IMPL #define DESCRIPTORS_IMPL #define PIPELINES_IMPL #define CALLBACK_IMPL #define TIMER_IMPL #define LOGGER_IMPL #define TYPES_IMPL #define VMA_IMPLEMENTATION // Custom includes #include "images.h" #include "initializers.h" #include "callbacks.h" #include "descriptors.h" #include "pipelines.h" #include "loader.h" #include "Common/Types.h" #include "Common/Timer.h" #include "Common/Logger.h" // Imgui #include "imgui.h" #include "imgui_impl_glfw.h" #include "imgui_impl_vulkan.h" #include "imgui_memory_editor.h" // For mem edit #include "VkBootstrap.h" #include "vk_mem_alloc.h" // STL includes #include #ifdef DEBUG constexpr bool bUseValidationLayers = true; #else constexpr bool bUseValidationLayers = false; #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("[Engine:Error] Vulkan error : {}", string_VkResult(err)); \ // abort(); \ // } \ // } while (0) \ //====================== //=== Main Functions === //====================== void Engine::parseConfig( EngineConfig conf ) { _conf = conf; if(!conf.modelPath.size() == 0) { #ifdef DEBUG _logger.logAll(ENGINE, EINFO, "Model overwrite, using {}", conf.modelPath); #endif } } void Engine::init() { #ifdef DEBUG std::ifstream infile("LICENSE"); _logger.logAll(ENGINE, EINIT, "Initializing the engine..."); _logger.logAll(ENGINE, EINFO, "Current engine version {}", BUILD_ID); _logger.logAll(ENGINE, EINFO, "Compiled on {} at {}", __DATE__ ,__TIME__); if (infile.good()) { std::string sLine; std::getline(infile, sLine); _logger.logAll(ENGINE, EINFO, "Current Licence : {}", sLine); } #endif if(!glfwInit()) { _logger.logFatal(ENGINE, "Failed to init GLFW !"); } // Get primary monitor GLFWmonitor* monitor { glfwGetPrimaryMonitor() }; // Get video mode (resolution) const GLFWvidmode* mode = glfwGetVideoMode(monitor); if(mode) { _windowExtent.width = mode->width; _windowExtent.height = mode->height; } glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); window = glfwCreateWindow( static_cast(_windowExtent.width), static_cast(_windowExtent.height), "Engine", nullptr, nullptr ); glfwSetKeyCallback(window, Callback::keyboardCallback); initRender(); initDefaultData(); _isInitialized = true; #ifdef DEBUG _logger.logRAM(ENGINE, EINIT, "Engine Initialized !"); #endif } void Engine::cleanup() { #ifdef DEBUG _logger.logConsole(CLEANUP, EINFO, "Cleaning up..."); _logger.logConsole(CLEANUP, EINFO, "Total mem usage {} Bytes", _gpuMemusage); #endif if (_isInitialized) { // Wait for the GPU to stop working vkDeviceWaitIdle(_device); _syncDeletionQueue.flush(); for (int i = 0; i < FRAME_OVERLAP; i++) { vkDestroyCommandPool(_device, _frames[i]._commandPool, nullptr); // Sync objects // Uses the deletion queue to clear ressources _frames[i]._deletionQueue.flush(); } // Destroy meshes for(auto& mesh: testMeshes) { destroyBuffer(mesh->meshBuffers.indexBuffer); destroyBuffer(mesh->meshBuffers.vertexBuffer); } // 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); // Destroy the window (at the end) glfwDestroyWindow(window); glfwTerminate(); } _logger.logSeparator(); #ifdef DEBUG _logger.logConsole(CLEANUP, EINFO, "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; VkResult aquireResult {vkAcquireNextImageKHR(_device, _swapchain, UINT64_MAX, getCurrentFrame()._swapchainSemaphore, nullptr, &swapchainImageIndex)}; VkSemaphore renderSemaphore = _renderSemaphores[swapchainImageIndex]; if (aquireResult == VK_SUBOPTIMAL_KHR || aquireResult == VK_ERROR_OUT_OF_DATE_KHR) { // Recreate swapchain _requestResize = true; return; } else if (aquireResult != VK_SUCCESS) { _logger.logFatal(ENGINE, "Failed to aquire swapchain image"); } // Reset fences to redo sync VK_CHECK(vkResetFences(_device, 1, &getCurrentFrame()._renderFence)); VkCommandBuffer cmd = getCurrentFrame()._mainCommandBuffer; _drawExtent.width = std::min(_viewportPanelSize.width, _drawImage.imageExtent.width) * renderScale; _drawExtent.height = std::min(_viewportPanelSize.height, _drawImage.imageExtent.height) * renderScale; 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); // Prepare for geometry draw vkutil::transitionImage( cmd, _drawImage.image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL ); vkutil::transitionImage( cmd, _depthImage.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL ); drawGeometry(cmd); /* // Commented for Viewport // Transfer the draw image and swapchain image to the correct layouts vkutil::transitionImage( cmd, _drawImage.image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, 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 ); // Set the swapchain image layout to Attachment Optimal so ImGui can write on top of it vkutil::transitionImage( cmd, _swapchainImages[swapchainImageIndex], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL ); */ vkutil::transitionImage( cmd, _drawImage.image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL ); vkutil::transitionImage( cmd, _swapchainImages[swapchainImageIndex], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL ); drawImgui( cmd, _swapchainImageViews[swapchainImageIndex] ); // Uses the present layout to display into the screen vkutil::transitionImage( cmd, _swapchainImages[swapchainImageIndex], VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_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, renderSemaphore); VkSubmitInfo2 submit = vkinit::submitInfo(&cmdinfo, &signalInfo, &waitInfo); VK_CHECK(vkQueueSubmit2(_graphicsQueue, 1, &submit, getCurrentFrame()._renderFence)); VkPresentInfoKHR presentInfo { .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, .pNext = nullptr, .waitSemaphoreCount = 1, .pWaitSemaphores = &renderSemaphore, .swapchainCount = 1, .pSwapchains = &_swapchain, .pImageIndices = &swapchainImageIndex }; VkResult presentResult {vkQueuePresentKHR(_graphicsQueue, &presentInfo)}; if (presentResult == VK_SUBOPTIMAL_KHR || presentResult == VK_ERROR_OUT_OF_DATE_KHR) { // Recreate swapchain _requestResize = true; return; } else if (presentResult != VK_SUCCESS) { _logger.logFatal(ENGINE, "Failed to present swapchain image"); } _frameNumber++; } void Engine::run() { // Todo, add frametime, and average // Average buffer should have a fixed size, // overwriting older values // It should display at the end, alongside engine runtime #ifdef DEBUG _logger.logAll(ENGINE, EINFO,"Running..."); RunTimer timer; timer.startTimer(); #endif bool stopRendering = false; while (!glfwWindowShouldClose(window)) { glfwPollEvents(); // Pipeline change monitor int currentID = currentBackgroundEffect; if (prevID != currentID) { //_logger.logRAM(ENGINE, EINFO, "Background shader changed"); //_logger.logFatal(ENGINE, "Background shader changed !"); } prevID = currentID; int w, h; glfwGetFramebufferSize(window, &w, &h); if (w != _windowExtent.width || h != _windowExtent.height ) { _requestResize = true; }; if (_requestResize) {resizeSwapchain();}; if (glfwGetWindowAttrib(window, GLFW_ICONIFIED)) { stopRendering = true; } else { stopRendering = false; } if (stopRendering) { // Minimized, pause std::this_thread::sleep_for(std::chrono::milliseconds(100)); continue; } buildImgui(); draw(); ImGuiIO& io = ImGui::GetIO(); if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) { ImGui::UpdatePlatformWindows(); ImGui::RenderPlatformWindowsDefault(); } // Merge glfw logger with main logger _logger.merge(logger.logBuffer); logger.clear(); } #ifdef DEBUG _logger.logImportant(ENGINE, EINFO, "Engine has finished running after {} seconds!", timer.endTimer()); #endif } //====================== //=== Init Functions === //====================== void Engine::initRender() { initVulkan(); initSwapchain(); initCommands(); initSyncStructures(); initDescriptors(); initPipelines(); initImgui(); } void Engine::initVulkan() { #ifdef DEBUG _logger.logRAM(ENGINE, EINIT, "Initializing Vulkan..."); #endif volkInitialize(); vkb::InstanceBuilder builder; // Create a basic VK instance, with debuging features if compiled in debug mode 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; volkLoadInstance(_instance); // Create the surface if (glfwCreateWindowSurface(_instance, window, nullptr, & _surface) != VK_SUCCESS) { _logger.logFatal(ENGINE, "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 }; if(_conf.gpuIndex < 0) { // No overwrite, use VKB // 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 }; _logger.logAll(ENGINE, EINFO, "Device {}", physicalDevice.name); 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() }; } else { // Overwrite, select GPU by hand #ifdef DEBUG _logger.logAll(ENGINE, EINFO, "Manual GPU selection, choose {}", _conf.gpuIndex); #endif uint32_t deviceCount = 0; vkEnumeratePhysicalDevices(_instance, &deviceCount, nullptr); if (deviceCount == 0) { _logger.logFatal(ENGINE, "No Vulkan GPU found !"); } std::vector devices(deviceCount); vkEnumeratePhysicalDevices(_instance, &deviceCount, devices.data()); if (_conf.gpuIndex >= deviceCount) { _logger.logFatal( ENGINE, "Invalid GPU index, there is {} available gpus (you selected {})", deviceCount, _conf.gpuIndex ); } _choosenGPU = devices[_conf.gpuIndex]; VkPhysicalDeviceProperties props{}; vkGetPhysicalDeviceProperties(_choosenGPU, &props); _logger.logAll(ENGINE, EINFO, "Device {}", props.deviceName); // Now, find queues by hand uint32_t queueFamilyCount = 0; vkGetPhysicalDeviceQueueFamilyProperties(_choosenGPU, &queueFamilyCount, nullptr); std::vector queueFamilies(queueFamilyCount); vkGetPhysicalDeviceQueueFamilyProperties(_choosenGPU, &queueFamilyCount, queueFamilies.data()); std::optional queueFamily; for (uint32_t i = 0; i < queueFamilyCount; ++i) { VkBool32 presentSupport = VK_FALSE; vkGetPhysicalDeviceSurfaceSupportKHR(_choosenGPU, i, _surface, &presentSupport); if ((queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) && presentSupport) { queueFamily = i; break; } } if (!queueFamily.has_value()) { _logger.logFatal(ENGINE, "No graphics queue compatible with the surface on the selected GPU !"); } _graphicsQueueFamily = queueFamily.value(); // Create logical device float queuePriority = 1.0f; VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = _graphicsQueueFamily; queueCreateInfo.queueCount = 1; queueCreateInfo.pQueuePriorities = &queuePriority; features13.pNext = &features12; std::vector deviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; VkDeviceCreateInfo deviceCreateInfo{ .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .pNext = &features13, .queueCreateInfoCount = 1, .pQueueCreateInfos = &queueCreateInfo, .enabledExtensionCount = static_cast(deviceExtensions.size()), .ppEnabledExtensionNames = deviceExtensions.data() }; if (vkCreateDevice(_choosenGPU, &deviceCreateInfo, nullptr, &_device) != VK_SUCCESS) { _logger.logFatal(ENGINE, "Failed to create Vulkan device !"); } vkGetDeviceQueue(_device, _graphicsQueueFamily, 0, &_graphicsQueue); } volkLoadDevice(_device); VmaVulkanFunctions vmaFuncs{ .vkGetInstanceProcAddr = vkGetInstanceProcAddr, // Volk pointers .vkGetDeviceProcAddr = vkGetDeviceProcAddr }; VmaAllocatorCreateInfo allocatorInfo = { .flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT, .physicalDevice = _choosenGPU, .device = _device, .pVulkanFunctions = &vmaFuncs, .instance = _instance }; vmaCreateAllocator(&allocatorInfo, &_allocator); _mainDeletionQueue.pushFunction([this]() { #ifdef DEBUG logger.logConsole(CLEANUP, EINFO, "Allocator"); #endif vmaDestroyAllocator(_allocator); }); #ifdef DEBUG _logger.logRAM(ENGINE, EINIT, "Vulkan initialized !"); #endif } void Engine::initSwapchain() { #ifdef DEBUG _logger.logRAM(ENGINE, EINIT, "Initializing the swapchain..."); #endif /* glfwGetFramebufferSize( window, (int*)&_windowExtent.width, (int*)&_windowExtent.height ); */ createSwapchain(_windowExtent.width, _windowExtent.height); // Allocate the offscreen target once, at a big enough size // so we don't need to reallocate. const GLFWvidmode* mode = glfwGetVideoMode(glfwGetPrimaryMonitor()); uint32_t maxW = mode ? mode->width : _windowExtent.width; uint32_t maxH = mode ? mode->height : _windowExtent.height; VkExtent3D drawImageExtent = { maxW, maxH, 1 }; // Seeds a drawExtent so frame 0 has something to render // if ImGui isn't drawn yet _drawExtent = { _windowExtent.width, _windowExtent.height }; // 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 | VK_IMAGE_USAGE_SAMPLED_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 ) ); // Depth image _depthImage.imageFormat = VK_FORMAT_D32_SFLOAT; _depthImage.imageExtent = drawImageExtent; VkImageUsageFlags depthImageUsages {}; depthImageUsages |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; VkImageCreateInfo dImgInfo { vkinit::imageCreateInfo( _depthImage.imageFormat, depthImageUsages, drawImageExtent ) }; // Allocate and create the image vmaCreateImage( _allocator, &dImgInfo, &rImgAllocInfo, &_depthImage.image, &_depthImage.allocation, nullptr ); // Build an image view for the draw image (used for rendering) VkImageViewCreateInfo dViewInfo { vkinit::imageViewCreateInfo( _depthImage.imageFormat, _depthImage.image, VK_IMAGE_ASPECT_DEPTH_BIT ) }; VK_CHECK( vkCreateImageView( _device, &dViewInfo, nullptr, &_depthImage.imageView ) ); // Add to deletion queue _mainDeletionQueue.pushFunction([this]() { #ifdef DEBUG _logger.logConsole(CLEANUP, EINFO, "Swapchain (draw images)"); #endif // Draw image vkDestroyImageView( _device, _drawImage.imageView, nullptr ); vmaDestroyImage( _allocator, _drawImage.image, _drawImage.allocation ); // Depth image vkDestroyImageView( _device, _depthImage.imageView, nullptr ); vmaDestroyImage( _allocator, _depthImage.image, _depthImage.allocation ); }); #ifdef DEBUG _logger.logRAM(ENGINE, EINIT, "Swapchain initialized !"); _logger.logRAM( ENGINE, EINFO, "DrawImage dimensions {} x {}", drawImageExtent.width, drawImageExtent.height ); #endif } void Engine::initCommands() { #ifdef DEBUG _logger.logRAM(ENGINE, EINIT, "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 ) ); } // Immediate commands VK_CHECK( vkCreateCommandPool( _device, &commandPoolInfo, nullptr, &_immCommandPool ); ); VkCommandBufferAllocateInfo cmdAllocInfo = vkinit::commandBufferAllocateInfo(_immCommandPool, 1); VK_CHECK( vkAllocateCommandBuffers( _device, &cmdAllocInfo, &_immCommandBuffer ) ); _mainDeletionQueue.pushFunction([this]() { #ifdef DEBUG _logger.logConsole(CLEANUP, EINFO, "Command pools (immediate"); #endif vkDestroyCommandPool( _device, _immCommandPool, nullptr ); }); #ifdef DEBUG _logger.logRAM(ENGINE, EINIT, "Vulkan commands initialized !"); #endif } void Engine::initSyncStructures() { #ifdef DEBUG _logger.logRAM(ENGINE, EINIT, "Creating sync structures..."); #endif // Render semaphores, depends on swapchain _renderSemaphores.resize(_swapchainImages.size()); VkSemaphoreCreateInfo semaphoreCreateInfo = vkinit::semaphoreCreateInfo(); for (size_t i = 0; i < _renderSemaphores.size(); ++i) { VK_CHECK(vkCreateSemaphore(_device, &semaphoreCreateInfo, nullptr, &_renderSemaphores[i])); _syncDeletionQueue.pushFunction([this, i]() { vkDestroySemaphore(_device, _renderSemaphores[i], nullptr); }); } // Now, other sync objects, that depends on FRAME_OVERLAP VkFenceCreateInfo fenceCreateInfo = vkinit::fenceCreateInfo(VK_FENCE_CREATE_SIGNALED_BIT); 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)); _syncDeletionQueue.pushFunction([this, i]() { vkDestroyFence(_device, _frames[i]._renderFence, nullptr); vkDestroySemaphore(_device, _frames[i]._swapchainSemaphore, nullptr); }); } // Immediate sync objects VK_CHECK( vkCreateFence( _device, &fenceCreateInfo, nullptr, &_immFence ); ); _syncDeletionQueue.pushFunction([this]() { #ifdef DEBUG _logger.logConsole(CLEANUP, EINFO, "Sync structures (immediate fences)"); #endif vkDestroyFence( _device, _immFence, nullptr ); }); #ifdef DEBUG _logger.logRAM(ENGINE, EINIT, "Sync structures created !"); #endif } void Engine::initDescriptors(){ #ifdef DEBUG _logger.logRAM(ENGINE, EINIT, "Initializing descriptors..."); #endif std::vector sizes = { { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1 } }; globalDescriptorAllocator.initPool( _device, 10, sizes ); { DescriptorLayoutBuilder builder; builder.addBinding(0, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE); _drawImageDescriptorLayout = builder.build( _device, VK_SHADER_STAGE_COMPUTE_BIT ); } _drawImageDescriptors = globalDescriptorAllocator.allocate( _device, _drawImageDescriptorLayout ); VkDescriptorImageInfo imgInfo { .imageView = _drawImage.imageView, .imageLayout = VK_IMAGE_LAYOUT_GENERAL }; VkWriteDescriptorSet drawImageWrite = { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .pNext = nullptr, .dstSet = _drawImageDescriptors, .dstBinding = 0, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, .pImageInfo = &imgInfo }; vkUpdateDescriptorSets( _device, 1, &drawImageWrite, 0, nullptr ); _mainDeletionQueue.pushFunction([this]() { #ifdef DEBUG _logger.logConsole(CLEANUP, EINFO, "Descriptors sets"); #endif globalDescriptorAllocator.destroyPool(_device); vkDestroyDescriptorSetLayout( _device, _drawImageDescriptorLayout, nullptr ); }); #ifdef DEBUG _logger.logRAM(ENGINE, EINIT, "Descriptors initialized !"); #endif } void Engine::initImgui() { #ifdef DEBUG _logger.logRAM(ENGINE, EINIT, "Initializing ImGui..."); #endif // Oversized descriptor pool for IMGUI, from the official tutorial // Ajust for optimization, or needs VkDescriptorPoolSize poolSizes[] = { { VK_DESCRIPTOR_TYPE_SAMPLER, 1000 }, { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 }, { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 }, { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000 }, { VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000 }, { VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000 }, { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000 }, { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000 }, { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000 }, { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000 }, { VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 } }; VkDescriptorPoolCreateInfo poolInfo { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, .maxSets = 1000, .poolSizeCount = (uint32_t)std::size(poolSizes), .pPoolSizes = poolSizes }; VkDescriptorPool imguiPool; VK_CHECK( vkCreateDescriptorPool( _device, &poolInfo, nullptr, &imguiPool ); ); // Init IMGUI ImGui::CreateContext(); // Config ImGuiIO& io = ImGui::GetIO(); // Docking io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Viewports io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Init backends ImGui_ImplGlfw_InitForVulkan( window, true ); ImGui_ImplVulkan_InitInfo initInfo { .Instance = _instance, .PhysicalDevice = _choosenGPU, .Device = _device, .Queue = _graphicsQueue, .DescriptorPool = imguiPool, .MinImageCount = 3, .ImageCount = 3, .PipelineInfoMain = { .MSAASamples = VK_SAMPLE_COUNT_1_BIT, .PipelineRenderingCreateInfo { .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR, .colorAttachmentCount = 1, .pColorAttachmentFormats = &_swapchainImageFormat } }, .UseDynamicRendering = true }; ImGui_ImplVulkan_Init(&initInfo); // Create a sampler for Viewport rendering // Todo // Conversion to structs VkSamplerCreateInfo samplerInfo = {}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; samplerInfo.magFilter = VK_FILTER_LINEAR; samplerInfo.minFilter = VK_FILTER_LINEAR; samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; samplerInfo.anisotropyEnable = VK_FALSE; samplerInfo.maxAnisotropy = 1.0f; VK_CHECK(vkCreateSampler(_device, &samplerInfo, nullptr, &_viewportSampler)); // Register the draw image as a texture for ImGui _viewportTextureID = ImGui_ImplVulkan_AddTexture( _viewportSampler, _drawImage.imageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL ); _mainDeletionQueue.pushFunction([this, imguiPool]() { #ifdef DEBUG _logger.logConsole(CLEANUP, EINFO, "Imgui"); #endif ImGui_ImplVulkan_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); vkDestroyDescriptorPool( _device, imguiPool, nullptr ); vkDestroySampler( _device, _viewportSampler, nullptr ); }); #ifdef DEBUG _logger.logRAM(ENGINE, EINIT, "ImGui initialized !"); #endif } //================= //=== Pipelines === //================= void Engine::initPipelines() { #ifdef DEBUG _logger.logRAM(ENGINE, EINIT, "Initializing pipelines..."); #endif // COMPUTE PIPELINES initBackgroundPipelines(); // GRAPHICS PIPELINES initMeshPipeline(); #ifdef DEBUG _logger.logRAM(ENGINE, EINIT, "Pipelines initialized !"); #endif } void Engine::initBackgroundPipelines() { #ifdef DEBUG _logger.logRAM(ENGINE, EINIT, "Initializing compute pipelines..."); #endif VkPushConstantRange pushConstant { .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, .offset = 0, .size = sizeof(ComputePushConstants) }; VkPipelineLayoutCreateInfo computeLayout { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .pNext = nullptr, .setLayoutCount = 1, .pSetLayouts = &_drawImageDescriptorLayout, .pushConstantRangeCount = 1, .pPushConstantRanges = &pushConstant }; VK_CHECK( vkCreatePipelineLayout( _device, &computeLayout, nullptr, &_gradientPipelineLayout ); ); // Create the actual shader VkShaderModule gradientShader; if ( !vkutil::loadShaderModule( "shaders/gradient_color.spv", _device, &gradientShader ) ) { _logger.logFatal(ENGINE, "Error when building the gradient compute shader !"); } // Create the second shader VkShaderModule skyShader; if ( !vkutil::loadShaderModule( "shaders/sky.spv", _device, &skyShader ) ) { _logger.logFatal(ENGINE, "Error when building the sky compute shader !"); } // Fist Shader VkPipelineShaderStageCreateInfo stageInfo{ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .pNext = nullptr, .stage = VK_SHADER_STAGE_COMPUTE_BIT, .module = gradientShader, .pName = "main" }; VkComputePipelineCreateInfo computePipelineCreateInfo { .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, .pNext = nullptr, .stage = stageInfo, .layout = _gradientPipelineLayout }; ComputeEffect gradient { .name = "gradient", .layout = _gradientPipelineLayout, .data = { .data1 = glm::vec4(1, 0, 0, 1), .data2 = glm::vec4(0, 0, 1, 1) } }; VK_CHECK( vkCreateComputePipelines( _device, VK_NULL_HANDLE, 1, &computePipelineCreateInfo, nullptr, &gradient.pipeline ); ); // Change the shader module to create the sky shader computePipelineCreateInfo.stage.module = skyShader; ComputeEffect sky { .name = "sky", .layout = _gradientPipelineLayout, .data = { .data1 = glm::vec4(0.1, 0.2, 0.4, 0.97) } }; VK_CHECK( vkCreateComputePipelines( _device, VK_NULL_HANDLE, 1, &computePipelineCreateInfo, nullptr, &sky.pipeline ); ); // Add the effects into the array backgroundEffects.push_back(gradient); backgroundEffects.push_back(sky); // Do some cleanup vkDestroyShaderModule( _device, gradientShader, nullptr ); vkDestroyShaderModule( _device, skyShader, nullptr ); _mainDeletionQueue.pushFunction([this, sky, gradient]() { #ifdef DEBUG _logger.logConsole(CLEANUP, EINFO, "Compute pipelines"); #endif vkDestroyPipelineLayout( _device, _gradientPipelineLayout, nullptr ); vkDestroyPipeline( _device, sky.pipeline, nullptr ); vkDestroyPipeline( _device, gradient.pipeline, nullptr ); }); #ifdef DEBUG _logger.logRAM(ENGINE, EINIT, "Compute pipelines initalized !"); #endif } void Engine::initMeshPipeline() { #ifdef DEBUG _logger.logRAM(ENGINE, EINIT, "Initializing mesh pipeline..."); #endif VkShaderModule triangleFragShader; if (!vkutil::loadShaderModule("shaders/colored_triangle_fragment.spv", _device, &triangleFragShader)) { _logger.logFatal(ENGINE, "Error when building the triangle fragment shader module"); } else { _logger.logRAM(ENGINE, EINFO, "Triangle fragment shader succesfully loaded"); } VkShaderModule triangleVertexShader; if (!vkutil::loadShaderModule("shaders/colored_triangle_mesh.spv", _device, &triangleVertexShader)) { _logger.logFatal(ENGINE, "Error when building the triangle vertex shader module"); } else { _logger.logRAM(ENGINE, EINFO, "Triangle vertex shader succesfully loaded"); } VkPushConstantRange bufferRange{}; bufferRange.offset = 0; bufferRange.size = offsetof(GPUDrawPushConstants, vertexBuffer) + sizeof(VkDeviceAddress); bufferRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; VkPipelineLayoutCreateInfo pipelineLayoutInfo = vkinit::pipelineLayoutCreateInfo(); pipelineLayoutInfo.pPushConstantRanges = &bufferRange; pipelineLayoutInfo.pushConstantRangeCount = 1; VK_CHECK(vkCreatePipelineLayout(_device, &pipelineLayoutInfo, nullptr, &_meshPipelineLayout)); PipelineBuilder pipelineBuilder; // Use the triangle layout we created pipelineBuilder._pipelineLayout = _meshPipelineLayout; // Connecting the vertex and pixel shaders to the pipeline pipelineBuilder.setShaders(triangleVertexShader, triangleFragShader); // It will draw triangles pipelineBuilder.setInputTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); // Filled triangles pipelineBuilder.setPolygonMode(VK_POLYGON_MODE_FILL); // No backface culling pipelineBuilder.setCullMode(VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE); // No multisampling pipelineBuilder.setMultisamplingNone(); // Blending //pipelineBuilder.disableBlending(); pipelineBuilder.enableBlendingAdditive(); pipelineBuilder.enableDepthtest( true, VK_COMPARE_OP_GREATER_OR_EQUAL /* Greater or equal because 0 is far and 1 is near, so render the pixels if the current value (depth) is greater than the value on the depth image */ ); // Connect the image format we will draw into, from draw image pipelineBuilder.setColorAttachmentFormat(_drawImage.imageFormat); pipelineBuilder.setDepthFormat(_depthImage.imageFormat); // Finally build the pipeline _meshPipeline = pipelineBuilder.buildPipeline(_device); // Clean structures vkDestroyShaderModule(_device, triangleFragShader, nullptr); vkDestroyShaderModule(_device, triangleVertexShader, nullptr); _mainDeletionQueue.pushFunction([this]() { vkDestroyPipelineLayout(_device, _meshPipelineLayout, nullptr); vkDestroyPipeline(_device, _meshPipeline, nullptr); }); #ifdef DEBUG _logger.logRAM(ENGINE, EINIT, "Mesh pipeline initialized !"); #endif } //==================================== //=== Allocation-related Functions === //==================================== AllocatedBuffer Engine::createBuffer( size_t allocSize, VkBufferUsageFlags usage, VmaMemoryUsage memoryUsage ) { #ifdef DEBUG _logger.logRAM(ENGINE, EINIT, "Creating a buffer with memory usage {} KiB and usage: {}", (float)allocSize/1024.0f, string_VkBufferUsageFlags(usage)); _gpuMemusage += allocSize; #endif // Allocate the buffer VkBufferCreateInfo bufferInfo { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .pNext = nullptr, .size = allocSize, .usage = usage }; VmaAllocationCreateInfo vmaAllocInfo { .flags = VMA_ALLOCATION_CREATE_MAPPED_BIT, .usage = memoryUsage }; AllocatedBuffer newBuffer; newBuffer.size = allocSize; // Allocate the buffer VK_CHECK( vmaCreateBuffer( _allocator, &bufferInfo, &vmaAllocInfo, &newBuffer.buffer, &newBuffer.allocation, &newBuffer.info ); ); return newBuffer; } void Engine::destroyBuffer( const AllocatedBuffer& buffer ) { #ifdef DEBUG _gpuMemusage -= buffer.size; #endif vmaDestroyBuffer( _allocator, buffer.buffer, buffer.allocation ); } //============================== //=== Mesh-related functions === //============================== GPUMeshBuffers Engine::uploadMesh( std::span indices, std::span vertices ) { #ifdef DEBUG _logger.logRAM(ENGINE, EINFO, "Uploading mesh..."); #endif const size_t vertexBufferSize = vertices.size() * sizeof(Vertex); const size_t indexBufferSize = indices.size() * sizeof(uint32_t); GPUMeshBuffers newSurface; // Create a vertex buffer newSurface.vertexBuffer = createBuffer( vertexBufferSize, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, VMA_MEMORY_USAGE_GPU_ONLY ); // Get the address of the vertex buffer VkBufferDeviceAddressInfo deviceAddressInfo { .sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, .buffer = newSurface.vertexBuffer.buffer }; newSurface.vertexBufferAddress = vkGetBufferDeviceAddress(_device, &deviceAddressInfo); // Create a index buffer newSurface.indexBuffer = createBuffer( indexBufferSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_GPU_ONLY ); // Create a staging buffer AllocatedBuffer staging = createBuffer( vertexBufferSize + indexBufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_CPU_ONLY ); void* data = staging.allocation->GetMappedData(); // Copy vertex buffer memcpy( data, vertices.data(), vertexBufferSize ); // Same, but for index buffer memcpy( (char*)data + vertexBufferSize, indices.data(), indexBufferSize ); immediateSubmit( [&](VkCommandBuffer cmd) { VkBufferCopy vertexCopy{ 0 }; vertexCopy.dstOffset = 0; vertexCopy.srcOffset = 0; vertexCopy.size = vertexBufferSize; vkCmdCopyBuffer( cmd, staging.buffer, newSurface.vertexBuffer.buffer, 1, &vertexCopy ); VkBufferCopy indexCopy{ 0 }; indexCopy.dstOffset = 0; indexCopy.srcOffset = vertexBufferSize; indexCopy.size = indexBufferSize; vkCmdCopyBuffer( cmd, staging.buffer, newSurface.indexBuffer.buffer, 1, &indexCopy ); } ); destroyBuffer(staging); return newSurface; } //============================== //=== Default data functions === //============================== void Engine::initDefaultData() { #ifdef DEBUG _logger.logRAM(ENGINE, EINIT, "Creating default data..."); #endif std::array rectVertices; rectVertices[0].position = {0.5, -0.5, 0}; rectVertices[1].position = {0.5, 0.5, 0}; rectVertices[2].position = {-0.5, -0.5, 0}; rectVertices[3].position = {-0.5, 0.5, 0}; rectVertices[0].color = {0, 0, 0, 1}; rectVertices[1].color = {0.5, 0.5, 0.5, 1}; rectVertices[2].color = {1, 0, 0, 1}; rectVertices[3].color = {0, 1, 0, 1}; std::array rectIndices; rectIndices[0] = 0; rectIndices[1] = 1; rectIndices[2] = 2; rectIndices[3] = 2; rectIndices[4] = 1; rectIndices[5] = 3; rectangle = uploadMesh( rectIndices, rectVertices ); // Load the mesh testMeshes = loadGltfMeshes(this, _modelPath).value(); // Delete the default data on engine shutdown _mainDeletionQueue.pushFunction([this](){ #ifdef DEBUG _logger.logConsole(CLEANUP, EINFO, "Default Data"); #endif destroyBuffer(rectangle.indexBuffer); destroyBuffer(rectangle.vertexBuffer); }); #ifdef DEBUG _logger.logRAM(ENGINE, EINIT, "Default data created !"); #endif } //================================ //=== Render-related Functions === //================================ void Engine::createSwapchain( uint32_t width, uint32_t height ) { #ifdef DEBUG _logger.logRAM(ENGINE, EINIT, "Creating the Swapchain with dimensions {}x{}", width, height); #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) // FIFO = VSync // Mailbox = Unapped .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 _logger.logRAM(ENGINE, EINFO, "Swapchain created with dimentions {}x{}", vkbSwapchain.extent.width, vkbSwapchain.extent.height); #endif } // Ran once/frame, no logs because it will be noise void Engine::buildImgui() { // New frames ImGui_ImplVulkan_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); // Docking ImGui::DockSpaceOverViewport(); // Fullscreen dockspace //_logger.logRAM(ENGINE, EINFO, "Framecount {}", ImGui::GetFrameCount()); // Main ImGui things ImGui::ShowDemoWindow(); // Viewport if (ImGui::Begin( "Viewport", nullptr, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse )) { ImVec2 size = ImGui::GetWindowSize(); if (size.x > 0 && size.y > 0) { // Panel size is used to determine // render target for this frame _viewportPanelSize = { (uint32_t)size.x, (uint32_t)size.y }; ImVec2 uv1( (float)_drawExtent.width / (float)_drawImage.imageExtent.width, (float)_drawExtent.height / (float)_drawImage.imageExtent.height ); ImGui::Image(_viewportTextureID, size, ImVec2(0, 0), uv1); } } ImGui::End(); // Depth image // Todo // But follows the same logic as viewport, // but does not resize dynamicly // Shader control if(ImGui::Begin("Background Shader control")) { ComputeEffect& selected = backgroundEffects[currentBackgroundEffect]; ImGui::SliderFloat("Render scale", &renderScale, 0.3f, 1.0f); ImGui::SliderInt("Mesh Index", &meshIndex, 0, 2); ImGui::Text( "Selected effect: %s", selected.name ); ImGui::SliderInt( "Effect Index", ¤tBackgroundEffect, 0, backgroundEffects.size() - 1 ); ImGui::InputFloat4( "data1", (float*)& selected.data.data1 ); ImGui::InputFloat4( "data2", (float*)& selected.data.data2 ); ImGui::InputFloat4( "data3", (float*)& selected.data.data3 ); ImGui::InputFloat4( "data4", (float*)& selected.data.data4 ); } ImGui::End(); // Logs if(ImGui::Begin("Logs")) { ImGui::TextWrapped("%s", _logger.logBuffer.c_str()); float scrollY = ImGui::GetScrollY(); float maxScrollY = ImGui::GetScrollMaxY(); if (scrollY >= maxScrollY - 1.0f) { ImGui::SetScrollHereY(1.0f); } } ImGui::End(); #ifdef DEBUG // Stats if(ImGui::Begin("Stats")) { // Todo: // Frametime // Triangle count // Etc... ImGui::TextWrapped("GPU memory usage : %.2f KiB", _gpuMemusage/1024.0f); ImGui::TextWrapped("Swapchain dimensions : %ix%i", _swapchainExtent.width, _swapchainExtent.height); if(ImGui::Button("Crash (debug)")) { _logger.logFatal(ENGINE, "Crash ! (debug)"); } } ImGui::End(); #endif #ifdef DEBUG // Memory editor if(ImGui::Begin("Memory")) { _memEdit.DrawContents(this, sizeof(*this), (size_t)this); } ImGui::End(); #endif // Finish everything ImGui::Render(); } void Engine::destroySwapchain() { #ifdef DEBUG _logger.logConsole(CLEANUP, EINFO, "Swapchain"); #endif vkDestroySwapchainKHR(_device, _swapchain, nullptr); for (uint32_t i = 0; i < _swapchainImageViews.size(); i++) { vkDestroyImageView(_device, _swapchainImageViews[i], nullptr); } } 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); ComputeEffect& effect = backgroundEffects[currentBackgroundEffect]; vkCmdBindPipeline( cmd, VK_PIPELINE_BIND_POINT_COMPUTE, effect.pipeline ); vkCmdBindDescriptorSets( cmd, VK_PIPELINE_BIND_POINT_COMPUTE, _gradientPipelineLayout, 0, 1, &_drawImageDescriptors, 0, nullptr ); /* ComputePushConstants pc; pc.data1 = glm::vec4(1, 0, 0, 1); pc.data2 = glm::vec4(0, 0, 1, 1); */ vkCmdPushConstants( cmd, _gradientPipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(ComputePushConstants), &effect.data ); vkCmdDispatch( cmd, std::ceil( _drawExtent.width / 16.0 ), std::ceil( _drawExtent.height / 16.0 ), 1 ); } void Engine::drawGeometry( VkCommandBuffer cmd ) { // Store some info uint32_t width = _drawExtent.width; uint32_t height = _drawExtent.height; // Begin rendering // Some info structs VkRenderingAttachmentInfo colorAttachment { vkinit::attachmentInfo( _drawImage.imageView, nullptr, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL ) }; VkRenderingAttachmentInfo depthAttachement { vkinit::depthAttachmentInfo( _depthImage.imageView, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL ) }; VkRenderingInfo renderInfo { vkinit::renderingInfo( _drawExtent, &colorAttachment, &depthAttachement ) }; vkCmdBeginRendering(cmd, &renderInfo); // Bind the mesh pipeline vkCmdBindPipeline( cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _meshPipeline ); // Dynamic viewport and scissor VkViewport viewport = { .x = 0, .y = 0, .width = (float)width, .height = (float)height, .minDepth = 0.f, .maxDepth = 1.f }; vkCmdSetViewport(cmd, 0, 1, &viewport); VkRect2D scissor = { .offset = { .x = 0, .y = 0 }, .extent = { .width = (uint32_t)width, .height = (uint32_t)height } }; vkCmdSetScissor(cmd, 0, 1, &scissor); GPUDrawPushConstants pushConstants; pushConstants.worldMatrix = glm::mat4{ 1.0f }; pushConstants.vertexBuffer = rectangle.vertexBufferAddress; vkCmdPushConstants( cmd, _meshPipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, offsetof(GPUDrawPushConstants, vertexBuffer) + sizeof(VkDeviceAddress), &pushConstants ); vkCmdBindIndexBuffer( cmd, rectangle.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32 ); // Draw the rectangle vkCmdDrawIndexed(cmd, 6, 1, 0, 0, 0); // Makes it turn static auto startTime = std::chrono::high_resolution_clock::now(); auto currentTime = std::chrono::high_resolution_clock::now(); float time = std::chrono::duration(currentTime - startTime).count(); // Model glm::mat4 model = rotate(glm::mat4(1.0f), time/10 * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); // turn // Correct the matrix glm::mat4 view = glm::translate(glm::vec3{ 0, 0, -5}); // Camera projection glm::mat4 projection = glm::perspective( glm::radians(70.0f), (float)_drawExtent.width / (float)_drawExtent.height, 10000.0f, 0.1f ); // Invert the Y direction projection[1][1] *= -1; pushConstants.worldMatrix = projection * view * model; pushConstants.vertexBuffer = testMeshes[2]->meshBuffers.vertexBufferAddress; vkCmdPushConstants( cmd, _meshPipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(GPUDrawPushConstants), &pushConstants ); vkCmdBindIndexBuffer( cmd, testMeshes[meshIndex]->meshBuffers.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32 ); vkCmdDrawIndexed( cmd, testMeshes[meshIndex]->surfaces[0].count, 1, testMeshes[meshIndex]->surfaces[0].startIndex, 0, 0 ); vkCmdEndRendering(cmd); } void Engine::drawImgui( VkCommandBuffer cmd, VkImageView targetImageView ) { VkRenderingAttachmentInfo colorAttachment { vkinit::attachmentInfo( targetImageView, nullptr, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL ) }; VkRenderingInfo renderInfo { vkinit::renderingInfo( _swapchainExtent, &colorAttachment, nullptr ) }; vkCmdBeginRendering( cmd, &renderInfo ); ImGui_ImplVulkan_RenderDrawData( ImGui::GetDrawData(), cmd ); vkCmdEndRendering(cmd); } void Engine::immediateSubmit( std::function&& function ) { VK_CHECK( vkResetFences( _device, 1, &_immFence ); ); VK_CHECK( vkResetCommandBuffer( _immCommandBuffer, 0 ); ); VkCommandBuffer cmd = _immCommandBuffer; VkCommandBufferBeginInfo cmdBeginInfo = vkinit::commandBufferBeginInfo(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT); VK_CHECK( vkBeginCommandBuffer( cmd, &cmdBeginInfo ); ); function(cmd); VK_CHECK( vkEndCommandBuffer( cmd ); ); VkCommandBufferSubmitInfo cmdInfo = vkinit::commandBufferSubmitInfo(cmd); VkSubmitInfo2 submit = vkinit::submitInfo( &cmdInfo, nullptr, nullptr ); // Blocking operation, waits until graphics operation finish VK_CHECK( vkQueueSubmit2( _graphicsQueue, 1, &submit, _immFence ); ); VK_CHECK( vkWaitForFences( _device, 1, &_immFence, true, 9999999999 //INT64_MAX ); ); } void Engine::resizeSwapchain() { #ifdef DEBUG _logger.logRAM(ENGINE, EINFO, "Resizing swapchain..."); #endif vkDeviceWaitIdle(_device); destroySwapchain(); int w, h; glfwGetFramebufferSize(window, &w, &h); _windowExtent.width = w; _windowExtent.height = h; _syncDeletionQueue.flush(); createSwapchain(_windowExtent.width, _windowExtent.height); initSyncStructures(); _requestResize = false; #ifdef DEBUG _logger.logRAM(ENGINE, EINFO, "Swapchain resized !"); #endif } // CLI functions void Engine::getAvailableGPU() { // Creates a temporary Vulkan Instance // Largely copied from initVulkan function #ifdef DEBUG _logger.logDisk(ENGINE, EINFO, "Listing available GPUs..."); #endif if (volkInitialize() != VK_SUCCESS) { _logger.logFatal(ENGINE, "Failed to initialize volk (no Vulkan loader found)"); return; } vkb::InstanceBuilder builder; // Create a minimal instance auto instRet = builder.set_app_name("Engine") .request_validation_layers(false) .set_headless() .require_api_version(1, 3, 0) .build(); if (!instRet) { _logger.logFatal(ENGINE, "Failed to create Vulkan instance for GPU listing: {}", instRet.error().message()); return; } vkb::Instance vkbInst = instRet.value(); VkInstance instance = vkbInst.instance; volkLoadInstance(instance); // Get GPUs uint32_t deviceCount {0}; vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); // Juicy vector, to interate upon std::vector devices(deviceCount); vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); _logger.logAll(ENGINE, EINFO, "Found {} devices !", deviceCount); // Now, loop ! for( uint32_t i = 0; i < devices.size(); ++i ) { // Get properties VkPhysicalDeviceProperties2 deviceProperties{ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2 }; vkGetPhysicalDeviceProperties2( devices[i], &deviceProperties ); _logger.logAll(ENGINE, EINFO, "GPU {}:\n{}", i, deviceProperties.properties.deviceName); } vkb::destroy_instance(vkbInst); } #endif #endif