Memory Barrier
和 Semaphore
是Vulkan中用于同步操作的两种机制,主要区别在于它们的应用范围和使用场景。
Memory Barrier (内存屏障)
作用:
- 用于在单个命令缓冲区内,或者同一队列中的多个命令之间同步内存访问。
- 确保在执行某些操作(如计算着色器写入数据)之后,内存中的数据能够被后续操作正确读取。
使用场景:
- 适用于单个队列内的命令同步,例如在计算着色器和后续读取这些数据的命令之间。
- 常用于细粒度的同步,例如确保数据写入和读取的正确顺序。
设计原因:
- 完成内存屏障需要指定源阶段(source stage)和目的阶段(destination stage),以及涉及的内存访问类型。这可以细致地控制同步操作的执行顺序和范围,提高效率。
示例:
VkMemoryBarrier memoryBarrier = {};
memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
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_VERTEX_SHADER_BIT, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr);
上述代码在计算着色器写入数据后,保证顶点着色器可以正确读取这些数据。
Semaphore (信号量)
作用:
- 用于跨队列的任务同步以及多个命令缓冲区之间的操作协调。
- 确保一个队列中的操作完成后,另一个队列中的操作才能开始。
使用场景:
- 适用于跨队列同步,例如计算队列和图形队列之间的同步,或者图形队列和传输队列之间的同步。
- 常用于粗粒度的同步,例如帧缓冲区渲染完成后,信号传输队列开始传输纹理数据。
设计原因:
- 信号量在队列提交时使用,综合了硬件和驱动调度机制,适合较高层次的任务同步。
- 与
fence
不同的是,信号量主要用于GPU-GPU的同步,而fence
多用于CPU-GPU的同步。
示例:
VkSemaphoreCreateInfo semaphoreInfo = {};
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
VkSemaphore semaphore;
vkCreateSemaphore(device, &semaphoreInfo, nullptr, &semaphore);
// 在计算队列提交任务后信号信号量
VkSubmitInfo computeSubmitInfo = {};
computeSubmitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
computeSubmitInfo.signalSemaphoreCount = 1;
computeSubmitInfo.pSignalSemaphores = &semaphore;
vkQueueSubmit(computeQueue, 1, &computeSubmitInfo, VK_NULL_HANDLE);
// 在图形队列等待信号量再提交任务
VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT};
VkSubmitInfo graphicsSubmitInfo = {};
graphicsSubmitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
graphicsSubmitInfo.waitSemaphoreCount = 1;
graphicsSubmitInfo.pWaitSemaphores = &semaphore;
graphicsSubmitInfo.pWaitDstStageMask = waitStages;
vkQueueSubmit(graphicsQueue, 1, &graphicsSubmitInfo, VK_NULL_HANDLE);
上述代码展示了在计算队列和图形队列之间使用信号量进行同步。
总结
- 内存屏障 提供了细粒度的控制,适合在同一队列内短时间内的同步操作,确保数据在各个阶段的正确性。
- 信号量 提供了粗粒度的控制,适合跨队列或者跨命令缓冲区的同步操作,可以高效地协调复杂的任务调度和数据传递。