In-depth: Bringing Regal OpenGL to Native Client
In this reprinted <a href="http://altdevblogaday.com/">#altdevblogaday</a> in-depth piece, Google engineer John McCutchan examines OpenGL's portability library Regal, and how it can make porting games to Native Client and Android easier.
September 7, 2012
In this reprinted #altdevblogaday in-depth piece, Google engineer John McCutchan examines OpenGL's portability library Regal, and how it can make porting games to Native Client and Android easier. Porting your game to Native Client and Android just got a lot easier. The new OpenGL portability library 'Regal' emulates legacy GL features such as immediate mode and fixed function pipeline. Regal is the 'Write Once, Run Everywhere' GL library. At the end of July, I joined Google to help game developers make amazing games for the web using open technologies like Native Client, Dart, and HTML5. The first project I picked was to port Regal to NaCl. Regal is a new GL portability library designed so your GL 1.x, 2.x, 3.x, or 4.x application can run on a native GL library regardless of its version. Regal was written by Cass Everitt, Scott Nations, Mathias Schott, and Nigel Stewart. When an application linked with Regal makes GL calls they don't go directly to the GPU driver, instead they are handled in different ways inside Regal. Most GL calls are forwarded directly to the native GL library while others are emulated by Regal (when possible given GPU hardware restrictions). This approach allows Regal to become the 'Write Once Run Everywhere' GL library. Regal offers an impressive set of features:
Consistent OpenGL API that runs on all major platforms: Windows, Linux, Mac, iOS, Chrome (Native Client), and Android.
Emulation of deprecated OpenGL functionality. Some examples are immediate mode, the fixed function pipeline, and GL_QUADS.
Emulation of modern OpenGL functionality, for example, Direct State Access (DSA) and Vertex Array Object (VAO) are emulated when your native GL doesn't support them.
Licensed under the 2 clause BSD license. In other words: you get the source, you can change the source, and you are not obligated to share your changes even if you use them for commercial purposes. If you need to extend Regal but aren't able to share your changes with the public, it's cool.
Enhanced GL debuggability. Regal keeps an internal copy of the GL state so it's possible to set a breakpoint and inspect the GL state machine directly. Regal also has extensive logging which enables API call traces. API traces are a powerful source of data that, for example, when analyzed can be used to eliminate redundant GL state changes, reducing application CPU usage.
Presently, NaCl supports GL ES 2.0. ES stands for Embedded Systems. WebGL is based on ES 2.0 and the two have equivalent feature sets. ES 2.0 has a subset of GL 2.0 features. Think of it as GL 2.0-lite. Most games are written against OpenGL 2.0 proper. What makes it difficult to port from GL 2.0 to GL ES 2.0? Well, let's look at one example in depth: ES 2.0 removed support for the fixed function pipeline (FFP). FFP allows primitives to be drawn without writing custom vertex and fragment shader programs. Removing it introduces two problems to the porter: First, any calls to FFP functions will fail to compile because the function definitions have been removed. Second, developers must re-implement features from the fixed function pipeline they depend on. This kind of development is akin to pulling on a loose string, you often end up unravelling tons of code that subtly depended on legacy GL functionality. It's messy and near impossible to do piecemeal. As I've already mentioned, Regal emulates FFP along with other legacy GL functionality. Porting the game has gone from a tedious exercise to a simple recompile.
Porting Regal
As mentioned above, when a game linked with Regal makes a GL call, it is either forwarded to native GL or emulated by Regal. The first step in porting Regal to NaCl was finding the code that forwards the calls to the native GL library. In order to do that, I needed to understand how Regal worked internally. Internally Regal keeps track of multiple dispatch tables. The Regal dispatch table is a structure containing many function pointers, one for each OpenGL entry point. struct DispatchTable { // GL_VERSION_1_0 void (REGAL_CALL *glAccum)(GLenum op, GLfloat value); void (REGAL_CALL *glAlphaFunc)(GLenum func, GLclampf ref); void (REGAL_CALL *glBegin)(GLenum mode); void (REGAL_CALL *glBitmap)(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); void (REGAL_CALL *glBlendFunc)(GLenum sfactor, GLenum dfactor); … } When a new Regal OpenGL context is initialized each Regal dispatcher overrides certain function pointers in its dispatch table. Dispatchers include: logging, debugging, emulation, NaCl, and a dynamic loader. For the logging, debugging, and emulation Regal keeps track of a stack of dispatch tables. This stack is used to allow, for example, the logging dispatcher to call the real GL call after it has logged the call. The flow looks something like this: Note: The logging and debug layers add function calls between you and the native GL functions. Emulation of legacy GL functionality can have non-trivial performance impact. Luckily, each of these Regal features can be compiled out or disabled at runtime. When I started my port there, was no NaCl dispatcher so I added support to the Regal dynamic loader The dynamic loader works by querying for the address of each GL function the first time they are called. It does this by calling the platform equivalent of: void* getProcAddress(const char* functionName); Which under NaCl is implemented by calling dlsym. I didn't need to go the full dynamic loading route so I just created a table mapping GL function names to wrapper functions I wrote which call into the native NaCl GL interface. By implementing a NaCl specific getProcAddress I was able to watch Regal startup and respond to calls from my GL demo. A little while later I had added enough wrapper functions to get a triangle on the screen. Not just any triangle, but a triangle built from immediate mode that used the fixed function pipeline. Something not possible with OpenGL ES 2.0. Success! Now that I had OpenGL 1.0 features running on top of OpenGL ES 2.0, I wanted to see about OpenGL 3.0 features. I pinged the Regal guys, and they suggested I try out Direct State Access. DSA worked like a charm, and I started to see how powerful Regal will be when put in the hands of Native Client developers. Cut down, the code looked like this: {
// direct state access
glMatrixLoadIdentityEXT(GL_PROJECTION); glMatrixLoadIdentityEXT(GL_MODELVIEW); // immediate mode glBegin(GL_TRIANGLES); glColor3f(1.0, 0.0, 0.0); glVertex3f(0, 0, 0); glColor3f(0.0, 1.0, 0.0); glVertex3f(1, 0, 0); glColor3f(0.0, 0.0, 1.0); glVertex3f(0, 1, 0); glEnd(); }
Doing it right
At this point, I reached out to Nigel Stewart by sending him a pull request on GitHub. Nigel was great about pulling in my changes even if they were not perfect. Being relaxed about work-in-progress pull requests and iterating with contributing developers is a great way to build momentum for your project. Nigel and I both wanted the NaCl port to be a first class platform supported by Regal. The next day I woke up and noticed that Regal now contained a proper NaCl dispatcher built using Regal's code generation system. Over the week Nigel and I iterated back and forth until the NaCl port became seamlessly integrated the rest of Regal.
Getting things small
Regal has multiple implementations of every function from every version of OpenGL. One for logging, one for debugging, etc. "With great power, comes large binary sizes." I may have messed that quote up. NaCl executables are downloaded over the web so every byte counts both for the end user and the person who pays the bandwidth bill. Now that the port was functional, I needed to get the binary size down. The initial weigh in was 16MB, and that was just the .text segment which means even stripping the binary wouldn't help. Most features in Regal are optional and many are only useful during development. RegalDispatchLog.cpp, I'm looking at you. After throwing logging off the island the .text segment slimmed down to 9.5MB, a 40% drop. Nigel spent some time making it possible to build Regal with many features turned off at compile time. After flipping all those switches, the .text segment weighed in at only 0.2MB, a 98% drop in binary size.
Built-in shrink ray
By correctly configuring the web server hosting the NaCl application, we can get things even smaller. Web browsers support receiving data that has been gzipped by the server. Make sure this is enabled, it will save you money and make your users happier!
Using Regal in your NaCl projects
Using Regal in your NaCl game is the same as using any other GL library. After you've created a NaCl GL context and interface you call one function to initialize the Regal GL context: // Initialize Regal using the NaCl GL context and interface RegalMakeCurrent(opengl_context, ppb_opengl_interface); Optionally, you can capture logging output by making this call: // Initialize Regal logging system glLogMessageCallbackREGAL(regalLogCallback); The callback function should have the following definition: typedef void (*RegalLogCallback)(GLenum stream, GLsizei length, const GLchar *message, GLvoid *context); You must add the following to your link line: -lRegal
What about Android?
Android games are also limited to OpenGL ES 2.0. Lucky for Android developers, Regal is already ported to Android. If you are doing game development on Android and are porting, consider targeting Regal to accelerate your porting efforts.
Conclusion
Porting your game to NaCl and Android just got a lot easier. Regal removes many of the hurdles and will get you up and running quickly. Once you're up and running you can whittle your usage of desktop OpenGL features down to zero. The key here is that with Regal it can be a gradual move away from desktop OpenGL to OpenGL ES. Regal offers some compelling features for all OpenGL games, even desktop GL games. For example, I'm sure Regal's built-in logging and debug dispatchers will save some frustration.
Getting Started
Bugs
Note: Regal is still in early development. Use it with caution. If you run into a bug with the NaCl port, shoot me an email and I will help fix it. [This piece was reprinted from #AltDevBlogADay, a shared blog initiative started by @mike_acton devoted to giving game developers of all disciplines a place to motivate each other to write regularly about their personal game development passions.]
You May Also Like