30.4. Optimizing Everything Else

If you have determined that rendering is not your bottleneck, or if you have already optimized rendering as much as possible and a significant amount of time is still being spent doing something other than rendering, it’s time to look for other bottlenecks.

This section helps you find other bottlenecks, and suggests Open Inventor-specific things to look for by discussing the following:

Useful Tools

Standard performance analysis tools make performance analysis of the non-graphics part of your application easy. See the reference pages for information on using these tools.

Optimizing file reading

Open Inventor 8.0 introduces a new .iv Open Inventor file format and several improvements to writing and reading Open Inventor files. Note that all previous versions of Open Inventor since version 2.1, wrote the version 2.1 header by default. In order to take maximum advantage of the new features, Open Inventor 8.0 writes the new header and format by default. Applications built with an older version of Open Inventor will not be able to load these files. However files can be converted using the ivCat tool as described below.

Format changes

New file header strings were introduced with Open Inventor 8.0:

#Inventor V8.0 ascii [LE/BE]

#Inventor V8.0 binary [LE/BE]

  • Before Open Inventor 8.0, binary .iv files could only be written in big endian format. Using the new 8.0 header format, binary files are now written (by default) with the native endianness of the system allowing faster reading and writing. An additional keyword in the file header specifies the endianness of the values in the file. The application can also specify the endianness to be used by setting the appropriate file header string with SoOutput::setHeaderString. The endianness keyword is permitted in an ASCII file header string, but has no effect.

  • For ASCII .iv files, multiple value fields like SoMFVec3f written in the new 8.0 format can be loaded much faster because the file now contains the number of values in the field. This can significantly improve loading performance for large geometry (although binary files are still much smaller and faster of course).

The ivCat Open Inventor file converter (located in $OIVHOME/src/Inventor/tools/ivCat) provides a simple way to convert files from ASCII to binary from/to different version.

For instance, converting from 2.1 to to 8.0 can be done with the following command line:

%> ivCat -s \"#Inventor V8.0 ascii\" -o myFile.iv file.iv

Compressed files support

Open Inventor 8.1 added support for reading and writing compressed Inventor format (.iv) files. Loading a compressed file is much faster, especially for large files, because fewer bytes need to be transferred from the physical disk or network.

Open Inventor supports the Deflate compression format, used by zlib and gzip. In order to compress a file, any software which supports gzip compression can be used.

The file extension “.ivz” is suggested for convenience, but compressed IV files don’t need any specific extension in order to be supported by Open Inventor. The compression detection is automatic and there is no need to add extra code to support compressed files. The class SoInput SoInput SoInput natively supports compressed files.

Compression management is performed by the classes SoInput SoInput SoInput and SoOutput SoOutput SoOutput . SoInput SoInput SoInput supports compressed IV files even when the streaming mode is enabled, but the “read from buffer” feature does not support compression at this time. The SoOutput SoOutput SoOutput class has been improved to compress the data on-the-fly. In order to write a compressed file a new file property can be set in any SoOutput SoOutput SoOutput based object.

Example 30.1. Setting an SoOutput object for compressed output

SoOutput fileOutputInterface;
fileOutputInterface.setFileProperty( SoOutput::CompressedZlib );
SoOutput fileOutputInterface = new SoOutput();
fileOutputInterface.SetFileProperty( SoOutput.FileProperties.CompressedZlib );
SoOutput fileOutputInterface = new SoOutput();
fileOutputInterface.setFileProperty( SoOutput.FileProperties.CompressedZlib );


All applications that use SoInput SoInput SoInput can read compressed files, including the SceneViewer, LargeModelViewer and IvTune.

Asynchronous reading

Open Inventor is now able to read a file and create the scene graph in parallel in order to accelerate large file loading. In the asynchronous loading mode, a separate thread reads the file in blocks and each time a new block of data is available the main thread creates the scene graph for that block while the reader thread loads the next block.

The number of buffers and the size of a buffer can be configured using the following environment variables:

  • OIV_STREAM_BUFFERS_NUMBER: Number of buffers to be used (4 by default).

  • OIV_STREAM_BUFFER_SIZE: Size of each buffer (1 MByte by default).

To enable or disable asynchronous loading use the new (optional) third parameter of SoInput SoInput SoInput ::openFile, Example:

myInput->openFile( “myFile.iv”, FALSE, TRUE );

