This blog series is a part of the write-up assignments of our Game Engineering II class in the Master of Entertainment Arts & Engineering program at University of Utah.
The main purpose of this assignment is to move the responsibilities of creating and being in charge of the meshes, effects from our Graphics system to our Game class. There, the data being rendered is now no longer the Graphics own concern but being submitted by our game logic through some functions.
The first thing that we need to do is to add reference counting to our mesh and effect classes and remove the ownership of them from all systems. To achieve this, they need to be dynamically allocated instead of just being allocated on the stack. We created static factory functions, which will create a new pointer to a mesh/effect after it is successfully initialized. Also, we moved the initialization function and constructor/destructor to private, and delete the copy/move constructors and assign operators, making it very clear that any user can only create new meshes and effects through the factory function.
After our Game created some meshes and effects, they can be submitted to our Graphics system in pairs to be rendered later. The background color can also be submitted in a similar way.
Right now, we are running two threads at the same time, one for Game and one for Graphics, so we need two instances of a struct (let’s call these buckets) inside Graphics that caches all the information needed to render a frame.
When the game loop is running is deciding which to render in frame N, the game is filling up bucket A by submitting stuff to Graphics. At the same time, our Graphics system is rendering the stuff inside bucket B, which contains the information of frame N-1 that was submitted by the game loop a while ago. After our graphics finish rendering bucket B, it cleans up bucket B, and swap the two buckets. Now, Graphics is gonna start rendering the stuff in bucket A, and let the Game fill up bucket B!
If Graphics renders everything that the game submits immediately, then Graphics will be constantly waiting for Game to submit data, this will tightly couple our Graphics and Game system and can lead to a lot of time getting wasted. It might run fine if both parts are doing their work perfectly. But by separating them from each other, we can get a more steady system. Even though this means that what we’re displaying on screen is the information from the previous frame.
(two meshes with two effects submitted by Game)
(Holding “Z” down to make on mesh disappear)
(Holding “X” down to let both meshes use the same effect)
After adding reference counting to our classes, we need to check the memory usage again.
x86 (OpenGL): 20 Bytes
x64 (D3D): 32 Bytes
Memory usage went up 4 bytes for x86, which is expected since we added an unsigned int. However, x64 is using the same memory amount as before. This is because that there were 4 bytes wasted due to alignment (it was actually only using 28 Bytes).
So far, the mesh class is using as little memory as possible for both platforms. However, we can purposefully increase it by rearranging some of the private variables. We can increase the usage in x64 by moving a struct (12 bytes) between the two unsigned integers. Now the memory allocation will look like this,
4 bytes (unsigned int) + 4 bytes (alignment) + 24 bytes (struct) + 4 bytes (unsigned int) + 4 bytes (alignment)
But of course, we don’t want this.
x86 (OpenGL): 20 Bytes
x64 (D3D): 48 Bytes
x86 is now using 4 bytes more because of the new unsigned int. x64 is using 8 bytes more due to alignment (extra 4 bytes wasted). In both cases, this is the minimal usage that we can achieve even though there is some wastage, it’s inside a struct that’s provided by another piece of code. Now let’s try to make them bigger.
It turned out that we cannot make them bigger either.
x86 (OpenGL): 180 Bytes
x64 (D3D): 200 Bytes
Which means that Graphics is using totally 360/400 bytes to render all the frames. One struct in the bucket is an array that stores all the meshes and effects pointers that it needs to render. I don’t consider these as Graphic’s memory usage, but there should be some fixed amount of memory in our engine that can be allocated to 3d models, shaders, textures, etc.
In order to get the same system/simulation time that is being passed into to the Graphics RenderFrame() function to achieve background color changing, We need to look at how the base application class is submitting those time values to Graphics, which is inside the UpdateUntilExit() game loop. We can see that during each simulation update, tickCount_perSimulationUpdate is added to m_tickCount_simulationTime_totalElapsed. We can easily use the member variable with our time conversion function to convert the ticks to seconds. This won’t be the exact same value that is being passed into Graphics. Therefore, the background animation won’t look that smooth like before. However, I think it makes sense that this being the value that our Game class can gain access to since it should never really know what is going on inside Graphics.
Hold down “Z” to disappear the mesh on the right, hold down “X” to change the effect of the mesh on the left!
This assignment is focusing on separating our engine from our game loop, the game should be in charge of what should be rendered in which frames, while our Graphics shouldn’t have any knowledge of these except for just rendering them on the screen. Trying to come up with a good scheme to submit data from Game to Graphics, and also keep multi-threading in mind at the same time is definitely pretty challenging but also extremely fun!
Special thanks to
for some tips.