added buffer manager for vertex and index buffers

This commit is contained in:
matthias@arch 2022-11-10 16:56:35 +01:00
parent 342395129c
commit 17033bf104
4 changed files with 258 additions and 1 deletions

152
src/buffer_manager.cpp Normal file
View File

@ -0,0 +1,152 @@
#include "buffer_manager.hpp"
#include "exceptions.hpp"
#include "vulkan_allocator.hpp"
#include "vulkan_instance.hpp"
#include <gz-util/string/to_string.hpp>
namespace gz::vlk {
std::string BufferInfo::toString() const {
return "<index: " + gz::toString(index) + ", offset: " + gz::toString(offset) + ", count:" + gz::toString(count) + ">";
}
template class BufferManager<Vertex2D, uint32_t>;
template class BufferManager<Vertex3D, uint32_t>;
template class BufferManager<Vertex2D, uint16_t>;
template class BufferManager<Vertex3D, uint16_t>;
template<VertexType VertexT, SupportedIndexType IndexT>
BufferManager<VertexT, IndexT>::BufferManager(VulkanInstance& instance)
: vk(instance)
{
bLog = vk.createSublog(true, "BufferManager", gz::Color::BO_CYAN);
vk.registerCleanupCallback(std::bind(&BufferManager::cleanup, this));
}
template<VertexType VertexT, SupportedIndexType IndexT>
vk::Buffer BufferManager<VertexT, IndexT>::getVertexBuffer(const BufferInfo& vertexBufferInfo)const {
if (vertexBuffers.size() <= vertexBufferInfo.index) {
throw VkUserError("Invalid buffer index: " + gz::toString(vertexBufferInfo.index), "BufferManager::getVertexBuffer");
}
return vertexBuffers.at(vertexBufferInfo.index).buffer;
}
template<VertexType VertexT, SupportedIndexType IndexT>
vk::Buffer BufferManager<VertexT, IndexT>::getIndexBuffer(const BufferInfo& indexBufferInfo)const {
if (indexBuffers.size() <= indexBufferInfo.index) {
throw VkUserError("Invalid buffer index: " + gz::toString(indexBufferInfo.index), "BufferManager::getIndexBuffer");
}
return indexBuffers.at(indexBufferInfo.index).buffer;
}
// helpers
bool searchForSpaceInBuffers(std::vector<Buffer>& buffers, uint32_t allocSize, BufferInfo& bufferInfo) {
// search for allocSize space in buffers, return true and set bufferinfo to found space if found
for (auto bufferIt = buffers.begin(); bufferIt != buffers.end(); bufferIt++) {
try {
bufferInfo.offset = bufferIt->allocator.allocate(allocSize, 1);
bufferInfo.index = bufferIt - buffers.begin();
return true;
}
catch (...) {}
}
return false;
};
template<typename T>
void allocateNewBuffer(std::vector<Buffer>& buffers, uint32_t count, VulkanInstance& vk) {
uint32_t bufferSize = sizeof(T) * count;
buffers.emplace_back(Buffer{.buffer = vk::Buffer(), .bufferMemory = MemoryInfo(), .bufferSize = bufferSize, .allocator = DynBlockAllocator(bufferSize)});
if constexpr (VertexType<T>) {
vk.createVertexBuffer<T>(count, buffers.back().buffer, buffers.back().bufferMemory, buffers.back().bufferSize);
}
else if constexpr (SupportedIndexType<T>) {
vk.createIndexBuffer<T>(count, buffers.back().buffer, buffers.back().bufferMemory, buffers.back().bufferSize);
}
else {
static_assert(VertexType<T> or SupportedIndexType<T>, "T is not vertex or index type");
}
};
template<VertexType VertexT, SupportedIndexType IndexT>
template<util::ContiguousRange<VertexT> VertexRange, util::ContiguousRange<IndexT> IndexRange>
std::pair<BufferInfo, BufferInfo> BufferManager<VertexT, IndexT>::addVertices(const VertexRange& vertices, const IndexRange& indices) {
std::pair<BufferInfo, BufferInfo> bufferInfos;
bufferInfos.first.count = vertices.size();
// search for space for vertex buffer
if (!searchForSpaceInBuffers(vertexBuffers, sizeof(VertexT) * vertices.size(), bufferInfos.first)) {
bLog.log0("Allocating new vertex buffer nr.", vertexBuffers.size());
// allocate new vertex buffer
allocateNewBuffer<VertexT>(vertexBuffers, std::max(vertices.size(), TODO_VERTICES_COUNT), vk);
bufferInfos.first.index = vertexBuffers.size() - 1;
bufferInfos.first.offset = vertexBuffers.back().allocator.allocate(sizeof(VertexT) * vertices.size(), 1);
}
// copy vertices to vertex buffer
Buffer& vertexBuffer = vertexBuffers.at(bufferInfos.first.index);
vk.copyToDeviceBuffer(vertices.data(), sizeof(VertexT) * vertices.size(), vertexBuffer.buffer, vertexBuffer.bufferMemory, bufferInfos.first.offset);
bufferInfos.second.count = indices.size();
// search for space for index buffer
if (!searchForSpaceInBuffers(indexBuffers, sizeof(IndexT) * indices.size(), bufferInfos.second)) {
bLog.log0("Allocating new index buffer nr.", indexBuffers.size());
// allocate new index buffer
allocateNewBuffer<IndexT>(indexBuffers, std::max(indices.size(), TODO_INDICES_COUNT), vk);
bufferInfos.second.index = indexBuffers.size() - 1;
bufferInfos.second.offset = indexBuffers.back().allocator.allocate(sizeof(IndexT) * indices.size(), 1);
}
// copy indices to index buffer
Buffer& indexBuffer = indexBuffers.at(bufferInfos.first.index);
vk.copyToDeviceBuffer(indices.data(), sizeof(IndexT) * indices.size(), indexBuffer.buffer, indexBuffer.bufferMemory, bufferInfos.second.offset);
bLog.log0("addVertices: VertexBufferInfo:", bufferInfos.first, "IndexBufferInfo", bufferInfos.second);
return bufferInfos;
}
template std::pair<BufferInfo, BufferInfo> BufferManager<Vertex2D, uint32_t>::addVertices(const std::vector<Vertex2D>& vertices, const std::vector<uint32_t>& indices);
template<VertexType VertexT, SupportedIndexType IndexT>
void BufferManager<VertexT, IndexT>::removeVertices(BufferInfo& vertexBufferInfo, BufferInfo& indexBufferInfo) {
if (vertexBuffers.size() <= vertexBufferInfo.index) {
throw VkUserError("Invalid vertex buffer index:" + gz::toString(vertexBufferInfo.index), "BufferManager::removeVertices");
}
Buffer& vertexBuffer = vertexBuffers.at(vertexBufferInfo.index);
vertexBuffer.allocator.free(vertexBufferInfo.offset);
vertexBufferInfo.index = BUFFER_NOT_INITIALIZED;
vertexBufferInfo.offset = BUFFER_NOT_INITIALIZED;
vertexBufferInfo.count = BUFFER_NOT_INITIALIZED;
if (indexBuffers.size() <= indexBufferInfo.index) {
throw VkUserError("Invalid index buffer index:" + gz::toString(indexBufferInfo.index), "BufferManager::removeVertices");
}
Buffer& indexBuffer = indexBuffers.at(indexBufferInfo.index);
indexBuffer.allocator.free(indexBufferInfo.offset);
indexBufferInfo.index = BUFFER_NOT_INITIALIZED;
indexBufferInfo.offset = BUFFER_NOT_INITIALIZED;
indexBufferInfo.count = BUFFER_NOT_INITIALIZED;
}
template<VertexType VertexT, SupportedIndexType IndexT>
void BufferManager<VertexT, IndexT>::cleanup() {
for (auto bufferIt = vertexBuffers.begin(); bufferIt != vertexBuffers.end(); bufferIt++) {
// check if all blocks are free
for (auto blockIt = bufferIt->allocator.begin(); blockIt != bufferIt->allocator.end(); blockIt++) {
if (blockIt->free) { continue; }
bLog.warning("cleanup: vertex buffer was not removed:", *blockIt, "index:", bufferIt - vertexBuffers.begin());
}
vk.destroyBuffer(bufferIt->buffer, bufferIt->bufferMemory);
}
vertexBuffers.clear();
for (auto bufferIt = indexBuffers.begin(); bufferIt != indexBuffers.end(); bufferIt++) {
// check if all blocks are free
for (auto blockIt = bufferIt->allocator.begin(); blockIt != bufferIt->allocator.end(); blockIt++) {
if (blockIt->free) { continue; }
bLog.warning("cleanup: index buffer was not removed:", *blockIt, "index:", bufferIt - indexBuffers.begin());
}
vk.destroyBuffer(bufferIt->buffer, bufferIt->bufferMemory);
}
indexBuffers.clear();
}
} // namespace gz::vlk

73
src/buffer_manager.hpp Normal file
View File

@ -0,0 +1,73 @@
#pragma once
#include "vulkan_allocator.hpp"
#include "dynamic_size_block_allocator.hpp"
#include "vertex.hpp"
#include <gz-util/concepts.hpp>
#include <cstdint>
namespace gz::vlk {
constexpr size_t TODO_VERTICES_COUNT = 100000;
constexpr size_t TODO_INDICES_COUNT = 100000;
constexpr uint32_t BUFFER_NOT_INITIALIZED = UINT32_MAX;
struct BufferInfo {
uint32_t index = BUFFER_NOT_INITIALIZED;
uint32_t offset = BUFFER_NOT_INITIALIZED;
uint32_t count = BUFFER_NOT_INITIALIZED;
std::string toString() const;
};
struct Buffer {
vk::Buffer buffer;
MemoryInfo bufferMemory;
vk::DeviceSize bufferSize;
DynBlockAllocator allocator;
};
// defined in vulkan_instance.hpp
class VulkanInstance;
/**
* @brief Manage multiple vertex and index buffers on device local memory
* @details
* Instantiated for Vertex2D, Vertex3D with uint16_t and uint32_t respectively.
* Manages buffers allocated from VulkanAllocator.
* The vertex and index buffers are then suballocated from those buffers (using DynBlockAllocator).
*/
template<VertexType VertexT, SupportedIndexType IndexT>
class BufferManager {
public:
BufferManager(VulkanInstance& instance);
vk::Buffer getVertexBuffer(const BufferInfo& vertexBufferInfo) const;
vk::Buffer getIndexBuffer(const BufferInfo& indexBufferInfo) const;
/**
* @brief Copy vertices and indices to a gpu-local buffer
* @details
* Instantiated for std::vectors
*/
template<util::ContiguousRange<VertexT> VertexRange, util::ContiguousRange<IndexT> IndexRange>
[[ nodiscard ]] std::pair<BufferInfo, BufferInfo> addVertices(const VertexRange& vertices, const IndexRange& indices);
/**
* @brief Free the vertex and index buffers and reset bufferInfos
* @todo Deallocate memory if unused
*/
void removeVertices(BufferInfo& vertexBufferInfo, BufferInfo& indexBufferInfo);
private:
/**
* @brief Deallocate all buffers
* @details
* Called by VulkanInstance::cleanup()
*/
void cleanup();
std::vector<Buffer> vertexBuffers;
std::vector<Buffer> indexBuffers;
VulkanInstance& vk;
Log bLog;
};
} // namespace gz::vlk

