Dustin's Pages

Wednesday, October 3, 2012

JavaOne 2012: JavaFX Graphics Tips and Tricks

I returned to the Hilton (Imperial Ballroom B) to see Richard Bair's (Oracle Java Client Architect) "JavaFX Graphics Tips and Tricks." Bair is associated with FX Experience and obviously knows JavaFX.

Bair said a theme of his talk is on performance. He cautioned that as with most things performance related, avoid performance pre-optimization. He had a big yellow caution screen stating "WRITE CLEAN CODE, THEN PROFILE!" He said his talk is based on JavaFX 2.2 and some of the tips and tricks may not be applicable with JavaFX 8.

Bair covered the "GUIMark 2 Vector" benchmark for several browers on three different operating systems (versions not specified): Windows, Linux, and Mac OS X. Bair compared JavaFX to these browsers' native support. He also pointed out that sometimes SceneGraph is faster and sometimes Canvas is faster. Many of the points Bair brought up are more important on smaller devices than on desktops.

JavaFX was much quicker than the browsers in GUIMark 2 Bitmap and JavaFX Canvas was the quickest of all. The GUIMark 2 Text test did not provide useful data for Windows due to limited rate, but JavaFX did well on Linux and Mac OS X. Bair intends to release benchmarking approaches for public consumption and he showed a chart indicating significant performance improvement from JavaFX 2.2 to JavaFX 8.

Bair's Performance Rule #1 is "Do Less Work." Bair stated, "Smaller systems require a much more intense round of performance tuning." He added that "every line counts" and "extra method calls add up." Although in traditional desktop Java we've been taught not to worry about number of methods calls, this can be an issue on smaller devices ("excessive inlining is expensive" and "excessive method invocations are expensive"). Bair showed how to use a local final variable to reduce the number of method invocations. He acknowledged that it is "absolutely micro performance pre-optimization" on the desktop, but is a useful tactic for smaller devices.

Bair said that "fill rate" is a limitation with "nearly 100% certainty." Geometry rate is unlikely to be a significant limit in JavaFX unless you have "zillions of vertices." CSS overhead is a possible limitation as is layout computation. JavaFX does a lot of caching and the latter may not always be an issue. There is a "good chance" that system I/O will limit you, especially on smaller devices.

Bair showed an example of "abusing the fill rate" by drawing the furthest back background first and then drawing over the majority of that with another fill. He had some points for avoiding this unnecessary filling such as "only draw what has changed." Bair pointed out that the developer identifies "dirty regions" in Swing, but that JavaFX SceneGraph "does this automatically!" He did caution that JavaFX Canvas requires the developer to identify the "dirty regions."

