Adding a Resource Manager, aka “I’m really sick of passing all my shaders through all the functions in my program”

At the end of my last blog post I noted that I had implemented some changes I’d not yet blogged about. I ended up deciding they were too small and disjointed to bother with a blog post. In short, those changes were mostly me just removing all the old code from my pre component approach. If you’re super interested you can have a look at the commit here.

After that I decided it was finally time to reintroduce text into the game. In my old implementation a Text class was a child of the Sprite class. I took some time evaluating whether that was the best approach – an alternative I thought about (and even started to code for a while) was having a Text class as a child of the GameObject instead, and just contain a SpriteComponent. However in the end I decided on a “FontComponent” that is a child of the SpriteComponent.

The FontComponent is rather similar to my old Text class, so I won’t bother to post the code. The only difference is I gave the user the option to change the colour of the text in the Draw call. What I don’t like about this implementation is that I have to create an empty GameObject in each Screen that needs text, just so I can add a FontComponent to it. And then I have to load the same font texture and xml at the beginning of each Screen. It would be nice to only have to load these once at the beginning of the game…

Anyway, I tested this and discovered that the font texture doesn’t actually use an alpha channel, just black space to represent what isn’t part of a character. Which looks ugly if the background behind your text is anything but black. No problem, right? I can just write a fragment shader that turns any black pixels into transparent pixels. So I did:

in vec4 colour;
in vec2 TexCoord;

out vec4 FragColor;

uniform sampler2D textureSampler;

void main()
{
    vec4 fontTexColour = texture(textureSampler, TexCoord).bgra;
    FragColor = fontTexColour * colour;
    FragColor.a = fontTexColour.b;
}

Great! Basically in that last line, if a pixel is black then any of b, g or r will have a value of 0. Which is exactly what I want the alpha to be.

OK, so all that was left was to create a new GLSLProgram using this fragment shader instead of my default one, and then… pass it through all my scenes and components like I was doing with my original shader program?

I could see this was not the way to go, and started doing some research. It quickly became clear (and seriously, how had I gotten this far without realising this) that I needed some sort of Resource Manager. I read through quite a few implementations, but I ended up creating my own one for now (which I think was probably influenced by a similar setup we had at my last job).

I decided that each type of resource (shader program, texture, audio clip, etc) would have it’s own ResourceStore which was basically a map wrapped in more descriptive function names. I’m still not very good at working out when it’s better to have contiguous memory – but since I never loop through these stores I think in this case it doesn’t matter (so std::map is fine in this case).

I decided to use enums for the keys in the ResourceStore. We used strings at my previous workplace, but string comparisons are slow, right? So an enum seemed like a much better idea. I store these enums in a header file which currently looks like this:

enum class ShaderResources
{
    DEFAULT_SPRITE,
    ARIAL_FONT,
    NUM_SHADER_RESOURCES
};

Not super exciting, but once I migrate my other assets into the ResourceManager it will have more in there.

Then I created a ResourceManager to take care of all these ResourceStores. This is a Singleton that contains a number of ResourceStores. I decided that the ResourceManager should be responsible for actually loading each Resource from disk (with the idea that at some point some resources might be separated into loading stages – so some resources are only loaded for a particular scene, while some might be global). As soon as I started writing the code to load the resources I discovered it would be a good idea to store the file path locations of each resource in a corresponding map.

I wrapped this in a class called ResourceLocationMap. Then in the Load function the ResourceManager loops through the ResourceLocationMap for each Resource and passes the path to a more specialised load function:

void ResourceManager::LoadResources()
{
    for (unsigned int i = 0; ShaderResources(i) < ShaderResources::NUM_SHADER_RESOURCES; i++)
    {
        m_shaders.AddResource(ShaderResources(i), LoadShader(m_shaderLocations.GetFilePath(ShaderResources(i))));
    }
}

GLSLProgram&& ResourceManager::LoadShader(std::string a_path)
{
    std::ifstream readMarker(a_path);

    GLSLProgram shader;
    char path[255];

    while (readMarker.getline(path, 255)) {
        switch (path[0])
        {
        case 'v':
            shader.compileShaderFromFile(path + 2, GLSLShaderType::VERTEX);
            break;
        case 'f':
            shader.compileShaderFromFile(path + 2, GLSLShaderType::FRAGMENT);
            break;
        default:
            break;
        }
    }
    shader.link();
    return std::move(shader);
}

I somewhat modified my shader loading pipeline too. I created a file that basically just stores the filepaths of all the shader program pieces. This is read in by my LoadShader function which then hands over the actual reading of the individual shader pieces to the ShaderLoader class.

So then I created an InitResources function (for now) that will probably end up a large list of all the things I want to load in. So far it looks like this:

void InitResources()
{
    ResourceManager::getInstance().m_shaderLocations.AddResource(ShaderResources::DEFAULT_SPRITE, "triangle.shader");
    ResourceManager::getInstance().m_shaderLocations.AddResource(ShaderResources::ARIAL_FONT, "arial.shader");
    ResourceManager::getInstance().LoadResources();
}

I’ll probably end up wrapping the ability to add a path to a ResourceLocationMap so it’s a bit neater, but this works for now.

I then collect a pointer to my resources like so:

m_pSpriteShader = &ResourceManager::getInstance().m_shaders.GetResource(ShaderResources::DEFAULT_SPRITE);
m_pFontShader = &ResourceManager::getInstance().m_shaders.GetResource(ShaderResources::ARIAL_FONT);

And hooray, it all works well! I was pretty happy that my plan worked so well – I mostly only encountered small silly typos. One bug that did trip me up for a while was that I forgot to set the projection matrix uniform before trying to draw my text with this new shader (mental note to future me – make sure to remember to set all your shader uniforms again after switching shader program).

And finally I could test my new shader! Look – now I can see the colour behind the text properly!

 

transparentText
(OK, my boyfriend wasn’t super impressed either)

So now I need to migrate all my other resources into this system. And once that’s done I can start reworking my Audio system so it works within the Component system.

Anyway, please let me know what you think of my resource management system so far. And feel free to browse through the code on GitHub.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s