AS3 Blitting is Slower than a Movieclip. Why?

1.5k views Asked by At

I tried following a combination of Lee Brimlow's blitting tutorial series and and the technique in Rex Van der spuy's "advanced game design with flash"

I am a developer working on a web online virutal world made in flash. I made a phone application (works similar to the phone in grand theft auto games). Anyway, when a message is sent we want to play this crazy animation of an envelope flying around and transforming with sparkles around it. It was laggy (especially on older computers) so I thought it would be a great chance to use blitting. However, the blitting animation actually plays slower than a regular movieclip!! What the heck is going on here? Is blitting only better for mobile devices and actually slower on computers? Maybe I am doing something wrong. Here is my code:

// THIS PART HAPPENS WHEN PHONE IT INITIALIZED

//**                
//---------------- Blitting stuff ----------------------------------
// add this bitmap stage to the display list so we can see it
            _bitmapStage = new BitmapData(550, 400, true, 0xD6D6D6);


        _phoneItself.addChild(new Bitmap(_bitmapStage));

        var _spritesheetClass:Class = getDefinitionByName("ESpritesheet_1") as Class;
        _spritesheet = new _spritesheetClass() as BitmapData;

        _envelopeBlit = new BlitSprite(_spritesheet, BlitConfig.envelopeAnimAry , _bitmapStage);
        _envelopeBlit.x = -100;
        _envelopeBlit.y = 0;

        _envelopePlayTimer = new Timer(5, 0);
        _envelopePlayTimer.addEventListener(TimerEvent.TIMER, onEnterTimerFrame);
        _envelopeBlit.addEventListener("ENV_ANIM_DONE", onEnvAnimFinished);

// a "BlitSprite" is a class that I made. It looks like this:

package com.fs.util_j.blit_utils
{
    import flash.display.BitmapData;
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.geom.Point;
    import flash.geom.Rectangle;

    public class BlitSprite extends EventDispatcher
    {

        private var _fullSpriteSheet:BitmapData;
        private var _rects:Array;
        private var _bitmapStage:BitmapData;

        private var pos:Point = new Point ();
        public var x:Number = 0;
        public var y:Number = 0;

        public var _animIndex:

int = 0; private var _count:int = 0;

    public var animate:Boolean = true;
    private var _whiteTransparent:BitmapData;
    private var _envelopeAnimAry:Array;
    private var _model:Object;



    public function BlitSprite(fullSpriteSheet:BitmapData, envelopeAnimAry:Array, bitmapStage:BitmapData, model:Object = null) 
    {
        _fullSpriteSheet = fullSpriteSheet;
        _envelopeAnimAry = envelopeAnimAry;
            _bitmapStage = bitmapStage;
            _model= model;

            init();
        }

        private function init():void
        {
//          _whiteTransparent = new BitmapData(100, 100, true, 0x80FFffFF);

            this.addEventListener("ENV_ANIM_DONE", onEvnAnimDone);

        }       

        protected function onEvnAnimDone(event:Event):void
        {

        }       

        public function render():void
        {

//          pos.x = x - _rects[_animIndex].width*.5;
//          pos.y = y - _rects[_animIndex].width*.5;

//          if (_count % 1 == 0 && animate == true)
//          {

//              trace("rendering");

                if (_animIndex == (_envelopeAnimAry.length - 1) )
                {
//                  _animIndex = 0;
                    dispatchEvent(new Event("ENV_ANIM_DONE", true));
                    animate = false;
//                  trace("!!!!animate over " + _model.animOver);

//                  if (_model != null)
//                  {
//                      _model.animOver = true;
//                  }

//                  trace("!!!!animate over " + _model.animOver);

                }

                else 
                {
                    _animIndex++;
                }


                pos.x = x + _envelopeAnimAry[_animIndex][1];
                pos.y = y + _envelopeAnimAry[_animIndex][2];


                _bitmapStage.copyPixels(_fullSpriteSheet, _envelopeAnimAry[_animIndex][0], pos, null, null, true);

        }



    }
}




// THIS PART HAPPENS WHEN PHONE'S SEND BUTTON IS CLICKED


                _envelopeBlit.animate = true;
                _envelopeBlit._animIndex = 0;
                _darkSquare.visible = true;
                _envelopePlayTimer.addEventListener(TimerEvent.TIMER, onEnterTimerFrame);
                _envelopePlayTimer.start();

it also uses BlitConfig which stores the info about the spritesheet spit out by TexturePacker

    package com.fs.pack.phone.configuration
    {
        import flash.geom.Rectangle;

        public final class BlitConfig
        {




            public static var _sending_message_real_20001:Rectangle = new Rectangle(300,1020,144,102);
            public static var _sending_message_real_20002:Rectangle = new Rectangle(452,1012,144,102);
            public static var _sending_message_real_20003:Rectangle = new Rectangle(852,852,146,102);
            public static var _sending_message_real_20004:Rectangle = new Rectangle(2,1018,146,102);
            public static var _sending_message_real_20005:Rectangle = new Rectangle(702,822,148,102);
.
.
.
public static var _sending_message_real_20139:Rectangle = new Rectangle(932,144,1,1);

    public static var envelopeAnimAry:Array = [

                // rectangle, x offset, y offset
            [ _sending_message_real_20001, 184,155],
            [ _sending_message_real_20002, 184,155],
            [ _sending_message_real_20003, 183,155],
            [ _sending_message_real_20004, 183,155],
.
.
.
[ _sending_message_real_20139, 0,0]
        ]



        public function BlitConfig()
        {
        }


    }
}
1

There are 1 answers

4
Josh On

EDIT: Knowing that this is not mobile, my answer below is irrelevant. I will leave it there, though, in case someone is having trouble with blitting on mobile in the future.

With regards to this specific question, you are running your timer every 5ms. First off, the lowest range that a Timer is accurate is >15ms so that will never be a viable solution. For any Timer relating to displaying soemthing on the stage, you should never do it less than a single frame. (1000/stage.framerate. ~40ms for a 30fps app)

For blitting, the goal is to reduce calculations and rendering. The way you have this set up right now, it looks like you are blitting every 5ms. That is actually more than 8 times as often as the MovieClip is rendering. You should reduce how often you blit. Only do it when a change has actually been made beyond translation. Doing it any more often than that is overkill and the reason it is so slow (again, creating bitmaps is slow)


In general, you do not want to blit in an AIR for Mobile application (which I assume you are doing since you mentioned the phone being initialized). I'm not sure if it is okay to do it using other/native SDKs, but avoid it in AIR.

Essentially, it comes down to how blitting works. Blitting takes a screen capture and displays that on the stage rather than the actual object. In general, this is great. It means that your display objects, particularly vectors which are slow to render, have to render far less often. It is especially good when animating because an object tends to re-render every time it is translated in any way, but not a bitmap.

On mobile platforms, however, creating that bitmap is incredibly slow. I've never looked into how the SDK creates the Bitmaps, but it doesn't do it efficiently (it often makes me wonder if it does it pixel-by-pixel). On desktops, this is generally fine. There is plenty of CPU and plenty of RAM to make this happen quickly. On mobile, however, that luxury is not there at the moment. So when you blit and create that bitmap, it takes a while to run that process.

The problem is exacerbated on high-resolution screens. An app I developed from January to May of this year selectively used blitting to use filters in a GPU accelerated environment. On an iPad 2, the blitting took my app from 30fps to ~24fps. Not a big deal, not anything the user would notice. On an iPad 3 with retina display, however, it dropped down to 10fps. It makes sense when you think about it, as retina iPads have 4x as many pixels as non-retina iPads do.

If you do want to use blitting on mobile, I recommend a few things:

  1. Use GPU rendering mode. Without it, you stand no chance. Be aware that, at least with pre-AIR 3.7, filters were not supported in GPU mode. I am unsure if that is still the case. You should avoid using filters on mobile regardless, though, as they are very slow to render
  2. Make sure to test a release-mode application. Depending on build settings, the difference between debug mode and a release mode app can be substantial, especially on iOS. An app I just developed went from taking 2-3 seconds to create a new Flex View in debug mode to less than a frame (~40ms) to do it in release mode on an iPhone 4
  3. Use blitting sparingly. Only do it where absolutely necessary
  4. Look for ways to simplify your display list. It is easy to have an object with 40 children to create a button. Instead, look for ways to simplify that into fewer objects and fewer filters (even if removing a filter requires you add another object). I don't believe this will help with the actual blitting process, but it should help with rendering the objects in the first place.

So in general, use blitting sparingly on mobile because bitmap creation is slow.