C++ Rendering Engine I – Abstracting the Render Device

You can obtain the all the code I used for this post from GitHub: https://github.com/amesgames/RenderDevice.

Introduction

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.

Dependencies

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.

6 thoughts on “C++ Rendering Engine I – Abstracting the Render Device

        1. Andy Post author

          It certainly has been a while.

          I am currently working on a Vulkan engine (one at work and one at home). Soon, I will write about the choices I am making to support both OpenGL and Vulkan abstractions without sacrificing performance or expressiveness with Vulkan.

          Reply
  1. Mathew

    Thank you so much, words can explain how great it is to finally find something on this topic that is relevant and makes sense. I haven’t yet gotten to implementing the shader uniforms or texture part of the abstraction but with the knowledge I’ve gained so far, I now I understand that the possibilities are truly endless.

    I honestly think I’ve never truly grasped abstraction until today.

    I now understand that in order to create an API agnostic rendering engine, things need to be broken down, but not to the point where you end up defining methods that are specific to each API.

    When I first saw that you created a “VertexShader” and a “PixelShader” class I was so confused thinking to myself “But wait a minute… Those two things could just be grouped together as one ‘Shader’ class” and then as I soon as I started to implement the design myself… Something clicked inside of me! Those two classes are of course better off separate because we as programmers are horrible at seeing the future.

    Once again, I am thrilled to have found these articles.

    Thank you!

    Reply
    1. Andy Post author

      Thank you so much for the encouragement. A year later, it really is about time to do the next article in the series.

      My recent work is in task-based multithreading. Perhaps it is time for a particle system post.

      Reply

Leave a Reply to Nabil Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.