Godot Development: MSAA in the GLES2 backend
For this contribution, I added Multisample Anti-Aliasing (MSAA) to the GLES2 backend of Godot. Aliasing is a property of rendered graphics where you see jagged lines caused by pixelation. Anti-aliasing comprises a range of techniques for reducing jagged edges in computer graphics. Below is a sample of different techniques from the QT documentation (open full size to really see the difference).
Almost all games have settings for anti-aliasing quality, typically FXAA, MXAA (at differing levels, 4X, 8X, 16X), and for higher end games TAA. Godot relies on MXAA which is usually built into the graphics drivers themselves. In the higher end backend (GLES3) this has always been supported but it has not been in the lower end (GLES2) backend.
GLES2? What is that?
GLES2 is the compatibility backend for Godot's renderer, it is meant to be leaner and able to run on almost any device.
A word about the backends here, Godot has two distinct rendering backends. GLES3 is its primary rendering backend, it supports the most features and has the better quality of the two. However, it is only supported by newer hardware (OpenGL ES 3.0 was released in 2012, so new here is relative). Many devices, especially lower-end mobile devices are still lagging behind and do not support OpenGL ES 3.0 and with it the better subset of features. Accordingly Godot offers a backend for older devices, GLES2. The GLES2 backend is new with the 3.1 release of Godot and still needs a little love and polish. One of the things many users missed was the ability to have Multisampled anti-aliasing.
Implementing MSAA part 1: Loading the OpenGl extension
At first I didn't think this would be such a large project. I have used MSAA in almost all of my OpenGL-based projects and it is usually easy to implement (famous last words, I know). In order to use MSAA in OpenGL ES 2.0, you need to check if the hardware supports it. Many hardware vendors provide support for features like MSAA, even though it isn't in the core specification of OpenGL ES 2.0. For Desktop applications (X11, WIndows, OSX) this can be done easily by polling the available extensions:
// Check for multisample support config.multisample_supported = config.extensions.has("GL_EXT_framebuffer_multisample") || config.extensions.has("GL_EXT_multisampled_render_to_texture") || config.extensions.has("GL_APPLE_framebuffer_multisample");
However, on Android and ios it wasn't so simple. If you read through the PR log you can see me struggling to understand how to do this simple operation in Android and ios. In the end, when the PR was merged, we were still unsure that it was done the best way possible. It works on all the hardware its been tested on. But the problem with supporting legacy devices is that oftentimes things are implemented in unexpected ways and things that should be easy end up breaking randomly.
In contrast to that nice single-line above. For android I needed to manually define the function pointers
#include <GLES2/gl2ext.h> PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC glRenderbufferStorageMultisampleEXT; PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC glFramebufferTexture2DMultisampleEXT; #define glRenderbufferStorageMultisample glRenderbufferStorageMultisampleEXT #define glFramebufferTexture2DMultisample glFramebufferTexture2DMultisampleEXT
And then resolve them to the library:
void *gles2_lib = dlopen("libGLESv2.so", RTLD_LAZY); glRenderbufferStorageMultisampleEXT = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC)dlsym(gles2_lib, "glRenderbufferStorageMultisampleEXT"); glFramebufferTexture2DMultisampleEXT = (PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC)dlsym(gles2_lib, "glFramebufferTexture2DMultisampleEXT");
We think this will ensure maximum compatibility, but until someone tests on a really old device, we won't be sure.
Implementing MSAA part 2: rendering
In order to take advantage of MSAA, the renderbuffers used to render the screen need to be allocated with Multisample enabled. This is done with custom OpenGL function calls that were loaded above. This happens here.
Then, when rendering in order to draw the framebuffer to screen you need to copy it. The process of copying it is different whether you are using desktop, ios or Android. It can be found here.
Finally, with all that put together, you can now use MSAA in GLES2 but only on devices that support it. Devices that don't are still stuck with aliased rendering. Hopefully this will make it easier for people to produce high-end graphics on low-end software.
You can find the relevant PR here:
Fair warning, the PR contains days of desperately trying to get the changes to compile on Android and ios.
For fun I have included a bunch of bonus PR bug fixes of mine that have been merged in the last few weeks.
- Issue: https://github.com/godotengine/godot/issues/27141
- Description: This was a longstanding bug in the GLES2 backend that made it impossible to use transparent objects. Whenever transparent objects were used they were rendered in the wrong order. Further objects were rendered in front of close objects. The fix ended up being implementing reverse depth sorting rather than regular depth sorting (which had been mistakenly implemented).
Two objects of the same size, the smaller one appears smaller because it is far behind the other one, but is drawn in front
- Issue: https://github.com/godotengine/godot/issues/27888
- Description: This was an interesting bug. Godot only took the light contribution from the background into account when the sky was read from a cubemap (actually an equirectangular map in Godot). If the background was a solid color it wasn't used. The issue comes into play when reflective materials are used. As the color of metals comes mostly from their reflections (in a PBR renderer it all comes from reflection). So when using a solid color background, all metals suddenly rendered black. The solution was to add a separate code path for when a solid background was used. In the PR is also changes to the calculation of environment lighting that makes the Godot renderer energy conserving. Energy conservation is a property of a good PBR renderer. Overall, it will make objects appear more natural, and make the Godot renderer higher in quality, more like the renderers in Unity or Unreal.
Before, all light is lost
Middle, solid color background taken into account, but leaking light
After, all light contained
- Issue: https://github.com/godotengine/godot/issues/23185
- Description: This bug was relatively minor (fixed with only a few lines of code) but it cause shaders to crash when compiling. The issue was caused by Godot allowing users to write to an input variable (which is constant by nature). The solution was to make a local variable and define all references to the input variable to point to the local variable instead.