Progress status

Progress loading a file can now be monitored using the SoInput SoInput SoInput class. To do so, the application must create its own class derived from SoInput SoInput SoInput and implement the method:

SoInput::updateReadPercent( double readPercent )

This method is called periodically during a file read operation to update the progress. The readPercent parameter ranges from 0 to 100. This example shows how it could be done with a Qt progress bar:

class MyInput : public SoInput
  {
  public:
    MyInput( QProgressBar* pgb ) : SoInput()
    {
      m_pgbPtr = pgb;
    }

    ~MyInput() {};

    virtual void updateReadPercent( double readPercentage )
    {
      m_pgbPtr->setValue( int(readPercentage) );
    }

  private:
    QProgressBar* m_pgbPtr;
  };
        
class MyInput : SoInput
{
  private ProgressBar myProgressBar;

  public MyInput(ProgressBar pgb) : base()
  {
    myProgressBar = pgb;
  }

  public virtual void updateReadPercent(double readPercentage)
  {
    myProgressBar.Value = (int)readPercentage;
  }
}
      
class MyInput extends SoInput
{
  private JProgressBar myProgressBar;

  public MyInput(JProgressBar pgb)
  {
    super();
    myProgressBar = pgb;
  }

  public void updateReadPercent(double readPercentage)
  {
    myProgressBar.setValue((int)readPercentage);
  }
}
      

Optimizing Memory Usage

First, make sure your application isn’t running out of physical memory. If your application is swapping, try to reduce its memory usage as follows:

Looking at CPU Usage

If memory is not the problem, start by looking at “inclusive” CPU times for your procedures (inclusive times include time spent in that procedure and all procedures it calls; exclusive times are just the time spent in that procedure). Ignore the very highest level routines like main() or SoXt::mainLoop() ; look for Open Inventor beginTraversal() methods or for application routines that are taking a significant percentage of time. If a lot of time is spent in SoGLRenderAction::beginTraversal (), see Section 30.3, “ Optimizing Rendering” for information on improving rendering performance.

If your application is spending a lot of time in code written by you, you are on your own! The rest of this section describes Open Inventor routines that often show up on profile traces, describes what these routines do, and suggests ways of using them more efficiently.

Optimizing Action Construction and Set-Up

Open Inventor actions perform a lot of work the first time they are applied to a scene (subsequent traversals are very fast). Therefore, if your performance traces show a lot of time being spent inside an action’s constructor or the SoAction::setUpState() method, try to create an action once and reapply it instead of constructing a new action. For example, if you often compute the bounding boxes of some objects in the scene, keep an instance of an SoBoundingBoxAction around that is reused:

static SoGetBoundingBoxAction *bbAction = NULL;
if (bbAction == NULL) bbAction = new SoGetBoundingBoxAction;
bbAction->apply(myScene);
  
static SoGetBoundingBoxAction bbAction = null;
...
if (bbAction == null) bbAction = new SoGetBoundingBoxAction(viewportRegion);
bbAction.Apply(myScene);
  
static SoGetBoundingBoxAction bbAction = null;
...
if (bbAction == null) bbAction = new SoGetBoundingBoxAction(viewportRegion);
bbAction.apply(myScene);
  

instead of the much less efficient:

SoGetBoundingBoxAction bbAction;
// inefficient if called a lot!
bbAction.apply(myScene);
  
          
        
          
        

Decreasing Notification Overhead

Every time you change a field in the scene, Open Inventor performs a process called notification. A notification message travels up the scene graph to the node’s parents, scheduling sensors, causing caches to be destroyed, and marking any connections to engines or other fields as needing evaluation.

