Skip to content

Commit 51b28a7

Browse files
committed
Sync with chapter 21
1 parent d630a7c commit 51b28a7

File tree

1 file changed

+86
-42
lines changed

1 file changed

+86
-42
lines changed

chapter-20/chapter-20.md

Lines changed: 86 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -670,17 +670,24 @@ The main changes are related to the way we access material information and textu
670670
Now it is the turn to examine the changes in the `SceneRender` class. We will start by defining a set of constants that will be used in the code, one handle for the buffer that will have the indirect drawing instructions (`staticRenderBufferHandle`) and the number of drawing commands (`staticDrawCount`). We will need also to modify the `createUniforms` method according to the changes in the shaders shown before:
671671

672672
```java
673-
public class RenderBuffers {
673+
public class SceneRender {
674674
...
675675
public static final int MAX_DRAW_ELEMENTS = 100;
676676
public static final int MAX_ENTITIES = 50;
677677
private static final int COMMAND_SIZE = 5 * 4;
678678
private static final int MAX_MATERIALS = 20;
679679
private static final int MAX_TEXTURES = 16;
680680
...
681+
private Map<String, Integer> entitiesIdxMap;
682+
...
681683
private int staticDrawCount;
682684
private int staticRenderBufferHandle;
683685
...
686+
public SceneRender() {
687+
...
688+
entitiesIdxMap = new HashMap<>();
689+
}
690+
684691
private void createUniforms() {
685692
uniformsMap = new UniformsMap(shaderProgram.getProgramId());
686693
uniformsMap.createUniform("projectionMatrix");
@@ -713,10 +720,10 @@ public class RenderBuffers {
713720
}
714721
```
715722

716-
The main changes are in the `render` method, which is defined like this:
723+
The `entitiesIdxMap` will store the position in the list of entities associated to a model which each entity is located. We store that information in a `Map` using entity identifier as key. We will need this info later on since, the indirect drawing commands will be recorded iterating over meshes associated to each model. The main changes are in the `render` method, which is defined like this:
717724

718725
```java
719-
public class RenderBuffers {
726+
public class SceneRender {
720727
...
721728
public void render(Scene scene, RenderBuffers renderBuffers, GBuffer gBuffer) {
722729
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gBuffer.getGBufferId());
@@ -752,6 +759,19 @@ public class RenderBuffers {
752759
}
753760

754761
// Static meshes
762+
int drawElement = 0;
763+
List<Model> modelList = scene.getModelMap().values().stream().filter(m -> !m.isAnimated()).toList();
764+
for (Model model : modelList) {
765+
List<Entity> entities = model.getEntitiesList();
766+
for (RenderBuffers.MeshDrawData meshDrawData : model.getMeshDrawDataList()) {
767+
for (Entity entity : entities) {
768+
String name = "drawElements[" + drawElement + "]";
769+
uniformsMap.setUniform(name + ".modelMatrixIdx", entitiesIdxMap.get(entity.getId()));
770+
uniformsMap.setUniform(name + ".materialIdx", meshDrawData.materialIdx());
771+
drawElement++;
772+
}
773+
}
774+
}
755775
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, staticRenderBufferHandle);
756776
glBindVertexArray(renderBuffers.getStaticVaoId());
757777
glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, 0, staticDrawCount, 0);
@@ -764,42 +784,21 @@ public class RenderBuffers {
764784
}
765785
```
766786

767-
You can see that we now have to bind the array of texture samplers and activate all the texture units. In addition to that, we iterate over the entities and set up the uniform values for the model matrices. After that, we call the `glMultiDrawElementsIndirect` function to perform the indirect drawing. Prior to that, we need to bind the buffers that hold drawing instructions (drawing commands) and the VAO that holds the meshes and indices data. But, when do we populate the buffer for indirect drawing=? The answer is that this not need to be performed each render call, if there are no changes in the number of entities, you can record that buffer once, and use it in each render call. In this specific example, we will just populate that buffer at start-up. This means, that, if you want to make changes in the number of entities, you would nee to re-create that buffer again (you should do that for your own engine).
787+
You can see that we now have to bind the array of texture samplers and activate all the texture units. In addition to that, we iterate over the entities and set up the uniform values for the model matrices. The next step is to setup the `drawElements` array uniform withe the proper values for each of the entities that will point to the index of the model matrix and the material index. After that, we call the `glMultiDrawElementsIndirect` function to perform the indirect drawing. Prior to that, we need to bind the buffers that hold drawing instructions (drawing commands) and the VAO that holds the meshes and indices data. But, when do we populate the buffer for indirect drawing? The answer is that this not need to be performed each render call, if there are no changes in the number of entities, you can record that buffer once, and use it in each render call. In this specific example, we will just populate that buffer at start-up. This means, that, if you want to make changes in the number of entities, you would nee to re-create that buffer again (you should do that for your own engine).
768788

