Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6eb3ff2

Browse files
committed
Revert "Change drawFrame to use fence for synchronizing image acquisition with drawing"
This reverts commit 57e802d.
1 parent 15ba377 commit 6eb3ff2

14 files changed

Lines changed: 226 additions & 406 deletions

03_Drawing_a_triangle/03_Drawing/02_Rendering_and_presentation.md

Lines changed: 44 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -44,31 +44,23 @@ The difference is that the state of fences can be accessed from your program
4444
using calls like `vkWaitForFences` and semaphores cannot be. Fences are mainly
4545
designed to synchronize your application itself with rendering operation,
4646
whereas semaphores are used to synchronize operations within or across command
47-
queues. We will use a fence to synchronize swap chain image acquisition and a
48-
semaphore to synchronize drawing commands with presentation.
47+
queues. We want to synchronize the queue operations of draw commands and
48+
presentation, which makes semaphores the best fit.
4949

50-
## Fence
50+
## Semaphores
5151

52-
A fence is a synchronization primitive in Vulkan that allows you to synchronize
53-
your program with GPU operations. You can set up GPU operations like drawing
54-
commands to put such a fence is a *signalled* state when they are completed and
55-
use a function call like `vkWaitForFences` in your own program to wait for them
56-
to become signalled. This is useful because many functions in Vulkan that start
57-
operations return immediately and the actual operation is finished at some point
58-
in the background. With fences you can wait for one or more of these operations
59-
to finish before continuing code execution.
60-
61-
We'll be using a fence to signal that an image has been acquired from the swap
62-
chain and is ready for rendering. Create a class member to store this fence
63-
object:
52+
We'll need one semaphore to signal that an image has been acquired and is ready
53+
for rendering, and another one to signal that rendering has finished and
54+
presentation can happen. Create two class members to store these semaphore
55+
objects:
6456

6557
```c++
66-
VkFence imageAvailableFence;
58+
VkSemaphore imageAvailableSemaphore;
59+
VkSemaphore renderFinishedSemaphore;
6760
```
6861

69-
To create the fence, we'll add the last `create` function for this part of the
70-
tutorial: `createSynchronizationPrimitives`. It's named that way because we'll
71-
also create the semaphore in this function later on.
62+
To create the semaphores, we'll add the last `create` function for this part of
63+
the tutorial: `createSemaphores`:
7264

