Assignment 7: Independence 2

The Strategies for Independence

For this assignment we set out to take the shader code we had written for both Direct3D and OpenGL platforms, and to make them as platform independent as possible. To do, we’re using two important tactics to do so, macros and ifdef blocks.

On the usage of Macros (Declarations)

By capitalizing on the fact that shader code is essentially C, we have a number of tools at our disposal. Macros are a small fragment of code that, when used, essentially replaces the contents of the usage of that Macro with the defined lines of text or code behind it. For example, we may want to preserve the naming of a given function, but would like to quickly refer to it for some reason, this could be done with a macro like so:

In this example, we’re going to replace the longer function call of HardToRememberFunctionName() with a shorter name, EasyFunctionName(). When EasyFunctionName() is used in code, the exact line following the macro keyword is replaced in the document at compile time. For example, when declaring a texture for use in our shader, we do so by calling the following macro:

 

 

This line is valid in both the GL and D3D compiled version of the code, due to how the macro works. At compile time, this line is replaced with the actual declaration of a texture object following the syntax of either platform. We extend this approach to sampling a texture as well:

Here, to make sure we use the appropriate platform specific implementation of sampling a color, we use a macro that takes in a texture, it’s UV, and sampler state. This specific line is then replaced with the pertinent call to sample a color properly.

Note the usage of the float4 type here, a type that is used in D3D but not OpenGL. To explain this anomaly fully, we’ll do so in the following section on ifdef blocks.

On the Usage of ifdef

In the event that you’d like a certain block of code to run in place when a condition is met, we can achieve this goal using an ifdef block. In our code, we may want to only include code for a given platform when we’re building for that platform, the Direct3D platform for example:

 

 

 

 

 

Note, the EAE6320_PLATFORM_D3D macro is defined in our code for when the environment is detected as x64. When it’s detected, the macro is defined, and the code situated between the blocks are included in for compilation. This along with our macro usage discussed in the previous section, are the tools we used to implement our shader code into a single copy of a file, broken apart by a small use of #ifdef and macros. For ease of readability and sensibility, we still aim to keep this type of platform specific implementation sparse, and used when only necessary.

Type Tricks!

Recalling the previous point on the usage of float4 in shader code, a type that is used only in D3D. With ifdef blocks we can now do something like the following:

Now, we are able to use either type interchangeably and can assure that our program will reliably switch out the appropriate declarations where we use them in our code. This is the exact trick that we use to standardize the usage of gl_Position vs o_position.

 

 

 

Downloads

You can try out these games via the below links. The only difference is that Direct3D will be used in the x64 version, with OpenGL in the other. They have been built and verified to work on Windows. For this post, the space bar is used to swap the textures on screen.

Assign7_garinrk_x64

Assign7_garinrk_x86

-GarinRK

Assignment 6: Textures

 

Default State
Upper left texture automatically switches after 2 seconds
Upper right and bottom left texture swapped while space bar is pressed

Let There Be Textures!

Prior to this week our game engine supported rudimentary colors and shapes on the screen, represented with our Sprite and Effect system. For this week, we implemented Textures for our objects, to allow some interesting graphics to be shown on our sprites in game. Above, you can see screenshots of the game running with textures that change every interval, or via a button press.

Assigning UVs

I assigned UV values when the vertex buffers were specifically created. Following the standard for both the Direct3D and OpenGL platform, I specify the vertices of my triangles with the appropriate UVs. In our implementation, both GL and D3D U values are 0 on the left, 1 on the right. But, the V values are opposite one another. For D3D, 0 is the top whilst 1 is the bottom. GL is the exact opposite. With this information we can specify the corners of our sprites, composed of two triangles, with their appropriate UV Values. This is done directly within the platform specific implementations of these buffers for both the GL and D3D Platforms.

Texture Reference Counting

I spent a great deal this assignment pondering on the use and purpose of the Handle that refers to our texture data. In the past we had direct pointers to Effect and Sprite data, but this time around we merely had a handle with methods to access the underlying data, and a specific “Release” function that handled some pointer management functionalities for us. At first glance, I thought it was an interface designed for some specific error checking in mind? Perhaps the handle has some underlying functionalities such that it’s a safe wrapper for manipulating the texture data. It’s quite common for such a thing to be created to “dummy proof” working with specific types of data that require careful process.

