back to lofibucket.com

Introduction

I got the idea for an offline shader preprocessor when watching Oliver Franzke’s excellent GDC Europe presentation “Broken Age’s Approach to Scalability” (PDF slides here). According to the presentation, they used the powerful open source GLSL Optimizer with a custom preprocessor, but I couldn’t help but wonder if more “off-the-shelf” solutions would work too.

Enter the Mako

Mako is a pretty traditional templating engine meant for HTML generation. It supports arbitrary Python code inside templates and boasts powerful inheritance features.

One can e.g. include another file pretty easily:

<%include file="common.glsl"/>

This simply copies all code from the target file to the current scope. We’d like to have some control over the scope to minimize naming conflicts, so we include the file with the namespace tag:

<%namespace name="common" file="/common.glsl"/>

Here’s a simple shader that transforms model space coordinates to view space, and paints the model with a single texture. This example uses some Mako templating features. You can find the conversion code and the respective templates on GitHub.

single.glsl

<%namespace name="common" file="/common.glsl"/>
<%inherit file="/base.glsl"/>

<%block name="vertexshader">
layout (location=0) in vec3 ipos;
smooth out vec3 ex_Pos;
smooth out vec2 ex_uv;

${common.uniforms()}
${common.transform_uniforms()}

uniform float speed;

void main() 
{
    float ratio = screenSize.y/screenSize.x;
    ex_uv.y *= ratio;

    vec3 outpos = ipos;
    gl_Position = (projection*camera)*vec4(outpos, 1.0);
}
</%block>

<%block name="fragmentshader">
layout (location=0) out vec4 outcol;

smooth in vec3 ex_Pos;        
smooth in vec2 ex_uv;

uniform sampler2D tex;

void main() 
{
    vec4 teks = texture2D(tex, ex_uv + plus );
    vec4 col = vec4(teks.rgb, 1.0);

    outcol = col;
}
</%block>

The %namespace block loads two defs (python functions) from common.glsl. Calling a def from a template with e.g. ${common.uniforms()}, returns the contents of the block containted within its definition.

common.glsl

<%def name="uniforms()">
uniform vec2 screenSize;
uniform float t;
</%def>

<%def name="transform_uniforms()">
uniform mat4 projection;
uniform mat4 camera;
</%def>

The overall structure of a shader is defined in base.glsl, which looks like this

base.glsl

#version 330

#ifdef vertexcompile
<%block name="vertexshader"/>
#endif

#ifdef fragmentcompile 
<%block name="fragmentshader"/>
#endif

The empty blocks introduced in this file (vertexshader and fragmentshader) are overwritten by the definitions in plain.glsl.

The generated shader code.

Conclusion

Well it seems to work and it’s already pretty handy. It doesn’t utilize all features Mako provides and since you can write custom functions in the template code it should be able to work in a multitude of different scenarios.

On the other hand, the Mako template language is a bit verbose (including namespaces, the name attributes in blocks) and the SGML-like tags feel out of place.

Overall, abusing a HTML generation tool for graphics programming is kinda messy but cool 8)

References