diff --git a/CMakeLists.txt b/CMakeLists.txt index 354dca10..dad53933 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -224,6 +224,7 @@ set(SOURCE_FILES MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/UniformDescriptorBinder.cpp + MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.cpp new file mode 100644 index 00000000..14b55776 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.cpp @@ -0,0 +1,138 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.cpp +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#include "BufferArena.h" + +namespace MobileGL::MG_Backend::DirectVulkan { + Bool BufferArena::Initialize(const BufferArenaDesc& desc) { + Shutdown(); + + MOBILEGL_ASSERT(desc.allocator != nullptr, "BufferArena::Initialize requires valid allocator"); + MOBILEGL_ASSERT(desc.frameCount > 0, "BufferArena::Initialize requires non-zero frame count"); + MOBILEGL_ASSERT(desc.usage != 0, "BufferArena::Initialize requires non-zero buffer usage"); + + m_desc = desc; + m_frames.clear(); + m_frames.resize(desc.frameCount); + m_deferredReleases.resize(desc.frameCount); + return true; + } + + void BufferArena::Shutdown() { + for (auto& frame : m_frames) { + frame.buffer.Destroy(); + frame.writeCursor = 0; + } + m_frames.clear(); + m_deferredReleases.clear(); + m_desc = {}; + } + + void BufferArena::BeginFrame(Uint32 frameIndex) { + CollectDeferredReleases(frameIndex); + ResetFrame(frameIndex); + } + + void BufferArena::ResetFrame(Uint32 frameIndex) { + AssertValidFrameIndex(frameIndex); + m_frames[frameIndex].writeCursor = 0; + } + + void BufferArena::CollectDeferredReleases(Uint32 frameIndex) { + AssertValidFrameIndex(frameIndex); + m_deferredReleases[frameIndex].clear(); + } + + Bool BufferArena::Allocate(Uint32 frameIndex, VkDeviceSize size, VkDeviceSize alignment, BufferSlice& outSlice) { + AssertValidFrameIndex(frameIndex); + MOBILEGL_ASSERT(size > 0, "BufferArena::Allocate requires non-zero size"); + + auto& frame = m_frames[frameIndex]; + const VkDeviceSize resolvedAlignment = alignment > 0 ? alignment : 1; + const VkDeviceSize offset = (frame.writeCursor + resolvedAlignment - 1) & ~(resolvedAlignment - 1); + const VkDeviceSize endOffset = offset + size; + + if (!EnsureCapacity(frameIndex, endOffset)) { + return false; + } + + frame.writeCursor = endOffset; + outSlice = frame.buffer.GetSlice(offset, size); + return outSlice.IsValid(); + } + + Bool BufferArena::Upload(Uint32 frameIndex, const void* data, VkDeviceSize size, VkDeviceSize alignment, + BufferSlice& outSlice) { + MOBILEGL_ASSERT(data != nullptr || size == 0, "BufferArena::Upload data pointer is null"); + if (!Allocate(frameIndex, size, alignment, outSlice)) { + return false; + } + + if (outSlice.mapped != nullptr) { + Memcpy(outSlice.mapped, data, static_cast(size)); + return true; + } + + return m_frames[frameIndex].buffer.Upload(data, size, outSlice.offset); + } + + VkDeviceSize BufferArena::GetWriteCursor(Uint32 frameIndex) const { + AssertValidFrameIndex(frameIndex); + return m_frames[frameIndex].writeCursor; + } + + Uint32 BufferArena::GetFrameCount() const { + return static_cast(m_frames.size()); + } + + Bool BufferArena::EnsureCapacity(Uint32 frameIndex, VkDeviceSize requiredEndOffset) { + AssertValidFrameIndex(frameIndex); + auto& frame = m_frames[frameIndex]; + auto& buffer = frame.buffer; + + if (buffer.IsValid() && buffer.GetSize() >= requiredEndOffset) { + return true; + } + + VkDeviceSize newCapacity = buffer.IsValid() ? buffer.GetSize() : 0; + if (newCapacity < m_desc.minBufferSize) { + newCapacity = m_desc.minBufferSize; + } + if (newCapacity == 0) { + newCapacity = requiredEndOffset; + } + while (newCapacity < requiredEndOffset) { + newCapacity *= 2; + } + + if (buffer.IsValid()) { + m_deferredReleases[frameIndex].push_back(std::move(buffer)); + } + + VkBufferObjectDesc bufferDesc{}; + bufferDesc.allocator = m_desc.allocator; + bufferDesc.size = newCapacity; + bufferDesc.usage = m_desc.usage; + bufferDesc.memoryUsage = m_desc.memoryUsage; + bufferDesc.allocationFlags = m_desc.allocationFlags; + if (!buffer.Create(bufferDesc)) { + return false; + } + if (m_desc.persistentlyMapped && buffer.Map() == nullptr) { + buffer.Destroy(); + return false; + } + + frame.writeCursor = 0; + return true; + } + + void BufferArena::AssertValidFrameIndex(Uint32 frameIndex) const { + MOBILEGL_ASSERT(frameIndex < m_frames.size(), "BufferArena frame index out of range"); + } +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.h new file mode 100644 index 00000000..e9fc509a --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.h @@ -0,0 +1,56 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.h +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#pragma once + +#include "BufferSlice.h" +#include "VkBufferObject.h" +#include "../VkIncludes.h" +#include +#include + +namespace MobileGL::MG_Backend::DirectVulkan { + struct BufferArenaDesc { + VmaAllocator allocator = nullptr; + Uint32 frameCount = 0; + VkBufferUsageFlags usage = 0; + VmaMemoryUsage memoryUsage = VMA_MEMORY_USAGE_AUTO; + VmaAllocationCreateFlags allocationFlags = 0; + VkDeviceSize minBufferSize = 0; + Bool persistentlyMapped = false; + }; + + class BufferArena { + public: + Bool Initialize(const BufferArenaDesc& desc); + void Shutdown(); + + void BeginFrame(Uint32 frameIndex); + void ResetFrame(Uint32 frameIndex); + void CollectDeferredReleases(Uint32 frameIndex); + + Bool Allocate(Uint32 frameIndex, VkDeviceSize size, VkDeviceSize alignment, BufferSlice& outSlice); + Bool Upload(Uint32 frameIndex, const void* data, VkDeviceSize size, VkDeviceSize alignment, BufferSlice& outSlice); + + VkDeviceSize GetWriteCursor(Uint32 frameIndex) const; + Uint32 GetFrameCount() const; + + private: + struct FrameResources { + VkBufferObject buffer; + VkDeviceSize writeCursor = 0; + }; + + Bool EnsureCapacity(Uint32 frameIndex, VkDeviceSize requiredEndOffset); + void AssertValidFrameIndex(Uint32 frameIndex) const; + + BufferArenaDesc m_desc{}; + Vector m_frames; + Vector> m_deferredReleases; + }; +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferSlice.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferSlice.h new file mode 100644 index 00000000..e4645e0c --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferSlice.h @@ -0,0 +1,23 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/BufferSlice.h +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#pragma once + +#include "../VkIncludes.h" +#include + +namespace MobileGL::MG_Backend::DirectVulkan { + struct BufferSlice { + VkBuffer buffer = VK_NULL_HANDLE; + VkDeviceSize offset = 0; + VkDeviceSize size = 0; + void* mapped = nullptr; + + Bool IsValid() const { return buffer != VK_NULL_HANDLE; } + }; +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp index dbcf7b19..07818111 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.cpp @@ -13,11 +13,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_allocator = other.m_allocator; m_buffer = other.m_buffer; m_allocation = other.m_allocation; + m_mappedData = other.m_mappedData; m_size = other.m_size; other.m_allocator = nullptr; other.m_buffer = VK_NULL_HANDLE; other.m_allocation = nullptr; + other.m_mappedData = nullptr; other.m_size = 0; } @@ -31,11 +33,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_allocator = other.m_allocator; m_buffer = other.m_buffer; m_allocation = other.m_allocation; + m_mappedData = other.m_mappedData; m_size = other.m_size; other.m_allocator = nullptr; other.m_buffer = VK_NULL_HANDLE; other.m_allocation = nullptr; + other.m_mappedData = nullptr; other.m_size = 0; return *this; } @@ -44,6 +48,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { Destroy(); } + Bool VkBufferObject::Create(const VkBufferObjectDesc& desc) { + return Create(desc.allocator, desc.size, desc.usage, desc.memoryUsage, desc.allocationFlags); + } + Bool VkBufferObject::Create(VmaAllocator allocator, VkDeviceSize size, VkBufferUsageFlags usage, VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags allocationFlags) { MOBILEGL_ASSERT(allocator != nullptr, "VkBufferObject::Create requires valid VMA allocator"); @@ -78,6 +86,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } void VkBufferObject::Destroy() { + Unmap(); if (m_allocator != nullptr && m_buffer != VK_NULL_HANDLE && m_allocation != nullptr) { vmaDestroyBuffer(m_allocator, m_buffer, m_allocation); } @@ -87,6 +96,33 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_size = 0; } + void* VkBufferObject::Map() { + MOBILEGL_ASSERT(IsValid(), "VkBufferObject::Map called on invalid buffer"); + + if (m_mappedData != nullptr) { + return m_mappedData; + } + + const VkResult mapResult = vmaMapMemory(m_allocator, m_allocation, &m_mappedData); + if (mapResult != VK_SUCCESS || m_mappedData == nullptr) { + MGLOG_E("VkBufferObject::Map failed: vmaMapMemory returned %d", mapResult); + m_mappedData = nullptr; + return nullptr; + } + + return m_mappedData; + } + + void VkBufferObject::Unmap() { + if (!IsValid() || m_mappedData == nullptr) { + m_mappedData = nullptr; + return; + } + + vmaUnmapMemory(m_allocator, m_allocation); + m_mappedData = nullptr; + } + Bool VkBufferObject::Upload(const void* data, VkDeviceSize size, VkDeviceSize offset) { MOBILEGL_ASSERT(IsValid(), "VkBufferObject::Upload called on invalid buffer"); MOBILEGL_ASSERT(data != nullptr || size == 0, "VkBufferObject::Upload data pointer is null"); @@ -96,15 +132,30 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } - void* mapped = nullptr; - const VkResult mapResult = vmaMapMemory(m_allocator, m_allocation, &mapped); - if (mapResult != VK_SUCCESS || mapped == nullptr) { - MGLOG_E("VkBufferObject::Upload failed: vmaMapMemory returned %d", mapResult); + const Bool wasMapped = IsMapped(); + void* mapped = wasMapped ? m_mappedData : Map(); + if (mapped == nullptr) { + MGLOG_E("VkBufferObject::Upload failed: unable to map buffer"); return false; } Memcpy(static_cast(mapped) + offset, data, static_cast(size)); - vmaUnmapMemory(m_allocator, m_allocation); + if (!wasMapped) { + Unmap(); + } return true; } + + BufferSlice VkBufferObject::GetSlice(VkDeviceSize offset, VkDeviceSize size) const { + MOBILEGL_ASSERT(offset <= m_size, "VkBufferObject::GetSlice offset out of range"); + const VkDeviceSize resolvedSize = (size == VK_WHOLE_SIZE) ? (m_size - offset) : size; + MOBILEGL_ASSERT(offset + resolvedSize <= m_size, "VkBufferObject::GetSlice range out of bounds"); + + BufferSlice slice{}; + slice.buffer = m_buffer; + slice.offset = offset; + slice.size = resolvedSize; + slice.mapped = (m_mappedData != nullptr) ? static_cast(m_mappedData) + offset : nullptr; + return slice; + } } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.h index 48a06152..80be2981 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.h @@ -8,11 +8,20 @@ #pragma once +#include "BufferSlice.h" #include "../VkIncludes.h" #include #include namespace MobileGL::MG_Backend::DirectVulkan { + struct VkBufferObjectDesc { + VmaAllocator allocator = nullptr; + VkDeviceSize size = 0; + VkBufferUsageFlags usage = 0; + VmaMemoryUsage memoryUsage = VMA_MEMORY_USAGE_AUTO; + VmaAllocationCreateFlags allocationFlags = 0; + }; + class VkBufferObject { public: VkBufferObject() = default; @@ -23,20 +32,27 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkBufferObject(VkBufferObject&& other) noexcept; VkBufferObject& operator=(VkBufferObject&& other) noexcept; + Bool Create(const VkBufferObjectDesc& desc); Bool Create(VmaAllocator allocator, VkDeviceSize size, VkBufferUsageFlags usage, VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags allocationFlags = 0); void Destroy(); + void* Map(); + void Unmap(); Bool Upload(const void* data, VkDeviceSize size, VkDeviceSize offset = 0); VkBuffer GetHandle() const { return m_buffer; } VkDeviceSize GetSize() const { return m_size; } + BufferSlice GetSlice(VkDeviceSize offset = 0, VkDeviceSize size = VK_WHOLE_SIZE) const; + void* GetMappedData() const { return m_mappedData; } + Bool IsMapped() const { return m_mappedData != nullptr; } Bool IsValid() const { return m_allocator != nullptr && m_buffer != VK_NULL_HANDLE && m_allocation != nullptr; } private: VmaAllocator m_allocator = nullptr; VkBuffer m_buffer = VK_NULL_HANDLE; VmaAllocation m_allocation = nullptr; + void* m_mappedData = nullptr; VkDeviceSize m_size = 0; }; } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 6bdd7b61..6204e0d2 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -334,16 +334,32 @@ namespace MobileGL::MG_Backend::DirectVulkan { MOBILEGL_ASSERT(m_vertexInputStateFactory != nullptr, "VertexInputStateFactory creation failed."); CreateFrameContexts(); - m_frameVertexUploadBuffers.resize(m_frameContext.GetFrameCount()); - m_frameVertexUploadHeads.assign(m_frameContext.GetFrameCount(), 0); - m_frameIndexUploadBuffers.resize(m_frameContext.GetFrameCount()); - m_frameIndexUploadHeads.assign(m_frameContext.GetFrameCount(), 0); - m_deferredBufferReleases.clear(); - m_deferredBufferReleases.resize(m_frameContext.GetFrameCount()); + succeeded = m_vertexUploadArena.Initialize({ + .allocator = m_allocator, + .frameCount = m_frameContext.GetFrameCount(), + .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + .memoryUsage = VMA_MEMORY_USAGE_AUTO, + .allocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + .minBufferSize = 4 * 1024 * 1024, + .persistentlyMapped = false, + }); + MOBILEGL_ASSERT(succeeded, "Vertex upload arena initialization failed."); + succeeded = m_indexUploadArena.Initialize({ + .allocator = m_allocator, + .frameCount = m_frameContext.GetFrameCount(), + .usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT, + .memoryUsage = VMA_MEMORY_USAGE_AUTO, + .allocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + .minBufferSize = 1 * 1024 * 1024, + .persistentlyMapped = false, + }); + MOBILEGL_ASSERT(succeeded, "Index upload arena initialization failed."); // Prime the first frame so Render() always targets an acquired swapchain image. VK_VERIFY(m_frameContext.WaitAndAcquireNextImage(m_device, m_swapchainObject.GetHandle(), m_imageIndexAcquired), "Initialize, WaitAndAcquireNextImage"); + m_vertexUploadArena.BeginFrame(m_frameContext.GetCurrentFrameIndex()); + m_indexUploadArena.BeginFrame(m_frameContext.GetCurrentFrameIndex()); MGLOG_D("VulkanRenderer initialized"); } @@ -363,17 +379,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_textureManager.reset(); } m_vertexInputStateFactory.reset(); - for (auto& buffer : m_frameVertexUploadBuffers) { - buffer.Destroy(); - } - for (auto& buffer : m_frameIndexUploadBuffers) { - buffer.Destroy(); - } - m_frameVertexUploadBuffers.clear(); - m_frameVertexUploadHeads.clear(); - m_frameIndexUploadBuffers.clear(); - m_frameIndexUploadHeads.clear(); - m_deferredBufferReleases.clear(); + m_vertexUploadArena.Shutdown(); + m_indexUploadArena.Shutdown(); m_frameContext.Destroy(m_device, m_commandPool); @@ -418,58 +425,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { MGLOG_I("VulkanRenderer shut down completed"); } - void VulkanRenderer::DeferDestroyBuffer(VkBufferObject& buffer) { - if (!buffer.IsValid()) { - return; - } - const Uint32 frameIndex = m_frameContext.GetCurrentFrameIndex(); - if (m_deferredBufferReleases.size() < m_frameContext.GetFrameCount()) { - m_deferredBufferReleases.resize(m_frameContext.GetFrameCount()); - } - m_deferredBufferReleases[frameIndex].push_back(std::move(buffer)); - } - - void VulkanRenderer::CollectDeferredBufferReleases(Uint32 frameIndex) { - if (frameIndex >= m_deferredBufferReleases.size()) { - return; - } - m_deferredBufferReleases[frameIndex].clear(); - } - - Bool VulkanRenderer::EnsureFrameUploadBufferCapacity(Uint32 frameIndex, Bool isIndexBuffer, - VkDeviceSize requiredEndOffset, VkDeviceSize minCapacity, - VkBufferUsageFlags usage) { - auto& buffers = isIndexBuffer ? m_frameIndexUploadBuffers : m_frameVertexUploadBuffers; - auto& heads = isIndexBuffer ? m_frameIndexUploadHeads : m_frameVertexUploadHeads; - if (frameIndex >= buffers.size() || frameIndex >= heads.size()) { - return false; - } - - auto& uploadBuffer = buffers[frameIndex]; - if (uploadBuffer.IsValid() && uploadBuffer.GetSize() >= requiredEndOffset) { - return true; - } - - VkDeviceSize newCapacity = uploadBuffer.IsValid() ? uploadBuffer.GetSize() : 0; - if (newCapacity < minCapacity) { - newCapacity = minCapacity; - } - while (newCapacity < requiredEndOffset) { - newCapacity *= 2; - } - - DeferDestroyBuffer(uploadBuffer); - if (!uploadBuffer.Create(m_allocator, newCapacity, usage, VMA_MEMORY_USAGE_AUTO, - VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT)) { - MGLOG_E("EnsureFrameUploadBufferCapacity failed: create upload buffer (index=%d, capacity=%zu)", - isIndexBuffer, static_cast(newCapacity)); - return false; - } - - heads[frameIndex] = 0; - return true; - } - Bool VulkanRenderer::UploadAndBindVertexStreams( VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao) { auto& vertexInputState = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao); @@ -498,26 +453,19 @@ namespace MobileGL::MG_Backend::DirectVulkan { for (SizeT binding = 0; binding < bindingCount; ++binding) { const SizeT bufferKey = vertexInputState.bindingBufferKeys[binding]; const MG_State::GLState::BufferObject* sourceBuffer = findBufferByKey(bufferKey); + MOBILEGL_ASSERT(sourceBuffer != nullptr, "UploadAndBindVertexStreams failed to resolve source buffer"); const auto sourceData = sourceBuffer->GetDataReadOnly(); const SizeT sourceSize = sourceBuffer->GetSize(); - VkDeviceSize& frameHead = m_frameVertexUploadHeads[frameIndex]; - const VkDeviceSize writeOffset = (frameHead + 0x0F) & ~VkDeviceSize(0x0F); - const VkDeviceSize writeEnd = writeOffset + static_cast(sourceSize); - if (!EnsureFrameUploadBufferCapacity(frameIndex, false, writeEnd, 4 * 1024 * 1024, - VK_BUFFER_USAGE_VERTEX_BUFFER_BIT)) { - return false; - } - auto& frameUploadBuffer = m_frameVertexUploadBuffers[frameIndex]; - if (!frameUploadBuffer.Upload(sourceData->data(), static_cast(sourceSize), writeOffset)) { + BufferSlice slice{}; + if (!m_vertexUploadArena.Upload(frameIndex, sourceData->data(), static_cast(sourceSize), 16, + slice)) { MGLOG_E("UploadAndBindVertexStreams skipped: failed to upload binding %zu", binding); return false; } - - frameHead = writeEnd; - vkBuffers[binding] = frameUploadBuffer.GetHandle(); - vkOffsets[binding] = writeOffset; + vkBuffers[binding] = slice.buffer; + vkOffsets[binding] = slice.offset; } vkCmdBindVertexBuffers(commandBuffer, 0, static_cast(bindingCount), vkBuffers.data(), vkOffsets.data()); @@ -551,25 +499,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { "DrawElements index range out of bounds"); const Uint32 frameIndex = m_frameContext.GetCurrentFrameIndex(); - VkDeviceSize& frameIndexHead = m_frameIndexUploadHeads[frameIndex]; const VkDeviceSize alignment = indexSize; - const VkDeviceSize writeOffset = (frameIndexHead + alignment - 1) & ~(alignment - 1); - const VkDeviceSize writeEnd = writeOffset + static_cast(indexDataSizeBytes); - if (!EnsureFrameUploadBufferCapacity(frameIndex, true, writeEnd, 1 * 1024 * 1024, - VK_BUFFER_USAGE_INDEX_BUFFER_BIT)) { + BufferSlice slice{}; + if (!m_indexUploadArena.Upload(frameIndex, indexData->data() + pIndexBufferView->indexByteOffset, + static_cast(indexDataSizeBytes), alignment, slice)) { MGLOG_E("DrawElements skipped: failed to prepare index upload buffer"); return false; } - - auto& frameIndexUploadBuffer = m_frameIndexUploadBuffers[frameIndex]; - if (!frameIndexUploadBuffer.Upload(indexData->data() + pIndexBufferView->indexByteOffset, - static_cast(indexDataSizeBytes), writeOffset)) { - MGLOG_E("DrawElements skipped: failed to upload index data"); - return false; - } - - frameIndexHead = writeEnd; - vkCmdBindIndexBuffer(frame.commandBuffer, frameIndexUploadBuffer.GetHandle(), writeOffset, vkIndexType); + vkCmdBindIndexBuffer(frame.commandBuffer, slice.buffer, slice.offset, vkIndexType); return true; } @@ -1346,13 +1283,8 @@ void main() { result = VK_SUCCESS; } VK_VERIFY(result, "Present, vkAcquireNextImageKHR"); - CollectDeferredBufferReleases(m_frameContext.GetCurrentFrameIndex()); - if (m_frameContext.GetCurrentFrameIndex() < m_frameVertexUploadHeads.size()) { - m_frameVertexUploadHeads[m_frameContext.GetCurrentFrameIndex()] = 0; - } - if (m_frameContext.GetCurrentFrameIndex() < m_frameIndexUploadHeads.size()) { - m_frameIndexUploadHeads[m_frameContext.GetCurrentFrameIndex()] = 0; - } + m_vertexUploadArena.BeginFrame(m_frameContext.GetCurrentFrameIndex()); + m_indexUploadArena.BeginFrame(m_frameContext.GetCurrentFrameIndex()); } void VulkanRenderer::CreateInstance() { @@ -1888,12 +1820,32 @@ void main() { m_frameContext.GetCurrent().isCommandRecording = false; m_frameContext.GetCurrent().hasCommandBufferRecorded = false; } - m_deferredBufferReleases.clear(); - m_deferredBufferReleases.resize(m_frameContext.GetFrameCount()); - m_frameVertexUploadBuffers.resize(m_frameContext.GetFrameCount()); - m_frameVertexUploadHeads.assign(m_frameContext.GetFrameCount(), 0); - m_frameIndexUploadBuffers.resize(m_frameContext.GetFrameCount()); - m_frameIndexUploadHeads.assign(m_frameContext.GetFrameCount(), 0); + m_vertexUploadArena.Shutdown(); + Bool okArena = m_vertexUploadArena.Initialize({ + .allocator = m_allocator, + .frameCount = m_frameContext.GetFrameCount(), + .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + .memoryUsage = VMA_MEMORY_USAGE_AUTO, + .allocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + .minBufferSize = 4 * 1024 * 1024, + .persistentlyMapped = false, + }); + MOBILEGL_ASSERT(okArena, "RecreateSwapchain: vertex upload arena initialization failed"); + m_indexUploadArena.Shutdown(); + okArena = m_indexUploadArena.Initialize({ + .allocator = m_allocator, + .frameCount = m_frameContext.GetFrameCount(), + .usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT, + .memoryUsage = VMA_MEMORY_USAGE_AUTO, + .allocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + .minBufferSize = 1 * 1024 * 1024, + .persistentlyMapped = false, + }); + MOBILEGL_ASSERT(okArena, "RecreateSwapchain: index upload arena initialization failed"); + if (m_frameContext.GetFrameCount() > 0) { + m_vertexUploadArena.BeginFrame(m_frameContext.GetCurrentFrameIndex()); + m_indexUploadArena.BeginFrame(m_frameContext.GetCurrentFrameIndex()); + } } const PhysicalDevice& VulkanRenderer::GetPhysicalDevice() const { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 987392cf..ec18114b 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -8,6 +8,7 @@ #pragma once #include "Config.h" +#include "BufferArena.h" #include "FrameContext.h" #include "PipelineFactory.h" #include "ProgramFactory.h" @@ -170,11 +171,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkCommandPool m_commandPool = VK_NULL_HANDLE; - Vector m_frameVertexUploadBuffers; - Vector m_frameVertexUploadHeads; - Vector m_frameIndexUploadBuffers; - Vector m_frameIndexUploadHeads; - Vector> m_deferredBufferReleases; + BufferArena m_vertexUploadArena; + BufferArena m_indexUploadArena; Uint m_imageIndexAcquired = 0; FrameContext m_frameContext; @@ -208,13 +206,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { const MG_State::GLState::VertexArrayObject& vao, const RenderPassEntry& renderPassEntry); - void DeferDestroyBuffer(VkBufferObject& buffer); - void CollectDeferredBufferReleases(Uint32 frameIndex); - Bool EnsureFrameUploadBufferCapacity(Uint32 frameIndex, Bool isIndexBuffer, VkDeviceSize requiredEndOffset, - VkDeviceSize minCapacity, VkBufferUsageFlags usage); Bool UploadAndBindVertexStreams(VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao); Bool UploadAndBindIndexBuffer(FrameContext::FrameData& frame, - const MG_State::GLState::VertexArrayObject& vao, + const MG_State::GLState::VertexArrayObject& vao, const IndexBufferView* pIndexBufferView = nullptr); Bool InitializeBlitResources(); void ShutdownBlitResources();