Graphics Performance (mostly Connectors 2.0)

Ned Konz ned at squeakland.org
Thu Dec 30 00:04:42 UTC 2004


On Wednesday 29 December 2004 2:16 pm, Michael Latta wrote:
> At the bottom of this message is a MessageTally on dragging a shape
> around with 8 connectors attached to it.  These connectors have no
> arrow heads, but spend a fair amount of time dealing with arrow heads.
> 1) Is there some way to optimize this case?

Actually, they do have arrowheads, it's just that the arrowheads are very simple (i.e. a closed square end).

It looks as if you have Connectors on PasteUpMorphs inside SystemWindows, is that right?
And others are just out in the World.

Are these connectors smooth curves?
It looks as if at least some of them might be.
Have you tried making them segmented lines and seeing if the performance is better (it should be)?
In my tests, it seems to take about twice as long to re-position/re-draw the curves,
even if they're not really curved.

So here's an obvious optimization: if there are only two vertices (the ends), draw smooth curves as segmented curves.
However, then the ends would also be drawn as a segmented shape.
If your ends don't really have any curves in them, that's OK (i.e. if they are all straight lines).
Perhaps I need a Preference for this?
Or we could just look at the end decorations and see if they have any curves.

When I try the same thing with segmented lines with no bends,
I see about 35% of the time being spent in NCLineMorph>>computeBounds,
and about 28% in Delay>>wait, 7% in the event loop,
and the remaining 30% split between flushing bits to the screen (13% or so),
extra logic in step (6%) and some other methods.

> 2) Are you drawing an empty arrow on these cases, and spending time
> building and possibly rotating an empty form, or a form with a short
> line?

I'm drawing a closed polygon or bezier curve (depending on whether the line is curved).
The end decorations just contribute their line segments or curve segments to the closed region.

The computeArrowForms is actually just for (a) more accurate bounds testing,
though I certainly could use a rectangle containing all the vertices and control points
of the arrow shape for that, and (b) hit testing,
but we could be lazy about computing the forms until we actually need them for that purpose.

It would be useful to make sure that the ratio of calls to computeArrowForms to movement is 1:1 (or whatever is the correct ratio).

I did this:

* make a Workspace; choose "create textual references to dropped morphs".
Drop the shape to which the 8 connectors are attached into the workspace to get its name.
In my case it was 'tr3756'.

* run this code in the Workspace:

MessageTally tallySends: [ tr3756 position: tr3756 position + (3 at 3).
World doOneCycle. ]

When I look at this, I see that computeBounds (the big offender) is being called 64 times (or is it 80?),
but there's only 8 connectors.
Is this right? I don't know, but it sounds suspicious and could be somewhere to start looking.
Clearly, if we could cut that down to 8 times it would be a big improvement.

  |    |  |  |  |    |  |    |  |  |  94 NCLineMorph(Morph)>>fullDrawOn:
  |    |  |  |  |    |  |    |  |  |    20 NCLineMorph>>fullBounds
  |    |  |  |  |    |  |    |  |  |      |32 NCLineMorph>>computeBounds
  |    |  |  |  |    |  |    |  |  |      |  |8 NCLineMorph>>updateArrowContours

later...

  |    |  |    |  |  16 NCLineEndConstraintMorph>>step
  |    |  |    |  |    |11 NCLineEndConstraintMorph>>applyConstraint:
  |    |  |    |  |    |  |8 NCLineMorph>>verticesAt:put:
  |    |  |    |  |    |  |  |24 NCLineMorph>>setVertices:
  |    |  |    |  |    |  |  |  |32 NCLineMorph>>computeBounds
  |    |  |    |  |    |  |  |  |  |8 NCLineMorph>>updateArrowContours
  |    |  |    |  |    |  |  |  |  |  |48 NCLineArrowGlyph>>openForLineWidth:
  |    |  |    |  |    |  |  |  |  |  |  |16 NCLineArrowGlyph(NCCurve)>>updateBounds
  |    |  |    |  |    |  |  |  |  |  |  |  |96 NCLineArrowGlyph(NCCurve)>>boundingBox


