GLSL uniform bindings for Cocoa

Executive summary: This article demonstrates the use of Objective-C’s dynamic object model and the Foundation framework to extract attributes from objects using information not available at compile time. This is done on the context of implementing OpenGL Shader Language support in a cross-platform game.

Introduction

A major feature of the current development line of Oolite is support for GLSL shaders. Shaders need to be able to reflect the state of the object they’re attached to – for instance, by having spaceship engines glow in proportion to engine power. The mechanism GLSL provides for this is uniform variables, attributes set by the host application and read by the shader.

A screen shot of Oolite.

Debris model with per-pixel lighting and heat glow which fades over time from glow_alloy.oxp. Model by Arexack_Heretic, textures by Griff, shaders by Ahruman.

The initial implementation of shader support was quite simplistic:

for (each material in model)
{
    if (material is shader-based)
    {
        glUseProgramObjectARB(shader);
        if (shader uses uniform:"time")
        {
            glUniform1fARB(location for "time", current game time);
        }
        if (shader uses uniform:"engine_level")
        {
            glUniform1fARB(location for "engine_level", ship’s engine power level);
        }
        …
    }
    else
    {
        glUseProgramObjectARB(NULL);
        set up texture;
    }
    render geometry for this material;
}

The problem

This gets the job done, but it isn’t exactly a paragon of object-oriented design. There’s no encapsulation and no reuse. It’s definitely not very Cocoa-ey. The first step was obviously to make materials into a class hierarchy, with simple texture-based materials being one class, shader materials being another and the rendering code not really caring which was being used. However, if the shader material class set uniforms in the way outlined above, it would require the shader material class to know about the details of the various types of entity in the game, and special-case each one. Conversely, if the responsibility for setting up uniforms remained in the entity classes, they would need to know about the details of shaders.

So how does Cocoa handle comparable situations? Key-value coding. Key-value observation. Bindings.

As it stands, KVC doesn’t really suit our needs. Accessors for relevant properties tend not to return objects. Properties of potential interest to shaders tend to be numbers, vectors, matrices, quaternions (Oolite will convert quaternions to either vectors or rotation matrices for shader bindings) and colours. Colours are represented by objects, but the others are generally not. So I decided to roll my own.

Getting the goods

This turned out to be almost disappointingly simple, although there was a complication arising from the need to support GNUstep as well as Cocoa. First, in order to support multiple types, we need to use -[NSObject methodSignatureForSelector:] to check the return type of the requested method. Then (assuming the type is supported, and other sanity checks), -[NSObject methodForSelector:], which returns an IMP, or function pointer to the method implementation. This function pointer can then be cast to a function pointer with the correct return type, and called to acquire the current value of the attribute in question.

Note: there are some limitations to this approach. In particular, it shortcuts certain dynamic method look-up behaviours. It will not work if the object being bound to (the binding target) is a proxy, and it will not notice if the object’s class changes, or the class’s method table changes. Such changes are made by the key-value observation mechanism used for normal Cocoa bindings. This doesn’t matter in Oolite, and probably won’t matter in the KVO case either, but it is important to be aware of it.

- (BOOL)setBindingTarget:(id)target selector:(SEL)selector
{
    NSMethodSignature       *signature;
    IMP                     method;
    unsigned                argCount;
    ShaderUniformType       type;

    if (target == nil)  return NO;
    if (![target respondsToSelector:selector])  return NO;

    // Get the IMP
    method = [target methodForSelector:selector];
    if (method == NULL)  return NO;

    // Get the method signature
    signature = [target methodSignatureForSelector:selector];
    if (signature == nil)  return NO;

    // All methods have two implicit arguments: self and _msg.
    // Getters have no explicit arguments and therefore have
    // a total of two arguments.
    argCount = [signature numberOfArguments];
    if (argCount != 2)  return NO;

    type = ShaderUniformTypeFromMethodSignature(signature);
    if (type == kShaderUniformTypeInvalid)  return NO;

    // All tests passed – binding is complete.
    _target = target;
    _selector = selector;
    _method = method;
    _type = type;
}

typedef float (*FloatReturnMsgSend)(id, SEL);

- (void)apply
{
    switch (_type)
    {
        case kShaderUniformTypeFloat:
            float fValue = ((FloatReturnMsgSend)_method)(_target, _selector);
            glUniform1fARB(_location, fValue);
            break;

        // Handle other types
        …
    }
}

But, er… what is it?

