Performance guidelines for Lightspeed views

LuciadLightspeed comes with default performance settings that ensure a consistent, high frame rate on most hardware and most screen sizes. Most of the time, the only thing you need to specify is the available heap memory. LuciadLightspeed can work with less than 512MB, but more heap memory is preferable. This is especially the case on 64-bit systems and with higher screen resolutions, such as on screens offering Full HD and more. Fore more information on memory, refer to Memory guidelines.

If you need to display an exceptional amount of data, or if your hardware has limited resources, you can enable more aggressive performance optimizations using the view builder API. This is explained in Tuning view performance optimizations. Specific performance settings for shape and raster painting are described in Tuning shape and raster painter settings.

The remainder of this article discusses more advanced performance-related options.

Tuning view performance optimizations

TLspViewBuilder has several performance-related settings. The most obvious one is the buildAWTView method. It allows you to create an AWT view that is perfectly compatible with a Swing-based application and provides the highest frame rate.

If you need to display an exceptional amount of data, or if your hardware has limited resources, you can activate more aggressive performance optimizations by calling the paintingHints method and passing in the MAX_PERFORMANCE painting hint. If you do so, you will trade in some visual quality. The view painting hints may also affect the default behavior of the other settings discussed here. However, any manual override of the default settings will take precedence.

Finally, if your device has a high-resolution screen and a low-end GPU, you could consider rendering the view in a lower resolution by calling the resolutionScale method with a parameter smaller than 1. As a result of the upscaling, text and icons will look larger.

Tuning shape and raster painter settings

The TLspShapeLayerBuilder and TLspShapePainter allow controlling the quality and performance of vector rendering. For more information, refer to the Javadoc of TLspShapePaintingHints.

The TLspRasterStyle provides a few basic properties to control whether a given raster is visible at a given scale:

  • startResolutionFactor is the highest pixel density (number of raster pixels per screen pixel) at which a raster is painted. Small values (for example 2.0) generally improve performance, by not displaying the raster or raster level if its resolution appears to be high, relative to the resolution of the view.

  • levelSwitchFactor is the factor that affects the scale point at which a raster level is selected. A value of 1.0 gives the highest practical raster quality: as soon as a single raster pixel would project to several screen pixels, a higher resolution level is used. A lower value delays this switching: a single raster pixel may project to multiple screen pixels. This can cause pixelation or blurring effects, but it will improve the painting performance and reduce memory usage. The default value is 0.3, which provides a good trade-off between quality and performance for most applications.

The behavior of a raster layer is also influenced by its layer type:

  • INTERACTIVE: indicates that the layer should support smooth interaction, such as changes of the visibility or style of the raster data.

  • BACKGROUND: indicates that the raster is used as background data in the view, and rarely changes. The view combines all background layers at the bottom of the view to optimize memory usage and painting performance.

LuciadLightspeed configuration options

The guidelines discussed in this section are related to OpenGL configuration options, and are intended for developers with solid knowledge of OpenGL.

OpenGL resource cache

LuciadLightspeed layers that use OpenGL data such as textures, buffers, and so on, cache this data in the ILspGLResourceCache, which can be obtained from the view’s services (TLspViewServices). The resource cache takes ownership of the data. It pushes out and destroys the least recently used data at its own discretion when its limit is reached.

By default, all views created with TLspViewBuilder share a single GL resource cache instance. This prevents applications which use multiple views from eventually using up all available GPU memory. To disable automatic sharing and give each view its own resource cache, use the disableAutomaticContextSharing() method in the view builder.

The default resource cache size for a Lightspeed view is suited for all view sizes and a moderate amount of content. You can override the resource cache size while you are creating the view with TLspViewBuilder. Alternatively, you can modify the cache size using the system property -Dcom.luciad.view.lightspeed.opengl.cacheSize=X, where X corresponds to the requested resource cache size in megabytes.

The number of entries that can be stored in the cache is by default not limited. You can change the default behavior during view construction with TLspViewBuilder, or you can specify a maximal entry count using the system property -Dcom.luciad.view.lightspeed.opengl.maxCacheEntries=X, where X corresponds to the requested maximal number of cache entries.

You can only access the ILspGLResourceCache during the paint loop, for example within a paint method call of an ILspPaintableLayer object, or a paintObjects call of an ILspPainter object.

Draping on terrain

