vulkan-project/vulkan_instance.hpp
2022-10-14 20:58:20 +02:00

543 lines
23 KiB
C++

#pragma once
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include "vk_convert.hpp"
#include "vertex.hpp"
#include "shape.hpp"
#include "vulkan_util.hpp"
#include <gz-util/log.hpp>
#include <gz-util/settings_manager.hpp>
#include <gz-util/util/string.hpp>
#include <vulkan/vulkan_core.h>
#include <cstdint>
#include <vector>
namespace gz::vk {
constexpr gz::Color VULKAN_MESSAGE_PREFIX_COLOR = gz::Color::BO_BLUE;
constexpr gz::Color VULKAN_MESSAGE_TIME_COLOR = gz::Color::BLUE;
const std::string CONFIG_FILE = "vulkan.conf";
const int MAX_FRAMES_IN_FLIGHT = 3;
#define SettingsTypes uint32_t, bool, float
/* const std::string MODEL_PATH = "models/gazebo-3d-model/gazebo.obj"; */
/* const std::string TEXTURE_PATH = "models/gazebo-3d-model/gazebo_diffuse.png"; */
const std::string MODEL_PATH = "models/armoire-3d-model/Armoire.obj";
const std::string TEXTURE_PATH = "models/armoire-3d-model/Armoire_diffuse.png";
const gz::util::unordered_string_map<std::string> INITIAL_SETTINGS = {
{ "framerate", "60" },
{ "anisotropy_enable", "false" },
{ "max_anisotropy", "1" },
/* { "", "" } */
};
constexpr VkClearColorValue missingTextureColor = { { 0.4f, 0.0f, 0.4f, 1.0f } };
const int BINDING = 0;
const uint32_t NO_FLAGS = 0;
const uint32_t NO_OFFSET = 0;
constexpr VkAllocationCallbacks* NO_ALLOC = nullptr;
const size_t VERTEX_BUFFER_SIZE = 512;
const size_t INDEX_BUFFER_SIZE = 512;
class VulkanInstance {
friend class Renderer;
friend class Renderer2D;
friend class Renderer3D;
friend class TextureManager;
friend class TextureAtlas;
public:
VulkanInstance(gz::SettingsManagerCreateInfo<SettingsTypes>smCI) : settings(smCI) {};
/**
* @brief Initializes the vulkan instance
* @details
* -# @ref createWindow "create a window through glfw"
* -# @ref createInstance "create the vulkan instance"
* -# @ref setupDebugMessenger "sets up the debug messenger"
* -# @ref createSurface "create a the window surface"
* -# @ref selectPhysicalDevice "select a GPU"
* -# @ref setValidSettings "set the possible settings for the SettingsManager"
* -# @ref createLogicalDevice "create a logical device"
* -# @ref createSwapChain "create the swap chain"
* -# @ref createSwapChainImageViews "create the swap chain image views"
* -# @ref createCommandPools "create the command pools"
* @todo move depth image, texture, texture samples, model stuff to renderers
* -# @ref createCommandBuffers "create command buffers for swap chain image layout transitions"
* -# @ref createSyncObjects "create synchronization objects"
*/
void init();
/**
* @brief Acquires a new frame from the swap chain and sets the image layout
* @details
* How to draw stuff right now:
* -# Call this function
* -# Call the draw functions from the renderers.
* The renderers need to put the recorded command buffers into the commandBuffersToSubmitThisFrame vector using submitThisFrame()
* -# Call endFrameDraw
*/
uint32_t beginFrameDraw();
void submitThisFrame(VkCommandBuffer& cmdBuffer);
void endFrameDraw(uint32_t imageIndex);
GLFWwindow* window;
/**
* @brief Destroys everything that was initalized in init
* @details
* -# Calls every callback registered by registerCleanupCallback() (FILO order)
* -# calls cleanupSwapChain()
* -# destroys everything initalized in init()
* -# destroys the window
* -# calls glfwTerminate()
*/
void cleanup();
void registerCleanupCallback(std::function<void()> callbackF);
void registerSwapChainRecreateCallback(std::function<void()> callbackF);
/// The frame in the swap chain that is currently drawn to
uint32_t currentFrame = 0;
//
// SETTINGS
//
gz::SettingsManager<SettingsTypes> settings;
private:
std::vector<VkCommandBuffer> commandBuffersToSubmitThisFrame;
void createWindow();
std::vector<std::function<void()>> cleanupCallbacks;
std::vector<std::function<void()>> scRecreateCallbacks;
//
// INSTANCE
//
VkInstance instance;
/**
* @brief Create the vulkan instance
* @details
* -# check if validationLayers are available (if enabled)
* -# create instance with info
* -# check if all extensions required by glfw are available
*/
void createInstance();
//
// PHYSICAL DEVICE
//
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
/// all the properties of the selected physcial device
VkPhysicalDeviceProperties phDevProperties;
/// all the features that the selected physical device supports
PhysicalDeviceFeatures phDevFeatures;
/**
* @brief Get rhe required physical device features
* @details
* The required features are:
* - synchronization2
*/
PhysicalDeviceFeatures getRequiredFeatures() const;
/**
* @brief Assign the physicalDevice handle to the @ref rateDevice "best rated" GPU
* @details
* After this method, physicalDevice, phDevProperties and phDevFeatures will be initialized
*/
void selectPhysicalDevice();
/**
* @brief Assigns a score to a physical device.
* @details
* score = GPU_TYPE_FACTOR + VRAM_SIZE (in MB) + SUPPORTED_QUEUES_FACTOR + FEATURES_FACTORS
* A GPU is unsuitable and gets score 0 if it does not:
* - have the needed queue families
* - support all necessary extensions
* - have swap chain support
*/
unsigned int rateDevice(VkPhysicalDevice device);
/**
* @brief Set valid values for the SettingsManager according to phDevFeatures and phDevProperties
* @details
* Must be called after selectPhysicalDevice and before createLogicalDevice
* Sets valid values for:
* - anisotropy_enable
* - max_anisotropy
*
*/
void setValidSettings();
/**
* @brief Find the best of the supported formats
* @param candidates Candidate format, from best to worst
* @returns The first format from candidates that is supported
*/
VkFormat findSupportedFormat(const std::vector<VkFormat>& candidates, VkImageTiling tiling, VkFormatFeatureFlags features);
//
// QUEUE
//
QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device);
QueueFamilyIndices qFamilyIndices;
VkQueue graphicsQ;
VkQueue presentQ;
// if possible, use dedicated transferQ. if not available, transferQ = graphicsQ
VkQueue transferQ;
//
// LOGICAL DEVICE
//
VkDevice device;
/**
* @details
* request anisotropic sampling feature
*/
void createLogicalDevice();
//
// SURFACE
//
VkSurfaceKHR surface;
void createSurface();
//
// SWAP CHAIN
//
VkSwapchainKHR swapChain;
std::vector<VkImage> scImages;
std::vector<VkImageView> scImageViews;
VkFormat scImageFormat;
VkExtent2D scExtent;
SwapChainSupport querySwapChainSupport(VkPhysicalDevice device);
/**
* @todo Rate formats if preferred is not available
*/
VkSurfaceFormatKHR selectSwapChainSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats);
/**
* @todo Check settings for preferred mode
*/
VkPresentModeKHR selectSwapChainPresentMode(const std::vector<VkPresentModeKHR>& availableModes);
VkExtent2D selectSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities);
void createSwapChain();
void createSwapChainImageViews();
void cleanupSwapChain();
/**
* @brief Recreate the swap chain for the current window size
* @details
* Does:
* -# get new window dimensions
* - blocks while dimensions == 0 (minimized)
* -# calls cleanupSwapChain
* -# calls createSwapChain
* -# other stuff
* -# calls all callbacks registered with registerSwapChainRecreateCallback
*/
void recreateSwapChain();
//
// IMAGE VIEW
//
/**
* @brief Create a 2D imageView with format for image.
*/
void createImageView(VkFormat format, VkImage& image, VkImageView& imageView, VkImageAspectFlags aspectFlags);
//
// RENDER PASS
//
/* VkRenderPass renderPassBegin; */
/* VkRenderPass renderPassEnd; */
/**
* @brief Create the render pass
* @details
* The subpass will contain the following attachments
* -# color attachment
* -# depth stencil attachment
*/
/* void createRenderPassBegin(); */
/* void createRenderPassEnd(); */
//
// DESCRIPTORS
//
/* VkDescriptorSetLayout descriptorSetLayout; */
/**
* @brief Create a descriptor layout with bindings
*/
void createDescriptorSetLayout(std::vector<VkDescriptorSetLayoutBinding> bindings, VkDescriptorSetLayout& layout);
/**
* @}
*/
//
// PIPELINE
//
/* void createGraphicsPipeline(); */
VkShaderModule createShaderModule(const std::vector<char>& code);
/**
* @brief Create a new graphics pipeline
* @param vertexShader Path to the SPIR-V vertex shader
* @param fragmentShader Path to the SPIR-V fragment shader
* @param useDepthStencil Wether to use depth // TODO
* @param renderPass The (already created) render pass
* @param pipelineLayout Pipeline layout handle to bind
* @param pipeline Pipeline handle to bind
* @details
* Create a pipeline with:
* - 2 shader stages: vertex and fragment shader
* - VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, vertices are triangles
* - viewport viewing the whole image as described by scExtent
* - scissor with offset (0, 0)
* - rasterizer:
* - triangles are filled with the colors from the vertex (VK_POLYGON_FILL)
* - counter clockwise front face (VK_FRONT_FACE_COUNTER_CLOCKWISE)
*
*
*/
template<VertexType T>
void createGraphicsPipeline(const std::string& vertexShader, const std::string& fragmentShader, std::vector<VkDescriptorSetLayout>& descriptorSetLayouts, bool useDepthStencil, VkRenderPass& renderPass, Pipeline& pipeline);
//
// FONT
//
// TODO
/* VkPipeline fontPipeline; */
/* VkPipelineLayout fontPipelineLayout; */
/* void createFontPipeline(); */
//
// BUFFERS
//
void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory, VkSharingMode sharingMode=VK_SHARING_MODE_EXCLUSIVE, std::vector<uint32_t>* qFamiliesWithAccess=nullptr);
uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties);
/**
* @brief Begin a command buffer that is going to be used once
* @param commandPool: The command pool from which the buffer should be allocated
*/
VkCommandBuffer beginSingleTimeCommands(VkCommandPool commandPool);
/**
* @brief Submit cmdBuffer on the queue and free it afterwards
* @param cmdBuffer: Command buffer returned by beginSingleTimeCommands()
* @param q: The queue belonging to the commandPool parameter from beginSingleTimeCommands()
*/
void endSingleTimeCommands(VkCommandBuffer cmdBuffer, VkCommandPool commandPool, VkQueue q);
/**
* @brief Copy from srcBuffer to dstBuffer
*/
void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size);
//
// VERTEX BUFFER
//
/**
* @brief Create a vertex buffer
* @param vertexCount Number of vertices the buffer should contain
* @todo Basically the same as createIndexBuffer
*/
template<VertexType VertexT>
void createVertexBuffer(size_t vertexCount, VkBuffer& vertexBuffer, VkDeviceMemory& vertexBufferMemory, VkDeviceSize& vertexBufferSize);
//
// INDEX BUFFER
//
template<SupportedIndexType T>
void createIndexBuffer(size_t indexCount, VkBuffer& indexBuffer, VkDeviceMemory& indexBufferMemory, VkDeviceSize& indexBufferSize);
//
// UNIFORM BUFFER
//
template<typename T>
void createUniformBuffers(std::vector<VkBuffer>& uniformBuffers, std::vector<VkDeviceMemory>& uniformBuffersMemory);
/* void createUniformBuffers(); */
//
// FRAMEBUFFERS
//
/**
* @brief Destroy the framebuffers
*/
void destroyFramebuffers(std::vector<VkFramebuffer>& framebuffers);
void createFramebuffers(std::vector<VkFramebuffer>& framebuffers, std::vector<VkImageView>& imageViews, VkRenderPass& renderPass);
bool frameBufferResized = false;
static void frameBufferResizedCallback(GLFWwindow* window, int width, int height);
//
// COMMAND POOL
//
VkCommandPool commandPoolGraphics;
VkCommandPool commandPoolTransfer;
void createCommandPools();
//
// COMMAND BUFFER
//
/**
* @brief Create MAX_FRAMES_IN_FLIGHT command buffers from the commandPoolGraphics
*/
void createCommandBuffers(std::vector<VkCommandBuffer>& commandBuffers);
/**
* @brief Destroy all command buffers (must be from commandPoolGraphics)
*/
void destroyCommandBuffers(std::vector<VkCommandBuffer>& commandBuffers);
std::vector<VkCommandBuffer> commandBuffersBegin;
std::vector<VkCommandBuffer> commandBuffersEnd;
//
// IMAGE UTILITY
//
/**
* @brief Create the image, allocate the imageMemory and bind the image to it
*/
void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags memoryProperties, VkImage& image, VkDeviceMemory& imageMemory);
void copyBufferToImage(VkBuffer buffer, VkImage image, int32_t offsetX, int32_t offsetY, uint32_t width, uint32_t height);
/**
* @todo make a version using vkCmdResolveImage for multisampled images
* @brief Copy srcImage to dstImage
* @details
* Both images must have:
* - same extent
* - mipLevel 0
* - layerCount 1
*
* Calls vkCmdBlitImage, but does NOT submit the commandBuffer
* @param cmdBuffer The command buffer where the command will be recorded to
* @param srcImage Image with layout TRANSFER_SRC_OPTIMAL
* @param dstImage Image with layout TRANSFER_DST_OPTIMAL
*/
void copyImageToImage(VkCommandBuffer& cmdBuffer, VkImage srcImage, VkImage dstImage, VkExtent2D extent); //, VkCommandPool& commandPool=commandPoolTransfer);
/**
* @brief Transition the layout of image from oldLayout to newLayout
* @details
* Supported transitions:
* - UNDEFINED -> DEPTH_STENCIL_ATTACHMENT_OPTIMAL (graphics q)
* - UNDEFINED -> TRANSFER_DST_OPTIMAL (transfer q)
* - SHADER_READ_ONLY_OPTIMAL -> TRANSFER_DST_OPTIMAL (graphics q)
* - TRANSFER_DST_OPTIMAL -> SHADER_READ_ONLY_OPTIMAL (graphics q)
* - TRANSFER_DST_OPTIMAL -> PRESENT_SRC_KHR (graphics q)
*
* If you do not provide a command buffer, a command buffer from the indicated queue will be created and submitted.
* If you do provide a command buffer, the command is recorded but NOT submitted.
*
* The transition is done by submitting a VkImageMemoryBarrier2
*
* @param cmdBuffer [Optional] The command buffer where the command will be recorded to
* @throws VkUserError if the transition is not supported.
*/
void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout, VkCommandBuffer* cmdBuffer=nullptr);
//
// DEPTH
//
/* VkImage depthImage; */
/* VkDeviceMemory depthImageMemory; */
/* VkImageView depthImageView; */
VkFormat findDepthFormat();
/* void createDepthImageAndView(); */
//
// TEXTURES
//
/* VkImage textureImage; */
/* VkDeviceMemory textureImageMemory; */
/* VkImageView textureImageView; */
/* VkSampler textureSampler; */
/// Load textures/image.png
/* void createTextureImageAndView(); */
void createTextureSampler(VkSampler& sampler);
//
// SYNCHRONIZATION
//
std::vector<VkSemaphore> imageAvailableSemaphores;
std::vector<VkSemaphore> renderFinishedSemaphores;
std::vector<VkFence> inFlightFences;
void createSyncObjects();
//
// SHADERS
//
static std::vector<char> readFile(const std::string& filename);
//
// MODELS
//
void loadModel();
VerticesAndIndices<uint32_t> model;
//
// DEBUG
//
void setupDebugMessenger();
void cleanupDebugMessenger();
VkDebugUtilsMessengerEXT debugMessenger;
static gz::Log vLog;
public:
//
// UTILITY
//
/**
* @name Handle ownership
* @brief Retrieve the owner of a vulkan handle
* @see ObjectUsingVulkan
* @{
*/
static std::vector<ObjectUsingVulkan> objectsUsingVulkan;
static void registerObjectUsingVulkan(const ObjectUsingVulkan& obj);
static void getHandleOwnerString(std::string_view message);
static std::string handleOwnerString;
static bool lastColorWhite;
/// @}
/**
* @brief Log messages from validation layers with the Apps logger
* @details
* Using the static vLog to log vulkan messages with a prefix dependant on the messageType.
* - Validation and performace messages usually have a bracket containing a VUID,
* these brackets are printed in yellow/red when its a warning/error.
* - Handles printed by the validation layer are checked with objectsUsingVulkan, if found
* the owner of the handle is added to the message @see registerObjectUsingVulkan, ObjectUsingVulkan
* - Different message sources (layer, shader, general...) get a prefix with a different color
*/
static VKAPI_ATTR VkBool32 VKAPI_CALL debugLog(
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverety,
VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
void* pUserData);
template<typename T, typename... Args>
VkResult runVkResultFunction(const char* name, Args&&... args) {
auto f = reinterpret_cast<T>(vkGetInstanceProcAddr(instance, name));
if (f == nullptr) {
vLog.error("getVkFunction: Could not get function:", name);
throw std::runtime_error("getVkFunction: Could not get function.");
}
else {
return f(std::forward<Args>(args)...);
}
};
template<typename T, typename... Args>
void runVkVoidFunction(const char* name, Args&&... args) {
auto f = reinterpret_cast<T>(vkGetInstanceProcAddr(instance, name));
if (f == nullptr) {
vLog.error("getVkFunction: Could not get function:", name);
throw std::runtime_error("getVkFunction: Could not get function.");
}
else {
f(std::forward<Args>(args)...);
}
}
}; // VulkanInstance
//
// IMPLEMENTATIONS
//
//
// UNIFORM BUFFERS
//
template<typename T>
void VulkanInstance::createUniformBuffers(std::vector<VkBuffer>& uniformBuffers, std::vector<VkDeviceMemory>& uniformBuffersMemory) {
VkDeviceSize bufferSize = sizeof(T);
uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT);
uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT);
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]);
}
}
}
/**
* @file Creating a vulkan instance
* @todo Write/get allocator for buffers
*/