Another approach for improving fill rate is to "limit use of (some) effects." "Effects are almost free on your desktop systems," but may need to watch them more closely on smaller devices. Bair discussed a bullet stating, "Limit use of non-rectangular non-axis aligned clips" as another tactic for improving fill rate. Directly clipping aligned images is quick, but the process of anti-aliasing, rendering as a background image, and rotating non-aligned pixel boundaries "costs you a little more" (but you won't notice on most desktop applications).

Bair stated that reducing overdraw is an effective way of improving fill rate. Related to reduction of overdraw, he discussed using "image skinning." Bair also mentioned here that JavaFX 8 includes automatic region texture cache. Other ideas for reducing overdraw include simplifying the style (Metro, Android), consolidating background fills, and reducing the number of overlapping nodes.

Bair stated that Microsoft intentionally came up with an easy-to-draw style in Metro. The Android style is similarly quicker and easier to draw.

"Occlusion Culling" allows avoidance of drawing (culling) things that won't be visible." Doing this allows us to "reduce overdraw and increase rendering performance." The JavaFX engine can respond to JavaFX CSS opaque insets to know when to not redraw these areas.

There are CSS costs to be aware of such as parsing a stylesheet. Bair showed a "CSS Horror Show" slide with .parent:hover .child {...} and explanation of why this is so terrifying: all the children must be revisited each time the parent is hovered over. Similarly, .parent .child {...} can be bad if there is a large number of children since "when we encounter a node with the .child style class, we must walk up the entire scene graph until we find it." It is better to limit the search to immediate parent.

Bair stated that the setStyle CSS property is very convenient, but can be costly. The parsing and other support can add to performance problems. CSS provides power, flexibility, and convenience, but that does come at a performance cost.

One of Bair's tips is to "avoid structural changes to SceneGraph." All CSS from the changed point on down must be recomputed. Besides this reapplication of CSS, "structural integrity checks" are required when the SceneGraph is changed. JavaFX has optimized toFront/toBack, so use these rather than removing and adding back.

Another Bair tip is to "use FXCollections." His first bullet on this stated, "Shoot for minimal notification overhead." A sub-bullet recommended using setAll instead of clear and addAll. Another sub-bullet added, "Avoid multiple add calls."

Use of FXColections.sort() is best because it "sends 'permutation' change events." This means that JavaFX engine knows what has changed and so only recomputes what is necessary for that specific type of change. These "permutations" are "handled by separate fast paths."

Bair stated that "ListView is blistering fast" because it "reuses nodes" and maintains minimum changes. Bair concluded that slide with "Reuse ListView for all your virtualization needs!"

Bair's "Manual Layout" tip included the idea of custom extensions of Region. He cautioned that you almost always need to implement computePrefWidth and computePrefHeight when extending Region.

Bair had a slide listing the questions that "JavaFX asks" when handling layout. These are questions like "How wide/tall would you like to be?" and "Can you be resized?" JavaFX asks these questions at least once and sometimes many more times that number of times when trying to render a layout. A customized layout can reduce the number of times attempted and the number of questions asked. "JavaFX asks a lot of questions" and they are "all asked for each node during layout."

Bair had a "Major Tip!" related to "Content Bias." If height depends on width, you're HORIZONTAL biased. If width depends on height, you're VERTICAL biased. Bair stated that "(contentBias = null) is by far the fastest" (all computed preferences for height and width are cached). Content Bias is typically null or horizontal. There is a bug in that "contentBias != null isn't actually well supported in the built-in layouts."

Everything covered so far has been under Bair's Rule #1 (do less work). Rule #2 is now "Know Your Device." Bair showed a slide comparing the powerful NVidia GForce GTX 690 to the less powerful NVidia GForce 310 and to the even lowlier PowerVR SGX543MP3. Bair's point, of course, is that "JavaFX gives you a single development platform and a single set of APIs, but which APIs you can and can't use is going to depend on the inherent performance characteristics of the device."

Bair had some rules of thumb for JavaFX on devices. Use desktop application for handling 20 thousand to 100 thousand nodes. Five hundred to one thousand nodes is the better range for embedded. For really small embedded devices, stick to range of 100 to 200 nodes for the JavaFX application.

Bair provided another tip related to cache. He talked about caching a chart because it will then only be drawn to the image once and can then quickly be drawn to the screen "a bazillion times." Bair cautioned, though, that this "backfires if the node is changing a lot." Bair said that he'll often turn cache to true, do an animation, and then set cache to false again.

CacheHint can be set to SPEED when rotating and scaling for better performance. If you want it redrawn when it rotates for greater accuracy, then use cache hint other than SPEED.

JavaFX 8 has a Pulse Logger (-Djavafx.pulseLogger=true system property) that "prints out a lot of crap" (in a good way) about the JavaFX engine's execution. There is a lot of provided information on a per-pulse basis including pulse number (auto-incremented integer), pulse duration, and time since last pulse. The information also includes thread details and events details. This data allows a developer to see what it taking most of the time.

Bair ended the session with the same bright yellow caution slide: Write Clean Code, Then Profile! The slide also points out, "Don't overdo it or you will have an unmaintainable mess."