If your performance traces show a lot of time is being spent in a startNotify() method, try the following to decrease notification overhead:

  • If you are modifying several values in a multiple-valued field, use the setValues() methods or the startEditing() / finishEditing() methods instead of repeatedly calling the set1Value() method.

  • Build scenes from the bottom up. Set leaf nodes’ fields first, then add them to their parents, then add the parents to their parents, and so on. For example, do this:

    SoCube *myCube = new SoCube;
    myCube->width = 10.0;
    SoCylinder *myCylinder = new SoCylinder;
    myCylinder->radius = 4.0;
    SoSwitch *mySwitch = new SoSwitch;
    mySwitch->whichChild = 0;
    mySwitch->addChild(myCube);
    mySwitch->addChild(myCylinder);
    SoSeparator *root = new SoSeparator;
    root->ref();
    root->addChild(mySwitch);
             
    SoCube myCube = new SoCube();
    myCube.width.SetValue(10f);
    SoCylinder myCylinder = new SoCylinder();
    myCylinder.radius.SetValue(4f);
    SoSwitch mySwitch = new SoSwitch();
    mySwitch.whichChild.SetValue(0);
    mySwitch.AddChild(myCube);
    mySwitch.AddChild(myCylinder);
    SoSeparator root = new SoSeparator();
    root.AddChild(mySwitch);
             
    SoCube myCube = new SoCube();
    myCube.width.setValue(10f);
    SoCylinder myCylinder = new SoCylinder();
    myCylinder.radius.setValue(4f);
    SoSwitch mySwitch = new SoSwitch();
    mySwitch.whichChild.setValue(0);
    mySwitch.addChild(myCube);
    mySwitch.addChild(myCylinder);
    SoSeparator root = new SoSeparator();
    root.addChild(mySwitch);
             

    instead of the less efficient:

    SoSeparator *root = new SoSeparator;
    root->ref();
    SoSwitch *mySwitch = new SoSwitch;
    root->addChild(mySwitch);
    mySwitch->whichChild = 1;
    SoCube *myCube = new SoCube;
    mySwitch->addChild(myCube);
    myCube->width = 4.0;
    SoCylinder *myCylinder = new SoCylinder;
    mySwitch->addChild(myCylinder);
    myCylinder->radius = 4.0;
             
    SoSeparator root = new SoSeparator();
    SoSwitch mySwitch = new SoSwitch();
    root.AddChild(mySwitch);
    mySwitch.whichChild.SetValue(1);
    SoCube myCube = new SoCube();
    mySwitch.AddChild(myCube);
    myCube.width.SetValue(4f);
    SoCylinder myCylinder = new SoCylinder();
    mySwitch.AddChild(myCylinder);
    myCylinder.radius.SetValue(4f);
           
    SoSeparator root = new SoSeparator();
    SoSwitch mySwitch = new SoSwitch();
    root.addChild(mySwitch);
    mySwitch.whichChild.setValue(1);
    SoCube myCube = new SoCube();
    mySwitch.addChild(myCube);
    myCube.width.setValue(4f);
    SoCylinder myCylinder = new SoCylinder();
    mySwitch.addChild(myCylinder);
    myCylinder.radius.setValue(4f);
           
  • Using many path sensors can cause notification to become slow, since an SoPathSensor SoPathSensor SoPathSensor is notified whenever any change happens underneath the head node of the SoPath SoPath SoPath monitored by the SoPathSensor SoPathSensor SoPathSensor .

    SoPath SoPath SoPath itself does not have this problem in Open Inventor 2.X (and later, but did in Inventor 1.X).

  • Notification can be enabled or disabled on a per-node or per-engine basis. Beware that because caching, sensors, and connections rely on notification for proper operation, you must be very careful when using this feature. See the SoFieldContainer SoFieldContainer SoFieldContainer reference page for information on the enableNotify() method.

Picking and Handling Events

If your application profiles show a lot of time is spent inside the methods SoHandleEventAction::beginTraversal() or SoPickAction::beginTraversal() , try the following to improve picking and/or event handling performance:

  • Insert SoPickStyle::UNPICKABLE nodes in your scene to turn off picking for objects that should never be picked (for example “dead” background graphics).

  • Insert SoPickStyle::BOUNDING_BOX nodes in your scene if you do not need detailed picking information. This helps most for complicated objects like SoText3 SoText3 SoText3 or SoTriangleStripSets with many triangles.

  • If you have objects with a lot (thousands) of polygons in them, break them up into several objects under different separators, grouping polygons that are close to each other. This allows SoSeparator SoSeparator SoSeparator pick culling to quickly reject many of the triangles.

  • To speed up event handling, try to put active objects that respond to events toward the left and top of the scene graph. An SoHandleEventAction SoHandleEventAction SoHandleEventAction ends traversal as soon as a node reports that it has handled the event.

  • If you write your own event callback node, or implement a node that responds to events, be sure to use the grabEvents() method when appropriate. Because grabbing short-circuits traversal of the scene, it is a useful way to speed up event distribution.