You can obtain the all the code I used for this post from GitHub: https://github.com/amesgames/RenderDevice.
In my day job, we have a rendering engine where we have abstracted the graphics library behind our own interface. We deliberated at length on whether or not we should abstract the graphics library away or program directly to it and abstract later, once there is a strong business need for it.
Regardless of where my company ends up, there are definitely some advantages of abstracting the graphics library away. Namely, for platform portability and business agility. Especially in the game industry, this is quite an important advantage. For example, having a working PC engine even if your game is only shipping on Playstation can make programming and content development much easier. Another advantage is that you can hide some details of the graphics library that you never intend to use or raise the level of abstraction slightly in order to eliminate common errors. This can make graphics programming slightly more accessible for junior staff members.
In this article, I will show what a typical graphics library abstraction can look like in a rendering engine. We will call the abstraction a render device.
I was hesitant to start a rendering engine series with a render device abstraction for several reasons.
First, abstractions are difficult. We want the abstraction to have minimal performance overhead; we want it easy to use; we want it easy to maintain; we want it to surface all functionality; we want the abstraction to not “leak”; and so on.
Second, if you are one of those junior programmers beginning your career, starting off with OpenGL or DirectX, and not abstracting them, is a better choice. If you have never seen more than one graphics library, you are not going to know how to abstract it.
Finally, if you work for a professional graphics team, abstracting the render device will depend on your business’ needs. If you are a Windows-only shop building software expected to only ever run on Windows, you may want to choose DirectX and not abstract it away. Abstracting the render device when your business does not want or need it can limit turnaround time for features due to the extra cognitive burden of an extra abstraction.
Assuming you have the desire or need to abstract the render device in your engine, this article can help. To follow along, you should already be familiar with general graphics programming principles and have experience programming with at least one graphics library, preferably in C or C++.
Personally, I prefer having a render device abstraction, even in my hobby engines and projects. That way, I can easily port to a new platform and graphics library and have all of my projects work in that environment.
In this article, we will implement our render device around the OpenGL 4 core profile. We will make use of the GLFW library. GLFW abstracts away the platform-specific concerns of a window, swap chain, and OpenGL context creation.
I am choosing to use GLFW to abstract various platform concerns that are not directly related to the render device, such as input. In a production-ready, cross-platform engine, you will also need to abstract the window and other platform elements. In this article, we will leverage GLFW for those concerns so that the code we build will work on as many platforms as possible.
Porting to DirectX will require us to abstract away more of those platform components using a library other than GLFW. That will not be covered in this article.
I am using CMake for the build system. Again, to ensure the code is easy to run on many different platforms.
I have tested the code on Windows 10 using Microsoft Visual Studio 2015 and on Mac OS X (Sierra) using Xcode 8.