|
|
@@ -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);
|
|
|
+ }
|
|
|
+}
|