Skip to content

Commit 75b219c

Browse files
authored
Refactor render loop: use smaller, isolated functions (#7)
* Refactor render loop: use smaller, isolated functions * Use latest highlight.js with cpp and glsl grammars
1 parent 1561b3c commit 75b219c

File tree

4 files changed

+676
-207
lines changed

4 files changed

+676
-207
lines changed

guide/src/rendering/dynamic_rendering.md

Lines changed: 140 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,146 +1,179 @@
11
# Dynamic Rendering
22

3-
Dynamic Rendering enables us to avoid using Render Passes, which are quite a bit more verbose (but also generally more performant on tiled GPUs). Here we tie together the Swapchain, Render Sync, and rendering.
3+
Dynamic Rendering enables us to avoid using Render Passes, which are quite a bit more verbose (but also generally more performant on tiled GPUs). Here we tie together the Swapchain, Render Sync, and rendering. We are not ready to actually render anything yet, but can clear the image to a particular color.
44

5-
In the main loop, attempt to acquire a Swapchain image / Render Target:
5+
Add these new members to `App`:
66

77
```cpp
8-
auto const framebuffer_size = glfw::framebuffer_size(m_window.get());
9-
// minimized? skip loop.
10-
if (framebuffer_size.x <= 0 || framebuffer_size.y <= 0) { continue; }
11-
// an eErrorOutOfDateKHR result is not guaranteed if the
12-
// framebuffer size does not match the Swapchain image size, check it
13-
// explicitly.
14-
auto fb_size_changed = framebuffer_size != m_swapchain->get_size();
15-
auto& render_sync = m_render_sync.at(m_frame_index);
16-
auto render_target = m_swapchain->acquire_next_image(*render_sync.draw);
17-
if (fb_size_changed || !render_target) {
18-
m_swapchain->recreate(framebuffer_size);
19-
continue;
20-
}
8+
auto acquire_render_target() -> bool;
9+
auto wait_for_frame() -> vk::CommandBuffer;
10+
void transition_for_render(vk::CommandBuffer command_buffer) const;
11+
void render(vk::CommandBuffer command_buffer);
12+
void transition_for_present(vk::CommandBuffer command_buffer) const;
13+
void submit_and_present();
14+
15+
// ...
16+
glm::ivec2 m_framebuffer_size{};
17+
std::optional<RenderTarget> m_render_target{};
2118
```
2219
23-
Wait for the associated fence and reset ('un'signal) it:
20+
The main loop can now use these to implement the Swapchain and rendering loop:
2421
2522
```cpp
26-
static constexpr auto fence_timeout_v =
27-
static_cast<std::uint64_t>(std::chrono::nanoseconds{3s}.count());
28-
auto result = m_device->waitForFences(*render_sync.drawn, vk::True,
29-
fence_timeout_v);
30-
if (result != vk::Result::eSuccess) {
31-
throw std::runtime_error{"Failed to wait for Render Fence"};
23+
while (glfwWindowShouldClose(m_window.get()) == GLFW_FALSE) {
24+
glfwPollEvents();
25+
if (!acquire_render_target()) { continue; }
26+
auto const command_buffer = wait_for_frame();
27+
transition_for_render(command_buffer);
28+
render(command_buffer);
29+
transition_for_present(command_buffer);
30+
submit_and_present();
3231
}
33-
// reset fence _after_ acquisition of image: if it fails, the
34-
// fence remains signaled.
35-
m_device->resetFences(*render_sync.drawn);
3632
```
3733

38-
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:
34+
Acquire the Render Target:
3935

4036
```cpp
41-
auto command_buffer_bi = vk::CommandBufferBeginInfo{};
42-
// this flag means recorded commands will not be reused.
43-
command_buffer_bi.setFlags(
44-
vk::CommandBufferUsageFlagBits::eOneTimeSubmit);
45-
render_sync.command_buffer.begin(command_buffer_bi);
46-
```
47-
48-
We are not ready to actually render anything yet, but can clear the image to a particular color. First we need to transition the image for rendering, ie Attachment Optimal layout. Set up the image barrier and record it:
37+
auto App::acquire_render_target() -> bool {
38+
m_framebuffer_size = glfw::framebuffer_size(m_window.get());
39+
// minimized? skip loop.
40+
if (m_framebuffer_size.x <= 0 || m_framebuffer_size.y <= 0) {
41+
return false;
42+
}
43+
// an eErrorOutOfDateKHR result is not guaranteed if the
44+
// framebuffer size does not match the Swapchain image size, check it
45+
// explicitly.
46+
auto fb_size_changed = m_framebuffer_size != m_swapchain->get_size();
47+
auto& render_sync = m_render_sync.at(m_frame_index);
48+
m_render_target = m_swapchain->acquire_next_image(*render_sync.draw);
49+
if (fb_size_changed || !m_render_target) {
50+
m_swapchain->recreate(m_framebuffer_size);
51+
return false;
52+
}
4953

50-
```cpp
51-
auto dependency_info = vk::DependencyInfo{};
52-
auto barrier = m_swapchain->base_barrier();
53-
// Undefined => AttachmentOptimal
54-
// we don't need to block any operations before the barrier, since we
55-
// rely on the image acquired semaphore to block rendering.
56-
// any color attachment operations must happen after the barrier.
57-
barrier.setOldLayout(vk::ImageLayout::eUndefined)
58-
.setNewLayout(vk::ImageLayout::eAttachmentOptimal)
59-
.setSrcAccessMask(vk::AccessFlagBits2::eNone)
60-
.setSrcStageMask(vk::PipelineStageFlagBits2::eTopOfPipe)
61-
.setDstAccessMask(vk::AccessFlagBits2::eColorAttachmentWrite)
62-
.setDstStageMask(
63-
vk::PipelineStageFlagBits2::eColorAttachmentOutput);
64-
dependency_info.setImageMemoryBarriers(barrier);
65-
render_sync.command_buffer.pipelineBarrier2(dependency_info);
54+
return true;
55+
}
6656
```
6757

68-
Create an Rendering Attachment Info using the acquired image as the color target. We use a red clear color, make sure the Load Op clears the image, and Store Op stores the results (currently just the cleared image):
58+
Wait for the Fence associated with the current frame and reset ('un'signal) it, and begin Command Buffer recording:
6959

7060
```cpp
71-
auto attachment_info = vk::RenderingAttachmentInfo{};
72-
attachment_info.setImageView(render_target->image_view)
73-
.setImageLayout(vk::ImageLayout::eAttachmentOptimal)
74-
.setLoadOp(vk::AttachmentLoadOp::eClear)
75-
.setStoreOp(vk::AttachmentStoreOp::eStore)
76-
// temporarily red.
77-
.setClearValue(vk::ClearColorValue{1.0f, 0.0f, 0.0f, 1.0f});
61+
auto App::wait_for_frame() -> vk::CommandBuffer {
62+
auto const& render_sync = m_render_sync.at(m_frame_index);
63+
static constexpr auto fence_timeout_v =
64+
static_cast<std::uint64_t>(std::chrono::nanoseconds{3s}.count());
65+
auto result =
66+
m_device->waitForFences(*render_sync.drawn, vk::True, fence_timeout_v);
67+
if (result != vk::Result::eSuccess) {
68+
throw std::runtime_error{"Failed to wait for Render Fence"};
69+
}
70+
// reset fence _after_ acquisition of image: if it fails, the
71+
// fence remains signaled.
72+
m_device->resetFences(*render_sync.drawn);
73+
74+
auto command_buffer_bi = vk::CommandBufferBeginInfo{};
75+
// this flag means recorded commands will not be reused.
76+
command_buffer_bi.setFlags(vk::CommandBufferUsageFlagBits::eOneTimeSubmit);
77+
render_sync.command_buffer.begin(command_buffer_bi);
78+
return render_sync.command_buffer;
79+
}
7880
```
7981

80-
Set up a Rendering Info object with the color attachment and the entire image as the render area:
82+
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).
8183

82-
```cpp
83-
auto rendering_info = vk::RenderingInfo{};
84-
auto const render_area =
85-
vk::Rect2D{vk::Offset2D{}, render_target->extent};
86-
rendering_info.setRenderArea(render_area)
87-
.setColorAttachments(attachment_info)
88-
.setLayerCount(1);
89-
```
90-
91-
Finally, execute a render:
84+
Transition the image for rendering, ie Attachment Optimal layout. Set up the image barrier and record it:
9285

9386
```cpp
94-
render_sync.command_buffer.beginRendering(rendering_info);
95-
// draw stuff here.
96-
render_sync.command_buffer.endRendering();
87+
void App::transition_for_render(vk::CommandBuffer const command_buffer) const {
88+
auto dependency_info = vk::DependencyInfo{};
89+
auto barrier = m_swapchain->base_barrier();
90+
// Undefined => AttachmentOptimal
91+
// we don't need to block any operations before the barrier, since we
92+
// rely on the image acquired semaphore to block rendering.
93+
// any color attachment operations must happen after the barrier.
94+
barrier.setOldLayout(vk::ImageLayout::eUndefined)
95+
.setNewLayout(vk::ImageLayout::eAttachmentOptimal)
96+
.setSrcAccessMask(vk::AccessFlagBits2::eNone)
97+
.setSrcStageMask(vk::PipelineStageFlagBits2::eTopOfPipe)
98+
.setDstAccessMask(vk::AccessFlagBits2::eColorAttachmentWrite)
99+
.setDstStageMask(vk::PipelineStageFlagBits2::eColorAttachmentOutput);
100+
dependency_info.setImageMemoryBarriers(barrier);
101+
command_buffer.pipelineBarrier2(dependency_info);
102+
}
97103
```
98104
99-
Transition the image for presentation:
105+
Create an Rendering Attachment Info using the acquired image as the color target. We use a red clear color, make sure the Load Op clears the image, and Store Op stores the results (currently just the cleared image). Set up a Rendering Info object with the color attachment and the entire image as the render area. Finally, execute the render:
100106
101107
```cpp
102-
// AttachmentOptimal => PresentSrc
103-
// the barrier must wait for color attachment operations to complete.
104-
// we don't need any post-synchronization as the present Sempahore takes
105-
// care of that.
106-
barrier.setOldLayout(vk::ImageLayout::eAttachmentOptimal)
107-
.setNewLayout(vk::ImageLayout::ePresentSrcKHR)
108-
.setSrcAccessMask(vk::AccessFlagBits2::eColorAttachmentWrite)
109-
.setSrcStageMask(vk::PipelineStageFlagBits2::eColorAttachmentOutput)
110-
.setDstAccessMask(vk::AccessFlagBits2::eNone)
111-
.setDstStageMask(vk::PipelineStageFlagBits2::eBottomOfPipe);
112-
dependency_info.setImageMemoryBarriers(barrier);
113-
render_sync.command_buffer.pipelineBarrier2(dependency_info);
108+
void App::render(vk::CommandBuffer const command_buffer) {
109+
auto color_attachment = vk::RenderingAttachmentInfo{};
110+
color_attachment.setImageView(m_render_target->image_view)
111+
.setImageLayout(vk::ImageLayout::eAttachmentOptimal)
112+
.setLoadOp(vk::AttachmentLoadOp::eClear)
113+
.setStoreOp(vk::AttachmentStoreOp::eStore)
114+
// temporarily red.
115+
.setClearValue(vk::ClearColorValue{1.0f, 0.0f, 0.0f, 1.0f});
116+
auto rendering_info = vk::RenderingInfo{};
117+
auto const render_area =
118+
vk::Rect2D{vk::Offset2D{}, m_render_target->extent};
119+
rendering_info.setRenderArea(render_area)
120+
.setColorAttachments(color_attachment)
121+
.setLayerCount(1);
122+
123+
command_buffer.beginRendering(rendering_info);
124+
// draw stuff here.
125+
command_buffer.endRendering();
126+
}
114127
```
115128

116-
End the command buffer and submit it:
129+
Transition the image for presentation:
117130

118131
```cpp
119-
render_sync.command_buffer.end();
120-
121-
auto submit_info = vk::SubmitInfo2{};
122-
auto const command_buffer_info =
123-
vk::CommandBufferSubmitInfo{render_sync.command_buffer};
124-
auto wait_semaphore_info = vk::SemaphoreSubmitInfo{};
125-
wait_semaphore_info.setSemaphore(*render_sync.draw)
126-
.setStageMask(vk::PipelineStageFlagBits2::eTopOfPipe);
127-
auto signal_semaphore_info = vk::SemaphoreSubmitInfo{};
128-
signal_semaphore_info.setSemaphore(*render_sync.present)
129-
.setStageMask(vk::PipelineStageFlagBits2::eColorAttachmentOutput);
130-
submit_info.setCommandBufferInfos(command_buffer_info)
131-
.setWaitSemaphoreInfos(wait_semaphore_info)
132-
.setSignalSemaphoreInfos(signal_semaphore_info);
133-
m_queue.submit2(submit_info, *render_sync.drawn);
132+
void App::transition_for_present(vk::CommandBuffer const command_buffer) const {
133+
auto dependency_info = vk::DependencyInfo{};
134+
auto barrier = m_swapchain->base_barrier();
135+
// AttachmentOptimal => PresentSrc
136+
// the barrier must wait for color attachment operations to complete.
137+
// we don't need any post-synchronization as the present Sempahore takes
138+
// care of that.
139+
barrier.setOldLayout(vk::ImageLayout::eAttachmentOptimal)
140+
.setNewLayout(vk::ImageLayout::ePresentSrcKHR)
141+
.setSrcAccessMask(vk::AccessFlagBits2::eColorAttachmentWrite)
142+
.setSrcStageMask(vk::PipelineStageFlagBits2::eColorAttachmentOutput)
143+
.setDstAccessMask(vk::AccessFlagBits2::eNone)
144+
.setDstStageMask(vk::PipelineStageFlagBits2::eBottomOfPipe);
145+
dependency_info.setImageMemoryBarriers(barrier);
146+
command_buffer.pipelineBarrier2(dependency_info);
147+
}
134148
```
135149
136-
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:
150+
End the command buffer and submit it. 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:
137151
138152
```cpp
139-
m_frame_index = (m_frame_index + 1) % m_render_sync.size();
140-
141-
if (!m_swapchain->present(m_queue, *render_sync.present)) {
142-
m_swapchain->recreate(framebuffer_size);
143-
continue;
153+
void App::submit_and_present() {
154+
auto const& render_sync = m_render_sync.at(m_frame_index);
155+
render_sync.command_buffer.end();
156+
157+
auto submit_info = vk::SubmitInfo2{};
158+
auto const command_buffer_info =
159+
vk::CommandBufferSubmitInfo{render_sync.command_buffer};
160+
auto wait_semaphore_info = vk::SemaphoreSubmitInfo{};
161+
wait_semaphore_info.setSemaphore(*render_sync.draw)
162+
.setStageMask(vk::PipelineStageFlagBits2::eTopOfPipe);
163+
auto signal_semaphore_info = vk::SemaphoreSubmitInfo{};
164+
signal_semaphore_info.setSemaphore(*render_sync.present)
165+
.setStageMask(vk::PipelineStageFlagBits2::eColorAttachmentOutput);
166+
submit_info.setCommandBufferInfos(command_buffer_info)
167+
.setWaitSemaphoreInfos(wait_semaphore_info)
168+
.setSignalSemaphoreInfos(signal_semaphore_info);
169+
m_queue.submit2(submit_info, *render_sync.drawn);
170+
171+
m_frame_index = (m_frame_index + 1) % m_render_sync.size();
172+
m_render_target.reset();
173+
174+
if (!m_swapchain->present(m_queue, *render_sync.present)) {
175+
m_swapchain->recreate(m_framebuffer_size);
176+
}
144177
}
145178
```
146179

0 commit comments

Comments
 (0)