Use allocator for buffers/images

everything now uses VulkanAllocator for allocations (through
VulkanInstance::createXXX functions)
This commit is contained in:
matthias@arch 2022-10-23 01:13:51 +02:00
parent e8f736d27f
commit 2bc7fe05f2
17 changed files with 435 additions and 172 deletions

View File

@ -13,7 +13,7 @@ CXXFLAGS += $(IFLAGS)
# SETTINGS # SETTINGS
OBJECT_DIR = ../build OBJECT_DIR = ../build
EXEC = ../vulkan_test EXEC = ../vulkan_test
LOG_LEVEL = LOG_LEVEL_2 LOG_LEVEL = LOG_LEVEL_0
CXXFLAGS += -D $(LOG_LEVEL) CXXFLAGS += -D $(LOG_LEVEL)

View File

@ -64,13 +64,13 @@ namespace gz::vk {
try { try {
uint32_t imageIndex; uint32_t imageIndex;
/* std::chrono::time_point now = std::chrono::system_clock::now(); */ /* std::chrono::time_point now = std::chrono::system_clock::now(); */
while (! glfwWindowShouldClose(vulkanInstance.window)) { while (! glfwWindowShouldClose(vulkanInstance.getWindow())) {
glfwPollEvents(); glfwPollEvents();
imageIndex = vulkanInstance.beginFrameDraw(); imageIndex = vulkanInstance.beginFrameDraw();
r2D.drawFrame(imageIndex); r2D.drawFrame(imageIndex);
r3D.drawFrame(imageIndex); r3D.drawFrame(imageIndex);
vulkanInstance.endFrameDraw(imageIndex); vulkanInstance.endFrameDraw(imageIndex);
auto SLEEP_TIME = std::chrono::milliseconds(1000 / vulkanInstance.settings.get<uint32_t>("framerate")); auto SLEEP_TIME = std::chrono::milliseconds(1000 / vulkanInstance.getSettings().get<uint32_t>("framerate"));
std::this_thread::sleep_for(SLEEP_TIME); std::this_thread::sleep_for(SLEEP_TIME);
/* std::chrono::time_point now2 = std::chrono::system_clock::now(); */ /* std::chrono::time_point now2 = std::chrono::system_clock::now(); */
/* std::chrono::nanoseconds dur = std::chrono::duration_cast<std::chrono::nanoseconds>(now2 - now); */ /* std::chrono::nanoseconds dur = std::chrono::duration_cast<std::chrono::nanoseconds>(now2 - now); */

View File

@ -43,6 +43,9 @@ namespace gz::vk {
void Renderer2D::cleanup() { void Renderer2D::cleanup() {
rLog.log0("cleanup: cleaning up");
// UPDATE DOC ON CHANGES!
/* vk.destroyCommandBuffers(commandBuffers); */ /* vk.destroyCommandBuffers(commandBuffers); */
cleanupSwapChainDependantResources(); cleanupSwapChainDependantResources();
cleanup_(); cleanup_();
@ -68,9 +71,8 @@ namespace gz::vk {
vk.destroyFramebuffers(framebuffers); vk.destroyFramebuffers(framebuffers);
for (size_t i = 0; i < images.size(); i++) { for (size_t i = 0; i < images.size(); i++) {
vkDestroyImageView(vk.getDevice(), imageViews[i], nullptr); vk.destroyImageView(imageViews[i]);
vkDestroyImage(vk.getDevice(), images[i], nullptr); vk.destroyImage(images[i], imageMemory[i]);
vkFreeMemory(vk.getDevice(), imageMemory[i], nullptr);
} }
vkDestroyRenderPass(vk.getDevice(), renderPass, nullptr); vkDestroyRenderPass(vk.getDevice(), renderPass, nullptr);

View File

@ -3,6 +3,7 @@
#include "renderer.hpp" #include "renderer.hpp"
#include "shape.hpp" #include "shape.hpp"
#include "vulkan_allocator.hpp"
#include "vulkan_util.hpp" #include "vulkan_util.hpp"
namespace gz::vk { namespace gz::vk {
@ -67,7 +68,7 @@ namespace gz::vk {
* @{ * @{
*/ */
std::vector<VkImage> images; std::vector<VkImage> images;
std::vector<VkDeviceMemory> imageMemory; std::vector<MemoryInfo> imageMemory;
std::vector<VkImageView> imageViews; std::vector<VkImageView> imageViews;
/** /**
* @brief Creates the images (on imageMemory) and imageViews with the format of the VulkanInstance::swapChain images * @brief Creates the images (on imageMemory) and imageViews with the format of the VulkanInstance::swapChain images

View File

@ -1,6 +1,7 @@
#include "renderer3D.hpp" #include "renderer3D.hpp"
#include "vertex.hpp" #include "vertex.hpp"
#include "vulkan_allocator.hpp"
#include "vulkan_instance.hpp" #include "vulkan_instance.hpp"
#include "texture_manager.hpp" #include "texture_manager.hpp"
#include "exceptions.hpp" #include "exceptions.hpp"
@ -49,10 +50,14 @@ namespace gz::vk {
} }
void Renderer3D::cleanup() { void Renderer3D::cleanup() {
rLog.log0("cleanup: cleaning up");
// UPDATE DOC ON CHANGES!
for (size_t i = 0; i < vk.getMaxFramesInFlight(); i++) { for (size_t i = 0; i < vk.getMaxFramesInFlight(); i++) {
vkDestroyBuffer(vk.getDevice(), uniformBuffers[i], nullptr); vk.destroyBuffer(uniformBuffers[i], uniformBuffersMemory[i]);
vkFreeMemory(vk.getDevice(), uniformBuffersMemory[i], nullptr);
} }
vkDestroyDescriptorSetLayout(vk.getDevice(), descriptorSetLayout, NO_ALLOC);
vkDestroyDescriptorPool(vk.getDevice(), descriptorPool, NO_ALLOC);
cleanupSwapChainDependantResources(); cleanupSwapChainDependantResources();
cleanup_(); cleanup_();
} }
@ -77,9 +82,8 @@ namespace gz::vk {
vk.destroyFramebuffers(framebuffers); vk.destroyFramebuffers(framebuffers);
for (size_t i = 0; i < images.size(); i++) { for (size_t i = 0; i < images.size(); i++) {
vkDestroyImageView(vk.getDevice(), imageViews[i], nullptr); vk.destroyImageView(imageViews[i]);
vkDestroyImage(vk.getDevice(), images[i], nullptr); vk.destroyImage(images[i], imageMemory[i]);
vkFreeMemory(vk.getDevice(), imageMemory[i], nullptr);
} }
vkDestroyRenderPass(vk.getDevice(), renderPass, nullptr); vkDestroyRenderPass(vk.getDevice(), renderPass, nullptr);
@ -313,32 +317,28 @@ namespace gz::vk {
// copy to vertexBuffer // copy to vertexBuffer
// create staging buffer // create staging buffer
VkBuffer stagingBuffer; VkBuffer stagingBuffer;
VkDeviceMemory stagingBufferMemory; MemoryInfo stagingBufferMemory;
vk.createBuffer(requiredVertexBufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); vk.createBuffer(requiredVertexBufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);
// fill staging buffer // fill staging buffer
void* data; void* data;
vkMapMemory(vk.getDevice(), stagingBufferMemory, NO_OFFSET, requiredVertexBufferSize, NO_FLAGS, &data); vkMapMemory(vk.getDevice(), stagingBufferMemory.memory, stagingBufferMemory.offset, requiredVertexBufferSize, NO_FLAGS, &data);
memcpy(data, model.vertices.data(), requiredVertexBufferSize); memcpy(data, model.vertices.data(), requiredVertexBufferSize);
vkUnmapMemory(vk.getDevice(), stagingBufferMemory); vkUnmapMemory(vk.getDevice(), stagingBufferMemory.memory);
// fill vertex buffer // fill vertex buffer
vk.copyBuffer(stagingBuffer, vertexBuffer, requiredVertexBufferSize); vk.copyBuffer(stagingBuffer, vertexBuffer, requiredVertexBufferSize);
vkDestroyBuffer(vk.getDevice(), stagingBuffer, nullptr); vk.destroyBuffer(stagingBuffer, stagingBufferMemory);
vkFreeMemory(vk.getDevice(), stagingBufferMemory, nullptr);
rLog.log0("Renderer3D: loadModel: filling index buffer"); rLog.log0("Renderer3D: loadModel: filling index buffer");
data = nullptr; data = nullptr;
stagingBuffer = VK_NULL_HANDLE;
stagingBufferMemory = VK_NULL_HANDLE;
// copy to index buffer // copy to index buffer
vk.createBuffer(requiredIndexBufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory); vk.createBuffer(requiredIndexBufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);
// fill staging buffer // fill staging buffer
vkMapMemory(vk.getDevice(), stagingBufferMemory, NO_OFFSET, requiredIndexBufferSize, NO_FLAGS, &data); vkMapMemory(vk.getDevice(), stagingBufferMemory.memory, stagingBufferMemory.offset, requiredIndexBufferSize, NO_FLAGS, &data);
memcpy(data, model.indices.data(), requiredIndexBufferSize); memcpy(data, model.indices.data(), requiredIndexBufferSize);
vkUnmapMemory(vk.getDevice(), stagingBufferMemory); vkUnmapMemory(vk.getDevice(), stagingBufferMemory.memory);
// fill index buffer // fill index buffer
vk.copyBuffer(stagingBuffer, indexBuffer, requiredIndexBufferSize); vk.copyBuffer(stagingBuffer, indexBuffer, requiredIndexBufferSize);
vkDestroyBuffer(vk.getDevice(), stagingBuffer, nullptr); vk.destroyBuffer(stagingBuffer, stagingBufferMemory);
vkFreeMemory(vk.getDevice(), stagingBufferMemory, nullptr);
} }
// //
@ -434,10 +434,11 @@ namespace gz::vk {
/* ubo.view = glm::mat4(1); */ /* ubo.view = glm::mat4(1); */
/* ubo.projection = glm::mat4(1); */ /* ubo.projection = glm::mat4(1); */
/* ubo.projection[1][1] *= -1; // y coordinate inverted in opengl */ /* ubo.projection[1][1] *= -1; // y coordinate inverted in opengl */
MemoryInfo& uniformBufferMI = uniformBuffersMemory[vk.getCurrentFrame()];
void* data; void* data;
vkMapMemory(vk.getDevice(), uniformBuffersMemory[vk.getCurrentFrame()], NO_OFFSET, sizeof(ubo), NO_FLAGS, &data); vkMapMemory(vk.getDevice(), uniformBufferMI.memory, uniformBufferMI.offset, sizeof(ubo), NO_FLAGS, &data);
memcpy(data, &ubo, sizeof(ubo)); memcpy(data, &ubo, sizeof(ubo));
vkUnmapMemory(vk.getDevice(), uniformBuffersMemory[vk.getCurrentFrame()]); vkUnmapMemory(vk.getDevice(), uniformBufferMI.memory);
} }

View File

@ -2,6 +2,7 @@
#include "renderer.hpp" #include "renderer.hpp"
#include "vulkan_allocator.hpp"
#include "vulkan_util.hpp" #include "vulkan_util.hpp"
namespace gz::vk { namespace gz::vk {
@ -34,7 +35,7 @@ namespace gz::vk {
private: private:
void recordCommandBuffer(uint32_t imageIndex, uint32_t currentFrame); void recordCommandBuffer(uint32_t imageIndex, uint32_t currentFrame);
std::vector<VkBuffer> uniformBuffers; std::vector<VkBuffer> uniformBuffers;
std::vector<VkDeviceMemory> uniformBuffersMemory; std::vector<MemoryInfo> uniformBuffersMemory;
void createUniformBuffers(); void createUniformBuffers();
void updateUniformBuffer(); void updateUniformBuffer();
@ -45,7 +46,7 @@ namespace gz::vk {
*/ */
/// @{ /// @{
std::vector<VkImage> images; std::vector<VkImage> images;
std::vector<VkDeviceMemory> imageMemory; std::vector<MemoryInfo> imageMemory;
std::vector<VkImageView> imageViews; std::vector<VkImageView> imageViews;
/** /**
* @brief Creates the images (on imageMemory) and imageViews with the format of the VulkanInstance::swapChain images * @brief Creates the images (on imageMemory) and imageViews with the format of the VulkanInstance::swapChain images
@ -137,6 +138,7 @@ namespace gz::vk {
* @details: * @details:
* Does: * Does:
* -# destroy uniform buffers * -# destroy uniform buffers
* -# destroy descriptor set layout and pool
* -# call cleanupSwapChainDependantResources() * -# call cleanupSwapChainDependantResources()
* -# call Renderer::cleanup_() * -# call Renderer::cleanup_()
*/ */

View File

@ -20,17 +20,28 @@ std::string TextureImageArea::toString() const {
return "<(" + gz::toString(x) + "," + gz::toString(y) + "), (" + gz::toString(width) + "x" + gz::toString(height) + ")>"; return "<(" + gz::toString(x) + "," + gz::toString(y) + "), (" + gz::toString(width) + "x" + gz::toString(height) + ")>";
} }
// //
// INIT & CLEANUP // INIT & CLEANUP
// //
TextureAtlas::TextureAtlas(VulkanInstance& instance, uint16_t slotWidth, uint16_t slotHeight, uint16_t slotCountX, uint16_t slotCountY) TextureAtlas::TextureAtlas(VulkanInstance& instance, uint16_t slotWidth, uint16_t slotHeight, uint16_t slotCountX, uint16_t slotCountY)
: vk(instance), slotWidth(slotWidth), slotHeight(slotHeight), slotCountX(slotCountX), slotCountY(slotCountY) : vk(instance),
slotWidth(slotWidth), slotHeight(slotHeight),
slotCountX(slotCountX), slotCountY(slotCountY)
{ {
#ifdef LOG_LEVEL_0
LogCreateInfo logCI{};
logCI.logfile = "textureAtlas.log";
logCI.storeLog = false;
logCI.prefix = "TextureAtlas (" + gz::toString(slotCountX) + "x" + gz::toString(slotCountY) + ")X(" + gz::toString(slotWidth) + "x" + gz::toString(slotHeight) + ")";
logCI.prefixColor = Color::GREEN;
logCI.timeColor = VULKAN_MESSAGE_TIME_COLOR;
tLog = Log(std::move(logCI));
#endif
// whole image // whole image
freeAreas.insert({ 0, 0, slotCountX, slotCountY}); freeAreas.insert({ 0, 0, slotCountX, slotCountY});
createImageResources(); createImageResources();
vk.registerCleanupCallback(std::bind(&TextureAtlas::cleanup, this)); /* vk.registerCleanupCallback(std::bind(&TextureAtlas::cleanup, this)); */
VulkanInstance::registerObjectUsingVulkan(ObjectUsingVulkan(std::string("TextureAtlas-") + gz::toString(slotWidth) + "x" + gz::toString(slotHeight), VulkanInstance::registerObjectUsingVulkan(ObjectUsingVulkan(std::string("TextureAtlas-") + gz::toString(slotWidth) + "x" + gz::toString(slotHeight),
{ &textureImage, &textureImageMemory, &textureImageView, &textureSampler }, { &textureImage, &textureImageMemory, &textureImageView, &textureSampler },
{})); {}));
@ -38,11 +49,12 @@ TextureAtlas::TextureAtlas(VulkanInstance& instance, uint16_t slotWidth, uint16_
void TextureAtlas::cleanup() { void TextureAtlas::cleanup() {
VulkanInstance::vLog("TextureAtlas::cleanup, textureSampler", reinterpret_cast<uint64_t>(textureSampler), "textureImageView", reinterpret_cast<uint64_t>(textureImageView)); #ifdef LOG_LEVEL_0
vkDestroySampler(vk.getDevice(), textureSampler, nullptr); tLog.log0("cleanup: cleaning up");
vkDestroyImageView(vk.getDevice(), textureImageView, nullptr); #endif
vkDestroyImage(vk.getDevice(), textureImage, nullptr); vk.destroyTextureSampler(textureSampler);
vkFreeMemory(vk.getDevice(), textureImageMemory, nullptr); vk.destroyImageView(textureImageView);
vk.destroyImage(textureImage, textureImageMemory);
} }
@ -51,7 +63,7 @@ void TextureAtlas::createImageResources() {
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
textureImage, textureImageMemory); textureImage, textureImageMemory);
VkCommandBuffer cmdBuffer = vk.beginSingleTimeCommands(vk.commandPoolGraphics); VkCommandBuffer cmdBuffer = vk.beginSingleTimeCommands(POOL_GRAPHICS);
vk.transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &cmdBuffer); vk.transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &cmdBuffer);
VkImageSubresourceRange subresourceRange{}; VkImageSubresourceRange subresourceRange{};
subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
@ -61,7 +73,7 @@ void TextureAtlas::createImageResources() {
subresourceRange.layerCount = 1; subresourceRange.layerCount = 1;
vkCmdClearColorImage(cmdBuffer, textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &missingTextureColor, 1, &subresourceRange); vkCmdClearColorImage(cmdBuffer, textureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &missingTextureColor, 1, &subresourceRange);
vk.transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, &cmdBuffer); vk.transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, &cmdBuffer);
vk.endSingleTimeCommands(cmdBuffer, vk.commandPoolGraphics, vk.graphicsQ); vk.endSingleTimeCommands(cmdBuffer, POOL_GRAPHICS);
vk.createImageView(VK_FORMAT_R8G8B8A8_SRGB, textureImage, textureImageView, VK_IMAGE_ASPECT_COLOR_BIT); vk.createImageView(VK_FORMAT_R8G8B8A8_SRGB, textureImage, textureImageView, VK_IMAGE_ASPECT_COLOR_BIT);
vk.createTextureSampler(textureSampler); vk.createTextureSampler(textureSampler);
@ -135,7 +147,9 @@ std::pair<glm::vec2, glm::vec2> TextureAtlas::addTexture(uint8_t* pixels, uint16
freeAreas.insert(std::move(node)); freeAreas.insert(std::move(node));
} }
mergeFreeAreas(); mergeFreeAreas();
VulkanInstance::vLog("TextureAtlas::addTexture: Adding texture at position x,y=(", x, y, "), with slotCountX,Y=(", slotsX, slotsY, "), with dimensions w,h=(", width, height, ")"); #ifdef LOG_LEVEL_0
tLog.log0("addTexture: Adding texture at position x,y=(", x, y, "), with slotCountX,Y=(", slotsX, slotsY, "), with dimensions w,h=(", width, height, ")");
#endif
blitTextureOnImage(pixels, x, y, width, height); blitTextureOnImage(pixels, x, y, width, height);
// topleft normalized: slot / slotCount // topleft normalized: slot / slotCount
// bottomright normalized: (slot + (textureSize/slotCountize)) / slotCount // bottomright normalized: (slot + (textureSize/slotCountize)) / slotCount
@ -149,14 +163,14 @@ void TextureAtlas::blitTextureOnImage(uint8_t* pixels, uint16_t slotX, uint16_t
constexpr size_t BYTES_PER_PIXEL = 4; constexpr size_t BYTES_PER_PIXEL = 4;
VkDeviceSize imageSize = textureWidth * textureHeight * BYTES_PER_PIXEL; VkDeviceSize imageSize = textureWidth * textureHeight * BYTES_PER_PIXEL;
VkBuffer stagingBuffer; VkBuffer stagingBuffer;
VkDeviceMemory stagingBufferMemory; MemoryInfo stagingBufferMemory;
vk.createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, vk.createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
stagingBuffer, stagingBufferMemory); stagingBuffer, stagingBufferMemory);
void* data; void* data;
vkMapMemory(vk.getDevice(), stagingBufferMemory, NO_OFFSET, imageSize, NO_FLAGS, &data); vkMapMemory(vk.getDevice(), stagingBufferMemory.memory, stagingBufferMemory.offset, imageSize, NO_FLAGS, &data);
memcpy(data, pixels, static_cast<size_t>(imageSize)); memcpy(data, pixels, static_cast<size_t>(imageSize));
vkUnmapMemory(vk.getDevice(), stagingBufferMemory); vkUnmapMemory(vk.getDevice(), stagingBufferMemory.memory);
vk.transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); vk.transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
vk.copyBufferToImage(stagingBuffer, textureImage, vk.copyBufferToImage(stagingBuffer, textureImage,
@ -164,14 +178,15 @@ void TextureAtlas::blitTextureOnImage(uint8_t* pixels, uint16_t slotX, uint16_t
static_cast<uint32_t>(textureWidth), static_cast<uint32_t>(textureHeight)); // dimensions static_cast<uint32_t>(textureWidth), static_cast<uint32_t>(textureHeight)); // dimensions
vk.transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); vk.transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
vkDestroyBuffer(vk.getDevice(), stagingBuffer, nullptr); vk.destroyBuffer(stagingBuffer, stagingBufferMemory);
vkFreeMemory(vk.getDevice(), stagingBufferMemory, nullptr);
} }
void TextureAtlas::mergeFreeAreas() { void TextureAtlas::mergeFreeAreas() {
begin: begin:
VulkanInstance::vLog("TextureAtlas::mergeFreeAreas before merge:", freeAreas); #ifdef LOG_LEVEL_0
tLog.log0("mergeFreeAreas before merge:", freeAreas);
#endif
// surely not the most efficient way, but it should only be run at load time and thus not be too problematic // surely not the most efficient way, but it should only be run at load time and thus not be too problematic
// iterate over the areas for each area // iterate over the areas for each area
for (auto it = freeAreas.begin(); it != freeAreas.end(); it++) { for (auto it = freeAreas.begin(); it != freeAreas.end(); it++) {
@ -195,8 +210,9 @@ begin:
} }
} }
} }
VulkanInstance::vLog("TextureAtlas::mergeFreeAreas after merge:", freeAreas); #ifdef LOG_LEVEL_0
tLog.log0("mergeFreeAreas after merge:", freeAreas);
#endif
} }
std::string TextureAtlas::toString() const { std::string TextureAtlas::toString() const {

View File

@ -1,11 +1,18 @@
#pragma once #pragma once
#include "vulkan_allocator.hpp"
#include <gz-util/log.hpp>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <vulkan/vulkan_core.h> #include <vulkan/vulkan_core.h>
#include <vector> #include <vector>
#include <string> #include <string>
#include <set> #include <set>
// only log when trace
#ifdef LOG_LEVEL_0
#include <gz-util/log.hpp>
#endif
namespace gz::vk { namespace gz::vk {
/** /**
@ -34,6 +41,7 @@ namespace gz::vk {
/// Defined in vulkan_instance.hpp /// Defined in vulkan_instance.hpp
class VulkanInstance; class VulkanInstance;
class TextureAtlas { class TextureAtlas {
public: public:
/** /**
@ -58,12 +66,14 @@ namespace gz::vk {
std::string toString() const; std::string toString() const;
private:
VulkanInstance& vk;
/** /**
* @brief Destroy all vulkan objects owned by this object * @brief Destroy all vulkan objects owned by this object
* @note This function has to be called manually (by TextureManager). This class does not register a cleanupCallback with the instance!
*/ */
void cleanup(); void cleanup();
private:
VulkanInstance& vk;
void blitTextureOnImage(uint8_t* pixels, uint16_t slotX, uint16_t slotY, uint16_t textureWidth, uint16_t textureHeight); void blitTextureOnImage(uint8_t* pixels, uint16_t slotX, uint16_t slotY, uint16_t textureWidth, uint16_t textureHeight);
/** /**
* @brief Create textureImage, textureImageView and textureSampler * @brief Create textureImage, textureImageView and textureSampler
@ -72,7 +82,7 @@ namespace gz::vk {
*/ */
void createImageResources(); void createImageResources();
VkImage textureImage; VkImage textureImage;
VkDeviceMemory textureImageMemory; MemoryInfo textureImageMemory;
VkImageView textureImageView; VkImageView textureImageView;
VkSampler textureSampler; VkSampler textureSampler;
@ -87,5 +97,10 @@ namespace gz::vk {
uint16_t slotHeight; uint16_t slotHeight;
uint16_t slotCountX; uint16_t slotCountX;
uint16_t slotCountY; uint16_t slotCountY;
/// Use log only with trace
#ifdef LOG_LEVEL_0
Log tLog;
#endif
}; };
} // namespace gz::vk } // namespace gz::vk

View File

@ -9,6 +9,14 @@ namespace gz::vk {
TextureManager::TextureManager(VulkanInstance& instance) TextureManager::TextureManager(VulkanInstance& instance)
: vk(instance) : vk(instance)
{ {
LogCreateInfo logCI{};
logCI.logfile = "textureManager.log";
logCI.storeLog = false;
logCI.prefix = "Texture";
logCI.prefixColor = Color::GREEN;
logCI.timeColor = VULKAN_MESSAGE_TIME_COLOR;
tLog = Log(std::move(logCI));
vk.registerCleanupCallback(std::bind(&TextureManager::cleanup, this)); vk.registerCleanupCallback(std::bind(&TextureManager::cleanup, this));
atlases.insert({ 0, TextureAtlas(vk, 24, 24, 24, 24) }); atlases.insert({ 0, TextureAtlas(vk, 24, 24, 24, 24) });
createDescriptorSetLayout(); createDescriptorSetLayout();
@ -21,9 +29,14 @@ namespace gz::vk {
void TextureManager::cleanup() { void TextureManager::cleanup() {
tLog.log0("cleanup: cleaning up");
/* vkFreeDescriptorSets(vk.getDevice(), descriptorPool, 1, &descriptorSet); */ /* vkFreeDescriptorSets(vk.getDevice(), descriptorPool, 1, &descriptorSet); */
vkDestroyDescriptorSetLayout(vk.getDevice(), descriptorSetLayout, NO_ALLOC); vkDestroyDescriptorSetLayout(vk.getDevice(), descriptorSetLayout, NO_ALLOC);
vkDestroyDescriptorPool(vk.getDevice(), descriptorPool, NO_ALLOC); vkDestroyDescriptorPool(vk.getDevice(), descriptorPool, NO_ALLOC);
for (auto& [i, atlas] : atlases) {
atlas.cleanup();
}
} }
@ -32,7 +45,7 @@ namespace gz::vk {
loadTextureFromFile(textureName); loadTextureFromFile(textureName);
} }
TextureInfo& texI = textureInfos[textureName]; TextureInfo& texI = textureInfos[textureName];
VulkanInstance::vLog("getTexCoords", texCoords, "textureInfo: topleft", texI.texCoordTopLeft, "botright", texI.texCoordBottomRight); tLog.log0("getTexCoords", texCoords, "textureInfo: topleft", texI.texCoordTopLeft, "botright", texI.texCoordBottomRight);
/* texCoords.x = texI.texCoordTopLeft.x + texCoords.x * (texI.texCoordBottomRight.x - texI.texCoordTopLeft.x); */ /* texCoords.x = texI.texCoordTopLeft.x + texCoords.x * (texI.texCoordBottomRight.x - texI.texCoordTopLeft.x); */
/* texCoords.y = texI.texCoordTopLeft.y + texCoords.y * (texI.texCoordBottomRight.y - texI.texCoordTopLeft.y); */ /* texCoords.y = texI.texCoordTopLeft.y + texCoords.y * (texI.texCoordBottomRight.y - texI.texCoordTopLeft.y); */
texCoords = texI.texCoordTopLeft + texCoords * (texI.texCoordBottomRight - texI.texCoordTopLeft); texCoords = texI.texCoordTopLeft + texCoords * (texI.texCoordBottomRight - texI.texCoordTopLeft);
@ -49,10 +62,10 @@ namespace gz::vk {
texI.atlas = 0; texI.atlas = 0;
auto texCoords = atlases.at(0).addTexture(pixels, textureWidth, textureHeight); auto texCoords = atlases.at(0).addTexture(pixels, textureWidth, textureHeight);
stbi_image_free(pixels); stbi_image_free(pixels);
VulkanInstance::vLog("TextureManager::loadTextureFromFile: TexCoords of new image:", texCoords.first, texCoords.second); tLog.log0("TextureManager::loadTextureFromFile: TexCoords of new image:", texCoords.first, texCoords.second);
texI.texCoordTopLeft = std::move(texCoords.first); texI.texCoordTopLeft = std::move(texCoords.first);
texI.texCoordBottomRight = std::move(texCoords.second); texI.texCoordBottomRight = std::move(texCoords.second);
VulkanInstance::vLog("TextureManager::loadTextureFromFile: After loading:", atlases.at(0)); tLog.log0("TextureManager::loadTextureFromFile: After loading:", atlases.at(0));
} }
@ -84,9 +97,9 @@ void TextureManager::createDescriptorSetLayout() {
void TextureManager::createDescriptorPool() { void TextureManager::createDescriptorPool() {
std::array<VkDescriptorPoolSize, 1> poolSizes; std::array<VkDescriptorPoolSize, 1> poolSizes;
/* poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; */ /* poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; */
/* poolSizes[0].descriptorCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT); */ /* poolSizes[0].descriptorCount = static_cast<uint32_t>(vk.getMaxFramesInFlight()); */
poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; poolSizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
poolSizes[0].descriptorCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT); poolSizes[0].descriptorCount = static_cast<uint32_t>(vk.getMaxFramesInFlight());
VkDescriptorPoolCreateInfo poolCI{}; VkDescriptorPoolCreateInfo poolCI{};
poolCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; poolCI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
@ -102,16 +115,16 @@ void TextureManager::createDescriptorPool() {
void TextureManager::createDescriptorSet() { void TextureManager::createDescriptorSet() {
/* std::vector<VkDescriptorSetLayout> layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); */ /* std::vector<VkDescriptorSetLayout> layouts(vk.getMaxFramesInFlight(), descriptorSetLayout); */
VkDescriptorSetAllocateInfo setAI{}; VkDescriptorSetAllocateInfo setAI{};
setAI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; setAI.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
setAI.descriptorPool = descriptorPool; setAI.descriptorPool = descriptorPool;
/* setAI.descriptorSetCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT); */ /* setAI.descriptorSetCount = static_cast<uint32_t>(vk.getMaxFramesInFlight()); */
/* setAI.pSetLayouts = layouts.data(); */ /* setAI.pSetLayouts = layouts.data(); */
setAI.descriptorSetCount = 1; setAI.descriptorSetCount = 1;
setAI.pSetLayouts = &descriptorSetLayout; setAI.pSetLayouts = &descriptorSetLayout;
/* descriptorSets.resize(MAX_FRAMES_IN_FLIGHT); */ /* descriptorSets.resize(vk.getMaxFramesInFlight()); */
/* VkResult result = vkAllocateDescriptorSets(vk.getDevice(), &setAI, descriptorSets.data()); */ /* VkResult result = vkAllocateDescriptorSets(vk.getDevice(), &setAI, descriptorSets.data()); */
VkResult result = vkAllocateDescriptorSets(vk.getDevice(), &setAI, &descriptorSet); VkResult result = vkAllocateDescriptorSets(vk.getDevice(), &setAI, &descriptorSet);
if (result != VK_SUCCESS) { if (result != VK_SUCCESS) {

View File

@ -3,8 +3,11 @@
#include "texture_atlas.hpp" #include "texture_atlas.hpp"
#include "stb_image.h" #include "stb_image.h"
#include <gz-util/util/string.hpp> #include <gz-util/util/string.hpp>
#include <gz-util/log.hpp>
#include <glm/fwd.hpp> #include <glm/fwd.hpp>
#include <unordered_map> #include <unordered_map>
namespace gz::vk { namespace gz::vk {
@ -38,6 +41,7 @@ namespace gz::vk {
std::unordered_map<uint32_t, TextureAtlas> atlases; std::unordered_map<uint32_t, TextureAtlas> atlases;
util::unordered_string_map<TextureInfo> textureInfos; util::unordered_string_map<TextureInfo> textureInfos;
VulkanInstance& vk; VulkanInstance& vk;
Log tLog;
/** /**
* @name Create desciptors * @name Create desciptors
* @details These functions create a desciptor with bindings for * @details These functions create a desciptor with bindings for

View File

@ -95,6 +95,28 @@ namespace gz::vk {
} }
void getBufferMemoryRequirements(const VkDevice& device, const VkBuffer& buffer, VkBufferMemoryRequirementsInfo2& bufMemReq, VkMemoryRequirements2& memReq) {
bufMemReq.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_REQUIREMENTS_INFO_2;
bufMemReq.pNext = nullptr;
bufMemReq.buffer = buffer;
memReq.sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2;
memReq.pNext = nullptr;
vkGetBufferMemoryRequirements2(device, &bufMemReq, &memReq);
}
void getImageMemoryRequirements(const VkDevice& device, const VkImage& image, VkImageMemoryRequirementsInfo2& imMemReq, VkMemoryRequirements2& memReq) {
imMemReq.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2;
imMemReq.pNext = nullptr;
imMemReq.image = image;
memReq.sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2;
memReq.pNext = nullptr;
vkGetImageMemoryRequirements2(device, &imMemReq, &memReq);
}
// //
// //
// //

View File

@ -127,6 +127,20 @@ namespace gz::vk {
* @brief Get a VkDependencyInfo struct for a single image memory barrier * @brief Get a VkDependencyInfo struct for a single image memory barrier
*/ */
VkDependencyInfo getDepInfo(const VkImageMemoryBarrier2& barrier); VkDependencyInfo getDepInfo(const VkImageMemoryBarrier2& barrier);
/**
* @brief Fill bufMeqReq and memReq
* @details
* Fill the structs and call vkGetBufferMemoryRequirements2()
*/
void getBufferMemoryRequirements(const VkDevice& device, const VkBuffer& buffer, VkBufferMemoryRequirementsInfo2& bufMemReq, VkMemoryRequirements2& memReq);
/**
* @brief Fill imMemReq and memReq
* @details
* Fill the structs and call vkGetImageMemoryRequirements2()
*/
void getImageMemoryRequirements(const VkDevice& device, const VkImage& image, VkImageMemoryRequirementsInfo2& imMemReq, VkMemoryRequirements2& memReq);
/// @} /// @}
/** /**

View File

@ -4,28 +4,21 @@
#include "vulkan_instance.hpp" #include "vulkan_instance.hpp"
#include <glm/vector_relational.hpp> #include <glm/vector_relational.hpp>
#include <gz-util/util/string_conversion.hpp>
#include <vulkan/vulkan_core.h> #include <vulkan/vulkan_core.h>
namespace gz::vk { namespace gz::vk {
struct MemoryBlock { std::string MemoryBlock::toString() const {
size_t size; return std::string("<offset: " + gz::toHexString(offset, 0) + ", size: " + gz::toHexString(size, 0) + ", free: " + gz::toString(free) + ">");
size_t offset; }
bool free;
};
struct DeviceMemory {
DeviceMemory() = delete; DeviceMemory::DeviceMemory(VkDeviceSize size_) {
DeviceMemory(VkDeviceSize size_) {
size = size_; size = size_;
memory = VK_NULL_HANDLE; memory = VK_NULL_HANDLE;
blocks.emplace_back(MemoryBlock{size, 0, true}); blocks.emplace_back(MemoryBlock{size, 0, true});
}; };
VkDeviceSize size;
VkDeviceMemory memory;
std::list<MemoryBlock> blocks;
};
VulkanAllocator::VulkanAllocator(VulkanInstance& instance) VulkanAllocator::VulkanAllocator(VulkanInstance& instance)
: vk(instance) : vk(instance)
@ -40,30 +33,46 @@ namespace gz::vk {
} }
void VulkanAllocator::allocate(const VkMemoryAllocateInfo& allocI, MemoryInfo& memoryInfo) { void VulkanAllocator::allocate(const VkMemoryAllocateInfo& allocI, const VkMemoryRequirements2& memReq, MemoryInfo& memoryInfo) {
aLog.log0("allocate: Requesting memory with ( size", toHexString(allocI.allocationSize),
"), ( memoryTypeIndex", allocI.memoryTypeIndex,
"), ( alignment", toHexString(memReq.memoryRequirements.alignment), ")");
// go through allocated memories with matching memoryType // go through allocated memories with matching memoryType
for (auto& deviceMemory : memory[allocI.memoryTypeIndex]) { for (auto deviceMemory = memory[allocI.memoryTypeIndex].begin(); deviceMemory != memory[allocI.memoryTypeIndex].end(); deviceMemory++) {
/* auto bestBlockIt = deviceMemory.blocks.end(); */ /* auto bestBlockIt = deviceMemory.blocks.end(); */
// go through blocks in memory // go through blocks in memory
for (auto block = deviceMemory.blocks.begin(); block != deviceMemory.blocks.end(); block++) { for (auto block = deviceMemory->blocks.begin(); block != deviceMemory->blocks.end(); block++) {
if (!block->free) { continue; } if (!block->free) { continue; }
if (block->size >= allocI.allocationSize) { if (block->size >= allocI.allocationSize) {
aLog.log0("allocate: memoryTypeIndex:", allocI.memoryTypeIndex, // check if alignment is ok
"size:", allocI.allocationSize, "block size:", block->size, "block offset:", block->offset); if (block->offset % memReq.memoryRequirements.alignment != 0) {
// move non-aligned space to previous block
// new offset === offset + (align - offset % align)
size_t moveToPreviousBlock = memReq.memoryRequirements.alignment - block->offset % memReq.memoryRequirements.alignment;
// check if still large enough
if (block->size - moveToPreviousBlock < allocI.allocationSize) { continue; }
block--;
block->size += moveToPreviousBlock;
block++;
block->offset += moveToPreviousBlock;
block->size -= moveToPreviousBlock;
}
// if the block is larger than the needed size, split the block // if the block is larger than the needed size, split the block
if (block->size > allocI.allocationSize) { if (block->size > allocI.allocationSize) {
// emplace free part of block after used part of block // emplace free part of block after used part of block
auto newBlock = ++block; auto newBlock = ++block;
block--; block--;
deviceMemory.blocks.emplace(newBlock, deviceMemory->blocks.emplace(newBlock,
MemoryBlock{ block->size - allocI.allocationSize, block->offset + allocI.allocationSize, true }); MemoryBlock{ block->size - allocI.allocationSize, block->offset + allocI.allocationSize, true });
block--; /* block--; */
block->size = allocI.allocationSize; block->size = allocI.allocationSize;
} }
block->free = false; block->free = false;
memoryInfo.memory = deviceMemory.memory; memoryInfo.memory = deviceMemory->memory;
memoryInfo.offset = block->offset; memoryInfo.offset = block->offset;
memoryInfo.memoryTypeIndex = allocI.memoryTypeIndex; memoryInfo.memoryTypeIndex = allocI.memoryTypeIndex;
aLog.log0("allocate: (memory", memoryInfo.memoryTypeIndex, "-", gz::toString(deviceMemory - memory[memoryInfo.memoryTypeIndex].begin()) + ") Blocks:", deviceMemory->blocks);
return; return;
} }
} }
@ -75,7 +84,7 @@ namespace gz::vk {
allocI_.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; allocI_.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocI_.memoryTypeIndex = allocI.memoryTypeIndex; allocI_.memoryTypeIndex = allocI.memoryTypeIndex;
allocI_.allocationSize = memory[allocI.memoryTypeIndex].back().size; allocI_.allocationSize = memory[allocI.memoryTypeIndex].back().size;
aLog.log0("allocate: Allocating new memory of size:", allocI_.allocationSize, " and memoryTypeIndex:", allocI.memoryTypeIndex); aLog.log0("allocate: Allocating new memory of size:", gz::toHexString(allocI_.allocationSize, 0), "(memory", memoryInfo.memoryTypeIndex, "-", gz::toString(memory[memoryInfo.memoryTypeIndex].size()) + ")");
VkResult result = vkAllocateMemory(vk.getDevice(), &allocI_, NO_ALLOC, &memory[allocI.memoryTypeIndex].back().memory); VkResult result = vkAllocateMemory(vk.getDevice(), &allocI_, NO_ALLOC, &memory[allocI.memoryTypeIndex].back().memory);
if (result != VK_SUCCESS) { if (result != VK_SUCCESS) {
throw getVkException(result, "Failed to allocate memory", "VulkanAllocator::allocate"); throw getVkException(result, "Failed to allocate memory", "VulkanAllocator::allocate");
@ -87,9 +96,11 @@ namespace gz::vk {
deviceMemory.blocks.front().size -= allocI.allocationSize; deviceMemory.blocks.front().size -= allocI.allocationSize;
deviceMemory.blocks.emplace_front(MemoryBlock{ allocI.allocationSize, 0, false }); deviceMemory.blocks.emplace_front(MemoryBlock{ allocI.allocationSize, 0, false });
// alignment always satisfied with vkAllocateMemory() and offset 0
memoryInfo.memory = deviceMemory.memory; memoryInfo.memory = deviceMemory.memory;
memoryInfo.offset = 0; memoryInfo.offset = 0;
memoryInfo.memoryTypeIndex = allocI.memoryTypeIndex; memoryInfo.memoryTypeIndex = allocI.memoryTypeIndex;
aLog.log0("allocate: (memory", memoryInfo.memoryTypeIndex, "-", gz::toString(memory[memoryInfo.memoryTypeIndex].size()) + ") Blocks:", deviceMemory.blocks);
} }
@ -100,8 +111,9 @@ namespace gz::vk {
// go through blocks in memory // go through blocks in memory
for (auto block = deviceMemory->blocks.begin(); block != deviceMemory->blocks.end(); block++) { for (auto block = deviceMemory->blocks.begin(); block != deviceMemory->blocks.end(); block++) {
if (block->offset != memoryInfo.offset) { continue; } if (block->offset != memoryInfo.offset) { continue; }
aLog.log0("free: Freeing block at offset", memoryInfo.offset); aLog.log0("free: (memory", memoryInfo.memoryTypeIndex, "-", gz::toString(deviceMemory - memory[memoryInfo.memoryTypeIndex].begin()) + ") Freeing block at offset:", gz::toHexString(memoryInfo.offset, 0));
block->free = true; block->free = true;
if (block != deviceMemory->blocks.begin()) {
// merge with previous block, if free // merge with previous block, if free
auto otherBlock = block; auto otherBlock = block;
otherBlock--; otherBlock--;
@ -110,19 +122,24 @@ namespace gz::vk {
deviceMemory->blocks.erase(block); deviceMemory->blocks.erase(block);
block = otherBlock; block = otherBlock;
} }
}
if (block != --(deviceMemory->blocks.end())) {
// merge with next block, if free // merge with next block, if free
otherBlock = block; auto otherBlock = block;
otherBlock++; otherBlock++;
if (otherBlock->free) { if (otherBlock->free) {
block->size += otherBlock->size; block->size += otherBlock->size;
deviceMemory->blocks.erase(otherBlock); deviceMemory->blocks.erase(otherBlock);
} }
}
memoryInfo.memory = VK_NULL_HANDLE; memoryInfo.memory = VK_NULL_HANDLE;
memoryInfo.offset = 0; memoryInfo.offset = 0;
aLog.log0("free: (memory", memoryInfo.memoryTypeIndex, "-", gz::toString(deviceMemory - memory[memoryInfo.memoryTypeIndex].begin()) + ") Blocks:", deviceMemory->blocks);
// if now there is only one free block, everything is free -> deallocate memory // if now there is only one free block, everything is free -> deallocate memory
if (deviceMemory->blocks.size() == 1) { if (deviceMemory->blocks.size() == 1) {
aLog.log0("free: Deallocting memory of size:", deviceMemory->size, "and memoryTypeIndex:", memoryInfo.memoryTypeIndex); aLog.log0("free: Deallocating (memory", memoryInfo.memoryTypeIndex, "-", gz::toString(deviceMemory - memory[memoryInfo.memoryTypeIndex].begin()) + ") of size:", gz::toHexString(deviceMemory->size, 0));
vkFreeMemory(vk.getDevice(), deviceMemory->memory, NO_ALLOC); vkFreeMemory(vk.getDevice(), deviceMemory->memory, NO_ALLOC);
memory[memoryInfo.memoryTypeIndex].erase(deviceMemory); memory[memoryInfo.memoryTypeIndex].erase(deviceMemory);
} }
@ -133,4 +150,21 @@ namespace gz::vk {
} }
void VulkanAllocator::cleanup() {
aLog.log0("cleanup: cleaning up");
for (auto memType = memory.begin(); memType != memory.end(); memType++) {
for (auto deviceMemory = memType->second.begin(); deviceMemory != memType->second.end(); deviceMemory++) {
// check if all blocks are free
for (auto block = deviceMemory->blocks.begin(); block != deviceMemory->blocks.end(); block++) {
if (block->free) { continue; }
aLog.warning("cleanup: (memory", memType->first, "-", gz::toString(deviceMemory - memType->second.begin()) + ") Block not freed: ", *block);
}
aLog.log0("free: Deallocating (memory", memType->first, "-", gz::toString(deviceMemory - memType->second.begin()) + ") of size:", gz::toHexString(deviceMemory->size, 0));
vkFreeMemory(vk.getDevice(), deviceMemory->memory, NO_ALLOC);
}
}
memory.clear();
}
} // namespace gz::vk } // namespace gz::vk

View File

@ -13,20 +13,99 @@
/* #include <vulkan/vulkan_core.h> */ /* #include <vulkan/vulkan_core.h> */
namespace gz::vk { namespace gz::vk {
/**
* @brief Contains information about a subsection (block) of a VkDeviceMemory
*/
struct MemoryInfo { struct MemoryInfo {
/**
* @brief Handle of the memory
*/
VkDeviceMemory memory = VK_NULL_HANDLE; VkDeviceMemory memory = VK_NULL_HANDLE;
/**
* @brief Offset into memory
*/
VkDeviceSize offset = 0; VkDeviceSize offset = 0;
/**
* @brief The memoryTypeIndex memory was allocated from. Needed for VulkanAllocator::free()
*/
uint32_t memoryTypeIndex = 0; uint32_t memoryTypeIndex = 0;
}; };
/**
* @brief Information on a single block of memory
*/
struct MemoryBlock {
size_t size;
size_t offset;
bool free;
std::string toString() const;
};
/**
* @brief Manage a single VkDeviceMemory chunk
*/
struct DeviceMemory {
DeviceMemory() = delete;
DeviceMemory(VkDeviceSize size_);
VkDeviceSize size;
VkDeviceMemory memory;
std::list<MemoryBlock> blocks;
};
// if no memory is available, allocate a chunk of multiplier * requested size // if no memory is available, allocate a chunk of multiplier * requested size
constexpr VkDeviceSize TODO_ALLOCATION_SIZE_MULTIPLIIER = 10; constexpr VkDeviceSize TODO_ALLOCATION_SIZE_MULTIPLIIER = 10;
// defined in vulkan_instance.hpp // defined in vulkan_instance.hpp
class VulkanInstance; class VulkanInstance;
// defined in vulkan_allocator.cpp
struct DeviceMemory;
/**
* @brief Allocator for device local memory
* @details
* Allocates larger chunks of VkDeviceMemory from which blocks can be @ref allocate() "allocated".
*
* This class is for device local memory only, not for host memory.
* You can not use it for vulkan memory allocation callbacks (pAllocator).
*
* @subsection usage How to use
* The usage is explained with the example of creating and destroying a single buffer.
* @code
* // Create buffer handle
* VkBuffer buffer;
* // Create empty memory info struct
* MemoryInfo bufferMI;
*
* VkBufferCreateInfo bufferCI{}
* ...
* vkCreateBuffer(...);
*
* // get memory requirements
* VkMemoryRequirements2 memReq;
* VkBufferMemoryRequirementsInfo2 bufMemReq;
* // from vulkan_util.hpp, same exists for imageMemoryRequirements
* getBufferMemoryRequirements(device, buffer, bufMemReq, memReq);
*
* // set allocation info
* VkMemoryAllocateInfo memAI{};
* ...
* memAI.memoryTypeIndex = VulkanInstance::findMemoryType(memReq.memoryRequirements.memoryTypeBits, wantedProperties);
*
* // allocate memory for the buffer
* VulkanAllocator::allocate(memAI, memReq, bufferMI);
*
* // bind the buffer to the memory
* vkBindBufferMemory(device, buffer, bufferMI.memory, bufferMI.offset);
*
* ...
*
* vkDestroyBuffer(device, buffer);
* VulkanAllocator::free(bufferMemoryInfo);
* @endcode
*
* You will of course have to use a VulkanAllocator and VulkanInstance object, since the used methods are not static.
*/
class VulkanAllocator { class VulkanAllocator {
public: public:
VulkanAllocator(VulkanInstance& instance); VulkanAllocator(VulkanInstance& instance);
@ -39,8 +118,10 @@ namespace gz::vk {
* You can then bind something of size allocI.allocationSize to memoryInfo.memory at offset memoryInfo.offset. * You can then bind something of size allocI.allocationSize to memoryInfo.memory at offset memoryInfo.offset.
* @throws VkException when a call to vkAllocateMemory is needed and fails * @throws VkException when a call to vkAllocateMemory is needed and fails
* @todo Determine the size of new allocations * @todo Determine the size of new allocations
* @todo alignment, maybe use VkMemoryRequirements instead of VkMemoryAllocateInfo?
* @todo maybe increase the block size of the allocated block so that the following blocks is already 16-aligned?
*/ */
void allocate(const VkMemoryAllocateInfo& allocI, MemoryInfo& memoryInfo); void allocate(const VkMemoryAllocateInfo& allocI, const VkMemoryRequirements2& memReq, MemoryInfo& memoryInfo);
/** /**
* @brief Free a block allocated with allocate() * @brief Free a block allocated with allocate()
* *
@ -48,10 +129,21 @@ namespace gz::vk {
* @throws VkUserError if memoryInfo was not allocated from this allocator * @throws VkUserError if memoryInfo was not allocated from this allocator
*/ */
void free(MemoryInfo& memoryInfo); void free(MemoryInfo& memoryInfo);
/**
* @brief Deallocate all memory
* @details
* Prints warnings if blocks have not been freed.
*
* @note This function has to be called manually (by VulkanInstance). This class does not register a cleanupCallback with the instance!
*
*/
void cleanup();
private: private:
/// allocated memory for memoryIndexType /// Allocated memory for memoryIndexType
std::map<uint32_t, std::vector<DeviceMemory>> memory; std::map<uint32_t, std::vector<DeviceMemory>> memory;
Log aLog; Log aLog;
/// Needed for access to logical device
VulkanInstance& vk; VulkanInstance& vk;
}; // class VulkanAllocator }; // class VulkanAllocator

View File

@ -5,6 +5,7 @@
#include "vk_enum_string.h" #include "vk_enum_string.h"
#include "vulkan_allocator.hpp" #include "vulkan_allocator.hpp"
#include "vulkan_util.hpp"
#include <bits/types/cookie_io_functions_t.h> #include <bits/types/cookie_io_functions_t.h>
#include <chrono> #include <chrono>
#include <glm/vector_relational.hpp> #include <glm/vector_relational.hpp>
@ -248,6 +249,8 @@ using std::uint32_t;
vkDestroyCommandPool(device, commandPoolGraphics, nullptr); vkDestroyCommandPool(device, commandPoolGraphics, nullptr);
vkDestroyCommandPool(device, commandPoolTransfer, nullptr); vkDestroyCommandPool(device, commandPoolTransfer, nullptr);
allocator.cleanup();
vkDestroyDevice(device, nullptr); vkDestroyDevice(device, nullptr);
vkDestroySurfaceKHR(instance, surface, nullptr); vkDestroySurfaceKHR(instance, surface, nullptr);
cleanupDebugMessenger(); cleanupDebugMessenger();
@ -465,6 +468,43 @@ using std::uint32_t;
} }
void VulkanInstance::loadModel(VerticesAndIndices<uint32_t>& model) {
tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> materials;
std::string warnings, errors;
if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warnings, &errors, MODEL_PATH.c_str())) {
vLog.warning("Warning from tinyobj::LoadObj: ", warnings);
throw gz::Exception("Error loading obj: " + errors, "loadModel");
}
if (!warnings.empty()) {
vLog.warning("Warning from tinyobj::LoadObj: ", warnings);
}
std::unordered_map<Vertex3D, uint32_t> uniqueVertices;
for (const auto& shape : shapes) {
for (const auto& index : shape.mesh.indices) {
Vertex3D vertex;
vertex.pos = {
attrib.vertices[3 * index.vertex_index + 0],
attrib.vertices[3 * index.vertex_index + 1],
attrib.vertices[3 * index.vertex_index + 2]
};
vertex.texCoord = {
attrib.vertices[2 * index.texcoord_index + 0],
// obj: y = 0 means bottom, vulkan: y = 0 means top
1.0f - attrib.vertices[2 * index.texcoord_index + 1],
};
vertex.color = { 1.0f, 1.0f, 1.0f };
if (!uniqueVertices.contains(vertex)) {
uniqueVertices.insert({ vertex, model.vertices.size() });
model.vertices.push_back(vertex);
}
/* model.vertices.push_back(vertex); */
model.indices.push_back(uniqueVertices[vertex]);
}
}
}
// TODO // TODO
// DEPTH // DEPTH
/* VkFormat VulkanInstance::findDepthFormat() { */ /* VkFormat VulkanInstance::findDepthFormat() { */
@ -702,7 +742,7 @@ using std::uint32_t;
// BUFFER // BUFFER
void VulkanInstance::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, MemoryInfo& memI, VkSharingMode sharingMode, std::vector<uint32_t>* qFamiliesWithAccess) { void VulkanInstance::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, MemoryInfo& bufferMI, VkSharingMode sharingMode, std::vector<uint32_t>* qFamiliesWithAccess) {
VkBufferCreateInfo bufferCI{}; VkBufferCreateInfo bufferCI{};
bufferCI.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferCI.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferCI.size = size; bufferCI.size = size;
@ -722,18 +762,23 @@ using std::uint32_t;
throw getVkException(result, "Failed to create buffer", "createBuffer"); throw getVkException(result, "Failed to create buffer", "createBuffer");
} }
VkMemoryRequirements memoryRequirements{}; VkMemoryRequirements2 memReq;
vkGetBufferMemoryRequirements(device, buffer, &memoryRequirements); VkBufferMemoryRequirementsInfo2 bufMemReq;
getBufferMemoryRequirements(device, buffer, bufMemReq, memReq);
VkMemoryAllocateInfo memoryAI{}; VkMemoryAllocateInfo memoryAI{};
memoryAI.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; memoryAI.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
memoryAI.allocationSize = memoryRequirements.size; memoryAI.allocationSize = memReq.memoryRequirements.size;
VkMemoryPropertyFlags wantedProperties = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; VkMemoryPropertyFlags wantedProperties = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
memoryAI.memoryTypeIndex = findMemoryType(memoryRequirements.memoryTypeBits, wantedProperties); memoryAI.memoryTypeIndex = findMemoryType(memReq.memoryRequirements.memoryTypeBits, wantedProperties);
allocator.allocate(memoryAI, memI); allocator.allocate(memoryAI, memReq, bufferMI);
vkBindBufferMemory(device, buffer, memI.memory, memI.offset); result = vkBindBufferMemory(device, buffer, bufferMI.memory, bufferMI.offset);
if (result != VK_SUCCESS) {
throw getVkException(result, "Failed to bind buffer to its memory", "createBuffer");
}
vLog.log0("createBuffer: created and bound buffer of size", memoryAI.allocationSize);
} }
@ -843,8 +888,15 @@ using std::uint32_t;
} }
} }
void VulkanInstance::destroyTextureSampler(VkSampler& sampler) {
vkDestroySampler(device, sampler, NO_ALLOC);
sampler = VK_NULL_HANDLE;
}
// PUBLIC INTERFACE: IMAGE UTILITY // PUBLIC INTERFACE: IMAGE UTILITY
void VulkanInstance::createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags memoryProperties, VkImage& image, VkDeviceMemory& imageMemory) { void VulkanInstance::createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags memoryProperties, VkImage& image, MemoryInfo& imageMI) {
VkImageCreateInfo imageCI{}; VkImageCreateInfo imageCI{};
imageCI.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageCI.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageCI.imageType = VK_IMAGE_TYPE_2D; imageCI.imageType = VK_IMAGE_TYPE_2D;
@ -866,22 +918,29 @@ using std::uint32_t;
throw getVkException(result, "Failed to create image", "createImage"); throw getVkException(result, "Failed to create image", "createImage");
} }
VkMemoryRequirements memoryRequirements; VkMemoryRequirements2 memReq;
vkGetImageMemoryRequirements(device, image, &memoryRequirements); VkImageMemoryRequirementsInfo2 imMemReq;
getImageMemoryRequirements(device, image, imMemReq, memReq);
VkMemoryAllocateInfo memoryAI{}; VkMemoryAllocateInfo memoryAI{};
memoryAI.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; memoryAI.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
memoryAI.allocationSize = memoryRequirements.size; memoryAI.allocationSize = memReq.memoryRequirements.size;
memoryAI.memoryTypeIndex = findMemoryType(memoryRequirements.memoryTypeBits, memoryProperties); memoryAI.memoryTypeIndex = findMemoryType(memReq.memoryRequirements.memoryTypeBits, memoryProperties);
MemoryInfo memI; allocator.allocate(memoryAI, memReq, imageMI);
allocator.allocate(memoryAI, memI);
/* result = vkAllocateMemory(device, &memoryAI, NO_ALLOC, &imageMemory); */
result = vkBindImageMemory(device, image, memI.memory, memI.offset); result = vkBindImageMemory(device, image, imageMI.memory, imageMI.offset);
if (result != VK_SUCCESS) { if (result != VK_SUCCESS) {
throw getVkException(result, "Failed to create image", "createImage"); throw getVkException(result, "Failed to bind image to its memory", "createImage");
} }
vLog.log0("createImage: created and bound image of size", memoryAI.allocationSize);
}
void VulkanInstance::destroyImage(VkImage& image, MemoryInfo& imageMemory) {
vkDestroyImage(device, image, NO_ALLOC);
allocator.free(imageMemory);
image = VK_NULL_HANDLE;
} }
@ -908,6 +967,12 @@ using std::uint32_t;
} }
void VulkanInstance::destroyImageView(VkImageView& imageView) {
vkDestroyImageView(device, imageView, NO_ALLOC);
imageView = VK_NULL_HANDLE;
}
void VulkanInstance::copyBufferToImage(VkBuffer buffer, VkImage image, int32_t offsetX, int32_t offsetY, uint32_t width, uint32_t height) { void VulkanInstance::copyBufferToImage(VkBuffer buffer, VkImage image, int32_t offsetX, int32_t offsetY, uint32_t width, uint32_t height) {
VkCommandBuffer cmdBuffer = beginSingleTimeCommands(POOL_TRANSFER); VkCommandBuffer cmdBuffer = beginSingleTimeCommands(POOL_TRANSFER);
VkBufferImageCopy region{}; VkBufferImageCopy region{};
@ -1724,45 +1789,6 @@ using std::uint32_t;
} }
// MODEL
void VulkanInstance::loadModel(VerticesAndIndices<uint32_t>& model) {
tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> materials;
std::string warnings, errors;
if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warnings, &errors, MODEL_PATH.c_str())) {
vLog.warning("Warning from tinyobj::LoadObj: ", warnings);
throw gz::Exception("Error loading obj: " + errors, "loadModel");
}
if (!warnings.empty()) {
vLog.warning("Warning from tinyobj::LoadObj: ", warnings);
}
std::unordered_map<Vertex3D, uint32_t> uniqueVertices;
for (const auto& shape : shapes) {
for (const auto& index : shape.mesh.indices) {
Vertex3D vertex;
vertex.pos = {
attrib.vertices[3 * index.vertex_index + 0],
attrib.vertices[3 * index.vertex_index + 1],
attrib.vertices[3 * index.vertex_index + 2]
};
vertex.texCoord = {
attrib.vertices[2 * index.texcoord_index + 0],
// obj: y = 0 means bottom, vulkan: y = 0 means top
1.0f - attrib.vertices[2 * index.texcoord_index + 1],
};
vertex.color = { 1.0f, 1.0f, 1.0f };
if (!uniqueVertices.contains(vertex)) {
uniqueVertices.insert({ vertex, model.vertices.size() });
model.vertices.push_back(vertex);
}
/* model.vertices.push_back(vertex); */
model.indices.push_back(uniqueVertices[vertex]);
}
}
}
// DEBUG // DEBUG
void VulkanInstance::setupDebugMessenger() { void VulkanInstance::setupDebugMessenger() {
// get the address of the vkCreateDebugUtilsMessengerEXT function // get the address of the vkCreateDebugUtilsMessengerEXT function

View File

@ -97,8 +97,25 @@ namespace gz::vk {
*/ */
uint32_t beginFrameDraw(); uint32_t beginFrameDraw();
void endFrameDraw(uint32_t imageIndex); void endFrameDraw(uint32_t imageIndex);
/**
* @brief Cleanup the instance
* @details
* -# wait for the device to idle
* -# call @ref registerCleanupCallback() "cleanup callbacks" in reverse order to the registration
* -# destroy command buffers
* -# call cleanupSwapChain()
* -# destroy @ref createSyncObjects() "synchronization objects"
* -# destroy @ref createCommandPools() "command pools"
* -# call VulkanAllocator::cleanup()
* -# destroy device
* -# destroy surface
* -# call cleanupDebugMessenger()
* -# destroy instance
* -# call glfwDestroyWindow()
* -# call glfwTerminate()
*/
void cleanup(); void cleanup();
const GLFWwindow* getWindow() const { return window; } GLFWwindow* getWindow() { return window; }
/// @} /// @}
/** /**
@ -142,21 +159,22 @@ namespace gz::vk {
public: public:
/// @{ /// @{
const VkDevice& getDevice() const { return device; } const VkDevice& getDevice() const { return device; }
const SettingsManager<SettingsTypes>& getSettings() const { return settings; }
const VkExtent2D& getScExtent() const { return scExtent; } const VkExtent2D& getScExtent() const { return scExtent; }
const std::vector<VkImage>& getScImages() const { return scImages; } const std::vector<VkImage>& getScImages() const { return scImages; }
const VkFormat& getScImageFormat() const { return scImageFormat; } const VkFormat& getScImageFormat() const { return scImageFormat; }
SettingsManager<SettingsTypes>& getSettings() { return settings; }
/** /**
* @brief Get the maximum number of frames in flight * @brief Get the maximum number of frames in flight
* @see MAX_FRAMES_IN_FLIGHT * @see MAX_FRAMES_IN_FLIGHT
*/ */
const uint32_t getMaxFramesInFlight() const { return MAX_FRAMES_IN_FLIGHT; } uint32_t getMaxFramesInFlight() const { return MAX_FRAMES_IN_FLIGHT; }
/** /**
* @brief Get the frame that is currently in flight * @brief Get the frame that is currently in flight
* @see currentFrame * @see currentFrame
*/ */
const uint32_t getCurrentFrame() const { return currentFrame; } uint32_t getCurrentFrame() const { return currentFrame; }
void submitThisFrame(VkCommandBuffer& cmdBuffer); void submitThisFrame(VkCommandBuffer& cmdBuffer);
@ -178,6 +196,8 @@ namespace gz::vk {
* @param commandPool: The same pool as passed to beginSingleTimeCommands() * @param commandPool: The same pool as passed to beginSingleTimeCommands()
*/ */
void endSingleTimeCommands(VkCommandBuffer cmdBuffer, InstanceCommandPool commandPool); void endSingleTimeCommands(VkCommandBuffer cmdBuffer, InstanceCommandPool commandPool);
void loadModel(VerticesAndIndices<uint32_t>& model);
/// @} /// @}
/** /**
@ -234,6 +254,9 @@ namespace gz::vk {
* @param buffer The (null) handle to the buffer * @param buffer The (null) handle to the buffer
* @param bufferMemory Reference to an (uninitialized) MemoryInfo struct. It will be valid when the function returns * @param bufferMemory Reference to an (uninitialized) MemoryInfo struct. It will be valid when the function returns
* @param properties Properties that the buffers memory needs to satisfy * @param properties Properties that the buffers memory needs to satisfy
* @details
* The memory the buffer will be bound to is HOST_VISIBLE and HOST_COHERENT
*
*/ */
void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, MemoryInfo& bufferMemory, VkSharingMode sharingMode=VK_SHARING_MODE_EXCLUSIVE, std::vector<uint32_t>* qFamiliesWithAccess=nullptr); void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, MemoryInfo& bufferMemory, VkSharingMode sharingMode=VK_SHARING_MODE_EXCLUSIVE, std::vector<uint32_t>* qFamiliesWithAccess=nullptr);
/** /**
@ -284,6 +307,8 @@ namespace gz::vk {
*/ */
void createTextureSampler(VkSampler& sampler); void createTextureSampler(VkSampler& sampler);
void destroyTextureSampler(VkSampler& sampler);
/** /**
* @name Public Interface: Image utility * @name Public Interface: Image utility
* @details * @details
@ -292,13 +317,16 @@ namespace gz::vk {
*/ */
/// @{ /// @{
/** /**
* @brief Create the image, allocate the imageMemory and bind the image to it * @brief Create a 2D 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 createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags memoryProperties, VkImage& image, MemoryInfo& imageMI);
void destroyImage(VkImage& image, MemoryInfo& imageMemory);
/** /**
* @brief Create a 2D imageView with format for image. * @brief Create a 2D imageView with format for image.
*/ */
void createImageView(VkFormat format, VkImage& image, VkImageView& imageView, VkImageAspectFlags aspectFlags); void createImageView(VkFormat format, VkImage& image, VkImageView& imageView, VkImageAspectFlags aspectFlags);
void destroyImageView(VkImageView& imageView);
void copyBufferToImage(VkBuffer buffer, VkImage image, int32_t offsetX, int32_t offsetY, uint32_t width, uint32_t height); 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 * @todo make a version using vkCmdResolveImage for multisampled images
@ -601,14 +629,6 @@ namespace gz::vk {
static std::vector<char> readFile(const std::string& filename); static std::vector<char> readFile(const std::string& filename);
/// @} /// @}
/**
* @name Models
*/
/// @{
void loadModel(VerticesAndIndices<uint32_t>& model);
/// @}
/** /**
* @name Utility * @name Utility
*/ */

View File

@ -1,4 +1,5 @@
# Written by writeKeyValueFile # Written by writeKeyValueFile
max_frames_in_flight = 3
max_anisotropy = 1 max_anisotropy = 1
anisotropy_enable = false anisotropy_enable = false
framerate = 60 framerate = 60