When you request a Context3D you’ll either get the requested profile or software rendering. But what if you’d rather fall back to a lesser hardware context? What if your app can make use of the regular/baseline profile but can run with reduced graphical effects using the constrained mode profile? Today’s article presents a utility class that makes it a snap to always get the best Context3D that Flash Player can give you.

The following class—GetContext3D—works by trying to get an extended profile Context3D so you can use the most features. If that fails, it falls back to the next-best profile until it either acquires one successfully or fails to get any Context3D at all. Here’s the order it tries:

  1. Extended profile (hardware rendering)
  2. Regular/baseline profile (hardware rendering)
  3. Constrained mode (hardware rendering)
  4. Software rendering

The API is really simple. All you do is construct the class and it calls you back when the context is ready. Here’s a quick example:

package
{
	import flash.display3D.Context3D;
	import flash.display.Sprite;
	import flash.display.Stage3D;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.utils.getTimer;
 
	/**
	 * Test app to show how to use GetContext3D to get the best Stage3D profile
	 * @author Jackson Dunstan, JacksonDunstan.com
	 */
	public class Stage3DProfileTest extends Sprite 
	{
		/**
		 * Application entry point
		 */
		public function Stage3DProfileTest()
		{
			// Disable classic stage scaling
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
 
			// Get the best context we can
			new GetContext3D(stage.stage3Ds[0], onContext);
		}
 
		/**
		 * Callback for when the context is created
		 * @param success If the context was created successfully
		 * @param message Mesage about the context creation
		 */
		private function onContext(success:Boolean, message:String): void
		{
			// Show a message text field
			var logger:TextField = new TextField();
			logger.background = true;
			logger.backgroundColor = 0xffffffff;
			logger.autoSize = TextFieldAutoSize.LEFT;
			logger.text = message;
			addChild(logger);
 
			// The context was created successfully
			if (success)
			{
				// Get the context
				var stage3D:Stage3D = stage.stage3Ds[0];
				var context3D:Context3D = stage3D.context3D;
 
				// Setup the back buffer
				var width:Number = stage.stageWidth;
				var height:Number = stage.stageHeight;
				context3D.configureBackBuffer(width, height, 0, true);
 
				// Show a message about what driver is being used
				logger.appendText("\nDriver: " + context3D.driverInfo);
 
				// Start rendering every frame
				addEventListener(Event.ENTER_FRAME, onEnterFrame);
			}
			// Failed to create the context
			else
			{
				logger.appendText("\nContext creation failed");
			}
		}
 
		/**
		 * Callback for when a frame should be rendered
		 * @param ev ENTER_FRAME event
		 */
		private function onEnterFrame(ev:Event): void
		{
			// Just render a pulsating gray screen
			var context3D:Context3D = stage.stage3Ds[0].context3D;
			var gray:Number = (Math.sin(getTimer()/1000.0) + 1) / 2;
			context3D.clear(gray, gray, gray);
			context3D.present();
		}
	}
}

Launch the example app

GetContext3D is written in such a way that it has no dependencies and doesn’t even need you to compile your SWF for Flash Player 11.8 (extended profile support) or Flash Player 11.4 (constrained profile support). You can compile for any version of Flash Player starting at the first one to support Stage3D and Context3D at all: 11.0.

Here’s the code:

