Batching GUI elements

Hi,
I am rolling my own GUI as an experient. I want it to be really simple and run flawlessly on mobile devices.
I noticed that currently each panel/image (Geometry in principle) that I have is rendered as separate object, which kills the framerate on mobiles. So I would like to know how other GUI authors overcome this?

Thanks!

Lemur does the same thing… though I guess folks haven’t really had a problem on mobile with the kind of simpler UIs there.

BatchNode should still work for Lemur though I haven’t tried it. Might work for you, too.

You’d do well to take a look at some of the other Lemur sub-modules but especially picking. Lemur handles multitouch in a way that I think is more elegant than any of the other frameworks.

Hey, thanks for reply!
I have looked also into Lemur for inspiration, but for learning purposes I just decided to go from scratch and only reuse if I really get stuck (probably will reuse Lemur’s multitouch picking anyway).
In any case I simplified my wishes to 2D so I am currently making a game in GUI node and I figured that rolling my own GUI would be the most sane thing currently. :smile:

Have just tried BatchNode and must admit that it works a bit strange. Even though I create new material instance for each of my panels and assign different colors/textures to them, they still get batched together (I usually get just one big black or randomly colored quad instead of separate colored quads). Also, how to solve picking when geometries are batched?

Thanks!

Batch node still leaves the geometries separate so picking should still work. At least in theory.

As to the other, I don’t know why it would batch stuff that is using different parameters. In fact, there used to be the opposite bug where it wouldn’t batch things even if they had the same parameters.

Else I cannot say why in your case it’s not working.

Ok I think I have found the problem.
If material property is changed after the node is batched already, it won’t be rebatched on second batch() call.

I made a simple test case to demonstrate that:

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.scene.BatchNode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.shape.Quad;

public class TestBatching extends SimpleApplication {
    private static final float SCALE = 50f;
    
    public static void main(String[] args) {
        TestBatching app = new TestBatching();
        app.start();
    }

    @Override
    public void simpleInitApp() {
        Mesh mesh = new Quad(1, 1);
        
        Geometry g1 = createPanel(mesh, ColorRGBA.Green);
        g1.setLocalTranslation(cam.getWidth() / 2, 0, 0);
        
        Geometry g2 = createPanel(mesh, ColorRGBA.Green);
        g2.setLocalTranslation(cam.getWidth() / 2, SCALE, 0);
        
        Geometry g3 = createPanel(mesh, ColorRGBA.Green);
        g3.setLocalTranslation(cam.getWidth() / 2, SCALE * 2, 0);
        
        BatchNode node = new BatchNode("geomNode");
        node.attachChild(g1);
        node.attachChild(g2);
        node.attachChild(g3);
        node.batch();
        
        g3.getMaterial().setColor("Color", ColorRGBA.Blue);
        node.batch();
        
        guiNode.attachChild(node);
    }
    
    private Geometry createPanel(Mesh mesh, ColorRGBA color) {
        Geometry geom = new Geometry("geom", mesh);
        geom.setLocalScale(SCALE);
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", color);
        geom.setMaterial(mat);
        return geom;
    }
}

Note the second batch() call will not show blue quad but will still show all quads in green color.
Should the behavior of BatchNode be changed to support this use case?

I don’t know. I’m not that familiar with the batching code but that might be a “bridge too far” as they say.

Maybe any idea on how to debug batching?
I am kinda not able to get the list of batches from node.
I am not even able to implement my own BatchNode, since the existing one accesses some fields and functions of Geometry class that are package private. I am trying to batch based on actual instance of the material instead of comparing all Material properties with contentEquals() function, so I could manually control that.
For now I extended Material class to override this function, but still looks like it is batching some things in a wrong way. Probably because not all materials in a scene are actually extended - BitmapText for example.
Could we update BatchNode to provide extensible method for material comparison?

I’m not sure it’s needed. For one thing, you are kind of grasping at straws to find your issue and it could turn out to be unrelated.

Can you put together a simple test case that illustrates the issue?

Changing the material of a batched geometry is not supposed to be supported. And actually when you change the material, you should have an UnsupportedOperationException. Here…since you change the color of the material, there is no real way to detect the change anyway.

If there is no change in the already batched geoms in the subgraph the batchNode won’t rebatch them when you call batch()(it will only append newly added geoms to the existing batch)

That said, you can force a geom to be marked as “rebatch this” using the unassociateFromGroupNode() method.
Note that it will rebatch the whole subgraph though.

So basically :

...
        node.batch();
        
        g3.getMaterial().setColor("Color", ColorRGBA.Blue);
        g3.unassociateFromGroupNode(); // <- here we go
     
        node.batch();
...

And it will work.

Sure will try and provide a test case.

I mean, my issue is that when using BatchNode for GUI parent node, batches are created in a weird way. I would like to have more control over that (for example to define exactly which geometries in the tree of nodes will get batched and which will be left alone). Maybe I am using the wrong tool and BatchNode is actually not the solution for this. My use-case is kinda: have a whole tree of nodes/geometries that correspond to GUI layout, have them batched automatically according to the knowledge that I provide. So don’t batch dynamic parts of GUI or parts that will later change color/material, just batch the static parts (back panels, static indicators, background).

My example:

I have this GUI with 3 panels, each is a node with more elements attached (name, creature picture, properties, etc.).
Each quad is kinda its own object. Each creature is random and it can change with time.

What I would like is for example to batch only these white-transparent back-quads behind the text, because they never change. So go from 3 x 4 = 12 draw calls to just 1.

But after I batch the root node of those panels I get this result:

Note that some properties (like creature texture and selection quad) are added later after other geometries are already batched.

Maybe I am not supposed to batch different geometries from different node subtrees? Or maybe the batched geometry mesh doesn’t take into account local transforms of geometries in subnodes?

What do you advise to solve this problem? :smile:
Thank you!

If you want to control what is batched an what is not batched you can use the batchHint on a spatial.
Inherit wil use the parent batchint, never will never batch, always will always batch.

So you could mark the dynamic parts of the UI as BatchHint.Never.

Heheh, thank you nehon, tried it and it works fine! :smile:
Well, almost fine in my case. :smile:

The result of batching those white-transparent text backgrounds:

The layering is obviously mixed up now, but this could as well be my fault, because the batched geometries become new children of the node for which I manage the Z coordinate. I suppose even using Lemur’s layering system wouldn’t help here?

Also for future adventurers in this direction, picking doesn’t work as well on batched geometries. :smile: Probably would need special handling for that also, to somehow find the “actual” clicked geometry.
Any idea how to implement that?

Thanks!

In my opinion, if BatchedNode isn’t passing collideWith on to the real children then it’s broken.

I know there’s a way to use BatchNode that deletes the children after batching… making sure you aren’t doing it that way? It might be nice to see a little code to confirm and in case it jogs @nehon’s brain. :slight_smile:

Actually there isn’t. I was planning to do it at some point, but I didn’t and never will. The original geometries are now used as references for transformation (like the bind pose of the skeleton).

Ok… I haven’t looked at the code… what is collision testing done against?

It’s done like in a regular node, the problem is that the batch geometry is not excluded from the pick so you might select it… idk tbh.

So the solution would be to override collideWith() method and pass it to original geometries?
Sorry I don’t know how skeleton bind poses work …

You don’t have to. Node’s collideWith method will call collideWith on all the Node’s children.
In case of the batchNode those children are : the batches’ geometries, and the original geometries.
The thing is, the batches’s geoms overlap with the original geoms so you might get them first (or not) in your collision result.
You can identify a batch geometry by its name. It will be of the form <BatchNodeName>-batch<number>.
You can check the name of the geometry in the collision result and exclude those that match this pattern.

Is there a logical reason to do twice the work? Or should we just do collidesWith against the original geometries? (Or worst case, make it optional + off by default.)

No reason… It’s just never been implemented. We could override collideWith and exclude the batch georms.