In this blog, we will discuss creating platform-independent interfaces and adding platform-specific implementations and how it simplifies their usage in our project. We will also draw a square instead of a triangle this time and more.
So we far we have set up our game engine project with the provided graphics library. But if we go through the graphics library there are two implementations for drawing the triangle on screen an OpenGL one and a Direct3D one. Both OpenGL and Direct3D handle things differently and perform different operations to get the triangle on the screen. But a game developer does not need to know all the various implementations he would just like to draw a triangle on the screen. So to make things simple we have to abstract these implementations by providing interfaces that are easy to use. Another important thing to notice is these interfaces must be platform-independent. So no matter what graphics API I use (provided its support by the engine) the user should be able to use the same interface to draw the triangle.
Let's begin by creating an interface for the geometry (the actual triangle mesh). So to create any interface we have to first understand the usage of how will this interface be used. If we look at our current logic to draw mesh we notice that Direct3D and OpenGL both use different data to represent mesh but both have 3 common operations Initializing, Drawing & Cleaning the mesh. So we would want a similar behavior in our interface. There are many ways this can be done but I opted to create a class Mesh with those methods. I also created a struct to hold the mesh data just to makes things easier.
Now Let's update the shading part. The shading part is a little bit simple as there is no platform-specific data( except for one in OpenGL). But similar to Mesh we have 3 common operations Initliaze, Cleanup & Binding. Again similar to Mesh I created an Effect class to represent the platform-independent interface. I also have some platform-specific functions that handle the platform-specific code.
After updating the old geometry & shading data with the new Mesh & Effect objects the graphics.gl.cpp/graphics.d3d.cpp will look like as following
s_Effect & s_GLMesh are objects of corresponding interfaces.
Drawing More Than One Triangle
So far we drew only one triangle now let's add more triangles to the screen. Without going into many details about graphics lets understand how are we drawing our current triangle we use a vertex buffer to store all the vertex positions for now think them as points which GPU uses to draw. we need 3 points to draw a triangle so to draw another triangle we need three more points. So we add 3 more points to the vertex buffer. When we are adding the points we have to keep in mind the winding order as OpenGL and Direct3D use different winding orders. OpenGL uses an anti-clockwise/right-hand winding order whereas Direct3D uses a clockwise/left-hand winding order. The output after adding these 3 vertices can be found in the above gif which shows a square(combing 2 triangles). As a fun challenge, I added 3 more vertices to make a hut.
Debugging Graphics
Debugging graphics might be difficult even if you are familiar with the graphics APIs. So today let's know about two tools that help in capturing the graphics data for debugging.
Visual studio graphics analyzer is used to capture frames while running your application and these captures can be used for debugging purposes. Following is a capture data from the Visual Studio graphics analyzer
You can see the event list on the left and the render target shows the data based on the highlighted event. In this case, ClearRenderTargetView(with black color) is highlighted.
here the Draw event is selected with vertex shader in the pipelines stage which shows a wireframe of the 2 triangles.
Finally, the Draw Event with shading.
RenderDoc is another tool that is used to do captures in OpenGL applications. Following is the data from RenderDoc
Similar to the Visual Studio analyzer you can find the event list on the left and the render target on the right. here glclearcolor event is selected which shows the black color
here glDrawArrays event is selected which shows the wireframe of the 2 triangles.
Finally glDrawArrays with shading.
Differences in platform-specific Graphics.cpp files
Other differences that I noticed in graphics.gl.cpp & graphics.d3d.cpp are Direct3D uses views which are objects that allow a texture to be used in a specific way. examples of this are RenderTargetView which allows a texture to have a color rendered. The way I would abstract this is similar to how I did for my Effect class I can create a graphics interface that is platform-independent but has a private platform-specific initialization or cleanup method which can be used for any platform-specific purposes.
Controls:
ESC – exit.
UP Arrow – To Increase the speed of simulation.
DOWN Arrow – To decrease the speed of simulation.
Comments