You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Vulkan API中仅在首个命令缓冲中清除屏幕?

Vulkan多线程渲染:仅首个命令缓冲执行屏幕清除的正确实现

问题背景

我尝试用Vulkan API实现多线程渲染,从其他线程创建并执行多个命令缓冲,但每个命令缓冲都执行屏幕清除操作,导致画面无法正常显示。

原代码片段:

VkCommandBufferBeginInfo beginInfo{};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;

VkRenderPassBeginInfo renderPassInfo{};
renderPassInfo.sType                = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass           = m_pRenderPass;
renderPassInfo.framebuffer          = m_swapChainFrameBufferPtrs[m_nImageIndex];
renderPassInfo.renderArea.offset    = { 0, 0 };
renderPassInfo.renderArea.extent    = { m_nWidth, m_nHeight };

std::array<VkClearValue, 2> clearValues{};
clearValues[0].color            = { {0.0f, 0.0f, 0.0f, 1.0f} };
clearValues[1].depthStencil     = { 1.0f, 0 };
renderPassInfo.clearValueCount  = static_cast<uint32_t>(clearValues.size());
renderPassInfo.pClearValues     = clearValues.data();

VkViewport viewport{...};
VkRect2D scissor{...};

CFrameCommandBuffer& currentFrame = m_frames[m_nCurrentFrame];

for (std::uint32_t n = 0; n < currentFrame.m_commandBufferPtrs.size(); ++n)
{
    auto& pCmdBuffer = currentFrame.m_commandBufferPtrs[n];

    vkResetCommandBuffer(pCmdBuffer, 0);
    vkBeginCommandBuffer(pCmdBuffer, &beginInfo);
    vkCmdBeginRenderPass(pCmdBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
    vkCmdSetViewport(pCmdBuffer, 0, 1, &viewport);
    vkCmdSetScissor(pCmdBuffer, 0, 1, &scissor);
}

尝试修改第二个及后续命令缓冲不执行清除时,设置:

renderPassInfo.clearValueCount  = 0;
renderPassInfo.pClearValues     = nullptr;

触发错误:

In vkCmdBeginRenderPass the VkRenderPassBeginInfo struct has a clearValueCount of 0 but there must be at least 2 entries in pClearValues array to account for the highest index attachment in VkRenderPass 0x1ebe0f000000012a[] that uses VK_ATTACHMENT_LOAD_OP_CLEAR is 2. Note that the pClearValues array is indexed by attachment number so even if some pClearValues entries between 0 and 1 correspond to attachments that aren't cleared they will be ignored.

核心原因

Vulkan的RenderPass规则是:只要RenderPass中附件的加载操作设置为VK_ATTACHMENT_LOAD_OP_CLEAR,任何启动该RenderPass的命令缓冲都必须提供足够数量的ClearValue——因为清除操作是RenderPass启动阶段的全局行为,无法在单个命令缓冲中跳过。

正确实现方案

方案一:主命令缓冲启动RenderPass,二级命令缓冲执行绘制逻辑

通过主命令缓冲统一启动RenderPass并执行清除,其他子命令缓冲作为二级缓冲,仅录制RenderPass内的绘图逻辑,最后由主缓冲调用执行。

代码调整示例:

// 主命令缓冲(首个缓冲)
auto& mainCmdBuffer = currentFrame.m_commandBufferPtrs[0];
vkResetCommandBuffer(mainCmdBuffer, 0);
vkBeginCommandBuffer(mainCmdBuffer, &beginInfo);

// 启动RenderPass并执行清除,指定使用二级命令缓冲
vkCmdBeginRenderPass(mainCmdBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS);

// 录制二级命令缓冲
std::vector<VkCommandBuffer> secondaryBuffers;
for (uint32_t n = 1; n < currentFrame.m_commandBufferPtrs.size(); ++n) {
    auto& subCmdBuffer = currentFrame.m_commandBufferPtrs[n];
    vkResetCommandBuffer(subCmdBuffer, 0);

    // 二级缓冲必须指定继承信息,关联到目标RenderPass
    VkCommandBufferBeginInfo subBeginInfo{};
    subBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
    subBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT;
    
    VkCommandBufferInheritanceInfo inheritInfo{};
    inheritInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO;
    inheritInfo.renderPass = m_pRenderPass;
    inheritInfo.subpass = 0;
    subBeginInfo.pInheritanceInfo = &inheritInfo;
    
    vkBeginCommandBuffer(subCmdBuffer, &subBeginInfo);
    vkCmdSetViewport(subCmdBuffer, 0, 1, &viewport);
    vkCmdSetScissor(subCmdBuffer, 0, 1, &scissor);
    // 录制子缓冲的绘制逻辑...
    vkEndCommandBuffer(subCmdBuffer);
    secondaryBuffers.push_back(subCmdBuffer);
}

// 主缓冲中执行所有二级缓冲
vkCmdExecuteCommands(mainCmdBuffer, secondaryBuffers.size(), secondaryBuffers.data());
vkCmdEndRenderPass(mainCmdBuffer);
vkEndCommandBuffer(mainCmdBuffer);

方案二:修改RenderPass加载模式,手动执行清除

将RenderPass的附件加载操作改为VK_ATTACHMENT_LOAD_OP_LOAD,然后在首个命令缓冲中手动调用清除指令,后续缓冲直接加载已有画面内容。

1. 重新创建RenderPass

// 颜色附件修改加载模式
VkAttachmentDescription colorAttachment{};
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; // 替换原VK_ATTACHMENT_LOAD_OP_CLEAR
// 深度模板附件同理
VkAttachmentDescription depthAttachment{};
depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;

2. 命令缓冲录制逻辑

for (std::uint32_t n = 0; n < currentFrame.m_commandBufferPtrs.size(); ++n)
{
    auto& pCmdBuffer = currentFrame.m_commandBufferPtrs[n];

    vkResetCommandBuffer(pCmdBuffer, 0);
    vkBeginCommandBuffer(pCmdBuffer, &beginInfo);
    vkCmdBeginRenderPass(pCmdBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
    vkCmdSetViewport(pCmdBuffer, 0, 1, &viewport);
    vkCmdSetScissor(pCmdBuffer, 0, 1, &scissor);
    
    // 仅首个缓冲执行手动清除
    if (n == 0) {
        VkClearAttachment clearAttachments[2];
        // 清除颜色附件
        clearAttachments[0].attachment = 0;
        clearAttachments[0].aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
        clearAttachments[0].clearValue = clearValues[0];
        // 清除深度模板附件
        clearAttachments[1].attachment = 1;
        clearAttachments[1].aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
        clearAttachments[1].clearValue = clearValues[1];
        
        VkClearRect clearRect{};
        clearRect.rect = { {0,0}, {m_nWidth, m_nHeight} };
        clearRect.baseArrayLayer = 0;
        clearRect.layerCount = 1;
        
        vkCmdClearAttachments(pCmdBuffer, 2, clearAttachments, 1, &clearRect);
    }
    
    // 录制绘制逻辑...
    vkCmdEndRenderPass(pCmdBuffer);
    vkEndCommandBuffer(pCmdBuffer);
}

注意事项

  • 方案一中,二级命令缓冲必须以VK_COMMAND_BUFFER_LEVEL_SECONDARY的级别创建,否则无法被主缓冲调用。
  • 方案二中,使用VK_ATTACHMENT_LOAD_OP_LOAD时,需确保交换链图像的初始状态有效,或在RenderPass中正确处理图像布局转换。

内容的提问来源于stack exchange,提问作者Fobes Misterio

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 21:33:23