5
\$\begingroup\$

In games like the new "Doom", you have a console, from which you can use commands to reduce the game's quality or even to switch graphics API, from Opengl to Vulkan and viceversa.

How is this done? I think it reconstructs the window context based on the chosen graphics API (maybe?). How could this be implemented?

\$\endgroup\$
  • \$\begingroup\$ I haven't seen how Doom does it, but if you make a game reasonably organised and have separated the game logic from the graphics library, it's possible to just switch which graphics library you are pointing to. Most likely you need to re-construct the context (which would mean re-creating the window as you said) but one could theoretically handle the context themselves and only use a graphics library for the drawing part. \$\endgroup\$ – TomTsagk Apr 10 '18 at 11:59
  • \$\begingroup\$ Thanks. The way Doom does it is through an in-game console btw \$\endgroup\$ – Gabriele Vierti Apr 10 '18 at 12:00
  • \$\begingroup\$ Often times the game just kills the graphics context and restarts it. Many games require you to restart the game so that graphics settings can be reinitialized. \$\endgroup\$ – Sidar Apr 10 '18 at 12:00
  • \$\begingroup\$ @GabrieleVierti Does it close the window and re-open it? if not, probably they have separate code to handle the window and separate for the graphics context, but I could be wrong. \$\endgroup\$ – TomTsagk Apr 10 '18 at 12:10
  • \$\begingroup\$ @TomTsagk Yes, apparently it closes the window and reopens it \$\endgroup\$ – Gabriele Vierti Apr 10 '18 at 12:24
6
\$\begingroup\$

A common way to approach this, assuming you actually want to (see postscript for why you may not), is to implement your game in terms of a rendering interface and simply implement that interface for all graphics API back-ends you want to support.

For example if your game is only capable of drawing colored squares:

struct IRenderer {
  virtual void Initialize() = 0;
  virtual void Shutdown() = 0;
  virtual void DrawSquare(int x, int y, const Color& color) = 0;
};

You then implement this interface for D3D and OpenGL (and anything else you want to support):

struct D3DRenderer : IRenderer { ...details... };
struct OpenGLRenderer : IRenderer { ...details... };

There's a huge spectrum of specific options you could use to do this, ranging from the direct approach to using PIMPL to hide the implementation, putting the implementation directly in the game versus shoving it off into a DLL in case the user never wants to switch, et cetera. The subject would make an interesting book.

Then, to swap at runtime, when the user issues the appropriate command, you call Shutdown() on the current renderer, initialize a new renderer and call Initialize() on it, and continue.

Rendering is a fundamental part of a game, so it's likely you will need to spend a non-trivial amount of time setting up the code to execute this handoff correctly. You're almost certainly going to have to close and re-open the underlying OS window, depending on the APIs you use, although you may be able to do some tricks there. But more importantly you're going to have to make sure every outstanding game object that has any kind of reference to some object in the rendering API gets that object swapped out with a replacement or otherwise knows how to handle this major bit of platform infrastructure being swapped out underneath it.

You can be as blunt about it as requiring the user to essentially terminate the gameplay session and restart from the main menu (this is probably the more common approach), or you can be as complex about it as trying to hot-reload everything so cleanly you preserve the precise state of everything's animation, et cetera. The latter is way harder. I'd recommend you look at techniques used for "hot reloading" general game assets to learn about the basic stuff you'd need to do there.


That said, I don't generally recommend people do this. Especially small teams or hobby developers. It takes a lot of effort, as you can hopefully see by even my rough sketch above. For that effort, you get very little payoff. It's an interesting technical achievement but the ability to hot-swap rendering systems in the middle of the game isn't usually a major selling feature.

If you are small or single-person team, I would generally recommend you support different rendering backends at compile time only, and only when necessary to get the "best" API for your supported platforms. If you're going to ship on Windows and macOS and want to support D3D12 and Metal so you can achieve optimal graphics quality and performance on each platform, great. But if you also want to include OpenGL and Vulkan on those same two platforms you've massively increased the workload for yourself and you're likely to end up with substandard implementations of everything instead of good implementations of a specifically chosen technology.

\$\endgroup\$

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.