Canvas Animation Trouble with Invalidate vs Refresh

  1. 2 months ago

    Eugene D

    Sep 12 Pre-Release Testers, Xojo Pro Canada scispec.ca

    Hello Everyone,

    I am helping a colleague with a project and am having issues with an animation example on windows. Attached is a Canvas speed test which creates filled circles and determines the frames-per-second rate. The program physically runs when both invalidate and refresh are used but.... refresh shows animation, whereas invalidate does not.

    Here is the Canvas speed test program: Canvas Speed Test

    Here are some instructions:
    Press the 'Run Test' button will draw 100 circles, 100 times with invalidate enabled and will show the frames per second. If the number of circles are 50, then circles with a diameter of 1, 2, 3, ....50 are drawn. If 100 circles are drawn then circles will be drawn with a diameter of 1, 2, 3, ...99, 100. The more cirlces drawn then the more time needed for drawing because the ball size is larger.

    The number of redraws forces drawing the number of times each ball is redrawn.

    If the invalidate checkbox is checked, then canvas drawing happens with invalidate, when unchecked then canvas drawing is with refresh.

    Pressing the reset button starts all of the circles at 0,0.

    Colours for each ball are random.

    I am finding that using invalidate doesn't show the animation, whereas using Canvas refresh shows the canvas circles 'moving' on the canvas.

    What am I missing that invalidate is not refreshing the screen for animation?

    Thanks!

    Refresh - Update the canvas NOW... do not pass GO, do not collect $200.... just DO IT
    Invalidate - Wait until the end of the current run cycle and then update the control

    Refresh - Pro
    * it does it NOW
    Con
    * issuing too many, too fast wastes a ton of resouces as each one will force the control to update even if it is not necessary

    Invalidate - Pro
    * you can issue as many as you want, and it will execute ONCE at the end of the run cycle
    Con
    * it happens only at the end of the run cycle

    for i=1 to 1000
    xyz.refresh // this will update xyz 1000 times
    next i
    for i=1 to 1000
    xyz.invalidate
    next i
    // this will probably update xyz at this point (after the loop is done)
  2. Dave S

    Sep 12 Answer San Diego, California USA
    Edited 2 months ago

    Refresh - Update the canvas NOW... do not pass GO, do not collect $200.... just DO IT
    Invalidate - Wait until the end of the current run cycle and then update the control

    Refresh - Pro
    * it does it NOW
    Con
    * issuing too many, too fast wastes a ton of resouces as each one will force the control to update even if it is not necessary

    Invalidate - Pro
    * you can issue as many as you want, and it will execute ONCE at the end of the run cycle
    Con
    * it happens only at the end of the run cycle

    for i=1 to 1000
    xyz.refresh // this will update xyz 1000 times
    next i
    for i=1 to 1000
    xyz.invalidate
    next i
    // this will probably update xyz at this point (after the loop is done)
  3. Markus W

    Sep 12 Pre-Release Testers #JeSuisHuman Europe (Germany)...

    Might be better to use a timer (after all what good is a refresh rate of 150? Only flies see that fast)

  4. Eugene D

    Sep 12 Pre-Release Testers, Xojo Pro Canada scispec.ca

    HI Dave,

    Thank you very much, as this is very helpful!

    How would I be able to get the best-of-both worlds, meaning the speed of invalidate with the animation effect of refresh? My initial thought would be to have half of the Canvas calls be invalidate and half be refresh, although. I am just experimenting to try an optimize invalidate speed with refresh screen animation.

    Once again, thanks!

  5. Dave S

    Sep 12 San Diego, California USA

    only difference is WHEN they happen.... otherwise they are identical in the "result"

  6. Eugene D

    Sep 12 Pre-Release Testers, Xojo Pro Canada scispec.ca

    Thanks Dave,

    When I added an 'invalidate flag' that allows firing of invalidate 10 times for every 1 refresh, I get great refresh speeds with automation. Below is the final result.

    CanvasSpeedTest Rev 1

    Dave, thanks for letting me know about this, as this is a 'breakthrough' for me!!! Superfast canvas for Windows!!!

    For others, to change the ratio, go to the PbRunTest pushbutton action event and change the Invalidate flag number. Right now it is at 10, which means about 10 invalidate calls to 1 refresh call.

  7. Dave S

    Sep 12 San Diego, California USA
    Edited 2 months ago
    For i = 1 to j
       'PBuffer.Graphics.ClearRect(0,0,Canvas1.Width, Canvas1.Height)
       ReDrawCircles
       //Check if invalidate is checked, otherwise default to refresh
       'If CBInvalidate.State = CheckBox.CheckedStates.Checked Then
       If InvalidateFlag = 10 then
          Canvas1.Refresh(False)  ///   THIS HAPPENS AT THIS POINT IN THE CODE
          InvalidateFlag = 0
       Else
          Canvas1.Invalidate(false) /// THIS DOES NOT HAPPEN HERE (it just sends a message)
          InvalidateFlag = InvalidateFlag + 1
       End If
    Next I
      //// THE INVALIDATE(S) happen HERE regardless of how many times you sent an invalidate message

    all you really accomplish is to REFRESH every 10 iterations, and INVALIDATE once.....
    and the INVALIDATE really only adds anything if J is not modulus 10

  8. Thom M

    Sep 12 Pre-Release Testers Greater Hartford Area, CT

    Only in very rare circumstances should you need to use refresh. One such would be in a MouseDown event before showing a menu, so the item can appear pressed before the menu appears. In nearly all cases, you should be using invalidate so the canvas will update when the system deems appropriate, such as not faster than the screen can display.

    A classic example of where refresh is the wrong choice is to update a progress bar. Writing a loop such as For I As Integer = 1 To 100 and refreshing each time wastes resources and blocks the app until the loop is complete. Animation is another case where refresh is almost certainly the wrong choice, as it means the animation is likely based on a number of frames instead of an amount of time. Frames per second is not constant, so it is not possible to compute the correct amount of time based on a number of frames. So, as @Markus W mentioned, a timer is likely the right choice so you can let the system redraw exactly as fast or as slow as necessary without affecting the total time of the animation.

    My Animation Kt project may serve as some inspiration for you.

  9. Dave S

    Sep 12 San Diego, California USA
    Edited 2 months ago

    this produces the exact same result

    For i = 1 to j
       'PBuffer.Graphics.ClearRect(0,0,Canvas1.Width, Canvas1.Height)
       ReDrawCircles
       //Check if invalidate is checked, otherwise default to refresh
       'If CBInvalidate.State = CheckBox.CheckedStates.Checked Then
       If InvalidateFlag = 10 then
          Canvas1.Refresh(False) 
          InvalidateFlag = 0
       Else
          InvalidateFlag = InvalidateFlag + 1
       End If
    Next I
     Canvas1.Invalidate(false) 
  10. Eugene D

    Sep 12 Pre-Release Testers, Xojo Pro Canada scispec.ca

    @Thom M and @Dave S ,

    I see what you are saying, as the Invalidate just wastes resources. Dave, I like your code, as it helps explain this a great deal, where invalidate isn't needed.

    Thanks to both of you for your help, this is great information!!

  11. Thom M

    Sep 12 Pre-Release Testers Greater Hartford Area, CT

    @Eugene D @Thom M and @Dave S ,

    I see what you are saying, as the Invalidate just wastes resources. Dave, I like your code, as it helps explain this a great deal, where invalidate isn't needed.

    Thanks to both of you for your help, this is great information!!

    I'm saying the opposite. Refresh probably wastes resources.

  12. Markus W

    Sep 12 Pre-Release Testers #JeSuisHuman Europe (Germany)...

    Dave, thanks for letting me know about this, as this is a 'breakthrough' for me!!! Superfast canvas for Windows!!!

    What??? I told you that months ago when I reviewed your "I wish I knew how to program the Canvas" book. The biggest mistake in the book was that you consistently used refresh when you should have used invalidate, and vice versa, and I urged you to bring out a quick revision.

  13. Eugene D

    Sep 12 Pre-Release Testers, Xojo Pro Canada scispec.ca

    @Markus W What??? I told you that months ago when I reviewed your "I wish I knew how to program the Canvas" book. The biggest mistake in the book was that you consistently used refresh when you should have used invalidate, and vice versa, and I urged you to bring out a quick revision.

    HI Markus,

    Yes, your right. And thank you for letting me know. At the time I didn't understand. I knew there was an issue, but I didn't understand the reasons why at the moment. Sorry, I just needed time to digest the information.

    Your right Markus, Thanks.,

  14. Eugene D

    Sep 12 Pre-Release Testers, Xojo Pro Canada scispec.ca

    @Thom M My Animation Kt project may serve as some inspiration for you.

    Hi Thom,

    Thanks for the work you did with the AnimationKit. The example program works great on my ancient mac mini with Xojo 2017 and flickers and adds extra lines on a fast PC. Do you happen to have some adaptation that works just as good on a PC?

    Sincerely,

    Eugene

  15. Eugene D

    Sep 12 Pre-Release Testers, Xojo Pro Canada scispec.ca

    @Thom M I'm saying the opposite. Refresh probably wastes resources.

    Hi Thom,

    I agree that resources are wasted because using invalidate is faster, and the canvas does not refresh until the end, which means that there is no animation occurring when using invalidate until the very end. From what I am seeing, using refresh is the only option at the moment.

    In the PBRunSpeedTest Action event, I tried the two following code snippets, one with invalidate and the other with refresh. Code with refresh is the only one which animates by redrawing the circles. Using Invalidate only updates the Canvas at the end, which does not seem helpful when showing animation.

    //Animates the Canvas well

    For i = 1 to j
      'PBuffer.Graphics.ClearRect(0,0,Canvas1.Width, Canvas1.Height)
      ReDrawCircles
      //Check if invalidate is checked, otherwise default to refresh
      'If CBInvalidate.State = CheckBox.CheckedStates.Checked Then
      If InvalidateFlag = 10 then
        Canvas1.Refresh(False)
        'Canvas1.Invalidate(False)
        InvalidateFlag = 0
      Else
        'Canvas1.Invalidate(false) //commented out
        InvalidateFlag = InvalidateFlag + 1
      End If
    Next I

    This does not animate until the end, which is what Dave's original comments were, which has the Canvas updating at the end of the run.

    For i = 1 to j
      'PBuffer.Graphics.ClearRect(0,0,Canvas1.Width, Canvas1.Height)
      ReDrawCircles
      //Check if invalidate is checked, otherwise default to refresh
      'If CBInvalidate.State = CheckBox.CheckedStates.Checked Then
      If InvalidateFlag = 10 then
        'Canvas1.Refresh(False) //commented out
        Canvas1.Invalidate(False)
        InvalidateFlag = 0
      Else
        'Canvas1.Invalidate(false)
        InvalidateFlag = InvalidateFlag + 1
      End If
    Next I

    I am sure that I am missing something, and is there a way to have the Canvas show animation with Invalidate?

    Thanks for your patience.

  16. Thom M

    Sep 12 Pre-Release Testers Greater Hartford Area, CT

    @Eugene D Hi Thom,

    Thanks for the work you did with the AnimationKit. The example program works great on my ancient mac mini with Xojo 2017 and flickers and adds extra lines on a fast PC. Do you happen to have some adaptation that works just as good on a PC?

    Sincerely,

    Eugene

    Flicker is only avoidable on the canvas subclasses, as control of their refreshing is yours. Built-in controls will always flicker like crazy because that’s what they do.

  17. Thom M

    Sep 12 Pre-Release Testers Greater Hartford Area, CT
    Edited 2 months ago

    @Eugene D Hi Thom,

    I agree that resources are wasted because using invalidate is faster, and the canvas does not refresh until the end, which means that there is no animation occurring when using invalidate until the very end. From what I am seeing, using refresh is the only option at the moment.

    In the PBRunSpeedTest Action event, I tried the two following code snippets, one with invalidate and the other with refresh. Code with refresh is the only one which animates by redrawing the circles. Using Invalidate only updates the Canvas at the end, which does not seem helpful when showing animation.

    //Animates the Canvas well

    For i = 1 to j
      'PBuffer.Graphics.ClearRect(0,0,Canvas1.Width, Canvas1.Height)
      ReDrawCircles
      //Check if invalidate is checked, otherwise default to refresh
      'If CBInvalidate.State = CheckBox.CheckedStates.Checked Then
      If InvalidateFlag = 10 then
        Canvas1.Refresh(False)
        'Canvas1.Invalidate(False)
        InvalidateFlag = 0
      Else
        'Canvas1.Invalidate(false) //commented out
        InvalidateFlag = InvalidateFlag + 1
      End If
    Next I

    This does not animate until the end, which is what Dave's original comments were, which has the Canvas updating at the end of the run.

    For i = 1 to j
      'PBuffer.Graphics.ClearRect(0,0,Canvas1.Width, Canvas1.Height)
      ReDrawCircles
      //Check if invalidate is checked, otherwise default to refresh
      'If CBInvalidate.State = CheckBox.CheckedStates.Checked Then
      If InvalidateFlag = 10 then
        'Canvas1.Refresh(False) //commented out
        Canvas1.Invalidate(False)
        InvalidateFlag = 0
      Else
        'Canvas1.Invalidate(false)
        InvalidateFlag = InvalidateFlag + 1
      End If
    Next I

    I am sure that I am missing something, and is there a way to have the Canvas show animation with Invalidate?

    Thanks for your patience.

    This is what I was talking about earlier. Your animation is based on frames instead of time.

    If you want to compute the frames in a single loop, then I’d suggest using an array of pictures, and paint the correct one to the canvas depending on how much time has elapsed. Then use a canvas to invalidate the canvas rapidly, perhaps every 16 ticks. I’m speaking very high level here, so there are lots of blanks you’ll need to fill in.

    Determining the correct frame might be something like floor(((now - start time) / duration) * total frames)

  18. Eugene D

    Sep 12 Pre-Release Testers, Xojo Pro Canada scispec.ca

    @Thom M This is what I was talking about earlier. Your animation is based on frames instead of time.

    Hi Thom,

    Yes, your are correct, my animation is based on frames instead of time. My intent is to calculate maximum frame rates (Canvas Speed Test). You are correct that I can slow the frame rate down to improve quality and get into accurate timing, and this exercise is to determine the maximum frame rate. Although skipping frames artificially increases the number, I was hoping that there is a way to redraw at a fast rate while redrawing the screen.

    @Thom M Flicker is only avoidable on the canvas subclasses, as control of their refreshing is yours. Built-in controls will always flicker like crazy because that’s what they do.

    Having a Canvas subclasss is an interesting option. I'll do some testing and see what I can come up with.

  19. Norman P

    Sep 12 Xojo Inc
    Edited 2 months ago

    new desktop project
    add a canvas to window1 (it will end up being called canvas1)
    turn erase background off
    add a property, mStepSize as integer, to Window1
    add a timer to Window1 - set period to 0 repeat multiple
    add the action event to the timer - in there just put

    canvas1.invalidate

    in canvas1.paint put

    If mstepsize < 1 Then mStepSize = 1
    
    g.forecolor = &cFFFFFF
    g.FillRect 0,0,g.width,g.height
    g.forecolor = &cFF0000
    g.DrawOval g.width/2, g.height/2, mstepsize, mstepsize
    
    mstepsize  = mstepsize + 1
    If mstepsize > 100 Then mstepsize = 1

    run
    runs quite quickly and shouldn't flicker

    should be able to encapsulate the timer & canvas into a custom subclass

    avoiding a loop is the trick - invalidate only works when the main loop gets a chance to execute and flush the drawing to the screen
    Refresh can be a real speed hit esp on OS X as it can cause a lot of compositing to be done right then so it can be a real slow thing esp in a tight loop
    It can be a real hit on Windows as well

    You could precompute a lot of frames and draw the right one in the paint event
    And also throttle things in the timer to draw each frame based on the passage of a certain amount of time (but it will only be close and if you need something more precise there are ways to do that)

  20. Eugene D

    Sep 12 Pre-Release Testers, Xojo Pro Canada scispec.ca

    Hi Norman,

    Thanks for the code. I added the code to the project, and there is much flickering. Attached is a YouTube video taken from my computer that runs HTC Vive Virtual Reality:

    YouTube Screen Capture

    Here is a the download link to this project:

    Canvas Timer

    The good news is that the Canvas refreshes with invalidate (Thank you), it seems to have a high amount of flicker.

    I'll work on this some more, and thanks for the code.

    @Norman P You could precompute a lot of frames and draw the right one in the paint event
    And also throttle things in the timer to draw each frame based on the passage of a certain amount of time (but it will only be close and if you need something more precise there are ways to do that)

    Yes, understood. Throttling and timing isn't a problem when there is a very nice frame rate to start.

    Thanks for the code, and I'll keep trying. :)

  21. Newer ›

or Sign Up to reply!