Search code examples
c++renderingrendervulkan

Using one render pass on top of another


I'm trying to implement imGUI in my app which already have some render pass for rendering meshes. Its command buffer updates only when new meshes added to scene, while imGUI command buffer should be updated every frame. Secondary command buffer doesn't fits me because I always have to reference it from primary cb, which doesn't update so often.

I also want to mention that my code is based on this tutorial.

I came to conclusion that I should have two render passes with two primary command buffers. The only problem now is that I can't combine these two render passes.

There is the code for main rp:

    vk::AttachmentDescription colorAttachment;
    colorAttachment.format = swapChainImageFormat;
    colorAttachment.samples = vk::SampleCountFlagBits::e1;
    colorAttachment.loadOp = vk::AttachmentLoadOp::eClear;
    colorAttachment.storeOp = vk::AttachmentStoreOp::eStore;
    colorAttachment.stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
    colorAttachment.stencilStoreOp = vk::AttachmentStoreOp::eNoneQCOM;
    colorAttachment.initialLayout = vk::ImageLayout::eUndefined;
    colorAttachment.finalLayout = vk::ImageLayout::ePresentSrcKHR;
     
    vk::AttachmentDescription depthAttachment;
    depthAttachment.format = FindDepthFormat();
    depthAttachment.samples = vk::SampleCountFlagBits::e1;
    depthAttachment.loadOp = vk::AttachmentLoadOp::eClear;
    depthAttachment.storeOp = vk::AttachmentStoreOp::eDontCare;
    depthAttachment.stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
    depthAttachment.stencilStoreOp = vk::AttachmentStoreOp::eDontCare;
    depthAttachment.initialLayout = vk::ImageLayout::eUndefined;
    depthAttachment.finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal;
     
    std::array<vk::AttachmentDescription, 2> attachments = { colorAttachment, depthAttachment };
     
    vk::AttachmentReference colorAttachmentRef;
    colorAttachmentRef.attachment = 0;
    colorAttachmentRef.layout = vk::ImageLayout::eColorAttachmentOptimal;
     
    vk::AttachmentReference depthAttachmentRef;
    depthAttachmentRef.attachment = 1;
    depthAttachmentRef.layout = vk::ImageLayout::eDepthStencilAttachmentOptimal;
     
    vk::SubpassDescription subpass;
    subpass.colorAttachmentCount = 1;
    subpass.pColorAttachments = &colorAttachmentRef;
    subpass.pDepthStencilAttachment = &depthAttachmentRef;
     
    vk::SubpassDependency dependency;
    dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
    dependency.dstSubpass = 0;
    dependency.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput;
    dependency.srcAccessMask = vk::AccessFlagBits();
    dependency.dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput;
    dependency.dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite;
     
    vk::RenderPassCreateInfo renderPassInfo;
    renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
    renderPassInfo.pAttachments = attachments.data();
    renderPassInfo.subpassCount = 1;
    renderPassInfo.pSubpasses = &subpass;
    renderPassInfo.dependencyCount = 1;
    renderPassInfo.pDependencies = &dependency;
     
    gameRenderPass = device.createRenderPass(renderPassInfo);

There is the code for ui rp:

vk::AttachmentDescription colorAttachment;
colorAttachment.format = swapChainImageFormat;
colorAttachment.samples = vk::SampleCountFlagBits::e1;
colorAttachment.loadOp = vk::AttachmentLoadOp::eLoad;
colorAttachment.storeOp = vk::AttachmentStoreOp::eStore;
colorAttachment.stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
colorAttachment.stencilStoreOp = vk::AttachmentStoreOp::eDontCare;
colorAttachment.initialLayout = vk::ImageLayout::ePresentSrcKHR;
colorAttachment.finalLayout = vk::ImageLayout::ePresentSrcKHR;

vk::AttachmentDescription depthAttachment;
depthAttachment.format = FindDepthFormat();
depthAttachment.samples = vk::SampleCountFlagBits::e1;
depthAttachment.loadOp = vk::AttachmentLoadOp::eClear;
depthAttachment.storeOp = vk::AttachmentStoreOp::eDontCare;
depthAttachment.stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
depthAttachment.stencilStoreOp = vk::AttachmentStoreOp::eDontCare;
depthAttachment.initialLayout = vk::ImageLayout::eUndefined;
depthAttachment.finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal;

std::array<vk::AttachmentDescription, 2> attachments = { colorAttachment, depthAttachment };
  
vk::AttachmentReference colorAttachmentRef;
colorAttachmentRef.attachment = 0;
colorAttachmentRef.layout = vk::ImageLayout::eColorAttachmentOptimal;

vk::AttachmentReference depthAttachmentRef;
depthAttachmentRef.attachment = 1;
depthAttachmentRef.layout = vk::ImageLayout::eDepthStencilAttachmentOptimal;

