GLKit Extended: Refactoring the View Controller

If you’ve been following my tutorials on OpenGL ES 2 for iOS, by the time you finish Texturing (Part 6 or so) you’ll have a lot of code crammed into a single UIViewController. This is intentional: the tutorials are largely self-contained, and only create classes and objects where OpenGL itself uses class-like-things.

…but most of the code is re-used from tutorial to tutorial, and it’s getting in the way. It’s time for some refactoring.

UIViewController vs GLKViewController

Recap: Both these classes are provided by Apple; GLKViewController is a UIViewController that’s been modified for OpenGL apps. It has the simplest possible interface – a single “update” method that you override, which is called (automatically) once per frame.

As I explained in the first GL ES 2 tutorial, everything in OpenGL revolves around the Draw call. Most engines recognize this, so we’ll subclass GLKViewController and replace:

  1. Once per frame: call “update”; no intelligence

…with:

  1. In viewDidLoad: Correct setup of EAGLContext (required)
  2. In viewDidLoad: Pre-Create all the Draw calls needed for the app
  3. Once per frame: For each Draw call in the list:
    1. …update all the OpenGL per-Draw settings
    2. …update the OpenGL Shader settings
    3. …render the Draw call
    4. …reset any OpenGL state that “must” be turned off immediately after use (e.g. GL_SCISSOR)

Most of this is identical for all apps – we can create a single base class “GLK2DrawCallViewController”, and put the code in there. A few items will be unique from app to app (e.g. the list of Draw calls actually rendered!); we do those with their own public methods so that you can write a subclass per-app which only overrides the methods it needs to change.

GLK2DrawCallViewController.h:

@interface GLK2DrawCallViewController : GLKViewController

@property(nonatomic,retain) EAGLContext* localContext;
@property(nonatomic, retain) NSMutableArray* drawCalls;

This class manages the EAGLContext correctly, and populates the drawCalls – most subclasses can ignore these properties. But some special effects will require you to access the super-class’s data here.

-(NSMutableArray*) createAllDrawCalls;
-(void) willRenderFrame;
-(void) willRenderDrawCallUsingVAOShaderProgramAndDefaultUniforms:(GLK2DrawCall*) drawCall;
@end

One callback to create the Draw calls – this is essential, if you don’t implement it, nothing will be drawn.

The other two are optional, although the one about “willRender….Using…Uniforms” is needed for most non-trivial shaders (any shader where a Uniform is changing from frame to frame – e.g. whenever your 3D objects are moving).

Internally, it’s all existing code that I’ve simply moved around.

Usage

Usage is exceptionally simple: change your ViewController.h in your app project to extend the new class:

ViewController.h:

@interface ViewController : GLK2DrawCallViewController

…and override the special method for creating Draw calls. Move all your tutorial / logic there:

ViewController.h:

-(NSMutableArray*) createAllDrawCalls
{	
	/** All the local setup for the ViewController */
	NSMutableArray* result = [NSMutableArray array];
	
	/** -- Draw Call 1: clear the background
	 */
	GLK2DrawCall* simpleClearingCall = [[GLK2DrawCall new] autorelease];
	simpleClearingCall.shouldClearColorBit = TRUE;
	[simpleClearingCall setClearColourRed:0.5 green:0 blue:0 alpha:1];
	[result addObject: simpleClearingCall];
	
... etc
	return result;
}

Source files on Github:

Re-using the “draw one triangle” code

The tutorials frequently use “draw a single triangle” to demonstrate new code. It would be great to re-use this code – we use it a lot for debugging, not just for tutorials. But it can’t live inside the OpenGL API: to keep the API small and easy to learn, it only includes commands for talking to the GPU efficiently.

With desktop GL they solved this problem by creating a separate, “optional” library called GLU. Because GLU is purely software – it has no access to hardware, instead using GL to do all the GPU work – you can copy/paste GLU code and use it with different versions of GL, often with little or no changes.

NOTE: I’m not including this code in the GLKit-Extended library – it’s stuff that would be written differently for different 3D engines, and I want the library to be pure and simple. Instead, I’m including it in the GLKit-Extended Demo project, and you can copy/paste the file direct to your own projects if you choose

Sadly, most GLU code won’t work with GL ES unless you tweak it – GLU uses lots of outdated commands that were stripped to make GL ES cheaper to implement. But we can copy the idea…

CommonGLEngineCode.h:

@interface CommonGLEngineCode : NSObject
+(GLK2DrawCall*) drawCallWithUnitTriangleAtOriginUsingShaders:(GLK2ShaderProgram*) shaderProgram;
+(GLK2DrawCall*) drawCallWithUnitCubeAtOriginUsingShaders:(GLK2ShaderProgram*) shaderProgram;
... over time, we'll add more and more "re-usable" methods here...
@end

Each method assumes that you’ve NOT CHANGED ANYTHING in the camera-setup of OpenGL – you’re using an unconfigured, default frustum etc. i.e. the projection is Orthogonal, and only shows things from (-1,-1,-1) to (1,1,1). It sticks a 1-unit wide triangle (or cube) roughly in the middle of the screen.

If the methods were 100% generic, they’d take another argument: a dictionary of GLK2Attributes, each one “tagged” so the method knows which ones to fill with 3D positions, which to fill with 2D texture co-ordinates, etc. For simplicity, I’ve assumed that you pass in a pre-compiled GLK2ShaderProgram containing a shader pair with:

  • REQUIRED: an Attribute named “position” which you’ll read to get the 3D position of each vertex
  • OPTIONAL: an Attribute named “textureCoordinate” which you’ll read if you’re using Texturing to get a texture-co-ordinate ranging (0-1) in X (i.e. “u”) and (0-1) in Y (i.e. “v”)

Usage

Combining this with the new GLK2DrawCallViewController above, we get something like:

ViewController.h:

-(NSMutableArray*) createAllDrawCalls
{	
	/** All the local setup for the ViewController */
	NSMutableArray* result = [NSMutableArray array];
	
	GLK2DrawCall* dcTri = [CommonGLEngineCode drawCallWithUnitTriangleAtOriginUsingShaders:
						   [GLK2ShaderProgram shaderProgramFromVertexFilename:@"VertexProjectedWithTexture" fragmentFilename:@"FragmentWithTexture"]];
	GLK2Uniform* samplerTexture1 = [dcTri.shaderProgram uniformNamed:@"s_texture1"];

...OPTIONAL (if you're using texture-mapping in your shaders):

	GLK2Texture* texture = [GLK2Texture textureNamed:@"tex2"];
	[dcTri setTexture:texture forSampler:samplerTexture1];
		
	[result addObject:dcTri];
...
	return result;
}

Source files on Github:

Leave a Reply

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