This week, I will be creating a binary mesh format that will be created from the Lua file during the build process. For this, I will be moving the Lua-loading code from the Graphics library to the AssetBuilder system, specifically the MeshBuilder application. This changed some things in the solution:
- Dependency on the Lua library moved from Graphics to the Mesh Builder project.
- Changing run-time error messages in the code to build-time error messages
- Making the sVertex struct accessible.
The last point lead to a design decision. This struct was in the private section of my Mesh class, and the easiest way to make this accessible was to move this to the public section, but then this also makes it accessible to other classes who include this header. While I am the only one who will be editing this code, I wish there was some way in C++ to make only certain parts of the class accessible instead of revealing the whole public section(would make abstraction a lot more “secure”?). For e.g. if a class needs only the sVertex, other public members should not be revealed. To my knowledge, this is not possible(maybe in other OO languages?), so I made the choice to make this struct public.
Once the Lua loading code was moved, the data that I got from the lua had to be converted to bytes that will be read by the Graphics library during run-time. The code that I chose for this is like this:
// Write vertexCount
char *vertexCount = NULL;
vertexCount = reinterpret_cast<char *>(&verticesCount);
This code essentially creates a char pointer(pointer to a 1-byte address) and points to the starting address of the vertexCount. The vertexCount itself is a 4-byte value, which means if I tell ofstream to write this buffer pointer with the size of the vertex count, it will write the 4 bytes starting from the vertexCount address. This approach is followed for the remaining data(vertices, indexCount and indices) as well.
My Binary Mesh format:
Shown above is my binary mesh format, where the order of data insertion is: vertex count, list of vertices, indices count and the list of indices. The reason I chose this order is because this is the order I maintain and create my vertex and index buffers in code.
Why must the number of elements in an array come before the actual array?
The reason for this is when I read the data from this mesh, say, I need to know how many vertices I need to read, or more precisely, how many bytes the list of vertices occupies in this file. The number lets me know exactly how many bytes by multiplying the count into the size of the sVertex struct(which is a total of 12 bytes). In this format, you can see the first four bytes tell me how many vertices there are(04 00 00 00 => 4), which is 4. Now I read the next 48 bytes as my list of vertices, since now I know the vertex count. Same goes for the indices as well.
Advantages of binary format:
- Faster loading of data during runtime. Converting floats or 32-bit integers to bytes takes time. Maybe not a lot, but we will have more vertices to deal with once we start reading data supplied from the Maya exporter which we will eventually make this semester, and the conversion time will become more significant.
- The size of the binary file is much smaller as opposed to a human-readable format. This is particularly useful during shipping games, as game data has to occupy a limited amount of space on DVDs, and binary format. This also applies for Steam games, which allows the user to download games faster and the games occupy less space(Yay for me personally, since I can have more games on my system).
- This advantage might not performance or memory-related, but there is also no reason to make these files human-readable, since a player is almost never going to look at these files, much less modify them. Although, I have seen some cases where some config files have had text formats, I personally never modify them unless I have to(for fixing glitches or bugs in games, instead of waiting for patches).
Binary formats at run-time VS human-readable formats at build time
This should be clear from the third point I mentioned before, but keeping human-readable formats only makes sense when there are potential “humans” who will read and, more importantly, modify them. Which is why we keep the lua files human-readable so that they can be clearly understood and easily modified by developers(only me at this point) and designers. However, I wonder if we will ever devise a way where changing these Lua files will dynamically change the output in our game(the way Unity works where you can move objects even during play mode).
As for the size advantage, my Mesh.lua file is 359 bytes whereas my binary Mesh file is only 80 bytes(4 times less than the Lua file, a significant difference).
Whether the built binary mesh files should be the same or different for the different platforms, and why
I believe for this assignment, the binary mesh files can be the same, since both Direct3D and OpenGL accepts int values for the buffer sizes(well, Direct3D accepts unsigned int and OpenGL accepts int, but both are 4 bytes on the Windows architecture).
However, a warning from Microsoft – Data Type Ranges
The int and unsigned int types have a size of four bytes. However, portable code should not depend on the size of int because the language standard allows this to be implementation-specific.
Meaning that for different architectures might consider int as occupying different sizes(maybe consoles?), and so care should be taken when reading or writing the amount of bytes from these binary files, since you may end up reading/writing more or less than the intended data.
Extracting the four pieces of data from the binary file when loaded at run-time
During run-time, I need to read in the binary data to populate the four pieces of information I need to render a mesh: vertex count, list of vertices, indices count and the list of indices.
I do this using the following code snippet:
The first read essentially converts the char pointer(1 byte) to an int pointer(4 byte), meaning when I de-reference the pointer, I get the data occupying the first four bytes of the buffer pointer, which is the vertex count. The next set of bytes should give me the list of vertices, but the starting point to read from is specified by the vertexCount(or more accurately, the size of the vertex count, which is 4 bytes). Now, if the list of vertices had come before the vertex count, I would not have known where to read the vertex count from, since I dont know the size of the list of vertices. Same goes for the indices as well. Since i know the number of vertices, I can easily offset the data bytes occupied by the vertex count and the list of vertices to get to the index data.
Once I obtain these four pieces of data, I do the same as before; create vertex and index buffers to be used by the Graphics library for rendering.
The second(and the easier part) was to encapsulate the vertex and fragment shader into an “Effect” class. I had to essentially have two platfom-independent interface functions accepting the path of the fragment and the vertex shader to create the platform-specific API objects, namely the IDirect3DVertexShader9 and the IDirect3DPixelShader9 objects for Direct3D and the GLuint programID for OpenGL. I made these objects as private members of my Effect class and added two public platform-independent functions; CreateEffect – for creating and loading shaders, and SetEffect for binding the shaders to the device(or program for OpenGL). In addition to this, I also have a platform-independent destructor, which will cleanup the platform-specific graphics objects once we are done with them.
My Effect class:
(apologies if it is small; clicking on it should make it look clearer)
Each of these functions have platform-specific implementations, which was just copied code from the Graphics library.
Code to load the effect:
where s_Effect is the global effect, and the CreateEffect takes the path of the fragment and vertex shader as arguments to load the respective shaders. This code is the same for both Direct3D and OpenGL versions, since I believe the goal for this assignment is to make the Graphics as much as platform-independent as possible(maybe completely; that would be cool).
Code to set the effect:
This code segment is also the same for both versions(I say segment because the code preceding this is still different where the image buffer is cleared).
I hope at the end of this semester we make the Graphics source to be completely platform-independent, though that seems to be an ideal goal at this point. But even so, there seems to be very little code that is platform-specific at this point, so this should be easy enough to achieve, so fingers crossed 😉
The output for this assignment is the same as last time:
Link to the build:
That’s all for this week. Following week is fall break, so no assignments( I hope, or not, since I enjoy doing these assignments)…
Stay tuned for more after fall break…