Blogger Templates

Tuesday, September 19, 2017

0

Graphics (4 of ∞) Reference counting and submission of sprite and effect from game code to graphics.

Posted in ,
EAE 6320-001
write-ups





In my last post, I talked about adding new interfaces for effect and sprite by encapsulating some of the logic from the graphics code. In this post, I will be making changes to the ownership of effect and sprite such that the game code would drive the ownership for them. I will also make those classes reference counted in a way to control the scope/existence of the object.

In computer science, reference counting is a technique of storing the number of references, pointers, or handles to a resource such as an object, block of memory, disk space or other resource. - Wiki


Submission pipeline
The first task I started working on was to enable a gameplay programmer to define the background color directly from the game code. To get to this stage, I was trying to find patterns of similar implementation where the data was being submitted to the application thread. I was following the implementation of SubmitElapsedTime and applied the same approach for my SubmitBackgroundColor function. All I had to do was to create a variable for my color palette class inside sDataRequiredToRenderAFrame such that it can allow caching the data that is required for the rendering. Inside my submission logic, all I have to do was to pass the incoming color data from the game to the application thread(the same variable we created for caching). At runtime, the application thread will be swapped with the rendering thread and all of the user specified color would be accessed from the cached struct and will be used as the clear/background color.


Reference counting and factory methods
The next one I worked on was making my effect and struct class reference counted. For this, I started by taking reference of a class that had implemented the reference counting and cShader was my go-to class. All I had to do was include a reference to ReferenceCountedAssets.h and include three macros that would give me access to a reference count that can be incremented and decremented. I found the implementation to be simple and clean as it just gives me the part of reference counting alone. Then I wrote factory functions that can initialize the pointer for me with the reference count. The intent of writing the factory method is to initialize my class and to prevent object construction of the class through other means by the gameplay programmer(i.e we would make the constructors private)


Why caching?
Generally, Games have graphic intensive content to be rendered on each frames. A way to offload some work of the graphic intensive process is to use multi threading where we could use one thread for submitting user data and other thread dedicated for rendering. In our project we would call the game thread as the application thread and the one which does all the graphic rendering as the rendering thread. Since both thread waits on the other thread to complete its process and be available, We use caching as a way to synchronize data between our application thread and rendering thread.
Wrapper class and submitting sprite/effects
To make the submission process simpler for sprite and effect, I wrapped them in a class called cGameObject. This class had member functions that helped me to invoke the sprite and effect’s individual member functions. Now to submit this wrapper class, I just have to follow the same pattern as I did with color. But in this case, since I have a situation of submitting many sprites and effect(cGameObject) together, I would make my functions easier by taking a collection data structure such as vector. Now the flow of my project would start from my game’s initialize method where I would create the necessary cGameObject and add them to a vector. Now I will use the SubmitDataToBeRendered method to pass the vector from my game code and submit them to the application thread. Meanwhile, I also created a vector inside sDataRequiredToRenderAFrame to cache the incoming data from the game code. Since we made the class to be reference counted, we would manually increment the reference count whenever we make an extra pointer to it. In our case, we make a copy from our game’s vector to the sDataRequiredToRenderAFrame vector so we will increment a reference count. After the swap, we directly invoke the bind and draw functions of respective effect and sprite from cGameObject. Then we would clean that data by decrementing the reference count and clearing the vector data in the render thread. The graphic code is very dynamic such that the logic is written to support n number of vector items. I just iterate over all the items and perform the same operation. This means - A gameplay programmer can define as many gameobjects as he want from game code and my current implementation would do all the graphics work without him needing to mod the graphics code.


Grouping user data
To make the user data grouping easier between effect and sprite, I wrote a handy struct gameobject that would populate it with all the user specific information for sprite and effect. This struct is just a representation of the data and does not contain our actual effect and sprite.
I would then use this populated struct data to initialize my cGameObject class’s effect and sprite. I took this approach as a way to support data driven development. Later, if we plan to use a lua script or some UI framework to populate the user data, all I have to do is just replace my existing struct with new user data form.


While making these sprite and effect classes, the first approach I took was to pass the user data as part of constructor and hold them in a member variable and use them across the class on need basis. That was bloating the size of the class based on the extra unwanted member variables. After some inspection, I realized I could initialize the variables upfront without having them to be cached in a member variable. Following are the sizes I came up after cleaning up the unwanted member variables. The size also depends on the way the member variables take advantage of the alignment in x64 and x32 bit platforms.


OpenGL
sizeof(cSprite) = 12
sizeof(cEffect) = 20


Direct3D
sizeof(cSprite) = 24
sizeof(cEffect) = 48


I had an issue when I did not decrement the reference count properly in the cleanup. The manager continued to hold a pointer to an asset. To fix it, I had to decrement the reference count across the application thread and the rendering thread as we are not sure of the data state at the time when the game exits. I would like to thank my Professor, Ameya, Akshay and Chen for clarifying me on the same issue.



0 comments: