PCSX2 中的 Vulkan Memory Allocator(VMA):从 vk_mem_alloc 头文件到 GS 渲染器的版本演进与实战解读
【免费下载链接】pcsx2PCSX2 - The Playstation 2 Emulator项目地址: https://gitcode.com/GitHub_Trending/pc/pcsx2
Vulkan 相比 D3D11/OpenGL 在内存管理上复杂得多:VkDeviceMemory必须与VkBuffer/VkImage分开分配再手动绑定,驱动能力需要查询、不同厂商的内存类型各不相同,且系统对内存块数量存在硬性上限。Vulkan Memory Allocator(VMA)正是为解决这些问题而生的单头文件 C++ 库,也是 PCSX2 的 Vulkan 图形后端(GS)管理显存的核心依赖。本文以仓库中 3rdparty/vulkan/include/CHANGELOG.md 为骨架,结合 vk_mem_alloc.h 头文件与 pcsx2/GS/Renderers/Vulkan 目录下的真实调用代码,梳理 VMA 从 1.0.0 到 3.4.0 的版本演进、关键 API 变迁,以及 PCSX2 在后端中如何配置和使用它。读完本文,你将掌握 VMA 的核心概念(分配器、分配对象、内存类型选择、自定义池、线性/TLSF 算法、碎片整理、虚拟分配器)、主要宏与配置项的含义,以及一套可直接参考的工程级接入范例。
仓库中的 VMA:位置与角色
在 PCSX2 仓库中,VMA 位于3rdparty/vulkan目录下,其结构为:
- 3rdparty/vulkan/include/vk_mem_alloc.h —— 库的唯一头文件(同时包含声明与实现,需以
VMA_IMPLEMENTATION宏编译一次实现单元); - 3rdparty/vulkan/include/CHANGELOG.md —— 官方变更日志(本文主题文档);
- 3rdparty/vulkan/include/README.md —— 功能特性与快速上手说明;
- 3rdparty/vulkan/CMakeLists.txt —— 以 INTERFACE 库方式导出头文件包含路径;
- 3rdparty/vulkan/include/LICENSE.txt 与 3rdparty/vulkan/LICENSE.md —— MIT 许可证。
从 CMakeLists.txt 可以看到,VMA 以INTERFACE库形式集成,仅导出include目录,本身不产生编译产物。头文件中当前版本宏为VMA_VERSION (VK_MAKE_VERSION(3, 4, 0)),对应 CHANGELOG 中最新的 3.4.0 版本。
PCSX2 的 Vulkan GS 渲染器通过 VKLoader.h 预定义宏后包含该头文件:
#define VMA_STATIC_VULKAN_FUNCTIONS 1 #define VMA_DYNAMIC_VULKAN_FUNCTIONS 0 #define VMA_STATS_STRING_ENABLED 0 #include "vk_mem_alloc.h"即:使用静态 Vulkan 函数绑定、关闭动态加载、并关闭统计字符串生成(对应 CHANGELOG 3.0.0 中VMA_STATS_STRING_ENABLED相关的配置体系)。实现单元则在 vk_mem_alloc.cpp 中通过#define VMA_IMPLEMENTATION 1单独编译一次。
VMA 是什么:设计动机与核心能力
在深入版本演进之前,先明确 VMA 解决的问题(依据 README.md):
- Vulkan 需要大量样板代码:
VkDeviceMemory与VkBuffer/VkImage分开创建后再绑定,多了一层间接性; - 必须向驱动查询受支持的内存堆(heap)与内存类型(memory type),不同 GPU 厂商差异巨大;
- 推荐的做法是分配更大的内存块,再把块内区间分配给具体资源,因为系统对内存块数量存在上限。
VMA 提供的核心能力:
- 根据内存预期用途自动选择正确且最优的内存类型(用比 Vulkan 标志更高层的语义描述内存的必需/偏好特征);
- 分配内存块、把块内未使用区间切分返回给用户(
VkDeviceMemory+ offset + size),内部追踪块内已用/未用区间、为每次分配寻找最佳匹配区间,并遵守对齐与 buffer/image 粒度规则; - 一条调用同时完成创建 buffer/image、分配内存并绑定;
- 附加能力:线程安全(内部对同一设备内存块并发访问做同步、映射引用计数)、自定义 CPU 内存分配器、非相干内存的 flush/invalidate(自动尊重
nonCoherentAtomSize)、资源别名、稀疏绑定便捷函数、自定义池、线性分配器、Vulkan 1.0~1.4 支持、GPU/CPU 碎片整理、统计信息、JSON dump、外部内存互操作、虚拟分配器等。
从 1.0 到 3.4:版本演进全景
1.0.0(2017-06-16)与 1.0.1(2017-07-04)
首次公开发布;1.0.1 修复 Linux GCC 编译问题,并把 "CONFIGURATION SECTION" 改为#ifndef包裹,允许用户在使用方定义配置宏而不是修改头文件本身。
2.0.0(2018-03-19):引入VmaAllocation句柄
这是首个大量兼容性破坏的大版本,引入的机制至今仍是 VMA 的核心:
- 引入
VmaAllocation句柄:分配函数必须返回它,释放时必须连同VkBuffer/VkImage一起传入; - 引入
VmaAllocationInfo结构体,从中读取VkDeviceMemory与 offset,而不再从分配函数直接取; - 支持引用计数的映射与持久映射分配(
vmaMapMemory、VMA_ALLOCATION_CREATE_MAPPED_BIT); - 支持自定义内存池(
VmaPool、VmaPoolCreateInfo、vmaCreatePool); - 支持碎片整理(
vmaDefragment及配套结构); - 支持 "lost allocations" 特性(该特性在 3.0.0 被整体移除)。
2.1.0(2018-08-27/09-10):线性分配算法与调试增强
- 新增线性分配算法,可用于自定义池,支持 free-at-once、栈、双栈、环形缓冲区四种形态(
VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT、VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT); - 新增调用序列录制与回放(record & replay,
VmaAllocatorCreateInfo::pRecordSettings、VmaReplay 应用,该特性在 3.0.0 被移除); - 非相干内存支持增强:
vmaFlushAllocation、vmaInvalidateAllocation,自动尊重nonCoherentAtomSize; - 调试能力:
VMA_DEBUG_DETECT_CORRUPTION、vmaCheckCorruption、vmaCheckPoolCorruption、VMA_DEBUG_INITIALIZE_ALLOCATIONS、VMA_DEBUG_MARGIN(现在块首尾都会加边距)。
2.2.0(2018-12-13):GPU 碎片整理与 buddy 算法
- 更强大的碎片整理:新增
VmaDefragmentationInfo2、vmaDefragmentationBegin/vmaDefragmentationEnd,支持 GPU 内存整理;CPU 内存整理改用memmove可移动重叠区域,且对HOST_VISIBLE但非HOST_COHERENT的类型可用;需要VmaVulkanFunctions::vkCmdCopyBuffer; - 自定义池可用 buddy 算法(
VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT,该标志在 3.0.0 被移除); - 为稀疏绑定新增批量分配/释放:
vmaAllocateMemoryPages、vmaFreeMemoryPages; - 新增
vmaResizeAllocation(原地扩容尝试,2.3.0 起废弃,3.0.0 移除); - 分配策略标志:BEST_FIT / WORST_FIT / FIRST_FIT 及其别名;
- 分配大小为 0 的 buffer/image 返回
VK_ERROR_VALIDATION_FAILED_EXT。
2.3.0(2019-12-04):Vulkan 1.1 与内存预算
- 支持 Vulkan 1.1(新增
VmaAllocatorCreateInfo::vulkanApiVersion;使用 1.1 时无需再启用VK_KHR_dedicated_allocation、VK_KHR_bind_memory2); - 内存预算查询:
vmaGetBudget、VmaBudget(比完整统计更高效);配合VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT与VmaAllocatorCreateInfo::instance可从系统查询真实预算,否则基于堆大小估算;VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT使超出预算时分配失败; - 新内存用途:
VMA_MEMORY_USAGE_CPU_COPY、VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED; VK_KHR_bind_memory2支持:VMA_ALLOCATION_CREATE_DONT_BIND_BIT、VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT、vmaBindBufferMemory2、vmaBindImageMemory2(可指定局部偏移与 pNext);vmaSetPoolName/vmaGetPoolName为自定义池命名;- 碎片整理仅对
VK_IMAGE_TILING_LINEAR的 buffer/image 合法(受实现与 Vulkan 规范限制); - 内部优化:
VmaAllocation对象从内部自由链表分配器分配,使分配/释放平均产生 0 次动态 CPU 堆分配。
3.0.0(2022-03-25):API 现代化大版本
这是继 2.0.0 之后的又一次大规模变更,基础 API 保持向后兼容,但删除了一批旧特性:
新增(部分破坏兼容):
- 新的内存类型偏好 API:
VMA_MEMORY_USAGE_AUTO、VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE、VMA_MEMORY_USAGE_AUTO_PREFER_HOST,以及VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT、VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT、VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT;旧值如VMA_MEMORY_USAGE_GPU_ONLY仍可用但不再推荐; - 全新碎片整理 API:
VmaDefragmentationInfo、VmaDefragmentationMove、VmaDefragmentationPassMoveInfo、VmaDefragmentationStats,函数vmaBeginDefragmentation/vmaEndDefragmentation/vmaBeginDefragmentationPass/vmaEndDefragmentationPass; - 重新设计的统计 API:
VmaStatistics、VmaDetailedStatistics、VmaTotalStatistics、VmaBudget,函数vmaGetHeapBudgets、vmaCalculateStatistics、vmaGetPoolStatistics、vmaCalculatePoolStatistics、vmaGetVirtualBlockStatistics、vmaCalculateVirtualBlockStatistics; - 虚拟分配器:
vmaCreateVirtualBlock/vmaDestroyVirtualBlock等,把核心分配算法复用于任意自定义内存(如一块大 buffer 内的区间); VmaAllocation同时携带void* pUserData与char* pName,新增vmaSetAllocationName、VmaAllocationInfo::pName;VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT被废弃;- Vulkan 函数导入方式澄清:
VMA_STATIC_VULKAN_FUNCTIONS、VMA_DYNAMIC_VULKAN_FUNCTIONS、VmaVulkanFunctions,动态模式新增必填的vkGetInstanceProcAddr、vkGetDeviceProcAddr; - 自定义池新特性:支持专用分配,
VmaPoolCreateInfo::pMemoryAllocateNext、minAllocationAlignment; - 支持 Vulkan 1.2、1.3;扩展支持:
VK_KHR_buffer_device_address(VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT)、VK_EXT_memory_priority(priority成员)、VK_AMD_device_coherent_memory、VmaAllocatorCreateInfo::pTypeExternalMemoryHandleTypes; - 新函数:
vmaGetAllocatorInfo、vmaFlushAllocations/vmaInvalidateAllocations(批量)、VMA_ALLOCATION_CREATE_CAN_ALIAS_BIT、vmaCreateBufferWithAlignment、vmaGetAllocationMemoryProperties、vmaCreateAliasingBuffer/vmaCreateAliasingImage。
移除(破坏兼容):
- 整个 "lost allocations" 特性(
VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT、VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT、vmaCreateLostAllocation、vmaMakePoolAllocationsLost、vmaTouchAllocation、frameInUseCount成员); - 整个 record & replay 特性(
pRecordSettings、VmaRecordSettings等,VmaReplay 应用一并移除); - buddy 算法标志
VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT; - 策略标志别名(
STRATEGY_MIN_FRAGMENTATION_BIT、STRATEGY_WORST_FIT_BIT、VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_FRAGMENTATION_BIT); vmaResizeAllocation。
算法与工程变化:
- 默认分配算法改为 Two-Level Segregated Fit(TLSF),批量释放或
bufferImageGranularity较大时明显更快; - 调试宏
VMA_DEBUG_ALIGNMENT更名VMA_MIN_ALIGNMENT; - 弃用 Premake、全面改为 CMake;
vmaInvalidateAllocation/vmaFlushAllocation返回VkResult;- 为 Clang 增加空指针注解(
VMA_NULLABLE、VMA_NOT_NULL等); - JSON dump 格式变更。
3.1.0(2024-05-27):维护 4/5 与调试宏细化
- 便捷函数
vmaCopyMemoryToAllocation、vmaCopyAllocationToMemory; vmaCreateAliasingBuffer2/vmaCreateAliasingImage2:在已有分配内创建 buffer/image 并支持allocationLocalOffset;vmaGetAllocationInfo2与VmaAllocationInfo2:返回更多分配信息,便于与其他 API 互操作;- 碎片整理中断回调:
VmaDefragmentationInfo::pfnBreakCallback、PFN_vmaCheckDefragmentationBreakFunction、pBreakCallbackUserData,可打断长时间运行的vmaBeginDefragmentation; - 扩展支持:
VK_KHR_maintenance4(VMA_ALLOCATOR_CREATE_KHR_MAINTENANCE4_BIT)、VK_KHR_maintenance5(VMA_ALLOCATOR_CREATE_KHR_MAINTENANCE5_BIT); - 调试/配置宏拆分:
VMA_DEBUG_LOG与VMA_DEBUG_LOG_FORMAT分离(#297),新增VMA_ASSERT_LEAK、VMA_LEAK_LOG_FORMAT(#379、#385)、VMA_EXTENDS_VK_STRUCT(#347); - 修复无
HOST_CACHED内存类型的 GPU(如树莓派)上主内存类型选择算法(#362),以及部分编译器snprintf未声明的 include 问题(#312)。
3.2.0(2024-12-30)/ 3.2.1(2025-02-05):Vulkan 1.4 与外部内存
- 3.2.0:支持 Vulkan 1.4;支持
VK_KHR_external_memory_win32(VMA_ALLOCATOR_CREATE_KHR_EXTERNAL_MEMORY_WIN32_BIT、vmaGetMemoryWin32Handle及专门文档章节,#442);修复线程安全问题(#451);改进 Cmake 脚本与各类编译器/GPU 兼容性; - 3.2.1:修复
vmaCreateAllocator在 Vulkan 1.4 下断言错误失败(#457);修复启用VMA_DYNAMIC_VULKAN_FUNCTIONS时导入vkGetPhysicalDeviceMemoryProperties2/KHR的问题(#410)。
3.3.0(2025-05-12):volk 集成与默认值调整
- 新增
vmaImportVulkanFunctionsFromVolk,便于配合 volk 库加载 Vulkan 函数指针; - 新增宏
VMA_DEBUG_DONT_EXCEED_HEAP_SIZE_WITH_ALLOCATION_SIZE(默认值 1); VMA_DEBUG_DONT_EXCEED_MAX_MEMORY_ALLOCATION_COUNT默认值由 0 改为 1;- 新增 "Frequently asked questions" 文档章节。
3.4.0(2026-??-??,当前仓库所携带版本)
- 新增
VmaAllocationCreateInfo::minAlignment(#523):- 官方特别提醒:结构体务必以零初始化,不要依赖特定
sizeof以保证向后兼容; vmaCreateBufferWithAlignment被标记为废弃(其能力可由minAlignment取代);
- 官方特别提醒:结构体务必以零初始化,不要依赖特定
- 外部内存导出/导入改进(#503):新增
vmaCreateDedicatedBuffer、vmaCreateDedicatedImage、vmaAllocateDedicatedMemory,均带额外参数void* pMemoryAllocateNext;新增vmaGetMemoryWin32Handle2,带参数VkExternalMemoryHandleTypeFlagBits handleType; - 新增
VMA_VERSION宏携带库版本号(#507),当前值VK_MAKE_VERSION(3, 4, 0); - 改进
VMA_MEMORY_USAGE_AUTO*的内存类型选择算法(#520); - 修复 Clang 21 与 GCC15 下 C++20 modules 兼容性(#513、#514);
- 其他修复与平台/编译器兼容性改进。
版本演进中的关键技术主题
内存类型选择:从显式标志到 AUTO 语义
VMA 把 Vulkan 的底层内存属性抽象为更高层的"用途"描述。3.0.0 之前,VMA_MEMORY_USAGE_GPU_ONLY/CPU_ONLY/CPU_TO_GPU/GPU_TO_CPU等枚举直接驱动类型选择,且部分枚举保证特定属性(如HOST_VISIBLE、HOST_COHERENT)。2.3.0 的说明明确指出:内部类型选择算法调整后,用户不能再假定非显式保证的属性一定存在。3.0.0 引入的VMA_MEMORY_USAGE_AUTO*系列则把"必需/偏好"语义分离:用VMA_ALLOCATION_CREATE_HOST_ACCESS_*标志描述 CPU 访问需求,由库在可用内存类型中自动挑选最佳者;旧枚举保留兼容但不再推荐。3.4.0 又进一步改进 AUTO 系列的选择算法(#520)。
分配算法:线性、buddy 与 TLSF
- 线性算法(2.1.0)面向"一次性释放全部/栈式/环形"的短生命周期场景,分配成本极低;
- buddy 算法(2.2.0)面向大块内存的快速拆分与合并,但 3.0.0 因维护成本与适用性被移除;
- TLSF(3.0.0 起为默认)兼顾分配速度与碎片率,尤其在批量释放或
bufferImageGranularity较大时优势明显。
碎片整理的三代演进
第一代(2.0.0)vmaDefragment仅支持 CPU 内存;第二代(2.2.0)vmaDefragmentationBegin/End引入分阶段(pass)处理并支持 GPU 内存(通过vkCmdCopyBuffer),同时明确仅VK_IMAGE_TILING_LINEAR合法;第三代(3.0.0)以vmaBeginDefragmentation+ pass 循环的形态取代旧接口,3.1.0 再增加pfnBreakCallback让长时间整理可被安全打断。
外部内存互操作
从 2.3.0 起支持外部内存类型;3.2.0 增加 Windows 平台VK_KHR_external_memory_win32的句柄获取(vmaGetMemoryWin32Handle);3.4.0 提供vmaGetMemoryWin32Handle2指定句柄类型,并新增vmaCreateDedicatedBuffer/Image、vmaAllocateDedicatedMemory携带pMemoryAllocateNext的专用分配入口,用于跨 API(如与 OpenGL/D3D12 共享)场景。
实战:PCSX2 如何接入与使用 VMA
1. 分配器创建与扩展启用
PCSX2 在 GSDeviceVK::CreateAllocator 中构建VmaAllocatorCreateInfo:
VmaAllocatorCreateInfo ci = {}; ci.vulkanApiVersion = VK_API_VERSION_1_1; // 目标 Vulkan 版本 ci.flags = VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT; // 由外部(单线程)同步 ci.physicalDevice = m_physical_device; ci.device = m_device; ci.instance = m_instance; if (m_optional_extensions.vk_ext_memory_budget) ci.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT; // 真实内存预算查询这段代码印证了 2.3.0 引入的vulkanApiVersion与VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT的用法。它还展示了pHeapSizeLimit的工程用法:在 Debug 设备(如 RenderDoc 回放)下,PCSX2 会把小于 512MB 的 DEVICE_LOCAL|HOST_VISIBLE "上传堆" 上限设为 0,避免 NVIDIA 驱动频繁耗尽显存导致回放困难。分配器句柄保存在m_allocator(见 GSDeviceVK.h),并通过GetAllocator()提供给各资源类。
注意:PCSX2 使用VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT,意味着它承诺在外部自行串行化对分配器的访问,VMA 内部不再加锁。
2. 纹理(Image)分配:GPU_ONLY + 预算控制 + 专用内存回退
GSTextureVK.cpp 展示了纹理分配的核心模式:
aci.usage = VMA_MEMORY_USAGE_GPU_ONLY; aci.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT; // 大纹理优先尝试专用内存 aci.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; VkResult res = vmaCreateImage(allocator, &ici, &aci, &image, &allocation, nullptr); if (aci.flags & VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT && res != VK_SUCCESS) { aci.flags &= ~VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; // 回退:取消专用内存再试 res = vmaCreateImage(allocator, &ici, &aci, &image, &allocation, nullptr); }要点:VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT(2.3.0 引入)使分配在超出预算时返回失败而不是压低系统;VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT对需要专用分配的图像先尝试专用块,失败后回退到常规子分配。
3. 可映射纹理与 staging:MAPPED + CPU_TO_GPU / GPU_TO_CPU
同文件 GSTextureVK.cpp 中可映射纹理使用:
aci.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; aci.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;回读路径(GSTextureVK.cpp)则使用VMA_MEMORY_USAGE_GPU_TO_CPU+VMA_ALLOCATION_CREATE_MAPPED_BIT。MAPPED_BIT(2.0.0 引入)让分配返回后即为持久映射,无需显式vmaMapMemory。在 GSTextureVK.cpp 中,回读数据通过vmaInvalidateAllocation使 CPU 侧缓存失效(针对非相干内存),上传侧则通过vmaFlushAllocation刷新写缓冲(GSTextureVK.cpp)——这两类函数正是 2.1.0 引入、3.0.0 起返回VkResult的接口。
4. 流式上传缓冲:持久映射 + HOST_COHERENT 偏好
VKStreamBuffer.cpp 演示了高性能动态缓冲的经典配方:
VmaAllocationCreateInfo aci = {}; aci.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT; aci.usage = VMA_MEMORY_USAGE_CPU_TO_GPU; aci.preferredFlags = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; // 偏好相干内存,省去手动 flush vmaCreateBuffer(allocator, &bci, &aci, &new_buffer, &new_allocation, &ai); m_host_pointer = static_cast<u8*>(ai.pMappedData); // 直接取映射指针preferredFlags让库在满足必需条件的前提下优先选择HOST_COHERENT类型,从而在每帧大量写入时免去逐次vmaFlushAllocation。释放时(VKStreamBuffer.cpp)调用vmaDestroyBuffer,且为避免 GPU 仍在读取时释放,PCSX2 提供DeferBufferDestruction机制延迟销毁。
5. 预初始化 GPU 缓冲:staging 拷贝的完整流程
GSDeviceVK::AllocatePreinitializedGPUBuffer 展示了完整的"CPU 写 → flush → 拷贝到 GPU"链路:
// 1) CPU staging 缓冲:MAPPED + CPU_ONLY const VmaAllocationCreateInfo cpu_aci = {VMA_ALLOCATION_CREATE_MAPPED_BIT, VMA_MEMORY_USAGE_CPU_ONLY, 0, 0}; vmaCreateBuffer(m_allocator, &cpu_bci, &cpu_aci, &cpu_buffer, &cpu_allocation, &cpu_ai); // 2) GPU 常驻缓冲:GPU_ONLY + 偏好 DEVICE_LOCAL const VmaAllocationCreateInfo gpu_aci = {0, VMA_MEMORY_USAGE_GPU_ONLY, 0, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT}; vmaCreateBuffer(m_allocator, &gpu_bci, &gpu_aci, gpu_buffer, gpu_allocation, &ai); // 3) 填充 + 刷新 + 拷贝 fill_callback(cpu_ai.pMappedData); vmaFlushAllocation(m_allocator, cpu_allocation, 0, size); vkCmdCopyBuffer(GetCurrentInitCommandBuffer(), cpu_buffer, *gpu_buffer, 1, &buf_copy); DeferBufferDestruction(cpu_buffer, cpu_allocation);这里的VmaAllocationCreateInfo初始化顺序为{flags, usage, requiredFlags, preferredFlags}(见 GSDeviceVK.cpp 与 GSDeviceVK.cpp),四个字段分别对应flags、usage、requiredFlags、preferredFlags。
集成与配置要点小结
| 主题 | 关键 API / 宏 | 引入版本 |
|---|---|---|
| 分配器创建 | vmaCreateAllocator、VmaAllocatorCreateInfo | 1.0.0 |
| 分配对象与信息 | VmaAllocation、VmaAllocationInfo、vmaGetAllocationInfo2 | 2.0.0 / 3.1.0 |
| 内存用途语义 | VMA_MEMORY_USAGE_AUTO*+HOST_ACCESS_* | 3.0.0 |
| 持久映射 | VMA_ALLOCATION_CREATE_MAPPED_BIT | 2.0.0 |
| 预算控制 | VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT、WITHIN_BUDGET_BIT、vmaGetBudget | 2.3.0 |
| 自定义池 | VmaPool、vmaCreatePool、线性算法 | 2.0.0 / 2.1.0 |
| 默认算法 | TLSF | 3.0.0 |
| 碎片整理 | vmaBeginDefragmentation+ pass 循环、pfnBreakCallback | 3.0.0 / 3.1.0 |
| 虚拟分配器 | vmaCreateVirtualBlock系列 | 3.0.0 |
| 外部内存 | VMA_ALLOCATOR_CREATE_KHR_EXTERNAL_MEMORY_WIN32_BIT、vmaGetMemoryWin32Handle2 | 3.2.0 / 3.4.0 |
| 版本宏 | VMA_VERSION | 3.4.0 |
| 对齐控制 | minAlignment(vmaCreateBufferWithAlignment已废弃) | 3.4.0 |
工程启示:从 CHANGELOG 读出的接入建议
- 结构体零初始化:3.4.0 的官方提醒(新增成员不破坏二进制兼容的前提是调用方
= {}初始化)对任何引入新版本 VMA 的项目都适用——PCSX2 代码中所有VmaAllocationCreateInfo aci = {};正是这一实践; - 优先使用 AUTO 语义:新代码应使用
VMA_MEMORY_USAGE_AUTO*与HOST_ACCESS_*标志组合,而不是硬编码旧枚举; - 充分利用调试宏:
VMA_DEBUG_DETECT_CORRUPTION、VMA_DEBUG_INITIALIZE_ALLOCATIONS、VMA_DEBUG_MARGIN、VMA_DEBUG_LOG_FORMAT、VMA_ASSERT_LEAK可帮助排查越界写、未初始化内存与泄漏; - 碎片整理注意边界:GPU 碎片整理仅适用于
VK_IMAGE_TILING_LINEAR资源,且应通过pfnBreakCallback避免单帧卡顿; - 映射内存注意缓存一致性:非相干内存必须配对使用
vmaFlushAllocation/vmaInvalidateAllocation(或利用HOST_COHERENT偏好规避)。
结语
VMA 从 1.0.0 到 3.4.0 的演进,本质上是一部"Vulkan 内存管理最佳实践"的浓缩史:从最基本的分配/绑定封装,到线性/TLSF 算法、GPU 碎片整理、预算感知、外部内存互操作,再到AUTO语义与minAlignment的现代化 API。PCSX2 作为长期使用 Vulkan 后端的开源项目,其 GSDeviceVK.cpp、GSTextureVK.cpp、VKStreamBuffer.cpp 中的每一处调用,都是对上述演进结论的工程验证。对于想要在自己的 Vulkan 引擎中用好 VMA 的开发者,vk_mem_alloc.h 中的 Doxygen 注释是权威参考,而本文梳理的版本脉络与 PCSX2 用例则是可以直接落地的路线图。
【免费下载链接】pcsx2PCSX2 - The Playstation 2 Emulator项目地址: https://gitcode.com/GitHub_Trending/pc/pcsx2
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考