Vulkan/src/Engine/engine/engine.h
Alexandre 5ce6f70155
Some checks failed
Build VulkanEngine / build (push) Failing after 2m24s
Fixed a compilation error
2026-10-01 00:21:24 +02:00

2285 lines
56 KiB
C++

#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 <GLFW/glfw3.h>
#include <cstdint>
#include <fmt/base.h>
#include <functional>
#include <vulkan/vulkan_core.h>
#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<VkImage> _swapchainImages;
std::vector<VkImageView> _swapchainImageViews;
VkExtent2D _swapchainExtent;
FrameData _frames[FRAME_OVERLAP];
FrameData& getCurrentFrame() { return _frames[_frameNumber % FRAME_OVERLAP]; };
// Sync
std::vector<VkSemaphore> _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<ComputeEffect> 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<std::shared_ptr<MeshAsset>> testMeshes;
void parseConfig(
EngineConfig conf
);
void init();
void cleanup();
void draw();
void run();
void immediateSubmit(
std::function <void(VkCommandBuffer cmd)>&& function
);
// Public functions (for CLI args)
void getAvailableGPU();
// Meshes
GPUMeshBuffers uploadMesh(
std::span<uint32_t> indices,
std::span<Vertex> 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 <iostream>
#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<uint32_t>(_windowExtent.width),
static_cast<uint32_t>(_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();
// 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
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;
// 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<VkPhysicalDevice> 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<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
vkGetPhysicalDeviceQueueFamilyProperties(_choosenGPU, &queueFamilyCount, queueFamilies.data());
std::optional<uint32_t> 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<const char*> deviceExtensions = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME
};
VkDeviceCreateInfo deviceCreateInfo{
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.pNext = &features13,
.queueCreateInfoCount = 1,
.pQueueCreateInfos = &queueCreateInfo,
.enabledExtensionCount = static_cast<uint32_t>(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);
}
VmaAllocatorCreateInfo allocatorInfo = {
.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT,
.physicalDevice = _choosenGPU,
.device = _device,
.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<DescriptorAllocator::PoolSizeRatio> 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<uint32_t> indices,
std::span<Vertex> 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<Vertex, 4> 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<uint32_t, 6> 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",
&currentBackgroundEffect,
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<float, std::chrono::seconds::period>(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<void(VkCommandBuffer cmd)>&& 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
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;
// Get GPUs
uint32_t deviceCount {0};
vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
// Juicy vector, to interate upon
std::vector<VkPhysicalDevice> 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);
}
}
#endif
#endif