Stage3D VRAM Tester
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:
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!
#1 by crookedspoon on October 24th, 2011 ·
Isn’t the VRAM used by all open apps simultaneously? Or, what is the VRAM tester looking for? Amount of available VRAM vs total amount of VRAM?
What if a background app claims more VRAM while your app is running?
thanks for your feedback!
#2 by jackson on October 24th, 2011 ·
That is up to various factors: OS, drivers, Flash Player, browsers, etc. For testing purposes, I’d recommend closing everything down except the test app. As a way of benchmarking during a live game, I suppose you’d at least have a minimum available VRAM count, just like a CPU test would tell you a minimum amount of CPU power given that other apps could be chugging away as well.
#3 by Richard Davey on October 24th, 2011 ·
This is interesting stuff, but I wonder how accurate it is. I feel there are Adobe imposed limitations at play here. For example my GPU has 512MB GDDR3 dedicated RAM but 4095MB available to it (using system shared, standard for most NVIDIA cards). The test reports back as having 760MB VRAM available. Which of course is more than the dedicated RAM I actually have, but far short of the available ram.
So I wonder what is going on in Flash Player to enforce this.
#4 by jackson on October 24th, 2011 ·
Try the test again now that I’ve fixed some bugs with it. You should get more accurate results.
Still, this is a problem. The analogy would be trying to figure out how much system RAM a user has by allocating
BitmapData
objects until you got an error. You’d run through all their actual, physical RAM and then start eating into virtual memory (a.k.a. “swap space”) until you filled that up. It’d be easy to get a reading back saying that the user has 4 GB of RAM when they actually only have 2 GB and 2 GB of virtual memory.As far as policy goes, I’d expect to use these VRAM test numbers to get a “ballpark” estimate of how much VRAM you’re dealing with. Your app should probably have large “buckets” to slot the user in. For example, users with less than 64MB of VRAM get tiny textures, 64MB-1024MB get medium textures, and 1024MB+ get large textures. You may guess wrong, but I guess that’s why there are “graphics settings” dialogs. :)
#5 by Tronster on October 24th, 2011 ·
I hope I’m reading this wrong, but isn’t minDim not being used in doTest?
#6 by Tronster on October 24th, 2011 ·
I believe the fix is for line 2 of doTest():
#7 by jackson on October 24th, 2011 ·
You’re right and I’ve updated the article to include that change. I also changed a couple of other lines:
The test should be much more accurate now. I’ve updated the article with the new code and example SWF.
Thanks for the catch!
#8 by skyboy on October 29th, 2011 ·
doesn’t this inflate the reported size by adding all of the sizes (i.e., 1 * 2048 * 2048 * 4 + 1 * 1024 * 1024 * 4 + 1 * 512 * 512 * 4 + 2 * 256 * 256 * 4 … for 16.25 MB)
with the fix being:
#9 by jackson on October 29th, 2011 ·
You’re correct, this was leading to over-counting VRAM. I’ve updated the article and SWF with the correction.
#10 by Albob on August 6th, 2012 ·
The article still shows a bug a reader suggested a fix for :
#11 by Albob on August 6th, 2012 ·
Where it should be
#12 by jackson on August 6th, 2012 ·
Good catch! I’ve updated the article.
#13 by Sebastian on November 20th, 2012 ·
Hi,
#14 by Sebastian on November 20th, 2012 ·
Hi,
two things:
1) You are not counting mip maps in your allocations. Stage 3D counts those as size*1.5 as an approximation.
2) This just hits a limit in Stage3D that is 512mb for all active texture allocations. That limit is there mostly to catch programming errors, so there are separate pools for cube maps and vertex and index buffers.
#15 by Dave on January 31st, 2013 ·
Hmm, no matter what system I run this on (including some Android devices) it reports 341mb of available VRAM. Curious indeed…
#16 by jackson on January 31st, 2013 ·
Perhaps you’re hitting the cap:
#17 by Gurel on March 5th, 2013 ·
This simply won’t work. I have a Mac Book with 256Mb NVidia Graphics Card. The test still gives 341MB. If you exceed the VRAM it will still work but use the CPU RAM as a solution, dropping the FPS down. You will get 3-9 fps, for the textures to be passed front and back from the RAM to the VRAM although still functioning. If you don’t account for the fps this program will probably always give you the max limit on Stage3D