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.