13 Replies Latest reply: Sep 9, 2012 5:28 PM by kglad RSS

    Simple animation using doublebufferd canvas is not smooth

    Tomato Gerd

      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

        • 1. Re: Simple animation using doublebufferd canvas is not smooth
          kglad MVP

          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().

          • 2. Re: Simple animation using doublebufferd canvas is not smooth
            Tomato Gerd Community Member

            Hi kglad,

             

            you're obviously right in the point that the "new Date()" calls each frame are redundant.

            Unfortunately the use of "getTimer()" doen't fix the problem.

            It seems to me that it's not the framerate but the backbuffer isn't actually drawn on each redraw event.

            Any further suggestions?

             

            Regards Gerd

            • 3. Re: Simple animation using doublebufferd canvas is not smooth
              kglad MVP

              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.

              • 4. Re: Simple animation using doublebufferd canvas is not smooth
                Tomato Gerd Community Member

                Hi,

                I have implemented all the suggestions and updated my uploaded files:

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

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

                I don't think that performance is the problem, since the frame rate doesn't decrease when the interuptions appear.

                It seems to me that the updated image is not displayed every frame.

                Could it be a flash player inherent problem ? (the disruptions are only every 5-10 seconds but clearly disturbing)

                 

                This is the relevant code (also contained in the provided zip file).

                Thanks for further suggestions.

                 

                test.mxml:

                framerate set to 100, "holder" is an mx:UIComponent.

                                    
                          
                     import test.GraphicsController;
                          import test.ResourceManager;
                          
                          private var frontBuffer : Bitmap;
                
                          public function enterFrame(event:Event):void
                          {
                               GraphicsController.Instance.enterFrame();     
                          }                                   
                          
                          public function init():void
                          {     
                               GraphicsController.Instance.startup(myCanvas.width, myCanvas.height);
                               frontBuffer = new Bitmap(GraphicsController.Instance.backBuffer);     
                               holder.addChild(frontBuffer);
                
                               addEventListener(Event.ENTER_FRAME, enterFrame);
                          }
                
                

                GraphicsController.as

                package test
                {
                     import flash.display.*;
                     import flash.events.*;
                     import flash.geom.Point;
                     import flash.geom.Rectangle;
                     
                     import mx.collections.*;
                     import mx.core.*;
                     
                     import spark.primitives.Rect;
                     
                     public class GraphicsController          
                     {
                          // double buffer
                          public var backBuffer:BitmapData;          
                          
                          // colour to use to clear backbuffer with          
                          public var clearColor:uint = 0xFFFFFFFF;
                          
                          // static instance
                          protected static var instance:GraphicsController = null;
                          
                          private var testGraphic : GraphicsResource = ResourceManager.testGraphic;
                          private var direction : Boolean = true;
                          private var pos : Point = new Point(0, 0);
                          private var rect : Rectangle;
                          private var bitmapData : BitmapData;
                          
                          
                          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 startup(width : Number, height : Number):void          
                          {               
                               backBuffer = new BitmapData(width,height);     
                               rect = this.testGraphic.bitmap.bitmapData.rect;
                               bitmapData = this.testGraphic.bitmap.bitmapData;
                          }
                
                          public function enterFrame():void               
                          {
                               this.backBuffer.lock();
                
                               // erase backBuffer
                               this.backBuffer.fillRect(backBuffer.rect, 0xFFFFFFFF);
                               
                               // set new postion of the small testimage
                               if (this.pos.x > 600 || this.pos.x < 0) {
                                    this.direction = !this.direction;
                               }
                               
                               // increase / decrease vertical position
                               if (this.direction) {
                                    this.pos.x += 2;
                               } else {
                                    this.pos.x -= 2;
                               }
                               
                               // draw small test image at postion "offset"
                               this.backBuffer.copyPixels(     this.bitmapData, 
                                                                  this.rect, 
                                                                  this.pos);               
                               this.backBuffer.unlock();
                          }
                          
                          
                     }
                }
                
                • 5. Re: Simple animation using doublebufferd canvas is not smooth
                  kglad MVP

                  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.

                  • 6. Re: Simple animation using doublebufferd canvas is not smooth
                    Tomato Gerd Community Member

                    Hi,

                    first of all, thanks for you further interest on my problem...

                    I now calculate the framerate per frame (not averaged over one second) using

                     

                              public function enterFrame():void               
                              {
                                   // Calculate the time since the last frame                         
                                   var thisFrame : Number = Number(getTimer());               
                                   var dT : Number = (thisFrame - lastFrame)/1000.0;               
                                   lastFrame = thisFrame;          
                                   trace(1.0/dT);
                                            ....
                    
                    

                    I interrupted the trace output directly after these hicups occured. These are the fps values measured per frame:

                    https://dl.dropbox.com/u/55967135/fps.txt

                    The framereate was set to 1000 to allow fastest rendering. Although the framerate varies (as you said) down to ~30, this cannot explain these hicups in my opinion, since an fps of above 24 should be able to display continuous animation. Thanks again for your time.

                    • 7. Re: Simple animation using doublebufferd canvas is not smooth
                      kglad MVP

                      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.

                      • 8. Re: Simple animation using doublebufferd canvas is not smooth
                        Tomato Gerd Community Member

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

                        how do you duplicate it? How can I reprocuce your result?

                        • 9. Re: Simple animation using doublebufferd canvas is not smooth
                          kglad MVP

                          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.backBuffer, 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();

                                  }

                           

                           

                              }

                          • 10. Re: Simple animation using doublebufferd canvas is not smooth
                            Tomato Gerd Community Member

                            Thank you, I tried it out. However I still don't have an idea how the lack of performance could be explained, since I think there should be nothing very time consuming in the code. I'm hopefully looking forward to further suggestions.

                            Thanks

                            Gerd

                            • 11. Re: Simple animation using doublebufferd canvas is not smooth
                              kglad MVP

                              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).

                              • 12. Re: Simple animation using doublebufferd canvas is not smooth
                                Tomato Gerd Community Member

                                Hi kglad, in posting 4. I posted a version that doesnt use beginBitmapFill anymore. Also, all your suggestions have been implemented in that version - but the hiccup-problem remains the same.

                                I thought the doublebuffer technique to be a very common approach for flash games - lots of tutorials for this topic exist, e.g.

                                http://www.brighthub.com/internet/web-development/articles/11012.aspx

                                Thats why I wonder that this problem hasn't been discovered before.

                                I still hope, its just a mistake in my code, but comparing to existing tutorial sites, its quite the same.

                                Still looking forward to a solution...

                                • 13. Re: Simple animation using doublebufferd canvas is not smooth
                                  kglad MVP

                                  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