You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
402 lines
14 KiB
402 lines
14 KiB
#include "VkEngine.h"
|
|
|
|
#include <SDL.h>
|
|
#include <SDL_vulkan.h>
|
|
|
|
#include <VkInitializers.h>
|
|
#include <VkTypes.h>
|
|
|
|
//bootstrap library
|
|
#include "VkBootstrap.h"
|
|
#include "VkImages.h"
|
|
|
|
#define VMA_IMPLEMENTATION
|
|
#include "vma/vk_mem_alloc.h"
|
|
|
|
#include <chrono>
|
|
#include <thread>
|
|
//< includes
|
|
|
|
//> init
|
|
constexpr bool bUseValidationLayers = false;
|
|
|
|
VkEngine* loadedEngine = nullptr;
|
|
|
|
VkEngine::VkEngine()
|
|
{
|
|
|
|
}
|
|
|
|
VkEngine& VkEngine::Get()
|
|
{
|
|
return *loadedEngine;
|
|
}
|
|
|
|
void VkEngine::init()
|
|
{
|
|
// only one engine initialization is allowed with the application.
|
|
assert(loadedEngine == nullptr);
|
|
loadedEngine = this;
|
|
|
|
// We initialize SDL and create a window with it.
|
|
SDL_Init(SDL_INIT_VIDEO);
|
|
|
|
SDL_WindowFlags window_flags = static_cast<SDL_WindowFlags>(SDL_WINDOW_VULKAN);
|
|
|
|
_window = SDL_CreateWindow(
|
|
"Vulkan Engine",
|
|
SDL_WINDOWPOS_UNDEFINED,
|
|
SDL_WINDOWPOS_UNDEFINED,
|
|
_windowExtent.width,
|
|
_windowExtent.height,
|
|
window_flags);
|
|
|
|
init_vulkan();
|
|
|
|
init_swapchain();
|
|
|
|
init_commands();
|
|
|
|
init_sync_structures();
|
|
|
|
// everything went fine
|
|
_isInitialized = true;
|
|
}
|
|
|
|
void VkEngine::cleanup()
|
|
{
|
|
if (_isInitialized) {
|
|
|
|
vkDeviceWaitIdle(_device);
|
|
|
|
for (int i = 0; i < static_cast<int>(FRAME_OVERLAP); i++)
|
|
{
|
|
//already written from before
|
|
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);
|
|
|
|
_frames[i]._deletionQueue.flush();
|
|
}
|
|
|
|
_mainDeletionQueue.flush();
|
|
|
|
vkDeviceWaitIdle(_device);
|
|
|
|
for (int i = 0; i < static_cast<int>(FRAME_OVERLAP); i++) {
|
|
vkDestroyCommandPool(_device, _frames[i]._commandPool, nullptr);
|
|
}
|
|
|
|
destroy_swapchain();
|
|
|
|
vkDestroySurfaceKHR(_instance, _surface, nullptr);
|
|
vkDestroyDevice(_device, nullptr);
|
|
|
|
vkb::destroy_debug_utils_messenger(_instance, _debug_messenger);
|
|
vkDestroyInstance(_instance, nullptr);
|
|
SDL_DestroyWindow(_window);
|
|
}
|
|
|
|
// clear engine pointer
|
|
loadedEngine = nullptr;
|
|
}
|
|
|
|
void VkEngine::draw()
|
|
{
|
|
// wait until the gpu has finished rendering the last frame. Timeout of 1
|
|
// second
|
|
VK_CHECK(vkWaitForFences(_device, 1, &get_current_frame()._renderFence, true, 1000000000));
|
|
|
|
get_current_frame()._deletionQueue.flush();
|
|
|
|
VK_CHECK(vkResetFences(_device, 1, &get_current_frame()._renderFence));
|
|
|
|
uint32_t swapchainImageIndex;
|
|
VK_CHECK(vkAcquireNextImageKHR(_device, _swapchain, 1000000000, get_current_frame()._swapchainSemaphore, nullptr, &swapchainImageIndex));
|
|
|
|
//naming it cmd for shorter writing
|
|
VkCommandBuffer cmd = get_current_frame()._mainCommandBuffer;
|
|
|
|
// now that we are sure that the commands finished executing, we can safely
|
|
// reset the command buffer to begin recording again.
|
|
VK_CHECK(vkResetCommandBuffer(cmd, 0));
|
|
|
|
//begin the command buffer recording. We will use this command buffer exactly once, so we want to let vulkan know that
|
|
VkCommandBufferBeginInfo cmdBeginInfo = vkinit::command_buffer_begin_info(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT);
|
|
|
|
//start the command buffer recording
|
|
VK_CHECK(vkBeginCommandBuffer(cmd, &cmdBeginInfo));
|
|
|
|
//make the swapchain image into writeable mode before rendering
|
|
vkutil::transition_image(cmd, _swapchainImages[swapchainImageIndex], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
|
|
|
|
//make a clear-color from frame number. This will flash with a 120 frame period.
|
|
VkClearColorValue clearValue;
|
|
float flash = std::abs(std::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);
|
|
|
|
//make the swapchain image into presentable mode
|
|
vkutil::transition_image(cmd, _swapchainImages[swapchainImageIndex],VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
|
|
|
|
//finalize the command buffer (we can no longer add commands, but it can now be executed)
|
|
VK_CHECK(vkEndCommandBuffer(cmd));
|
|
|
|
//prepare the submission to the queue.
|
|
//we want to wait on the _presentSemaphore, as that semaphore is signaled when the swapchain is ready
|
|
//we will signal the _renderSemaphore, to signal that rendering has finished
|
|
|
|
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 command buffer to the queue and execute it.
|
|
// _renderFence will now block until the graphic commands finish execution
|
|
VK_CHECK(vkQueueSubmit2(_graphicsQueue, 1, &submit, get_current_frame()._renderFence));
|
|
|
|
//prepare present
|
|
// this will put the image we just rendered to into the visible window.
|
|
// we want to wait on the _renderSemaphore for that,
|
|
// as its necessary that drawing commands have finished before the image is displayed to the user
|
|
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));
|
|
|
|
//increase the number of frames drawn
|
|
_frameNumber++;
|
|
}
|
|
//< extras
|
|
|
|
//> drawloop
|
|
void VkEngine::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
|
|
if (e.type == SDL_QUIT)
|
|
bQuit = true;
|
|
|
|
if (e.type == SDL_WINDOWEVENT) {
|
|
if (e.window.event == SDL_WINDOWEVENT_MINIMIZED) {
|
|
stop_rendering = true;
|
|
}
|
|
if (e.window.event == SDL_WINDOWEVENT_RESTORED) {
|
|
stop_rendering = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// do not draw if we are minimized
|
|
if (stop_rendering) {
|
|
// throttle the speed to avoid the endless spinning
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
continue;
|
|
}
|
|
|
|
draw();
|
|
}
|
|
}
|
|
|
|
void VkEngine::init_vulkan()
|
|
{
|
|
vkb::InstanceBuilder builder;
|
|
|
|
//make the vulkan instance, with basic debug features
|
|
auto inst_ret = builder.set_app_name("Example Vulkan Application")
|
|
.request_validation_layers(bUseValidationLayers)
|
|
.use_default_debug_messenger()
|
|
.require_api_version(1, 3, 0)
|
|
.build();
|
|
|
|
vkb::Instance vkb_inst = inst_ret.value();
|
|
|
|
//grab the instance
|
|
_instance = vkb_inst.instance;
|
|
_debug_messenger = vkb_inst.debug_messenger;
|
|
|
|
SDL_Vulkan_CreateSurface(_window, _instance, &_surface);
|
|
|
|
//vulkan 1.3 features
|
|
VkPhysicalDeviceVulkan13Features features{};
|
|
features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES;
|
|
features.dynamicRendering = true;
|
|
features.synchronization2 = true;
|
|
|
|
//vulkan 1.2 features
|
|
VkPhysicalDeviceVulkan12Features features12{};
|
|
features12.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES;
|
|
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;
|
|
_chosenGPU = physicalDevice.physical_device;
|
|
|
|
_graphicsQueue = vkbDevice.get_queue(vkb::QueueType::graphics).value();
|
|
_graphicsQueueFamily = vkbDevice.get_queue_index(vkb::QueueType::graphics).value();
|
|
|
|
// initialize the memory allocator
|
|
VmaAllocatorCreateInfo allocatorInfo = {};
|
|
allocatorInfo.physicalDevice = _chosenGPU;
|
|
allocatorInfo.device = _device;
|
|
allocatorInfo.instance = _instance;
|
|
allocatorInfo.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT;
|
|
vmaCreateAllocator(&allocatorInfo, &_allocator);
|
|
|
|
_mainDeletionQueue.push_function([&]() {
|
|
vmaDestroyAllocator(_allocator);
|
|
});
|
|
|
|
}
|
|
|
|
void VkEngine::create_swapchain(uint32_t width, uint32_t height)
|
|
{
|
|
vkb::SwapchainBuilder swapchainBuilder{ _chosenGPU,_device,_surface };
|
|
|
|
_swapchainImageFormat = VK_FORMAT_B8G8R8A8_UNORM;
|
|
|
|
vkb::Swapchain vkbSwapchain = swapchainBuilder
|
|
//.use_default_format_selection()
|
|
.set_desired_format(VkSurfaceFormatKHR{ .format = _swapchainImageFormat, .colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR })
|
|
//use vsync present mode
|
|
.set_desired_present_mode(VK_PRESENT_MODE_FIFO_KHR)
|
|
.set_desired_extent(width, height)
|
|
.add_image_usage_flags(VK_IMAGE_USAGE_TRANSFER_DST_BIT)
|
|
.build()
|
|
.value();
|
|
|
|
_swapchainExtent = vkbSwapchain.extent;
|
|
//store swapchain and its related images
|
|
_swapchain = vkbSwapchain.swapchain;
|
|
_swapchainImages = vkbSwapchain.get_images().value();
|
|
_swapchainImageViews = vkbSwapchain.get_image_views().value();
|
|
}
|
|
|
|
void VkEngine::destroy_swapchain()
|
|
{
|
|
vkDestroySwapchainKHR(_device, _swapchain, nullptr);
|
|
|
|
// destroy swapchain resources
|
|
for (int i = 0; i < static_cast<int>(_swapchainImageViews.size()); i++) {
|
|
|
|
vkDestroyImageView(_device, _swapchainImageViews[i], nullptr);
|
|
}
|
|
}
|
|
|
|
void VkEngine::init_swapchain()
|
|
{
|
|
create_swapchain(_windowExtent.width, _windowExtent.height);
|
|
|
|
//draw image size will match the window
|
|
VkExtent3D drawImageExtent = {
|
|
_windowExtent.width,
|
|
_windowExtent.height,
|
|
1
|
|
};
|
|
|
|
//hardcoding the draw format to 32 bit float
|
|
_drawImage.imageFormat = VK_FORMAT_R16G16B16A16_SFLOAT;
|
|
_drawImage.imageExtent = drawImageExtent;
|
|
|
|
VkImageUsageFlags drawImageUsages{};
|
|
drawImageUsages |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
|
drawImageUsages |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
|
drawImageUsages |= VK_IMAGE_USAGE_STORAGE_BIT;
|
|
drawImageUsages |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
|
|
|
VkImageCreateInfo rimg_info = vkinit::image_create_info(_drawImage.imageFormat, drawImageUsages, drawImageExtent);
|
|
|
|
//for the draw image, we want to allocate it from gpu local memory
|
|
VmaAllocationCreateInfo rimg_allocinfo = {};
|
|
rimg_allocinfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
|
|
rimg_allocinfo.requiredFlags = VkMemoryPropertyFlags(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
|
|
|
//allocate and create the image
|
|
vmaCreateImage(_allocator, &rimg_info, &rimg_allocinfo, &_drawImage.image, &_drawImage.allocation, nullptr);
|
|
|
|
//build a image-view for the draw image to use for rendering
|
|
VkImageViewCreateInfo rview_info = vkinit::imageview_create_info(_drawImage.imageFormat, _drawImage.image, VK_IMAGE_ASPECT_COLOR_BIT);
|
|
|
|
VK_CHECK(vkCreateImageView(_device, &rview_info, nullptr, &_drawImage.imageView));
|
|
|
|
//add to deletion queues
|
|
_mainDeletionQueue.push_function([=]() {
|
|
vkDestroyImageView(_device, _drawImage.imageView, nullptr);
|
|
vmaDestroyImage(_allocator, _drawImage.image, _drawImage.allocation);
|
|
});
|
|
|
|
}
|
|
|
|
void VkEngine::init_commands()
|
|
{
|
|
VkCommandPoolCreateInfo commandPoolInfo = vkinit::command_pool_create_info(_graphicsQueueFamily, VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT);
|
|
|
|
for (int i = 0; i < static_cast<int>(FRAME_OVERLAP); i++) {
|
|
|
|
VK_CHECK(vkCreateCommandPool(_device, &commandPoolInfo, nullptr, &_frames[i]._commandPool));
|
|
|
|
// allocate the default command buffer that we will use for rendering
|
|
VkCommandBufferAllocateInfo cmdAllocInfo = vkinit::command_buffer_allocate_info(_frames[i]._commandPool, 1);
|
|
|
|
VK_CHECK(vkAllocateCommandBuffers(_device, &cmdAllocInfo, &_frames[i]._mainCommandBuffer));
|
|
}
|
|
}
|
|
|
|
void VkEngine::init_sync_structures()
|
|
{
|
|
//create syncronization structures
|
|
//one fence to control when the gpu has finished rendering the frame,
|
|
//and 2 semaphores to syncronize rendering with swapchain
|
|
//we want the fence to start signalled so we can wait on it on the first frame
|
|
VkFenceCreateInfo fenceCreateInfo = vkinit::fence_create_info(VK_FENCE_CREATE_SIGNALED_BIT);
|
|
VkSemaphoreCreateInfo semaphoreCreateInfo = vkinit::semaphore_create_info();
|
|
|
|
for (int i = 0; i < static_cast<int>(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));
|
|
}
|
|
}
|
|
|