Flattening a node

JavaFX is a very good language due in large part to the scenegraph. There are Nodes, Groups, Shapes, Effects, etc… This allows you to create some really interesting graphics. Unfortunately, as you add more nodes to the scene, you take a performance hit.

A Decidely Simple Board

A Decidely Simple Board

For Clash (the real-time simulation game I’m working on), we need to create a board to play on. That means taking a set of tiles, and then putting them together on a board. This isn’t just a chess board though…we’re talking at a minimum a 100×100 board – or roughly 10,000 tiles. Each tile on its own might be a couple of nodes.

If you remember my BubbleMark test, you would know that performance drops off around 512 nodes or so… That means that if I were to have each Tile render individually on the Board (10,000 Tiles in total…), it would result in an epic failure.

But, heres the thing about the Board, once the Tiles are set, it never changes. It doesn’t scale – so basically, I don’t need to have all those nodes on the board…I just need one…the board’s image.

What I did to make this work was to essentially ‘flatten’ the Board into a single ImageView Node. This means that I drew the scene into a Group, rasterized that Group into a BufferedImage, and then used SwingUtils to import that BufferedImage into a JavaFX image.

The most difficult part of this was rasterizing the Group (or node) into a BufferedImage. The JavaFX forums helped me though. I essentially ripped what they had, and put the extra line in there to convert it back to a node.

function convertToImage(node : Node, width : Integer, height : Integer) : Node {
    var context = FXLocal.getContext();
    var nodeClass = context.findClass("javafx.scene.Node");
    var getFXNode = nodeClass.getFunction("impl_getPGNode");
    var sgNode = (getFXNode.invoke(context.mirrorOf(node)) as FXLocal.ObjectValue).asObject();
    var g2dClass = (context.findClass("java.awt.Graphics2D") as FXLocal.ClassType).getJavaImplementationClass();
    var boundsClass=(context.findClass("com.sun.javafx.geom.Bounds2D") as FXLocal.ClassType).getJavaImplementationClass();
    var affineClass=(context.findClass("com.sun.javafx.geom.AffineTransform") as FXLocal.ClassType).getJavaImplementationClass();

    // getContentBounds() method have different signature in JavaFX 1.2
    var getBounds = sgNode.getClass().getMethod("getContentBounds",boundsClass,affineClass);
    var bounds = getBounds.invoke(sgNode, new com.sun.javafx.geom.Bounds2D(), new com.sun.javafx.geom.AffineTransform()) as com.sun.javafx.geom.Bounds2D;

    // Same with render() method
    var paintMethod = sgNode.getClass().getMethod("render", g2dClass, boundsClass, affineClass);
    var img = new java.awt.image.BufferedImage(width, height, java.awt.image.BufferedImage.TYPE_INT_ARGB);
    var g2 =img.createGraphics();
    paintMethod.invoke(sgNode,g2, bounds, new com.sun.javafx.geom.AffineTransform());
    g2.dispose();

    return ImageView {
    	image: SwingUtils.toFXImage(img);
    	cache: true, smooth: false, fitWidth: width, fitHeight: height
    }
}

Please note that there is one significant drawback to this method. This method uses a lot of ‘implementation’ methods within JavaFX. These methods may not be here in the next release. So, I would recommend if you use this function, encapsulate it somewhere and know that you’ll need to change it when the next release comes out (potentially).

Now, as for the benefits for me, I get a massive speed boost. I’ve now rendered approximately 100,000 nodes into a single ImageView…sure it may be 2400 pixels wide and 2400 pixels long…but it works and is pretty fast. :-)

 

5 Responses to “Flattening a node”

  1. Couldn’t you just put all these 10,000 nodes inside a Group with cache:true set? Then JavaFX will render the thing only once, cache it to a bitmap, and deliver max performance… at least in theory; the only risk I see is that cache:true is documented as a “hint”, so I wonder if the runtime may not obey that hint in some situation.

  2. Drew says:

    I could, but then there is also an increase in memory as well, because then I need to keep 10,000 references to those nodes around…. That takes some extra memory that I really don’t want to waste. That said, I wonder if that will increase the performance of my Bubblemark test…

    [update] Seems as though you already tested that in your Bubblemark test :-) Good job.

  3. [...] own methods to save a Node into an Image. I have seen several methods do this, and I even wrote a post on how to do this. The bug is to add a feature to JavaFX so that hacks do not need to be implemented within each [...]

  4. [...] This entry was posted on Wednesday, July 15th, 2009 at 5:00 pm and is filed under Java FX. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site. …Page 2 [...]

  5. thank you. [...] own methods to save a Node into an Image. I have seen several methods do this, and I even wrote a post on how to do this. The bug is to add a feature to JavaFX so that hacks do not need to be implemented within each [...]

Leave a Reply