I'm writing a game in C++ using OpenGL.
For those that don't know, with the OpenGL API you make a lot of calls to things like glGenBuffers
and glCreateShader
etc. These return types of GLuint
which are unique identifiers to what you just created. The thing being created lives on the GPU memory.
Considering that GPU Memory is sometimes limited, you don't want to create two things that are the same when they're to be used by multiple objects.
For example, Shaders. You link a Shader Program and then you have a GLuint
. When you're done with the Shader, you should call glDeleteShader
(or something to that affect).
Now, let's say I have a shallow class hierarchy like:
class WorldEntity
{
public:
/* ... */
protected:
ShaderProgram* shader;
/* ... */
};
class CarEntity : public WorldEntity
{
/* ... */
};
class PersonEntity: public WorldEntity
{
/* ... */
};
Any code I've ever seen would require that all the Constructors have a ShaderProgram*
passed to it to be stored in the WorldEntity
. ShaderProgram
is my class that encapsulates the binding of a GLuint
to the current shader state in the OpenGL context as well as a few other helpful things that you need to do with Shaders.
The problem I have with this is:
- There's a lot of parameters needed to construct a
WorldEntity
(consider that there might be a mesh, a shader, a bunch of textures etc, all of which could be shared, so they're passed as pointers) - Whatever is creating the
WorldEntity
needs to know whatShaderProgram
it needs - This probably requires some sort of gulp
EntityManager
class that knows what instance of whatShaderProgram
to pass to different entities.
So now because there's a Manager
the classes need to either register themselves with the EntityManager
along with the ShaderProgram
instance they need, or I need a big-ass switch
in the manager that I need to update for every new WorldEntity
derived type.
My first thought was to create a ShaderManager
class (I know, Managers are bad) that I pass by reference or pointer to the WorldEntity
classes so that they can create whatever ShaderProgram
they want, via the ShaderManager
and the ShaderManager
can keep track of already existing ShaderProgram
s, so it can return one that already exists or create a new one if needed.
(I could store the ShaderProgram
s via the hash of the filenames of the ShaderProgram
s actual source code)
So now:
- I'm now passing pointers to
ShaderManager
instead ofShaderProgram
, so there's still a lot of parameters - I don't need an
EntityManager
, the entities themselves will know what instance ofShaderProgram
to create, andShaderManager
will handle the actualShaderProgram
s. - But now I don't know when
ShaderManager
can safely delete aShaderProgram
that it holds.
SO now I've added reference counting to my ShaderProgram
class that deletes its internal GLuint
via glDeleteProgram
and I do away with ShaderManager
.
So now:
- An object can create whatever
ShaderProgram
it needs - But now there's duplicate
ShaderProgram
s because there's no external Manager keeping track
Finally I come to make one of two decisions:
1. Static Class
A static class
that's invoked to create ShaderProgram
s. It keeps an internal track of ShaderProgram
s based on a hash of the filenames
- this means I no longer need to pass pointers or references to ShaderProgram
s or ShaderManager
s around, so less parameters
- The WorldEntities
have all knowledge about the instance of ShaderProgram
they want to create
This new static ShaderManager
needs to:
- keep a count of the number of times a
ShaderProgram
is used and I makeShaderProgram
no copyable OR ShaderProgram
s count their references and only callglDeleteProgram
in their destructor when the count is0
ANDShaderManager
periodically checks forShaderProgram
's with a count of 1 and discards them.
The downsides to this approach I see are:
I have global static class which might be a problem. The OpenGL Context needs to be created prior to the invoking any
glX
functions. So potentially, aWorldEntity
might be created and try to create aShaderProgram
prior to OpenGL Context creation, which will result in a crash.The only way around this is back to passing everything around as pointers/references, or having a global GLContext class that can be queried, or holding everything in a class that creates the Context on construction. Or maybe just a global boolean
IsContextCreated
that can be checked. But I worry that this gives me ugly code everywhere.What I can see the devolving to is:
- The big
Engine
class that has every other class hidden inside of it so that it can control the construction/deconstruction order appropriately. This seems like a big mess of interface code between the user of the engine and the engine, like a wrapper over a wrapper - A whole slew of "Manager" classes that keep track of instances and delete things when neccessary. This might be a necessary evil?
- The big
AND
- When to actually clear
ShaderProgram
s out of thestatic ShaderManager
? Every few minutes? Every Game Loop? I'm gracefully handling the re-compiling of a shader in the case when aShaderProgram
was deleted but then a newWorldEntity
requests it; but I'm sure there's a better way.
2. A better method
That's what I'm asking for here
WorldEntity
s; isnt that shifting some of the problem? Because now the WorldFactory class needs to pass each WolrdEntity the correct ShaderProgram.