Browse Source

Added basic render loop.

Jef Belmans 2 years ago
parent
commit
3c0d11fe37
5 changed files with 305 additions and 19 deletions
  1. 245 14
      src/vk_engine.cpp
  2. 28 2
      src/vk_engine.h
  3. 28 0
      src/vk_images.cpp
  4. 3 2
      src/vk_images.h
  5. 1 1
      src/vk_types.h

+ 245 - 14
src/vk_engine.cpp

@@ -5,6 +5,7 @@
 
 #include <vk_initializers.h>
 #include <vk_types.h>
+#include <vk_images.h>
 
 #include "VkBootstrap.h"
 
@@ -14,6 +15,12 @@
 VulkanEngine* loadedEngine = nullptr;
 VulkanEngine& VulkanEngine::Get() { return *loadedEngine; }
 
+#ifdef NDEBUG
+constexpr bool bUseValidationLayers = false;
+#else
+constexpr bool bUseValidationLayers = true;
+#endif
+
 void VulkanEngine::init()
 {
     // only one engine initialization is allowed with the application.
@@ -33,24 +40,121 @@ void VulkanEngine::init()
         _windowExtent.height,
         window_flags);
 
+    init_vulkan();
+
+    init_swapchain();
+
+    init_commands();
+
+    init_sync_structures();
+
     // everything went fine
     _isInitialized = true;
 }
 
 void VulkanEngine::cleanup()
 {
-    if (_isInitialized) {
+    if (_isInitialized)
+    {
+        // Wait for GPU
+        vkDeviceWaitIdle(_device);
+
+        // Cleanup frame data
+        for (auto & frame : _frames)
+        {
+            vkDestroyCommandPool(_device, frame._commandPool, nullptr);
+
+            // Sync structures
+            vkDestroyFence(_device, frame._renderFence, nullptr);
+            vkDestroySemaphore(_device, frame._renderSemaphore, nullptr);
+            vkDestroySemaphore(_device, frame._swapchainSemaphore, nullptr);
+        }
+
+        destroy_swapchain();
+
+        vkDestroySurfaceKHR(_instance, _surface, nullptr);
+        vkDestroyDevice(_device, nullptr);
 
+        vkb::destroy_debug_utils_messenger(_instance, _debugMessenger);
+        vkDestroyInstance(_instance, nullptr);
         SDL_DestroyWindow(_window);
     }
 
-    // clear engine pointer
     loadedEngine = nullptr;
 }
 
 void VulkanEngine::draw()
 {
-    // nothing yet
+    // Wait for GPU to finish rendering last frame, timeout: 1 second
+    VK_CHECK(vkWaitForFences(_device, 1, &get_current_frame()._renderFence, true, 1000000000));
+    VK_CHECK(vkResetFences(_device, 1, &get_current_frame()._renderFence));
+
+    // Request swapchain image
+    uint32_t swapchainImageIndex;
+    VK_CHECK(vkAcquireNextImageKHR(_device, _swapchain, 1000000000, get_current_frame()._swapchainSemaphore, nullptr,
+                                   &swapchainImageIndex));
+
+    // Get current frames command buffer
+    VkCommandBuffer cmd = get_current_frame()._mainCommandBuffer;
+
+    // Commands finished executing, reset command buffer
+    VK_CHECK(vkResetCommandBuffer(cmd, 0));
+
+    // Command buffer will be used exactly once
+    VkCommandBufferBeginInfo cmdBeginInfo = vkinit::command_buffer_begin_info(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT);
+
+    // Start recording
+    VK_CHECK(vkBeginCommandBuffer(cmd, &cmdBeginInfo));
+
+    // Transition swapchain image to writable
+    vkutil::transition_image(cmd, _swapchainImages[swapchainImageIndex],  VK_IMAGE_LAYOUT_UNDEFINED,
+                             VK_IMAGE_LAYOUT_GENERAL);
+
+    // Flash clear color every 120 frames
+    VkClearColorValue clearValue;
+    float flash = abs(sin(_frameNumber / 120.f));
+    clearValue = { { 0.0f, 0.0f, flash, 1.0f } };
+
+    VkImageSubresourceRange clearRange = vkinit::image_subresource_range(VK_IMAGE_ASPECT_COLOR_BIT);
+
+    // Clear image
+    vkCmdClearColorImage(cmd, _swapchainImages[swapchainImageIndex], VK_IMAGE_LAYOUT_GENERAL, &clearValue, 1, &clearRange);
+
+    // Transition swapchain image to presentable
+    vkutil::transition_image(cmd, _swapchainImages[swapchainImageIndex],VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
+
+    // Finalize command buffer for execution
+    VK_CHECK(vkEndCommandBuffer(cmd));
+
+    // Wait for swapchain to be ready (_presentSemaphore)
+    // Signal when renderign has finished (_renderSemaphore)
+    VkCommandBufferSubmitInfo cmdinfo = vkinit::command_buffer_submit_info(cmd);
+
+    VkSemaphoreSubmitInfo waitInfo = vkinit::semaphore_submit_info(VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR,get_current_frame()._swapchainSemaphore);
+    VkSemaphoreSubmitInfo signalInfo = vkinit::semaphore_submit_info(VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT, get_current_frame()._renderSemaphore);
+
+    VkSubmitInfo2 submit = vkinit::submit_info(&cmdinfo,&signalInfo,&waitInfo);
+
+    // Submit to queue and execute
+    // Block until queue finishes execution (_renderFence)
+    VK_CHECK(vkQueueSubmit2(_graphicsQueue, 1, &submit, get_current_frame()._renderFence));
+
+    // Wait for drawing commands to have finished (_renderSemaphore)
+    // Then display the image
+    VkPresentInfoKHR presentInfo = {};
+    presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
+    presentInfo.pNext = nullptr;
+    presentInfo.pSwapchains = &_swapchain;
+    presentInfo.swapchainCount = 1;
+
+    presentInfo.pWaitSemaphores = &get_current_frame()._renderSemaphore;
+    presentInfo.waitSemaphoreCount = 1;
+
+    presentInfo.pImageIndices = &swapchainImageIndex;
+
+    VK_CHECK(vkQueuePresentKHR(_graphicsQueue, &presentInfo));
+
+    _frameNumber++;
 }
 
 void VulkanEngine::run()
@@ -58,31 +162,158 @@ void VulkanEngine::run()
     SDL_Event e;
     bool bQuit = false;
 
-    // main loop
-    while (!bQuit) {
-        // Handle events on queue
-        while (SDL_PollEvent(&e) != 0) {
-            // close the window when user alt-f4s or clicks the X button
+    while (!bQuit)
+    {
+        while (SDL_PollEvent(&e) != 0)
+        {
             if (e.type == SDL_QUIT)
                 bQuit = true;
 
-            if (e.type == SDL_WINDOWEVENT) {
-                if (e.window.event == SDL_WINDOWEVENT_MINIMIZED) {
+            if (e.type == SDL_WINDOWEVENT)
+            {
+                if (e.window.event == SDL_WINDOWEVENT_MINIMIZED)
+                {
                     _stopRendering = true;
                 }
-                if (e.window.event == SDL_WINDOWEVENT_RESTORED) {
+                if (e.window.event == SDL_WINDOWEVENT_RESTORED)
+                {
                     _stopRendering = false;
                 }
             }
         }
 
         // do not draw if we are minimized
-        if (_stopRendering) {
-            // throttle the speed to avoid the endless spinning
+        if (_stopRendering)
+        {
             std::this_thread::sleep_for(std::chrono::milliseconds(100));
             continue;
         }
 
         draw();
     }
-}
+}
+
+void VulkanEngine::init_vulkan()
+{
+    vkb::InstanceBuilder builder;
+
+    auto inst_ret = builder.set_app_name("Coral3D")
+            .request_validation_layers(bUseValidationLayers)
+            .use_default_debug_messenger()
+            .require_api_version(1, 3, 0)
+            .build();
+
+    vkb::Instance vkb_inst = inst_ret.value();
+
+    _instance = vkb_inst.instance;
+    _debugMessenger = vkb_inst.debug_messenger;
+
+    SDL_Vulkan_CreateSurface(_window, _instance, &_surface);
+
+    //vulkan 1.3 features
+    VkPhysicalDeviceVulkan13Features features{};
+    features.dynamicRendering = true;
+    features.synchronization2 = true;
+
+    //vulkan 1.2 features
+    VkPhysicalDeviceVulkan12Features features12{};
+    features12.bufferDeviceAddress = true;
+    features12.descriptorIndexing = true;
+
+    //use vkbootstrap to select a gpu.
+    //We want a gpu that can write to the SDL surface and supports vulkan 1.3 with the correct features
+    vkb::PhysicalDeviceSelector selector{ vkb_inst };
+    vkb::PhysicalDevice physicalDevice = selector
+            .set_minimum_version(1, 3)
+            .set_required_features_13(features)
+            .set_required_features_12(features12)
+            .set_surface(_surface)
+            .select()
+            .value();
+
+
+    //create the final vulkan device
+    vkb::DeviceBuilder deviceBuilder{ physicalDevice };
+
+    vkb::Device vkbDevice = deviceBuilder.build().value();
+
+    // Get the VkDevice handle used in the rest of a vulkan application
+    _device = vkbDevice.device;
+    _physicalGPU = physicalDevice.physical_device;
+
+    _graphicsQueue = vkbDevice.get_queue(vkb::QueueType::graphics).value();
+    _graphicsQueueFamily = vkbDevice.get_queue_index(vkb::QueueType::graphics).value();
+}
+
+void VulkanEngine::init_swapchain()
+{
+    create_swapchain(_windowExtent.width, _windowExtent.height);
+}
+
+void VulkanEngine::init_commands()
+{
+    // Create command pool for commands submitted to the graphics queue
+    // and allow resetting individual command buffers;
+    VkCommandPoolCreateInfo commandPoolInfo =  vkinit::command_pool_create_info(_graphicsQueueFamily,
+                                                                                VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT);
+
+    for (auto & frame : _frames)
+    {
+        VK_CHECK(vkCreateCommandPool(_device, &commandPoolInfo, nullptr, &frame._commandPool));
+
+        // Allocate default command buffer used for rendering
+        VkCommandBufferAllocateInfo cmdAllocInfo = vkinit::command_buffer_allocate_info(frame._commandPool, 1);
+
+        VK_CHECK(vkAllocateCommandBuffers(_device, &cmdAllocInfo, &frame._mainCommandBuffer));
+    }
+}
+
+void VulkanEngine::init_sync_structures()
+{
+    // Fence is used to not record into cmd buffer that is being rendered
+    // and 2 semaphores to synchronize rendering with swapchain.
+    // Fence start signaled to be able to wait on it in the first frame.
+    VkFenceCreateInfo fenceCreateInfo = vkinit::fence_create_info(VK_FENCE_CREATE_SIGNALED_BIT);
+    VkSemaphoreCreateInfo semaphoreCreateInfo = vkinit::semaphore_create_info();
+
+    for (auto & frame : _frames)
+    {
+        VK_CHECK(vkCreateFence(_device, &fenceCreateInfo, nullptr, &frame._renderFence));
+
+        VK_CHECK(vkCreateSemaphore(_device, &semaphoreCreateInfo, nullptr, &frame._swapchainSemaphore));
+        VK_CHECK(vkCreateSemaphore(_device, &semaphoreCreateInfo, nullptr, &frame._renderSemaphore));
+    }
+}
+
+void VulkanEngine::create_swapchain(uint32_t width, uint32_t height)
+{
+    vkb::SwapchainBuilder swapchainBuilder{ _physicalGPU,_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 present mode
+            .set_desired_extent(width, height)
+            .add_image_usage_flags(VK_IMAGE_USAGE_TRANSFER_DST_BIT)
+            .build()
+            .value();
+
+    _swapchainExtent = vkbSwapchain.extent;
+
+    // Store swapchain and its images
+    _swapchain = vkbSwapchain.swapchain;
+    _swapchainImages = vkbSwapchain.get_images().value();
+    _swapchainImageViews = vkbSwapchain.get_image_views().value();
+}
+
+void VulkanEngine::destroy_swapchain()
+{
+    vkDestroySwapchainKHR(_device, _swapchain, nullptr);
+
+    for (int i = 0; i < _swapchainImageViews.size(); ++i)
+    {
+        vkDestroyImageView(_device, _swapchainImageViews[i], nullptr);
+    }
+}

+ 28 - 2
src/vk_engine.h

@@ -1,13 +1,30 @@
 #pragma once
 #include <vk_types.h>
 
+struct FrameData
+{
+    VkCommandPool _commandPool;
+    VkCommandBuffer _mainCommandBuffer;
+
+    // Sync structures
+    VkSemaphore _swapchainSemaphore, _renderSemaphore;
+    VkFence _renderFence;
+};
+constexpr unsigned int FRAMES_IN_FLIGHT = 2;
+
 class VulkanEngine
 {
 public:
-	bool _isInitialized { false };
+    FrameData _frames[FRAMES_IN_FLIGHT];
+    FrameData& get_current_frame() { return _frames[_frameNumber % FRAMES_IN_FLIGHT]; }
+
+    VkQueue _graphicsQueue;
+    uint32_t _graphicsQueueFamily;
+
+    bool _isInitialized { false };
 	int _frameNumber { 0 };
 	bool _stopRendering { false };
-	VkExtent2D _windowExtent { 1700 , 900 };
+	VkExtent2D _windowExtent { 1280 , 720 };
 
 	struct SDL_Window* _window { nullptr };
 
@@ -17,6 +34,12 @@ public:
     VkDevice _device{};
     VkSurfaceKHR _surface{};
 
+    VkSwapchainKHR _swapchain;
+    VkFormat _swapchainImageFormat;
+    std::vector<VkImage> _swapchainImages;
+    std::vector<VkImageView> _swapchainImageViews;
+    VkExtent2D _swapchainExtent;
+
 	static VulkanEngine& Get();
 	void init();
 	void cleanup();
@@ -28,4 +51,7 @@ private:
     void init_swapchain();
     void init_commands();
     void init_sync_structures();
+
+    void create_swapchain(uint32_t width, uint32_t height);
+    void destroy_swapchain();
 };

+ 28 - 0
src/vk_images.cpp

@@ -1 +1,29 @@
 #include <vk_images.h>
+#include <vk_initializers.h>
+
+void vkutil::transition_image(VkCommandBuffer cmd, VkImage image, VkImageLayout currentLayout, VkImageLayout newLayout)
+{
+    VkImageMemoryBarrier2 imageBarrier {.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2};
+    imageBarrier.pNext = nullptr;
+
+    imageBarrier.srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
+    imageBarrier.srcAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT;
+    imageBarrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
+    imageBarrier.dstAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT | VK_ACCESS_2_MEMORY_READ_BIT;
+
+    imageBarrier.oldLayout = currentLayout;
+    imageBarrier.newLayout = newLayout;
+
+    VkImageAspectFlags aspectMask = (newLayout == VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL) ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
+    imageBarrier.subresourceRange = vkinit::image_subresource_range(aspectMask);
+    imageBarrier.image = image;
+
+    VkDependencyInfo depInfo {};
+    depInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
+    depInfo.pNext = nullptr;
+
+    depInfo.imageMemoryBarrierCount = 1;
+    depInfo.pImageMemoryBarriers = &imageBarrier;
+
+    vkCmdPipelineBarrier2(cmd, &depInfo);
+}

+ 3 - 2
src/vk_images.h

@@ -1,6 +1,7 @@
 #pragma once
+#include <vulkan/vulkan.h>
+
 namespace vkutil
 {
-
-
+    void transition_image(VkCommandBuffer cmd, VkImage image, VkImageLayout currentLayout, VkImageLayout newLayout);
 };

+ 1 - 1
src/vk_types.h

@@ -28,4 +28,4 @@
             fmt::println("Detected Vulkan error: {}", string_VkResult(err)); \
             abort();                                                    \
         }                                                               \
-    } while (0)
+    } while (0)