Concerning the lack of a specific interface to increment the reference count, made me think back to the purpose of the reference count. Specifically, such counter is incremented when you have a new reference, hence the name, to the data. Seeing as we honestly, only want to load the data once, perhaps this fact correlates with the lack of an incrementation interface? I believe using a handle, along with a specific manager for this, keeps us from loading the same file more than once, duplicating data and creating inconsistencies. The handles operate in the background by checking if a given file was loaded, and returns that reference while also incrementing the reference count on its own.

On the topic of using the Release functionality to decrement this reference count, this is crucial because one could have multiple references to a single file throughout code. When a handle is released, the handle is destroyed, and a reference count is decremented. It isn’t until all the handles for a given data or file reference is destroyed that the underlying pointer is then destroyed. In the worst case, if we manipulated the reference count directly instead of the Release function, we could end up in a situation where perhaps we have a “valid” handle that represents a destroyed pointer.

Hidden References!

In this assignment there was a particularly interesting gotcha that I had to hunt down for. When adding the newly provided TextureBuilder project, there is a scenario where specifically you need the TextureBuilder to be created before the AssetBuilder project could access it to well…begin to create assets. Specifcally, this inclusion is not apparent in any C++ code at all.

Than, it hit me realizing that there is a single, sensible, point of entry for the creation of these assets, the BuildExampleGameAssets project! Hence, adding the TextureBuilder as a project dependency to it allowed the appropriate executables to be created before TextureBuilder would need to access it to create the files.

 Downloads

You can try out these games via the below links. The only difference is that Direct3D will be used in the x64 version, with OpenGL in the other. They have been built and verified to work on Windows. For this post, the space bar is used to swap the textures on screen.

Assign6_garinrk_x64

Assign6_garinrk_x86

-GarinRK

Assignment 5: Lua

The game running in 256 x 256 Resolution

The usefulness of Settings.ini

The settings file will contain useful, relevant variables of the game that can be manipulated by the player. In our specific example, it merely sets the resolution of the output window of our game. Being able to configure the game without recompilation saves time, and negates the need to have access to the source code to set these variables. In useful real work applications, if a game were to properly utilize a human readable, sensible settings file. Than perhaps, the player could take their settings along with them from installation to installation, like that of a LAN party, and keep their personal settings along with them. Along with allowing players to manage their own custom settings, this could also make the distribution of custom settings as easy as authoring a properly formatted lua or text file.

The benefits of allowing simple, or even complex, game settings and variables easily manipulated and set without the need of compilation is a useful feature. Offering quality of life benefits to players’ experiences, easy distribution that may foster the community within the player base, and also less developer leg work without requiring specific releases / executables for mass deployment of differing setting configurations.

 

The game running in the default resolution, 512 x 512
Compiled LUA Code

The Benefits and Disadvantages of Compiled Lua

  • Benefits
    • Because the Lua compiler doesn’t need to actively recompile the file when the game begins, it is relatively faster than if the file needed to be compiled first. This process could take some time and is also dependent on the size or complexity of the input Lua file.
    • Shipping compiled files negates the need for the inclusion of the compiler in the shipped product. If you will never need to recompile any Lua file, that’s less dependencies and one less external to be included at distribution.
    • The compiled byte code, is much smaller than the raw text Lua file.
    • Finally, the fact that Lua must be compiled before shipping, you could catch errors at this point in time rather than down the road when the final product is given to customers. There’s  fair amount of assurance that during transmission that the file’s integrity is intact, but that’s not always a complete guarantee.
  • Disadvantages
    • Readability no longer exists in this file. As seen in the graphic above, the Lua file is no longer human readable. This makes debugging, or attempting to find errors in your script near impossible. Also, it is less clear to the end user whom is using this compiled file what the Lua file is actually doing. You lose the transparency that you may be aiming to uphold with your end users.
    • With the raw compiled output still easy to edit via a text application, a simple mis-delete or accidental addition of a character can completely corrupt the file making it unusable. By directly altering the compiled code you may cause unforeseen errors ranging from code that does not work properly to code that may completely crash the application.

 Leave Settings.ini Alone!

