diff --git a/src/buffer_manager.cpp b/src/buffer_manager.cpp new file mode 100644 index 0000000..b99c580 --- /dev/null +++ b/src/buffer_manager.cpp @@ -0,0 +1,152 @@ +#include "buffer_manager.hpp" + +#include "exceptions.hpp" +#include "vulkan_allocator.hpp" +#include "vulkan_instance.hpp" +#include + +namespace gz::vlk { + std::string BufferInfo::toString() const { + return ""; + } + + template class BufferManager; + template class BufferManager; + template class BufferManager; + template class BufferManager; + template + BufferManager::BufferManager(VulkanInstance& instance) + : vk(instance) + { + bLog = vk.createSublog(true, "BufferManager", gz::Color::BO_CYAN); + vk.registerCleanupCallback(std::bind(&BufferManager::cleanup, this)); + } + + + + template + vk::Buffer BufferManager::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 + vk::Buffer BufferManager::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& 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 + void allocateNewBuffer(std::vector& 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) { + vk.createVertexBuffer(count, buffers.back().buffer, buffers.back().bufferMemory, buffers.back().bufferSize); + } + else if constexpr (SupportedIndexType) { + vk.createIndexBuffer(count, buffers.back().buffer, buffers.back().bufferMemory, buffers.back().bufferSize); + } + else { + static_assert(VertexType or SupportedIndexType, "T is not vertex or index type"); + } + }; + + + template + template VertexRange, util::ContiguousRange IndexRange> + std::pair BufferManager::addVertices(const VertexRange& vertices, const IndexRange& indices) { + std::pair 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(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(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 BufferManager::addVertices(const std::vector& vertices, const std::vector& indices); + + + template + void BufferManager::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 + void BufferManager::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 diff --git a/src/buffer_manager.hpp b/src/buffer_manager.hpp new file mode 100644 index 0000000..81d6128 --- /dev/null +++ b/src/buffer_manager.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include "vulkan_allocator.hpp" +#include "dynamic_size_block_allocator.hpp" +#include "vertex.hpp" + +#include + +#include + +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 + 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 VertexRange, util::ContiguousRange IndexRange> + [[ nodiscard ]] std::pair 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 vertexBuffers; + std::vector indexBuffers; + + VulkanInstance& vk; + Log bLog; + }; + +} // namespace gz::vlk diff --git a/src/shape.cpp b/src/shape.cpp index e7e629c..420c788 100644 --- a/src/shape.cpp +++ b/src/shape.cpp @@ -1,5 +1,6 @@ #include "shape.hpp" +#include "buffer_manager.hpp" #include "texture_manager.hpp" namespace gz::vlk { @@ -36,6 +37,13 @@ namespace gz::vlk { } } + + void Shape::initalizeBufferInfos(BufferManager& bufferManager) { + assert(vertexBufferInfo.index == BUFFER_NOT_INITIALIZED); + std::tie(vertexBufferInfo, indexBufferInfo) = bufferManager.addVertices(vertices, indices); + } + + void Rectangle::setTextureCoordinates(glm::vec2 topLeft, glm::vec2 bottomRight) { vertices[0].texCoord = topLeft; vertices[1].texCoord.x = bottomRight.x; @@ -51,7 +59,13 @@ namespace gz::vlk { textureManager.getTexCoords(texture, vertices[1].texCoord); textureManager.getTexCoords(texture, vertices[2].texCoord); textureManager.getTexCoords(texture, vertices[3].texCoord); + textureAtlasIndex = textureManager.getTextureAtlasIndex(texture); } + else { + textureAtlasIndex = 0; + + } + // must be after getTexCoords, since that might load the texture } diff --git a/src/shape.hpp b/src/shape.hpp index ab8bbb3..2a77221 100644 --- a/src/shape.hpp +++ b/src/shape.hpp @@ -1,18 +1,31 @@ #pragma once +#include "buffer_manager.hpp" #include "vertex.hpp" #include namespace gz::vlk { // defined in texture_manager.hpp 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 { public: const std::vector& getVertices() const { return vertices; } const std::vector& getIndices() const { return indices; } 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); /** @@ -21,12 +34,17 @@ namespace gz::vlk { void normalizeVertices(float width, float height); virtual void setTextureCoordinates(glm::vec2 topLeft, glm::vec2 bottomRight) {}; virtual void setTextureCoordinates(TextureManager& textureManager) {}; + bool buffersInitalized() const { return vertexBufferInfo.index != BUFFER_NOT_INITIALIZED; } + void initalizeBufferInfos(BufferManager& bufferManager); virtual ~Shape() {}; protected: std::string texture = "texture.png"; + uint32_t textureAtlasIndex; std::vector vertices; std::vector indices; + BufferInfo vertexBufferInfo; + BufferInfo indexBufferInfo; }; class Rectangle : public Shape {