Flash 11’s new Stage3D enables us to make amazing 3D games and applications in Flash. It also burdens us with two forms of memory: the system memory (RAM) we’re used to and the video card’s memory (VRAM) that stores objects like textures, buffers, and shaders. In order to not use more VRAM than the player’s video card has, we must know how much VRAM they have. Unfortunately, the Stage3D API does not provide us with this information. Today’s article provides a workaround function that allows you to quickly test your players’ VRAM. UPDATED to fix some bugs in the test

Since we cannot simply access an API to query the size of the VRAM, we must instead attempt to fill it with objects whose size we know until we get an error. The following test function starts by allocating the largest texture possible (2048×2048=16MB) and then allocating smaller and smaller textures (1024×1024=4MB, 512×512=1MB, etc.) all the way down to a minimum size (32×32=1KB). The more granular the minimum, the more textures may need to be created and the longer the test will take to execute, so the minimum texture dimension is left up to you via a parameter. Here’s the function:

/**
*   Test how much available VRAM there is (e.g. for textures, buffers)
*   @param ctx Context to test the VRAM of
*   @param minDim Minimum dimensions of textures to create to test the
*                 VRAM size. Smaller minimum dimensions results in finer
*                 granularity, but the test may take longer. Capped to
*                 [32:2048].
*   @return The number of bytes of VRAM available
*   @throws ArgumentError If the given context is null
*   @author Jackson Dunstan (http://jacksondunstan.com/articles/1465)
*/
public static function testVRAM(ctx:Context3D, minDim:uint=512): uint
{
	if (!ctx)
	{
		throw new ArgumentError("'ctx' is null");
	}
 
	minDim = Math.max(32, Math.min(2048, minDim));
 
	var size:uint;
	var textures:Vector.<Texture> = new Vector.<Texture>();
 
	for (var dim:uint = 2048; dim >= minDim; dim >>= 1)
	{
		var count:uint = 0;
		try
		{
			while (true)
			{
				var texture:Texture = ctx.createTexture(
					dim,
					dim,
					Context3DTextureFormat.BGRA,
					false
				);
				count++;
				textures.push(texture);
			}
		}
		catch (err:Error)
		{
			size += count * dim * dim * 4;
		}
	}
	for each (texture in textures)
	{
		texture.dispose();
	}
 
	return size;
}

And now let’s look at an app that uses the above function to check VRAM:

Launch Stage3D VRAM Tester

And here’s the source code:

