Java 3D Programming by Daniel Selman - HTML preview

PLEASE NOTE: This is an HTML preview only and some elements such as links or page numbers may be incorrect.
Download the book in PDF, ePub, Kindle for a complete version.

CHAPTER 18

Java 3D system architecture

18.1 Introduction

18.2 Threads running a Java 3D application

18.3 MasterControl

18.4 BehaviorScheduler

18.5 InputDeviceScheduler

18.6 Renderer

18.7 StructureUpdateThread

18.8 TimerThread

18.9 SceneGraphObject

18.10 Node types

18.11 Exception Strings

18.12 J3D DLL

18.13 Summary

By now I hope you are curious to know how Java 3D achieves what it does. In this chapter we gallop through some of the implementation details behind the Java 3D API gaining insight into getting the most from Java 3D and maximizing the performance of your applications. This chapter gives you some clues for implementing your own 3D graphics API in Java.

Sun’s Java 3D internal implementation details are all subject to change at any time. The version described here is Java 3D 1.2 running on the Win32 platform.

18.1 Introduction

The implementation of the Java 3D API has undergone considerable revision between the 1.1 and 1.2 versions of the API. About 70 percent of the Java code was rewritten between these two versions! The Java 3D 1.1 architecture was fairly simplistic and could be considered a proof-of-concept implementation of the API.

Version 1.2 moved to a message-based architecture and took tighter control of the various worker threads created by the API implementation. The collision detection thread was removed in 1.2, and collision detection is carried out by the new TimerThread functionality. By explicitly activating the threads in the system, the 1.2 API has removed the free-running threads (behaviors and collision detection) that caused 100 percent CPU utilization in Java 3D 1.1.

18.2 Threads running a Java 3D application

As you can see from tables 18.1 through 18.3, a typical Java 3D application contains many threads, some concerned with system behavior, some running the GUI event processing, and some (13) controlling the Java 3D runtime environment. The two ThreadGroups, “main” and “Java 3D,” are subgroups of the top-level “system” ThreadGroup.

The Java 3D threads have the default thread priority of 5, although this can be controlled using the VirtualUniverse.setJ3DThreadPriority method. The priority must fall between Thread.MAX_PRIORITY and Thread.MIN_PRIORITY. The default is Thread.NORM_PRIORITY. The priority of the parent ThreadGroup (Java 3D) cannot be exceeded. Note that unlike nondaemon threads, daemon threads do not have to exit their Runnable’s run method for the JVM to exit.

Table 18.1 Thread Group: System

img216.png

Table 18.2 Thread Group: Main

img217.png

Table 18.3 Thread Group: Java3D

img218.png

The Java 3D implementation is heavily multithreaded. Typically each thread controls access to a particular data structure. The threads use the MasterControl thread to periodically synchronize and exchange notifications and messages across threads. Each thread maintains a subclass of the J3dStructure member, which contains the messages to be processed by the thread. The J3dStructure.processMessages abstract method is implemented by each subclass to examine the message queue and process messages as appropriate.

Once a message is posted to a thread by the MasterControl thread, the receiving thread is marked as having work to do and scheduled for activation. See the discussion of the MasterControl class for details of the message dispatch and processing mechanism.

Most threads are associated with an instance of a VirtualUniverse (threads with the -1 suffix), while the MasterControl, RenderingAttributesStructureUpdateThread, and TimerThread are systemwide and maintain data structures that apply across all VirtualUniverse instances.

The multithreaded nature of Java 3D allows it to leverage multiple CPUs if available; however, it does make synchronizing certain operations difficult or impossible.

18.2.1 Native Windows threads running a Java 3D application

As illustrated in figure 18.1 a Windows Java 3D application creates five threads above normal priority and three Time Critical threads. This will cause other applications running simultaneously to experience a dramatic slowdown when the Java 3D application is in the foreground.

18.3 MasterControl

The MasterControl class is Java 3D’s central message dispatch and thread scheduling mechanism. The MasterControl object is a static member of the VirtualUniverse class, and is initialized by a static initializer on the VirtualUniverse class. All instances of the VirtualUniverse class therefore share the same MasterControl instance.

The first time a message is posted to the MasterControl class, it creates the MasterControlThread. The MasterControlThread runs the main message-processing loop for the MasterControl class.

img219.png

Figure 18.1 Native (Windows) threads running a Java 3D application

 Master Root Thread Group Name: Java3D

 Thread Name: J3D-MasterControl