7365
```c++
7466
void initVulkan() {
@@ -84,92 +76,46 @@ void initVulkan() {
8476
createFramebuffers();
8577
createCommandPool();
8678
createCommandBuffers();
87-
createSynchronizationPrimitives();
79+
createSemaphores();
8880
}
8981

9082
...
9183

92-
void createSynchronizationPrimitives() {
93-
94-
}
95-
```
96-
97-
Creating a fence requires filling in the `VkFenceCreateInfo` struct, but in the
98-
current version of the API we'll only need to fill in the `sType` field. There
99-
is an optional `flags` field that allows you to initialize a fence to already be
100-
signalled, but we don't need that.
101-
102-
```c++
103-
void createSynchronizationPrimitives() {
104-
VkFenceCreateInfo fenceInfo = {};
105-
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
106-
}
107-
```
108-
109-
Creating the fence follows the familiar pattern with `vkCreateFence`:
84+
void createSemaphores() {
11085

111-
```c++
112-
if (vkCreateFence(device, &fenceInfo, nullptr, &imageAvailableFence) != VK_SUCCESS) {
113-
throw std::runtime_error("failed to create fence!");
11486
}
11587
```
11688

117-
The fence should be cleaned up at the end of the program, when all commands have
118-
finished and no more synchronization is necessary:
119-
120-
```c++
121-
void cleanup() {
122-
vkDestroyFence(device, imageAvailableFence, nullptr);
123-
```
124-
125-
## Semaphore
126-
127-
We will also need to synchronize the completion of drawing operations with the
128-
operation to present an image to the screen. We could accomplish this with
129-
fences as well, but for this operation we'll look at another synchronization
130-
primitive: *semaphores*. Fences are required for synchronization between the
131-
code on the CPU and operations on the GPU, but semaphores are a more efficient
132-
way to synchronize only GPU operations.
133-
134-
We'll need a semaphore to signal that rendering has finished and presentation
135-
can happen. Add a class member to store this semaphore object:
136-
137-
```c++
138-
VkFence imageAvailableFence;
139-
VkSemaphore renderFinishedSemaphore;
140-
```
141-
142-
We'll continue working in the `createSynchronizationPrimitives` function.
143-
Creating semaphores requires filling in the `VkSemaphoreCreateInfo` struct, but
144-
in the current version of the API it doesn't actually have any required fields
145-
besides `sType`:
89+
Creating semaphores requires filling in the `VkSemaphoreCreateInfo`, but in the
90+
current version of the API it doesn't actually have any required fields besides
91+
`sType`:
14692

14793
```c++
148-
void createSynchronizationPrimitives() {
149-
...
150-
94+
void createSemaphores() {
15195
VkSemaphoreCreateInfo semaphoreInfo = {};
15296
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
15397
}
15498
```
15599

156100
Future versions of the Vulkan API or extensions may add functionality for the
157101
`flags` and `pNext` parameters like it does for the other structures. Creating
158-
a semaphore is done through `vkCreateSemaphore`:
102+
the semaphores follows the familiar pattern with `vkCreateSemaphore`:
159103

160104
```c++
161-
if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphore) != VK_SUCCESS) {
162-
throw std::runtime_error("failed to create semaphore!");
105+
if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphore) != VK_SUCCESS ||
106+
vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphore) != VK_SUCCESS) {
107+
108+
throw std::runtime_error("failed to create semaphores!");
163109
}
164110
```
165111
166-
The semaphore should be cleaned up at the end of the program, when all commands
112+
The semaphores should be cleaned up at the end of the program, when all commands
167113
have finished and no more synchronization is necessary:
168114
169115
```c++
170116
void cleanup() {
171117
vkDestroySemaphore(device, renderFinishedSemaphore, nullptr);
172-
vkDestroyFence(device, imageAvailableFence, nullptr);
118+
vkDestroySemaphore(device, imageAvailableSemaphore, nullptr);
173119
```
174120

175121
## Acquiring an image from the swap chain
@@ -182,7 +128,7 @@ convention:
182128
```c++
183129
void drawFrame() {
184130
uint32_t imageIndex;
185-
vkAcquireNextImageKHR(device, swapChain, std::numeric_limits<uint64_t>::max(), VK_NULL_HANDLE, imageAvailableFence, &imageIndex);
131+
vkAcquireNextImageKHR(device, swapChain, std::numeric_limits<uint64_t>::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex);
186132
}
187133
```
188134

@@ -194,34 +140,14 @@ maximum value of a 64 bit unsigned integer disables the timeout.
194140
The next two parameters specify synchronization objects that are to be signaled
195141
when the presentation engine is finished using the image. That's the point in
196142
time where we can start drawing to it. It is possible to specify a semaphore,
197-
fence or both. We're going to use our `imageAvailableFence` for that purpose
143+
fence or both. We're going to use our `imageAvailableSemaphore` for that purpose
198144
here.
199145

200146
The last parameter specifies a variable to output the index of the swap chain
201147
image that has become available. The index refers to the `VkImage` in our
202148
`swapChainImages` array. We're going to use that index to pick the right command
203149
buffer.
204150

205-
This function returns immediately, possibly before an image is actually
206-
available. To wait for this to happen, we should now wait on our fence to be
207-
signalled using `vkWaitForFences`:
208-
209-
```c++
210-
vkWaitForFences(device, 1, &imageAvailableFence, VK_TRUE, std::numeric_limits<uint64_t>::max());
211-
```
212-
213-
This function takes an array of fences to wait on. The fourth parameter can be
214-
set to `VK_TRUE` to indicate that all fences should be signalled or `VK_FALSE`
215-
if only one is sufficient. In our case it doesn't make a difference. The last
216-
parameter is a timeout parameter, just like the one in `vkAcquireNextImageKHR`.
217-
218-
The `vkWaitForFences` function doesn't automatically reset the fence, so it is
219-
still signalled at this point. We should reset it to be used for the next frame:
220-
221-
```c++
222-
vkResetFences(device, 1, &imageAvailableFence);
223-
```
224-
225151
## Submitting the command buffer
226152

227153
Queue submission and synchronization is configured through parameters in the
@@ -231,15 +157,20 @@ Queue submission and synchronization is configured through parameters in the
231157
VkSubmitInfo submitInfo = {};
232158
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
233159

234-
submitInfo.waitSemaphoreCount = 0; // Optional
235-
submitInfo.pWaitSemaphores = nullptr; // Optional
236-
submitInfo.pWaitDstStageMask = nullptr; // Optional
160+
VkSemaphore waitSemaphores[] = {imageAvailableSemaphore};
161+
VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
162+
submitInfo.waitSemaphoreCount = 1;
163+
submitInfo.pWaitSemaphores = waitSemaphores;
164+
submitInfo.pWaitDstStageMask = waitStages;
237165
```
238166
239-
The rendering operations should wait with writing to the image until after it
240-
has successfully been acquired. We've already explicitly synchronized this by
241-
waiting on the image acquisition fence to be signalled before submitting the
242-
drawing command buffer in the first place, so no semaphore is necessary here.
167+
The first three parameters specify which semaphores to wait on before execution
168+
begins and in which stage(s) of the pipeline to wait. We want to wait with
169+
writing colors to the image until it's available, so we're specifying the stage
170+
of the graphics pipeline that writes to the color attachment. That means that
171+
theoretically the implementation can already start executing our vertex shader
172+
and such while the image is not available yet. Each entry in the `waitStages`
173+
array corresponds to the semaphore with the same index in `pWaitSemaphores`.
243174
244175
```c++
245176
submitInfo.commandBufferCount = 1;
@@ -268,9 +199,10 @@ if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS)
268199

