learn-vulkan/vk_engine.cpp

368 lines
12 KiB
C++

#include "vk_engine.h"
#include "vk_images.h"
#include <print>
#include <SDL.h>
#include <SDL_vulkan.h>
#include <vk_types.h>
#include <vk_initializers.h>
#include "VkBootstrap.h"
#include <chrono>
#include <thread>
#define VMA_IMPLEMENTATION
#include "vk_mem_alloc.h"
constexpr bool bUseValidationLayers = false;
VulkanEngine* loadedEngine = nullptr;
VulkanEngine& VulkanEngine::Get() {
return *loadedEngine;
}
void VulkanEngine::init()
{
assert(loadedEngine == nullptr);
loadedEngine = this;
// We initialize SDL and create a window with it.
SDL_Init(SDL_INIT_VIDEO);
SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_VULKAN);
_window = SDL_CreateWindow(
"Vulkan Engine",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
int(_windowExtent.width),
int(_windowExtent.height),
window_flags);
init_vulkan();
init_swapchain();
init_commands();
init_sync_structures();
//everything went fine
_isInitialized = true;
}
void VulkanEngine::cleanup()
{
if (_isInitialized) {
vkDeviceWaitIdle(_device);
for(size_t 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);
_frames[i]._deletionQueue.flush();
}
_mainDeletionQueue.flush();
destroy_swapchain();
vkDestroySurfaceKHR(_instance, _surface, nullptr);
vkDestroyDevice(_device, nullptr);
vkb::destroy_debug_utils_messenger(_instance, _debug_messenger);
vkDestroyInstance(_instance, nullptr);
SDL_DestroyWindow(_window);
}
loadedEngine = nullptr;
}
void VulkanEngine::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 = 0;
VK_CHECK(vkAcquireNextImageKHR(_device, _swapchain, 1000000000, get_current_frame()._swapchainSemaphore, nullptr, &swapchainImageIndex));
VkCommandBuffer cmd = get_current_frame()._mainCommandBuffer;
VK_CHECK(vkResetCommandBuffer(cmd, 0));
VkCommandBufferBeginInfo cmdBeginInfo = vkinit::command_buffer_begin_info(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT);
_drawExtent.width = _drawImage.imageExtent.width;
_drawExtent.height = _drawImage.imageExtent.height;
VK_CHECK(vkBeginCommandBuffer(cmd, &cmdBeginInfo));
// transition our main draw image into general layout so we can write into it
// we will overwrite it all so we dont care about what was the older layout
vkutil::transition_image(cmd, _drawImage.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);
draw_background(cmd);
vkutil::transition_image(cmd, _drawImage.image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
vkutil::transition_image(cmd, _swapchainImages[swapchainImageIndex], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
vkutil::copy_image_to_image(cmd, _drawImage.image,
_swapchainImages[swapchainImageIndex],
_drawExtent, _swapchainExtent);
vkutil::transition_image(cmd, _swapchainImages[swapchainImageIndex], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
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++;
}
void VulkanEngine::run()
{
SDL_Event e;
bool bQuit = false;
bool stop_rendering = 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;
}
}
}
if(stop_rendering) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue;
}
draw();
}
}
void VulkanEngine::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
vkb::PhysicalDeviceSelector selector{ vkb_inst };
vkb::PhysicalDevice physicalDevice = selector
.set_minimum_version(1, 4)
.set_required_features_13(features)
.set_required_features_12(features12)
.set_surface(_surface)
.select()
.value();
vkb::DeviceBuilder deviceBuilder{physicalDevice};
vkb::Device vkbDevice = deviceBuilder.build().value();
_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 VulkanEngine::create_swapchain(uint32_t width, uint32_t height) {
vkb::SwapchainBuilder swapchainBuilder{ _chosenGPU, _device, _surface};
_swapchainImageFormat = VK_FORMAT_B8G8R8A8_UNORM;
VkSurfaceFormatKHR surfaceFormat{};
surfaceFormat.format=_swapchainImageFormat;
vkb::Swapchain vkbSwapchain = swapchainBuilder
//.use_default_format_selection()
.set_desired_format(surfaceFormat)
// 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;
_swapchain = vkbSwapchain.swapchain;
_swapchainImages = vkbSwapchain.get_images().value();
_swapchainImageViews = vkbSwapchain.get_image_views().value();
}
void VulkanEngine::destroy_swapchain() {
vkDestroySwapchainKHR(_device, _swapchain, nullptr);
for(auto& view : _swapchainImageViews) {
vkDestroyImageView(_device, view, nullptr);
}
}
void VulkanEngine::init_swapchain() {
create_swapchain(_windowExtent.width, _windowExtent.height);
VkExtent3D drawImageExtent = {
_windowExtent.width,
_windowExtent.height,
1
};
_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);
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([=, this]() {
vkDestroyImageView(_device, _drawImage.imageView, nullptr);
vmaDestroyImage(_allocator, _drawImage.image, _drawImage.allocation);
});
}
void VulkanEngine::init_commands() {
//create a command pool for commands submitted to the graphics queue.
//we also want the pool to allow for resetting of individual command buffers
VkCommandPoolCreateInfo commandPoolInfo = vkinit::command_pool_create_info(_graphicsQueueFamily, VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT);
for (size_t i = 0; i < 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 VulkanEngine::init_sync_structures() {
VkFenceCreateInfo fenceCreateInfo = vkinit::fence_create_info(VK_FENCE_CREATE_SIGNALED_BIT);
VkSemaphoreCreateInfo semaphoreCreateInfo = vkinit::semaphore_create_info();
for (size_t 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));
}
}
void VulkanEngine::draw_background(VkCommandBuffer cmd)
{
VkClearColorValue clearValue;
float flash = std::abs(std::sin(float(_frameNumber) / 120.0f));
clearValue = { { 0.0f, 0.0f, flash, 1.0f} };
VkImageSubresourceRange clearRange = vkinit::image_subresource_range(VK_IMAGE_ASPECT_COLOR_BIT);
vkCmdClearColorImage(cmd, _drawImage.image, VK_IMAGE_LAYOUT_GENERAL, &clearValue, 1, &clearRange);
}