Last Ray of Hope Home of Kaluriel Hargrove

15Aug/11Off

LONG_MAX on 64bit OSX

Some Sensorites have been known to be dicks

I came by an annoying problem when I was adding some asserts to my FBX to Athena tool. I was basically adding a check to my vertex buffer size to make sure it was not larger than a GLsizeiptr (defined as a long in iOS5 SDK).

I've always assumed chars to be 8, shorts to be 16, longs to be 32 and ints to be whatever the system wants it to be (most likely the register size for an atomic copy). However, my tool is setup using 64bit OSX project, and the long comes out as 8 bytes, so the value given by LONG_MAX is huge, while the int remains at 4 bytes.

I wouldn't have even noticed that my assert would have never been hit if it had not been for the warning the IDE gave after writing the statement, "Comparison is always true due to limited range of data type".

In the end I just replaced LONG_MAX with INT32_MAX (stdint.h). And after a bit of googling, I found this on wikipedia.

The sizes of short, int, and long in C/C++ are dependent upon the implementation of the language:

  • On older, 16-bit operating systems, int was 16-bit and long was 32-bit.
  • On 32-bit Unix, DOS, and Windows, int and long are 32-bits, while long long is 64-bits. This is also true for 64-bit processors running 32-bit programs.
  • On 64-bit Unix, int is 32-bits, while long and long long are 64-bits.

While this does sound kinda familiar to me, I do not think I've ever had a situation where it has appeared to me until now.

Tagged as: , , , No Comments
30Jul/11Off

Using Vertex Array Objects

Amy Pond <3

With my recent post about what iOS devices supported what extensions, I noticed a lot of people got to my website trying to work out how to use Vertex Array Objects (VAO) with the GL_OES_vertex_array_object extension.

They're easy to use so I thought I would write up a quick how-to use them. There is a limitation with the VAO however, it cannot be shared between contexts and can be problematic for multithreading.

If you plan to use one, either generate and create for each context you will be using it on. Or generate and create on the context you will be mostly using it on. It might be a mistake, since it says they didn't want to create the first non-shared named object in OpenGL ES, however at the same time they say no to sharing.

Like with any OpenGL object, first a name must be generated for the object, make sure you always initialise your name variables to 0, since this refers to a non-object.

GLuint gVAO = 0;
 
//
//
int main()
{
	//
	GenerateVAO();
	CreateVAO();
 
	//
	do
	{
		BindVAO();
		DrawSomething();
		UnbindVAO();
	}
	while( 1 );
 
	//
	DestroyVAO();
}
 
//
//
void GenerateVAO()
{
	// Generate Name for VAO
	glGenVertexArraysOES( 1, &gVAO );
}
 
//
//
void DestroyVAO()
{
	if( gVAO )
	{
		glDeleteVertexArraysOES( 1, &gVAO );
		gVAO = 0;
	}
}

I've also put the syntax in there for deleting the VAO when you do not need it anymore.

Now for filling in the VAO. The VAO is basically a block of memory for storing vertex data schematic. For fixed function, the schematic includes information about data type, count, stride, buffer offset and whether it is enabled. This is for every pointer, glVertexPointer, glNormalPointer, glColorPointer, glTexCoordPointer, etc.

And for the shader program pipeline, it contains the schematic for each glVertexAttribPointer.

//
//
void CreateVAO()
{
	// Bind VAO
	glBindVertexArrayOES( gVAO );
 
	// Bind Vertex Buffer
	glBindBuffer( GL_ARRAY_BUFFER, gSomeVBO );
 
	// Fill in the VAO with our vertex schematic
	if( hasShaders )
	{
		glEnableVertexAttribArray( 0 );
 
		glVertexAttribPointer( 0,			// Attribute bind location
				       3,			// Data type count
				       GL_FLOAT,		// Data type
				       GL_FALSE,		// Normalise this data types?
				       sizeof(float) * 3,	// Stride to the next vertex
				       0 );			// Vertex Buffer starting offset
	}
	else
	{
		glEnableClientState( GL_VERTEX_ARRAY );
 
		glVertexPointer( 3,			// Data type count
				 GL_FLOAT,		// Data type
				 sizeof(float) * 3,	// Stride to the next vertex
				 0 );			// Vertex Buffer starting offset
	}
 
	// Unbind VAO
	glBindVertexArrayOES( 0 );
}

 

You'll notice that when I am finished with with the VAO, I unbind it. This is because any changes to the vertex pointers will override any we have set within this function, so to protect the VAO we have just filled in, we unbind it.

When using a VAO, you will not need to bind the GL_ARRAY_BUFFER again the next time you use the VAO, since whenever you make a call to gl*Pointer() or glVertexAttribPointer(), the vertex buffer is stored with the vertex schematic. Because of this, you can have multiple streams of data from different vertex buffers.

