1

I write my own 3D-graphics engine for education and have some difficulties with it's architecture. I wrote classes like OpenGLTexure, OpenGLMaterial, OpenGLGpuProgram, etc. I also wrote a class ResourceManager for loading textures, shaders and materials.

Now I want to separate OpenGL graphics component of my engine in order to add DirectX support too. I have realized interfaces for graphics classes (ITexture, IMaterial). But my resource manager uses OpenGL functions to load and initialize resources (for example, shaders) and I don't know how add DirectX support with this architecture right. I think I can write factory class to create objects for every graphics API, but how can I choose what class should I use.

How can I solve this problem right?

1

There are a few ways you can control which type of resources to return. It will depend on how a user of your library uses the library. If they include the full source (like Unity and Unreal Engine do), the easiest way is to require a user of your library to define which system they're using - OpenGL or DirectX - at compile time. You might require the user to define some preprocessor macros like this:

#define IMPLEMENTATION_OPENGL 1

for OpenGL and

#define IMPLEMENTATION_DIRECTX 1

for DirectX.

You'd require that a user of your library defines which of those to use in some pre-compiled header. When the compiler got to the files of your library it would check which one to build, like this:

class ITextureLoader {
    public:
        ITextureLoader(const char* imagePath);
        ~ITextureLoader();
    // ... etc.
};

#if IMPLEMENTATION_OPENGL
class TextureLoaderOpenGL : public ITextureLoader {
    public:
        TetxureLoaderOpenGL(const char* imagePath);
        ~TextureLoaderOpenGL();
    //... etc.
};
#elif IMPLEMENTATION_DIRECTX
class TextureLoaderDirectX : public ITextureLoader {
    public:
        TetxureLoaderDirectX(const char* imagePath);
        ~TextureLoaderDirectX();
    //... etc.
};
#endif

Then you'd have a TextureLoaderFactory that would return an ITextureLoader object like this:

ITextureLoader* CreateTextureLoader(const char* imagePath)
{
    #if IMPLEMENTATION_OPENGL
    return new TextureLoaderOpenGL(imagePath);
    #elif IMPLEMENTATION_DIRECTX
    return new TextureLoaderDirectX(imagePath);
    #endif
}

If you aren't including the source for users, then you need to decide at runtime. It might be easier in that case to just build 2 different libraries for the user. It's not very frequently that a user wants to use both OpenGL and DirectX in their application.

You could also have them create some sort of context object that defines things like which renderer to use. They'd then pass that context object to the factory methods that create the concrete objects. Something like this:

enum Technology {
    Technology_OpenGL = 1,
    Technology_DirectX = 2
} Technology;

struct TechnologyContext {
    Technology tech;
    int majorVersion;
    int minorVersion;
};

Then your factory would look like this:

ITextureLoader* CreateTextureLoader(const TechnologyContext& ctx, const char* path)
{
    if (ctx.tech == Technology_OpenGL)
    {
        return new TextureLoaderOpenGL(path);
    }
    else
    {
        return new TextureLoaderDirectX(path);
    }
}

As a user, I would find this more cumbersome, personally.

  • Thank you very much. The second variant seems good for me :) – Nikolai Paukov Jan 6 '18 at 17:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.