When a webcanvas is drawn into (using the paint event graphic object), why is it much quicker the first time (i.e., on a blank canvas) than when the canvas is already populated, at least for images that are significantly different from one paint event to the next? I read somewhere that to reduce data transfer, the framework compares the old list of draw events with the new and only sends the differences - is this comparison slow with complicated images?
I’m using the latest Xojo on Windows 7 64bit. You can see the effect in the following test project, which paints 50, 100, 150 or 200 squares randomly on a webcanvas. When painting first time, 200 squares don’t take much longer to appear than 50 squares, but after that, the more squares there are on the canvas, the slower the next image (whether 50 or 200 squares) takes to appear in the browser.
I suspect you are correct about the cause of the delay. You can eliminate it by clearing the entire canvas first:
g.ClearRect(0, 0, g.Width, g.Height)
That obviously won’t work if you have to retain the previous drawing. I’m not sure if there’s a work around in this case.
If there isn’t and that’s the problem you’re running into, consider jCanvas in my Web Custom Controls tool kit. The actual code to draw the rects is slower. But there is zero penalty for drawing on top of previous drawings. And if you have complex drawings which frequently change, you can treat it as a vector surface and only manipulate the shapes that change. This potentially can be a lot faster depending on the drawing.
My kit includes a class which allows you to treat a jCanvas similar to a desktop Xojo canvas. When the update with jCanvas was released the WebCanvas control had not yet been added to Real Studio/Xojo. For maximum speed and flexibility I would recommend just dealing with the jCanvas object directly.
Calling ClearRect does not clear the instruction cache. Keep in mind though, if you’re displaying random squares and every one of them changes with each refresh, that’s the worse-case scenario for our diff calculation. lets say you have 50 squares. that’ll be 50 removals (for the old ones) and 50 additions (for the new ones)
On the other hand, if you’re changing just one of those squares, you probably get a single replace (which would equate to a single instruction)
Ok that makes sense. The rectangles example is a worst case, but what’s odd is that I was plotting two charts, one large one (~50 stacked columns) that would highlight a single column when clicked (<0.5sec), and another that was a simple line chart (3-4 data points) that was slow (3-6 secs). (The code run times are always <0.05sec). It appears that there are cases where the diff calculation has problems. Is the diff calculation a standard Linux diff? I may have a closer look at the data package sent to the browser to see in what cases the intrustion cache is too different from that of the previous paint event. I’ll try to do that and report back.
In any case, I have sped things up by setting a property to cause the paint event to blank out the canvas (after invalidating it) and then a timer to allow the update to be sent to the browser before a second invalidate sends the final image. I.e., the diff calcs are between old chart instruction cache<->blank and then blank<->new chart instruction cache.
I will probably put in a feature request to turn off the diff calc in the webcanvas for times when it would be faster without it.
It’s not a linux diff. We wrote one specifically for dealing with the WebCanvas so it would be cross-platform, but also so we could serialize the diffs. From what you are telling me, it doesn’t sound like you should be having a problem, but keep in mind that if things get out of sync (like diff 4 arrives at the browser before diff 3 does) the browser will request that the web app send the entire script again to sync everything up again.
Daniel, I’ve updated the test project with a checkbox that, when set, forces the canvas to erase before repainting (the first invalidate does nothing in the paint event, then a short webtimer fires to invalidate again to paint the new set of rectangles). You should see a speed improvement.
I’ve also added another button for 300 random rectangles. To refresh 100, 200 and 300 rectangles takes 0.8s, 6s and 20s (!) respectively. The initial paint to a blank canvas is under 1 second for all three.
Greg, what do you mean by “serializing the diffs”, please? I had assumed that the framework waits for all my code to finish, then sends across all the paint instructions (or rather, the diff between the new and the previous instructions) in one batch.
I’ve settled on the following solution to deal with my speed issues: put the canvas in a webcontainer and replace it with a new one everytime I want to repaint my chart. No webtimers needed. Here’s the test2 example:
I wasn’t using a timer, just calling ClearRect first thing in the Paint event.
Using your new project I do see an improvement. Drawing 300 rectangles to a blank canvas takes just under 1s. Drawing with the “Erase first” option takes 3.5s. Note that I’m going off a stopwatch and not the seconds field. There is a difference between what it reports and the actual visible appearance of the rectangles. (Naturally it can’t capture what happens after the Paint event exits.)
Sounds drastic, but does seem to work. You should file a Feedback request to add something like a ClearCache method to WebCanvas. When the Paint event knows the canvas is going to radically change, ClearCache could eliminate the whole diff process.
I mean that each complete paint event needs to be sent in order when doing diffs. If you were to send 1, 2, 3, everything would be fine, but sometimes when sending data really fast, they arrive at the browser in the wrong order. It’s a latency/internet thing.
While I’m at it, I’ll add a request to be able to draw horizontal and vertical lines of one pixel. At the moment, due to the wierdness of HTML5, one can only do 2 pixels width at best due to anti-aliasing:
I have invested significant time into creating a custom control to manage scheduling data (due to another bug where labels text simple does not show… grrr) only to find mysteriously it takes at least 10 seconds to refresh when invalidated from all but one source.
If I click on my date picker (which fires a complicated SQL query back to the DB server (mysql) before invalidating the canvas) the whole thing runs lickety split - ie 0.2 seconds (this includes the query and the refresh!).
I tried moving the query inside the paint event and mixing up code and it still takes 10 seconds everywhere but the date picker.
All I am doing is changing a value in a text field, hitting a “refresh” button which moves the value into a global variable. When the canvas is invalidated it uses this variable to calculate where things need to be.
WebCanvas uses a diff algorithm to minimize the number of commands that need to be sent to the browser, but data still needs to be sent. You’ll have he best luck if the drawing sequence remains as much the same from refresh to refresh with minor changes for updates as possible.
have you profiled your code to see where the time actually goes?
I was actually going to ask about profiling in a new thread. Do you have to have a valid licence for it to work? I am paid up to 2014 r1.1 and frankly will not buy 2015 r1 until I am happy there are no bugs affecting my work.
I have profiling checked but nothing happens after I stop the web app. I saw the blog article on it but after following it nothing.
I then tried opening the project in 2014 r1.1 but it bugs out and throws exceptions. Am I missing something simple with the profiler, I have used it many times before!
Also in regards to your reply Greg, thankyou I understand the diff algorithm comes into play but seriously all I am doing is adjusting the number of columns/rows if the recordset returned from the DB is different. I have read that drawstring has a huge time penalty also. But in saying that why does it load quickly to begin with? My db is on the same LAN also so serial issues should’t be a huge concern - or should they?
OK so after realizing the issue with the profiler… here is what I get
Firstly I realalised why I click the date picker and everything happens fast. Its because although I am running an sql query the canvas is not updated. When I started sorting data into the canvas, thats when speed dropped off, probably as mentioned above by Greg due to the invalidate algorithm.
Here is what I dont get. The profiler tells me that wpStaffSchedule.cnvSchedule.Paint only took 31ms but the ccDatePicker.lblCalButtons.MouseUp takes 35750ms! If I move invalidate out of the mouse up it drops to an insignificant time, ie mouse up is taking so long due to invalidate but profiler is reporting paint is just 31ms??? Where has my 35719ms gone?