package
{
	import flash.display3D.*;
	import flash.display3D.textures.*;
	import flash.external.*;
	import flash.display.*;
	import flash.sampler.*;
	import flash.system.*;
	import flash.events.*;
	import flash.utils.*;
	import flash.text.*;
	import flash.geom.*;
 
	import com.adobe.utils.*;
 
	[SWF(width=640,height=480,backgroundColor=0xEEEAD9)]
	public class Stage3DVRAMTester extends Sprite
	{
		private static const PAD:Number = 3;
		private static const TEXT_FORMAT:TextFormat = new TextFormat("_sans", 11);
 
		private var __stage3D:Stage3D;
		private var __tf:TextField = new TextField();
		private var __context:Context3D;
		private var __mode:String;
		private var __driverInfo:String;
 
		public function Stage3DVRAMTester()
		{
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
			__stage3D = stage.stage3Ds[0];
 
			makeButton("Toggle Hardware", onToggleHardware);
			makeButton("Test VRAM (minDim=2048)", onTestVRAM2048);
			makeButton("Test VRAM (minDim=1024)", onTestVRAM1024);
			makeButton("Test VRAM (minDim=512)", onTestVRAM512);
			makeButton("Test VRAM (minDim=256)", onTestVRAM256);
			makeButton("Test VRAM (minDim=128)", onTestVRAM128);
			makeButton("Test VRAM (minDim=64)", onTestVRAM64);
			makeButton("Test VRAM (minDim=32)", onTestVRAM32);
 
			var about:TextField = new TextField();
			about.autoSize = TextFieldAutoSize.LEFT;
			about.defaultTextFormat = TEXT_FORMAT;
			about.htmlText = '<font color="#0071BB">'
				+ '<a href="http://JacksonDunstan.com/articles/1465">'
				+ 'JacksonDunstan.com'
				+ '</a></font>\n'
				+ 'October 2011';
			about.x = this.width + PAD*5;
			about.y = PAD;
			addChild(about);
 
			__tf.autoSize = TextFieldAutoSize.LEFT;
			__tf.y = this.height + PAD;
			addChild(__tf);
 
			__mode = "No Readback";
			setupContext(Context3DRenderMode.AUTO);
		}
 
		private function setupContext(renderMode:String): void
		{
			__tf.text = "Setting up context with render mode: " + renderMode;
			__stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContextCreated);
			__stage3D.requestContext3D(renderMode);
		}
 
		private function onContextCreated(ev:Event): void
		{
			__stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onContextCreated);
 
			const width:int = stage.stageWidth;
			const height:int = stage.stageHeight;
 
			__context = __stage3D.context3D;
			__context.configureBackBuffer(width, height, 0, true);
			__driverInfo = __context.driverInfo;
 
			__tf.text = "Context created with driver: " + __driverInfo;
		}
 
		private function onToggleHardware(ev:MouseEvent): void
		{
			__context.dispose();
			__tf.text = "Toggling hardware...";
			setupContext(
				__driverInfo.toLowerCase().indexOf("software") >= 0
					? Context3DRenderMode.AUTO
					: Context3DRenderMode.SOFTWARE
			);
		}
 
		private function onTestVRAM2048(ev:MouseEvent): void
		{
			doTest(2048);
		}
 
		private function onTestVRAM1024(ev:MouseEvent): void
		{
			doTest(1024);
		}
 
		private function onTestVRAM512(ev:MouseEvent): void
		{
			doTest(512);
		}
 
		private function onTestVRAM256(ev:MouseEvent): void
		{
			doTest(256);
		}
 
		private function onTestVRAM128(ev:MouseEvent): void
		{
			doTest(128);
		}
 
		private function onTestVRAM64(ev:MouseEvent): void
		{
			doTest(64);
		}
 
		private function onTestVRAM32(ev:MouseEvent): void
		{
			doTest(32);
		}
 
		private function doTest(minDim:uint): void
		{
			var beforeTime:int = getTimer();
			var size:uint = testVRAM(__context, minDim);
			var afterTime:int = getTimer();
			var time:int = afterTime - beforeTime;
 
			var mb:String = (size / (1024 * 1024)).toFixed(2);
			__tf.text = "Driver: " + __driverInfo + "\n"
				+ "VRAM: " + size + " bytes (" + mb + " MB)\n"
				+ "Test time: " + time + " ms";
		}
 
		/**
		*   Test how much available VRAM there is (e.g. for textures, buffers)
		*   @param ctx Context to test the VRAM of
		*   @param minDim Minimum dimensions of textures to create to test the
		*                 VRAM size. Smaller minimum dimensions results in finer
		*                 granularity, but the test may take longer. Capped to
		*                 [32:2048].
		*   @return The number of bytes of VRAM available
		*   @throws ArgumentError If the given context is null
		*   @author Jackson Dunstan (http://jacksondunstan.com/articles/1465)
		*/
		public static function testVRAM(ctx:Context3D, minDim:uint=512): uint
		{
			if (!ctx)
			{
				throw new ArgumentError("'ctx' is null");
			}
 
			minDim = Math.max(32, Math.min(2048, minDim));
 
			var size:uint;
			var textures:Vector.<Texture> = new Vector.<Texture>();
 
			for (var dim:uint = 2048; dim >= minDim; dim >>= 1)
			{
				var count:uint = 0;
				try
				{
					while (true)
					{
						var texture:Texture = ctx.createTexture(
							dim,
							dim,
							Context3DTextureFormat.BGRA,
							false
						);
						count++;
						textures.push(texture);
					}
				}
				catch (err:Error)
				{
					size += count * dim * dim * 4;
				}
			}
			for each (texture in textures)
			{
				texture.dispose();
			}
 
			return size;
		}
 
		private function makeButton(label:String, callback:Function): void
		{
			var tf:TextField = new TextField();			
			tf.defaultTextFormat = TEXT_FORMAT;
			tf.text = label;
			tf.autoSize = TextFieldAutoSize.LEFT;
			tf.selectable = false;
			tf.x = tf.y = PAD;
 
			var button:Sprite = new Sprite();
			button.graphics.beginFill(0xE6E2D1);
			button.graphics.drawRect(0, 0, tf.width+PAD*2, tf.height+PAD*2);
			button.graphics.endFill();
			button.graphics.lineStyle(1, 0x000000);
			button.graphics.drawRect(0, 0, tf.width+PAD*2, tf.height+PAD*2);
			button.addChild(tf);
			button.addEventListener(MouseEvent.CLICK, callback);
 
			button.x = PAD;
			button.y = PAD + this.height;
			addChild(button);
		}
	}
}

There are a couple of obvious scenarios where this will be useful:

  • Running the function by itself on the first run of a game or app using Stage3D. This would allow you to modify your usage of VRAM, such as switching to lower- or higher-resolution textures when VRAM is small or large, respectively.
  • Running the above app on target machines to see how much VRAM Stage3D will give you and how fast it will allocate textures. For example, you could install the app via the AIR packager on a new tablet and see how much VRAM it has.

In any case, I hope you find the Stage3D VRAM Tester useful.

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