In this blog, we will discuss how the application(game) & the graphics system work in sync to render data. From previous blogs, we know our meshes and effects are in the graphics code and the game does not have any control over it. But what we want is the game to update these meshes and update their state's and the graphics to render whatever the game submits.
The way we achieve this sync is by using threads we use 2 threads application and render but the problem is how do they handle shared data access? The way it is done is each thread has struct of data that they use to do their operations to avoid shared data access. The application thread then submits the data for the render thread and the render thread starts working on it and resets it's struct once it's done. Each thread uses signals to notify the other thread to avoid any synchronization problems.
So let's start moving code from graphics to game. Let's begin with the background color, the background is currently set in the graphics code but let's give the control to the game so that it can change the color. I created a simple interface to do this. The first thing I added is a struct the represents color so that it is for the gameplay programmer to just add color rather than knowing what all the floats do. I added a few default colors which can be handy whenever required.
Following is the snippet showing how a user set's background color.
The next code we move to the game is submitting the actual mesh/effect pair. The easiest way to create a better interface is by trying to imagine how the interface will be used it helps in making the interface simple. I would imagine a gameplay programmer expects an interface with some mesh & effect data as parameters. So created a similar interface following is the interface I created to submit the mesh/effect pair.
The DrawModel accepts a mesh & an effect and that's it the graphics system takes care of the rest. The gameplay programmer can have as many drawModel calls as he wishes the graphics system should handle the appropriate behavior. There is a hard limit on how many models(mesh with effect) the graphics system can render but that is a worry of the graphics system, not the gameplay programmer.
So why are we doing this data submission to render thread instead of rendering things immediately? So the reason we are using 2 separate threads is to maximize performance rather than doing things sequentially we are using the threads to do work parallelly to improve performance but the biggest problem while using threads is shared data between threads. Whenever 2 threads try to access the same data a race occurs and to maintain the correctness any one of the 2 threads should wait for the other thread to finish. But this affects the performance as we lose the advantage of parallel work as one of the threads is waiting for the other to finish which is not better than working sequentially. In fact, it might be worse because of the overhead of using threads. So to avoid the shared data access and to improve performance what we do is create a struct that represents the shared data. we create 2 instances of this shared data one for each thread so that they need not wait for other thread to finish. So for example application thread starts updating it's shared data instance and once it's ready to be rendered it submits the data and notifies the render thread. The render thread then swaps it's the instance with the application thread's and starts rendering, before it starts rendering it notifies the application to start submitting the data to its Instance. By this method both threads can do work parallelly without waiting.
Another problem when dealing with threads is ownership of data as the mesh data and effect will be shared by both threads it is important to maintain the ownership of these objects so that they don't get deleted when other thread is using them. We use reference counting to acheive this. Inorder to add reference counting we add more information to our mesh and effect classes. The size of the Mesh after adding reference counting information is 32 bytes in D3D and 16 bytes in OpenGL
Following are the members of my Mesh
#if defined(EAE6320_PLATFORM_GL)
GLuint m_VertexBufferId = 0;
GLuint m_IndexBufferId = 0;
GLuint m_VertexArrayId = 0;
#if defined(EAE6320_PLATFORM_D3D)
eae6320::Graphics::cVertexFormat* m_VertexFormat = nullptr;
ID3D11Buffer* m_VertexBuffer = nullptr;
ID3D11Buffer* m_IndexBuffer = nullptr;
uint16_t m_IndexCount = 0;
EAE6320_ASSETS_DECLAREREFERENCECOUNT()
I don't think I can make them any smaller in D3D 3 pointers take 24 bytes and the 2 uint16_t (one for indexcount & one for refrence count) take 4 bytes with alignment a total of 32 bytes similarly in OpenGL 3 Gluint's take 12 bytes and 4 bytes by other two members a total of 16 bytes. Previously I used a meshdata struct which contained all my mesh data members but it increased the size of mesh because of alignment so I moved everything to Mesh class.
Similarly the size of Effect after adding reference counting is 32 bytes in D3D and 20 bytes in OpenGL. Following are the members of effect
eae6320::Graphics::cShader* m_vertexShader = nullptr;
eae6320::Graphics::cShader* m_fragmentShader = nullptr;
eae6320::Graphics::cRenderState* m_renderState = nullptr;
#if defined(EAE6320_PLATFORM_GL)
GLuint m_programId = 0;
EAE6320_ASSETS_DECLAREREFERENCECOUNT()
It is very similar to the mesh and I don't think it can be made any smaller. InD3D 3 pointers occupy 24 bytes, the reference count ocupies 2 bytes with alignment a total of 32 bytes. In OpenGL 3 pointers occupy 12 bytes, GLuint takes 4 bytes, uint16_t for reference count takes 2 bytes with alignment a total of 20 bytes.
After Moving all the mesh and effect code now in the graphics project we only have the sDataRequiredToRenderAFrame instances and the size of this is struct is 200 bytes in D3D and 180 in OpenGL. As we have 2 of those the total memory would be 400 bytes in D3D and 360 in OpenGL. One thing to note is this size is without the mesh and effect data because these are not allocated by the graphics system. There might be a point where the graphics might have the ownership to the memory of Mesh and effect but as they are not allocated from the graphics system I think they might not be included in the budget for graphics.
Some Screenshots
The application game has to override a few functions to update states either based on simulation time or based on Input. I added few more controls to the game now when enter is pressed it hides the square mesh and also changes the effect on the triangle. The triangle is rendered every alternate second.
Controls:
ESC – exit.
UP Arrow – To Increase the speed of simulation.
DOWN Arrow – To decrease the speed of simulation.
ENTER - Hides Square and changes Triangle color (to animation effect)
Triangle Mesh is rendered after every one second.
Comments