diff --git a/NothinFancy/CMakeLists.txt b/NothinFancy/CMakeLists.txt index a55624e..e31f233 100644 --- a/NothinFancy/CMakeLists.txt +++ b/NothinFancy/CMakeLists.txt @@ -1,5 +1,5 @@ # NF library CMakeLists.txt -add_library(NothinFancy STATIC "src/Engine.cpp" "src/include/nf.h" "src/pch.h" "src/util.h" "src/util/log.h" "src/util/log.cpp" "src/include/nf/config.h" "src/util/util.cpp" "src/util/file.h" "src/util/file.cpp" "src/client/Client.h" "src/client/Client.cpp" "src/client/Window.h" "src/client/Window.cpp" "src/client/render/RenderEngine.h" "src/client/render/RenderEngine.cpp" "src/client/render/ShaderModule.h" "src/client/render/ShaderModule.cpp" "src/client/render/VulkanResource.h" "src/client/render/Buffer.h" "src/client/render/Buffer.cpp") +add_library(NothinFancy STATIC "src/Engine.cpp" "src/include/nf.h" "src/pch.h" "src/util.h" "src/util/log.h" "src/util/log.cpp" "src/include/nf/config.h" "src/util/util.cpp" "src/util/file.h" "src/util/file.cpp" "src/client/Client.h" "src/client/Client.cpp" "src/client/Window.h" "src/client/Window.cpp" "src/client/render/RenderEngine.h" "src/client/render/RenderEngine.cpp" "src/client/render/ShaderModule.h" "src/client/render/ShaderModule.cpp" "src/client/render/Resource.h" "src/client/render/Buffer.h" "src/client/render/Buffer.cpp" "src/client/render/VideoMemoryAllocator.h" "src/client/render/VideoMemoryAllocator.cpp") # Use C++20 set_property(TARGET NothinFancy PROPERTY CXX_STANDARD 20) diff --git a/NothinFancy/src/client/Client.cpp b/NothinFancy/src/client/Client.cpp index d4739b9..734b421 100644 --- a/NothinFancy/src/client/Client.cpp +++ b/NothinFancy/src/client/Client.cpp @@ -18,8 +18,18 @@ namespace nf::client { std::thread inputThread(&Client::runInputThread, this, std::move(windowPromise)); m_renderEngine = std::make_unique(std::move(windowFuture.get()), m_config.display); + auto fpsClock1 = std::chrono::high_resolution_clock::now(), fpsClock2 = fpsClock1; + unsigned int frame = 0; while (m_running) { m_renderEngine->doFrame(); + frame++; + fpsClock2 = std::chrono::high_resolution_clock::now(); + std::chrono::duration duration = fpsClock2 - fpsClock1; + if (duration.count() >= 1.0) { + NFLog(std::format("FPS: {}", frame)); + frame = 0; + fpsClock1 = std::chrono::high_resolution_clock::now(); + } } inputThread.join(); diff --git a/NothinFancy/src/client/render/Buffer.cpp b/NothinFancy/src/client/render/Buffer.cpp index 10bac89..0728c36 100644 --- a/NothinFancy/src/client/render/Buffer.cpp +++ b/NothinFancy/src/client/render/Buffer.cpp @@ -5,18 +5,24 @@ #include "util.h" namespace nf::client::render { - Buffer::Buffer(BufferType type, const VkDevice& device, const VkPhysicalDevice& physicalDevice, const VkCommandPool& commandPool, const VkQueue& queue, void* bufferData, size_t bufferSize) - : VulkanResource(device) + Buffer::Buffer(BufferType type, const VkDevice& device, VideoMemoryAllocator& allocator, const VkCommandPool& commandPool, const VkQueue& queue, void* bufferData, size_t bufferSize) + : Resource(device) , m_handle() , m_memory() + , m_offset() + , m_allocator(allocator) { VkBuffer stagingBuffer = nullptr; - VkDeviceMemory stagingBufferMemory = nullptr; - createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, physicalDevice, stagingBuffer, stagingBufferMemory); + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, stagingBuffer); - void* bufferPointer = nullptr; - vkMapMemory(m_device, stagingBufferMemory, 0, bufferSize, 0, &bufferPointer); - std::memcpy(bufferPointer, bufferData, bufferSize); + VkDeviceMemory stagingBufferMemory = nullptr; + VkDeviceSize stagingBufferMemoryOffset = 0; + m_allocator.allocateForBuffer(stagingBuffer, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBufferMemory, stagingBufferMemoryOffset); + vkBindBufferMemory(m_device, stagingBuffer, stagingBufferMemory, stagingBufferMemoryOffset); + + void* stagingBufferPointer = nullptr; + vkMapMemory(m_device, stagingBufferMemory, stagingBufferMemoryOffset, bufferSize, 0, &stagingBufferPointer); + std::memcpy(stagingBufferPointer, bufferData, bufferSize); vkUnmapMemory(m_device, stagingBufferMemory); VkBufferUsageFlags mainUsage = NULL; @@ -31,50 +37,26 @@ namespace nf::client::render { break; } - createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | mainUsage, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, physicalDevice, m_handle, m_memory); + createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | mainUsage, m_handle); + m_allocator.allocateForBuffer(m_handle, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_memory, m_offset); + vkBindBufferMemory(m_device, m_handle, m_memory, m_offset); copyBuffer(stagingBuffer, m_handle, bufferSize, commandPool, queue); - destroyBuffer(stagingBuffer, stagingBufferMemory); + destroyBuffer(stagingBuffer, stagingBufferMemory, stagingBufferMemoryOffset); } const VkBuffer& Buffer::getHandle() const { return m_handle; } - void Buffer::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags memoryProperties, VkPhysicalDevice physicalDevice, VkBuffer& buffer, VkDeviceMemory& bufferMemory) { + void Buffer::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkBuffer& buffer) { VkBufferCreateInfo bufferCI = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; bufferCI.size = size; bufferCI.usage = usage; if (vkCreateBuffer(m_device, &bufferCI, nullptr, &buffer) != VK_SUCCESS) NFError("Could not create vertex buffer."); - - VkMemoryRequirements memoryRequirements = {}; - vkGetBufferMemoryRequirements(m_device, buffer, &memoryRequirements); - - VkMemoryAllocateInfo allocateInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; - allocateInfo.allocationSize = memoryRequirements.size; - allocateInfo.memoryTypeIndex = findMemoryType(physicalDevice, memoryRequirements.memoryTypeBits, memoryProperties); - - if (allocateInfo.memoryTypeIndex == -1) - NFError("Could not find suitable memory type."); - - if (vkAllocateMemory(m_device, &allocateInfo, nullptr, &bufferMemory) != VK_SUCCESS) - NFError("Could not allocate buffer memory."); - - vkBindBufferMemory(m_device, buffer, bufferMemory, 0); - } - - uint32_t Buffer::findMemoryType(VkPhysicalDevice physicalDevice, uint32_t typeFilter, VkMemoryPropertyFlags properties) { - VkPhysicalDeviceMemoryProperties memoryProperties = {}; - vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memoryProperties); - - for (int i = 0; i < memoryProperties.memoryTypeCount; i++) - if (typeFilter & (1 << i) && (memoryProperties.memoryTypes[i].propertyFlags & properties) == properties) - return i; - - return -1; } void Buffer::copyBuffer(VkBuffer bufferSource, VkBuffer bufferDestination, VkDeviceSize size, VkCommandPool commandPool, VkQueue queue) { @@ -111,12 +93,12 @@ namespace nf::client::render { vkFreeCommandBuffers(m_device, commandPool, 1, &commandBuffer); } - void Buffer::destroyBuffer(VkBuffer buffer, VkDeviceMemory bufferMemory) { - vkFreeMemory(m_device, bufferMemory, nullptr); + void Buffer::destroyBuffer(VkBuffer buffer, VkDeviceMemory bufferMemory, VkDeviceSize offset) { + m_allocator.deallocate(bufferMemory, offset); vkDestroyBuffer(m_device, buffer, nullptr); } Buffer::~Buffer() { - destroyBuffer(m_handle, m_memory); + destroyBuffer(m_handle, m_memory, m_offset); } } diff --git a/NothinFancy/src/client/render/Buffer.h b/NothinFancy/src/client/render/Buffer.h index 6fadbde..eea3eab 100644 --- a/NothinFancy/src/client/render/Buffer.h +++ b/NothinFancy/src/client/render/Buffer.h @@ -1,7 +1,8 @@ // Buffer class header #pragma once -#include "VulkanResource.h" +#include "Resource.h" +#include "VideoMemoryAllocator.h" namespace nf::client::render { enum class BufferType { @@ -9,21 +10,24 @@ namespace nf::client::render { Index }; - class Buffer : VulkanResource { + class Buffer : Resource { public: - Buffer(BufferType type, const VkDevice& device, const VkPhysicalDevice& physicalDevice, const VkCommandPool& commandPool, const VkQueue& queue, void* bufferData, size_t bufferSize); + Buffer(BufferType type, const VkDevice& device, VideoMemoryAllocator& allocator, const VkCommandPool& commandPool, const VkQueue& queue, void* bufferData, size_t bufferSize); const VkBuffer& getHandle() const; ~Buffer(); private: - void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags memoryProperties, VkPhysicalDevice physicalDevice, VkBuffer& buffer, VkDeviceMemory& bufferMemory); - uint32_t findMemoryType(VkPhysicalDevice physicalDevice, uint32_t typeFilter, VkMemoryPropertyFlags properties); + void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkBuffer& buffer); void copyBuffer(VkBuffer sourceBuffer, VkBuffer destinationBuffer, VkDeviceSize size, VkCommandPool commandPool, VkQueue queue); - void destroyBuffer(VkBuffer buffer, VkDeviceMemory bufferMemory); + void destroyBuffer(VkBuffer buffer, VkDeviceMemory bufferMemory, VkDeviceSize offset); + VkBuffer m_handle; VkDeviceMemory m_memory; + VkDeviceSize m_offset; + + VideoMemoryAllocator& m_allocator; }; } diff --git a/NothinFancy/src/client/render/RenderEngine.cpp b/NothinFancy/src/client/render/RenderEngine.cpp index 5e023e9..14c0567 100644 --- a/NothinFancy/src/client/render/RenderEngine.cpp +++ b/NothinFancy/src/client/render/RenderEngine.cpp @@ -36,6 +36,7 @@ namespace nf::client::render { , m_numFramesInFlight(2) , m_frameExecutors() , m_currentFrameExecutor() + , m_allocator() , m_bufferVertex() , m_bufferIndex() { @@ -52,6 +53,7 @@ namespace nf::client::render { createOutputPipeline(); createFrameExecutors(); + m_allocator = std::make_unique(m_device, m_physicalDevice); createBuffers(); m_window->show(); @@ -452,14 +454,14 @@ namespace nf::client::render { }; size_t triangleVerticesSize = sizeof(triangleVertices[0]) * triangleVertices.size(); - m_bufferVertex = new Buffer(BufferType::Vertex, m_device, m_physicalDevice, m_commandPool, m_queueGraphics, triangleVertices.data(), triangleVerticesSize); + m_bufferVertex = std::make_unique(BufferType::Vertex, m_device, *m_allocator, m_commandPool, m_queueGraphics, triangleVertices.data(), triangleVerticesSize); std::vector triangleIndices = { 0, 1, 2, 2, 3, 0 }; size_t triangleIndicesSize = sizeof(triangleIndices[0]) * triangleIndices.size(); - m_bufferIndex = new Buffer(BufferType::Index, m_device, m_physicalDevice, m_commandPool, m_queueGraphics, triangleIndices.data(), triangleIndicesSize); + m_bufferIndex = std::make_unique(BufferType::Index, m_device, *m_allocator, m_commandPool, m_queueGraphics, triangleIndices.data(), triangleIndicesSize); } void RenderEngine::doFrame() { @@ -591,8 +593,8 @@ namespace nf::client::render { RenderEngine::~RenderEngine() { waitIdle(); - delete m_bufferIndex; - delete m_bufferVertex; + m_bufferIndex.reset(); + m_bufferVertex.reset(); for (int i = 0; i < m_frameExecutors.size(); i++) { vkDestroyFence(m_device, m_frameExecutors[i].fenceFrameInFlight, nullptr); diff --git a/NothinFancy/src/client/render/RenderEngine.h b/NothinFancy/src/client/render/RenderEngine.h index 0cf8e8f..4805573 100644 --- a/NothinFancy/src/client/render/RenderEngine.h +++ b/NothinFancy/src/client/render/RenderEngine.h @@ -3,6 +3,7 @@ #include "client/Window.h" #include "nf/config.h" +#include "VideoMemoryAllocator.h" #include "Buffer.h" namespace nf::client::render { @@ -68,7 +69,8 @@ namespace nf::client::render { std::vector m_frameExecutors; uint32_t m_currentFrameExecutor; - Buffer* m_bufferVertex; - Buffer* m_bufferIndex; + std::unique_ptr m_allocator; + std::unique_ptr m_bufferVertex; + std::unique_ptr m_bufferIndex; }; } diff --git a/NothinFancy/src/client/render/VulkanResource.h b/NothinFancy/src/client/render/Resource.h similarity index 72% rename from NothinFancy/src/client/render/VulkanResource.h rename to NothinFancy/src/client/render/Resource.h index 7700f7e..9666006 100644 --- a/NothinFancy/src/client/render/VulkanResource.h +++ b/NothinFancy/src/client/render/Resource.h @@ -2,9 +2,9 @@ #pragma once namespace nf::client::render { - class VulkanResource { + class Resource { public: - VulkanResource(const VkDevice& device) + Resource(const VkDevice& device) : m_device(device) {} diff --git a/NothinFancy/src/client/render/ShaderModule.cpp b/NothinFancy/src/client/render/ShaderModule.cpp index 16f231e..18fc29d 100644 --- a/NothinFancy/src/client/render/ShaderModule.cpp +++ b/NothinFancy/src/client/render/ShaderModule.cpp @@ -6,7 +6,7 @@ namespace nf::client::render { ShaderModule::ShaderModule(const VkDevice& device, const std::string& shaderBinary) - : VulkanResource(device) + : Resource(device) , m_shaderModule() { VkShaderModuleCreateInfo shaderModuleCI = { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO }; diff --git a/NothinFancy/src/client/render/ShaderModule.h b/NothinFancy/src/client/render/ShaderModule.h index 63c642e..7083aa5 100644 --- a/NothinFancy/src/client/render/ShaderModule.h +++ b/NothinFancy/src/client/render/ShaderModule.h @@ -1,10 +1,10 @@ // ShaderModule class header #pragma once -#include "VulkanResource.h" +#include "Resource.h" namespace nf::client::render { - class ShaderModule : VulkanResource { + class ShaderModule : Resource { public: ShaderModule(const VkDevice& device, const std::string& shaderBinary); diff --git a/NothinFancy/src/client/render/VideoMemoryAllocator.cpp b/NothinFancy/src/client/render/VideoMemoryAllocator.cpp new file mode 100644 index 0000000..ca73802 --- /dev/null +++ b/NothinFancy/src/client/render/VideoMemoryAllocator.cpp @@ -0,0 +1,130 @@ +// VideoMemoryAllocator class implementation +#include "pch.h" + +#include "VideoMemoryAllocator.h" +#include "util.h" + +// 1 GiB +#define HEAP_SIZE_LARGE 1024ull * 1024 * 1024 +// 50 MiB +#define DEFAULT_ALLOCATION_AMOUNT 50ull * 1024 * 1024 + +namespace nf::client::render { + VideoMemoryAllocator::VideoMemoryAllocator(const VkDevice& device, const VkPhysicalDevice& physicalDevice) + : m_device(device) + , m_physicalDevice(physicalDevice) + , m_blocks() + {} + + void VideoMemoryAllocator::allocateForBuffer(VkBuffer buffer, VkMemoryPropertyFlags memoryPropertyFlags, VkDeviceMemory& outDeviceMemory, VkDeviceSize& outOffset) { + VkMemoryRequirements memoryRequirements = {}; + vkGetBufferMemoryRequirements(m_device, buffer, &memoryRequirements); + + VkMemoryAllocateInfo allocateInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; + allocateInfo.allocationSize = memoryRequirements.size; + allocateInfo.memoryTypeIndex = findMemoryType(memoryRequirements.memoryTypeBits, memoryPropertyFlags); + + if (allocateInfo.memoryTypeIndex == -1) + NFError("Could not find suitable memory type."); + + allocateFromBlock(allocateInfo, memoryRequirements.alignment, outDeviceMemory, outOffset); + } + + void VideoMemoryAllocator::deallocate(VkDeviceMemory memoryBlock, VkDeviceSize offset) { + const auto blockIterator = std::find_if(m_blocks.begin(), m_blocks.end(), [&](const auto& currentBlock) { + if (currentBlock.memory == memoryBlock) + return true; + else return false; + }); + + if (blockIterator == m_blocks.end()) + NFError("Could not find video memory block to deallocate from."); + + MemoryBlock& block = *blockIterator; + + const auto allocationIterator = block.allocations.find(offset); + + if (allocationIterator == block.allocations.end()) + NFError("Could not find video memory allocation to deallocate."); + + block.allocations.erase(allocationIterator); + + // If block is now empty, free it + if (block.allocations.empty()) { + vkFreeMemory(m_device, block.memory, nullptr); + m_blocks.erase(blockIterator); + } + } + + uint32_t VideoMemoryAllocator::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags desiredPropertyFlags) { + VkPhysicalDeviceMemoryProperties memoryProperties = {}; + vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &memoryProperties); + + for (int i = 0; i < memoryProperties.memoryTypeCount; i++) + if (typeFilter & (1 << i) && (memoryProperties.memoryTypes[i].propertyFlags & desiredPropertyFlags) == desiredPropertyFlags) + return i; + + return -1; + } + + void VideoMemoryAllocator::allocateFromBlock(VkMemoryAllocateInfo& allocateInfo, uint32_t alignment, VkDeviceMemory& outDeviceMemory, VkDeviceSize& outOffset) { + while (true) { + for (auto& block : m_blocks) { + if (block.memoryTypeIndex == allocateInfo.memoryTypeIndex) { + VkDeviceSize lastOffset = 0, lastSize = 0; + // Check between existing allocations + for (const auto& [currentOffset, currentSize] : block.allocations) { + // Check for alignment + VkDeviceSize checkOffset = lastOffset + lastSize, remainder = checkOffset % alignment; + checkOffset = remainder ? checkOffset + (alignment - remainder) : checkOffset; + // Check for enough space + if (currentOffset - lastOffset - lastSize >= allocateInfo.allocationSize) { + outDeviceMemory = block.memory; + outOffset = checkOffset; + block.allocations[checkOffset] = allocateInfo.allocationSize; + return; + } + + lastOffset = currentOffset, lastSize = currentSize; + } + + // Check end + VkDeviceSize checkOffset = lastOffset + lastSize, remainder = checkOffset % alignment; + checkOffset = remainder ? checkOffset + (alignment - remainder) : checkOffset; + if ((block.size - 1) - lastOffset - lastSize >= allocateInfo.allocationSize) { + outDeviceMemory = block.memory; + outOffset = checkOffset; + block.allocations[checkOffset] = allocateInfo.allocationSize; + return; + } + + // If not enough space, continue + continue; + } + } + + // No suitable block found, allocate one + VkMemoryAllocateInfo newBlockAllocateInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; + newBlockAllocateInfo.memoryTypeIndex = allocateInfo.memoryTypeIndex; + newBlockAllocateInfo.allocationSize = calculateBlockSize(allocateInfo.memoryTypeIndex); + allocateBlock(newBlockAllocateInfo); + } + } + + size_t VideoMemoryAllocator::calculateBlockSize(uint32_t memoryTypeIndex) const { + VkPhysicalDeviceMemoryProperties memoryProperties = {}; + vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &memoryProperties); + size_t heapSize = memoryProperties.memoryHeaps[memoryProperties.memoryTypes[memoryTypeIndex].heapIndex].size; + + return heapSize > HEAP_SIZE_LARGE ? DEFAULT_ALLOCATION_AMOUNT : (heapSize / 8); + } + + void VideoMemoryAllocator::allocateBlock(VkMemoryAllocateInfo& allocateInfo) { + VkDeviceMemory memoryBlock = nullptr; + + if (vkAllocateMemory(m_device, &allocateInfo, nullptr, &memoryBlock) != VK_SUCCESS) + NFError("Could not allocate video memory."); + + m_blocks.emplace_back(memoryBlock, allocateInfo.memoryTypeIndex, allocateInfo.allocationSize); + } +} \ No newline at end of file diff --git a/NothinFancy/src/client/render/VideoMemoryAllocator.h b/NothinFancy/src/client/render/VideoMemoryAllocator.h new file mode 100644 index 0000000..4023d49 --- /dev/null +++ b/NothinFancy/src/client/render/VideoMemoryAllocator.h @@ -0,0 +1,32 @@ +// VideoMemoryAllocator class header +#pragma once + +namespace nf::client::render { + struct MemoryBlock { + VkDeviceMemory memory; + uint32_t memoryTypeIndex; + size_t size; + + std::map allocations; + // Offset Size + }; + + class VideoMemoryAllocator { + public: + VideoMemoryAllocator(const VkDevice& device, const VkPhysicalDevice& physicalDevice); + + void allocateForBuffer(VkBuffer buffer, VkMemoryPropertyFlags memoryPropertyFlags, VkDeviceMemory& outDeviceMemory, VkDeviceSize& outOffset); + void deallocate(VkDeviceMemory memoryBlock, VkDeviceSize offset); + private: + uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags desiredPropertyFlags); + + void allocateFromBlock(VkMemoryAllocateInfo& allocateInfo, uint32_t alignment, VkDeviceMemory& outDeviceMemory, VkDeviceSize& outOffset); + size_t calculateBlockSize(uint32_t memoryTypeIndex) const; + void allocateBlock(VkMemoryAllocateInfo& allocateInfo); + + const VkDevice& m_device; + const VkPhysicalDevice& m_physicalDevice; + + std::vector m_blocks; + }; +}