From 0de84983502fbb202b41ce30f9755d60a5e26597 Mon Sep 17 00:00:00 2001 From: atdunbg Date: Wed, 23 Sep 2026 12:51:11 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=98=BE=E5=AD=98=E5=88=86=E9=85=8D?= =?UTF-8?q?=E5=99=A8=E8=BF=81=E7=A7=BB=E5=88=B0=20VMA=EF=BC=88P1=20?= =?UTF-8?q?=E8=B7=AF=E7=BA=BF=E7=AC=AC=E4=B8=80=E9=A1=B9=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - vendored VulkanMemoryAllocator 子模块,唯一实现编译单元 VmaImpl.cpp - MemoryAllocator 重写为 VMA 后端:接口从"Allocate(reqs)+手绑"升级为 "创建即分配即绑定"(CreateBuffer/CreateImage/Destroy/Flush), VMA 要求资源与内存成对释放,约束成单一出口 - Buffer/Texture/RenderGraph 三处调用方迁移;staging 持久映射 + 按分配 精确 flush(原来按整块 flush) - 获得:TLSF 通用分配、DedicatedAllocation 自动启用、memory budget 感知、 内存别名基础(TAA/复用前置) - 编译零错误、validation 零警告、8 秒冒烟通过 --- .gitmodules | 3 + Engine/CMakeLists.txt | 1 + Engine/include/Engine/Vulkan/Buffer.h | 5 - .../include/Engine/Vulkan/MemoryAllocator.h | 68 ++-- Engine/include/Engine/Vulkan/Texture.h | 1 - Engine/src/RenderGraph/RenderGraph.cpp | 15 +- Engine/src/Renderer/Renderer.cpp | 2 +- Engine/src/Vulkan/Buffer.cpp | 36 +- Engine/src/Vulkan/MemoryAllocator.cpp | 309 +++++++----------- Engine/src/Vulkan/Texture.cpp | 42 +-- Engine/src/Vulkan/VmaImpl.cpp | 12 + Lib/VulkanMemoryAllocator | 1 + 12 files changed, 200 insertions(+), 295 deletions(-) create mode 100644 Engine/src/Vulkan/VmaImpl.cpp create mode 160000 Lib/VulkanMemoryAllocator diff --git a/.gitmodules b/.gitmodules index 673f390..d14ceae 100644 --- a/.gitmodules +++ b/.gitmodules @@ -17,3 +17,6 @@ path = Lib/imgui url = https://github.com/ocornut/imgui.git branch = docking +[submodule "Lib/VulkanMemoryAllocator"] + path = Lib/VulkanMemoryAllocator + url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git diff --git a/Engine/CMakeLists.txt b/Engine/CMakeLists.txt index 83ee939..b230167 100644 --- a/Engine/CMakeLists.txt +++ b/Engine/CMakeLists.txt @@ -21,6 +21,7 @@ add_library(Engine STATIC ${ENGINE_SOURCES} ${IMGUI_SOURCES}) target_include_directories(Engine PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/Lib/entt/src + ${CMAKE_SOURCE_DIR}/Lib/VulkanMemoryAllocator/include ${IMGUI_DIR} ${IMGUI_DIR}/backends ) diff --git a/Engine/include/Engine/Vulkan/Buffer.h b/Engine/include/Engine/Vulkan/Buffer.h index 2b3ed08..c26f4e8 100644 --- a/Engine/include/Engine/Vulkan/Buffer.h +++ b/Engine/include/Engine/Vulkan/Buffer.h @@ -36,11 +36,6 @@ namespace Engine { vk::Buffer m_Buffer; vk::DeviceSize m_Size; - vk::DeviceMemory m_Memory; - vk::DeviceSize m_Offset; - - vk::DeviceSize m_BlockSize; // 所属内存块的总大小 - MemoryAllocator::Allocation m_Allocation; MemoryAllocator& m_Allocator; diff --git a/Engine/include/Engine/Vulkan/MemoryAllocator.h b/Engine/include/Engine/Vulkan/MemoryAllocator.h index fe5979a..6eaf70f 100644 --- a/Engine/include/Engine/Vulkan/MemoryAllocator.h +++ b/Engine/include/Engine/Vulkan/MemoryAllocator.h @@ -1,68 +1,74 @@ // // Created by atdunbg on 2026/9/20. // +// MemoryAllocator:显存分配器,VMA 后端。 +// +// 2026-09-23 从手写 bump+free-list 块分配迁移到 VMA(VulkanMemoryAllocator): +// - TLSF 式通用分配 + DedicatedAllocation 扩展自动启用 +// - 线性分配器(staging 类短命资源自动走线性算法) +// - VK_EXT_memory_budget 感知(分配决策参考真实显存余量) +// - 为后续内存别名(同一 VkDeviceMemory 多资源重绑)铺路 +// +// 接口哲学:不再是"Allocate(reqs) + 手动 bind"——VMA 明确不推荐把分配与 +// 绑定拆开。创建即分配即绑定,销毁必须走 Destroy(资源与内存成对释放), +// 这是本类存在的意义:把 VMA 的正确用法约束成一个出口。 +// #ifndef HELLOVULKAN_MEMORYALLOCATOR_H #define HELLOVULKAN_MEMORYALLOCATOR_H - #pragma once #include -#include -#include #include "Engine/Core/Types.h" +// VMA 内部类型前向声明(实现在 VmaImpl.cpp,头文件不引入 vk_mem_alloc.h) +struct VmaAllocator_T; +struct VmaAllocation_T; + namespace Engine { class Device; class MemoryAllocator { public: - MemoryAllocator(Device& device); + explicit MemoryAllocator(Device& device); ~MemoryAllocator(); MemoryAllocator(const MemoryAllocator&) = delete; MemoryAllocator& operator=(const MemoryAllocator&) = delete; + // 分配结果:资源句柄 + 分配句柄合一。 + // Buffer/Image 二选一有值;MappedPtr 仅 HOST_VISIBLE 非空(持久映射)。 struct Allocation { - vk::DeviceMemory Memory; - vk::DeviceSize Offset; - vk::DeviceSize Size; + vk::Buffer Buffer{}; + vk::Image Image{}; void* MappedPtr = nullptr; - - // 内部用于 Free - u32 BlockIndex = 0; - u32 TypeIndex = 0; + vk::DeviceSize Size = 0; bool Valid = false; + + // VMA 内部句柄(上层不要碰) + VmaAllocation_T* Handle = nullptr; }; - Allocation Allocate(vk::MemoryRequirements reqs, vk::MemoryPropertyFlags props); - void Free(const Allocation& alloc); + // 创建 + 分配 + 绑定一步完成(VMA 惯用法) + Allocation CreateBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, + vk::MemoryPropertyFlags props); + Allocation CreateImage(const vk::ImageCreateInfo& imageInfo, + vk::MemoryPropertyFlags props); + + // 销毁资源并释放分配(按 alloc.Buffer/Image 判断类型),随后 alloc 归零 + void Destroy(Allocation& alloc); + + // HOST_VISIBLE 非 coherent 写入后冲刷(VMA 内部对 coherent 自动跳过) + void Flush(const Allocation& alloc); void PrintStats() const; private: - struct FreeRange { - vk::DeviceSize Offset; - vk::DeviceSize Size; - }; - - struct Block { - vk::DeviceMemory Memory; - vk::DeviceSize Size; - vk::DeviceSize Used = 0; // bump 分配用 - void* MappedPtr = nullptr; - bool IsMapped = false; - std::vector FreeList; // 已释放的区域 - }; - Device& m_Device; - std::array, VK_MAX_MEMORY_TYPES> m_Blocks; - std::array m_TotalAllocated{}; - - vk::DeviceSize GetBlockSize(vk::MemoryPropertyFlags props) const; + VmaAllocator_T* m_Vma = nullptr; }; } // namespace Engine diff --git a/Engine/include/Engine/Vulkan/Texture.h b/Engine/include/Engine/Vulkan/Texture.h index 8d7bf52..590d532 100644 --- a/Engine/include/Engine/Vulkan/Texture.h +++ b/Engine/include/Engine/Vulkan/Texture.h @@ -52,7 +52,6 @@ namespace Engine { Device& m_Device; vk::Image m_Image; vk::ImageView m_View; - vk::DeviceMemory m_Memory; vk::Sampler m_Sampler; diff --git a/Engine/src/RenderGraph/RenderGraph.cpp b/Engine/src/RenderGraph/RenderGraph.cpp index fd64755..bf0a9df 100644 --- a/Engine/src/RenderGraph/RenderGraph.cpp +++ b/Engine/src/RenderGraph/RenderGraph.cpp @@ -45,8 +45,7 @@ namespace Engine::RG { for (auto& img : images) { if (!img->valid) continue; vkDevice.destroyImageView(img->view); - vkDevice.destroyImage(img->image); - m_Allocator.Free(img->allocation); + m_Allocator.Destroy(img->allocation); // image 资源与显存成对释放 } } m_Pool.clear(); @@ -530,8 +529,7 @@ namespace Engine::RG { for (auto& img : it->second) { if (!img->valid) continue; vkDevice.destroyImageView(img->view); - vkDevice.destroyImage(img->image); - m_Allocator.Free(img->allocation); + m_Allocator.Destroy(img->allocation); } it = m_Pool.erase(it); } else { @@ -576,11 +574,10 @@ namespace Engine::RG { .setSharingMode(vk::SharingMode::eExclusive) .setInitialLayout(vk::ImageLayout::eUndefined); - img->image = vkDevice.createImage(imageInfo); - - auto reqs = vkDevice.getImageMemoryRequirements(img->image); - img->allocation = m_Allocator.Allocate(reqs, vk::MemoryPropertyFlagBits::eDeviceLocal); - vkDevice.bindImageMemory(img->image, img->allocation.Memory, img->allocation.Offset); + // VMA:创建 + 分配 + 绑定一步完成 + img->allocation = m_Allocator.CreateImage(imageInfo, + vk::MemoryPropertyFlagBits::eDeviceLocal); + img->image = img->allocation.Image; vk::ImageViewCreateInfo viewInfo; viewInfo.setImage(img->image) diff --git a/Engine/src/Renderer/Renderer.cpp b/Engine/src/Renderer/Renderer.cpp index f005b87..4cb12d1 100644 --- a/Engine/src/Renderer/Renderer.cpp +++ b/Engine/src/Renderer/Renderer.cpp @@ -367,7 +367,7 @@ namespace Engine .setWaitSemaphores(presentWait); try { - auto result = m_Device->GetPresentQueue().presentKHR(presentInfo); + const auto result = m_Device->GetPresentQueue().presentKHR(presentInfo); if (result == vk::Result::eSuboptimalKHR) { HandleResize(); } diff --git a/Engine/src/Vulkan/Buffer.cpp b/Engine/src/Vulkan/Buffer.cpp index 24c42d8..e188efa 100644 --- a/Engine/src/Vulkan/Buffer.cpp +++ b/Engine/src/Vulkan/Buffer.cpp @@ -18,31 +18,18 @@ namespace Engine { vk::MemoryPropertyFlags memProps) : m_Device(device), m_Allocator(allocator) , m_Size(size) { - vk::BufferCreateInfo bufferInfo; - bufferInfo.setSize(size) - .setUsage(usage) - .setSharingMode(vk::SharingMode::eExclusive); - - m_Buffer = m_Device.GetDevice().createBuffer(bufferInfo); - - auto reqs = m_Device.GetDevice().getBufferMemoryRequirements(m_Buffer); - auto alloc = allocator.Allocate(reqs, memProps); - - m_Memory = alloc.Memory; - m_Offset = alloc.Offset; - m_MappedPtr = alloc.MappedPtr; - - m_Device.GetDevice().bindBufferMemory(m_Buffer, m_Memory, m_Offset); - - m_Allocation = alloc; // 成员变量,保存起来 + // VMA 惯用法:创建 + 分配 + 绑定一步完成; + // HOST_VISIBLE 时持久映射,MappedPtr 直接可用 + m_Allocation = allocator.CreateBuffer(size, usage, memProps); + m_Buffer = m_Allocation.Buffer; + m_MappedPtr = m_Allocation.MappedPtr; STATS.AddBuffer(m_Size); } Buffer::~Buffer() { - m_Device.GetDevice().destroyBuffer(m_Buffer); - m_Allocator.Free(m_Allocation); + m_Allocator.Destroy(m_Allocation); STATS.RemoveBuffer(m_Size); } @@ -54,12 +41,7 @@ namespace Engine { } memcpy(static_cast(m_MappedPtr) + offset, data, size); - // flush 整个内存块,避免对齐问题 - vk::MappedMemoryRange range; - range.setMemory(m_Memory) - .setOffset(0) - .setSize(VK_WHOLE_SIZE); - - m_Device.GetDevice().flushMappedMemoryRanges(range); + // 非 coherent 内存写后冲刷(coherent 时 VMA 内部自动跳过) + m_Allocator.Flush(m_Allocation); } -} // namespace Engine \ No newline at end of file +} // namespace Engine diff --git a/Engine/src/Vulkan/MemoryAllocator.cpp b/Engine/src/Vulkan/MemoryAllocator.cpp index 0d80d7a..02f13a7 100644 --- a/Engine/src/Vulkan/MemoryAllocator.cpp +++ b/Engine/src/Vulkan/MemoryAllocator.cpp @@ -6,220 +6,147 @@ #include #include -#include +#include +#include #include "Engine/Core/Stats.h" namespace Engine { -MemoryAllocator::MemoryAllocator(Device& device) - : m_Device(device) -{ - ENGINE_INFO("MemoryAllocator created"); -} - -MemoryAllocator::~MemoryAllocator() -{ - const auto& vkDevice = m_Device.GetDevice(); - - for (auto& blocks : m_Blocks) { - for (auto& block : blocks) { - if (block.IsMapped) { - vkDevice.unmapMemory(block.Memory); - } - vkDevice.freeMemory(block.Memory); - - STATS.RemoveMemoryBlock(block.Size); - } - blocks.clear(); - } -} - -vk::DeviceSize MemoryAllocator::GetBlockSize(vk::MemoryPropertyFlags props) const -{ - if (props & vk::MemoryPropertyFlagBits::eHostVisible) { - return 16ull * 1024 * 1024; // 16 MB - } - return 64ull * 1024 * 1024; // 64 MB -} - -MemoryAllocator::Allocation MemoryAllocator::Allocate( - vk::MemoryRequirements reqs, vk::MemoryPropertyFlags props) -{ - u32 typeIndex = m_Device.FindMemoryType(reqs.memoryTypeBits, props); - auto& blocks = m_Blocks[typeIndex]; - - // 1. 扫描已有块的 FreeList,找能放下的区域(first-fit) - for (u32 i = 0; i < blocks.size(); ++i) { - auto& block = blocks[i]; - for (auto it = block.FreeList.begin(); it != block.FreeList.end(); ++it) { - vk::DeviceSize alignedOffset = - (it->Offset + reqs.alignment - 1) & ~(reqs.alignment - 1); - vk::DeviceSize end = alignedOffset + reqs.size; - - if (end <= it->Offset + it->Size) { - // 找到了 - Allocation alloc; - alloc.Memory = block.Memory; - alloc.Offset = alignedOffset; - alloc.Size = reqs.size; - alloc.MappedPtr = block.IsMapped - ? static_cast(block.MappedPtr) + alignedOffset - : nullptr; - alloc.BlockIndex = i; - alloc.TypeIndex = typeIndex; - alloc.Valid = true; - - // 更新 FreeList - if (alignedOffset == it->Offset && reqs.size == it->Size) { - // 整个区域被用了,移除 - block.FreeList.erase(it); - } else if (alignedOffset == it->Offset) { - // 从头部切 - it->Offset += reqs.size; - it->Size -= reqs.size; - } else if (end == it->Offset + it->Size) { - // 从尾部切 - it->Size = alignedOffset - it->Offset; - } else { - // 从中间切,拆成两段 - FreeRange tail; - tail.Offset = end; - tail.Size = (it->Offset + it->Size) - end; - it->Size = alignedOffset - it->Offset; - block.FreeList.insert(it + 1, tail); - } - - return alloc; + namespace + { + // props → VMA 创建参数: + // HOST_VISIBLE → AUTO + 持久映射 + 顺序写访问(VMA 自动挑 host 内存类型) + // 其余(DeviceLocal)→ AUTO_PREFER_DEVICE(优先显存,允许溢出到 BAR) + VmaAllocationCreateInfo MakeCreateInfo(vk::MemoryPropertyFlags props) + { + VmaAllocationCreateInfo ci{}; + if (props & vk::MemoryPropertyFlagBits::eHostVisible) { + ci.usage = VMA_MEMORY_USAGE_AUTO; + ci.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT + | VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + } else { + ci.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; } + // requiredFlags 兜底:调用方明确要求的属性 VMA 一定满足 + ci.requiredFlags = static_cast(props); + return ci; } } - // 2. 在已有块的尾部 bump 分配 - for (u32 i = 0; i < blocks.size(); ++i) { - auto& block = blocks[i]; - vk::DeviceSize alignedOffset = - (block.Used + reqs.alignment - 1) & ~(reqs.alignment - 1); + MemoryAllocator::MemoryAllocator(Device& device) + : m_Device(device) + { + VmaAllocatorCreateInfo ci{}; + ci.instance = static_cast(device.GetInstance()); + ci.physicalDevice = static_cast(device.GetPhysicalDevice()); + ci.device = static_cast(device.GetDevice()); + // 与 Device 创建实例时的 API 版本一致(启用 1.1+ 核心化的 + // dedicated allocation / memory requirements 控制等路径) + ci.vulkanApiVersion = VK_API_VERSION_1_4; + // VMA_STATIC_VULKAN_FUNCTIONS=1:直接用链接的 Vulkan loader 函数表 - if (alignedOffset + reqs.size <= block.Size) { - Allocation alloc; - alloc.Memory = block.Memory; - alloc.Offset = alignedOffset; - alloc.Size = reqs.size; - alloc.MappedPtr = block.IsMapped - ? static_cast(block.MappedPtr) + alignedOffset - : nullptr; - alloc.BlockIndex = i; - alloc.TypeIndex = typeIndex; - alloc.Valid = true; + if (vmaCreateAllocator(&ci, &m_Vma) != VK_SUCCESS) { + throw std::runtime_error("MemoryAllocator: vmaCreateAllocator failed"); + } - block.Used = alignedOffset + reqs.size; - return alloc; + ENGINE_INFO("MemoryAllocator created (VMA backend)"); + } + + MemoryAllocator::~MemoryAllocator() + { + if (m_Vma) { + vmaDestroyAllocator(m_Vma); } } - // 3. 创建新块 - vk::DeviceSize blockSize = std::max(GetBlockSize(props), reqs.size); + MemoryAllocator::Allocation MemoryAllocator::CreateBuffer( + vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags props) + { + VkBufferCreateInfo bi{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; + bi.size = size; + bi.usage = static_cast(usage); + bi.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - vk::MemoryAllocateInfo allocInfo; - allocInfo.setAllocationSize(blockSize) - .setMemoryTypeIndex(typeIndex); + VmaAllocationCreateInfo aci = MakeCreateInfo(props); - Block block; - block.Memory = m_Device.GetDevice().allocateMemory(allocInfo); - block.Size = blockSize; - block.Used = 0; - - if (props & vk::MemoryPropertyFlagBits::eHostVisible) { - block.MappedPtr = m_Device.GetDevice().mapMemory( - block.Memory, 0, VK_WHOLE_SIZE); - block.IsMapped = true; - } - - blocks.push_back(std::move(block)); - m_TotalAllocated[typeIndex] += blockSize; - - u32 newIndex = static_cast(blocks.size() - 1); - auto& newBlock = blocks[newIndex]; - - vk::DeviceSize alignedOffset = (reqs.alignment - 1) & ~(reqs.alignment - 1); - - Allocation alloc; - alloc.Memory = newBlock.Memory; - alloc.Offset = alignedOffset; - alloc.Size = reqs.size; - alloc.MappedPtr = newBlock.IsMapped - ? static_cast(newBlock.MappedPtr) + alignedOffset - : nullptr; - alloc.BlockIndex = newIndex; - alloc.TypeIndex = typeIndex; - alloc.Valid = true; - - newBlock.Used = alignedOffset + reqs.size; - - ENGINE_INFO("MemoryAllocator: new block of {} bytes (type {})", - blockSize, typeIndex); - - STATS.AddMemoryBlock(blockSize); - - STATS.AddMemoryUsed(reqs.size); - return alloc; -} - -void MemoryAllocator::Free(const Allocation& alloc) -{ - if (!alloc.Valid) return; - STATS.RemoveMemoryUsed(alloc.Size); - - auto& block = m_Blocks[alloc.TypeIndex][alloc.BlockIndex]; - - FreeRange range; - range.Offset = alloc.Offset; - range.Size = alloc.Size; - - // 插入 FreeList,保持按 offset 排序,方便合并 - auto it = std::lower_bound( - block.FreeList.begin(), block.FreeList.end(), range, - [](const FreeRange& a, const FreeRange& b) { - return a.Offset < b.Offset; - }); - - it = block.FreeList.insert(it, range); - - // 向前合并 - if (it != block.FreeList.begin()) { - auto prev = it - 1; - if (prev->Offset + prev->Size == it->Offset) { - prev->Size += it->Size; - block.FreeList.erase(it); - it = prev; + VkBuffer buffer = nullptr; + VmaAllocation handle = nullptr; + if (vmaCreateBuffer(m_Vma, &bi, &aci, &buffer, &handle, nullptr) != VK_SUCCESS) { + ENGINE_ERROR("MemoryAllocator: CreateBuffer({} bytes) failed", size); + return {}; } + + Allocation out; + out.Buffer = buffer; + out.Size = size; + out.Handle = handle; + out.Valid = true; + + VmaAllocationInfo info{}; + vmaGetAllocationInfo(m_Vma, handle, &info); + out.MappedPtr = info.pMappedData; // 非 MAPPED 标记时为 null + + STATS.AddMemoryUsed(size); + return out; } - // 向后合并 - auto next = it + 1; - if (next != block.FreeList.end()) { - if (it->Offset + it->Size == next->Offset) { - it->Size += next->Size; - block.FreeList.erase(next); + MemoryAllocator::Allocation MemoryAllocator::CreateImage( + const vk::ImageCreateInfo& imageInfo, vk::MemoryPropertyFlags props) + { + VkImageCreateInfo ii = imageInfo; // 同布局,隐式转换 + + VmaAllocationCreateInfo aci = MakeCreateInfo(props); + + VkImage image = nullptr; + VmaAllocation handle = nullptr; + if (vmaCreateImage(m_Vma, &ii, &aci, &image, &handle, nullptr) != VK_SUCCESS) { + ENGINE_ERROR("MemoryAllocator: CreateImage({}x{}) failed", + imageInfo.extent.width, imageInfo.extent.height); + return {}; } + + Allocation out; + out.Image = image; + out.Size = ii.extent.depth * ii.extent.height * ii.extent.width; // 近似值(统计用) + out.Handle = handle; + out.Valid = true; + + STATS.AddMemoryUsed(out.Size); + return out; } -} -void MemoryAllocator::PrintStats() const -{ - for (u32 type = 0; type < VK_MAX_MEMORY_TYPES; ++type) { - if (m_Blocks[type].empty()) continue; + void MemoryAllocator::Destroy(Allocation& alloc) + { + if (!alloc.Valid) return; - vk::DeviceSize totalUsed = 0; - for (auto& b : m_Blocks[type]) totalUsed += b.Used; + // VMA 要求资源与分配成对销毁,顺序不限但句柄必须配对 + if (alloc.Buffer) { + vmaDestroyBuffer(m_Vma, alloc.Buffer, alloc.Handle); + } else if (alloc.Image) { + vmaDestroyImage(m_Vma, alloc.Image, alloc.Handle); + } - ENGINE_INFO(" Type {}: {} blocks, total allocated {} MB, used {} MB", - type, - m_Blocks[type].size(), - m_TotalAllocated[type] / (1024 * 1024), - totalUsed / (1024 * 1024)); + STATS.RemoveMemoryUsed(alloc.Size); + alloc = {}; } -} -} // namespace Engine \ No newline at end of file + void MemoryAllocator::Flush(const Allocation& alloc) + { + if (!alloc.Valid || !alloc.Handle) return; + // HOST_COHERENT 时 VMA 内部自动跳过 + vmaFlushAllocation(m_Vma, alloc.Handle, 0, VK_WHOLE_SIZE); + } + + void MemoryAllocator::PrintStats() const + { + VmaTotalStatistics stats; + vmaCalculateStatistics(m_Vma, &stats); + ENGINE_INFO(" VMA: {} allocations in {} blocks, total {} MB (used {} MB)", + stats.total.statistics.allocationCount, + stats.total.statistics.blockCount, + stats.total.statistics.blockBytes / (1024 * 1024), + stats.total.statistics.allocationBytes / (1024 * 1024)); + } + +} // namespace Engine diff --git a/Engine/src/Vulkan/Texture.cpp b/Engine/src/Vulkan/Texture.cpp index 308a27a..b7ce63f 100644 --- a/Engine/src/Vulkan/Texture.cpp +++ b/Engine/src/Vulkan/Texture.cpp @@ -56,22 +56,13 @@ namespace Engine vk::DeviceSize imageSize = static_cast(width) * static_cast(height) * 4; // RGBA8 - // 2. 创建 Staging Buffer(HOST_VISIBLE) - vk::BufferCreateInfo stagingInfo; - stagingInfo.setSize(imageSize) - .setUsage(vk::BufferUsageFlagBits::eTransferSrc) - .setSharingMode(vk::SharingMode::eExclusive); - - vk::Buffer stagingBuffer = m_Device.GetDevice().createBuffer(stagingInfo); - - auto stagingReqs = m_Device.GetDevice().getBufferMemoryRequirements(stagingBuffer); - auto stagingAlloc = m_Allocator.Allocate( - stagingReqs, + // 2. 创建 Staging Buffer(HOST_VISIBLE,VMA 持久映射) + auto stagingAlloc = m_Allocator.CreateBuffer( + imageSize, + vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent); - m_Device.GetDevice().bindBufferMemory(stagingBuffer, stagingAlloc.Memory, stagingAlloc.Offset); - // 3. 拷贝像素到 Staging Buffer std::memcpy(stagingAlloc.MappedPtr, pixels, static_cast(imageSize)); @@ -88,15 +79,9 @@ namespace Engine .setSharingMode(vk::SharingMode::eExclusive) .setInitialLayout(vk::ImageLayout::eUndefined); - m_Image = m_Device.GetDevice().createImage(imageInfo); - - auto imageReqs = m_Device.GetDevice().getImageMemoryRequirements(m_Image); - auto imageAlloc = m_Allocator.Allocate(imageReqs, vk::MemoryPropertyFlagBits::eDeviceLocal); - - m_ImageAllocation = imageAlloc; - - m_Memory = imageAlloc.Memory; - m_Device.GetDevice().bindImageMemory(m_Image, m_Memory, imageAlloc.Offset); + m_ImageAllocation = m_Allocator.CreateImage(imageInfo, + vk::MemoryPropertyFlagBits::eDeviceLocal); + m_Image = m_ImageAllocation.Image; // 5. 创建 ImageView vk::ImageViewCreateInfo viewInfo; @@ -168,7 +153,7 @@ namespace Engine .setImageOffset({0, 0, 0}) .setImageExtent({m_Width, m_Height, 1}); - cmd.copyBufferToImage(stagingBuffer, m_Image, + cmd.copyBufferToImage(stagingAlloc.Buffer, m_Image, vk::ImageLayout::eTransferDstOptimal, region); // 6.3 布局:TRANSFER_DST → SHADER_READ_ONLY @@ -202,12 +187,10 @@ namespace Engine ENGINE_ERROR("Texture upload: waitForFences failed"); } - // 8. 清理临时对象 + // 8. 清理临时对象(staging 资源与分配经 VMA 成对释放) m_Device.GetDevice().destroyFence(fence); m_Device.GetDevice().destroyCommandPool(pool); - m_Device.GetDevice().destroyBuffer(stagingBuffer); - m_Allocator.Free(stagingAlloc); - // staging 内存由 MemoryAllocator 统一释放 + m_Allocator.Destroy(stagingAlloc); // 显存占用 = 宽 * 高 * 4 字节(RGBA8) m_TextureBytes = static_cast(m_Width) * m_Height * 4; @@ -220,10 +203,9 @@ namespace Engine vkDevice.destroySampler(m_Sampler); vkDevice.destroyImageView(m_View); - vkDevice.destroyImage(m_Image); - // 归还 image 的显存 - m_Allocator.Free(m_ImageAllocation); + // image 资源与显存经 VMA 成对释放 + m_Allocator.Destroy(m_ImageAllocation); STATS.RemoveTexture(m_TextureBytes); } diff --git a/Engine/src/Vulkan/VmaImpl.cpp b/Engine/src/Vulkan/VmaImpl.cpp new file mode 100644 index 0000000..afa2e32 --- /dev/null +++ b/Engine/src/Vulkan/VmaImpl.cpp @@ -0,0 +1,12 @@ +// +// Created by atdunbg on 2026/9/23. +// +// VMA 实现编译单元:整个引擎唯一一份 vk_mem_alloc 实现。 +// +// 静态链接 Vulkan loader(项目已链接 Vulkan::Vulkan),与 vulkan-hpp 的 +// dynamic loader 互不干扰(各自持有函数表)。 +// + +#define VMA_STATIC_VULKAN_FUNCTIONS 1 +#define VMA_IMPLEMENTATION +#include diff --git a/Lib/VulkanMemoryAllocator b/Lib/VulkanMemoryAllocator new file mode 160000 index 0000000..3aa9212 --- /dev/null +++ b/Lib/VulkanMemoryAllocator @@ -0,0 +1 @@ +Subproject commit 3aa921224c154a0d2c43912bc88e1c42ce1f7607