|
| 1 | +# Presenting Images |
| 2 | + |
| 3 | +In the main loop, attempt to acquire a Swapchain image / Render Target: |
| 4 | + |
| 5 | +```cpp |
| 6 | +auto const framebuffer_size = glfw::framebuffer_size(m_window.get()); |
| 7 | +// minimized? skip loop. |
| 8 | +if (framebuffer_size.x <= 0 || framebuffer_size.y <= 0) { continue; } |
| 9 | +// an eErrorOutOfDateKHR result is not guaranteed if the |
| 10 | +// framebuffer size does not match the Swapchain image size, check it |
| 11 | +// explicitly. |
| 12 | +auto fb_size_changed = framebuffer_size != m_swapchain->get_size(); |
| 13 | +auto& render_sync = m_render_sync.at(m_frame_index); |
| 14 | +auto render_target = m_swapchain->acquire_next_image(*render_sync.draw); |
| 15 | +if (fb_size_changed || !render_target) { |
| 16 | + m_swapchain->recreate(framebuffer_size); |
| 17 | + continue; |
| 18 | +} |
| 19 | +``` |
| 20 | + |
| 21 | +Wait for the associated fence and reset ('un'signal) it: |
| 22 | + |
| 23 | +```cpp |
| 24 | +static constexpr auto fence_timeout_v = |
| 25 | + static_cast<std::uint64_t>(std::chrono::nanoseconds{3s}.count()); |
| 26 | +auto result = m_device->waitForFences(*render_sync.drawn, vk::True, |
| 27 | + fence_timeout_v); |
| 28 | +if (result != vk::Result::eSuccess) { |
| 29 | + throw std::runtime_error{"Failed to wait for Render Fence"}; |
| 30 | +} |
| 31 | +// reset fence _after_ acquisition of image: if it fails, the |
| 32 | +// fence remains signaled. |
| 33 | +m_device->resetFences(*render_sync.drawn); |
| 34 | +``` |
| 35 | +
|
| 36 | +Since the fence has been reset, a queue submission must be made that signals it before continuing, otherwise the app will deadlock on the next wait (and eventually throw after 3s). We can now begin command buffer recording: |
| 37 | +
|
| 38 | +```cpp |
| 39 | +auto command_buffer_bi = vk::CommandBufferBeginInfo{}; |
| 40 | +// this flag means recorded commands will not be reused. |
| 41 | +command_buffer_bi.setFlags( |
| 42 | + vk::CommandBufferUsageFlagBits::eOneTimeSubmit); |
| 43 | +render_sync.command_buffer.begin(command_buffer_bi); |
| 44 | +``` |
| 45 | + |
| 46 | +We are not ready to actually render anything yet, but we can transition the acquired image to `ePresentSrcKHR` layout (in a technically "undefined" state) and submit it for presentation. Set up the image barrier and record it: |
| 47 | + |
| 48 | +```cpp |
| 49 | +auto dependency_info = vk::DependencyInfo{}; |
| 50 | +auto barrier = m_swapchain->base_barrier(); |
| 51 | +barrier.setOldLayout(vk::ImageLayout::eUndefined) |
| 52 | + .setNewLayout(vk::ImageLayout::ePresentSrcKHR) |
| 53 | + .setSrcAccessMask(vk::AccessFlagBits2::eColorAttachmentWrite) |
| 54 | + .setSrcStageMask(vk::PipelineStageFlagBits2::eColorAttachmentOutput) |
| 55 | + .setDstAccessMask(vk::AccessFlagBits2::eNone) |
| 56 | + .setDstStageMask(vk::PipelineStageFlagBits2::eBottomOfPipe); |
| 57 | +dependency_info.setImageMemoryBarriers(barrier); |
| 58 | +render_sync.command_buffer.pipelineBarrier2(dependency_info); |
| 59 | +``` |
| 60 | + |
| 61 | +End the command buffer and submit it: |
| 62 | + |
| 63 | +```cpp |
| 64 | +render_sync.command_buffer.end(); |
| 65 | + |
| 66 | +auto submit_info = vk::SubmitInfo2{}; |
| 67 | +auto const command_buffer_info = |
| 68 | + vk::CommandBufferSubmitInfo{render_sync.command_buffer}; |
| 69 | +auto wait_semaphore_info = vk::SemaphoreSubmitInfo{}; |
| 70 | +wait_semaphore_info.setSemaphore(*render_sync.draw) |
| 71 | + .setStageMask(vk::PipelineStageFlagBits2::eTopOfPipe); |
| 72 | +auto signal_semaphore_info = vk::SemaphoreSubmitInfo{}; |
| 73 | +signal_semaphore_info.setSemaphore(*render_sync.present) |
| 74 | + .setStageMask(vk::PipelineStageFlagBits2::eColorAttachmentOutput); |
| 75 | +submit_info.setCommandBufferInfos(command_buffer_info) |
| 76 | + .setWaitSemaphoreInfos(wait_semaphore_info) |
| 77 | + .setSignalSemaphoreInfos(signal_semaphore_info); |
| 78 | +m_queue.submit2(submit_info, *render_sync.drawn); |
| 79 | +``` |
| 80 | +
|
| 81 | +The `draw` Semaphore will be signaled by the Swapchain when the image is ready, which will trigger this command buffer's execution. It will signal the `present` Semaphore and `drawn` Fence on completion, with the latter being waited on the next time this virtual frame is processed. Finally, we increment the frame index, pass the `present` semaphore as the one for the subsequent present operation to wait on: |
| 82 | +
|
| 83 | +```cpp |
| 84 | +m_frame_index = (m_frame_index + 1) % m_render_sync.size(); |
| 85 | +
|
| 86 | +if (!m_swapchain->present(m_queue, *render_sync.present)) { |
| 87 | + m_swapchain->recreate(framebuffer_size); |
| 88 | + continue; |
| 89 | +} |
| 90 | +``` |
| 91 | + |
| 92 | +Make sure validation layers are enabled, and there are no reported errors on window resize / minimize / restore / etc. |
| 93 | + |
| 94 | +> Wayland users: congratulaions, you can finally see and interact with the window! |
| 95 | +
|
| 96 | +Note: while the window might appear to be showing black images, the image contents are technically undefined. OSs / compositors will zero-out memory for security reasons, but the spec doesn't involved. Capture a frame in [RenderDoc](https://renderdoc.org/) for confirmation: |
| 97 | + |
| 98 | + |
| 99 | + |
| 100 | +At the time of writing, RenderDoc doesn't support inspecting Wayland applications. Temporarily force X11 (XWayland) by calling `glfwInitHint()` before `glfwInit()`: |
| 101 | + |
| 102 | +```cpp |
| 103 | +glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_X11); |
| 104 | +``` |
| 105 | +
|
| 106 | +Setting up a command line option to conditionally call this is a simple and flexible approach: just set that argument in RenderDoc itself and/or pass it whenever an X11 backend is desired: |
| 107 | +
|
| 108 | +```cpp |
| 109 | +// main.cpp |
| 110 | +// skip the first argument. |
| 111 | +auto args = std::span{argv, static_cast<std::size_t>(argc)}.subspan(1); |
| 112 | +while (!args.empty()) { |
| 113 | + auto const arg = std::string_view{args.front()}; |
| 114 | + if (arg == "-x" || arg == "--force-x11") { |
| 115 | + glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_X11); |
| 116 | + } |
| 117 | + args = args.subspan(1); |
| 118 | +} |
| 119 | +lvk::App{}.run(); |
| 120 | +``` |
0 commit comments