Godot Development: MSAA in the GLES2 backend

in #utopian-io3 years ago

Repository

https://github.com/godotengine/godot

New Features

Anti-Aliasing

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");

https://github.com/godotengine/godot/blob/aa3c5f59f27055245004ef19622e895e7b99a297/drivers/gles2/rasterizer_storage_gles2.cpp#L5590

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:
https://github.com/godotengine/godot/pull/28518
Fair warning, the PR contains days of desperately trying to get the changes to compile on Android and ios.

BONUS

Bug Fixes

For fun I have included a bunch of bonus PR bug fixes of mine that have been merged in the last few weeks.

Transparency order in GLES2
  • 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
Radiance addition + bugfix
  • 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

Projection matrix bug
  • 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.

GitHub Account

https://github.com/clayjohn

Sort:  
  • Great post with lots of images, code samples and explanation of coding choices.
  • Great job on getting your improvements merge on such an impressive project
  • Great commit messages and code comments
  • I'm curious to know why you left this dead code in:

Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.

To view those questions and the relevant answers related to your post, click here.


Need help? Chat with us on Discord.

[utopian-moderator]

I was hoping you wouldn't ask, haha!

That was left in because my gut tells me it is necessary to make these changes work on iPhone. However, I don't have an ios dev kit or any ios devices to test on. I essentially relied on the integrated CI in Godot's github to make sure that compiling against ios was working. The compile error messages made it sound like something in the SDK was performing the extension loading already and I was overwriting the function pointers. Accordingly, I removed the lines and it compiled fine. However, the core devs and myself suspect that this code (or something similar) may be necessary, so it was left in for now. Ideally we will get some feedback over the next few months that can help us decide how to move forward (it is equally likely that they will decide to just not support MSAA on older ios devices).

Thank you for your review, @helo! Keep up the good work!

Hey, @clayjohn!

Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!

Get higher incentives and support Utopian.io!
Simply set @utopian.pay as a 5% (or higher) payout beneficiary on your contribution post (via SteemPlus or Steeditor).

Want to chat? Join us on Discord https://discord.gg/h52nFrV.

Vote for Utopian Witness!

Hi @clayjohn!

Your post was upvoted by @steem-ua, new Steem dApp, using UserAuthority for algorithmic post curation!
Your post is eligible for our upvote, thanks to our collaboration with @utopian-io!
Feel free to join our @steem-ua Discord server

Coin Marketplace

STEEM 0.46
TRX 0.08
JST 0.060
BTC 49159.84
ETH 4180.02
BNB 550.02
SBD 5.80