Screen damage logic [was: Re: RE: Timers, Stepping, ...]
Stefan Matthias Aust
sma at 3plus4.de
Wed Mar 22 21:33:31 UTC 2000
I'm trying to catch up from the latest emails so please excuse me if this
has already been mentioned. Recently, I tried to figure out why Morphic
feels slower than MVC. I used a slow P100 to run a 2.6 image with deferred
updates disabled. I also listened to MP3 music (for example mp3.com,
Lu[c]id) which notable slows down the machine. Actually, it's a flickering
First, I thought that time is lost because Morphic is drawing things twice
but I didn't find an evidence. There are small problems with popup menus
and with scrollbars. The first is by design. I fixed the latter. But
they don't result in visible speedups.
Then, I looked at the damage recoder and the way it collects damage.
Actually it doesn't really collect damage. Morphic is very busy to redraw
damage immedately. I don't fully understand why. Probably, because all
SystemWindows redraw theirselves five times a second. Or because even
without events, #doOneCycle calls #doOneCycleNow (which redraws damage) is
called upto 50 times a second. So damage rectangles cannot accumulate and
very often, parts of the screen are redrawn which are invalidated afterwards.
There's one major problem with some classes: Often, morphs are first
inserted into the world or into other morphs and only then positioned.
This leads to an invalidation and redraw of the upper left corner of the
screen - or even worse - that region is combined with some other damage
rectange and so a larger-than-neccessary region is redrawn.
This happens with halos, for example. A workaround is to raise the damage
rectangle combination limit from 10 to 15. This still redraws a small part
in the upper left corner of the screen but will not combine everything into
one large region. The speedup is noticable.
The biggest problem are TextMorphs. The "self changed" in the morph's #fit
method to be exact. The comment already describes the problem. Even the
smallest change to a text morph will result in a complete redisplay.
This, I believe, is the reason why Morphic feels so slow.
Moving a cursor will not only invalidate and redraw the old and new
position of the cursor but also invalidate and redraw all text. Every time,
the cursor is moved, a character is inserted or the mouse is moved out of
or into a text morph. Unfortunately, the text morph code is really messy
and I don't see an easy way to fix it.
But once this is fixed (just try to comment-out the "self changed"),
Morphic should feel significantly faster - at least on slow machines.
BTW, for a Java application with a similar damage region handling,
splitting up the damage region of lines from the enclosing rectangle to
three smaller rectangles resulted in a small speedup.
>By simply inserting a couple of calls to
> self world displayWorld
>I was able to cause only a couple of tiny border strips (and none of the
actual text panes) to get refreshed and things ran *much* faster. Get it?
For SystemWindows, this is fine. But I don't understand most other sends
of #displayWorld which are scattered in the code. (Some more comments
would be nice ;-) I commented out most of them I noticed no difference.
(And for example #definePath and #followPath are evil because they
implement their own little loops which smell like polling) And
#snapToEdgeIfAppropriate is also evil, because that method is called often
and the operation triggered by #adhereToEdge: should already record the
needed damage. The explicit #displayWorld call will render all damage
region caching in the DamageRecorder useless, I think.
>You can see another case of this in the current system in
SystemWindow>>passivate. Here I discovered that the damage from
un-highlighting the title bar of the passivating window was getting merged
with the damage from activating another, and causing almost the whole
screen to repaint.
So what's about implementing a better damage recorder instead of clever
hacking? Instead of simply merging overlapping damage regions, I'd
recommend to find the larger rectangle and cut of the overlapping part from
the smaller rectange (using #areasOutside:) and to merge adjecting damage
rectangles if that side has the same length. Sounds difficult but probably
only I can't explain it better. The AMIGA used exactly this algorithm for
its Layer library which was the foundation for Intuition's overlapping
>[Hackers notebook: For anyone interested in damage reporting and
consolidation, note the lines
> false ifTrue: [ "*make this true to flash damaged areas for testing*"
> self flashRects: allDamage color: Color black].
>in PasteUpMorph>>displayWorld. If you change the false to true, you will
see a screen flash wherever damage is being repaired. It can be extemely
I found it even more enlightening to add a "Display flash: aRectangle" into
the #recordInvalidRect: method of the DamageRecorder.
>Back on topic: So if you are doing a lot of long diagonal lines, you
could put a displayWorld before and after and every 9 or 10 sub-rectangles
along the way, and thus avoid a complete repaint of your screen. Naturally
there will be tradeoffs that affect how fine to make the segmentation.
Veto. As one will probably not draw just one line but always a bunch of
lines, explicit #displayWorld calls will slow down the application. A
more clever damage recorder and a fast line clipping algorithm in Canvas
together with smaller damage rectangles will do a much better job IMHO.
Stefan Matthias Aust // Bevor wir fallen, fallen wir lieber auf.
More information about the Squeak-dev