Vulkan异步加载图像时无流水线依赖的布局过渡实现疑问
我想要实现异步加载图像,在图像加载完成前显示GPU上已有的占位图。核心思路是用提交队列时获取的fence判断图像是否就绪,渲染逻辑如下:
if(fenceIsSignaled) // 使用目标图像绘制 else // 使用占位图绘制
我用staging buffer填充首层级mip数据,并把以下步骤录制到命令缓冲:
- 通过屏障将图像从
LAYOUT_UNDEFINED过渡到LAYOUT_TRANSFER_DST_OPTIMAL:
const VkImageMemoryBarrier imgBarrier = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .srcAccessMask = VK_ACCESS_NONE, .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, .image = imgVk, .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = img.mipLevels, .baseArrayLayer = 0, .layerCount = 1, } }; vkCmdPipelineBarrier(cmdBuffer, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, // VkDependencyFlagBits 0, nullptr, 0, nullptr, 1, &imgBarrier);
- 调用
vkCmdCopyBufferToImage把staging buffer的数据复制到图像; - 调用
vkCmdBlitImage生成mipmap链,并配合额外屏障; - 最后需要把整个图像从
TRANSFER_DST_OPTIMAL或TRANSFER_SRC_OPTIMAL过渡到SHADER_READ_ONLY_OPTIMAL,以支持片段着色器采样,但这一步出了问题。
我尝试用如下屏障:
vkCmdPipelineBarrier(cmdBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, uploadMode == UploadMode::ASYNC ? VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT : VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, std::size(imgBarriers) - firstBarrierIsEmpty, imgBarriers + firstBarrierIsEmpty);
我想用VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT作为dstStageMask来避免流水线依赖(如果用VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,流水线会在片段着色器阶段等待图像就绪,影响帧渲染),但验证层报错说布局过渡和dstStageMask不兼容:
VUID-vkCmdPipelineBarrier-dstAccessMask-02816(ERROR / SPEC): msgNum: 1774732925 - Validation Error: [ VUID-vkCmdPipelineBarrier-dstAccessMask-02816 ] Object 0: handle = 0x253d375a5f0, type = VK_OBJECT_TYPE_COMMAND_BUFFER; | MessageID = 0x69c8467d | vkCmdPipelineBarrier(): .pImageMemoryBarriers[0].dstAccessMask bit VK_ACCESS_SHADER_READ_BIT is not supported by stage mask (VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT). The Vulkan spec states: The dstAccessMask member of each element of pMemoryBarriers must only include access flags that are supported by one or more of the pipeline stages in dstStageMask, as specified in the table of supported access types
请问怎么实现预期的无流水线依赖的布局过渡行为?
解决方案
问题出在混淆了布局过渡的依赖同步和fence的外部同步逻辑。要实现无流水线阻塞的异步加载,需要调整屏障参数并优化队列提交策略:
修正最终布局过渡的屏障参数
当异步加载时,不需要把过渡的目标阶段绑定到片段着色器,但必须保证布局过渡完成后后续着色器访问合法。正确做法是:- 保留
dstAccessMask为VK_ACCESS_SHADER_READ_BIT,因为最终要给着色器读取; - 把
dstStageMask设为VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,但通过将图像操作的命令缓冲提交到独立的传输队列,并绑定fence,让渲染队列和传输队列并行执行,避免渲染流水线阻塞。
- 保留
调整异步加载流程
- 将图像上传、mip生成、布局过渡的命令缓冲提交到专用传输队列,而非渲染队列,同时给该提交绑定fence;
- 渲染线程每帧检测fence状态:若fence触发,说明图像的所有操作(包括布局过渡)已完成,可安全在渲染队列中使用该图像;若未触发,继续使用占位图。
这种方式下,传输队列的操作和渲染队列的帧渲染完全并行,渲染流水线不会被阻塞,直到明确知道图像就绪后才切换使用。
替代方案:临时过渡到
VK_IMAGE_LAYOUT_GENERAL
如果一定要避免和片段着色器阶段绑定,可以先把图像过渡到VK_IMAGE_LAYOUT_GENERAL(dstStageMask用VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,dstAccessMask用VK_ACCESS_MEMORY_READ_BIT),在第一次使用图像前,再从GENERAL过渡到SHADER_READ_ONLY_OPTIMAL并绑定片段着色器阶段。但这会多一次布局过渡,效率不如队列分离方案。
总结:最合理的实现是分离传输队列与渲染队列,用fence标记传输任务完成,渲染线程仅在fence触发后使用图像,此时布局过渡的屏障参数可正常使用片段着色器作为目标阶段,且不会阻塞渲染流水线。
内容的提问来源于stack exchange,提问作者tuket

