3

I'm creating a basic 3D game in C++ with SDL2 and OpenGL3, and one of my learning goals for this project is to design my code around the ability to swap out SDL or OpenGL. However, the problem I'm finding is that the libraries are very coupled. It doesn't seem like I can just defineSDLWindow and GLRenderer classes due to functions like SDL_GL_*() (e.g SDL_GL_CreateContext()). Placing this function within SDLWindow would be a problem if I switched to Vulkan, while placing it in GLRenderer would be a problem if I switched from SDL.

What I would like to know is what strategies can I use to decouple these dependencies? Based on my knowledge, I assume I need some mechanism to conditionally call the appropriate functions, so my mind goes to using enums and switch statements, but I can either have each class function do this or create concrete implementations based on specific combinations of libraries, so SDLGLWindow instead of SDLWindow and SDLGLRenderer instead of GLRenderer, and then use a factory function that uses an enum and switch statement to select the correct class(es). In both cases, though, it feels like a lot of unnecessary code, which makes me doubt how practical of a solution it is.

5
  • 1
    Are you trying to avoid virtual methods? Why you consider names like SDLWindow? Would not MyGraphicalPlatformWindowImplGL make more sense? Commented Sep 28 at 17:55
  • 2
    Define a high(er) level API, implement it and use it through polymorphism. In fact static (compile-time) polymorphism via templates. It is extremely unlikely that you are gonna switch rendering backend at runtime, so don't bother with virtuality. And always prefer polymorphism over enums+switch combination. Commented Sep 28 at 21:24
  • 1
    I think before approaching this with sophisticated C++ template mechanics, start to learn the basics. In this case, the abstract factory design pattern could be a good start, it helps you to manage a family of related classes, which is probably what you need here. Commented Sep 29 at 6:40
  • ... Moreover, wrapping a complex library like SDL in a C++ class library can add a significant amount of extra work to your project. Are you really sure this will ever pay off? Commented Sep 29 at 8:22
  • I'm not an expert on SDL and OpenGL, but why are you trying to use SDL functions in combination with GLRenderer functions? SDL has its own renderer, SDLRenderer, which will use GLRenderer (or the Vulkan renderer). Use of OpenGL functions should not be necessary to create a SDL application. Commented Sep 29 at 10:32

3 Answers 3

3

The usual way to do it is to create a class that you can swap for another one at will. The key is to have an abstraction over the low level stuff, making it possible for the caller to ask what to do, not how to do it.

Let's say you write an application for a library that handles a list of books. If the books are stored in a SQL database, they can be changed through three basic SQL operations: insert, update, and delete, and they can be retrieved using select. Now, those operations are low level—they are SQL dependant. If you decide to swap the database for plain text storage where the information about each book is kept in a separate JSON file on disk, there is no such thing as selecting files or inserting files. Nor would you update a given field—you'd rather read the whole file, change it in memory, and write the whole file back.

Now, if you have a more abstract approach where you have an interface that gives you a possibility to add or remove a book, or search for a book, or correct the author or the ISBN, then you reason in terms of an abstraction. It then belongs to a particular class to translate this abstraction into the precise actions for a given underlying technology.

(A single class would work for simple stuff, but for more complex implementations, you'll need this class to rely, in turn, on other classes. For instance, in my previous example, the class that makes it possible to persist books to a database would possibly use a whole third party library for a given database variant, but may also need some more specific classes, such as the one that would translate the violation of database-level constraints to human-readable error messages.)

How do you perform an actual swap depends on you—and the technologies you use. As stated by freakish in the comment to your question, you don't need, in your case, to swap the classes during runtime, and templates would be indeed a way to go over inheritance and virtual members. Things would be different with other languages—you'd very likely rely on the actual interfaces in Java or C#, for example.

Make absolutely sure, however, that you do test that all implementations compile. In fact, you don't want to find yourself in a situation where you forget about one of the implementations for some time, just to find out much later that it is completely outdated and needs severe rework in order to be used.

3
  • "The usual way to do it is to create a class that you can swap for another one at will." I think that is what OP already tried. Still, SDL is sufficiently complex enough to require not just one class, but a whole class library, with specific classes for the underlying layers (like OpenGL), and that's where the trouble starts. Commented Sep 29 at 6:36
  • @DocBrown: thanks. I edited my answer by adding the fourth paragraph. I'm not sure, however, that this is the actual difficulty, given the way the question is phrased, the examples that were given, and the mention of enums and conditionals. Commented Sep 29 at 8:04
  • I guess you are right to the degree that this is not the only problem the OP is facing, probably they have to learn the very basics like how to use polymorphism or the strategy pattern first. Commented Sep 29 at 8:16
1

It sounds like you are trying to hang on to too much.

These low level calls will be part of a presentation layer. If you accept that you will lose the entire presentation layer if you switch out openGL. Then you should be able to keep your core game code.

If you try to encapsulate the low level gfx calls then you will find it hard, and, even if you can achieve it, you will be coupled to the design of those calls, which might not find analogous functions in a different gfx framework.

There is a balance between the extra added time it will take to split your presentation layer into 2 sub layers vs the time it would take to re write the presentation layer later if you switch tech.

If you have both frameworks to hand, say openGL and directX I expect you could split the presentation layer some. You will know the full requirements of each set of functions and be able to code around them.

If you just want to leave room to switch out to an unnamed different tech at some future time, I wouldn't bother, there are too many guesses involved. Just make sure you can switch the presentation layer out.

1

What I would like to know is what strategies can I use to decouple these dependencies?

What you need is to define your games rendering needs. If it needs to put a dot on the screen define that in a way that is not dependent on the rendering engine.

The problem here is how easy it is for those dependencies to emerge. You think you know what a dot is until you change rendering engines and now what you thought was a pixel is now a percentage of the screen.

It's all too easy to let the needs of the rendering engine leak into your abstraction. It's far more than simply the interface. You need a clean way to model what should be rendered.

Start small. Test the model against as many engines as you can get your hands on. You'll start to see what decisions can't be left to the engine and must be dictated by your model.

You may find there isn't any one model that allows for every rendering engine. Sadly, if your needs drive you there then you'll be stuck having to write adapters to get your game working with different rendering engines. In the world of printers we call them drivers. Same trick. Different surface.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.