All checks were successful
Build VulkanEngine / build (push) Successful in 5m10s
Build VulkanEngine / nix (push) Successful in 3m22s
Build VulkanEngine / debug (push) Successful in 3m16s
Build VulkanEngine / release (BUNDLE) (push) Successful in 3m45s
Build VulkanEngine / release (PORTABLE) (push) Successful in 3m35s
Build VulkanEngine / notify (push) Successful in 4s
2312 lines
56 KiB
C++
2312 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();
|
|
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
|
|
ImGui::UpdatePlatformWindows();
|
|
ImGui::RenderPlatformWindowsDefault();
|
|
}
|
|
|
|
// 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
|
|
volkInitialize();
|
|
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;
|
|
|
|
volkLoadInstance(_instance);
|
|
|
|
// 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);
|
|
}
|
|
|
|
volkLoadDevice(_device);
|
|
|
|
VmaVulkanFunctions vmaFuncs{
|
|
.vkGetInstanceProcAddr = vkGetInstanceProcAddr, // Volk pointers
|
|
.vkGetDeviceProcAddr = vkGetDeviceProcAddr
|
|
};
|
|
|
|
VmaAllocatorCreateInfo allocatorInfo = {
|
|
.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT,
|
|
.physicalDevice = _choosenGPU,
|
|
.device = _device,
|
|
.pVulkanFunctions = &vmaFuncs,
|
|
.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",
|
|
¤tBackgroundEffect,
|
|
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
|
|
|
|
if (volkInitialize() != VK_SUCCESS) {
|
|
_logger.logFatal(ENGINE, "Failed to initialize volk (no Vulkan loader found)");
|
|
return;
|
|
}
|
|
|
|
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;
|
|
|
|
volkLoadInstance(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);
|
|
}
|
|
vkb::destroy_instance(vkbInst);
|
|
}
|
|
|
|
#endif
|
|
#endif
|