269200
We can now submit the command buffer to the graphics queue using
270201
`vkQueueSubmit`. The function takes an array of `VkSubmitInfo` structures as
271-
argument for efficiency when the workload is much larger. The last parameter can
272-
be used to signal a fence once the drawing commands have finished execution, but
273-
we're using a semaphore in this case so we'll just pass a `VK_NULL_HANDLE` here.
202+
argument for efficiency when the workload is much larger. The last parameter
203+
references an optional fence that will be signaled when the command buffers
204+
finish execution. We're using semaphores for synchronization, so we'll just pass
205+
a `VK_NULL_HANDLE`.
274206

275207
## Subpass dependencies
276208

@@ -392,7 +324,7 @@ from `debugCallback` tell us why:
392324
393325
![](/images/semaphore_in_use.png)
394326
395-
Remember that many of the operations in `drawFrame` are asynchronous. That means
327+
Remember that all of the operations in `drawFrame` are asynchronous. That means
396328
that when we exit the loop in `mainLoop`, drawing and presentation operations
397329
may still be going on. Cleaning up resources while that is happening is a bad
398330
idea.
@@ -416,43 +348,9 @@ You can also wait for operations in a specific command queue to be finished with
416348
perform synchronization. You'll see that the program now exits without problems
417349
when closing the window.
418350

419-
## Fence versus semaphore
420-
421-
We now used a fence to synchronize image acquisition with drawing commands and a
422-
semaphore to synchronize drawing with presentation. As you saw, it is also
423-
possible for `vkAcquireNextImageKHR` to signal a semaphore instead of a fence
424-
and have `vkQueueSubmit` wait on a semaphore through fields in `VkSubmitInfo`.
425-
426-
Doing this synchronization with a semaphore would look like this:
427-
428-
```c++
429-
vkAcquireNextImageKHR(device, swapChain, std::numeric_limits<uint64_t>::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex);
430-
431-
...
432-
433-
VkSemaphore waitSemaphores[] = {imageAvailableSemaphore};
434-
VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
435-
submitInfo.waitSemaphoreCount = 1;
436-
submitInfo.pWaitSemaphores = waitSemaphores;
437-
submitInfo.pWaitDstStageMask = waitStages;
438-
```
439-
440-
The rendering operations would then wait with writing colors to the image until
441-
it's available. That means that theoretically the implementation can already
442-
start executing our vertex shader and such while the image is not available yet.
443-
Each entry in the `waitStages` array corresponds to the semaphore with the same
444-
index in `pWaitSemaphores`.
445-
446-
Semaphores offer more fine-grained control over synchronization than fences, and
447-
therefore they should always be used if possible. However, if you use no
448-
explicit synchronization with the CPU code at all through either fences or other
449-
commands like `vkQueueWaitIdle`, then it is harder for the validation layers to
450-
properly function and this may lead to memory leaks. That's why I've opted to
451-
explicitly synchronize image acquisition with a fence for this tutorial.
452-
453351
## Conclusion
454352