class MasterControlThread extends Thread

{

  MasterControlThread(ThreadGroup threadgroup)

  {

    super(threadgroup, "J3D-MasterControl");

    VirtualUniverse.mc.createMCThreads();

    start();

  }

  public void run()

  {

    do

       while(VirtualUniverse.mc.running)

       {

         VirtualUniverse.mc.doWork(); Thread.yield();

       }

       while(!VirtualUniverse.mc.mcThreadDone());

  }

}

_________________________________________________________________

The constructor for the MasterControl class loads the J3D.DLL native DLL and reads the Java 3D system properties (see table 18.4).

Message processing and threading architecture

The MasterControl class is a message exchange mechanism between the various subsystems that compose the Java 3D runtime. The MasterControl thread maintains five UnorderList members, each containing Java 3D worker threads.

  • stateWorkThreads:

RenderingAttributesStructure calls updateMirrorObject on the NodeComponentRetained passed with the message.

GeometryStructure maintains a binary hash tree of GeometryAtom objects and Groups. Maintains the wakeupOnCollisionEntry, Exit, and Movement lists.

BehaviorStructure maintains a list of behaviors and marks them for activation.

RenderingEnvironmentStructure maintains a list of the lights, fog, alternate appearances, clipping regions, and backgrounds to be used for each RenderAtom.

SoundStructure maintains a list of sounds and soundscapes and schedules them for activation with the View’s SoundScheduler.

BehaviorScheduler, for all VirtualUniverse instances, maintains a list of BillBoard behaviors and behaviors in the system and calls the processStimulus method on each if marked for activation.

Inputdevicescheduler maintains lists of nonblocking and blocking input devices and calls the pollAndProcessInput method on the nonblocking devices. A new thread is created to handle the input from each blocking InputDevice.

RenderBin, for each View, maintains lists of RenderAtoms that describe rendering

operations to be performed. The RenderBin implements methods such as renderOpaque, renderBackground, renderOrdered, and renderTransparent, which are called by the Render class.

SoundScheduler, for each View, maintains a list of SoundSchedulerAtoms and marks each for activation. The render method iterates the scheduled SoundSchedulerAtoms and calls the start method on each.

  • renderWorkThreads:

Renderer for each Canvas3D’s Screen3D. The Render class runs the main Java 3D rendering loop by calling methods on the Canvas3D class and processing the RenderBin for the Canvas3D’s View.

  • requestRenderWorkThreads:

Renderer for each Screen3D in the device render map.

  • renderThreadData:

Renderer for each Screen3D for each View, including offscreen Renderers.

  • inputDeviceThreads:

A single Inputdevicescheduler.

Each UnorderList member contains an array of J3dThreadData objects, containing a J3dThread member and additional scheduling information.

The MasterControl.doWork method (invoked by the MasterControlThread.run method) runs in two phases. The first phase checks for any pending messages to be executed and returns if there is nothing to execute. The second phase runs the threads under the control of the MasterControl class.

void doWork()

{

 runMonitor( CHECK_FOR_WORK, null, null, null, null);

 if(pendingRequest)

     handlePendingRequest();

 if(!running)

     return;

 if(threadListsChanged)

     updateWorkThreads();

 updateTimeValues();

 View aview[] = (View[])views.toArray(false);

 for(int i = views.size() - 1; i >= 0; i--)

     if(aview[i].active)

         aview[i].updateViewCache();

runMonitor( RUN_THREADS, stateWorkThreads, renderWorkThreads,

             requestRenderWorkThreads, null);

 if(renderOnceList.size() > 0)

     clearRenderOnceList();

}

The actual runMonitor method is fairly complex, and it seems to perform the four functions shown in table 18.4, depending on the value of the first argument.

Table 18.4 runMonitor

img220.pngimg221.png

The other arguments are (in order): the array of State Work Threads, the array of Render Work Threads, and the array of Request Render Work Threads. As you can see in the following pseudocode, Java 3D has the ability to call the doWork method on each worker thread directly. This enables Java 3D to perform the work of multiple threads within a single thread.

For all Render Work Threads

If the Thread needs to be run (J3dThreadData member),

Check whether the View should be rendered based on its Minimum Frame

Cycle Time

Update the OrientatedShape3Ds in each View

If specified lock the geometry from updates,

If the CPU limit is 1, call the doWork method of the J3dThreadData’s

J3dThread directly

