Scaffolding Code From Shaders
Posted by Patrick on Wednesday, August 24, 2011Wed, Aug 24, 2011
tools
shaderize
xcode

I don't like writing boilerplate code.

A while back, I started dragging my game engine kicking and screaming into the world of OpenGL ES 2. The good news is this lets me write shaders. The bad news is I need to write boilerplate code every time I write a shader — you know the kind — scaffolding code to load the shader, code to bind the attributes and uniforms and provide some sort of constant, enum or handle to refer to them, etc.

Meh.

So of course this is a terrific opportunity to write some code to write my code for me.

I've blogged on Ragel and its usefulness for parsing things like shaders before. I don't know what happened to that post, though. It seems to have disappeared in the transition from Tumblr to my new site. Oh well. It was a bad post anyway.

I won't bore you with all the details, but I wrote a little tool called shaderize which takes a folder full of vertex and fragment shaders (hereafter referred to as the shader directory) along with an output folder containing templates (hereafter referred to as the output directory), parses the shaders and applies the data to the templates producing… code I did not have to write! Hooray!

Installation

I used Ruby to write shaderize, so I packaged it up as a gem. All you need to do is:

gem install shaderize

You'll probably need to prefix that with sudo unless you're using Ruby Version Manager. Note: OS X comes with Ruby installed out-of-the-box.

Templates

The templates are written in mustache, a wonderfully simple little templating language. Seriously, it will take you about 2 minutes to learn. Your code templates can have anything in them, but mine look something like:

// Shaders.h.tpl

#pragma once
#ifndef _Shaders_H_
#define _Shaders_H_

#include "ShaderProgram.h"
#include "ShaderUniform.h"
#include "ShaderAttribute.h"

namespace Shader
{


{{#shaders}}
class {{name}}_Shader : public ShaderProgram
{
public:
    {{#attributes}}
    ShaderAttribute {{name}};
    {{/attributes}}
    {{#uniforms}}
    ShaderUniform {{name}};
    {{/uniforms}}

public:
    void Load( void )
    {
        ShaderProgram::Load( "{{name}}" );
        {{#attributes}}
        {{name}} = GetAttribute( "{{name}}" );
        {{/attributes}}
        {{#uniforms}}
        {{name}} = GetUniform( "{{name}}" );
        {{/uniforms}}
    }
};

extern {{name}}_Shader {{name}};


{{/shaders}}

void LoadShaders( void );


}; // namespace

#endif // _Shaders_H_

And:

// Shaders.cpp.tpl

#include "Shaders.h"

namespace Shader
{


{{#shaders}}
{{name}}_Shader {{name}};
{{/shaders}}


void LoadAll( void )
{
  {{#shaders}}
  {{name}}.Load();
  {{/shaders}}
}


}; // namespace

You can see the files have a double extension. Shaderize expects the templates to end in .tpl and will produce files in the same directory with the same name sans the .tpl extension.

Invoking

You can run shaderize from the command line like so:

shaderize Resources/Shaders Source/Application

I have a shell build phase that does this for me. Shaderize is smart enough to only update the scaffolded files if something has changed that requires it to do so.

shaderize $PROJECT_DIR/Resources/Shaders $PROJECT_DIR/Source/Application

And that's it. Now when I hit build, shaderize will take all my shaders and produce wrapper classes for me, including members for the attributes and uniforms, along with a method that loads them all. This means I can write a shader, and use it without writing a single line of boilerplate code to manage it.

Nifty.

Of course there is a lot more code hiding behind ShaderProgram, ShaderAttribute and ShaderUniform, but that's just vanilla C++ code and left as an exercise to the reader.

Tangent

I do actually have a C version of my shader parser that I use to extract the uniforms and attributes at load time so I can set those up with the appropriate OpenGL handles and so on. As a result I have no enums, constants or #define statements littering my code. That's really a more complicated subject and could be done with shaderize too — I just haven't got around to it yet.

I'm really happy with my shader workflow now. Code for using them is very clean. There's a lot more that could be done, including smart shader generation, but I've done enough yak shaving for now.

Meta

You can get the Ruby source for shaderize on my GitHub page.

The code is released under the MIT license.

This post is part of iDevBlogADay,, a collaboration of blogs by indie iOS developers. You can subscribe to iDevBlogADay through RSS or follow the #iDevBlogADay hash tag or @idevblogaday on Twitter.