vulkan-project/vulkan_instance.hpp
2022-10-07 23:30:44 +02:00

488 lines
19 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 {
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" },
/* { "", "" } */
};
const int BINDING = 0;
const uint32_t NO_FLAGS = 0;
const uint32_t NO_OFFSET = 0;
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;
public:
VulkanInstance(gz::SettingsManagerCreateInfo<SettingsTypes>smCI) : settings(smCI) {};
void init();
uint32_t beginFrameDraw();
void submitThisFrame(VkCommandBuffer);
void endFrameDraw(uint32_t imageIndex);
void deInit();
GLFWwindow* window;
void cleanup();
/// The frame in the swap chain that is currently drawn to
uint32_t currentFrame = 0;
//
// SETTINGS
//
gz::SettingsManager<SettingsTypes> settings;
private:
void mainLoop();
std::vector<VkCommandBuffer> commandBuffersToSubmitThisFrame;
void createWindow();
//
// 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;
VkPhysicalDeviceProperties phDevProperties;
VkPhysicalDeviceFeatures phDevFeatures;
/**
* @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
* 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;
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 recreateSwapChain();
void cleanupSwapChain();
//
// IMAGE VIEW
//
std::vector<VkImageView> scImageViews;
void createImageViews();
/**
* @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
//
/**
* @name Create desciptors
* @details These functions create a desciptor with bindings for
* -# UniformBufferObject (DESCRIPTOR_TYPE_UNIFORM_BUFFER)
* -# Combined Image Sampler (DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER)
* @{
*/
VkDescriptorSetLayout descriptorSetLayout;
/**
* @brief Create a descriptor layout binding for the MVP uniform buffer object
*/
void createDescriptorSetLayout();
VkDescriptorPool descriptorPool;
/**
* @brief Create a descriptor layout binding for the MVP uniform buffer object
* @details Create a desciptor set layout with bindings for
* -# UniformBufferObject (DESCRIPTOR_TYPE_UNIFORM_BUFFER)
* -# Combined Image Sampler (DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER)
*/
void createDescriptorPool();
std::vector<VkDescriptorSet> descriptorSets;
/*
* @bried Create one descriptor set with layout descriptorSetLayout for each frame
*/
void createDescriptorSets();
const uint32_t bindingUniformBuffer = 0;
const uint32_t bindingCombinedImageSampler = 1;
/* const uint32_t bindingCombinedImageSampler = 1; */
/**
* @}
*/
//
// PIPELINE
//
PipelineContainer pipelines;
/* 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, 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
//
/* std::vector<VkFramebuffer> scFramebuffers; */
std::set<int> framebufferIDs;
// render pass is need for recreation of framebuffer
std::unordered_map<int, std::pair<std::vector<VkFramebuffer>, VkRenderPass>> scFramebuffers;
/**
* @brief Create a vector of framebuffers and return the id as a handle
* @returns id that can be used with getFramebuffers and destroyFramebuffers
*/
int createFramebuffers(std::vector<VkImageView>& imageViews, VkRenderPass& renderPass);
/**
* @brief Destroy the framebuffers from id
*/
void destroyFramebuffers(int id);
void createFramebuffers_(std::vector<VkFramebuffer>& framebuffers, std::vector<VkImageView>& imageViews, VkRenderPass& renderPass);
/**
* @brief Recreate all framebuffers in scFramebuffers
*/
void recreateFramebuffers();
/**
* @brief Get the framebuffer vector to id
*/
std::vector<VkFramebuffer>& getFramebuffers(int id);
bool frameBufferResized = false;
static void frameBufferResizedCallback(GLFWwindow* window, int width, int height);
//
// COMMAND POOL
//
VkCommandPool commandPoolGraphics;
VkCommandPool commandPoolTransfer;
void createCommandPools();
//
// COMMAND BUFFER
//
void createCommandBuffers(std::vector<VkCommandBuffer>& commandBuffers);
void destroyCommandBuffers(std::vector<VkCommandBuffer>& commandBuffers);
std::vector<VkCommandBuffer> commandBuffersBegin;
std::vector<VkCommandBuffer> commandBuffersEnd;
//
// IMAGE UTILITY
//
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, 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, uint32_t width, uint32_t height); //, VkCommandPool& commandPool=commandPoolTransfer);
/**
* @brief Transition the layout of image from oldLayout to newLayout
* @details
* Supported transitions:
* - undefined -> depth stencil attachment (graphics q)
* - undefined -> transfer dst optimal (transfer q)
* - transfer dst optimal -> shader read only optimal (graphics q)
* - transfer dst optimal -> present src (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.
* @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();
//
// 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
//
/**
* @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.
*/
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
*/