There's also 64 calls to layoutChanged (each of which calls submorphBounds),
80 calls to NCLineMorph>>arrowForms (all from computeBounds), in 2 groups of 40,
each of which group draws the arrows the 16 times that you'd expect), etc.

  |    |  |  |  |    |  |    |  |  |  94 NCLineMorph(Morph)>>fullDrawOn:
  |    |  |  |  |    |  |    |  |  |    20 NCLineMorph>>fullBounds
  |    |  |  |  |    |  |    |  |  |      |32 NCLineMorph>>computeBounds
  |    |  |  |  |    |  |    |  |  |      |  |8 NCLineMorph>>updateArrowContours
  |    |  |  |  |    |  |    |  |  |      |  |  |48 NCLineArrowGlyph>>openForLineWidth:


Now, I read the above as "though there are 20 calls to fullBounds,
we somehow have 32 calls to computeBounds".
In which case that might indicate that some unwanted recursion is going on.
Or that MessageTally is fooling me.

> 1) I had to comment out the delay at the end of each interaction cycle.
>   While this does mean the CPU is running at 100%, Unix can handle that,
> and even with high performance set it was taking 14%-30% in waiting on
> that delay.  With it disabled interaction is better, but still not
> really keeping up with the mouse.

What I do for debugging GUI performance is usually something like this:

MessageTally spyOn: [ 100 timesRepeat: [ tr3756 position: tr3756 position + (3 at 3). World doOneCycle. ] ]
 toFileNamed: 'messageTally.txt'.

> 2) It appears that Balloon draws to a form and then BitBlt the form to
> the screen.  Is this correct? 

Kinda. It does some internal caching.
Also, at the end of every drawing to the Display of morphs that use asBalloonCanvas there is a flush.

> It is spending a fair amount of time doing this copying of bits.

I didn't see that in my trace, though I do in yours.
Are these really long connectors, or ones with bends in them?
It could be that there is a really big area being covered.

> It looks like my next task it to think about line drawing and making
> that faster.  Optimizing arrow heads would also help.  Any suggestions
> on how to proceed would be appreciated. 

Well, the usual method... attack the slowest stuff first and repeat.

> A similar app written in 
> Objective-C is about 2x faster / more responsive.  The anti-alias of
> lines as lines rather than curves should be faster, than the current
> treatment. 

> For those shapes that are not being moved, it should be 
> possible to cache the form that Balloon is creating.  Would this work?

Yes; you could simply make a caching connector by adding a form.
But the form would have to be as big as the bounding rectangle of the connector, which could be at least as big as the screen;
that's a lot of memory. Or you could keep a collection of smaller rectangles that would cover the line instead.

If your drawings have a lot of connectors under other connectors,
perhaps making such a connector that caches its form in a collection of small rectangles might be the best first approach.

Or maybe (since these are bezier curves) you could just have N+1 (possibly overlapping) rectangles per N segments.
This would cut down the number of bits that would need to be moved,
if the DamageRecorder doesn't merge the rects on you (see below).

> That could reduce the drawing time for larger diagrams.  My test
> diagram has 3 connectors not affected by the drag.

If they aren't in an area that is invalidated by moving other connectors over them,
their drawing speed doesn't matter. 
If you turn on the Preference 'debugShowDamage' you will see the areas that are being invalidated and later redrawn.

Another thing that might help with both redrawing of static areas of the screen 
would be to invalidate smaller rectangles along the line.
However, you'd have to be careful, as the DamageRecorder will eventually merge 
the smaller rectangles so as not to redraw too much.
There's a balance between drawing the same Morph multiple times (clipping to different rectangles) and drawing more morphs once.

-- 
Ned Konz
http://bike-nomad.com/squeak/



More information about the Squeak-dev mailing list