//
//
void CreateVAO()
{
	// Bind VAO
	glBindVertexArrayOES( gVAO );
 
	// Vertex Buffer 1
	{
		// Bind Vertex Buffer
		glBindBuffer( GL_ARRAY_BUFFER, gSomeVBO1 );
 
		// Fill in the VAO with our vertex schematic
		if( hasShaders )
		{
			//
			glEnableVertexAttribArray( 0 );
 
			glVertexAttribPointer( 0,			// Attribute bind location
					       3,			// Data type count
					       GL_FLOAT,		// Data type
					       GL_FALSE,		// Normalise this data types?
					       sizeof(float) * 5,	// Stride to the next vertex
					       0 );			// Vertex Buffer starting offset
 
			//
			glEnableVertexAttribArray( 1 );
 
			glVertexAttribPointer( 1,			// Attribute bind location
					       2,			// Data type count
					       GL_FLOAT,		// Data type
					       GL_FALSE,		// Normalise this data types?
					       sizeof(float) * 5,	// Stride to the next vertex
					       sizeof(float) * 3 );	// Vertex Buffer starting offset
		}
		else
		{
			//
			glEnableClientState( GL_VERTEX_ARRAY );
 
			glVertexPointer( 3,			// Data type count
					 GL_FLOAT,		// Data type
					 sizeof(float) * 5,	// Stride to the next vertex
					 0 );			// Vertex Buffer starting offset
 
			//
			glClientActiveTexture( GL_TEXTURE0 );
			glEnableClientState( GL_TEXTURE_COORD_ARRAY );
 
			glTexCoordPointer( 2,						// Data type count
					   GL_FLOAT,					// Data type
					   sizeof(float) * 5,				// Stride to the next vertex
					   (const GLvoid *)(sizeof(float) * 3) );	// Vertex Buffer starting offset
		}
	}
 
	// Vertex Buffer 2
	{
		// Bind Vertex Buffer
		glBindBuffer( GL_ARRAY_BUFFER, gSomeVBO2 );
 
		// Fill in the VAO with our vertex schematic
		if( hasShaders )
		{
			glEnableVertexAttribArray( 2 );
 
			glVertexAttribPointer( 2,			// Attribute bind location
					       2,			// Data type count
					       GL_FLOAT,		// Data type
					       GL_FALSE,		// Normalise this data types?
					       sizeof(float) * 2,	// Stride to the next vertex
					       0 );			// Vertex Buffer starting offset
		}
		else
		{
			glClientActiveTexture( GL_TEXTURE1 );
			glEnableClientState( GL_TEXTURE_COORD_ARRAY );
 
			glTexCoordPointer( 2,			// Data type count
					   GL_FLOAT,		// Data type
					   sizeof(float) * 2,	// Stride to the next vertex
					   0 );			// Vertex Buffer starting offset
		}
	}
 
	// Unbind VAO
	glBindVertexArrayOES( 0 );
}

 

In the code above, the first texture coordinate set is interlaced with the position data (offset by the sizeof three floats), and is bound using the first vertex buffer (gSomeVBO1). But the second texture coordinate is contained within a second vertex buffer (gSomeVBO2).

Finally, using the VAO is easy.

//
//
void BindVAO()
{
	glBindVertexArrayOES( gVAO );
}
 
//
//
void UnbindVAO()
{
	glBindVertexArrayOES( 0 );
}

The call to glBindVertexArrayOES() with the VAO object name is just like calling everything we did in the create function. The only limitation is that one VAO is needed per vertex buffer, even if you have the same vertex schematic.

However, this is a great place for optimisation by sharing the same vertex buffer for vertex data with the same schematic. Then whenever you make a call to glDrawArrays() you can specify which vertex to start from. glDrawElements() is a little more complicated, you will need to increase all your elements in your index buffer so they point to the correct position within the shared buffer.

Or alternatively for glDrawElements(), you could share the same index buffer, and just specify the index buffer offset.

26Jul/11Off

Website Updates

I made some updates to a couple of entries I did a while ago. Fixed an error in my syntax where I calculated the reciprocal, but then divided by the reciprocal instead of multiplying.

I also changed the entries to show equivalent functions instead of my bad descriptions of what the parameters do.

iOS4 Vector and Matrix Math

iOS4 Vector and Matrix Math: Part 2

24Jul/11Off

iOS OpenGL ES Extension Support

Supported iOS OpenGL ES ExtensionsThis weekend I was adding improved extension support to the Athena engine for iOS. There is a page on the Apple site that tells what device has what extensions but it hasn't been updated since iOS 4.2.

So I ran through each version of GLES on a 1st Generate iPod Touch (MBX), an iPhone 4 (SGX) and an iPad 2 (A5), printing the returned string from glGetString( GL_EXTENSIONS ) for each device.

