How do you decouple SDL (and other libraries) from the graphics api?

I am creating a basic 3D game using SDL2 and OpenGL3, and while I intend to stick with these libraries for this project, I still want to abstract/wrap them so that I don’t leak types everywhere. Also, if I ever get around to learning another graphics API (which I’ll be doing in later semesters of college), it could be a little bit easier to swap things. What I’m noticing, though, is it’s not as simple as having SDL implementations and OpenGL implementations because there are functions like SDL_GL_SetAttribute. Also, I use libraries like ImGui and RmlUi (for creating game UI) which rely on SDL2 and OpenGL creating a similar problem.

I had asked a similar question elsewhere, and the suggestion I got was to have another class to, I guess, facilitate/communicate between the two? The only other solution that I can think of is just to wrap everything in switch statements or macros and change the logic based on some flag. I am open to other suggestions, though.

For reference here is some of my code.

void SDLPlatform::CreateWindow(std::string title, int width, int height, bool resizable)
{
    mMainWindow = new SDLWindow(title, width, height, resizable);
}

SDLWindow::SDLWindow(std::string title, int width, int height, bool resizable)
{
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);

    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
    SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);

    Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;
    if (resizable) {
        flags |= SDL_WINDOW_RESIZABLE;
    }

    mWindow = SDL_CreateWindow(
        title.c_str(),
        SDL_WINDOWPOS_CENTERED,
        SDL_WINDOWPOS_CENTERED,
        width,
        height,
        flags
    );

    mContext = SDL_GL_CreateContext(mWindow);

    SDL_GL_MakeCurrent(mWindow, mContext);
    SDL_GL_SetSwapInterval(0);
}
GLRenderer::GLRenderer()
{
	gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress);

	glEnable(GL_DEPTH_TEST);
}

(EDIT: I was making fun of an obvious ChatGPT reply here, but I’ve since deleted the bot account I replied to.)

Okay, ChatGPT nonsense aside, the first thing I would recommend is: don’t abstract out your renderer. SDL+OpenGL is probably fine until you get into serious stuff. Until that time, just getting pixels on the screen should be good enough to keep you building something interesting. Don’t design for perfection, just get something working first and worry about how to abstract it out later (or never!). Abstraction is good when applied to the right things, and it’s poison when you’re doing it “just in case,” for later.

But if you’re going to do this: yes, the two pieces (SDL_Window and OpenGL) do blur. It might make sense to have an abstract Renderer class, and that thing will manage the window, too (so the GL renderer’s constructor can have all the SDL_GL_SetAttribute calls, an SDL_CreateWindow() with the SDL_WINDOW_OPENGL flag, and an SDL_GL_CreateContext() call…if you really want them totally separate, you can at least have it be in charge of creating your SDLWindow object through a constructor that passes the right flags.

A Direct3DRenderer subclass would create the window, get its win32-specific HWND, and use that to create a D3DDevice. Etc, etc.

And then each renderer would implement its various methods: DrawTriangle, PresentToWindow, etc.

In C, not C++, you could look at how SDL’s own 2D API handles this (it keeps a bunch of function pointers for each implementation…which is basically just simulating what C++ classes do in C.).

1 Like