Custom Game Engine Devlog Week 3
Estimated Time Spent: 8 hours
Accomplishments:
Created a fully platform-independent graphics interface.
Added user-friendly interfaces for specified mesh and effect data.
Increased performance by utilizing indexed drawing and cutting down on stored member variables in instanced classes.
Added support for animated background colors.
Hey! My house has a door now…
Simplifying Graphics.cpp:
The most important thing about designing an engine is not a powerful grasp on c++ techniques, but having a strong plan and set of intentions for each element. I knew that I wanted graphics.cpp to be a generalized, user-facing manager that could provide an overview of the rendering process without getting too in-the-weeds on implementation. Last week’s iteration of the file was an improvement, but it still featured far too much platform-specific code for things like swapping buffers, clearing the background, and cleaning up proprietary member vaiables. To solve this, I created a new class titled ‘Platform Renderer’. This class uses the same strategy that graphics used to, in which a single header file has two .cpp files - one for D3D, another for openGL. I then deleted the .d3d and .gl files for graphics, replacing them with a single file called ‘graphics.cpp’. An static instance of the platform renderer is now declared in the graphics file, handling of all its dirty work with user-friendly, single lines of code.
Swapping Buffers in graphics.cpp is significantly easier now!
Next, it was time to tackle the effect code. Up to this point, shaders were hard-coded into the graphics file - not the most flexible system for a game engine. To handle this, I first added parameters to the ‘Effect’ class’s Initialization function, allowing it to take a string for the names of the vertex and fragment shaders it would like to use. However, I was still unsatisfied, as it felt like a pain point for users to write in full path strings to their desired shaders. Thus, I created another class titled ‘Shader Manager’, which stores all the names of commonly used shaders as string member variables. These strings can then be accessed through a function that takes an enum as a parameter - allowing the user to specify the type of shader they want, without needing to remember its exact name or path in the engine.
Diagram displaying the relationship between graphics.cpp and Shader Manager
This approach to calling the shaders did have some drawbacks, however. Namely, adding many different shaders to an enum as my game grows in scale is not a practical use of my time - and the namespaces required to actual call the Shader Manager create such a long line of code, as to partially defeat the point. Besides, I thought after completing the exercise - It’s all gonna change when I abstract this to the game layer, anway.
Comparing updated function calls in graphics.gl.cpp vs. graphics.d3d.cpp
Using Graphics Debuggers:
In addition to working on the engine itself, I spent a portion of my time this week exploring two different debugging tools:
Visual Studio Graphics Debugger (for Direct3D)
Render Doc (for OpenGL)
These tools allow me to run my executable, capturing and analyzing specific frames in great detail. While they aren’t neccessary at the moment, it’s important to grasp a general understanding of how they work while my code is still relatively small in scope, so that I can competently utilize them when I inevitably run into graphics-related issues later on. The below screenshots display some of the features these tools offer, such as viewing individual color codes on a rendered texture, or investigating an image at every stage of the graphics pipeline, or even viewing meshes in wifreframe mode.
Week Overview:
For this assignment, I continued my work from last week by making the graphics system in my custom engine entirely platform-independent. I removed the separate .d3d and .gl versions of my graphics file, replacing them with a standardized and platform-agnostic graphics.cpp file. This is a critical step toward creating a user-friendly engine, abstracting away complex tasks like establishing winding orders and buffer swapping to less immediately apparent manager classes. In addition, I added better interfaces and user specifications for mesh and effect loading, added support for animated background colors, and increased the engine’s peformance by switching from vertex-based to indexed drawing. The main challenges of this week are summarized below:
Understanding the useful lifespan of stored variables.
Weighing performance against useability.
Finding a balance between short-term fixes and overly-architectured future blueprints.
Analyzing a captured openGL frame in Render Doc
Analyzing a captured D3D frame in Visual Studio Graphics Debugger