There are a some new extensions in iOS5, some of which are iPad2 specific. But you can see from the image, each newer device supports all the previous extensions.

I haven't had chance to run it on an iPad1 yet, so it will be interesting to see if there is any difference. I've only wrote a rough idea of what version of what SDK is required to use them.

Anything with '***' does not need the extension to work and has an implementation for that version of OpenGL ES.

For more information on each extension, most of them can be viewed here. Some of the newer ones in the EXT namespace are viewable on the what's new page of the beta for iOS5, though the page calls them by the APPLE namespace still.

7Jul/11Off

iOS Multithreading OpenGL

Vashta Nerada

LEGO Vashta Nerada

I've been meaning to do this in my engine for a while, but just never had a time.

Multithreading in OpenGL is simple enough as long as you are not accessing the same object as something on another thread.

In my case, I'm only using it for my loading thread so I will not be accessing any of the objects created until loading is complete, and I will not be rendering on the separate thread either, at least not yet, I could use it for render targets.

So first of all, the creation of a OpenGL context on iOS. The examples I'm writing here are just simple main() loops so simplify what has to be done.

int main()
{
	EAGLContext * mContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
 
	// Make context active for this thread
	[EAGLContext setCurrentContext:mContext];
 
	//
	while( 1 )
	{
		// do some rendering
	}
 
	//
	return 0;
}

Simple enough, this code most people who have written any iPhone OpenGL apps will recognise, it creates an ES2 context and bind it to the active thread.

However, now we want a separate thread that does OpenGL commands. We could use the same context, but a call binding a buffer on one thread could override to bind on the other causing invalid or wrong calls, so we would need to synchronise the threads.

Instead we create a second context that shares objects with our first one.

//
//
EAGLContext * mMainContext = nil;
EAGLContext * mThreadContext = nil;
pthread_attr_t mThreadAttributes;
pthread_t mThread;
 
//
//
int main()
{
	mMainContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
	mThreadContext = [[EAGLContext alloc] initWithAPI:[mMainContext API]
					       sharegroup:[mMainContext sharegroup]];
 
	// Make context active for this thread
	[EAGLContext setCurrentContext:mMainContext];
 
	// Create second thread for OpenGL stuff
	pthread_attr_init( &mThreadAttributes );
 
	pthread_create( &mThread,
			&mThreadAttributes,
			OGLThread,
			NULL );
 
	//
	while( 1 )
	{
		// do some rendering
	}
 
	//
	return 0;
}
 
//
//
void * OGLThread( void * inParam )
{
	//
	[EAGLContext setCurrentContext:mThreadContext];
 
	//
	while( loadingSomething )
	{
		// other opengl commands
	}
 
	// Flush changes
	glFlush();
 
	//
	return 0;
}

With this second context, any calls to binding objects to the context will not affect the other thread's context. the [API] just makes it so that I'm using the same ES version as the first context, the [sharegroup] is the shared resource group that will be used by both contexts.

However you must take care not to update an object that is in use in the main thread, and whenever you make a change that will affect the other thread, call glFlush() on the thread that modified, and then rebind on the other thread so the changes take place.

In my own engine I hold shadow states of all options set, so one problem with this system would be I would need to pass in the Context depending on the thread. Instead I wrapped a class around the context that holds the shadow states and the context, as well as a pthread_t object for the thread it is assigned to.

Then whenever I do anything that will alter my shadow states, I make a call to pthread_self() to get the current thread, and find the relevant context.

struct MyContext
{
	EAGLContext * context;
	ShadowStates states;
	pthread_t thread;
};
 
MyContext mMainContext;
MyContext mThreadContext;
 
MyContext * GetCurrentContext()
{
	int ret = pthread_equal( pthread_self(), mMainContext.thread );
 
	if( ret == 0 ) // 0 means not equal when using pthread_equal()
	{
		return &mThreadContext;
	}
 
	return &mMainContext;
}

There is only one object so far that I have found doesn't share between contexts, and that is a Vertex Array Object (VAO).

For now I'm just making my code think the extension isn't present when multithreading is enabled, but I need to come up with a solution to generate the VAO for each thread it gets used on.

1Jul/11Off

iOS5 Airplay

It's been a while since I uploaded a video to YouTube. This is a video of my first play around at airplay mirroring on the iPad2 using two different images on each screen.

I used an online video editor, couldn't figure out how to flip it the right way around so thats why it is upside down.

YouTube Preview Image

It was a lot easier than I thought it would be to do, however I'm currently limited to 30fps per screen, but I think that is because I'm doing them as two separate context calls and renders, if I do all the rendering on one context then use the framebuffer on the other, I might be able to hit 60fps.

Tagged as: , , , No Comments