Settings.ini is a particularly useful file, as outline in the beginning of this post. By not compiling this file we allow it to be easily modified by the end user. In this specific example, we give the ability to set the resolution of the game window, something that the user may want to change often or on their own whim, via an uncompiled Lua file. It’s simply a matter of making trivial modifications, trivial to access.

You can try out these games via the below links. The only difference is that Direct3D will be used in the x64 version, with OpenGL in the other. They have been built and verified to work on Windows.

assign5_garinrk_x86Release

assign5_garinrk_x64Release

-GarinRK

Assignment 4: Renderables

Figure 1 – The game running with three Sprites using two Effects

Submitting the Background Color

Within cExampleGame.h owns a Color type variable titled bgColor. This variable is then initialized with the appropriate RGB values to construct a color in the Initialize() function of the game code. Than, it is submitted along with other data to the Graphics Library within the SubmitDataToBeRendered() function of the game class.

Submitting Renderable Objects: Sprites and Effects

Sprites

Sprites, or the representation of the polygons on screen, are created by specifying the height, width, and center of their representation (centerX and centerY). As the programmer, you can initialize these objects as pointers in your header file, for example, making sure to keep them as nullptr. Then, in the Initialize() function of your game you can factory load them with the Load() function.

 

Effects

Effects are the visual effects like color and transformation of a given Sprite in the game. These are initialized similarly to Sprites via their declaration as nullptrs and then loaded via a factory Load() function. Effects require a path to the location of the Fragment and Vertex .shd files. The initial render state must also be specified.

Packaging Sprites and Effects for Submission to the Graphics Library

In the main loop of the game application, the programmer must submit this data to Graphics in order to be rendered. To combine Sprites with their appropriate Effects, we use the std::pair<> collection. Which takes both the Sprite and Effect as pointers. This can be seen on line 9 of the Snippet2.cpp sample posted above.

Submission and Note about Cleanup

The actual submission call is quite simple! You can do this simply within the SubmitDataToBeRendered function of your game loop with a SubmitRenderableToRender() call with your pertinent pair(s). NOTE: The programmer is responsible for taking care of Decrementing the Reference Count of these pairs. This is done like so:

Concerns

You’re probably wondering why you have to cache all of the data for a single frame rather than immediately sending over the objects as they’re initialized. For our purposes, in a simple game such as this, we want to make sure that we are ready with all the required render data for a given frame before actually beginning the render process. It makes sense that if we wanted something to show on screen together that we also would like to ensure that all of the required data is ready to go at the same time.

On the topic of Multiple Sprites and/or Effects for a Given Frame

This is made possible via how the Graphics library handles the data, or std::pair<>’s that we used to package up our Sprite and Effect. Briefly, the collection used to store this data is a std::Vector behind the scenes, they are rendered in order of their placement into said vector.

sizeof(Sprite) and sizeof(Effect)

Below are the results of a sizeof() call on the two classes priorto improvements:

Concerning the topic of how to make either classes “Smaller” in their byte size. This can be done in a few ways:

  • Considering the probable size of a given member variable and adjusting to what’s needed
    • For example, using a uint8_t to represent a value instead of an entire int.
    • Using a bitfield instead of a Boolean to store a True or False value
  • Taking advantage of a given architecture’s data alignment and making sure that your member variables align with those specifications:
    • Some SSE2 instructions on x86 require 128-bit/16-byte aligned data, and padding may be inserted in order for data to fall on those bounds. Making sure that your variables are structured in such a way to minimize the padding required could make a large difference in the overall size of a given class
  • Prioritizing the use of pointers, which are merely an address, over actual references to objects in memory.
    • The con here being that now the programmer must take into consideration proper memory allocation, pointer management, and other pitfalls when using a pointer heavy architecture. Especially how accessing data via a pointer could lead to worse performance due to the possible lack of cache coherency.

