Recently we’ve noticed a bit of sluggishness creeping into the LiveCode engine, particularly when it comes to graphics. Over the last few weeks I’ve been working with Michael to investigate and put into action some ways to get the engine back up to speed and get your apps whizzing again.
Looking into this problem, we’ve identified one of the big contributing factors as the introduction of support for high-density displays – where previously an app on the iPhone may have covered an area of 320×480 pixels, on a device with a retina display it now covers 640×960 pixels – that’s 4 times the number of pixels that need to be drawn when updating the screen! This is even more noticeable on mobile devices, where the processing power can’t match that of the desktop.
So what can we do to speed things up a bit? One option is to go multi-threaded. A thread is a block of code that can be made to run independently of the main program. By splitting a task up into multiple threads, different tasks can be made to run concurrently rather than one after the other. Most modern processors have multiple cores – that is multiple separate CPUs that are each capable of running a single thread all at the same time. We can take advantage of this by splitting up the job of redrawing the screen into several smaller tasks, i.e. by splitting up the area that needs redrawing and giving each smaller area to a separate thread. This promises to deliver a pretty big speed-up when redrawing.
One other option that I’ve been working on involves making sure we don’t do more work when drawing than we need to.
There are many ways to try and make computer programs work faster. You can try to find inefficiencies in your code that make things slower than they need to be – perhaps you have a loop in which the same value is computed repeatedly, in which case it may be more efficient to compute that value once and store it in a variable to be referenced later. Another strategy is to make better use of the resources available – the example being the use of multi-threading discussed above. However, if you know a particular operation is going to take a long time maybe you can find a way to avoid doing it altogether, or as little as you can manage.
In recent years we introduced a couple of new graphical features that allow you to add color gradients to graphic objects and graphic effects to everything. These features both require a lot of computation to draw, and as such they can be the slowest to draw when updating the screen. We spent quite a lot of time and effort on making these as fast as possible, but even then they can slow things down considerably when the area they cover is large.
So let’s say you have a card with a nice gradient as the background. You click a button that performs some operation then updates the contents of a field and maybe toggles some other buttons on or off. If this all happens as a single operation then each of those objects affected will have to be redrawn at the same time. Whenever a change happens to an object that requires it to be redrawn (changing the color of a graphic, or updating the contents of a field for example), its enclosing rectangle will be added to the redraw region.
A region is a graphical programming object that collects together multiple rectangles as a single area and allows several useful operations to be performed on that region. You can add or exclude rectangles from the region, or combine that region with another. This makes them pretty useful for defining the areas of a scene that we need to redraw.
Once we know what areas of the window need redrawing the engine will then tell the windowing system (the part of the operating system responsible for handling windows) that we want to redraw, after which the engine will receive a message back telling which areas of the window to update. This allows the OS to have areas of the window redrawn if for instance another window is passed over ours.
Now we need to draw to the window in the area given to us by the OS. In order to prevent flickering as different objects are drawn we use double-buffering. This is a technique where an offscreen image (the buffer) is created into which we draw, after which the complete image is then drawn to the window, so only the end result of the redraw operation is seen.
How then do we reduce the amount of redrawing that gets done? Well, previously the engine would confine the drawing area to a rectangle by setting the clipping rect of the graphics context that is used to draw to the buffer. The old graphics context would only allow the clipping area to be set to a rectangle, so if the area being redrawn was composed of smaller rectangles with lots of space between them, there was no way to prevent those unneeded areas between from also being drawn into. By using the new graphics context introduced by the graphical refactor, and updating it to accept a region as the clipping area, we can now ensure that only those areas that need updating are redrawn, with those areas in between getting skipped over.
Going back to our example, we can see that previously a much larger area (the rectangle encompassing all the affected controls) would have been redrawn, whereas now only the much smaller individual areas covering the affected controls will be drawn. For a stack with an expensive (in terms of time needed to draw) background this provides a significant saving in the time spent updating the window.
This was a pretty satisfying job for me – I got to update some more of the older code in the engine, replacing lots of platform-specific region code with a single region object that could be used on all platforms – and one that was successful in improving the experience of users of LiveCode developed applications.
read more
Recent Comments