Skip to content

31 May 2012 by Stoo

MonoTouch and OpenGL: Really Rendering Images

I realised when I started writing this article that the previous installment was called "Rendering Images" but only covered loading them... So this time I'll really talk about rendering images. In the previous articles we looked at preparing the device and setting the viewport and loading the image using UIImage.

OpenGL: State Machine

One of the key aspects I covered in the first article is OpenGL is a state machine. You set a state and it remains in place until you set it to something else. There are many different states that can be set including:

  • Which image to render
  • The colour tint to render with the image
  • The type of rendering to perform

State changes are quite expensive to perform and, as far as I'm aware, OpenGL doesn't manage states internally. It just sets them to whatever you tell it, so it's best to manage states in your own code and only pass the state changes to OpenGL when you definitely need to change them.

Batching your images into a single sprite sheet helps here as it allows you to draw areas of your image without repeatedly changing the texture.

If you're looking for an easy way to do this I can recommend Texture Packer. It figures out how to best place your images in a single file and generates a co-ordinate file in a number of different formats. It will even generate a CSS file which was handy on a recent JavaScript game project.

Finally, the Actual Rendering

First up, you need to tell OpenGL that you want to draw an image.

GL.Enable(All.Texture2D);

Once that's done you neeed to to tell it which image to draw. In the previous article we passed a uint to OpenGl as a reference parameter which was filled when we loaded the image.

uint id;

GL.GenTextures(1, ref id);

We pass this to OpenGl again to tell it to send that texture to the graphics card in readiness.

GL.BindTexture(All.Texture2D, id);

Next we need to provide an array of co-ordinates to define where on the screen to draw our image. OpenGL expects an array of vectors to be provided but how it interprets those vectors depends... I've been using a triangle strip to specify a single rectangle to draw. This works for what I need but isn't necessarily the most performant way to do it. You can specify a triangle fan which allows you to draw more triangles with fewer points. To draw a single rectangular image on the screen you can do this:

// set up the area to do draw using an array of Vector2 structs
var quad = new Vector2[] {
	new Vector2(x, y),
	new Vector2(right, y),
	new Vector2(x, bottom),
	new Vector2(right, bottom)
};

// the first parameter is the number of co-ordinates per array element
// in this case 2 as the Vector2 contains both x and y co-ords
// the second parameter is the type of the co-ords
// the third parameter is unused in this example
// the final parameter is the array of co-ordinates
GL.VertexPointer(2, All.Float, 0, quad);

// draw a triangle strip starting at index 0 and drawing 4 triangles
GL.DrawArrays(All.TriangleStrip, 0, 4);

If you want to draw only a portion of the image you need to create an array of co-ordinate vectors within the image, in a similar manner to the previous code. This will need to be placed between the GL.VertexPointer call and the GL.DrawArrays call.

// area is a System.Drawing.RectangleF structure containing the drawing area within the image
// texture contains the full width and height of the image as floats
// (otherwise you have to clutter the code with a lot of casts)
float x = area.X / texture.Width;
float y = area.Y / texture.Height;
float width = x + area.Width / texture.Width;
float height = y + area.Height / texture.Height;

var coords = new Vector2[] {
	new Vector2(x, y),
	new Vector2(width, y),
	new Vector2(x, height),
	new Vector2(width, height)
};

// the parameters are essentially the same as GL.VertexPointer above
GL.TexCoordPointer(2, All.Float, 0, coords);

The third parameter of both the GL.VertexPointer and GL.TexCoordPointer allow you to provide an offset within the array which isn't drawn. This allows you to send all of your vertex data in one call, rather than in multiple calls. I haven't experimented with this yet, but as OpenGL doesn't like changing states, providing all of the data in one call will massively improve performance. I'm saving that optimisation for when I really need to eke out a few more frames per second ;)

Stoo Goff

Stoo Goff

With over ten years experience as a web developer in a number of different industries I have managed to develop skills in both server-side and client-side development on windows and linux servers. This includes full development life-cycle from initial functional requirements and technical specification through to version control, testing, deployment and user training.

Comments

No one has commented on this page yet.

RSS feed for comments on this page | RSS feed for all comments

Leave a comment