How do you design the code of a graphics engine to target multiple frameworks I am starting to think about how I can make my app (currently written in Metal) available on older device that dont have Metal as well as Android. This is a predicament because while I could write two separate rendering classes that use the different frameworks it doesn’t teach me anything about how to code for multiple graphics libraries. I can only get away with this because my rendering code is fairly short and has few states and draw calls. I want to learn for the future.

Writing a renderer that supports multiple frameworks especially becomes difficult when thinking of the two libraries I hope to support — OpenGL and Metal — which are quite different in how they expose the GPU.

I am curious how exactly one usually does design their code for apps that can target multiple graphics languages. Here are my theories of what could be done:


Write two different rendering classes keep the data and simulation stuff in its own area and just vary the renderer.
Take high level functions like createTexture or copyTextureOnto and have an if statement checking which language the device supports and have a custom implementation for it.
Coming up with your own graphics objects that are akin to the objects from the framework (such as MTLCommandEncoder or MTLTexture) and use those to build you app and just use the implantation that matches your graphics frameworks. Essentially recode the functions one of your graphics libraries has that you use and just add an if statement to your implementation is called on a OpenGL supporting device.


When you have to code a software to support multiple graphics libraries how do you generally do it? Do you loose any efficiency with the technique you use?
You do it by abstracting away the underlying technology. You know, for example, at a very high level that you'll need to work with images, geometry, and I'm guessing you'll have to write shaders to do your rendering. The textures and geometry can be in the same format for both systems.

So from that, you can design an interface for rendering. You'll probably have a scene of some sort that has the geometry in it. The geometry will likely have the textures applied to it. When rendering the geometry you'll need to use a shader. Those might be good places to start with your abstraction. You might need a scene object that holds all the different objects you want to render. Each object is made up of geometry and the images that will be applied to the geometry. The scene object, the geometry objects and the images can be the same between OpenGL and Metal because they aren't specific to either. (I'm assuming here that the geometry is just a buffer of floating point coordinates, normals, etc. and the image starts out as a buffer of RGBA pixels on the CPU.)

Next you need to come up with a way to render those things on the hardware you're currently running on with the framework you wish to use. So you create an abstract interface. Perhaps you have a Renderer object. It would have methods that allow you to add a piece of geometry with an image to use as the texture. It would allow you to associate a shader with the geometry. And it would have a method to render the geometry to either a texture or the screen.

You then either subclass the Renderer class (assuming you're using something like C++) or write a new class that implements the Renderer protocol (if you're using something like Objective-C or Swift). You have 2 different subclasses or implementations. One that does things using OpenGL and one that does things using Metal. 

So you might have an OGLRenderer and a MTLRenderer. The OGLRenderer class will implement the addGeometry() method by creating a Vertex Buffer Object and eventually calling glBufferData() to actually upload the geometry to the GPU. In your MTLRenderer you'll create a MTLBuffer via -newBufferWithLength:options:, and then copying the data into the buffer with memcpy(). And so on for the other functions.

So when writing the main code for your application, you'll code to the Renderer interface without knowing whether the underlying renderer that you're actually going to use is an OpenGL renderer or a Metal renderer. (In fact, you could even make it so they can be switched at run time if that would help in some way.) You can have a factory that either decides at run time which to use based on the OS you're on, or you can decide at compile time by only including the appropriate subclass.