ScrollRect and CacheAsBitmap
Anybody who has ever had a performance problem with a graphically-rich Flash app has seen the “Show Redraw Regions” option in the debug player. Well, at least I hope they have since it’s right there in the context menu and there’s even a function call to toggle them. Normally it’s pretty straightforward what gets redrawn and when, but when you start to mix in advanced features like scrollRect and cacheAsBitmap, things get complicated. Today I’m going to cover a technique for eliminating some mysterious redraw regions.
The scrollRect feature allows you to conveniently and efficiently mask a portion of a DisplayObject with a rectangle. These are commonly used to mask gameplay areas, scroller areas, and many other areas that are naturally rectangular. Yet it is common to see redraw regions outside of the scroll rectangle, even with the contents are as simple as a single Bitmap. The solution to this problem is to enable cacheAsBitmap as the following test app shows:
package { import flash.display.*; import flash.events.*; import flash.geom.*; import flash.profiler.*; import flash.system.*; import flash.text.*; import flash.ui.*; import flash.utils.*; [SWF(backgroundColor=0xEEEADB,frameRate=30)] public class CacheAsBitmapTest extends Sprite { public function CacheAsBitmapTest() { stage.scaleMode = StageScaleMode.NO_SCALE; stage.align = StageAlign.TOP_LEFT; var help:TextField = new TextField(); help.autoSize = TextFieldAutoSize.LEFT; help.defaultTextFormat = new TextFormat("_sans"); help.htmlText = "Press SPACE to toggle cacheAsBitmap\n" + "By " + "<font color=\"#0071BB\">" + "<u>" + "<a href=\"http://jacksondunstan.com\">Jackson Dunstan</a>" + "</u>" + "</font>"; addChild(help); var status:TextField = new TextField(); status.autoSize = TextFieldAutoSize.LEFT; status.defaultTextFormat = new TextFormat("_sans"); status.text = "cacheAsBitmap: false"; status.y = help.height; addChild(status); if (!Capabilities.isDebugger) { var warning:TextField = new TextField(); warning.autoSize = TextFieldAutoSize.LEFT; warning.defaultTextFormat = new TextFormat("_sans"); warning.htmlText = "<b>WARNING: this app is meant to be run in" + " a debug version of Flash Player</b>"; warning.y = status.y + status.height; addChild(warning); } var dir:int = 1; var scrollRect:Rectangle = new Rectangle(0, 0, 50, 50); var bmd:BitmapData = new BitmapData(200, 200); bmd.noise(Math.random()*10000); var map:Bitmap = new Bitmap(bmd); map.x = map.y = 200; map.scrollRect = scrollRect; addChild(map); showRedrawRegions(true, 0xff0000); var lastTime:int = getTimer(); addEventListener( Event.ENTER_FRAME, function(ev:Event): void { var elapsed:int = getTimer() - lastTime; lastTime = getTimer(); var moveAmount:int = (elapsed*dir) / 20; scrollRect.x = scrollRect.y = scrollRect.x + moveAmount; if (scrollRect.x > 150) { scrollRect.x = scrollRect.y = 150; dir = -dir; } else if (scrollRect.x < 0) { scrollRect.x = scrollRect.y = 0; dir = -dir; } map.scrollRect = scrollRect; } ); stage.addEventListener( KeyboardEvent.KEY_DOWN, function(ev:KeyboardEvent): void { if (ev.keyCode == Keyboard.SPACE) { map.cacheAsBitmap = !map.cacheAsBitmap; status.text = "cacheAsBitmap: " + map.cacheAsBitmap; showRedrawRegions(false, 0xff0000); showRedrawRegions(true, 0xff0000); } } ); } } }
To appreciate this, you really need to run it in the debug plugin (or standalone player). A simple screenshot will not suffice. So here is the app in action:
Notice that when cacheAsBitmap is turned on the redraw region is restricted to just the scroll rectangle, but when it’s turned off the redraw region is the entire Bitmap in the scroll rectangle and a very large portion of the stage is redrawn. It’s rather unintuitive to enable cacheAsBitmap on a Bitmap, but it does fix this problem and just might save the day when performance is at a premium.
This is the first time I’ve used the above Flash embedding script, so please let me know if you have any issues with it.
#1 by Piergiorgio Niero on March 9th, 2010 ·
pay attention in using cacheAsBitmap,
it solves the problem you underlined but there is no way to dispose the cached bitmap causing a memory leak.
#2 by jackson on March 9th, 2010 ·
You (the AS3 programmer) can’t explicitly dispose the cached bitmap, but you can’t explicitly dispose of anything. All you can do is remove all your references to an object and let the GC clean it up. In this case, the cached bitmap will be cleaned up when you clean up the display objects involved. There is definitely extra memory usage for this technique, but it certainly isn’t a memory leak.
#3 by Liam O'Donnell on December 17th, 2012 ·
Actually, explicitly disposing of the memory associated with the data of BitmapData objects via the dispose method does reclaim this memory immediately. This is not to be confused with the memory associated with the object itself (all couple of hundred bytes of it), which is, as you mentioned, reclaimed at some later stage by GC. Also, it’s probably worth noting that, under AIR for TV, Bitmaps are treated as proper Bitmaps – so the addition of cacheAsBitmap isn’t required, as above.
#4 by Alec McEachran on March 10th, 2010 ·
Hey Jackson,
I’ve just got around to catching up on your blog. Thanks for this interesting article. As you say, it’s pretty counter-intuitive.
#5 by Karl Knocking on March 14th, 2010 ·
A bit OT but is there an easy way to switch between Debug and Release Player inside the browser?
#6 by jackson on March 14th, 2010 ·
Not that I’ve found. The best way I know is Windows-only.
Anybody know a better way? Anybody know a good way for Mac?
#7 by skyboy on October 24th, 2010 ·
The problem with this method is that IE uses a different flash player all together, complete with its own unique bugs and slowdowns/speedups, based on some of my own experience (ie. x/y variables becoming undefined half way through a function where
this.removeMovieClip
gets called in AS2. resolution was to save it to a temp variable when the function started).#8 by Sindisil on March 16th, 2010 ·
Previous to FF 3.6, Flash Switcher (http://www.sephiroth.it/firefox/flash_switcher/) is an awesome tool for testing on multiple FP versions (both version & release/debug).
Sadly, I’m having no end of trouble running it under FF 3.6, either on WinXP or Vista x64.
I’ve resorted to always running release FP in my browser & doing all debug using the standalone Flash Player. Far from ideal, obviously.
If I have time this weekend, I’m going to spend a few hours trying to get FlashSwitcher working in FF 3.6.
#9 by jackson on March 16th, 2010 ·
Awesome! Let me know if you get it working.
#10 by Aidan Fraser on March 30th, 2010 ·
Awesome demo man. Very helpful.
Can I suggest a download link for the swf? I only have the debug player as a standalone app, not installed in my browser.
Also: “but when it’s turned on the redraw” should be “but when it’s turned off the redraw” ??
#11 by jackson on March 30th, 2010 ·
Good catch; I’ve updated the article to fix the typo. And I’ll add that download link soon.