Some thoughts however on our specific implementation:

  • cRenderState
    • Examining the cRenderState class, it could benefit from being a pointer. More specifically, it doesn’t exactly need to be contained whole in our Effect. Of course, there’s the considerations you must make when it comes to working with pointers but it should be fairly easy.
  • File Path Manipulation
    • Currently we require the entire path of given fragment/vertex data in the creation of an Effect. This is because I’m using char[] of a fixed size to hold the path of such. This could make more sense as a char* and size management. The goal being able to get rid of the MAX_PATH_LENGTH definition in Effect.h. There may be moments when this is too much or more common, perhaps not enough to store the entire path depending on where the programmer would like to store this data.
    • Currently we require the entire path of a given fragment/vertex data in the creation of said effect. Instead, we could make it more efficient and user friendly by allowing the programmer to only specify the file name of the given files. The actual file paths could then be hard-coded directly into the loading function itself.
    • By omitting the use of a char[] array in the Effect class, we were able to bring down the size even more because we can use the directly strings passed into the load function:

With this stage we began to move away from hardcoded objects from within the Graphics library and moving them to the more sensible location of within the game code. I found this assignment actually interesting and satisfying not just because it’s working towards a sensible goal, but it was structured in such a way that made sense.

 Downloads

You can try out these games via the below links. The only difference is that Direct3D will be used in the x64 version, with OpenGL in the other. They have been built and verified to work on Windows.

assign4_garinrk_x64Release

assign4_garinrk_x86Release

 

-GarinRK

 

Assignment 3: Independence

Figure 1 -The Game Running with a Red BG in D3D

 

Making Graphics Independent

After ripping apart the class in the previous assignment to create platform specific implementation of the Graphics calls, I had to bring things back together. Specifically, Graphics.cpp and Graphics.h will now be platform independent interfaces. Meaning that either D3D or GL platforms will utilize this interface synonymously. However, we still needed to consider the specific requirements of OpenGL and Direct3D via a platform dependent implementation. This was done via a class titled View.h and View.cpp respectively. This implementation will sit in the root directory of the Graphics project. It sits parallel to the location of Graphics.cpp and Graphics.h. Reasoning being that both platforms are used in this file, therefore it would not make sense to place into the specific Direct3D or OpenGL directories.

Clearing the Back Buffer Color (To Something besides Black)

Figure 2 -The Game Running with a Green BG in GL

 

The user is able to specify the color of this Back Buffer via the RenderViewFrame call in Graphics.cpp

Sprite and Effect Initialization

Figure 3 – Showing the declarations of Effects and Sprites

Concerning Sprites, users are able to specify the location of the sprites. These are floats that fall within the bounds of the window of [-1, 1] in both horizontal and vertical directions.

With Effects, users are able to specify the exact path of the built Vertex and Fragment shader files used to create the effects. As well as the initial value of the default render state.

Thoughts

Although this was only Assignment 3, I felt that it was the most interesting and beneficial assignment concerning my goal of learning better architecture. Taking an existing code base, that of John-Paul’s engine, and ripping it apart and putting it back together was a good exercise in identifying where platform independence and dependence lie. Also, while this assignment was less Visual Studio detail heavy, it was a good exercise in practicing some good old C++ skills with the few hours spent on Circular Dependency searching while compiling the initial version of Graphics.cpp and Graphics.h.

Downloads

You can try out these games via the below links. The only difference is that Direct3D will be used in the x64 version, with OpenGL in the other. They have been built and verified to work on Windows.

assign3_garinrk_x86Release

assign3_garinrk_x64Release

 

-GarinRK

Assignment 2: Encapsulation

The game running

My Approach and Point of View

Like mentioned in the post from before, I expected to continue to become more familiar with the engine given with this first assignment or two. While the first was getting into the structure of the engine as a whole by adding the Graphics system, now we were tasked with becoming familiar with this specific system by means of abstracting similar functionalities into independent platform specific classes.

I started the assignment slowly, hand writing the appropriate functions that deemed relevant to the two data types (Effect and Sprite) to gain a better understanding of their processes. Though I pushed that aside to focus on the larger task at hand, the abstraction and design, rather than the specific platform implementation. My hunch being that, at a further date and with more graphics experience that the logic would make more sense to me.

As I began to see which parts could be made general and how to construct these platform specific classes, I also gained some experience with the ShaderBuilder and shader data pipeline system at work. After these two objects were properly stripped apart, I got to directly dive into the construction of the triangles which gave me another brush with graphics code. Which I’m sure more chances will come in the following weeks.