package
{
	import flash.display.Stage3D;
	import flash.display3D.Context3DRenderMode;
	import flash.events.ErrorEvent;
	import flash.events.Event;
 
	/**
	 * A simple way to get an optimal Context3D. That is, an extended profile
	 * context is the first priority, then a baseline profile, then a
	 * constrained profile, and finally a software profile.
	 * @author Jackson Dunstan, JacksonDunstan.com
	 */
	public class GetContext3D
	{
		// Context3DProfile.BASELINE_EXTENDED. Use a string literal instead of
		// the Context3DProfile class so that this class can still be compiled
		// for Flash Players before the Context3DProfile class existed.
		private static const BASELINE_EXTENDED:String = "baselineExtended";
 
		// Context3DProfile.BASELINE. Use a string literal instead of
		// the Context3DProfile class so that this class can still be compiled
		// for Flash Players before the Context3DProfile class existed.
		private static const BASELINE:String = "baseline";
 
		// Context3DProfile.BASELINE_CONSTRAINED. Use a string literal instead of
		// the Context3DProfile class so that this class can still be compiled
		// for Flash Players before the Context3DProfile class existed.
		private static const BASELINE_CONSTRAINED:String = "baselineConstrained";
 
		/** Profile to get */
		private var profile:String;
 
		/** Render mode to get */
		private var renderMode:String;
 
		/** Stage3D to get the context for */
		private var stage3D:Stage3D;
 
		/** Callback to call when the context is acquired or an error occurs
		*   that prevents the context from being acquired. Passed a success
		*   Boolean and a message String if the callback function takes two or
		*   more parameters. If the callback function takes only one parameter,
		*   only the success Boolean is passed. If the callback function takes
		*   no parameters, none are passed.
		*/
		private var callback:Function;
 
		/**
		 * Get the best Context3D for a Stage3D and call the callback when done
		 * @param stage3D Stage3D to get the context for
		 * @param callback Callback to call when the context is acquired or an
		 *        error occurs that prevents the context from being acquired.
		 *        Passed a success Boolean and a message String if the callback
		 *        function takes two or more parameters. If the callback
		 *        function takes only one parameter, only the success Boolean is
		 *        passed. If the callback function takes no parameters, none are
		 *        passed.
		 * @throws Error If the given stage3D or callback is null
		 */
		public function GetContext3D(stage3D:Stage3D, callback:Function)
		{
			// Callback and Stage3D must be non-null
			if (callback == null)
			{
				throw new Error("Callback can't be null")
			}
			if (stage3D == null)
			{
				throw new Error("Stage3D can't be null")
			}
 
			// Save the callback and Stage3D
			this.callback = callback;
			this.stage3D = stage3D;
 
			// Start by trying to acquire the best kind of profile
			renderMode = Context3DRenderMode.AUTO;
			profile = BASELINE_EXTENDED;
			stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContext3DCreated);
			stage3D.addEventListener(ErrorEvent.ERROR, onStage3DError);
			requestContext();
		}
 
		/**
		 * Call the callback function and clean up
		 * @param success If the context was acquired successfully
		 * @param message Message about the context acquisition
		 */
		private function callCallback(success:Boolean, message:String): void
		{
			// Remove event listeners
			stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onContext3DCreated);
			stage3D.removeEventListener(ErrorEvent.ERROR, onStage3DError);
 
			// Release reference to the callback after this function completes
			var callback:Function = this.callback;
			this.callback = null;
 
			// Release reference to the Stage3D
			stage3D = null;
 
			// Pass as many arguments as the callback function requires
			var numArgs:uint = callback.length;
			switch (numArgs)
			{
				case 0: callback(); break;
				case 1: callback(success); break;
				case 2: callback(success, message); break;
			}
		}
 
		/**
		 * Request a context with the current profile
		 */
		private function requestContext(): void
		{
			try
			{
				// Only pass a profile when the parameter is accepted. Do this
				// dynamically so we can still compile for older versions.
				if (stage3D.requestContext3D.length >= 2)
				{
					stage3D["requestContext3D"](renderMode, profile);
				}
				else
				{
					stage3D.requestContext3D(renderMode);
				}
			}
			catch (err:Error)
			{
				// Failed to acquire the context. Fall back.
				fallback();
			}
		}
 
		/**
		 * Callback for when there is an error creating the context
		 * @param ev Error event
		 */
		private function onStage3DError(ev:ErrorEvent): void
		{
			callCallback(false, "Stage3D error ID: " + ev.errorID + "\n" + ev);
		}
 
		/**
		 * Callback for when the context is created
		 * @param ev CONTEXT3D_CREATE event
		 */
		private function onContext3DCreated(ev:Event): void
		{
			// Got a software driver
			var driverInfo:String = stage3D.context3D.driverInfo.toLowerCase();
			var gotSoftware:Boolean = driverInfo.indexOf("software") >= 0;
			if (gotSoftware)
			{
				// Trying to get a non-software profile
				if (renderMode == Context3DRenderMode.AUTO)
				{
					fallback();
				}
				else
				{
					// Trying to get software. Succeeded.
					callCallback(true, "Profile: " + profile);
				}
			}
			// Didn't get a software driver
			else
			{
				callCallback(true, "Profile: " + profile);
			}
		}
 
		/**
		 * Fall back to the next-best profile
		 */
		private function fallback(): void
		{
			// Check what profile we were trying to get
			switch (profile)
			{
				// Trying to get extended profile. Try baseline.
				case BASELINE_EXTENDED:
					profile = BASELINE;
					requestContext();
					break;
				// Trying to get baseline profile. Try constrained.
				case BASELINE:
					profile = BASELINE_CONSTRAINED;
					requestContext();
					break;
				// Trying to get constrained profile. Try software.
				case BASELINE_CONSTRAINED:
					profile = BASELINE;
					renderMode = Context3DRenderMode.SOFTWARE;
					requestContext();
					break;
			}
		}
	}
}

Hopefully this makes your Context3D acquisition easier and results in a context with more features.

Spot a bug? Have a question or suggestion? Post a comment!