Vulkan 的资源管理围绕三个核心概念展开:Memory(物理内存)、Buffer(线性数据)和 Image(结构化图像数据)。理解它们的层次关系和绑定方式是正确使用 Vulkan 的基础。
三个核心概念
- Memory (内存)
- 最底层的概念,代表实际的物理内存或显存
- 主要类型:
// 设备本地内存(显存) - 通常更快但CPU不可访问
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
// 主机可见内存 - CPU可以直接访问
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
// 主机一致性内存 - 确保CPU写入立即可见
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT- Buffer (缓冲区)
- 表示一段线性的内存空间
- 需要绑定到 Memory 才能使用
- 常见用途:
// 顶点缓冲区
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT
// 索引缓冲区
VK_BUFFER_USAGE_INDEX_BUFFER_BIT
// 统一缓冲区(Uniform Buffer)
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT- 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);内存管理最佳实践:
- 合并小内存分配
- 对齐要求的处理
- 使用内存堆而不是频繁分配
- 考虑使用 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 分别用于不同的同步场景,它们的粒度和用途不同:
- Memory Barriers(内存屏障)
VkMemoryBarrier memoryBarrier{};
// 最通用的屏障,作用于全局内存
// 同步所有内存访问
// 开销最大,应该尽量避免使用- Buffer Barriers(缓冲区屏障)
VkBufferMemoryBarrier bufferBarrier{};
// 只同步特定缓冲区的访问
// 比内存屏障更有效率
// 例如:同步顶点缓冲区的写入和读取- Image Barriers(图像屏障)
VkImageMemoryBarrier imageBarrier{};
// 专门用于图像资源
// 可以同时处理访问同步和布局转换
// 例如:从传输布局转换到着色器读取布局具体使用示例:
- 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
);- 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
);- 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
);选择建议:
- 总是使用最具体的屏障类型
- 避免使用全局内存屏障
- 合并多个相同类型的屏障以提高性能
- 准确指定访问掩码和管线阶段以优化性能