Vulkan绘制与计算命令同步及BufferMemory屏障配置问题咨询
疑问0解答
首先明确:即使你在同一个命令缓冲区中按draw/draw_indexed在前、compute在后的顺序提交命令,compute调度也不会默认等待光栅化阶段全部完成后再运行。
你看到的流水线阶段示意图是单个图形渲染命令的内部执行流程,并不约束不同类型命令的全局执行顺序。Vulkan的设计原则是最小隐式同步,同队列提交的不同命令只要没有显式同步约束,硬件就可以乱序、重叠执行,甚至可能出现compute调度比前面的draw更早完成的情况,必须通过显式屏障指定依赖关系才能保障执行顺序。
疑问1解答
首先先明确你的资源访问链路:compute_1 写 geometry_buffer -> draw 顶点输入阶段读 geometry_buffer -> compute_2 着色器阶段读 geometry_buffer,我们基于这个链路对比三个方案:
方案一:错误
方案一的核心问题是没有保障compute_2读取geometry_buffer的可见性与执行顺序:
- 第一个屏障只能保证
compute_1的写入对后续draw的顶点输入阶段可见,无法保障对compute_2的计算着色器阶段可见 - 你描述的第二个屏障位置(compute_2之后)、参数(目标访问为
SHADER_WRITE)完全不符合访问链路逻辑,即使调整到draw之后compute_2之前,仅使用VERTEX_INPUT/VERTEX_READ -> COMPUTE_SHADER/SHADER_WRITE也不符合compute_2只读的场景,会引入不必要的写访问约束。
方案二:正确
方案二额外添加的draw与compute_2之间的屏障是必要的:
- 这个屏障的源阶段为
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT、源访问为VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,目标阶段为VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT、目标访问为VK_ACCESS_SHADER_READ_BIT,刚好匹配draw读完成后再给compute_2读的依赖关系 - 优势是同步粒度更细:只有
compute_2的执行会被屏障阻塞,draw之后到屏障之前的其他不依赖geometry_buffer的计算工作可以正常和draw并行执行,驱动调度自由度更高。
方案三:修改阶段参数后正确,和方案二有性能差异
如果仅修改第一个屏障的访问位为VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_SHADER_READ_BIT,方案三依然错误,必须同步将第一个屏障的目标阶段修改为VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT才能保证正确性。
修改后的方案三逻辑也是通顺的:直接在第一个屏障中一次性声明geometry_buffer的写入要同时对draw的顶点输入、后续的计算着色器可见,不需要额外添加第二个屏障,代码更简洁。
它和方案二的核心差异是性能:
- 方案三会把所有计算着色器阶段的工作都阻塞到
compute_1完成后才能执行,即使这些计算工作不依赖geometry_buffer,会损失一定的并行度。 - 只有当你的
compute_2是draw之后的第一个计算任务、且中间没有其他无关计算工作时,两个方案的实际执行效率没有区别。
内容的提问来源于stack exchange,提问作者Denis

