【Vulkan】Memory(二)【MemoryAliasing】

Vulkan Memory(二)【MemoryAliasing】

  • Memory Aliasing
    • 背景简介
    • 基础示例
    • 具体实现
    • 方案限制
    • 代码地址

Memory Aliasing

背景简介

引擎在面对复杂的渲染管线时,常会面临内存的限制,尤其是在面临大量 full-screen 处理时,大量的内存相关资源仅作为中间结果,在一帧中不同时间段多次复用同一段内存资源是一个比较理想的选择。通常有两种做法:

  • 对象池:分配、回收 Image \ Buffer 对象
    • 优势:兼容性好,实现方便
    • 劣势:存在 size、format 限制
  • 内存池:分配、回收内存 suballocation
    • 优势:更高的灵活性与内存优化空间,可参考 framegraph
    • 劣势:仅在 Vulkan,DX12 等现代 Graphics API 支持,且存在一定的性能代价

基础示例

本文将通过 Sample 演示一个 Buffer 与 Image 在一帧内复用内存的场景,如下图所示:

具体管线如下图,图中绿线为读操作,红线为写操作:
【Vulkan】Memory(二)【MemoryAliasing】_第1张图片

按照时间线简单拉开,可以很明显的看出,ParticleOut 与 FullscreenOut 在时间线上无重叠的部分,这部分内存完全可以复用:
【Vulkan】Memory(二)【MemoryAliasing】_第2张图片

具体实现

对于引擎设计来说,最适合的是设计一个 TransientResourcePool,并从中创建 Transient Image 与 Buffer,在帧结束通过 TransientResourcePool 回收,在无 Memory 支持的情况下可以退化成对象池。本文示例简单处理 Memory 的复用逻辑,并无 Pool 设计,在引擎设计章节会再介绍。

复用代码:

VkMemoryRequirements requirements1 = {};
VkMemoryRequirements requirements2 = {};

descriptor.memory = VMA_MEMORY_USAGE_CPU_TO_GPU;
descriptor.size   = PARTICLE_NUM * sizeof(Particle);
descriptor.usage  = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
vkGetBufferMemoryRequirements(device->GetNativeHandle(), buffer->GetNativeHandle(), &requirements1);

imageDesc.format = swapChain->GetFormat();
imageDesc.extent = {128, 128, 1};
imageDesc.usage  = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
imageDesc.memory = VMA_MEMORY_USAGE_GPU_ONLY;
vkGetImageMemoryRequirements(device->GetNativeHandle(), image->GetNativeHandle(), &requirements2);


VkMemoryRequirements finalMemReq = {};
finalMemReq.size = std::max(requirements1.size, requirements2.size);
finalMemReq.alignment = std::max(requirements1.alignment, requirements2.alignment);
finalMemReq.memoryTypeBits = requirements1.memoryTypeBits & requirements2.memoryTypeBits;

VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;

vmaAllocateMemory(device->GetAllocator(), &finalMemReq, &allocCreateInfo, &alloc, nullptr);
vmaBindBufferMemory(device->GetAllocator(), alloc, buffer->GetNativeHandle());
vmaBindImageMemory(device->GetAllocator(), alloc, image->GetNativeHandle());
  • 通过 vkGetBufferMemoryRequirementsvkGetImageMemoryRequirements 分别获取对应的 memoryRequirements
  • 按最大值合并 sizealignmentmemoryTypeBits 需要同时满足,通过 & 处理
  • 申请内存并绑定 Buffer 与 Image
  • 添加 Barrier 处理,本例中该 Image 作为 ColorAttachment,RenderPass 中配置了 Undefined -> ColorAttachment 的 Barrier 处理。

Renderdoc 抓取结果,当前 Buffer 与 ColorAttachment 绑定在同一个内存对象的同一个偏移地址。
【Vulkan】Memory(二)【MemoryAliasing】_第3张图片
资源的生命周期计算可以参考 framegraph 的设计,可以记录第一个与最后一个使用的 pass,在管线的 compile 阶段分别完成从 TransientResourcePool 的申请与释放动作。

方案限制

  1. Buffer 对象绑定 DescriptorSet,Image 对象(非 sparse)创建 ImageView 的前提条件是已经完成内存绑定。
  2. 不存在 Memory 解绑和更新动作。

这两点方案产生的影响,在动态管线场景,Transient Image 与 Buffer 对象实际生命周期只存在于单帧内,包括 Image, Buffer,ImageView,FrameBuffer 对象都需要高频重建,而且这部分资源的创建逻辑对于应用层来说是个黑盒,实际厂商驱动的实现并不一定是一个轻量的操作。

代码地址

  • 地址:sample/VulkanMemoryAliasing/src/VulkanMemoryAliasing.cpp

你可能感兴趣的:(Vulkan,游戏引擎)