Otherwise call the runMonitor method (NOTIFY) to notify the thread

If specified release the lock on the geometry,

If specified, grab the current time and put it into the View’s

RenderBin

Next Rendering Thread

If specified, wait for all Rendering threads to complete

For all State Work Threads

If the CPU limit is 1, call the doWork method of the J3dThreadData’s

J3dThread directly

Otherwise call the runMonitor method (NOTIFY) to notify the thread

Next State Thread

If specified, wait for all State threads to complete

For all Request Render Work Threads

If the CPU limit is 1, call the doWork method of the J3dThreadData’s

J3dThread directly

Otherwise call the runMonitor method (NOTIFY) to notify the thread

Next State Thread

If specified, wait for all State threads to complete

Update the View Frame Timing Values

Update all the Mirror Objects registered with the MasterControl

Wait for Request Rendering to complete

Update the frame timestamps for each rendered View

J3dMessage

Java 3D communicates between its various worker threads using a message dispatch mechanism. Messages are instances of the J3dMessage class. Each message contains an identifier and a target VirtualUniverse as well as up to five parameters for the message (generic Objects). Certainly not the most OO of designs—in fact it reminds me of the WPARAM and LPARAM attributes on the MESSAGE structure used to propagate messages under Windows.

The message identifiers are defined in table 18.5.

Table 18.5 Message identifiers

img222.png

img223.png

img224.png

img225.png

The type member refers to one of the predefined message types from the table.

Each J3dMessage maintains a reference count so that it can clear the references to its arguments when its reference count reaches zero. Messages with a reference count of zero are placed back into a list of free messages, for subsequent reuse. This message instance cache minimizes the number of message objects created by the Java 3D at runtime in an attempt to prevent excessive garbage collection. Interestingly this type of Object pooling is no longer favored as Sun’s HotSpot compiler performs object pooling and reuse automatically.

The MasterControl method getMessage will either return a message from the free list if one is available or allocate a new J3dMessage instance, which will get added to the free list once its reference count reaches zero. The J3dMessage also maintains a reference to a View object and contains a thread identifier (bit-field) that identifies the threads that should receive the message, as shown in table 18.6.

Table 18.6 Java 3D Threads

img226.png

The sendMessage method updates appropriate message queue data structures based on the value of the thread identifier (table 18.7).

Table 18.7 Target thread identifiers for messages

img227.png

img228.png

18.3.1 System properties read by Java 3D

Java 3D reads a number of system properties, some documented and some internal. A list of these properties is also maintained on the J3D.ORG site at http://www.j3d.org/implementation/properties.html

Table 18.8 lists the system properties that are referenced in the Java code of the Java 3D distribution. Many more system properties are available which are specific to the OpenGL or DirectX versions of Java 3D. Please refer to the J3D.ORG website for the latest information on these properties.

Table 18.8 Thread Group: System

img229.pngimg230.png

18.4 BehaviorScheduler

The BehaviorScheduler is responsible for activating all the Behaviors registered with a VirtualUniverse. For active and activated Behaviors, the processStimulus method is called on the Behavior.

The BehaviorScheduler integrates tightly with the BehaviorStructure class, which maintains lists of all the Behaviors that have been created for a VirtualUniverse instance. The BehaviorStructure also contains much of the logic to determine when a Behavior has been activated, whether due to an AWT event, Behavior ID being posted, Bounds intersection, Sensor condition, or a Transform3D change.

18.5 InputDeviceScheduler

The InputDeviceScheduler maintains lists of nonblocking and blocking Java 3D InputDevices. It calls pollAndProcessInput on each nonblocking InputDevice.

18.6 Renderer

Name: J3D-Renderer-INSTANCE#

A Renderer instance is created for each screen device that is to be rendered into. The Renderer instances are kept in a static Hashtable in the Screen3D class. The Renderer calls into the Canvas3D instances (second JThreadData argument) that are available for rendering. If a device supports swapping, there may be multiple Canvas3Ds in the second argument.

Renderer uses the first argument, which is one of the GraphicsContext3D rendering commands (see table 18.9). The Renderer extracts the messages from the RendererStructure and calls methods on the GraphicsContext3D as appropriate.

The complex Renderer doWork method implements the main Java 3D rendering loop. It sets up the projection matrices, handles stereoscopic rendering, and performs the main rendering loop; specifically, it:

1. Clears the background using the background fill color

2. Calls preRender on the Canvas3D

3. Renders the background geometry