View File

@ -1,5 +1,6 @@
#include "shape.hpp" #include "shape.hpp"
#include "buffer_manager.hpp"
#include "texture_manager.hpp" #include "texture_manager.hpp"
namespace gz::vlk { namespace gz::vlk {
@ -36,6 +37,13 @@ namespace gz::vlk {
} }
} }
void Shape::initalizeBufferInfos(BufferManager<Vertex2D, uint32_t>& bufferManager) {
assert(vertexBufferInfo.index == BUFFER_NOT_INITIALIZED);
std::tie(vertexBufferInfo, indexBufferInfo) = bufferManager.addVertices(vertices, indices);
}
void Rectangle::setTextureCoordinates(glm::vec2 topLeft, glm::vec2 bottomRight) { void Rectangle::setTextureCoordinates(glm::vec2 topLeft, glm::vec2 bottomRight) {
vertices[0].texCoord = topLeft; vertices[0].texCoord = topLeft;
vertices[1].texCoord.x = bottomRight.x; vertices[1].texCoord.x = bottomRight.x;
@ -51,7 +59,13 @@ namespace gz::vlk {
textureManager.getTexCoords(texture, vertices[1].texCoord); textureManager.getTexCoords(texture, vertices[1].texCoord);
textureManager.getTexCoords(texture, vertices[2].texCoord); textureManager.getTexCoords(texture, vertices[2].texCoord);
textureManager.getTexCoords(texture, vertices[3].texCoord); textureManager.getTexCoords(texture, vertices[3].texCoord);
textureAtlasIndex = textureManager.getTextureAtlasIndex(texture);
} }
else {
textureAtlasIndex = 0;
}
// must be after getTexCoords, since that might load the texture
} }

