Moving Mesh and Effect Code from Engine to Game:
The main goal of this exercise is to move the mesh and effect initialization to game instead of the engine. By doing this we are abstracting the engine from the data as engine should be able to process any type of data but doesn’t have to know what that data means. In games, the gameplay programmer can just specify which mesh and effect he wants the engine to render and the engine must be able to render that.
Sending the Background Color to render
eae6320::Graphics::SetBackBufferValue(eae6320::Graphics::sColor{ abs(sin(i_elapsedSecondCount_systemTime)),abs(cos(i_elapsedSecondCount_systemTime)),abs(cos(i_elapsedSecondCount_systemTime)), 1});
Output of changing the background colors.
The first problem that we encounter is how to pass the data from the game thread and the application thread. We use a struct(sDataRequiredToRenderAFrame) that is used to store the data required to render a frame. We first create the data in the game and send that data to the engine to store in this struct to render. Since our game and the renderer runs on two different threads if we use only one struct, the renderer thread would be waiting for the game thread to fill the data and the game thread will be waiting for the renderer to finish rendering the current frame.
To maximize efficiency, we make the game thread populate the data that is required to render the next frame while the render thread is rendering the current frame. To achieve this we use two structs, one to store the data being rendered in the current frame and one to store the data we want to render in the next frame and after the current frame ends, we swap the data in the two structs.
Since we are creating our effects and meshes in the game instead of engine, we should restrict the access to mesh and effect classes and should not allow calling the constructor and destructor. Instead we implemented a Factory function which creates a new mesh or effect and gives us the pointer to the created mesh or effect. Since we are dealing with pointers, there is a possibility that the game or renderer might free the pointer when the other one is using, and this might create undefined behavior. To mitigate this, we use reference counting to keep track if the pointer is being used and when the game or renderer no longer uses the pointer, we decrement reference count and once reference count reaches zero, we then free the pointer. The framework for reference counting was already present in the engine and we just have to implement it.
To pass the mesh and effect data between game and engine, I am using a array of struct which takes an effect and mesh in that order and the renderer then first binds the effect and then draws the mesh in the same order
m_EffectsAndMeshes[0].m_RenderEffect=s_Effect; m_EffectsAndMeshes[0].m_RenderMesh=s_Mesh; m_EffectsAndMeshes[1].m_RenderEffect=s_Effect2; m_EffectsAndMeshes[1].m_RenderMesh=s_Mesh2; eae6320::Graphics::SetEffectsAndMeshesToRender(m_EffectsAndMeshes,m_NumberOfMeshesToRender);
Hiding the meshes and swapping the effects:
Since we moved the code to initialize and submit the effects and meshes to game, we can also specify which ones to render and which effect goes on which one. In my game, we can hide a mesh by pressing F1 key and swap effects between the two by holding the F2 key.
Removing mesh
Swap effect:
The reason why we submit all the data required to render a frame when the renderer is rendering the previous frame is that the renderer will know what to render in the next frame and this eliminates the renderer waiting for data to be submitted by the application.
Size of Mesh, Effect and sDataRequiredToRenderAFrame:
After making the mesh and effect reference counted, the size of mesh and effect turned out to be 20 bytes in OpenGL, 40 and 48 Bytes respectively in Direct3D. The size of the struct was 168 bytes in OpenGL and 176 Bytes in Direct3D. After rearranging the member variables in the struct, the size of mesh and effect came down as shown below.
Breakdown of sDataRequiredToRenderAFrame
Member | Size |
Constant data required per frame | 144 |
Color struct which holds four floats | 16 |
The struct which holds the effect and mesh to render | 8 / 16 * ( 10 pairs) |
unsigned int containing the number of mesh effect pairs being rendered | 4 |
Before Optimization:
Mesh | Effect | sDataRequiredToRenderAFrame | |
OpenGL | 20 | 20 | 244 |
DirectX | 40 | 48 | 328 |
After Optimization:
Mesh | Effect | sDataRequiredToRenderAFrame | |
OpenGL | 20 | 16 | 244 |
DirectX | 32 | 48 | 328 |
Size differences from last week:
For the previous assignment, the way I divided the platform specific code into a new class was not the ideal way of doing it and created a few problems while working on this weeks assignment. So I went in and changed the code so that it is better and in the process I removed a few member variables that I was using in both the mesh and the effect class which lead to the drastic decrease in the amount of memory taken by each class.
Total Memory for the graphics project:
The memory allocated to graphics project is budgeted, since memory is limited, especially in consoles and mobile. Hence the total number of meshes and effects that can be drawn at the same time is capped at 10. The total memory that will be taken is 488 Bytes in OpenGL and 656 Bytes when using Direct3D. When the game wants to render more than that number of meshes, the renderer only renders the first 10 pairs of effects and meshes and in Debug mode will throw an error.
Controls:
- Shift: To slow the simulation
- F1: To make a mesh invisible
- F2: Swap the effects between meshes