FLine Performance

Field's on-canvas drawing system — mainly based on the 'FLine' class and associated methods is a highly optimized system for displaying geometry and images. But sometimes, when dealing with a lot of geometry, it's important to understand some aspects of it's performance.

It's possible in Field to display drawings with around 1 million nodes while maintaining tolerable redraw speeds in the canvas. These kinds of geometric loads are well beyond what, say, Preview can tolerate and remain interactive (although, of course Preview's renderer is ultimately more accurate). This means that it's possible to produce PDFs that crush Preview (in our experience they still make it out of the printer just fine, OS X's rasterizer is quite robust). But it's also possible to generate drawings in Field with 1000 nodes that cause it to become miserably slow (while Preview chugs along).

Two things are 'expensive' in Field:

  • Animation — new material, especially new curved, stroked and filled material, is expensive. The system is optimized for situations where most geometry remains the same between frames. But you should pay only for the geometry that changes — everything else should be quite inexpensive. In general for "artwork quality" interactive drawing, you should be doing this on a different canvas (for example the ProcessingPlugin, or Field's internal graphics system).
  • FLines — by far easiest way to get Field to slow down is to have a 10000 small FLines where 10 large ones would do.

This last point bears some elaboration:

_self.lines.clear()
maximum=50000
for n in range(0, maximum):
    line = FLine().moveTo(*Vector2().noise(100)).lineTo(*Vector2().noise(100))(derived=1, color=Color4(0,0,0,0.1))
    _self.lines.add(line)

with maximum=50000 on a Mac Pro with a decent graphics card, Field is beginning to get irritatingly sluggish. That's quite a few lines, but nothing unreasonable:

If we rewrite the above code differently:

_self.lines.clear()
maximum=50000
line = FLine()
for n in range(0, maximum):
     line. moveTo(*Vector2().noise(100)).lineTo(*Vector2().noise(100))(derived=1, color=Color4(0,0,0,0.1))
_self.lines.add(line)

We get the exact same drawing, all contained in one FLine. Remember a FLine can contain disconnected segments. It renders at the same perceptual frame-rate as an empty canvas. In fact, although it will take 20 seconds or so to compute, we can easily raise maximum up to half a million without really changing our frame-rate. If all that happens is that this geometry sits there while we work on something else, we might forget about the million nodes over in the corner.

But not all programs can so easily be transformed from multiple-small FLines to a few-large ones. To automatically coalesce sets of FLines together, add:

_self.optimizeLines()

to the code in your box. With this line added our first example is indistinguishable from our second.

What's really expensive then isn't geometry that can be coalesced in this fashion, it's style. _self.optimizeLines() groups together lines that have the same properties (color, stroke, thickness &c). If every short line has it's own unique thickness and color then _self.optimizeLines() can't do anything — there's no grouping to be done. This means that if you are running into performance issues even after _self.optimizeLines() you should think through the range of state you have — can you quantize your line thicknesses? your color space? — to allow large groups to form.

Over the next development cycle we'll be looking to add and extend the intelligence of optimizeLines() to help with these cases. Currently, you should note, that this method does nothing for filled geometry and that this method should not be called on self-intersecting transparent geometry that is going to be exported to PDF.