Six weeks ago, I wrote a post on this blog about rewriting the render core of Caromble!. Back then, I thought I was only a few days away from finishing the project. But it turned out to be quite a bit tougher than expected. It seemed that every corner we cut during the past couple of years, came back to haunt us and delay finishing the rewrite.
But just a few hours ago, we finally managed to merge the changes back to the game, and that means that the old renderer is history. I will now briefly explain how the new core works. If that sounds too technical, just scroll down to look at the screenshot that proves that the Caromble! can now run on MacOS. Yeah!
For Caromble! we are using the Ardor3D engine to power the graphics. Ardor is quite flexible, shielding programmers from the exact versions of OpenGL they were using. This is a great way to keep games running on old hardware, while still allowing programmers to add new features for newer devices. The big drawback of this approach is that it mixes fixed function OpenGL stuff with the newer stuff. And that makes it impossible for programmers to explicitly use one or the other. Which is exactly what we needed to do if we wanted to have the game running on MacOS. Because for MacOS only OpenGL 3.2 is currently supported (if you want to use any of new stuff). For Windows, we’ll mostly be using OpenGL 3.2, except for the Intel HD embedded GPUs, which support only OpenGL 3.1.
The nice thing about OpenGL 3.1+ is that it will allow you to have very precise control about where data lives and how it is transferred to the rendering pipeline. For instance, in the old days if you wanted to specify the normals per vertex, you would have to tell a special “normal-pointer” function that the current buffer is used for normals. Later, during rendering, the gl_Normal variable would be magically available to read back these normals.
In modern OpenGL these special variables no longer exists. If you want normals you have to bind the normal buffers to a generic target, and then bind the generic target to the input that you’ll use for normals. It is a bit more work for the normal case, but it gives you much greater flexibility and allows you to write much cleaner code (more stuff can be shared).
So that means the core of our renderer will boil down to the following.
For each mesh that you want to draw you do the following during initialization:
- Create a Vertex Array Object. Confusingly named, this object will hold all pointers to all data describing you mesh.
- Gather all your vertex data (positions, normals, texture coordinates, …) and put it into Vertex Buffer Objects. Upload these to the graphics card.
- Explicitly bind these buffers to inputs of your vertex shader. (No more magic values such as GL_Normal).
- Upload the textures you’ll need an keep track of them.
During rendering we first activate the shaders we will be using for this renderpass (deferred shader, light shader, transparent object shader, etc). Then, for every mesh we need to draw we do the following:
- First we upload the relevant scene information to the graphics card (such as matrix transforms and the near and far plane). Strictly speaking it is a bit of a waste to do this for every mesh, but we have yet to make that optimization. (And we pixel shader limited right now anyway).
- Now we look up the textures that belong to this mesh, and bind them to the correct texture slots.
- We now bind the correct Vertex Array Object and tell OpenGL it is time to draw something
In a nutshell, that is how our current render-core works. Of course there is a more to it, but this is the most important bit.
For us an import advantage of our new render-core is that it is much smaller than the old core. There is only one code path that all objects take, and that will make it much easier for us to optimize the game during the last phase of development that we are now in. So while it took a lot of time and effort to port our render core to support OpenGL core-profiles, we are confident the investment will pay out by making our code much easier to maintain.
And with the promised screenshot, that will be all from me for now :-)