The draping algorithm implementation tries to use an OpenGL frame buffer object (FBO) if that is supported, and resorts to using the main framebuffer if not. If you are using the main framebuffer, it should be backed by a buffer that has an alpha channel. If you are not using an FBO for draping and if an alpha channel is not available, your draped objects will look oversaturated. The following options allow you to configure buffer usage for the draping algorithm:

  • Enforce the use of the main framebuffer instead of an offscreen framebuffer:

-Dcom.luciad.view.lightspeed.terrain.disableFBO=true

  • If the main framebuffer is requested, back it with a buffer with an alpha channel:

-Dcom.luciad.view.lightspeed.graphicsConfiguration.requestAlpha=true

  • Specify which visual to use. On some Linux systems, the above does not work correctly. In that case, you should explicitly specify which visual to use (see the Linux command glxinfo):

-Dcom.luciad.view.lightspeed.glx.visual=0x24

By default, LuciadLightspeed uses the following configuration option:

-Dcom.luciad.view.lightspeed.terrain.disableFBO=false.

Transparency

By default, a 3D Lightspeed view is configured with an order-independent transparency algorithm. This algorithm performs offscreen rendering to an OpenGL floating point frame buffer object (FBO). If the graphics board supports line smoothing when rendering to floating point buffers, a regular floating point buffer is used. In the other case, a multi-sample buffer with two samples per pixel is used, if that is supported.

This behavior can be overridden by specifying:

-Dcom.luciad.view.lightspeed.transparency.maxDefaultColorSamples=<value>, where <value> is 1, 2, or 4.

Instead of overriding the default maximal number of color samples, you can also explicitly specify how many color samples and coverage samples must be used:

  • To explicitly request the number of color samples (MSAA):

-Dcom.luciad.view.lightspeed.transparency.colorSamples=4

  • To explicitly request the number of coverage samples (CSAA):

-Dcom.luciad.view.lightspeed.transparency.coverageSamples=8

For example, NVIDIA hardware can typically be configured using the anti-aliasing settings 8x (4xMS, 4xCS), which corresponds to the following LuciadLightspeed settings:

-Dcom.luciad.view.lightspeed.transparency.colorSamples=4

-Dcom.luciad.view.lightspeed.transparency.coverageSamples=8

If FBOs are not supported, you can explicitly disable the default transparency algorithm with:

-Dcom.luciad.view.lightspeed.transparency.simple=true

In any case, the transparency algorithm will fall back to a lower quality setting if the requested parameters are not supported by the graphics hardware.

External configuration options

Memory guidelines

A LuciadLightspeed application consumes Java heap memory, native memory and video memory:

  • Java heap memory

    • Stores all objects created by calling Java’s new operator.

    • Is recollected by the garbage collector (GC) whenever possible.

    • Allows for the setting of the initial and maximum heap size with the JVM options -Xms and -Xmx.

      • Example: java -Xms1g -Xmx1g MainPanel

  • Native memory

    • Allocated through the NIO call java.nio.ByteBuffer.allocateDirect, for example.

      • The resulting buffer that holds the data is in native memory.

      • The Java heap object only contains a reference to this native buffer.

    • Automatically reclaimed through the GC process, but there is no incentive to do so if there is pressure on the native heap.

      • The GC process is triggered by the Java heap, not the native heap.

      • Can run out of native memory when the Java heap almost empty, and no GC events are triggered.

    • We strongly advise against using direct buffers yourself for any objects in a LuciadLightspeed application, and certainly for short-lived objects.

  • Video memory (VRAM)

    • Dedicated graphics memory, typically 1GB or larger.

    • Not controlled by the Java GC process.

    • Indirectly allocated by OpenGL and OpenCL API calls such as glTexImage2D.

    • Preferably controlled by the view’s ILspGLResourceCache.

It is important not to exhaust any of the above memory types. For example, on a Windows 32 bit JVM, there is typically only 1.5 gigabytes available to the Java process. Hence, running your application with the flags -Xms1500m -Xmx1500m is a bad idea, as there will be no space left for the native heap. It would be preferable to use a 750 MB Java heap and a 250 MB OpenGL resource cache, for example. This would ensure that there is still about 500 MB native memory that remains available if the OpenGL graphics driver duplicates and caches its data in native memory. Your application is unlikely to experience out-of-memory errors due to native heap exhaustion.

Note that the memory limit for each process is typically tighter on a 32 bit Linux JVM. If memory limitations are an issue, we recommend running your application on a 64 bit JVM.

Follow this link [1] for a more in-depth discussion on Java memory and the behavior when running out of (native) memory.

Garbage collection

