Can this animation be made faster

I just converted the only graphics oriented program I have ever written from carbon to cocoa. The carbon version was first written back in the FutureBasic days and converted to RealBasic shortly after it came out. I am finding that now that it is in Cocoa it almost 7 times slower and am wondering if someone far more knowledgable in this sort of thing can give me an idea of how to speed it up. Yes, the Carbon version did all of its work painting on to the Canvas from outside of the paint event. That has been corrected to make Cocoa happy.

The program shows the inner workings of 12 different sort algorithms with each element of the array being sorted shown as a bar on a canvas. Before the sort is started a picture object is filled with the proper background color and then the bars are painted from the random values that were placed in to the array. Canvas1.Refresh is then called and its paint event simply does a DrawPicture onto the canvas. Then as the sorting algorithm executes any time the need to move a bar, or pair of bars, either the drawOneBar or doSwap methods are called and the do the redrawing of the respective bar or bars.

The animation works, and I am happy that I got it that far; but, it sure seems that I should be able to get it to work better than what it is. I just tested the speed of the Carbon version versus the Cocoa version and got this.

To sort 251 bars in Carbon it was 19.6 seconds and in Cocoa it was 136.9 seconds, almost 7 times slower. And this was running Quick sort, the fastest of the sorts. I shudder to think the time it would take for the maximum number of bars the program can work with and the slowest sorting algorithm. Any suggestions on how to improve this? This old timer is in need of some help on this one. I am going to take a guess that it is the fact that the entire picture is being redrawn for every bar being moved and the program is running on a 27 inch screen with the canvas occupying nearly the whole screen. Still, the old carbon version with same sized canvas is seven times faster but it is only redrawing the bars as they move.

The following shows the four blocks of code that have anything to do with the animation. The comments at the top of all but the Canvas1.Paint event explains their function. In the drawOneBar method, if I change the canvas1.Refresh to canvas1.Invalidate I get no animation since the sorting algorithms do not relinquish time for the canvas to be updated.

[code]DrawBars
// This method is invoked any time the canvas needs to be redrawn with the bars in their
// original random order. So it is called as window opens, and then before the starrt of any
// sort routine. Also called any time barwidth or space between bars is changed since the number of
// bars will be changed. Last point it is called is if the user selects the New Bars option from the menu
Dim xPos, yPos, inc, i as Int32

#pragma BackgroundTasks False
#pragma BoundsChecking False
// Clear the canvas back to one solid color
sortPic.Graphics.foreColor = bkgndColor
sortPic.Graphics.fillRect 2, 2, canvas1.width - 2, canvas1.height - 2

//Draw border around the canvas where the bars will appear
sortPic.Graphics.foreColor = RGB(204, 0, 0)
sortPic.Graphics.drawline 1, 1, canvas1.width, 1 // top border
sortPic.Graphics.drawline 1, 1, 1, canvas1.height - 1 //left border
sortPic.Graphics.drawline canvas1.width - 1, 1, canvas1.width - 1, canvas1.height - 1 // right border
sortPic.Graphics.drawline 1, canvas1.height - 1, canvas1.width - 1, canvas1.height - 1 // bottom border

//Draw the bars
sortPic.Graphics.foreColor = barColor
xPos = 3
inc = barWidth + spaceBetweenBars
For i = 1 to gMaxBars
yPos = Canvas1.height - 1 - barsToSort(i)
sortPic.Graphics.fillRect xPos, yPos, barWidth, barsToSort(i)
xPos = xPos + inc
Next
Canvas1.Refresh
End Subroutine

doSwap(bar1 As Int32, bar2 As Int32)
// This method is called from the various sorting methods any time two bars need to be swapped.
// The element numbers in the array for the two bars are passed as arguments
// There are certain sort algorithms that bypass this method and call drawOneBar directly due
// to the logic within that algorithm. Quick, Merge, and Insertion sorts do this in certain cases
Dim temp as Int32

#pragma BackgroundTasks False
#pragma BoundsChecking False
// Swapping values in the i and j cells of the barsToSort array
temp = barsToSort(bar1)
barsToSort(bar1) = barsToSort(bar2)
barsToSort(bar2) = temp

