Skip to content

Commit ffc6918

Browse files
committed
Chapter 12 draft
Updated previous chapters due to VulkanBuffer map / unMap new methods
1 parent 2af22da commit ffc6918

File tree

182 files changed

+14629
-552
lines changed

Some content is hidden

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

182 files changed

+14629
-552
lines changed

bookcontents/chapter-06/chapter-06.md

Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public class VulkanBuffer {
1414
public VulkanBuffer(Device device, long size, int usage, int reqMask) {
1515
this.device = device;
1616
requestedSize = size;
17+
mappedMemory = NULL;
1718
try (MemoryStack stack = MemoryStack.stackPush()) {
1819
VkBufferCreateInfo bufferCreateInfo = VkBufferCreateInfo.callocStack(stack)
1920
.sType(VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO)
@@ -31,7 +32,7 @@ public class VulkanBuffer {
3132
}
3233
```
3334

34-
The constructor just receives the `device` that will be used to create this buffer, its size, a parameter named `usage` which will state what this buffer should be used for and a bit mask. This last parameter is use to set the requested memory properties that the data associated to this buffer should use. We will review how these two last parameters are used later on. In order to create a Buffer we need to setup a structure named `VkBufferCreateInfo`, which defines the following attributes:
35+
The constructor just receives the `device` that will be used to create this buffer, its size, a parameter named `usage` which will state what this buffer should be used for and a bit mask. This last parameter is use to set the requested memory properties that the data associated to this buffer should use. We will review how these two last parameters are used later on. The class also defines an attribute named `mappedMemory` which is a handle that will be used when mapping the buffer memory so it can be accessed from our application (if the buffer is created with the appropriate flags to be accessible from the CPU). In order to create a Buffer we need to setup a structure named `VkBufferCreateInfo`, which defines the following attributes:
3536

3637
- `sType`: It shall have the `VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO` value.
3738
- `size`: The number of bytes that the buffer will hold.
@@ -99,7 +100,7 @@ public class VulkanUtils {
99100
}
100101
```
101102

102-
The `typeBits` attribute is a bit mask which defines the supported memory types of the physical device. A bit set to `1` means that the type of memory (associated to that index) is supported. The `reqMask` attribute is the type of memory that we need (for example if that memory will be accessed only by the GPU or also by the application). This method basically iterates over all the memory types, checking if that memory index (first condition) is supported by the device and if that it meets the requested type (second condition). Now we can go back to the `VulkanBuffer` constructor and invoke the `vkAllocateMemory` to allocate the memory. After that we can get the finally allocated size and get a handle to that chunk of memory.
103+
The `typeBits` attribute is a bit mask which defines the supported memory types of the physical device. A bit set to `1` means that the type of memory (associated to that index) is supported. The `reqMask` attribute is the type of memory that we need (for example if that memory will be accessed only by the GPU or also by the application). This method basically iterates over all the memory types, checking if that memory index (first condition) is supported by the device and if that it meets the requested type (second condition). Now we can go back to the `VulkanBuffer` constructor and invoke the `vkAllocateMemory` to allocate the memory. After that we can get the finally allocated size and get a handle to that chunk of memory. We also allocate a `PointerBuffer` which will be used in other methods of the class.
103104

104105
```java
105106
public class VulkanBuffer {
@@ -109,6 +110,7 @@ public class VulkanBuffer {
109110
vkCheck(vkAllocateMemory(device.getVkDevice(), memAlloc, null, lp), "Failed to allocate memory");
110111
allocationSize = memAlloc.allocationSize();
111112
memory = lp.get(0);
113+
pb = PointerBuffer.allocateDirect(1);
112114
...
113115
}
114116
...
@@ -139,24 +141,39 @@ public class VulkanBuffer {
139141
vkFreeMemory(device.getVkDevice(), memory, null);
140142
}
141143

142-
public long getAllocationSize() {
143-
return allocationSize;
144-
}
145-
146144
public long getBuffer() {
147145
return buffer;
148146
}
149147

150-
public long getMemory() {
151-
return memory;
152-
}
153-
154148
public long getRequestedSize() {
155149
return requestedSize;
156150
}
151+
...
157152
}
158153
```
159154

155+
To complete the class, we define two methods to map and un-map the memory associated to the buffer so it can be accessed from our application (if they have been created with the flag `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT`, more on this later on). The `map` method just calls the `vkMapMemory` function which returns a handle that can be used to get a buffer to read / write its contents. The `unMap` method just calls the `vkUnmapMemory` to un-map the previously mapped buffer memory:
156+
```java
157+
public class VulkanBuffer {
158+
...
159+
public long map() {
160+
if (mappedMemory == NULL) {
161+
vkCheck(vkMapMemory(device.getVkDevice(), memory, 0, allocationSize, 0, pb), "Failed to map Buffer");
162+
mappedMemory = pb.get(0);
163+
}
164+
return mappedMemory;
165+
}
166+
167+
public void unMap() {
168+
if (mappedMemory != NULL) {
169+
vkUnmapMemory(device.getVkDevice(), memory);
170+
mappedMemory = NULL;
171+
}
172+
}
173+
}
174+
175+
```
176+
160177
## Vertex description
161178

162179
We have now created the buffers required to hold the data for vertices, the next step is to describe to Vulkan the format of that data. As you can guess, depending on the specific case, the structure of that data may change, we may have just position coordinates, or position with texture coordinates and normals, etc. Some of the vulkan elements that we will define later on, will need a handle to that structure. In order to support this, we will create an abstract class named `VertexInputStateInfo`, which just stores the handle to a `VkPipelineVertexInputStateCreateInfo` structure:
@@ -437,16 +454,10 @@ public class VulkanMesh {
437454
VulkanBuffer dstBuffer = new VulkanBuffer(device, bufferSize,
438455
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
439456

440-
try (MemoryStack stack = MemoryStack.stackPush()) {
441-
PointerBuffer pp = stack.mallocPointer(1);
442-
vkCheck(vkMapMemory(device.getVkDevice(), srcBuffer.getMemory(), 0, srcBuffer.getAllocationSize(), 0, pp),
443-
"Failed to map memory");
444-
445-
FloatBuffer data = pp.getFloatBuffer(0, numPositions);
446-
data.put(positions);
447-
448-
vkUnmapMemory(device.getVkDevice(), srcBuffer.getMemory());
449-
}
457+
long mappedMemory = srcBuffer.map();
458+
FloatBuffer data = MemoryUtil.memFloatBuffer(mappedMemory, (int) srcBuffer.getRequestedSize());
459+
data.put(positions);
460+
srcBuffer.unMap();
450461

451462
return new TransferBuffers(srcBuffer, dstBuffer);
452463
}
@@ -461,7 +472,7 @@ The intermediate buffer is created with the `VK_BUFFER_USAGE_TRANSFER_SRC_BIT` f
461472

462473
The destination buffer is created with the `VK_BUFFER_USAGE_TRANSFER_DST_BIT` as its usage parameter. With this flag we state that this buffer can used as the destination of a transfer command. We also set the flag `VK_BUFFER_USAGE_VERTEX_BUFFER_BIT` since it will be used for handling vertices data. For the `reqMask` attribute we use the `VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT` flag which states that the memory allocated by this buffer will be used by the GPU.
463474

464-
Once the buffers have been created we need to populate the source buffer. In order to do that, we need to map that memory in order to get a pointer to it so we can upload the data. This is done by calling the `vkMapMemory` function. Now we have a pointer to the memory of the buffer which we will use to load the positions. After we have finished copying the data to the source buffer we call the `vkUnmapMemory`.
475+
Once the buffers have been created we need to populate the source buffer. In order to do that, we need to map that memory in order to get a pointer to it so we can upload the data. This is done by calling the `map` method on the buffer instance. Now we have a pointer to the memory of the buffer which we will use to load the positions. After we have finished copying the data to the source buffer we call the `unMap` method over the buffer.
465476

466477
The definition of the `createIndicesBuffers` is similar:
467478

@@ -478,16 +489,10 @@ public class VulkanMesh {
478489
VulkanBuffer dstBuffer = new VulkanBuffer(device, bufferSize,
479490
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
480491

481-
try (MemoryStack stack = MemoryStack.stackPush()) {
482-
PointerBuffer pp = stack.mallocPointer(1);
483-
vkCheck(vkMapMemory(device.getVkDevice(), srcBuffer.getMemory(), 0, srcBuffer.getAllocationSize(), 0, pp),
484-
"Failed to map memory");
485-
486-
IntBuffer data = pp.getIntBuffer(0, numIndices);
487-
data.put(indices);
488-
489-
vkUnmapMemory(device.getVkDevice(), srcBuffer.getMemory());
490-
}
492+
long mappedMemory = srcBuffer.map();
493+
IntBuffer data = MemoryUtil.memIntBuffer(mappedMemory, (int) srcBuffer.getRequestedSize());
494+
data.put(indices);
495+
srcBuffer.unMap();
491496

492497
return new TransferBuffers(srcBuffer, dstBuffer);
493498
}

bookcontents/chapter-07/chapter-07.md

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -294,26 +294,22 @@ public class VulkanMesh {
294294
VulkanBuffer dstBuffer = new VulkanBuffer(device, bufferSize,
295295
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
296296

297-
try (MemoryStack stack = MemoryStack.stackPush()) {
298-
PointerBuffer pp = stack.mallocPointer(1);
299-
vkCheck(vkMapMemory(device.getVkDevice(), srcBuffer.getMemory(), 0, srcBuffer.getAllocationSize(), 0, pp),
300-
"Failed to map memory");
301-
302-
int rows = positions.length / 3;
303-
FloatBuffer data = pp.getFloatBuffer(0, numElements);
304-
for (int row = 0; row < rows; row++) {
305-
int startPos = row * 3;
306-
int startTextCoord = row * 2;
307-
data.put(positions[startPos + 0]);
308-
data.put(positions[startPos + 1]);
309-
data.put(positions[startPos + 2]);
310-
data.put(textCoords[startTextCoord + 0]);
311-
data.put(textCoords[startTextCoord + 1]);
312-
}
313-
314-
vkUnmapMemory(device.getVkDevice(), srcBuffer.getMemory());
297+
long mappedMemory = srcBuffer.map();
298+
FloatBuffer data = MemoryUtil.memFloatBuffer(mappedMemory, (int) srcBuffer.getRequestedSize());
299+
300+
int rows = positions.length / 3;
301+
for (int row = 0; row < rows; row++) {
302+
int startPos = row * 3;
303+
int startTextCoord = row * 2;
304+
data.put(positions[startPos]);
305+
data.put(positions[startPos + 1]);
306+
data.put(positions[startPos + 2]);
307+
data.put(textCoords[startTextCoord]);
308+
data.put(textCoords[startTextCoord + 1]);
315309
}
316310

311+
srcBuffer.unMap();
312+
317313
return new TransferBuffers(srcBuffer, dstBuffer);
318314
}
319315
...

bookcontents/chapter-08/chapter-08.md

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ public class Texture {
343343
height = h.get();
344344
mipLevels = 1;
345345

346-
createStgBuffer(stack, device, buf);
346+
createStgBuffer(device, buf);
347347
image = new Image(device, width, height, imageFormat, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
348348
mipLevels, 1);
349349
imageView = new ImageView(device, image.getVkImage(), image.getFormat(), VK_IMAGE_ASPECT_COLOR_BIT, mipLevels);
@@ -368,25 +368,21 @@ At the end of the constructor we create an `ImageView` associated to the image a
368368
// RGBA
369369
private static final int BYTES_PER_PIXEL = 4;
370370
...
371-
private void createStgBuffer(MemoryStack stack, Device device, ByteBuffer data) {
371+
private void createStgBuffer(Device device, ByteBuffer data) {
372372
int size = width * height * BYTES_PER_PIXEL;
373373
stgBuffer = new VulkanBuffer(device, size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
374374
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
375-
PointerBuffer pp = stack.mallocPointer(1);
376-
vkCheck(vkMapMemory(device.getVkDevice(), stgBuffer.getMemory(), 0,
377-
stgBuffer.getAllocationSize(), 0, pp), "Failed to map memory");
378-
379-
ByteBuffer buffer = pp.getByteBuffer(size);
375+
long mappedMemory = stgBuffer.map();
376+
ByteBuffer buffer = MemoryUtil.memByteBuffer(mappedMemory, (int) stgBuffer.getRequestedSize());
380377
buffer.put(data);
381378
data.flip();
382-
383-
vkUnmapMemory(device.getVkDevice(), stgBuffer.getMemory());
379+
stgBuffer.unMap();
384380
}
385381
...
386382
}
387383
```
388384

389-
We just create a new `VulkanBuffer` instance which size will be calculated assuming a RGBA model with one byte per channel. The buffer will be used to transfer the image data, this is why we use the `VK_BUFFER_USAGE_TRANSFER_SRC_BIT` flag and the `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT` (we will be loading the image from our application). We do not want to perform any flush operation while transferring the data so we also use the `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT` flag. After the buffer has been created, we just map the memory associated to it ans we just copy the image contents.
385+
We just create a new `VulkanBuffer` instance which size will be calculated assuming a RGBA model with one byte per channel. The buffer will be used to transfer the image data, this is why we use the `VK_BUFFER_USAGE_TRANSFER_SRC_BIT` flag and the `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT` (we will be loading the image from our application). We do not want to perform any flush operation while transferring the data so we also use the `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT` flag. After the buffer has been created, we just map the memory associated to it and copy the image contents.
390386

391387
The `Texture` class defines a `cleanup` method to free the resources and special method named `cleanupStgBuffer` to free the staging buffer when is no longer needed. It also provides some *getters* to get the path to the file used to load the texture and the image view.
392388

@@ -1214,7 +1210,7 @@ public class ForwardRenderActivity {
12141210
...
12151211
public ForwardRenderActivity(SwapChain swapChain, CommandPool commandPool, PipelineCache pipelineCache, Scene scene) {
12161212
...
1217-
VulkanUtils.copyMatrixToBuffer(device, projMatrixUniform, scene.getPerspective().getPerspectiveMatrix());
1213+
VulkanUtils.copyMatrixToBuffer(projMatrixUniform, scene.getPerspective().getPerspectiveMatrix());
12181214
}
12191215
...
12201216
}
@@ -1223,16 +1219,11 @@ The `copyMatrixToBuffer` method is defined like this:
12231219
```java
12241220
public class VulkanUtils {
12251221
...
1226-
private void copyMatrixToBuffer(VulkanBuffer vulkanBuffer, Matrix4f matrix) {
1227-
try (MemoryStack stack = MemoryStack.stackPush()) {
1228-
PointerBuffer pointerBuffer = stack.mallocPointer(1);
1229-
vkCheck(vkMapMemory(device.getVkDevice(), vulkanBuffer.getMemory(), 0, vulkanBuffer.getAllocationSize(),
1230-
0, pointerBuffer), "Failed to map UBO memory");
1231-
long data = pointerBuffer.get(0);
1232-
ByteBuffer matrixBuffer = MemoryUtil.memByteBuffer(data, (int) vulkanBuffer.getAllocationSize());
1233-
matrix.get(0, matrixBuffer);
1234-
vkUnmapMemory(device.getVkDevice(), vulkanBuffer.getMemory());
1235-
}
1222+
public static void copyMatrixToBuffer(VulkanBuffer vulkanBuffer, Matrix4f matrix) {
1223+
long mappedMemory = vulkanBuffer.map();
1224+
ByteBuffer matrixBuffer = MemoryUtil.memByteBuffer(mappedMemory, (int) vulkanBuffer.getRequestedSize());
1225+
matrix.get(0, matrixBuffer);
1226+
vulkanBuffer.unMap();
12361227
}
12371228
...
12381229
}
@@ -1332,7 +1323,7 @@ The `resize` method needs also to be modified to update the buffer that will bac
13321323
public class ForwardRenderActivity {
13331324
...
13341325
public void resize(SwapChain swapChain, Scene scene) {
1335-
VulkanUtils.copyMatrixToBuffer(device, projMatrixUniform, scene.getPerspective().getPerspectiveMatrix());
1326+
VulkanUtils.copyMatrixToBuffer(projMatrixUniform, scene.getPerspective().getPerspectiveMatrix());
13361327
...
13371328
}
13381329
...

bookcontents/chapter-09/chapter-09.md

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -728,7 +728,7 @@ public class ForwardRenderActivity {
728728
vulkanMesh.getTexture(), textureSampler, 0);
729729
descriptorSetMap.put(textureFileName, textureDescriptorSet);
730730
}
731-
updateMaterial(device, materialsBuffer, vulkanMesh.getMaterial(), materialOffset);
731+
updateMaterial(materialsBuffer, vulkanMesh.getMaterial(), materialOffset);
732732
meshCount++;
733733
}
734734
}
@@ -737,25 +737,18 @@ public class ForwardRenderActivity {
737737
```
738738

739739
As you can see, we calculate the offset in the buffer, to update the material data by calling the `updateMaterial` data:
740-
741740
```java
742741
public class ForwardRenderActivity {
743742
...
744-
private void updateMaterial(Device device, VulkanBuffer vulkanBuffer, Material material, int offset) {
745-
try (MemoryStack stack = MemoryStack.stackPush()) {
746-
PointerBuffer pointerBuffer = stack.mallocPointer(1);
747-
vkCheck(vkMapMemory(device.getVkDevice(), vulkanBuffer.getMemory(), offset,
748-
materialDescriptorSetLayout.getMaterialSize(), 0, pointerBuffer), "Failed to map UBO memory");
749-
long data = pointerBuffer.get(0);
750-
ByteBuffer materialBuffer = MemoryUtil.memByteBuffer(data, (int) vulkanBuffer.getAllocationSize());
751-
material.getDiffuseColor().get(0, materialBuffer);
752-
vkUnmapMemory(device.getVkDevice(), vulkanBuffer.getMemory());
753-
}
743+
private void updateMaterial(VulkanBuffer vulkanBuffer, Material material, int offset) {
744+
long mappedMemory = vulkanBuffer.map();
745+
ByteBuffer materialBuffer = MemoryUtil.memByteBuffer(mappedMemory, (int) vulkanBuffer.getRequestedSize());
746+
material.getDiffuseColor().get(offset, materialBuffer);
747+
vulkanBuffer.unMap();
754748
}
755749
...
756750
}
757751
```
758-
759752
In the `updateMaterial` method, we just map the buffer and dump the material diffuse color to the appropriate offset.
760753

761754
Finally, we just need to update a little bit the way we record the commands to bind the additional descriptor sets and to use the dynamic uniform buffers:
@@ -769,7 +762,7 @@ public class ForwardRenderActivity {
769762
.put(0, projMatrixDescriptorSet.getVkDescriptorSet())
770763
.put(1, viewMatricesDescriptorSets[idx].getVkDescriptorSet())
771764
.put(3, materialsDescriptorSet.getVkDescriptorSet());
772-
VulkanUtils.copyMatrixToBuffer(device, viewMatricesBuffer[idx], scene.getCamera().getViewMatrix());
765+
VulkanUtils.copyMatrixToBuffer(viewMatricesBuffer[idx], scene.getCamera().getViewMatrix());
773766
IntBuffer dynDescrSetOffset = stack.callocInt(1);
774767
int meshCount = 0;
775768
for (VulkanMesh mesh : meshes) {

0 commit comments

Comments
 (0)