feat: 显存分配器迁移到 VMA(P1 路线第一项)
- 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 秒冒烟通过
This commit is contained in:
@@ -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
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 <vulkan/vulkan.hpp>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#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<FreeRange> FreeList; // 已释放的区域
|
||||
};
|
||||
|
||||
Device& m_Device;
|
||||
std::array<std::vector<Block>, VK_MAX_MEMORY_TYPES> m_Blocks;
|
||||
std::array<vk::DeviceSize, VK_MAX_MEMORY_TYPES> m_TotalAllocated{};
|
||||
|
||||
vk::DeviceSize GetBlockSize(vk::MemoryPropertyFlags props) const;
|
||||
VmaAllocator_T* m_Vma = nullptr;
|
||||
};
|
||||
|
||||
} // namespace Engine
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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<char*>(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
|
||||
} // namespace Engine
|
||||
|
||||
@@ -6,220 +6,147 @@
|
||||
#include <Engine/Vulkan/Device.h>
|
||||
#include <Engine/Core/Log.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <vk_mem_alloc.h>
|
||||
#include <stdexcept>
|
||||
|
||||
#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<char*>(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<VkMemoryPropertyFlags>(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<VkInstance>(device.GetInstance());
|
||||
ci.physicalDevice = static_cast<VkPhysicalDevice>(device.GetPhysicalDevice());
|
||||
ci.device = static_cast<VkDevice>(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<char*>(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<VkBufferUsageFlags>(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<u32>(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<char*>(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
|
||||
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
|
||||
|
||||
@@ -56,22 +56,13 @@ namespace Engine
|
||||
vk::DeviceSize imageSize = static_cast<vk::DeviceSize>(width) *
|
||||
static_cast<vk::DeviceSize>(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<size_t>(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<u64>(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);
|
||||
}
|
||||
|
||||
@@ -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 <vk_mem_alloc.h>
|
||||
Reference in New Issue
Block a user