numberOfSwaps = numberOfSwaps + 1 // boost swap count for this sort
if doAnimation then // bypass if no animation running
drawOneBar(bar1) // redraw first bar
drawOneBar(bar2) // redraw the second bar
end if
End Subroutine

drawOneBar(theBar As Int32)
// This method redraws one bar the needs to be moved during the sorting process.
// The element number of the array pertaining to the bar is passed as the argument
Dim inc, xPos, yPos, yPosFull as Int32

#pragma BackgroundTasks False
#pragma BoundsChecking False
if doAnimation then // bypass if no animation running
inc = barWidth + spaceBetweenBars // width of bar and spacing
xPos = 3 + (theBar - 1) * inc // x pos for this bar
yPos = Canvas1.height - 1 - maxBarHeight // y pos for max bar height
yPosFull = yPos
sortPic.Graphics.foreColor = bkgndColor // set to bg color
sortPic.Graphics.fillRect xPos, yPos, barWidth, maxBarHeight // blank out any old bar with background color
yPos = Canvas1.height - 1 - barsToSort(theBar) // y position for new bar height
sortPic.Graphics.foreColor = barColor
sortPic.Graphics.fillRect xPos, yPos, barWidth, barsToSort(theBar) // draw new bar
//canvas1.Invalidate // NO ANIMATION if this is used instead of Refresh
canvas1.Refresh // get canvas to reflect the change
end if
End Subroutine

Canvas1.Paint event
g.DrawPicture(sortPic, 0, 0)[/code]

Without the whole code its hard to prove a speed up.

Quick responses:

In DrawBars change .refresh to .invalidate

in drawOneBar change .refresh to .refreshrect(xPos, yPos, barWidth, maxBarHeight )

The reason invalidate isn’t working in drawOneBar is the use of the pragma to disable background tasks

Another possibility worth thinking about:

Create a group2D theGroup
Add the bars as Object2D RectShapes.

In the Canvas paint event, just
g.drawobject theGroup

Instead of creating and drawing rectangles over and over, just change the x,y co-ordinate of the two bars you want to swap, and invalidate the canvas.

If I use invalidate instead of refresh no animation takes place, even if I comment out the pragmas at the top of the routine. That is why I used the refresh as it is done immediately.

In regards to refreshrect that can only be done in the paint event as it is a canvas method and the DrawBars and DrawOneBar are drawing to the Picture Graphics which has no refreshrect method.

In so far as the group2D I will have to read up on that as I have never used them. As I stated in the opening post, this is the only attempt at animation that I have ever tried and back in those days you could just paint on the Canvas from wherever you wanted and that is exactly what I did. In fact, this is the only project with a canvas that I have ever attempted. Done plenty of printing to the printer but that is a totally different animal.

That means it’s not running in a thread? Does this animation lock the app up while it runs?

The code doesn’t look too complicated. Have you tried the built-in profiler or Instruments to see where most of the time is spent. Usually, when I do this I get very nice surprises.

I feel you can either have a smooth animation or a fast sort, but not both.
(If you want smooth updates, then by definition you have to interrupt the sort to draw the bars.)

I wouldnt be surprised to hear that there are pragmas dotted around in code we can’t see above.

How many (maximum) bars are there, by the way?

When you have a tight loop then it makes sense to call the refresh once per outer loop, rather than perhaps at every swap

for x = for y = next refresh next

or even every iterations

for x = for y = next if x mod 10 = 0 then refresh next

Jeff, even if there were pragmas in other locations it is my understanding that the Pragma only affects the method where it exists.

In so far as the number of bars that can be varied. Originally there was the capability to set bar widths from 1 to 5 pixels each and the space between bars from 1 to 5 pixels. When I saw how much slower it runs in Cocoa I quickly changed both of those settings to have a range of 1 to 10. So if you have bars 1 pixel wide and space between bars of 1 pixel there are 1255 bars on my 30 inch cinema display. If you set the bars to 10 pixels wide and 10 pixel space between bars there are 125 bars.

Another curios thing is that if this is run in windows it is a whale of a lot faster than even the Carbon version was.

