Saturday, June 28, 2014

OpenGL 4.4 and beyond on Android

Things are changing so rapidly in mobile hardware that we are starting to enter an era where no longer will there a difference in feature sets or capabilities between what your phone can do, and what a high end PC can do. The difference will simply be how much power the chips are allowed to consume.

Now, I don't want to sound like I am just blindly pushing our own chips here, but I believe Tegra K1 is an early glimpse of things to come, and probably not just from us, I expect the rest of the industry to follow eventually until developers can safely assume a relatively consistent feature set from PC down to mobile. K1 is just the first example, because the GPU core in it is Kepler. And that is not just some marketing trickery, its actually same microarchitecture that powers crazy things like the GTX780. Over the last few years a lot of blood, sweat and tears went into making this possible. And while due to size, power and thermal constraints it only has 192 "cores" instead of 2304, it still smokes the competition perf wise, but more importantly its just in a completely different league when it comes to features as it is capable of full desktop class OpenGL 4.4, including geometry shaders, tessellation, compute, etc, plus extensions enabling things like bindless!

But Android only supports OpenGL ES, right? Actually, no! While the preferred and officially supported graphics API on Android today is OpenGL ES, you can already today create a "Big" OpenGL context on Android using EGL, that is, if your device supports it. This can be useful if you are porting over content from Windows/Linux/Mac/SteamOS that already has an OpenGL backend to get things up and running quickly as emulating things like clip planes, alpha test, or just the minor differences in the spec can make switching to GLES a bit of a pain. This can also serve as a great reference rendering backend to validate your port before the ES rendering path is up and running.

And don't worry, you can ship GLES and BigGL side by side in the same app! Actually, its easy! Most of the important bits here are just in EGL, which is the API Android exposes for OpenGL context creation. It is available through either Java or C/C++, but it is important to note this requires at least the 1.4 spec of EGL.

First, before you create your context (or call any other EGL functions) its good to know which flavor of GL is supported on your current device. This can be done by simply calling "eglBindAPI(EGL_OPENGL_API)", which will switch EGL to desktop OpenGL mode and return EGL_TRUE, or leave the state unchanged and return EGL_FALSE if the current device doesn't support it. Because this function toggles a global state, the safest thing to do is make this the first EGL call you make and never call it again.

Next comes context creation... if eglBindAPI() failed to switch to BigGL mode then you create your ES context just as you did before, no changes necessary there, but if it succeeded, you can now optionally create a BigGL context! Luckily EGL makes this easy. Since we already called eglBindAPI(EGL_OPENGL_API), EGL is already set to BigGL mode so we only need to tweak a few things in our configuration and context attributes.

First, in the configuration attributes which are passed into eglChooseConfig(), we need to make sure EGL_RENDERABLE_TYPE is set to EGL_OPENGL_BIT instead of EGL_OPENGL_ES2_BIT.

Second, in the attributes passed into eglCreateContext() also need minor tweaking. Typically for an ES context you will set EGL_CONTEXT_CLIENT_VERSION to 1, 2 or 3 depending on which version of OpenGL ES you want to target. For BigGL contexts this attribute isn't used, so don't set it. Infact you can actually get away with an empty attribute list on BigGL.

Roughly speaking, this looks as simple as (ignoring error checking for brevity):

  // Create a BigGL context...
  EGLDisplay display = eglGetDisplay(...);
  eglInitialize(display, ...);
  const EGLint configAttrs[] =
    // backbuffer attributes here...
  EGLConfig config;
  EGLint    numConfigs = 0;
  eglChooseConfig(display, configAttrs, &config, 1, &numConfigs);
  EGLint ctxAttrs[] =
  eglCreateContext(display, config, EGL_NO_CONTEXT, ctxAttrs);
  // TODO: Fallback to old ES context creation...

The final bit of the puzzle, and this is something I recommend for anyone using OpenGL or OpenGL ES no matter what platform they are on. But its especially important when toggling between APIs like this. And that is, don't implicitly link against and GL symbols! Even core functions you should use eglGetProcAddress(), and be careful not to share function pointers between contexts. If you did something crazy like create a BigGL context and an ES context in the same application, functions like glDrawElements(), which exist in both APIs may point to completely different implementations. This means you should only link against, and query all of your symbols through eglGetProcAddress(). Edit: it should be noted that technically this requires "EGL_KHR_get_all_proc_addresses" to be present for core functions to be available via eglGetProcAddress(), but I believe this is now implemented in Android's common EGL interface so it should be driver independent, but it might not work on older versions of Android. But if you are considering supporting BigGL, you probably don't want to mess with devices old enough for this to be an issue anyways.

Important Note: I strongly recommend that if possible, any shipping apps should also have a GLES rendering path. BigGL is useful for development, and even useful for getting at bleeding edge features, but having a GLES backend also helps Android avoid fragmentation and helps expose your app to as many users as possible.


Owen Shepherd said...

The safe thing to do in the case that "EGL_KHR_get_all_proc_addresses" is missing is to dlopen the GLES library and use dlsym to grab all of the core symbols.

Rany Albeg said...

Great post. Thanks!

LB said...

which devices do you know (even without Tegra) that supports those 1.4 spec of EGL ?