This Java 3D program demonstrates the use of the frames per second counter. The program displays a rotating cube and sets up the FPSCounter to compute the frame rate. The FPSCounter is set up with default values: - run indefinitely - 2 sec. warmup time - display average frame rate every fifth sampling interval. The default values can be changed through the command line arguments. Use FPSCounterDemo -h for help on the various arguments.

 import java.applet.Applet;
import java.awt.BorderLayout;
import java.awt.GraphicsConfiguration;
import java.text.NumberFormat;

import javax.media.j3d.Alpha;
import javax.media.j3d.Behavior;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.RotationInterpolator;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.WakeupOnElapsedFrames;
import javax.swing.JOptionPane;
import javax.vecmath.Point3d;

import com.sun.j3d.utils.applet.JMainFrame;
import com.sun.j3d.utils.geometry.ColorCube;
import com.sun.j3d.utils.universe.SimpleUniverse;

/**
 * This program demonstrates the use of the frames per second counter. The
 * program displays a rotating cube and sets up the FPSCounter to compute the
 * frame rate. The FPSCounter is set up with default values: - run indefinitely -
 * 2 sec. warmup time - display average frame rate every fifth sampling
 * interval. The default values can be changed through the command line
 * arguments. Use FPSCounterDemo -h for help on the various arguments.
 */

public class FPSCounterDemo extends Applet {
  private SimpleUniverse u = null;

  \private FPSCounter fpsCounter = new FPSCounter();

  BranchGroup createSceneGraph() {
    // Create the root of the branch graph
    BranchGroup objRoot = new BranchGroup();

    // Create the TransformGroup node and initialize it to the
    // identity. Enable the TRANSFORM_WRITE capability so that
    // our behavior code can modify it at run time. Add it to
    // the root of the subgraph.
    TransformGroup objTrans = new TransformGroup();
    objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
    objRoot.addChild(objTrans);

    // Create a simple Shape3D node; add it to the scene graph.
    objTrans.addChild(new ColorCube(0.4));

    // Create a new Behavior object that will perform the
    // desired operation on the specified transform and add
    // it into the scene graph.
    Transform3D yAxis = new Transform3D();
    Alpha rotationAlpha = new Alpha(-1, 4000);

    RotationInterpolator rotator = new RotationInterpolator(rotationAlpha,
        objTrans, yAxis, 0.0f, (float) Math.PI * 2.0f);
    BoundingSphere bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0),        100.0);
    rotator.setSchedulingBounds(bounds);
    objRoot.addChild(rotator);

    // Create the Framecounter behavior
    fpsCounter.setSchedulingBounds(bounds);
    objRoot.addChild(fpsCounter);

    return objRoot;
  }

  public FPSCounterDemo(String args[]) {
  }

  public FPSCounterDemo() {
  }

  public void init() {
    setLayout(new BorderLayout());
    GraphicsConfiguration config = SimpleUniverse
        .getPreferredConfiguration();

    Canvas3D c = new Canvas3D(config);
    add("Center", c);

    // Create a simple scene and attach it to the virtual universe
    BranchGroup scene = createSceneGraph();

    // Parse the command line to set the various parameters

    // Have Java 3D perform optimizations on this scene graph.
    scene.compile();
    u = new SimpleUniverse(c);

    // This will move the ViewPlatform back a bit so the
    // objects in the scene can be viewed.
    u.getViewingPlatform().setNominalViewingTransform();

    u.addBranchGraph(scene);

    JOptionPane
        .showMessageDialog(
            this,
            "\nThis program measures the number of frames rendered per second.\nNote that"+
      " the frame rate is limited by the refresh rate of the monitor.\nTo get the"+
      " true frame rate you need to disable vertical retrace.\n\nOn Windows(tm) you"+
      " do this through the Control Panel.\n\nOn Unix set the environment variable"+
      " OGL_NO_VBLANK\n(i.e. type \"setenv OGL_NO_VBLANK\" at the command prompt)",
            "Frame Counter", JOptionPane.INFORMATION_MESSAGE);
  }

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

  //
  // The following allows FPSCounterDemo to be run as an application
  // as well as an applet
  //
  public static void main(String[] args) {
    FPSCounterDemo fp = new FPSCounterDemo();
    fp.parseArgs(args);
    JMainFrame frame = new JMainFrame(fp, 256, 256);
  }

  /**
   * Parses the commandline for the various switches to set the FPSCounter
   * variables. All arguments are of the form <i>-name value </i>. All -name
   * arguments can be shortened to one character. All the value arguments take
   * a number. The arguments accepted are :
   * <ul>
   * <li>warmupTime : Specifies amount of time the FPSCounter should wait for
   * the HotSpot <sup><font size="-2">TM </font> </sup> VM to perform initial
   * optimizations. Specified in milliseconds <br>
   * <li>loopCount : Specifies the number of sampling intervals over which
   * the FPSCounter should calculate the aggregate and average frame rate.
   * Specified as a count. <br>
   * <li>maxLoops : Specifies that the FPSCounter should run for only these
   * many sampling intervals. Specified as number. If this argument is not
   * specified, the FPSCounter runs indefinitely. <br>
   * <li>help : Prints the accepted arguments. <br>
   * </ul>
   */

  private void parseArgs(String args[]) {
    for (int i = 0; i < args.length; i++) {
      if (args[i].startsWith("-")) {
        if (args[i].startsWith("w", 1)) {
          i++;
          System.out.println("Warmup time : " + args[i]);
          int w = new Integer(args[i]).intValue();
          fpsCounter.setWarmupTime(w);
        } else if (args[i].startsWith("l", 1)) {
          i++;
          System.out.println("Loop count : " + args[i]);
          int l = new Integer(args[i]).intValue();
          fpsCounter.setLoopCount(l);
        } else if (args[i].startsWith("m", 1)) {
          i++;
          System.out.println("Max Loop Count : " + args[i]);
          int m = new Integer(args[i]).intValue();
          fpsCounter.setMaxLoops(m);
        } else if (args[i].startsWith("h", 1)) {
          System.out.println("Usage : FPSCounterDemo [-name value]\n All arguments are"+
      " of the form -name value. All -name arguments can be shortened to one"+
    " character. All the value arguments take a number. The arguments accepted"+
    " are :\n  warmupTime : Specifies amount of time the FPSCounter should wait"+
    " for the HotSpot(tm) VM to perform initial optimizations. Specified in "+
    "milliseconds\n  loopCount : Specifies the number of sampling intervals "+
    "over which the FPSCounter should calculate the aggregate and average"+
    " frame rate. Specified as a count\n  maxLoops : Specifies that the"+
    " FPSCounter should run for only these many sampling intervals. Specified"+
    " as number. If this argument is not specified, the FPSCounter runs"+
    " indefinitely.\n  help : Prints this message.");
        }
      }
    }
  }

}

/**
 * This behavior calculates the frame rate and average frame rate of a Java3D
 * application. The behavior sets itself up to wakeup every time a new frame is
 * rendered.
 * 
 * <p>
 * The HotSpot(tm) compiler performs some initial optimizations before running
 * at optimal speed. Frame rates measured during this warmup period will be
 * inaccurate and not indicative of the true performance of the the application.
 * Therefore, before beginning the frame rate computation, the frame counter
 * waits for a fixed time period to allow the HotSpot(tm) compiler to
 * stablilize.
 * 
 * <p>
 * To avoid computing the frame rate too frequently (which would also hamper
 * rendering performance), the frame counter only computes the frame rate at
 * fixed time intervals. The default sampling duration is 10 seconds. After
 * waiting for the warmup period, the frame counter needs to calibrate itself.
 * It computes the number of frames rendered during the sampling period. After
 * doing this calibration, the frame counter reports the frame rate after these
 * many frames are rendered. It also reports the average frame rate after a
 * fixed number of sampling intervals (the default is 5).
 * 
 * <p>
 * The frame counter can be set up to run for a fixed number of sampling
 * intervals or to run indefinitely. The defaultis to run indefinitely.
 */

class FPSCounter extends Behavior {
  // Wakeup condition - framecount = 0 -> wakeup on every frame
  WakeupOnElapsedFrames FPSwakeup = new WakeupOnElapsedFrames(0);

  // Do calibration for these many millisec
  private static final long testduration = 1000;

  // Report frame rate after every sampleduration milliseconds
  private static final long sampleduration = 10000;

  // Flag to indicate that it is time to (re)calibrate
  private boolean doCalibration = true;

  // Flag to indicate the counter has started
  private boolean startup = true;

  // Wait for HotSpot compiler to perform optimizations
  private boolean warmup = true;

  // Time to wait for HotSpot compiler to stabilize (in milliseconds)
  private long warmupTime = 20000;

  // Counter for number of frames rendered
  private int numframes = 0;

  // Report frame rate after maxframe number of frames have been rendered
  private int maxframes = 1;

  // Variables to keep track of elapsed time
  private long startuptime = 0;

  private long currtime = 0;

  private long lasttime = 0;

  private long deltatime;

  // Run indefinitely or for a fixed duration
  private boolean finiteLoop = false;

  // No. of sampling intervals to run for if not running indefinitely
  private long maxLoops;

  // No. of sampling intervals run for so far
  private long numLoops = 0;

  // Total number of frames rendered so far
  private int sumFrames = 0;

  // Total time since last reporting of average frame rate
  private long sumTimes = 0;