Tim Hare…yes, then animation locks up the app other than the fact that it will catch the escape key being pressed as a means to cancel the sort if you are having a problem staying awake to see it finish. :wink: :wink: The locking up of the app doesn’t bother me as the whole intent of the app was to watch what is going on within the sorting algorithm as it gets the bars sorted. It’s just the fact that Cocoa is 7 times slower than Carbon and both Carbon and Cocoa are way slower than Windows.

I may just have to shrink the window size so that there are only a couple of dozen bars to sort but then you don’t get a really good look at how the algorithm does the sort.

Beatrix, I will give the profile a shot. More than likely the time will all take place in the paint event as it is drawing the picture to the screen. At least that would be my guess.

Well, I ran the profile and from it 69.40% of the time is spent in the Canvas1.Paint event and 30.48% of the time is in the DrawOneBar method. The other 0.12% of the time is opening the app, generating the starting array of values, copying that array to the array that will be sorted and any other start up goodies. So, the preponderance of the time is spent drawing the picture to the screen each time a bar is moved. I can probably achieve some speed up by making it a smaller window as I assume drawing a smaller picture takes less time…

I also notice that a Canvas has a RefreshRect method which updates only the given rectangle. Maybe I will try changing it so that the Paint event can test whether it needs to draw the picture at the very beginning before the sort starts or whether it needs to only draw a single rectangle while the sort is in progress. I could probably do that with an isCurrentlySorting boolean. If it only has to redraw that one rectangle I would think that that would be far faster than drawing the entire picture. It also sounds from the wording that the RefreshRect method will show the animation as the sort is progressing. Will give this a try.

I don’t know if this will help but… - for the canvas I always build my own redraw event. The paint event just paints a cached image that’s a property of the subclassed canvas. This was initially to prevent flickering on windows but might also help with things on Cocoa. You can save separate layers into their own cached images (background, foreground) etc… so you don’t need to rerender these if they dont change. Then the paint event just draw’s the cached layers.

I’m also unsure if doing a check to see if components are outside the view of the canvas and skipping their drawPicture event might speed things up. I don’t know if the graphics.DrawPicture event already has a good optimization for this? ~Anyone know?

Well, I changed the Canvas1.Paint event to the following:

[code] // If a sort is in progress then draw the bar that is moving otherwise
// draw the entire picture with all the bars ready to be sorted
if sortInProgress then
Dim xPos, yPos, yPosFull As Int32

xPos = 3 + (barToDraw - 1) * incrementWidth
yPosFull = baseYValue - maxBarHeight
g.ForeColor = bkgndColor
g.FillRect xPos, yPosFull, barWidth, maxBarHeight
yPos = baseYValue - barsToSort(barToDraw)
g.ForeColor = barColor
g.FillRect xPos, yPos, barWidth, barsToSort(barToDraw)

else
g.DrawPicture(sortPic, 0, 0)
end if[/code]

The problem is that when the sort starts all of the bars except for the one being moved disappear. Be darned if I know what is causing that but there is certainly something that is erasing the canvas. So as it is now, prior to the start of the sort a canvas refresh is invoked and the boolean sortInProgress indicates that no sort is in progress and so the DrawPicture is executed and the bars all appear nicely on the screen. When I choose the sort to be executed the boolean is set to true and then this code draws the bar that is being moved but something is causing a total clearing first but no code related to drawing is invoked except in the paint event once the sort is chosen.

Always draw on sortPic

In the paint event,
g.DrawPicture(sortPic, 0, 0)

You can use refreshrect to hint that only a small part should be painted. (as per my first post)

Check whether erase background is set to false in the canvas.

Jeff, I really appreciate your help since I am an absolute novice with this graphics stuff. I went back to the original code as posted at the start of this post and I change the Refresh to RefreshRect at the end of the DrawOneBar method. However, it took two RefreshRects to get the bars to look proper. With only the RefreshRect at the end it was only drawing the BarColor each time. Consequently if it draw a shorter bar where a taller bar had been the height of the bar, on screen, did not change. So higher up you see another RefreshRect which is drawing the background color to totally void out the old bar. I tried that bottom RefreshRects with both True and False for the EraseBackGround parameter and it made not difference so I went with the two RefreshRects and put False in both. The code now looks like:

[code]DrawOneBar(theBar As Int32)
// This method redraws one bar the needs to be moved during the sorting process.
// The element number of the array pertaining to the bar is passed as the argument
Dim inc, xPos, yPos, yPosFull as Int32