The only non-standard thing in the above is the function ShaderUniformTypeFromMethodSignature(). It takes an NSMethodSignature and returns an enum specifying the return type of the accessor method we are binding to. The method -[NSMethodSignature methodReturnType] exists for this very purpose. However, this is the source of the aforementioned complication. Cocoa’s implementation returns what you might expect: the @encode() string corresponding to the method’s return type. Therefore, the initial implementation of ShaderUniformTypeFromMethodSignature() in Oolite looked like this:

ShaderUniformType ShaderUniformTypeFromMethodSignature(NSMethodSignature *sig)
{
    const char *type = [sig methodReturnType];
    if (type == NULL)  return kShaderUniformTypeInvalid;

    if (strcmp(type, @encode(float)) == 0)  return kShaderUniformTypeFloat;
    // Handle other types
    …
    else return  kShaderUniformTypeInvalid;
}

However, as the astute reader may have guessed, this did not work under GNUstep. Assiduous link-followers will be quick to point out that the documentation clearly states, “This encoding is implementation-specific, so applications should use it with caution.” In fact, GNUstep seems to return an encoding string for the entire method, that is, the encoding of all the arguments as well as the return type. (I maintain that this is a bug, since it returns different strings for -(int)foo and -(int)bar:(id)frob. However, since we’re only interested in methods with no explicit arguments, that doesn’t matter.) More importantly to the Cocoa programmer, this statement in the documentation means Apple is free to change the Cocoa implementation at any time.

Fortunately, there’s a simple solution: compare to the methodReturnTypes of known methods that return the required type. The final implementation uses a template class which implements a method for each required return type and copies the methodReturnTypes of these methods into an array. The template class pattern may be familiar if you’ve written NSProxys.

Free code!

Oolite’s code is complicated. The code given above is too simple. So here’s some code that’s just right.

The sample application uses a stripped-down version of the shader code from Oolite. It presents a scene consisting of a ball with per-pixel lighting and two moving lights, and controls to set two properties, colour and shininess (a combination of specular exponent and specular intensity). The window controller (cleverly named ExampleController) moves the lights about on a timer.

A screen shot of the demo application. 
A screen shot of the demo application.

The demo application.

The controls do not affect the scene directly; their values are stored in a model object (ExampleModel) which knows nothing of OpenGL, shaders or lighting. The OpenGL view (ExampleOpenGLView) sets up a shader material and binds the uniforms uColor and uShininess to the appropriate model properties:

_material = [[JAShaderMaterial alloc] initWithVertexShaderSource:vertSource
                                            fragmentShaderSource:fragSource];

// Bind shader uniforms to model attributes.
[_material bindUniform:@"uColor" toObject:_model property:@selector(color)];
[_material bindUniform:@"uShininess" toObject:_model property:@selector(shininess)];

The material creates JAShaderUniform objects to handle the individual uniforms. Now, whenever the shader is used (specifically, when its -apply method is called), the uniform objects pull in their values from the model object. When the slider is moved, the model’s shininess value is changed; when the scene next redraws, the shader’s uShininess uniform reflects the new value. The controller and view don’t need to do anything to update them.

Limitations

The sample app, being a sample app, glosses over some issues. For one thing, binding targets are not retained, to avoid retain cycles. This is the Right Thing when the binding target is the owner of the material, but if it isn’t, you may inadvertently release an object which is bound to. Oolite avoids this by using a proxy-based weak reference system, which is beyond the scope of this article. The sample app also doesn’t bother to check whether its OpenGL context actually supports shaders.

However, the uniform implementation is pretty complete. It supports most of the types Oolite does, namely:

  • Signed and unsigned chars, shorts and longs
  • floats and doubles
  • Vectors, in the form of JAVector structs. In real life, you’d want to use whatever vector struct or class you use for the rest of your application.
  • NSPoints (as vec2s)
  • NSNumbers (as floats)
  • NSColors (as vec4s)

One Oolite feature removed for the example is filtering. Filters supported by Oolite include clamping numbers to the range 0..1, normalizing vectors, and converting quaternions to rotation matrices. These are simple operations that aren’t relevant to the focus of this article.

The sample code may be downloaded here (99 kB) and is distributed under the MIT/X11 License.

Disclaimer

Some of the above is not true. Some is misremembered. Some is glossed over and simplified. The code extracts are all simplified, untested code. Caveat lector.

This entry was posted in Cocoa, Oolite, OpenGL. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *