Skip navigation
Tomato Gerd
Currently Being Moderated

Simple animation using doublebufferd canvas is not smooth

Sep 7, 2012 5:59 AM

Tags: #graphics #performance #animation #buffering #flickering #canvas #double #lacking

Hi out there,

 

I try to develop a simple flash game using a doublebuffer to draw the animations.

The redraw happens when Event.ENTER_FRAME is fired.

The backbuffer used for doublebuffering is of type BitmapData.

The animation is very simple: I draw a 20x20-pixel Bitmap into the backbuffer with increasing x coordinate, so that it should move smoothly from the left to the right side of my canvas. This basically works fine, but if you look closely, you will see significant disruptions in this movement. This does not seem to be related to the frame rate, since its constantly over 60. The disruptions of the smooth moving are not acceptable for an animations, but I'm quite sure I've done nothing wrong with the doublebuffering, so I'm scared this could be a flash-player problem or something... I would be very relieved if this were not the case

 

Please have a look at the swf showing the simple animation:

https://dl.dropbox.com/u/55967135/test.swf

 

(Btw. the disruptions of the animation also don't disappear when the movement is based on the time between two frames - by now its constant 2 pixels per frame.)

I've uploaded a very lightweight flash builder project including the whole sourcecode for the swf above:

https://dl.dropbox.com/u/55967135/test.zip

 

This is the enter frame function, that draws the moving small image (20x20-pixel PNG):

        public function enterFrame():void               
          {
               
               // Calculate the time since the last frame  (NOT USED IN THE EXAMPL PROGRAM)                   
               var thisFrame : Date = new Date();               
               var dT : Number = (thisFrame.getTime() - lastFrame.getTime())/1000.0;               
               lastFrame = thisFrame;          
               
               // erase backBuffer
               backBuffer.fillRect(backBuffer.rect, 0xFFFFFFFF);
               
               
               // set new postion of the small testimage
               if (this.pos > 600 || this.pos < 0) {
                    this.direction = !this.direction;
               }
               
               // increase / decrease vertical position
               if (this.direction) {
                    this.pos += 2;
               } else {
                    this.pos -= 2;
               }
               
               //trace(pos);
               // draw small test image at postion "offset"
               backBuffer.copyPixels(     this.testGraphic.bitmap.bitmapData, 
                                             this.testGraphic.bitmap.bitmapData.rect, 
                                             new Point(pos, 0.0));               
          }

The enterFrame() is a method of my class GraphicsController, which handles the doublebuffering. It is launched by the applications enterFrame(event) method:

            public function enterFrame(event:Event):void
               {
                    GraphicsController.Instance.enterFrame();     
                    myCanvas.graphics.clear();
                    myCanvas.graphics.beginBitmapFill(GraphicsController.Instance.backBuffer, null, false, false);                              
                    myCanvas.graphics.drawRect(0, 0, this.width, this.height);                              
                    myCanvas.graphics.endFill();

               }     

Help would be GREATLY appreciated

 

Thank you

Gerd

 
Replies
  • kglad
    72,220 posts
    Jul 21, 2002
    Currently Being Moderated
    Sep 7, 2012 7:18 AM   in reply to Tomato Gerd

    i don't use flash builder so there could be a lot lost translating from fb to flash pro but the first problem i see in your code is the repeated use of new Data:

     

    don't use new Date() as a timer.  it's 6 times slower than getTimer().

     
    |
    Mark as:
  • kglad
    72,220 posts
    Jul 21, 2002
    Currently Being Moderated
    Sep 8, 2012 7:40 AM   in reply to Tomato Gerd

    pos should be an int, not a Number

     

    this.testGraphic.bitmap.bitmapData.rect should be replaced by a variable that stores that rectangle reference

     

    new Point() should be replaced by a variable whose x property is updated in your enterFrame function

     

    and, you can increase your frame rate to improve smoothness of motion.

     
    |
    Mark as:
  • kglad
    72,220 posts
    Jul 21, 2002
    Currently Being Moderated
    Sep 8, 2012 11:10 AM   in reply to Tomato Gerd

    performance is the issue.  just because your frame rate remains steady doesn't mean there will be no "hiccups" in your animation.

     

    almost certainly you are measuring an average frame rate.  so, if you find your animation is a steady 24 fps, it's highly unlikely that your display is updated every 1000/24 ms. 

     

    most likely you're getting an update between every 20 and 80 ms (and, it's possible because of your coding, you have an even wider range of display updates).

     

    to test, create a startTime int and in your enterFrame function use:

     

    trace(getTimer()-startTime);

    startTime=getTimer();

     

    run that code until you see a hiccup and then let me know what update range you saw.

     
    |
    Mark as:
  • kglad
    72,220 posts
    Jul 21, 2002
    Currently Being Moderated
    Sep 8, 2012 12:15 PM   in reply to Tomato Gerd

    i'm not sure what to make of those numbers because i'm not familiar with fb. 

     

    trying to duplicate your setup shows the frame rate drops below 6 when the animation stutters.

     
    |
    Mark as:
  • kglad
    72,220 posts
    Jul 21, 2002
    Currently Being Moderated
    Sep 8, 2012 12:38 PM   in reply to Tomato Gerd

    i used the following in my document class (Main):

     

    package {

     

        import flash.display.MovieClip;

        import test.GraphicsController;

        import flash.events.Event;

     

        public class Main extends MovieClip {

            var controller:GraphicsController;

            private var myCanvas:MovieClip;

     

            public function Main() {

                controller=GraphicsController.Instance;

                controller.startup(stage.stageWidth,stage.stageHeight,this);

                myCanvas = this;

                this.addEventListener(Event.ENTER_FRAME,enterFrame);

            }

            private function enterFrame(event:Event):void {

                GraphicsController.Instance.enterFrame();

                myCanvas.graphics.clear();

                myCanvas.graphics.beginBitmapFill(GraphicsController.Instance.backBuf fer, null, false, false);

                myCanvas.graphics.drawRect(0, 0, stage.stageWidth, stage.stageHeight);

                myCanvas.graphics.endFill();

     

            }

        }

     

    }

     

    and this in GraphicsController:

     

    package test

    {

        import flash.display.*;

        import flash.events.*;

        import flash.geom.Point;

        import flash.geom.Rectangle;

        import flash.utils.getTimer;

        //import flash

     

        //import mx.collections.*;

        import mx.core.*;

     

        public class GraphicsController       

        {

            // double buffer

            public var backBuffer:BitmapData;       

     

            // colour to use to clear backbuffer with       

            public var clearColor:uint = 0xFFFFFFFF;

            //private var canvas:Bitmap;

     

            // static instance

            protected static var instance:GraphicsController = null;

     

            // the last frame time

            //protected var lastFrame:Date;

     

            private var testGraphic : GraphicsResource = ResourceManager.testGraphic;

            private var rect:Rectangle;

            private var pos : int = 0;

            private var pt:Point = new Point(0,0);

            private var direction : Boolean = true;

            private var startTime:int = getTimer();

            var lastFrame:int = 0;

     

            public function startup(width : Number, height : Number, tl:MovieClip):void       

            {           

                backBuffer = new BitmapData(width,height);   

                rect = this.testGraphic.bitmap.bitmapData.rect;

     

                //canvas = new Bitmap(backBuffer);

                //lastFrame = new Date();   

                //tl.addChild(canvas);

            }

     

            static public function get Instance():GraphicsController

            {

                if(instance == null)

                {

                    instance = new GraphicsController();

                }

     

                return instance;

            }

     

     

            public function GraphicsController()

            {

                if(instance != null)

                {

                    throw new Error("Only one instance allowed.");

                }

            }

     

     

            public function enterFrame():void           

            {

     

                // Calculate the time since the last frame                   

                //var thisFrame : Date = new Date();           

                //var dT : Number = (thisFrame.getTime() - lastFrame.getTime())/1000.0;           

                //lastFrame = thisFrame;       

     

                // erase backBuffer

                backBuffer.lock();

                backBuffer.fillRect(backBuffer.rect, 0xFFFFFFFF);

     

     

                // set new postion of the small testimage

                if (this.pos > 300 || this.pos < 0) {

                    this.direction = !this.direction;

                }

     

                // increase / decrease vertical position

                if (this.direction) {

                    this.pos += 2;

                } else {

                    this.pos -= 2;

                }

                pt.x = pos;

                //trace(pos);

                // draw small test image at postion "offset"

                backBuffer.copyPixels(this.testGraphic.bitmap.bitmapData, rect, pt);           

                backBuffer.unlock();

     

                  var thisFrame : Number = Number(getTimer());              

                  var dT : Number = (thisFrame - lastFrame)/1000.0;              

                  lastFrame = thisFrame;         

                  trace(1.0/dT);

                //, (getTimer()-startTime)

                //trace(getTimer()-startTime);

                startTime=getTimer();

            }

     

     

        }

     
    |
    Mark as:
  • kglad
    72,220 posts
    Jul 21, 2002
    Currently Being Moderated
    Sep 9, 2012 2:08 PM   in reply to Tomato Gerd

    in addition to the problems i mentioned before, you are also using beginBitmapFill which is going to be slower (in most circumstances) than copyPixels which is what you should be using to implement a blitting application (assuming that's what you're trying to do).

     
    |
    Mark as:
  • kglad
    72,220 posts
    Jul 21, 2002
    Currently Being Moderated
    Sep 9, 2012 5:28 PM   in reply to Tomato Gerd

    i'm not an expert on flashbuilder or flex.

     

    i can tell you that flash is prefectly capable of handling a lot more than 1 square moving across the stage without any hiccups and there's no need to use any buffer. 

     

    simply drawing the display to a single bitmap is more than capable of handling what would otherwise be high-demand displays. 

     

    here's an example with 10,000 squares moving and rotating using blitting which is the use of one stage-sized bitmap to display what is seen on stage:  www.kglad.com/Files/forums/blit_test2.html

     
    |
    Mark as:

More Like This

  • Retrieving data ...

Bookmarked By (0)

Answers + Points = Status

  • 10 points awarded for Correct Answers
  • 5 points awarded for Helpful Answers
  • 10,000+ points
  • 1,001-10,000 points
  • 501-1,000 points
  • 5-500 points