if doAnimation then // bypass if no animation running
inc = barWidth + spaceBetweenBars // width of bar and spacing
xPos = 3 + (theBar - 1) * inc // x pos for this bar
yPos = Canvas1.height - 1 - maxBarHeight // y pos for max bar height
yPosFull = yPos
sortPic.Graphics.foreColor = bkgndColor // set to bg color
sortPic.Graphics.fillRect xPos, yPos, barWidth, maxBarHeight // blank out any old bar with background color
canvas1.RefreshRect(xPos, yPos, barWidth, maxBarHeight, False)
yPos = Canvas1.height - 1 - barsToSort(theBar) // y position for new bar height
sortPic.Graphics.foreColor = barColor
sortPic.Graphics.fillRect xPos, yPos, barWidth, barsToSort(theBar) // draw new bar
canvas1.RefreshRect(xPos, yPos, barWidth, barsToSort(theBar), False)
end if
End Subroutine[/code]

It now takes about 60% of the time that it previously took for the sort to complete. Still not as good as Carbon but better than it was. I can see from the profiler that it is taking less time in the Paint Event so I have to assume that because of the RefreshRects the paint event is updating using the Areas array and not painting the whole picture.

One question though. When that Canvas1.RefreshRect executes just where is it taking the information from for what to draw. The only thing that currently has the proper information in it is the Picture that has had the changes made so I have to assume it is taking it from there. Like I say, I am a total novice when it comes to graphics. Especially now when I can’t just draw to the Canvas like I could in Carbon. Thanks for the help though.

The refresh or refreshrect causes the canvas paint to fire.

The paint event contains this:
g.DrawPicture(sortPic, 0, 0)

So whatever is in sortPic is drawn in the canvas by that line.

sortpic is updated behind the scenes and is static

in your code above you only need one refreshrect at the end, and it is this one: the full area of a bar, including the erased background area

canvas1.RefreshRect(xPos, yPosFull, barWidth, maxBarHeight, False)

Jeff, I thought sure that I had tried exactly what you are saying, more than once and it never worked; but, being an old timer I may be wrong…that happens more and more frequently. Not sure I ever had the False in there when it was doing the full height refresh.

Well, I created a new little project with only the comb sort and the parts necessary to do the animation. I was tired of having all the stuff to deal with in the much larger program and figured that I could wheel and deal a lot faster in a tiny project. The animation now works fine with just that one RefreshRect as you suggested. My original was taking about 105 seconds to sort the 125 bars. The incorrect usage of two RefreshRects had it down to about 65 seconds. Now with just the one refresh it is in the neighborhood of 40 seconds and this is running from the IDE.

I really appreciate your help with this Jeff. I still don’t fully understand what is going on behind the scenes in that paint event. It seems that by doing a RefreshRect it has filled in an element for the Areas array parameter of the Paint Event and that makes the paint event paint only that one rectangle (bar) and not paint the whole sortPic picture. Painting the whole picture is only needed at the beginning of a sort to present the unsorted bars. Thanks again. I can now chalk this one up as ready for Cocoa.

Hi Harrie,

Just a couple of friendly suggestions:

Make a Thread-Timer pair. The thread is able to fire at several thousand times per second and should be used to perform the hard and lengthy calculations. Set the Timer to fire at about 30 Frames-Per-Second with the drawing graphics routine ( all math should have been performed with the thread). This should dramatically lower the sort-routine time.

The above usually works best with a picture buffer. This is where the program can draw the picture in any part of the program, and then the picture updates the graphic layer of the canvas.

Alain has some great tutorial information on his website at Always Busy Corner. I think his RB/Xojo tutorials start around September 2011 and go to this month.

There is also a Xojo Canvas book available - and it is at the intermediate/advanced level at Xojo Canvas Control

If you want to make it faster, have you tried developing whilst seated on a high-speed train?
Hope that helped?

Sorry - couldn’t resist :slight_smile:

[quote=121232:@Richard Summers]If you want to make it faster, have you tried developing whilst seated on a high-speed train?
Hope that helped?

Sorry - couldn’t resist :)[/quote]

Richard, you are abusing this special tea again :wink:

It’s Camomile and Honey!