4. Sets the frustrum planes for rendering

5. Renders the opaque geometry

6. Renders the ordered geometry

7. Calls renderField on the Canvas3D

8. Renders the semitransparent geometry

9. Calls postRender on the Canvas3D

10. Performs offscreen rendering

18.6.1 GraphicsContext3D commands

Table 18.9 lists the rendering commands that are used along with a RendererStructure instance to determine which methods on the GraphicsContext3D need to be invoked to execute a given command.

Table 18.9 GraphicsContext3D commands

img231.png

18.6.2 RenderAtoms and RenderMolecule

RenderAtoms are self-contained rendering units that can be passed to the underlying rendering engine. The RenderAtom contains lights, fog, model clipping information, an Appearance, and a model transformation matrix. RenderAtoms can be linked using double linked-list RenderAtom members within the RenderAtom class.

Higher level rendering operations are described using the RenderMolecule class. In addition to maintaining a list of RenderAtoms, RenderMolecules are able to remove redundant changes in Appearance between consecutive RenderAtoms. In this way, the number of Appearance state changes (Appearance, Material, Transparency, etc.) performed by the underlying rendering engine is minimized.

18.7 StructureUpdateThread

The StructureUpdateThread is a J3dThread that can be attached to a J3dStructure object to perform message processing. The StructureUpdateThread instances are:

  • J3D-GeometryStructureUpdateThread
  • J3D-RenderStructureUpdateThread
  • J3D-BehaviorStructureUpdateThread
  • J3D-SoundStructureUpdateThread
  • J3D-RenderingAttributesStructureUpdateThread
  • J3D-RenderingEnvironmentStructureUpdateThread
  • J3D-TransformStructureUpdateThread
  • J3D-SoundSchedulerUpdateThread

The StructureUpdateThread is attached to an instance of a J3dStructure object, and its doWork method calls the processMessages method on the J3dStructure. The various classes derived from J3dStructure (such as SoundStructure) implement structure specific message execution.

18.8 TimerThread

The J3D-TimerThread manages:

  • Any number of WakeupOnElapsedTime objects, stored in a WakeupOnElaspsedTimeHeap (sorted, resizable array)
  • 1 InputDeviceScheduler (sampling time loaded from System property)
  • 1 SoundScheduler (WakeupOnElapsedTime every 2 minutes, by default)

The TimerThread will call the setTriggered method on each WakeupOnElapsedTime as appropriate.

18.9 SceneGraphObject

SceneGraphObject is the base class for all the objects that can be added to a Java 3D scenegraph. It includes a number of interesting capabilities and defines some general architectural principals for Java 3D (such as capability bits and the retained delegate class pattern). The attributes of the SceneGraphObject class are described in more detail next.

BitSet capabilities

A BitSet is an object that contains the capability bits that have been set on the SceneGraphObject.

SceneGraphObjectRetained retained

The retained field holds the private Java 3D implementation object for this SceneGraphObject. The SceneGraphObjectRetained maintains a reference to its source object and implements several setLive methods that can be overridden to respond in a Node specific manner. By having an internal delegate class separate from the public implementation class defined by the specification Sun has more leeway in modifying the implementation without breaking the API or exposing protected or package level access to methods or fields.

private boolean compiled

This field is true if this node has been compiled.

private boolean live

This field is true if this node has been attached to a live scenegraph.

private boolean liveOrCompiled

This field is true if this node has been attached to a live scenegraph or has been compiled.

private Object userData

This field holds a reference to the User Data object for this scenegraph node.

Hashtable nodeHashtable

Some ScenegraphObjects have a Hashtable of NodeComponents associated with them.

18.10 Node types

Table 18.10 contains a list of Node types and their identifiers.

Table 18.10 Thread Group: System

img232.png

img233.png

18.11 Exception Strings

Read from ExceptionStrings.properties (inside J3DCORE.JAR).

Appearance0=Appearance: no capability to set material

Appearance1=Appearance: no capability to get material

Appearance2=Appearance: no capability to set texture

Appearance3=Appearance: no capability to get texture

Appearance4=Appearance: no capability to set textureAttributes

Appearance5=Appearance: no capability to get textureAttributes

And so forth…

18.12 J3D DLL

The native methods within the Java 3D implementation are packaged within J3D.DLL (for Windows) and called using JNI from the Java code. The native code implements a procedural API for both OpenGL and DirectX rendering.

There are many programming tools that can list