Vulkan 的资源管理围绕三个核心概念展开:Memory(物理内存)、Buffer(线性数据)和 Image(结构化图像数据)。理解它们的层次关系和绑定方式是正确使用 Vulkan 的基础。

三个核心概念

  1. Memory (内存)
  • 最底层的概念,代表实际的物理内存或显存
  • 主要类型:
// 设备本地内存(显存) - 通常更快但CPU不可访问
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
 
// 主机可见内存 - CPU可以直接访问
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
 
// 主机一致性内存 - 确保CPU写入立即可见
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
  1. Buffer (缓冲区)
  • 表示一段线性的内存空间
  • 需要绑定到 Memory 才能使用
  • 常见用途:
// 顶点缓冲区
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT
 
// 索引缓冲区
VK_BUFFER_USAGE_INDEX_BUFFER_BIT
 
// 统一缓冲区(Uniform Buffer)
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT
  1. Image (图像)
  • 表示二维或三维的结构化数据
  • 同样需要绑定到 Memory
  • 具有特定的布局 (layout)
  • 常见用途:
// 纹理
VK_IMAGE_USAGE_SAMPLED_BIT
 
// 渲染目标
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
 
// 深度/模板缓冲
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT

它们之间的关系:

Memory (基础)
   ↑
   | 绑定
   ↑
Buffer/Image (抽象接口)
   ↑
   | 使用
   ↑
具体应用(渲染、计算等)

完整示例:缓冲区创建与内存绑定

// 1. 创建缓冲区
VkBuffer buffer;
VkBufferCreateInfo bufferInfo = {};
bufferInfo.size = size;
bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
vkCreateBuffer(device, &bufferInfo, nullptr, &buffer);
 
// 2. 获取内存需求
VkMemoryRequirements memRequirements;
vkGetBufferMemoryRequirements(device, buffer, &memRequirements);
 
// 3. 分配内存
VkMemoryAllocateInfo allocInfo = {};
allocInfo.allocationSize = memRequirements.size;
allocInfo.memoryTypeIndex = findMemoryType(
    memRequirements.memoryTypeBits,
    VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
);
VkDeviceMemory memory;
vkAllocateMemory(device, &allocInfo, nullptr, &memory);
 
// 4. 绑定内存到缓冲区
vkBindBufferMemory(device, buffer, memory, 0);

内存管理最佳实践:

  1. 合并小内存分配
  2. 对齐要求的处理
  3. 使用内存堆而不是频繁分配
  4. 考虑使用 VMA (Vulkan Memory Allocator)

使用 VMA 简化内存管理

VMA(Vulkan Memory Allocator)(Vulkan Memory Allocator)封装了底层的内存分配细节,一次调用即可完成缓冲区创建和内存绑定。

VmaAllocationCreateInfo allocInfo = {};
allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
 
VkBuffer buffer;
VmaAllocation allocation;
vmaCreateBuffer(
    allocator,
    &bufferInfo,
    &allocInfo,
    &buffer,
    &allocation,
    nullptr
);

这三个概念的正确理解和使用对于 Vulkan 程序的性能至关重要。通常建议使用 VMA 这样的内存分配器来简化内存管理。

为什么 vkCmdPipelineBarrier 有 3 种 barrier 参数

如:

vkCmdPipelineBarrier(
    commandBuffer,
    VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,      // 源阶段
    VK_PIPELINE_STAGE_TRANSFER_BIT,          // 目标阶段
    0,
    0, nullptr,         // Memory barriers
    0, nullptr,         // Buffer barriers
    1, &barrier         // Image barriers
);

这三种 barriers 分别用于不同的同步场景,它们的粒度和用途不同:

  1. Memory Barriers(内存屏障)
VkMemoryBarrier memoryBarrier{};
// 最通用的屏障,作用于全局内存
// 同步所有内存访问
// 开销最大,应该尽量避免使用
  1. Buffer Barriers(缓冲区屏障)
VkBufferMemoryBarrier bufferBarrier{};
// 只同步特定缓冲区的访问
// 比内存屏障更有效率
// 例如:同步顶点缓冲区的写入和读取
  1. Image Barriers(图像屏障)
VkImageMemoryBarrier imageBarrier{};
// 专门用于图像资源
// 可以同时处理访问同步和布局转换
// 例如:从传输布局转换到着色器读取布局

具体使用示例:

  1. Memory Barrier 示例:
// 同步计算着色器的写入和片段着色器的读取
VkMemoryBarrier memoryBarrier{};
memoryBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
memoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
 
vkCmdPipelineBarrier(
    commandBuffer,
    VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
    VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
    0,
    1, &memoryBarrier,    // Memory barrier
    0, nullptr,           // No buffer barriers
    0, nullptr            // No image barriers
);
  1. Buffer Barrier 示例:
// 同步顶点缓冲区的更新和使用
VkBufferMemoryBarrier bufferBarrier{};
bufferBarrier.buffer = vertexBuffer;
bufferBarrier.offset = 0;
bufferBarrier.size = bufferSize;
bufferBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
bufferBarrier.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT;
 
vkCmdPipelineBarrier(
    commandBuffer,
    VK_PIPELINE_STAGE_TRANSFER_BIT,
    VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
    0,
    0, nullptr,           // No memory barriers
    1, &bufferBarrier,    // Buffer barrier
    0, nullptr            // No image barriers
);
  1. Image Barrier 示例:
// 图像布局转换
VkImageMemoryBarrier imageBarrier{};
imageBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
imageBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
imageBarrier.image = image;
imageBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
imageBarrier.srcAccessMask = 0;
imageBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
 
vkCmdPipelineBarrier(
    commandBuffer,
    VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
    VK_PIPELINE_STAGE_TRANSFER_BIT,
    0,
    0, nullptr,           // No memory barriers
    0, nullptr,           // No buffer barriers
    1, &imageBarrier      // Image barrier
);

选择建议:

  • 总是使用最具体的屏障类型
  • 避免使用全局内存屏障
  • 合并多个相同类型的屏障以提高性能
  • 准确指定访问掩码和管线阶段以优化性能