769-
The method that actually builds the indirect draw buffer is called `setupStaticCommandBuffer` and starts like this:
789+
The method that actually builds the indirect draw buffer is called `setupStaticCommandBuffer` which is defined like this:
770790
```java
771-
public class RenderBuffers {
791+
public class SceneRender {
772792
...
773793
private void setupStaticCommandBuffer(Scene scene) {
774794
List<Model> modelList = scene.getModelMap().values().stream().filter(m -> !m.isAnimated()).toList();
775-
Map<String, Integer> entitiesIdxMap = new HashMap<>();
776-
int entityIdx = 0;
777795
int numMeshes = 0;
778-
for (Model model : scene.getModelMap().values()) {
779-
List<Entity> entities = model.getEntitiesList();
796+
for (Model model : modelList) {
780797
numMeshes += model.getMeshDrawDataList().size();
781-
for (Entity entity : entities) {
782-
entitiesIdxMap.put(entity.getId(), entityIdx);
783-
entityIdx++;
784-
}
785798
}
786-
...
787-
}
788-
...
789-
}
790-
```
791-
792-
We firs start by iterating over the models to get the position in the list of entity instances each instance is. We store that information in a `Map`` using entity identifier as key. We will need this info later on since, the indirect drawing commands will be recorded iterating over meshes associated to each model. In addition to that, we calculate the total number of meshes. After that, we will create the buffer that wil hold indirect drawing instructions and populate it:
793799

794-
```java
795-
public class RenderBuffers {
796-
...
797-
private void setupStaticCommandBuffer(Scene scene) {
798-
...
799800
int firstIndex = 0;
800801
int baseInstance = 0;
801-
int drawElement = 0;
802-
shaderProgram.bind();
803802
ByteBuffer commandBuffer = MemoryUtil.memAlloc(numMeshes * COMMAND_SIZE);
804803
for (Model model : modelList) {
805804
List<Entity> entities = model.getEntitiesList();
@@ -816,16 +815,9 @@ public class RenderBuffers {
816815

817816
firstIndex += meshDrawData.vertices();
818817
baseInstance += entities.size();
819-
820-
for (Entity entity : entities) {
821-
String name = "drawElements[" + drawElement + "]";
822-
uniformsMap.setUniform(name + ".modelMatrixIdx", entitiesIdxMap.get(entity.getId()));
823-
drawElement++;
824-
}
825818
}
826819
}
827820
commandBuffer.flip();
828-
shaderProgram.unbind();
829821

830822
staticDrawCount = commandBuffer.remaining() / COMMAND_SIZE;
831823

@@ -839,13 +831,13 @@ public class RenderBuffers {
839831
}
840832
```
841833

842-
As you can see we firs allocate a `ByteBuffer`. This buffer will hold as many instruction sets as meshes. Each set of draw instructions si composed by five attributes, ech of the with a length of 4 bytes (total length of each set of parameters is what defines the `COMMAND_SIZE` constant). We cannot allocate this buffer using `MemoryStack` since we will run out of space quickly (the stack that LWJGL uses for this is limited in size). Therefore, we need to allocate it using `MemoryUtil` and remember to manually de-allocate that once we are done. Once we have the buffer we start iterating over the meshes associated to the model. You may have a look at the beginning of this chapter to check
834+
We first calculate the total number of meshes. After that, we will create the buffer that wil hold indirect drawing instructions and populate it. As you can see we first allocate a `ByteBuffer`. This buffer will hold as many instruction sets as meshes. Each set of draw instructions si composed by five attributes, each of them with a length of 4 bytes (total length of each set of parameters is what defines the `COMMAND_SIZE` constant). We cannot allocate this buffer using `MemoryStack` since we will run out of space quickly (the stack that LWJGL uses for this is limited in size). Therefore, we need to allocate it using `MemoryUtil` and remember to manually de-allocate that once we are done. Once we have the buffer we start iterating over the meshes associated to the model. You may have a look at the beginning of this chapter to check
843835
the struct that draw indirect requires. In addition to that, we also populate the `drawElements` uniform using the `Map` we calculated previously, to properly get the model matrix index for each entity. Finally, we just create a GPU buffer and dump the data into it.
844836

845837
We will need to update the `cleanup` method to free the indirect drawing buffer:
846838

847839
```java
848-
public class RenderBuffers {
840+
public class SceneRender {
849841
...
850842
public void cleanup() {
851843
shaderProgram.cleanup();
@@ -858,7 +850,7 @@ public class RenderBuffers {
858850
We will need a new method to the set up the values for the materials uniform:
859851

860852
```java
861-
public class RenderBuffers {
853+
public class SceneRender {
862854
...
863855
private void setupMaterialsUniform(TextureCache textureCache, MaterialCache materialCache) {
864856
List<Texture> textures = textureCache.getAll().stream().toList();
@@ -898,12 +890,32 @@ public class RenderBuffers {
898890

899891
We just check that we are not surpassing the maximum number of supported textures (`MAX_TEXTURES`) and just create an array of materials information with the information we used in the previous chapters. The only change is that we will need to store the index of the associated texture and normal maps in the material information.
900892

893+
We need another method to update the entities indices map:
894+
```java
895+
public class SceneRender {
896+
...
897+
private void setupEntitiesData(Scene scene) {
898+
entitiesIdxMap.clear();
899+
int entityIdx = 0;
900+
for (Model model : scene.getModelMap().values()) {
901+
List<Entity> entities = model.getEntitiesList();
902+
for (Entity entity : entities) {
903+
entitiesIdxMap.put(entity.getId(), entityIdx);
904+
entityIdx++;
905+
}
906+
}
907+
}
908+
...
909+
}
910+
```
911+
901912
To complete the changes in the `SceneRender` class, we will create a method that wraps the `setupXX` so it can be invoked from the `Render` class:
902913

903914
```java
904-
public class RenderBuffers {
915+
public class SceneRender {
905916
...
906917
public void setupData(Scene scene) {
918+
setupEntitiesData(scene);
907919
setupStaticCommandBuffer(scene);
908920
setupMaterialsUniform(scene.getTextureCache(), scene.getMaterialCache());
909921
}
@@ -945,15 +957,22 @@ void main()
945957
}
946958
```
947959

948-
Changes in `ShadowRender` are also pretty similar as the ones in the `ScenRender` class:
960+
Changes in `ShadowRender` are also pretty similar as the ones in the `SceneRender` class:
949961

950962
```java
951963
public class ShadowRender {
952964

953965
private static final int COMMAND_SIZE = 5 * 4;
954966
...
967+
private Map<String, Integer> entitiesIdxMap;
968+
...
955969
private int staticRenderBufferHandle;
956970
...
971+
public ShadowRender() {
972+
...
973+
entitiesIdxMap = new HashMap<>();
974+
}
975+
957976
public void cleanup() {
958977
shaderProgram.cleanup();
959978
shadowBuffer.cleanup();
@@ -975,7 +994,7 @@ public class ShadowRender {
975994
}
976995
```
977996

978-
The `createUniforms` method needs to be update to use the new uniforms and the `cleanup` one needs to free the indirect draw buffer. The `render` methdod will use now the `glMultiDrawElementsIndirect`instead of submitting individal draw commands for meshes and entities:
997+
The `createUniforms` method needs to be update to use the new uniforms and the `cleanup` one needs to free the indirect draw buffer. The `render` method will use now the `glMultiDrawElementsIndirect`instead of submitting individual draw commands for meshes and entities:
979998

980999
```java
9811000
public class ShadowRender {
@@ -1003,6 +1022,18 @@ public class ShadowRender {
10031022
}
10041023

10051024
// Static meshes
1025+
int drawElement = 0;
1026+
List<Model> modelList = scene.getModelMap().values().stream().filter(m -> !m.isAnimated()).toList();
1027+
for (Model model : modelList) {
1028+
List<Entity> entities = model.getEntitiesList();
1029+
for (RenderBuffers.MeshDrawData meshDrawData : model.getMeshDrawDataList()) {
1030+
for (Entity entity : entities) {
1031+
String name = "drawElements[" + drawElement + "]";
1032+
uniformsMap.setUniform(name + ".modelMatrixIdx", entitiesIdxMap.get(entity.getId()));
1033+
drawElement++;
1034+
}
1035+
}
1036+
}
10061037
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, staticRenderBufferHandle);
10071038
glBindVertexArray(renderBuffers.getStaticVaoId());
10081039
for (int i = 0; i < CascadeShadow.SHADOW_MAP_CASCADE_COUNT; i++) {
@@ -1021,15 +1052,28 @@ public class ShadowRender {
10211052
...
10221053
}
10231054
```
1024-
Finally, we need a similar method to set up the indirect draw buffer (which will be encpasulated into a `setupData` just to maintain the same style):
1055+
Finally, we need a similar method to set up the indirect draw buffer and the entities map:
10251056

10261057
```java
10271058
public class ShadowRender {
10281059
...
10291060
public void setupData(Scene scene) {
1061+
setupEntitiesData(scene);
10301062
setupStaticCommandBuffer(scene);
10311063
}
10321064

1065+
private void setupEntitiesData(Scene scene) {
1066+
entitiesIdxMap.clear();
1067+
int entityIdx = 0;
1068+
for (Model model : scene.getModelMap().values()) {
1069+
List<Entity> entities = model.getEntitiesList();
1070+
for (Entity entity : entities) {
1071+
entitiesIdxMap.put(entity.getId(), entityIdx);
1072+
entityIdx++;
1073+
}
1074+
}
1075+
}
1076+
10331077
private void setupStaticCommandBuffer(Scene scene) {
10341078
List<Model> modelList = scene.getModelMap().values().stream().filter(m -> !m.isAnimated()).toList();
10351079
Map<String, Integer> entitiesIdxMap = new HashMap<>();

0 commit comments

Comments
 (0)