If you’re an OpenGL developer, you might find yourself pining for an effect file format. You’d like a standard way of specifying shader strings, but without creating a kazillion little files for all your shaders.

There aren’t many alternatives. You’re faced with such tantalizing possibilities as:

Learn COLLADA and get mired in a Turing tarpit of entangled cross-referencing and XML namespacing.

Travel back in time to stop Khronos from abandoning their glFX effort.

In this post, we’ll explore the idea of using Lua as an effect file format. We can live without some of the features that we’d expect in a more full-blown FX format (for example, having the ability to associate an effect with a specific blend or cull state). In a way, we’re simply using Lua as an organization tool for shaders.

Let’s try designating each Lua file as a single “effect”, which we’ll loosely define as “a bundle of shaders”. Usually an effect declares at least one string for each programmable stage in the OpenGL pipeline. Later in the article, we’ll group together shaders for various generations of OpenGL; this is convenient for applications that detect the platform’s capabilities at run-time.

Obviously you’d want to hide code like this behind a utility function or class method. Also, you should always check the return value from luaL_dofile. The code in this article is for illustrative purposes only.

Fixing up the Line Numbers

The problem with the above approach is that any error reporting from the shader compiler will have incorrect line numbers. In the preceding example, if the shader compiler reports an issue with line 1 of the fragment shader, then the relevant line is actually line 12 of the Lua file.

We can fix this using the #line directive in GLSL and the debug.getinfo function in Lua. Instead of declaring strings directly, we’ll need the Lua script to call a function. We can define this function in a separate file called ShaderFactory.lua:

On the C/C++ side of things, we can load in the strings just as we did before, except that our script’s usage of debug.getinfo means that we should load in Lua’s debugging library before anything else. I usually load all the utility libraries in one fell swoop using luaL_openlibs. So, our C/C++ code now looks like this:

Accommodating Multiple Versions of GLSL

#line isn’t the only directive we’re interested in. One of the great annoyances of OpenGL 3.0+ is the #version directive, which is required if you’d like to use the latest and greatest GLSL syntax. Ideally our DeclareShader function would somehow know if a given shader is from the OpenGL 2.0 era or the OpenGL 3.0+ era, and prepend the string accordingly. One idea is passing in the version number as an argument to the DeclareShader function, like this:

Unfortunately, the second call to DeclareShader will overwrite the first call. Also note that the version of the shading language itself isn’t the same as the OpenGL API. The shading language for OpenGL 2.0+ is version 120, and the shading language for 3.0+ is 130.

Ok, so we need to scope the shader names to prevent naming collisions, plus it might be nice to have DeclareShader automatically infer the language version from the API version number. Lua’s tables are great for organizing strings. The ShaderFactory.lua file now becomes:

The above effect contains two fragment shaders but only one vertex shader. You’ll often find that the same vertex shader can be used for multiple fragment shaders, which is why COLLADA and the D3D FX format have so much cross-referencing. Our solution is a bit more simple: we’ll have our C/C++ utility function simply find the shader that has the longest matching shader key. You’ll see what I mean after an example.

Let’s define another term before listing out the new C/C++ utility function: an effect key is like a shader key, except that it has the effect name prepended.

On line 7, the caller requests a shader from the MyEffect.lua file with a shader key of Vertex.GL3.Kirk. Since the longest matching shader key is Vertex.GL3, that’s what gets returned. Also note that the Lua script for the effect gets delay loaded.

By the way, before I let you go, let me show you a trick for commenting out big swaths of Lua code that have multi-line strings. Normally you’d use the - -[[ and - -]] delimiters for multi-line comments, but they don’t work if you’re commenting out sections that have strings delimited with [[ and ]]. Lua allows you to use alternative delimiters by inserting an arbitrary number of equal signs between the square brackets, like this: