Skip to content

Commit 2b53742

Browse files
committed
Fix sync issue
1 parent 428e00d commit 2b53742

File tree

64 files changed

+312
-305
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+312
-305
lines changed

bookcontents/chapter-05/chapter-05.md

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -320,8 +320,7 @@ public class Queue {
320320
}
321321
long fenceHandle = fence != null ? fence.getVkFence() : VK_NULL_HANDLE;
322322

323-
vkCheck(vkQueueSubmit2(vkQueue, submitInfo, fenceHandle),
324-
"Failed to submit command to queue");
323+
vkCheck(vkQueueSubmit2(vkQueue, submitInfo, fenceHandle), "Failed to submit command to queue");
325324
}
326325
}
327326
...
@@ -367,7 +366,7 @@ Prior to progress on how we render graphics, we will analyze first how what we w
367366
command buffer and a command pool, of course having a queue to submit the commands and we will need something to do with the image views of the `SwapChain`.
368367
So, let's just create one instance of each and that's it, right? Well, it turns out that is not so easy. We need to make sure that the image we are rendering into
369368
is not in use. Ok, so let's then use a fence and a semaphore to prevent that, and that's all, right? Again, it is not so easy. Remember when we talked about the
370-
`SwapChain` class? We created several image views. We want to be able to perform operations in the CPU while the GPU is working, this is why we tried to use
369+
`SwapChain` class? We created several image views. We want to be able to perform operations in the CPU while the GPU is working, this is why we tried to use
371370
triple buffering. So we need to have several resources while processing each frame. How many of them? At first, you may think that you need as many as image views
372371
have the `SwapChain`image views. The reality, however, is that you do not need as many, with just two is enough. The reason is that we do not want the CPU to wait for
373372
the GPU to prevent having latency. We will refer to this number as frames in flight, which shall not be confused with total swap chain image views.
@@ -384,6 +383,32 @@ public class VkUtils {
384383
There is an excellent resource [here](https://docs.vulkan.org/tutorial/latest/03_Drawing_a_triangle/03_Drawing/03_Frames_in_flight.html)
385384
which provides additional information.
386385

386+
So then just create as many semaphores and fences as frames in flight an that's all, right? Again is not so easy!. We need to take care with swap chain image presentation.
387+
We will use a semaphore when submitting the work to a queue to be signaled after all the work has been done. We will use an array of semaphores for that, called
388+
`renderCompleteSemphs`. When presenting the acquired swap chain image we wil use the proper index `renderCompleteSemphs[i]` when calling the `vkQueuePresentKHR` function
389+
so presentation cannot start until render work has been finished. The issue here is that this will be an asynchronous call that can be processed later on. Imagine that we created as the `renderCompleteSemphs` array is size to contain as many instances as flights in frame, let's say `2` and we will have `3` swap chain images.
390+
391+
- In Frame `#0`:
392+
- We acquire the first swap chain image (with an index equal to `0`).
393+
- We submit the work using `renderCompleteSemphs[0]`.
394+
- We present the swap chain image (that is, call the `vkQueuePresentKHR` function using `renderCompleteSemphs[0]` sempahore).
395+
- In Frame `#1`:
396+
- We acquire the next swap chain image (with an index equal to `1`).
397+
- We submit the work using `renderCompleteSemphs[1]`.
398+
- We present the swap chain image (that is, call the `vkQueuePresentKHR` function using `renderCompleteSemphs[1]` sempahore).
399+
- Everything is ok up to this point.
400+
- In Frame `#2`:
401+
- We acquire the next swap chain image (with an index equal to `2`).
402+
- We have just `2` instances of `renderCompleteSemphs`, so we need to use `renderCompleteSemphs[0]`.
403+
- The issue here is that presentation for Frame `#0` may not have finished, and thus `renderCompleteSemphs[0]` may still be in use. We may have a synchronization issue here.
404+
405+
But, shouldn't fences help us prevent us from this issue? The answer is now, fences will be used when submitting work to a queue. Therefore, if we wait for a fence,
406+
we wil be sure that previous work associated to the same frame in flight index will have finished. The issue is with presentation, when presenting the swap chain
407+
image we just only pass a semaphore to wait to be signaled when the render work is finished, but we cannot signal when the presentation will be finished. Therefore, fence
408+
wait does not know anything about presentation state, The solution for this is to have as many render complete semaphores as swap chain images. The rest of synchronization
409+
elements and per-frame elements just need to be sized to the maximum number of flight, because they are concerned to just render activities, presentation is not involved
410+
at all.
411+
387412
Let's go now to the `Render` class and see the new attributes that we will need (showing the changes in the constructor and the `cleanup` method):
388413

389414
```java
@@ -410,13 +435,16 @@ public class Render {
410435
cmdPools = new CmdPool[VkUtils.MAX_IN_FLIGHT];
411436
cmdBuffers = new CmdBuffer[VkUtils.MAX_IN_FLIGHT];
412437
fences = new Fence[VkUtils.MAX_IN_FLIGHT];
413-
imageAqSemphs = new Semaphore[VkUtils.MAX_IN_FLIGHT];
414-
renderCompleteSemphs = new Semaphore[VkUtils.MAX_IN_FLIGHT];
438+
presCompleteSemphs = new Semaphore[VkUtils.MAX_IN_FLIGHT];
439+
int numSwapChainImages = vkCtx.getSwapChain().getNumImages();
440+
renderCompleteSemphs = new Semaphore[numSwapChainImages];
415441
for (int i = 0; i < VkUtils.MAX_IN_FLIGHT; i++) {
416442
cmdPools[i] = new CmdPool(vkCtx, graphQueue.getQueueFamilyIndex(), false);
417443
cmdBuffers[i] = new CmdBuffer(vkCtx, cmdPools[i], true, true);
418-
fences[i] = new Fence(vkCtx, true);
419444
imageAqSemphs[i] = new Semaphore(vkCtx);
445+
fences[i] = new Fence(vkCtx, true);
446+
}
447+
for (int i = 0; i < numSwapChainImages; i++) {
420448
renderCompleteSemphs[i] = new Semaphore(vkCtx);
421449
}
422450
scnRender = new ScnRender(vkCtx);
@@ -444,7 +472,8 @@ public class Render {
444472
We will need:
445473
- An array of command pools, whose size will be equal to the maximum number of flights in flight.
446474
- An array of command buffers, one per frame in flight where will be recording the commands for each of them.
447-
- Synchronization instances, semaphores and fences, as well, as many as frames in flight.
475+
- Synchronization instances, semaphores and fences, as well, as many as frames in flight with the exception of the semaphores that will be signaled when the render process
476+
is completed.
448477
- One one `Queue.GraphicsQueue` since we can use them in multiple frames. Synchronization wil be controlled by fences and semaphores.
449478
- You will see to new classes that have not been defined yet: `Queue.PresentQueue` (which will be used to present swap chain images)
450479
and `ScnRender` (to actually render a scene). We will see their definition later on.
@@ -499,9 +528,9 @@ public class Render {
499528

500529
recordingStop(cmdBuffer);
501530

502-
submit(cmdBuffer, currentFrame);
531+
submit(cmdBuffer, currentFrame, imageIndex);
503532

504-
swapChain.presentImage(presentQueue, renderCompleteSemphs[currentFrame], imageIndex);
533+
swapChain.presentImage(presentQueue, renderCompleteSemphs[imageIndex], imageIndex);
505534

506535
currentFrame = (currentFrame + 1) % VkUtils.MAX_IN_FLIGHT;
507536
}
@@ -518,7 +547,7 @@ The `render` loop performs the following actions:
518547
- We then call to `recordingStart` which resets the command pool and sets the command buffer in recording mode. Remember that we will not be resetting the command
519548
buffers but the pool. After this step we could start recording "A commands".
520549
- In our case, since we do not have "A commands" yet", we just acquire next swap chain image. We will see the implementation later on, but this method returns
521-
the index of the image acquired (it may not be just the next image index). The `imageAqSemphs` array is the semaphore used to synchronize image acquisitions.
550+
the index of the image acquired (it may not be just the next image index). The `imageAqSemphs` array is the semaphore used to synchronize image acquisition
522551
When the image is acquired, this semaphore will be signaled. Any operation depending on this image to be acquired, can use this semaphore as a blocking mechanism.
523552
- If the `acquireNextImage` returns a negative value, this will mean that the operation failed. This could be because the window has been resized. By now, we just return.
524553
- Then we can record "B commands" which we will do by calling `scnRender.render(vkCtx, cmdBuffer, imageIndex);`
@@ -530,7 +559,7 @@ The `submit` method is defined like this:
530559
```java
531560
public class Render {
532561
...
533-
private void submit(CmdBuffer cmdBuff, int currentFrame) {
562+
private void submit(CmdBuffer cmdBuff, int currentFrame, int imageIndex) {
534563
try (var stack = MemoryStack.stackPush()) {
535564
var fence = fences[currentFrame];
536565
fence.reset(vkCtx);
@@ -544,7 +573,7 @@ public class Render {
544573
VkSemaphoreSubmitInfo.Buffer signalSemphs = VkSemaphoreSubmitInfo.calloc(1, stack)
545574
.sType$Default()
546575
.stageMask(VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT)
547-
.semaphore(renderCompleteSemphs[currentFrame].getVkSemaphore());
576+
.semaphore(renderCompleteSemphs[imageIndex].getVkSemaphore());
548577
graphQueue.submit(cmds, waitSemphs, signalSemphs, fence);
549578
}
550579
}
@@ -560,8 +589,10 @@ since we depend on swap chain image view, we want to make sure that the image ha
560589
- `signalSemphs`: It holds a list of semaphores that will be signaled when all the commands have finished. Remember that we use semaphores for GPU-GPU synchronization. In this case, we are submitting the semaphore used in the swap chain presentation. This will provoke that the image cannot be presented until the commands have finished, that is, until
561590
render has finished. This is why we use the `VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT`, all the commands need to have finalized their journey through the pipeline.
562591

563-
Finally, we use the current `Fence` instance, this way we block the CPU from resetting command buffers that are still in use.
592+
Please notice that we have different array sizes for the image acquisition semaphores and the render complete semaphores. Later one (`renderCompleteSemphs`) will need to be
593+
in accessed with the swap chain acquire image index, while the first one (`imageAqSemphs`) will just need frame in flight index.
564594

595+
Finally, we use the current `Fence` instance, this way we block the CPU from resetting command buffers that are still in use.
565596

566597
Let's start now by reviewing the missing methods in `SwapChain` class. First method is `acquireNextImage`:
567598

bookcontents/chapter-07/chapter-07.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -781,9 +781,9 @@ public class Render {
781781

782782
recordingStop(cmdBuffer);
783783

784-
submit(cmdBuffer, currentFrame);
784+
submit(cmdBuffer, currentFrame, imageIndex);
785785

786-
resize = swapChain.presentImage(presentQueue, renderCompleteSemphs[currentFrame], imageIndex);
786+
resize = swapChain.presentImage(presentQueue, renderCompleteSemphs[imageIndex], imageIndex);
787787

788788
currentFrame = (currentFrame + 1) % VkUtils.MAX_IN_FLIGHT;
789789
}
@@ -802,6 +802,8 @@ public class Render {
802802
Arrays.asList(imageAqSemphs).forEach(i -> i.cleanup(vkCtx));
803803
for (int i = 0; i < VkUtils.MAX_IN_FLIGHT; i++) {
804804
imageAqSemphs[i] = new Semaphore(vkCtx);
805+
}
806+
for (int i = 0; i < vkCtx.getSwapChain().getNumImages(); i++) {
805807
renderCompleteSemphs[i] = new Semaphore(vkCtx);
806808
}
807809

bookcontents/chapter-11/chapter-11.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,9 +1157,9 @@ public class Render {
11571157

11581158
recordingStop(cmdBuffer);
11591159

1160-
submit(cmdBuffer, currentFrame);
1160+
submit(cmdBuffer, currentFrame, imageIndex);
11611161

1162-
resize = swapChain.presentImage(presentQueue, renderCompleteSemphs[currentFrame], imageIndex);
1162+
resize = swapChain.presentImage(presentQueue, renderCompleteSemphs[imageIndex], imageIndex);
11631163

11641164
currentFrame = (currentFrame + 1) % VkUtils.MAX_IN_FLIGHT;
11651165
}

bookcontents/vulkanbook.epub

-7 Bytes
Binary file not shown.

booksamples/chapter-05/src/main/java/org/vulkanb/eng/graph/Render.java

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public class Render {
1616
private final CmdPool[] cmdPools;
1717
private final Fence[] fences;
1818
private final Queue.GraphicsQueue graphQueue;
19-
private final Semaphore[] imageAqSemphs;
19+
private final Semaphore[] presCompleteSemphs;
2020
private final Queue.PresentQueue presentQueue;
2121
private final Semaphore[] renderCompleteSemphs;
2222
private final ScnRender scnRender;
@@ -33,13 +33,16 @@ public Render(EngCtx engCtx) {
3333
cmdPools = new CmdPool[VkUtils.MAX_IN_FLIGHT];
3434
cmdBuffers = new CmdBuffer[VkUtils.MAX_IN_FLIGHT];
3535
fences = new Fence[VkUtils.MAX_IN_FLIGHT];
36-
imageAqSemphs = new Semaphore[VkUtils.MAX_IN_FLIGHT];
37-
renderCompleteSemphs = new Semaphore[VkUtils.MAX_IN_FLIGHT];
36+
presCompleteSemphs = new Semaphore[VkUtils.MAX_IN_FLIGHT];
37+
int numSwapChainImages = vkCtx.getSwapChain().getNumImages();
38+
renderCompleteSemphs = new Semaphore[numSwapChainImages];
3839
for (int i = 0; i < VkUtils.MAX_IN_FLIGHT; i++) {
3940
cmdPools[i] = new CmdPool(vkCtx, graphQueue.getQueueFamilyIndex(), false);
4041
cmdBuffers[i] = new CmdBuffer(vkCtx, cmdPools[i], true, true);
42+
presCompleteSemphs[i] = new Semaphore(vkCtx);
4143
fences[i] = new Fence(vkCtx, true);
42-
imageAqSemphs[i] = new Semaphore(vkCtx);
44+
}
45+
for (int i = 0; i < numSwapChainImages; i++) {
4346
renderCompleteSemphs[i] = new Semaphore(vkCtx);
4447
}
4548
scnRender = new ScnRender(vkCtx);
@@ -51,7 +54,7 @@ public void cleanup() {
5154
scnRender.cleanup();
5255

5356
Arrays.asList(renderCompleteSemphs).forEach(i -> i.cleanup(vkCtx));
54-
Arrays.asList(imageAqSemphs).forEach(i -> i.cleanup(vkCtx));
57+
Arrays.asList(presCompleteSemphs).forEach(i -> i.cleanup(vkCtx));
5558
Arrays.asList(fences).forEach(i -> i.cleanup(vkCtx));
5659
for (int i = 0; i < cmdPools.length; i++) {
5760
cmdBuffers[i].cleanup(vkCtx, cmdPools[i]);
@@ -80,22 +83,22 @@ public void render(EngCtx engCtx) {
8083

8184
recordingStart(cmdPool, cmdBuffer);
8285

83-
int imageIndex = swapChain.acquireNextImage(vkCtx.getDevice(), imageAqSemphs[currentFrame]);
86+
int imageIndex = swapChain.acquireNextImage(vkCtx.getDevice(), presCompleteSemphs[currentFrame]);
8487
if (imageIndex < 0) {
8588
return;
8689
}
8790
scnRender.render(vkCtx, cmdBuffer, imageIndex);
8891

8992
recordingStop(cmdBuffer);
9093

91-
submit(cmdBuffer, currentFrame);
94+
submit(cmdBuffer, currentFrame, imageIndex);
9295

93-
swapChain.presentImage(presentQueue, renderCompleteSemphs[currentFrame], imageIndex);
96+
swapChain.presentImage(presentQueue, renderCompleteSemphs[imageIndex], imageIndex);
9497

9598
currentFrame = (currentFrame + 1) % VkUtils.MAX_IN_FLIGHT;
9699
}
97100

98-
private void submit(CmdBuffer cmdBuff, int currentFrame) {
101+
private void submit(CmdBuffer cmdBuff, int currentFrame, int imageIndex) {
99102
try (var stack = MemoryStack.stackPush()) {
100103
var fence = fences[currentFrame];
101104
fence.reset(vkCtx);
@@ -105,11 +108,11 @@ private void submit(CmdBuffer cmdBuff, int currentFrame) {
105108
VkSemaphoreSubmitInfo.Buffer waitSemphs = VkSemaphoreSubmitInfo.calloc(1, stack)
106109
.sType$Default()
107110
.stageMask(VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT)
108-
.semaphore(imageAqSemphs[currentFrame].getVkSemaphore());
111+
.semaphore(presCompleteSemphs[currentFrame].getVkSemaphore());
109112
VkSemaphoreSubmitInfo.Buffer signalSemphs = VkSemaphoreSubmitInfo.calloc(1, stack)
110113
.sType$Default()
111114
.stageMask(VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT)
112-
.semaphore(renderCompleteSemphs[currentFrame].getVkSemaphore());
115+
.semaphore(renderCompleteSemphs[imageIndex].getVkSemaphore());
113116
graphQueue.submit(cmds, waitSemphs, signalSemphs, fence);
114117
}
115118
}

booksamples/chapter-05/src/main/java/org/vulkanb/eng/graph/vk/Queue.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import org.lwjgl.vulkan.*;
66
import org.tinylog.Logger;
77

8-
import java.nio.*;
8+
import java.nio.IntBuffer;
99

1010
import static org.lwjgl.vulkan.VK13.*;
1111
import static org.vulkanb.eng.graph.vk.VkUtils.vkCheck;
@@ -47,8 +47,7 @@ public void submit(VkCommandBufferSubmitInfo.Buffer commandBuffers, VkSemaphoreS
4747
}
4848
long fenceHandle = fence != null ? fence.getVkFence() : VK_NULL_HANDLE;
4949

50-
vkCheck(vkQueueSubmit2(vkQueue, submitInfo, fenceHandle),
51-
"Failed to submit command to queue");
50+
vkCheck(vkQueueSubmit2(vkQueue, submitInfo, fenceHandle), "Failed to submit command to queue");
5251
}
5352
}
5453

booksamples/chapter-06/src/main/java/org/vulkanb/eng/graph/Render.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,15 @@ public Render(EngCtx engCtx) {
3838
cmdBuffers = new CmdBuffer[VkUtils.MAX_IN_FLIGHT];
3939
fences = new Fence[VkUtils.MAX_IN_FLIGHT];
4040
imageAqSemphs = new Semaphore[VkUtils.MAX_IN_FLIGHT];
41-
renderCompleteSemphs = new Semaphore[VkUtils.MAX_IN_FLIGHT];
41+
int numSwapChainImages = vkCtx.getSwapChain().getNumImages();
42+
renderCompleteSemphs = new Semaphore[numSwapChainImages];
4243
for (int i = 0; i < VkUtils.MAX_IN_FLIGHT; i++) {
4344
cmdPools[i] = new CmdPool(vkCtx, graphQueue.getQueueFamilyIndex(), false);
4445
cmdBuffers[i] = new CmdBuffer(vkCtx, cmdPools[i], true, true);
4546
fences[i] = new Fence(vkCtx, true);
4647
imageAqSemphs[i] = new Semaphore(vkCtx);
48+
}
49+
for (int i = 0; i < numSwapChainImages; i++) {
4750
renderCompleteSemphs[i] = new Semaphore(vkCtx);
4851
}
4952
scnRender = new ScnRender(vkCtx);
@@ -103,14 +106,14 @@ public void render(EngCtx engCtx) {
103106

104107
recordingStop(cmdBuffer);
105108

106-
submit(cmdBuffer, currentFrame);
109+
submit(cmdBuffer, currentFrame, imageIndex);
107110

108-
swapChain.presentImage(presentQueue, renderCompleteSemphs[currentFrame], imageIndex);
111+
swapChain.presentImage(presentQueue, renderCompleteSemphs[imageIndex], imageIndex);
109112

110113
currentFrame = (currentFrame + 1) % VkUtils.MAX_IN_FLIGHT;
111114
}
112115

113-
private void submit(CmdBuffer cmdBuff, int currentFrame) {
116+
private void submit(CmdBuffer cmdBuff, int currentFrame, int imageIndex) {
114117
try (var stack = MemoryStack.stackPush()) {
115118
var fence = fences[currentFrame];
116119
fence.reset(vkCtx);
@@ -124,7 +127,7 @@ private void submit(CmdBuffer cmdBuff, int currentFrame) {
124127
VkSemaphoreSubmitInfo.Buffer signalSemphs = VkSemaphoreSubmitInfo.calloc(1, stack)
125128
.sType$Default()
126129
.stageMask(VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT)
127-
.semaphore(renderCompleteSemphs[currentFrame].getVkSemaphore());
130+
.semaphore(renderCompleteSemphs[imageIndex].getVkSemaphore());
128131
graphQueue.submit(cmds, waitSemphs, signalSemphs, fence);
129132
}
130133
}

booksamples/chapter-06/src/main/java/org/vulkanb/eng/graph/vk/Queue.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import org.lwjgl.vulkan.*;
66
import org.tinylog.Logger;
77

8-
import java.nio.*;
8+
import java.nio.IntBuffer;
99

1010
import static org.lwjgl.vulkan.VK13.*;
1111
import static org.vulkanb.eng.graph.vk.VkUtils.vkCheck;
@@ -47,8 +47,7 @@ public void submit(VkCommandBufferSubmitInfo.Buffer commandBuffers, VkSemaphoreS
4747
}
4848
long fenceHandle = fence != null ? fence.getVkFence() : VK_NULL_HANDLE;
4949

50-
vkCheck(vkQueueSubmit2(vkQueue, submitInfo, fenceHandle),
51-
"Failed to submit command to queue");
50+
vkCheck(vkQueueSubmit2(vkQueue, submitInfo, fenceHandle), "Failed to submit command to queue");
5251
}
5352
}
5453

0 commit comments

Comments
 (0)