Java objects created with the new operator are collected by the garbage collector when possible. For recent versions of the Java VM, the Garbage first or G1 garbage collector is used by default. For older versions (Java 1.8), using the concurrent low pause time garbage collector might reduce application pause times. You can enable it using the following VM options:

  • -XX:+UseConcMarkSweepGC

  • -XX:+CMSParallelRemarkEnabled

See this link [2] for more Java performance tuning tips.

Video driver

Your graphics driver offers various configuration options, each of which impact the performance and visual quality of your LuciadLightspeed application. The most important options to consider, are:

  • Anti-aliasing: in many cases, you obtain the best performance and the smallest memory footprint when anti-aliasing is disabled, but image quality will be reduced. However, depending on your OpenGL implementation, anti-aliasing of point, line and triangle primitives may still occur, even if the anti-aliasing option is disabled. Note that if you use TLspLineStyle objects with a width larger than one, disabling anti-aliasing will not improve performance. Increasing the anti-aliasing quality setting will also increase video memory consumption of your LuciadLightspeed application. Please consult the configuration options outlined in Transparency as well.

  • Performance levels: some graphics drivers can be configured for adaptive GPU clocking. Choosing the maximum performance mode rather than the adaptive mode will increase your LuciadLightspeed application performance.

Monitoring the allocation of video memory

As mentioned earlier, video memory is not controlled by the Java garbage collector process, but through the ILspGLResourceCache instead. You can monitor the amount of video memory allocated or used by registering an ILspGLResourceCacheListener. Such a listener gets notified whenever a new resource is added, an existing resource is used, or an existing resource is removed from the cache.

The LuciadLightspeed sample applications use this mechanism to display video memory statistics in an overlay that can be enabled by pressing the statistics button in the top right corner of the sample frame. Program: Listener that counts the total bytes of VRAM used per frame shows the listener that is registered with the resource cache.

Program: Listener that counts the total bytes of VRAM used per frame (from samples/lightspeed/debug/StatisticsGLResourceCacheListener)
public class StatisticsGLResourceCacheListener implements ILspGLResourceCacheListener {

  private static final ILcdLogger sLogger = TLcdLoggerFactory.getLogger(StatisticsGLResourceCacheListener.class);

  private long fMaxBytesUsed = 0;
  private int fMaxCountUsed = 0;
  private Map<ALspGLResource, Long> fResourcesUsed
      = new IdentityHashMap<ALspGLResource, Long>();

  /**
   * Resets the count of bytes used since the last call to reset.
   * Also updates the maximal bytes used values.
   * This method is called between each frame.
   */
  public void reset() {
    fMaxBytesUsed = Math.max(fMaxBytesUsed, getBytesUsedSinceLastReset());
    fMaxCountUsed = Math.max(fMaxCountUsed, getResourcesCountUsedSinceLastReset());
    fResourcesUsed.clear();
  }

  @Override
  public void resourceCacheEvent(TLspGLResourceCacheEvent aEvent) {
    // Store the resource and its size if it was added or used.
    if (aEvent.getType() == TLspGLResourceCacheEvent.Type.RESOURCE_ADDED ||
        aEvent.getType() == TLspGLResourceCacheEvent.Type.RESOURCE_USED) {
      ALspGLResource resource = aEvent.getResource();
      fResourcesUsed.put(resource, resource.getBytes());
    }
  }

  /**
   * Gets the total bytes of resources that are used
   * since the last call to {@link #reset()}. If a resource
   * was used multiple times, it is only counted once.
   *
   * @return the total number of bytes of used resources.
   */
  public long getBytesUsedSinceLastReset() {
    long result = 0;
    for (Map.Entry<ALspGLResource, Long> entry : fResourcesUsed.entrySet()) {
      result += entry.getValue();
    }
    return result;
  }

  /**
   * Gets the number of resources used since the last
   * call to {@link #reset()}. If a resource
   * was used multiple times, it is only counted once.
   *
   * @return the number of resources used
   */
  public int getResourcesCountUsedSinceLastReset() {
    return fResourcesUsed.size();
  }

  /**
   * Returns the maximal total bytes used between two
   * consecutive calls to {@link #reset()}.
   *
   * @return the maximal bytes used.
   */
  public long getMaxBytesUsed() {
    return fMaxBytesUsed;
  }

  /**
   * Returns the maximal number of resources used
   * between two consecutive calls to {@link #reset()}.
   *
   * @return the maximal number of resources used
   */
  public int getMaxCountUsed() {
    return fMaxCountUsed;
  }
}