488 lines
19 KiB
C++
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
|
|
*/
|
|
|