543 lines
23 KiB
C++
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
|
|
*/
|
|
|