iOS Multithreading OpenGL

LEGO 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.