LLGL icon indicating copy to clipboard operation
LLGL copied to clipboard

Multi submission of command buffer results in flickering

Open mikex86 opened this issue 3 years ago • 1 comments

LLGL::CommandBufferFlags::MultiSubmit seems to imply, that it should be possible to record a command buffer once and resubmit it multiple times to prevent CPU cycles being wasted dispatching draw calls to the GPU while nothing has changed.

The results of testing out this theory made me wonder whether or not the results I was seeing were intended behaviour. Tested with Vulkan.

    LLGL::CommandBuffer *renderCmd;
    {
        LLGL::CommandBufferDescriptor commandBufferDescriptor{
                .flags =(LLGL::CommandBufferFlags::MultiSubmit)
        };
        renderCmd = renderSystem->CreateCommandBuffer(commandBufferDescriptor);
    }
    renderCmd->Begin();
    {
        renderCmd->SetViewport(renderContext->GetResolution());
        renderCmd->SetPipelineState(*pipeline);
        renderCmd->SetVertexBuffer(*vertexBuffer);
        renderCmd->SetClearColor(LLGL::ColorRGBAf(0.1f, 0.1f, 0.1f));

        renderCmd->BeginRenderPass(*renderContext);

        renderCmd->Clear(LLGL::ClearFlags::Color);
        renderCmd->Draw(3, 0);

        renderCmd->EndRenderPass();
    }
    renderCmd->End();
    while (window.ProcessEvents()) {
        queue->Submit(*renderCmd);
        renderContext->Present();
    }

This little example produces nothing more than the clear color flashing between black and the gray specified in the command buffer. The triangle, which renders just fine when the command buffer is re-recorded every frame in the main loop, is no where to be seen with this code.

However, some experimentation revealed even weirder behaviour: Re-recording the command buffer for the first three frames of the application and then continuing to use it works just fine. If the command buffer is altered at a later point in time, it requires only 2 repeated recordings (with a Submit call and a renderContext->Present() in between) for the changes to be displayed without flickering. Re-recording the command buffer less than 3 times for the initial frames of the loop, or less than 2 times at a later point in time will result in flickering back and forth between what seem to be the previously recorded draw calls and what actually should be displayed.

I think this might be happening because a double buffered swap chain also has two command buffers. When a new frame is forced to render, it will cycle back to the previous command buffer. If the command buffer contents have not been updated in a frame, no new image should be acquired from the swap chain.

Should it be possible to re-submit command buffers in this way? Am I using LLGL incorrectly?

mikex86 avatar Dec 05 '21 21:12 mikex86

This does look like a bug and I also never had the time to experiment a lot with multi-submit buffers unfortunately. I'm also out on vacation for the next four weeks, so won't have time to look into that. For the time being, I recommend wrapping the command buffer recording into a function and call it every frame for single submit command buffers until the issue is identified and fixed. Or use the GL backend until then, the multi-submit buffer implementation there should work fine.

LukasBanana avatar Dec 11 '21 21:12 LukasBanana

I believe the behaviour is most likely present because in VKSwapChain::GetPresentableImageIndex() the call to vkAcquireNextImageKHR only signals the semaphore to vkQueuePresentKHR but not to the command buffer(s) that render to the swap chain image. From https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkAcquireNextImageKHR:

The presentation engine may not have finished reading from the image at the time it is acquired, so the application must use semaphore and/or fence to ensure that the image layout and contents are not modified until the presentation engine reads have completed.

Currently, the following can happen:

  • vkAcquireNextImageKHR returns image n
  • command buffer is submitted and starts executing immediately, writing to image n
  • OS finishes reading from image n
  • vkQueuePresentKHR(n)

ceeac avatar Jun 02 '23 06:06 ceeac

This has been fixed with the new design of the BeginRenderPass interface (better late than never). I'll have to come up with an example just for multi-submit command buffers, but you basically have to record multiple command buffers when you render into a swap-chain. One command buffer for each swap-chain back-buffer:

LLGL::CommandBuffer cmdBuffers[3];
for (std::uint32_t swapBufferIndex = 0; swapBufferIndex < swapChain->GetNumSwapBuffers(); ++swapBufferIndex) {
  auto& renderCmd = cmdBuffers[swapBufferIndex];
  renderCmd = renderer->CreateCommandBuffer(LLGL::CommandBufferFlags::MultiSubmit);

  renderCmd->Begin();
  renderCmd->BeginRenderPass(*swapChain, nullptr, 0, nullptr, swapBufferIndex);

  /* Render into currently selected back buffer (via 'swapBufferIndex') ... */

  renderCmd->EndRenderPass();
  renderCmd->End();
}

while (/* ... */) {
  // Submit command buffer that encoded the back buffer that is currently active in the swap-chain
  queue->Submit(*cmdBuffers[swapChain->GetCurrentSwapIndex()]);
  swapChain->Present();
}

Command buffers that only render into render-targets (not swap-chains) don't have to be recorded for multiple states since regular render-targets don't have a chain of internal buffers.

LukasBanana avatar Jun 23 '23 23:06 LukasBanana