455-
About 900 lines of code later, we've finally gotten to the stage of seeing
353+
About 800 lines of code later, we've finally gotten to the stage of seeing
456354
something pop up on the screen! Bootstrapping a Vulkan program is definitely a
457355
lot of work, but the take-away message is that Vulkan gives you an immense
458356
amount of control through its explicitness. I recommend you to take some time

code/depth_buffering.cpp

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ class HelloTriangleApplication {
184184

185185
std::vector<VkCommandBuffer> commandBuffers;
186186

187-
VkFence imageAvailableFence;
187+
VkSemaphore imageAvailableSemaphore;
188188
VkSemaphore renderFinishedSemaphore;
189189

190190
void initWindow() {
@@ -221,7 +221,7 @@ class HelloTriangleApplication {
221221
createDescriptorPool();
222222
createDescriptorSet();
223223
createCommandBuffers();
224-
createSynchronizationPrimitives();
224+
createSemaphores();
225225
}
226226

227227
void mainLoop() {
@@ -279,7 +279,7 @@ class HelloTriangleApplication {
279279
vkFreeMemory(device, vertexBufferMemory, nullptr);
280280

281281
vkDestroySemaphore(device, renderFinishedSemaphore, nullptr);
282-
vkDestroyFence(device, imageAvailableFence, nullptr);
282+
vkDestroySemaphore(device, imageAvailableSemaphore, nullptr);
283283

284284
vkDestroyCommandPool(device, commandPool, nullptr);
285285

@@ -1204,19 +1204,14 @@ class HelloTriangleApplication {
12041204
}
12051205
}
12061206

1207-
void createSynchronizationPrimitives() {
1208-
VkFenceCreateInfo fenceInfo = {};
1209-
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
1210-
1211-
if (vkCreateFence(device, &fenceInfo, nullptr, &imageAvailableFence) != VK_SUCCESS) {
1212-
throw std::runtime_error("failed to create fence!");
1213-
}
1214-
1207+
void createSemaphores() {
12151208
VkSemaphoreCreateInfo semaphoreInfo = {};
12161209
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
12171210

1218-
if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphore) != VK_SUCCESS) {
1219-
throw std::runtime_error("failed to create semaphore!");
1211+
if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphore) != VK_SUCCESS ||
1212+
vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphore) != VK_SUCCESS) {
1213+
1214+
throw std::runtime_error("failed to create semaphores!");
12201215
}
12211216
}
12221217

@@ -1240,7 +1235,7 @@ class HelloTriangleApplication {
12401235

12411236
void drawFrame() {
12421237
uint32_t imageIndex;
1243-
VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits<uint64_t>::max(), VK_NULL_HANDLE, imageAvailableFence, &imageIndex);
1238+
VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits<uint64_t>::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex);
12441239

12451240
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
12461241
recreateSwapChain();
@@ -1249,15 +1244,14 @@ class HelloTriangleApplication {
12491244
throw std::runtime_error("failed to acquire swap chain image!");
12501245
}
12511246

1252-
vkWaitForFences(device, 1, &imageAvailableFence, VK_TRUE, std::numeric_limits<uint64_t>::max());
1253-
vkResetFences(device, 1, &imageAvailableFence);
1254-
12551247
VkSubmitInfo submitInfo = {};
12561248
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
12571249

1258-
submitInfo.waitSemaphoreCount = 0;
1259-
submitInfo.pWaitSemaphores = nullptr;
1260-
submitInfo.pWaitDstStageMask = nullptr;
1250+
VkSemaphore waitSemaphores[] = {imageAvailableSemaphore};
1251+
VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
1252+
submitInfo.waitSemaphoreCount = 1;
1253+
submitInfo.pWaitSemaphores = waitSemaphores;
1254+
submitInfo.pWaitDstStageMask = waitStages;
12611255

12621256
submitInfo.commandBufferCount = 1;
12631257
submitInfo.pCommandBuffers = &commandBuffers[imageIndex];

0 commit comments

Comments
 (0)