vk::SubpassDescription subpass;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorAttachmentRef;
subpass.pDepthStencilAttachment = &depthAttachmentRef;

vk::SubpassDependency dependency;
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
dependency.dstSubpass = 0;
dependency.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput;
dependency.srcAccessMask = vk::AccessFlagBits();
dependency.dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput;
dependency.dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite;

vk::RenderPassCreateInfo renderPassInfo;
renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
renderPassInfo.pAttachments = attachments.data();
renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpass;
renderPassInfo.dependencyCount = 1;
renderPassInfo.pDependencies = &dependency;

uiRenderPass = device.createRenderPass(renderPassInfo);

And there is the code of my DrawFrame function:

device.waitForFences(1, &inFlightFences[currentFrame], true, UINT64_MAX);

uint32_t imageIndex;
device.acquireNextImageKHR(swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], nullptr, &imageIndex);

if (imagesInFlight[imageIndex].operator!=(nullptr))
{
  device.waitForFences(1, &imagesInFlight[imageIndex], true, UINT64_MAX);
}
imagesInFlight[imageIndex] = inFlightFences[currentFrame];

vk::SubmitInfo submitInfo;

vk::Semaphore waitSemaphores[] = { imageAvailableSemaphores[currentFrame] };
vk::PipelineStageFlags waitStages[] = { vk::PipelineStageFlagBits::eColorAttachmentOutput };
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = waitSemaphores;
submitInfo.pWaitDstStageMask = waitStages;

vk::Semaphore signalSemaphores[] = { renderFinishedSemaphores[currentFrame] };
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signalSemaphores;

device.resetFences(1, &inFlightFences[currentFrame]);

UpdateUniformBuffer(imageIndex);

UpdateUiCommandBuffer(imageIndex);

if (comandUpdateRequired[imageIndex])
{
  UpdateGameCommandBuffer(imageIndex);
  comandUpdateRequired[imageIndex] = false;
}

std::vector<vk::CommandBuffer> commands = { gameCommandBuffers[imageIndex], uiCommandBuffers[imageIndex] };
  
submitInfo.commandBufferCount = static_cast<uint32_t>(commands.size());
submitInfo.pCommandBuffers = commands.data();

graphicsQueue.submit(1, &submitInfo, inFlightFences[currentFrame]);

vk::PresentInfoKHR presentInfo;
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = signalSemaphores;

vk::SwapchainKHR swapChains[] = { swapChain };
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = swapChains;
presentInfo.pImageIndices = &imageIndex;

presentQueue.presentKHR(presentInfo);

presentQueue.waitIdle();

currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;

And the result is: Bad result >:(

As you can see, clear color is used to overwrite the results of my first render pass, what is not what I want.

Note that load and store operations already in use and it still doesn't work.


UPDATE

There is a code of my second cb:

device.freeCommandBuffers(commandPool, 1, &uiCommandBuffers[index]);

vk::CommandBufferAllocateInfo allocInfo;
allocInfo.commandPool = commandPool;
allocInfo.level = vk::CommandBufferLevel::ePrimary;
allocInfo.commandBufferCount = 1;

device.allocateCommandBuffers(&allocInfo, &uiCommandBuffers[index]);

vk::CommandBufferBeginInfo beginInfo;

uiCommandBuffers[index].begin(beginInfo);

std::array<vk::ClearValue, 2> clearValues;
clearValues[0].color = vk::ClearColorValue(std::array<float, 4>{ 1.0f, 0.0f, 0.0f, 1.0f });
clearValues[1].depthStencil = { 1.0f, 0 };

vk::RenderPassBeginInfo renderPassInfo;
renderPassInfo.renderPass = gameRenderPass;
renderPassInfo.framebuffer = uiSwapChainFramebuffers[index];
renderPassInfo.renderArea.offset = { 0, 0 };
renderPassInfo.renderArea.extent = swapChainExtent;
renderPassInfo.clearValueCount = static_cast<uint32_t>(clearValues.size()); // It doesn't work at all if I set clear values count to 0
renderPassInfo.pClearValues = clearValues.data();
//renderPassInfo.clearValueCount = 0;
//renderPassInfo.pClearValues = nullptr;

uiCommandBuffers[index].beginRenderPass(renderPassInfo, vk::SubpassContents::eInline);

auto drawData = ImGui::GetDrawData();
if (drawData)
{
    ImGui_ImplVulkan_RenderDrawData(drawData, uiCommandBuffers[index]);
}

uiCommandBuffers[index].endRenderPass();

uiCommandBuffers[index].end();

Solution

  • If you want to render the UI using a second pass, just set the storeOp of the color attachment in the first render pass to VK_ATTACHMENT_STORE_OP_STORE and the loadOp for it in the second render pass to VK_ATTACHMENT_LOAD_OP_LOAD to keep the contents.

    Another option would be to do this in a single render pass, like I do in my samples. Just render your scene, and put the draw calls for the UI in the same render pass.