  // Counts no. of sampling intervals
  private int loop = 0;

  // Average frame rate is reported after loopCount number of
  // sampling intervals
  private int loopCount = 5;

  private double sumFps = 0.0;

  private String symbol[] = { "\\", "|", "|", "/", "-", "|", "-" };

  int index = 0;

  private NumberFormat nf = null;

  public FPSCounter() {
    setEnable(true);
    nf = NumberFormat.getNumberInstance();
  }

  // Called to init the behavior
  public void initialize() {
    // Set the trigger for the behavior to wakeup on every frame rendered
    wakeupOn(FPSwakeup);
  }

  // Called every time the behavior is activated
  public void processStimulus(java.util.Enumeration critera) {
    // Apply calibration algorithm to determine number of frames to
    // wait before computing frames per second.
    // sampleduration = 10000 -> to run test, pass for 10 seconds.

    if (doCalibration) { // start calibration
      if (startup) {
        // Record time at which the behavior was first invoked
        startuptime = System.currentTimeMillis();
        startup = false;
      } else if (warmup) { // Wait for the system to stabilize.
        System.out.print("\rFPSCounter warming up..."
            + symbol[(index++) % symbol.length]);
        currtime = System.currentTimeMillis();
        deltatime = currtime - startuptime;
        if (deltatime > warmupTime) {
          // Done waiting for warmup
          warmup = false;
          lasttime = System.currentTimeMillis();
          System.out.println("\rFPSCounter warming up...Done");
        }
      } else {
        numframes += 1;
        // Wait till at least maxframe no. of frames have been rendered
        if (numframes >= maxframes) {
          currtime = System.currentTimeMillis();
          deltatime = currtime - lasttime;
          // Do the calibration for testduration no. of millisecs
          if (deltatime > testduration) {
            // Compute total no. of frames rendered so far in the
            // current sampling duration
            maxframes = (int) Math
                .ceil((double) numframes
                    * ((double) sampleduration / (double) deltatime));

            // Done with calibration
            doCalibration = false;
            // reset the value for the measurement
            numframes = 0;
            lasttime = System.currentTimeMillis();
          } else {
            // Need to run the calibration routine for some more
            // time. Increase the no. of frames to be rendered
            maxframes *= 2;
          }
        }
      }
    } else { // do the measurement
      numframes += 1;
      if (numframes >= maxframes) {
        currtime = System.currentTimeMillis();
        deltatime = currtime - lasttime;
        // time is in millisec, so multiply by 1000 to get frames/sec
        double fps = (double) numframes / ((double) deltatime / 1000.0);

        System.out.println("Frame Rate : \n\tNo. of frames : "
            + numframes + "\n\tTime : "
            + ((double) deltatime / 1000.0) + " sec."
            + "\n\tFrames/sec : " + nf.format(fps));

        // Calculate average frame rate
        sumFrames += numframes;
        sumTimes += deltatime;
        sumFps += fps;
        loop++;
        if (loop >= loopCount) {
          double avgFps = (double) sumFrames * 1000.0
              / (double) sumTimes;
          double ravgFps = sumFps / (double) loopCount;
          System.out.println("Aggregate frame rate "
              + nf.format(avgFps) + " frames/sec");
          System.out.println("Average frame rate "
              + nf.format(ravgFps) + " frames/sec");
          numLoops++;
          if (finiteLoop && numLoops >= maxLoops) {
            System.out
                .println("************** The End **************\n");
            setEnable(false);
          }
          loop = 0;
          sumFps = 0;
        }
        numframes = 0;
        lasttime = System.currentTimeMillis();
        ;
      }
    }
    // Set the trigger for the behavior
    wakeupOn(FPSwakeup);
  }

  /**
   * The frame counter waits for some time before computing the frame rate.
   * This allows the HotSpot compiler to perform initial optimizations. The
   * amount of time to wait for is set by this method. The default is 20000
   * (20 sec)
   * 
   * @param Amount
   *            of time to wait for before computing frame rate (specified in
   *            milliseconds)
   */
  public void setWarmupTime(long wt) {
    warmupTime = wt;
  }

  /**
   * Sets the number of sampling intervals to wait for before computing the
   * average frame rate. The default is 5.
   * 
   * @param No.
   *            of sampling intervals over which to compute frame rate. A
   *            value of 0 implies the average frame rate is computed over one
   *            sampling interval
   */
  public void setLoopCount(int lc) {
    loopCount = lc;
  }

  /**
   * This method sets the number of sampling intervals for which the frame
   * counter should run.
   * 
   * @param No.
   *            of sampling intervals to run for
   */
  public void setMaxLoops(int ml) {
    maxLoops = ml;
    finiteLoop = true;
  }

}