View File

@ -1,18 +1,31 @@
#pragma once #pragma once
#include "buffer_manager.hpp"
#include "vertex.hpp" #include "vertex.hpp"
#include <cstdint> #include <cstdint>
namespace gz::vlk { namespace gz::vlk {
// defined in texture_manager.hpp // defined in texture_manager.hpp
class TextureManager; class TextureManager;
/**
* @brief Base class for shapes
* @details
* In this implementaiton, a shape is a 2D object made of several vertices.
* Each shape has its own vertex and index buffers, which are managed by a BufferManager.
* Their texture is managed by a TextureManager.
* @todo free resources and rule of 5
*/
class Shape { class Shape {
public: public:
const std::vector<Vertex2D>& getVertices() const { return vertices; } const std::vector<Vertex2D>& getVertices() const { return vertices; }
const std::vector<uint32_t>& getIndices() const { return indices; } const std::vector<uint32_t>& getIndices() const { return indices; }
const std::string& getTexture() const { return texture; } const std::string& getTexture() const { return texture; }
uint32_t getTexureAtlasIndex() const { return textureAtlasIndex; }
const BufferInfo& getVertexBufferInfo() const { return vertexBufferInfo; }
const BufferInfo& getIndexBufferInfo() const { return indexBufferInfo; }
/** /**
* @brief Add an offset to all indices * @brief Add an offset to all indices (useful when putting multiple shapes in the same vertex buffer)
*/ */
void setIndexOffset(uint32_t offset); void setIndexOffset(uint32_t offset);
/** /**
@ -21,12 +34,17 @@ namespace gz::vlk {
void normalizeVertices(float width, float height); void normalizeVertices(float width, float height);
virtual void setTextureCoordinates(glm::vec2 topLeft, glm::vec2 bottomRight) {}; virtual void setTextureCoordinates(glm::vec2 topLeft, glm::vec2 bottomRight) {};
virtual void setTextureCoordinates(TextureManager& textureManager) {}; virtual void setTextureCoordinates(TextureManager& textureManager) {};
bool buffersInitalized() const { return vertexBufferInfo.index != BUFFER_NOT_INITIALIZED; }
void initalizeBufferInfos(BufferManager<Vertex2D, uint32_t>& bufferManager);
virtual ~Shape() {}; virtual ~Shape() {};
protected: protected:
std::string texture = "texture.png"; std::string texture = "texture.png";
uint32_t textureAtlasIndex;
std::vector<Vertex2D> vertices; std::vector<Vertex2D> vertices;
std::vector<uint32_t> indices; std::vector<uint32_t> indices;
BufferInfo vertexBufferInfo;
BufferInfo indexBufferInfo;
}; };
class Rectangle : public Shape { class Rectangle : public Shape {