Context loss with Stage3D is real and will instantly take down your 3D scene. It’s an unavoidable problem, but one that many programmers still don’t handle gracefully. How does it occur? Why does it matter? How can you handle it so your app continues to function well? Today’s article explores the topic of Stage3D context loss so you can keep your 3D scene alive and well.

At the start of any Stage3D-based app you will need to request a Context3D with which you will render your 3D scenes. The problem of context loss manifests itself when you suddenly lose your Context3D. This can occur for a variety of reasons that are almost always outside of your app’s control. For example, pressing Ctrl+Alt+Delete on a Windows 7 computer will bring up a menu with options like “Lock this computer” and “Start Task Manager”. This also triggers a context loss. But this isn’t the only way to lose the context. Some screensavers starting up can trigger a context loss and so can closing the lid of a laptop. You can even simulate a context loss by calling Context3D.dispose yourself. It’s best for most Flash apps to simply assume that context loss can strike at any moment for any reason and to handle it as best as possible.

What does it mean to your app when you lose the Context3D? Well, you certainly can’t do any more rendering with your old Context3D instance. This means that you won’t be able to render the 3D scene at all. Since it may be a while until you get a new context, you may wish to show some kind of “loading” or similar screen to indicate that there is a problem and your app is waiting for a new context.

Perhaps the much more complex and expensive problem is that all of the textures, vertex buffers, index buffers, and shader programs you created with the old Context3D are also unusable. This means you’ll need to recreate all of these in order to restore your 3D scene. How you do this is up to you, but it often involves keeping a copy of the data that you uploaded to the GPU via a function like Texture.uploadFromBitmapData. Unfortunately, this means that your app will use a lot of extra system memory just to be able to recover from context loss. You may want to consider alternative ways of recreating these resources such as re-downloading and then re-uploading assets or perhaps simply considering context loss a “fatal error” and restarting the app, game level, or some other reboot.

So how do you know that you’ve lost the Context3D? If you’ve looked through the AS3 documentation you may have noticed that there doesn’t seem to be an event for this critical occurrence. Well, Flash Player’s way of letting you know is to simply dispatch another Event.CONTEXT3D_CREATE event from the Stage3D. This will often lead you to write some conditional code in your event handler that looks something like this:

var stage3D:Stage3D;
var context:Context3D;
 
// Request a context as part of starting up the Stage3D-based app
stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContextCreated);
stage3D.requestContext3D();
 
function onContextCreated(ev:Event): void
{
	// Save the old context so we can tell if this was the first context or a replacement
	// one due to context loss
	var oldContext:Context3D = context;
	context = stage3D.context3D;
 
	// create all textures, vertex buffers, index buffers, and shader programs and upload them...
 
	if (oldContext)
	{
		// had a context before
		// this means we lost it and got a new one
	}
	else
	{
		// getting a context is a normal part of app startup
		// we have one for the first time (i.e. during app startup)
		// so continue starting up the app...
	}
}

You’ll have to decide for your specific app what the best way to handle context loss, but the above is a general strategy: (re-)create and (re-)upload all of the previously-used 3D resources whenever the context is created.

Another strategy is to abstract 3D resources from some parts of your app to allow you to more easily swap them out when you need to recreate them. Here’s some pseudo-code to illustrate:

class MyTexture
{
	var bmd:BitmapData;
	var texture:Texture;
}
class MyScene
{
	var textures:Vector.<MyTexture>;
 
	function onContextCreated(ev:Event): void
	{
		// Re-create and re-upload all textures
		for each (var tex:MyTexture in textures)
		{
			tex.texture = context.createTexture(
				tex.bmd.width,
				tex.bmd.height,
				Context3DTextureFormat.BGRA
			);
			tex.texture.uploadFromBitmapData(tex.bmd);
		}
	}
}
class MyApp
{
	// Users don't use Texture objects directly
	// Instead they use the middle-man: MyTexture
	var tex:MyTexture;
 
	function render(): void
	{
		context.setTextureAt(0, tex.texture);
	}
}

Have any tips for handling context loss? Share them in the comments!