Blogger Templates

Wednesday, September 06, 2017


Graphics (2 of ∞) Refactoring Graphics, Shader Builds, GPU Captures and More Triangles

Posted in ,
In my last post, I talked about the experience of setting the graphics project environment through property sheets, excluding non platform specific files from builds, setting build dependencies and references. I was even able to render a fancy little triangle at the end. In this assignment I will carry forward all those learning with the intent of creating platform independent interface, rendering more than one triangle and even using GPU Captures to capture the instance of how the GPU renders the frame between different function calls.

Setting the Interface
The intent of writing an interface is to hide the differences of platforms to the developer and write code declarations that are common across platforms. This delta or the difference between platform’s implementation can vary. The small differences can be differentiated using platform related macros. In my project, I am using EAE6320_PLATFORM_D3D for DirectX and EAE6320_PLATFORM_GL for OpenGL. The large difference can be isolated by having platform specific files. In this assignment, I will be following the convention having .d3d suffix for files that only has DirectX functions and .gl suffix for files that only has OpenGL implementations. we will also be setting up an interface for Effect and Sprite representation that would allow us to centralize the shading data and geometry data logic respectively.

Moving the platform-specific and platform-independent code
Creating the sEffect interface allowed me to move many redundant code that was used for shading data between the OpenGL and DirectX Graphics implementation. I started with moving the variables from the Graphics implementation to my Effect interface and fixed all the references. The initialize shading data was just a duplicate code so I moved the code logic from d3d and gl implementation to my sEffect struct and made it shareable. While doing this, I had to split the initialize views logic in the OpenGL implementation as it was tied to the initialize shading data. The other shading related data that I wanted to move was bind and cleanup but this had platform specific code and I moved them to their respective platform specific files of sEffect struct.
I was able to carry forward the same approach of sEffect to cSprite. The only difference I found was almost all code related to geometry was platform specific and I had to move the implementation of initializing Geometry, Draw and Cleanup from Graphics(d3d and gl) implementation to cSprite(d3d and gl) implementation. Once I had all the code moved, all I have to do was create an instance of the sEffect struct or cSprite class representing the shading data and geometry data inside the graphics class.


After doing this, I found all of the geometry data and shading data to be more centralized and more readable. In any large code base, the most important thing for scalability is readability. This can easily be achieved through encapsulation and shareable code.

Building Shaders
In my last post, we manipulated the vertex information of the shader to get the animation movement effect. We will be reverting that to the defaults (i.e, there triangle will be static) to create a new copy of the shader information and build that HLSL version for Direct3D and GLSL version for OpenGL. The solution I am using also has a ShaderBuilder project which allows me to enter the copied shader location and build it to the Game Installation Directory.

More Triangles

The next part of the assignment is to get another version of the triangle exactly above our previous triangle such that both triangles looks together like a square. To get this working between two platforms, I had to familiarize myself with the winding order or the handedness which means the way in which the DirectX or OpenGL would perform its rendering. We use the left hand to decide the order for DirectX which is clockwise direction and we use the right hand for OpenGl which is counter clockwise direction. Once I know the order, all I have to do was manipulate the Draw call of cSprite to represent two triangles(Includes OpenGL and DirectX) by changing the triangle count and adding three more vertices to the vertexData array. While adding the new vertices, I was carefully typing the coordinates with consideration to the winding order. This vertexData is updated to represent sSprite vertex format from sGeometry.

Final Bones
After all the refactoring, the Graphics(d3d and gl) implementation still has some left over code. This includes anonymous namespace variable declarations(Eg: ConstantBuffers, SampleStates, cShader, sContext), RenderFrame, Initialization and CleanUp Functions. There are still some duplicate code in initializing the graphics object which are platform Independent and can be refactored. The render frame of D3D and GL seem to have the same approach for logic but has platform specific and independent code. I am still not sure if the RenderFrame is good at its current form or can be refactored. Overall, I feel the files(.d3d and .gl) has the potential to be merged as a single .cpp with more refactoring. This can be done by creating an additional interface for the views by moving the view related code across the helper functions, initialization and render frame to the corresponding platform independent and specific code.  I am really curious on how the future classes would shape this project in the days to come.

GPU Captures
The final ask of the assignment is taking screenshots using some GPU capture tools. Luckily for us, Visual studio provides an inbuilt version of GPU Capture tool for Direct3D but the same cannot be used for OpenGL. For the purpose of our assignment, I will be using a 3rd party GPU Capture tool called RenderDoc.


The above screenshot represent the VS version of GPU capture. As you can see the render frame is pitch black during ClearRenderTargetView(meaning there is nothing to render) The reason for this is that before drawing anything, the image buffer will be cleared with a solid color(Black is usually preferred)


The above screenshot represents the pipeline stages of the Draw call. This allows us to see the differences between Input Assembler, Vertex Shader, Pixel Shader and Output Merger.


The above function represents the GPU Capture of glClear method of OpenGL executable version. This is identical to the Direct3D’s ClearRenderTargetView.

RenderDoc1.PNG RenderDoc2.PNG

RenderDoc does it bit different when it comes to seeing the pipeline stages and Draw rendered frame in VS. The image on the left is accessed through texture viewer and shows a representation of the glDrawArrays. The image on the right is accessed through mesh output and shows the wireframe of the rendered image.


I also tried making a house using three triangles as part of an optional challenge quest. This is not the best in the world but this sort of shows the possibility I can achieve with the system.

I tend to include my build as part of the assignment ask. You can see the perfect square by trying any of the following downloads.

Direct3D x64
OpenGL x86