Canvas Animation Trouble with Invalidate vs Refresh

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)

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

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!

only difference is WHEN they happen… otherwise they are identical in the “result”

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.

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

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

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) 

@Thom McGrath 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!!

[quote=349966:@Eugene Dakin]@Thom McGrath 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!![/quote]
I’m saying the opposite. Refresh probably wastes resources.

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

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

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.

[quote=350018:@Eugene Dakin]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[/quote]
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.

[quote=350022:@Eugene Dakin]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.[/quote]
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)

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.

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

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)

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.

[quote=350031:@Norman Palardy]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)[/quote]
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. :slight_smile: