Skip to main content

Why a large number of hidden shapes slows rendering?

3 replies [Last post]
paasiala
Offline
Joined: 2004-09-17
Points: 0

Why a large number of hidden shapes slows rendering?

We have noticed that when we create a scene that contains a lot of objects, the performance (FPS) depends on whether the objects are made visible or not. We have made a test where first a large number of objects is shown and then almost all are hidden. When a similar scene is done so that only some objects are ever shown, the FPS is much better.

Below is a sample for testing this behavior. When you launch the program, you are asked if you want to show all. If you answer Yes, the scene is created and all components are shown. After that another dialog comes, and when you have accepted that, most of the components are hidden. If you answer No to the first dialog, only a fraction of the components is ever made visible. In that case the FPS is much better.

When you first show and then hide the components, the number of RenderMolecule instances gets large and a lot of time is spent in methods called from RenderMolecule.render and only very little in Canvas3D.swapBuffers.

Below is the code.

Pasi

[code]
import java.awt.BorderLayout;
import java.awt.GraphicsConfiguration;
import java.util.ArrayList;
import java.util.BitSet;

import javax.media.j3d.Appearance;
import javax.media.j3d.BoundingBox;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.Geometry;
import javax.media.j3d.Material;
import javax.media.j3d.PointLight;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Switch;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.TransparencyAttributes;
import javax.media.j3d.View;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.vecmath.Color3f;
import javax.vecmath.Point3d;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;

import com.sun.j3d.utils.behaviors.vp.OrbitBehavior;
import com.sun.j3d.utils.geometry.Sphere;
import com.sun.j3d.utils.universe.SimpleUniverse;

public final class Java3dPerformanceTest extends JFrame {

private static final long serialVersionUID = 1L;

private static final int N_OBJECTS = 100000;

private static final int N_OBJECTS_VISIBLE = 2000;

private static final boolean TRANSPARENT = false;

private static final BoundingBox INFINITE_BOUNDS = new BoundingBox(new Point3d(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY), new Point3d(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY));

private static final int CAMERA_DISTANCE = 1000;

private static final int GEOMETRY_LEVEL = 16;

private static final int SWITCH_SIZE = 1500;

private static final int APPEARANCE_MAPPING = 13;

private static final int GEOMETRY_MAPPING = 10;

private SimpleUniverse universe = null;

private TransparencyAttributes transparencyAttributest = new TransparencyAttributes(TransparencyAttributes.NICEST, 0.5f);

private Appearance getRandomAppearance() {
Appearance app = new Appearance();
Color3f emissiveColor = new Color3f();
Color3f diffuseColor = new Color3f((float)Math.random(), (float)Math.random(), (float)Math.random());
Color3f specularColor = new Color3f();
Color3f ambientColor = new Color3f(diffuseColor);
ambientColor.scale(0.2f);
float shininess = 64f;
Material mat = new Material(ambientColor, emissiveColor, diffuseColor, specularColor, shininess);
app.setMaterial(mat);
if(TRANSPARENT) {
app.setTransparencyAttributes(transparencyAttributest);
}
return app;
}

private Sphere getRandomSphere(double size) {
float radius = (float)(Math.random() * size * 0.5 + 0.5 * size);
return new Sphere(radius, Sphere.GENERATE_NORMALS, GEOMETRY_LEVEL);
}

private Geometry getRandomGeometry(double size) {
Sphere sphere = getRandomSphere(size);
Geometry geometry = sphere.getShape().getGeometry();
return geometry;
}

public Java3dPerformanceTest(boolean showAll) {
setTitle(showAll ? "All shown first" : "Only some shown");
getContentPane().setLayout(new BorderLayout());
GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration();

Canvas3D c = new Canvas3D(config);
getContentPane().add(c, BorderLayout.CENTER);

// Create a simple scene and attach it to the virtual universe
universe = new SimpleUniverse(c);

// This will move the ViewPlatform back a bit so the
// objects in the scene can be viewed.
universe.getViewingPlatform().setNominalViewingTransform();
universe.getViewer().getView().setBackClipDistance(CAMERA_DISTANCE * 2);
universe.getViewer().getView().setTransparencySortingPolicy(View.TRANSPARENCY_SORT_NONE);
Transform3D trm = new Transform3D();
trm.lookAt(new Point3d(0,0, CAMERA_DISTANCE), new Point3d(), new Vector3d(0,1,0));
trm.invert();
universe.getViewingPlatform().getViewPlatformTransform().setTransform(trm);

BranchGroup objRoot = new BranchGroup();

int t = (int) Math.ceil(Math.pow(N_OBJECTS, 1.0 / 3.0));

double offset = ((double) CAMERA_DISTANCE) / 2.0 / t;
Vector3d offsetVec = new Vector3d(1,1,1);
offsetVec.scale(((double) CAMERA_DISTANCE) / 4.0);
int objIndex = 0;
int geomIndex = 0;
int appIndex = 0;
int switchIndex = 0;
Geometry geom = null;
Appearance app = null;
Switch switchNode = null;
objRoot.addChild(switchNode);
System.out.println("Generating " + N_OBJECTS + " objects");
ArrayList<Switch> switchList = new ArrayList<Switch>();
for (int x = 0; x < t && objIndex < N_OBJECTS ; x++) {
System.out.print(".");
for (int y = 0; y < t && objIndex < N_OBJECTS ; y++) {
for (int z = 0; z < t && objIndex < N_OBJECTS ; z++, objIndex++) {
if(geomIndex++ % GEOMETRY_MAPPING == 0) {
geom = getRandomGeometry(offset);
}
if(appIndex++ % APPEARANCE_MAPPING == 0) {
app = getRandomAppearance();
}
if(switchIndex++ % SWITCH_SIZE == 0) {
switchNode = new Switch(Switch.CHILD_MASK);
switchNode.setChildMask(new BitSet(SWITCH_SIZE));
switchNode.setCapability(Switch.ALLOW_SWITCH_WRITE);
objRoot.addChild(switchNode);
switchList.add(switchNode);
}

Vector3d v = new Vector3d(x, y, z);
v.scale(offset);
v.sub(offsetVec);
TransformGroup objTrans = new TransformGroup();
Transform3D trans = new Transform3D();
trans.setTranslation(v);
objTrans.setTransform(trans);
switchNode.addChild(objTrans);

Shape3D child = new Shape3D(geom, app);
child.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
objTrans.addChild(child);
}
}
}

// Make visible all or first row
boolean first = true;
for (Switch sw : switchList) {
BitSet childMask = sw.getChildMask();
childMask.set(0, SWITCH_SIZE, first || showAll);
first = false;
sw.setChildMask(childMask);
}

System.out.println();
int numTriangles = getRandomSphere(offset).getNumTriangles();
System.out.println("Objects: " + N_OBJECTS);

System.out.println("Objects Visible: " + N_OBJECTS_VISIBLE);
System.out.println("Triangle count per Object: " + numTriangles);
System.out.println("Triangle count totally: " + N_OBJECTS * numTriangles);
System.out.println("Triangle count totally visible: " + N_OBJECTS_VISIBLE * numTriangles);

PointLight pointLight = new PointLight(true, new Color3f(1,1,1), new Point3f(0,0,CAMERA_DISTANCE), new Point3f(1,0,0));
pointLight.setInfluencingBounds(INFINITE_BOUNDS);
pointLight.setCapability(PointLight.ALLOW_POSITION_WRITE);
objRoot.addChild(pointLight);

TransformGroup viewPlatformTransform = universe.getViewingPlatform().getViewPlatformTransform();
NavigationBehaviour navigation = new NavigationBehaviour(c, viewPlatformTransform, pointLight);
navigation.setSchedulingBounds(INFINITE_BOUNDS);
navigation.setReverseRotate(true);
navigation.setReverseTranslate(true);
universe.getViewingPlatform().setViewPlatformBehavior(navigation);

// Have Java 3D perform optimizations on this scene graph.
objRoot.compile();

universe.addBranchGraph(objRoot);
setSize(800, 800);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);

if (showAll) {
try {
Thread.currentThread().sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// hide most of the scene
JOptionPane.showConfirmDialog(null, "Hide");
universe.getCanvas().stopRenderer();
System.out.println("Hiding");
boolean show = true;
for (Switch sw : switchList) {
BitSet childMask = sw.getChildMask();
childMask.set(0, SWITCH_SIZE, show);
show = false;
sw.setChildMask(childMask);
}
System.out.println("Hiding Done");
universe.getCanvas().startRenderer();
}
}

private class NavigationBehaviour extends OrbitBehavior {
private final Transform3D trans = new Transform3D();
private final Vector3f loc = new Vector3f();
private final TransformGroup tg;
private final PointLight pointLight;

private NavigationBehaviour(Canvas3D canvas, TransformGroup tg, PointLight pointLight) {
super(canvas);
this.tg = tg;
this.pointLight = pointLight;
}

protected void processMouseEvent(java.awt.event.MouseEvent evt) {
super.processMouseEvent(evt);
tg.getTransform(trans);
trans.get(loc);
pointLight.setPosition(loc.x, loc.y, loc.z);
}

public void setPointLightPosition(Point3d eye) {
pointLight.setPosition((float) eye.x, (float) eye.y, (float) eye.z);
}
}

public void destroy() {
universe.cleanup();
}

//
// The following allows HelloUniverse to be run as an application
// as well as an applet
//
public static void main(String[] args) {
int opt = JOptionPane.showConfirmDialog(null, "Show All?", "", JOptionPane.YES_NO_OPTION);
boolean showAll = opt == JOptionPane.YES_OPTION;
new Java3dPerformanceTest(showAll);
}
}

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
interactivemesh
Offline
Joined: 2006-06-07
Points: 0

Hi,

running your test scene I get following statistics:

- 67 top level Switch nodes
- 66 of them have 1,500 TransformGroups/Shape3Ds as children
- one of them has 1,000 TransformGroups/Shape3Ds as children
- in total 100,000 TransformGroups and 100,000 Shape3Ds

- 10,000 shared TriangleStripArrays with 128 triangles (Spheres)
- in total 100,000 Shape3Ds x 128 triangles = 12,800,000 triangles to render

- 7,693 shared Appearances

- the reduced visible scene consists of one Switch with 1,500 TransformGroups/Shape3Ds switched on and 192,000 triangles to render, all other Switches' children are switched off

The scene graph is completly created in not live mode.

Indeed, the reduced visible scene starts and renders faster if the corresponding switch-settings are done before the scene is going live (case A) in comparison to going live with all switches enabled and disabling them afterwards (case B).

After checking the Java 3D sources a bit my assumption is that the internal tree-like and hierarchical data structures are not fully created for Switch children as long as they are not enabled the first time. Once enabled the structures are kept. This might explain why the number of RenderMolecules increases but will not be reduced later.

So, case B is the default scenario with fully constructed internal structures and case A is a special scenario which benefits from the 'lazy' structure update.

The Switch node allows to manage the visibility of objects in a fast and simple manner. Alternatively, removing and adding thousends of Shape3Ds at the same time will cause a 'longer' interruption but will likely result in a faster rendered scene.

Which scenario is best for your application?

August

paasiala
Offline
Joined: 2004-09-17
Points: 0

Hello August,
We are controlling the visibility with switches by hiding too small shapes in a behavior. The visibility is recalculated each time the camera transformation is modified. Adding/removing shapes in such scenario would work much worse. The thing I'm wondering about is why iteration of 100000 entities can be so slow that it so dramatically slows down the frame rate.
Pasi

interactivemesh
Offline
Joined: 2006-06-07
Points: 0

Hi Pasi,
while spending more time on this subject it happened that I iconified and deiconified the window. Surprisingly all 98,500 switched off RenderMolecules were removed and FPS jumped up! Do your test case and application show the same effect?

So, I'm in doubt if case B is actually the default scenario or if it is a bug.

When a Java 3D application's window is deiconified internal structures will be 'refreshed' and RenderMolecules which SwitchState's currentSwitchOn attribute is false will be removed. I couldn't find any 'regular' method in the public Java 3D API that is designed to do such a structure refreshing. The desired effect will take place whenever the View instance will be activated. This happens when

view.removeAllCanvas3Ds();
view.addCanvas3D(canvas3D);

are called ( from a not-Java 3D-thread ? ). After adding these two lines into your test program behind 'System.out.println("Hiding Done");' it runs with similar FPS (112) as if it were started with 1,500 Shape3Ds (116). Is this workaround feasible for your application?

By the way, checking if a RenderMolecule is switched on or off takes 550 nanosec on my system. In total: 100,000 checks = 55,000,000 nanosec = 55 milsec = 18 FPS. So, the worst scenario of your test program can't run faster. It runs at 15 FPS.

August