In closing, the assignment and content of these lessons were dealing with the design decisions on how to handle multiple platforms in a given codebase. Using a specific example, geometry and shader data, to teach the concepts. My goal was to remove all logic pertinent to the Effect and Sprite types such that any work that deals with manipulating the inner workings of these types were placed in their own classes. I made an effort to ensure that the only properties that reached into these objects were only those necessary like status codes and application IDs.

Debugging on the Direct3D / x64 Platform

Debugging on the OpenGL / x86 Platform

 

Calling the Interface from Graphics.d3d.cpp

Above is a simple view of my RenderFrame() function, but with details omitted. Here, the information related to Geometry and Visuals (Sprites) are encapsulated into their own objects. With the specific platform specific code abstracted away, the user is just required to call simple Bind() and Draw() calls when drawing the frame on screen.

Remaining D3D vs. GL differences in the Graphics Library

As of this writing, there are two platform dependent Graphics files, one d3d.cpp and the other gl.cpp. They are “supposed” to contain only the specific code or calls related to their platform. This assignment was the first step in abstracting these classes into using a more general interface regardless of which platform it is run on. Sure, currently specific Sprite and Effect classes exist for each platform but in the future that may be abstracted and joined with a more independent Graphics library. In the ideal case, we’ll have a “lightweight” Graphics.cpp file that calls to the correct platform implementation functions.

There are few platform specific code going on in these Graphics files, with most if not all of these few differences only being very specific functionality to GL/D3D APIs. So few that in fact, the following list are very similar and can be included in a future encapsulation:

  • Declaration / Management of the Constant Buffer Object
  • Submission Data and the Submission Call
  • RenderFrame() is quite general and few lines deal with D3D / GL functionalities
  • The Initialization() and Shutdown() processes of the Graphics system as a whole

My first thought processes are to continue with the same methods that we utilized this assignment, platform specific classes (d3d.cpp vs gl.cpp). Like, making gl or d3d specific shutdown and initialization routines for the Graphics system as a whole. But, it could possibly be further organized into a single file for each of these points with the use of processor macros. Of course, at the cost of a little bit of readability, maintenance, etc.

Downloads

Like before, you can run these games via the below links. The x64 version uses Direct3D and OpenGL on the other:

assign2_garinrk_x86Release

assign2_garinrk_x64Release

-GarinRK

Assignment 1: Getting Situated

Welcome to GDev!

This post marks the first in a series of write-ups associated with my Game Engineering II at the University of Utah’s Entertainment Arts and Engineering Program. They will typically deal with assignments or any other related topics covered in the course.  In addition, there may be posts related to gaming, engineering, or any of other interesting topics technical in nature.

Images

Project References

In the end, the only projects that required a reference to the Graphics Project / Library was the main game project, ExampleGame. While there are projects that reference the “Graphics::” namespace, they are not inherently needed. This is because in these projects’ cases, they are just referring to things like a type or enum defined in that class. They are not specifically calling a function or referencing a private variable. In these cases, the reference would be needed as the program would need a way to enter these functions or accessing these variables. Examples in the solution include the Application and ShaderBuilder project.

Course Expectations

As per my experience with the first in class lecture and this introductory I expect to see trends, topics, or experiences like:

  • Content and expectations that prioritize the process.
  • More experience with working in an unfamiliar codebase
  • Detail oriented problems where the solutions lie in corner cases and conditions
  • Adherence to existing standards when working in an existing code base
  • A more advanced usage and application of Visual Studio and its features

 

Other Thoughts

The introduction of this assignment being directly after three tutorials on platform specific code inclusion, static library creation, and solution setup was really an exercise on all three topics. A way for us to get into the given code base and get it up and running on our machines. Also, to become accustomed to the procedure and expectations of the course. While there were numerous “gotchas” throughout working on this, the answers were already divulged via a tutorial or based off of a concept that we had learned in the tutorials. This along with the, meticulous and specific nature of the paperwork and lead me to believe that I’ll enjoy the course’s content. Because most of my curiosities lie in best practices, approach to issues as well as utilizing Visual Studio to its full potential I’m sure the rest of semester will be at least rewarding.

Downloads

You can try out these games via the below links. The only difference is that Direct3D will be used in the x64 version, with OpenGL in the other. They have been built and verified to work on Windows.

assign1_garinrk_x86Release

assign1_garinrk_x64Release

-GarinRK