Package javax.media.j3d

Source Code of javax.media.j3d.MasterControl

/*
* Copyright 1998-2008 Sun Microsystems, Inc.  All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.  Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*
*/

/*
* Portions of this code were derived from work done by the Blackdown
* group (www.blackdown.org), who did the initial Linux implementation
* of the Java 3D API.
*/

package javax.media.j3d;

import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.logging.Level;
import java.util.logging.Logger;

class MasterControl {

    /**
     * Options for the runMonitor
     */
    static final int CHECK_FOR_WORK = 0;
    static final int SET_WORK       = 1;
    static final int RUN_THREADS    = 2;
    static final int THREAD_DONE    = 3;
    static final int SET_WORK_FOR_REQUEST_RENDERER   = 5;
    static final int RUN_RENDERER_CLEANUP            = 6;

    // The thread states for MC
    static final int SLEEPING            = 0;
    static final int RUNNING             = 1;
    static final int WAITING_FOR_THREADS = 3;
    static final int WAITING_FOR_CPU     = 4;
    static final int WAITING_FOR_RENDERER_CLEANUP = 5;

    // Constants used in renderer thread argument
    static final Integer REQUESTRENDER = new Integer(Renderer.REQUESTRENDER);
    static final Integer RENDER = new Integer(Renderer.RENDER);
    static final Integer SWAP = new Integer(Renderer.SWAP);

    // Constants used for request from user threads
    static final Integer ACTIVATE_VIEW = new Integer(1);
    static final Integer DEACTIVATE_VIEW = new Integer(2);
    static final Integer START_VIEW = new Integer(3);
    static final Integer STOP_VIEW = new Integer(4);
    static final Integer REEVALUATE_CANVAS = new Integer(5);
    static final Integer UNREGISTER_VIEW = new Integer(6);
    static final Integer PHYSICAL_ENV_CHANGE = new Integer(7);
    static final Integer INPUTDEVICE_CHANGE = new Integer(8);
    static final Integer EMPTY_UNIVERSE = new Integer(9);
    static final Integer START_RENDERER = new Integer(10);
    static final Integer STOP_RENDERER = new Integer(11);
    static final Integer RENDER_ONCE = new Integer(12);
    static final Integer FREE_CONTEXT = new Integer(13);
    static final Integer FREE_DRAWING_SURFACE = new Integer(14);
    static final Integer FREE_MESSAGE = new Integer(15);
    static final Integer RESET_CANVAS = new Integer(16);
    static final Integer GETBESTCONFIG = new Integer(17);
    static final Integer ISCONFIGSUPPORT = new Integer(18);
    static final Integer SET_GRAPHICSCONFIG_FEATURES = new Integer(19);
    static final Integer SET_QUERYPROPERTIES = new Integer(20);
    static final Integer SET_VIEW = new Integer(21);

    // Developer logger for reporting informational messages; see getDevLogger()
    private static boolean devLoggerEnabled = false;
    private static Logger devLogger;

    // Stats logger for reporting runtime statistics; see getStatsLogger()
    private static boolean statsLoggerEnabled = false;
    private static Logger statsLogger;

    // Core logger for reporting internal errors, warning, and
    // informational messages; see getCoreLogger()
    private static boolean coreLoggerEnabled = false;
    private static Logger coreLogger;

    // Flag indicating that the rendering pipeline libraries are loaded
    private static boolean librariesLoaded = false;

    /**
     * reference to MasterControl thread
     */
    private MasterControlThread mcThread = null;

    /**
     * The list of views that are currently registered
     */
    private UnorderList views = new UnorderList(1, View.class);


    /**
     * by MIK OF CLASSX
     *
     * the flag to indicate whether the background of the offscreen
     * canvas must be transparent or not false by default
     */
    boolean transparentOffScreen = false;

    /**
     * Flag to indicate whether Pbuffers are used for off-screen
     * rendering; true by default.  Set by the "j3d.usePbuffer"
     * property, When this flag is set to false, Bitmap (Windows) or
     * Pixmap (UNIX) rendering will be used
     */
    boolean usePbuffer = true;

    /**
     * Flag to indicate whether should renderer view frustum culling is done;
     * true by default.
     * Set by the -Dj3d.viewFrustumCulling property, When this flag is
     * set to false, the renderer view frustum culling is turned off.
     */
    boolean viewFrustumCulling = true;

    /**
     * the flag to indicate whether the geometry should be locked or not
     */

    private boolean lockGeometry = false;

    /**
     * The number of registered views that are active
     */
    private int numActiveViews = 0;

    /**
     * The list of active universes get from View
     */
    private UnorderList activeUniverseList = new UnorderList(VirtualUniverse.class);

    /**
     * The list of universes register from View
     */
    private UnorderList regUniverseList = new UnorderList(VirtualUniverse.class);

    /**
     * A lock used for accessing time structures.
     */
    private Object timeLock = new Object();


    /**
     * The current "time" value
     */
    private long time = 0;

    /**
     * Use to assign threadOpts in Renderer thread.
     */
    private long waitTimestamp = 0;

    /**
     * The current list of work threads
     */
    private UnorderList stateWorkThreads =
                               new UnorderList(J3dThreadData.class);
    private UnorderList renderWorkThreads =
                               new UnorderList(J3dThreadData.class);
    private UnorderList requestRenderWorkThreads =
             new UnorderList(J3dThreadData.class);

    /**
     * The current list of work threads
     */
    private UnorderList renderThreadData = new UnorderList(J3dThreadData.class);

    /**
     * The list of input device scheduler thread
     */
    private UnorderList inputDeviceThreads =
                             new UnorderList(1, InputDeviceScheduler.class);

    /**
     * A flag that is true when the thread lists need updating
     */
    private boolean threadListsChanged;


    /**
     * Markers for the last transform structure update thread
     * and the last update thread.
     */
    private int lastTransformStructureThread = 0;
    private int lastStructureUpdateThread = 0;

    /**
     * The current time snapshots
     */
    private long currentTime;

    // Only one Timer thread in the system.
    TimerThread timerThread;

    // Only one Notification thread in the system.
    private NotificationThread notificationThread;

    /**
     * This flag indicates that MC is running
     */
    volatile boolean running = true;

    /**
     * This flag indicates that MC has work to do
     */
    private boolean workToDo = false;

    /**
     * This flag indicates that there is work for requestRenderer
     */
    private boolean requestRenderWorkToDo = false;

   /**
     * The number of THREAD_DONE messages pending
     */
    private int threadPending = 0;
    private int renderPending = 0;
    private int statePending = 0;

    /**
     * State variables for work lists
     */
    private boolean renderWaiting = false;
    private boolean stateWaiting = false;

    /**
     * The current state of the MC thread
     */
    private int state = SLEEPING;

    // time for sleep in order to met the minimum frame duration
    private long sleepTime = 0;


    /**
     * The number of cpu's Java 3D may use
     */
    private int cpuLimit;

    /**
     * A list of mirror objects to be updated
     */
    private UnorderList mirrorObjects = new UnorderList(ObjectUpdate.class);

    /**
     * The renderingAttributesStructure for updating node component
     * objects
     */
    private RenderingAttributesStructure renderingAttributesStructure =
                        new RenderingAttributesStructure();

    /**
     * The default rendering method
     */
    private DefaultRenderMethod defaultRenderMethod = null;

    /**
     * The text3D rendering method
     */
    private Text3DRenderMethod text3DRenderMethod = null;

    /**
     * The vertex array rendering method
     */
    private VertexArrayRenderMethod vertexArrayRenderMethod = null;

    /**
     * The displayList rendering method
     */
    private DisplayListRenderMethod displayListRenderMethod = null;

    /**
     * The compressed geometry rendering method
     */
    private CompressedGeometryRenderMethod compressedGeometryRenderMethod = null;

    /**
     * The oriented shape3D rendering method
     */
    private OrientedShape3DRenderMethod orientedShape3DRenderMethod = null;

    /**
     * This is the start time upon which alpha's and behaviors
     * are synchronized to. It is initialized once, the first time
     * that a MasterControl object is created.
     */
    static long systemStartTime = 0L;

    // This is a time stamp used when context is created
    private long contextTimeStamp = 0;

    // This is an array of  canvasIds in used
    private boolean[] canvasIds = null;
    private int canvasFreeIndex = 0;
    private Object canvasIdLock = new Object();

    // This is a counter for rendererBit
    private int rendererCount = 0;

    // Flag that indicates whether to shared display context or not
    boolean isSharedCtx = false;

    // Flag that tells us to use NV_register_combiners
    boolean useCombiners = false;

    // Flag that indicates whether compile is disabled or not
    boolean disableCompile = false;

    // Flag that indicates whether or not compaction occurs
    boolean doCompaction = true;

    // Flag that indicates whether separate specular color is disabled or not
    boolean disableSeparateSpecularColor = false;

    // Flag that indicates whether DisplayList is used or not
    boolean isDisplayList = true;

    // If this flag is set, then by-ref geometry will not be
    // put in display list
    boolean buildDisplayListIfPossible = false;

    // If this flag is set, then geometry arrays with vertex attributes can
    // be in display list.
    boolean vertexAttrsInDisplayList = false;

    // Issue 249 - flag that indicates whether the soleUser optimization is permitted
    boolean allowSoleUser = false;

    // Issue 266 - Flag indicating whether null graphics configs are allowed
    // Set by -Dj3d.allowNullGraphicsConfig property
    // Setting this flag causes Canvas3D to allow a null GraphicsConfiguration
    // for on-screen canvases. This is only for backward compatibility with
    // legacy applications.
    boolean allowNullGraphicsConfig = false;

    // Issue 239 - Flag indicating whether the stencil buffer is cleared by
    // default each frame when the color and depth buffers are cleared.
    // Note that this is a partial solution, since we eventually want an API
    // to control this.
    boolean stencilClear = false;

    // REQUESTCLEANUP messages argument
    static Integer REMOVEALLCTXS_CLEANUP = new Integer(1);
    static Integer REMOVECTX_CLEANUP     = new Integer(2);
    static Integer REMOVENOTIFY_CLEANUP  = new Integer(3);
    static Integer RESETCANVAS_CLEANUP   = new Integer(4);
    static Integer FREECONTEXT_CLEANUP   = new Integer(5);

    // arguments for renderer resource cleanup run
    Object rendererCleanupArgs[] = {new Integer(Renderer.REQUESTCLEANUP),
            null, null};


    // Context creation should obtain this lock, so that
    // first_time and all the extension initilialization
    // are done in the MT safe manner
    Object contextCreationLock = new Object();

    // Flag that indicates whether to lock the DSI while rendering
    boolean doDsiRenderLock = false;

    // Flag that indicates the pre-1.5 behavior of enforcing power-of-two
    // textures. If set, then any non-power-of-two textures will throw an
    // exception.
    boolean enforcePowerOfTwo = false;

    // Flag that indicates whether the framebuffer is sharing the
    // Z-buffer with both the left and right eyes when in stereo mode.
    // If this is true, we need to clear the Z-buffer between rendering
    // to the left and right eyes.
    boolean sharedStereoZBuffer = true;

    // True to disable all underlying multisampling API so it uses
    // the setting in the driver.
    boolean implicitAntialiasing = false;

    // False to disable compiled vertex array extensions if support
    boolean isCompiledVertexArray = true;

    // Number of reserved vertex attribute locations for GLSL (must be at
    // least 1).
    // Issue 269 - need to reserve up to 6 vertex attribtue locations to ensure
    // that we don't collide with a predefined gl_* attribute on nVidia cards.
    int glslVertexAttrOffset = 6;

    // Hashtable that maps a GraphicsDevice to its associated
    // Screen3D--this is only used for on-screen Canvas3Ds
    Hashtable<GraphicsDevice, Screen3D> deviceScreenMap = new Hashtable<GraphicsDevice, Screen3D>();

    // Use to store all requests from user threads.
    UnorderList requestObjList = new UnorderList();
    private UnorderList requestTypeList = new UnorderList(Integer.class);

    // Temporary storage to store stop request for requestViewList
    private UnorderList tempViewList = new UnorderList();
    private UnorderList renderOnceList = new UnorderList();

    // This flag is true when there is pending request
    // i.e. false when the above requestxxx Lists are all empty.
    private boolean pendingRequest = false;

    // Root ThreadGroup for creating Java 3D threads
    private static ThreadGroup rootThreadGroup;

    // Thread priority for all Java 3D threads
    private static int threadPriority;

    static private Object mcThreadLock = new Object();

  private ArrayList<View> timestampUpdateList = new ArrayList<View>(3);

    private UnorderList freeMessageList = new UnorderList(8);

    // Maximum number of lights
    int maxLights;

    // Set by the -Dj3d.sortShape3DBounds property, When this flag is
    // set to true, the bounds of the Shape3D node will be used in
    // place of the computed GeometryArray bounds for transparency
    // sorting for those Shape3D nodes whose boundsAutoCompute
    // attribute is set to false.
    boolean sortShape3DBounds = false;

    //Set by -Dj3d.forceReleaseView property.
    //Setting this flag as true disables the bug fix 4267395 in View deactivate().
    //The bug 4267395 can lock-up *some* systems, but the bug fix can
    //produce memory leaks in applications which creates and destroy Canvas3D
    //from time to time.
    //Set as true if you have memory leaks after disposing Canvas3D.
    //Default false value does affect Java3D View dispose behavior.
    boolean forceReleaseView = false;

    // Issue 480: Cache the bounds of nodes so that getBounds does not
    // recompute the boounds of the entire graph per call
    boolean cacheAutoComputedBounds = false;

    // issue 544
    boolean useBoxForGroupBounds = false;

    /**
     * Constructs a new MasterControl object.  Note that there is
     * exatly one MasterControl object, created statically by
     * VirtualUniverse.
     */
    MasterControl() {
        assert librariesLoaded;

        // Initialize the start time upon which alpha's and behaviors
        // are synchronized to (if it isn't already set).
        if (systemStartTime == 0L) {
            systemStartTime = J3dClock.currentTimeMillis();
        }

  if(J3dDebug.devPhase) {
      // Check to see whether debug mode is allowed
      J3dDebug.debug = getBooleanProperty("j3d.debug", false,
            "J3dDebug.debug");
  }

  // Check to see whether shared contexts are allowed
  isSharedCtx = getBooleanProperty("j3d.sharedctx", isSharedCtx, "shared contexts");

  doCompaction = getBooleanProperty("j3d.docompaction", doCompaction,
            "compaction");

  // by MIK OF CLASSX
  transparentOffScreen = getBooleanProperty("j3d.transparentOffScreen", transparentOffScreen, "transparent OffScreen");

  usePbuffer = getBooleanProperty("j3d.usePbuffer",
          usePbuffer,
          "Off-screen Pbuffer");

  viewFrustumCulling = getBooleanProperty("j3d.viewFrustumCulling", viewFrustumCulling,"View frustum culling in the renderer is");

  sortShape3DBounds =
      getBooleanProperty("j3d.sortShape3DBounds", sortShape3DBounds,
             "Shape3D bounds enabled for transparency sorting",
             "Shape3D bounds *ignored* for transparency sorting");

  forceReleaseView =
      getBooleanProperty("j3d.forceReleaseView", forceReleaseView,
             "forceReleaseView  after Canvas3D dispose enabled",
             "forceReleaseView  after Canvas3D dispose disabled");

// FIXME: GL_NV_register_combiners
//  useCombiners = getBooleanProperty("j3d.usecombiners", useCombiners,
//            "Using NV_register_combiners if available",
//            "NV_register_combiners disabled");

  if (getProperty("j3d.disablecompile") != null) {
      disableCompile = true;
      System.err.println("Java 3D: BranchGroup.compile disabled");
  }

  if (getProperty("j3d.disableSeparateSpecular") != null) {
      disableSeparateSpecularColor = true;
      System.err.println("Java 3D: separate specular color disabled if possible");
  }

  isDisplayList = getBooleanProperty("j3d.displaylist", isDisplayList,
             "display list");

  implicitAntialiasing =
      getBooleanProperty("j3d.implicitAntialiasing",
             implicitAntialiasing,
             "implicit antialiasing");

  isCompiledVertexArray =
      getBooleanProperty("j3d.compiledVertexArray",
             isCompiledVertexArray,
             "compiled vertex array");

        boolean j3dOptimizeSpace =
      getBooleanProperty("j3d.optimizeForSpace", true,
             "optimize for space");

        if (isDisplayList) {
            // Build Display list for by-ref geometry
            // ONLY IF optimizeForSpace is false
            if (!j3dOptimizeSpace) {
                buildDisplayListIfPossible = true;
            }

            // Build display lists for geometry with vertex attributes
            // ONLY if we are in GLSL mode and GLSL shaders are available
            vertexAttrsInDisplayList = true;
        }

        // Check to see whether Renderer can run without DSI lock
  doDsiRenderLock = getBooleanProperty("j3d.renderLock",
               doDsiRenderLock,
               "render lock");

        // Check to see whether we enforce power-of-two textures
        enforcePowerOfTwo = getBooleanProperty("j3d.textureEnforcePowerOfTwo",
                 enforcePowerOfTwo,
                 "checking power-of-two textures");

        // Issue 249 - check to see whether the soleUser optimization is permitted
        allowSoleUser = getBooleanProperty("j3d.allowSoleUser",
             allowSoleUser,
             "sole-user mode");

        // Issue 266 - check to see whether null graphics configs are allowed
        allowNullGraphicsConfig = getBooleanProperty("j3d.allowNullGraphicsConfig",
                 allowNullGraphicsConfig,
                 "null graphics configs");

        // Issue 239 - check to see whether per-frame stencil clear is enabled
        stencilClear = getBooleanProperty("j3d.stencilClear",
                                          stencilClear,
                                          "per-frame stencil clear");

        // Check to see if stereo mode is sharing the Z-buffer for both eyes.
  sharedStereoZBuffer =
      getBooleanProperty("j3d.sharedstereozbuffer",
             sharedStereoZBuffer,
             "shared stereo Z buffer");

  // Get the maximum number of concurrent threads (CPUs)
  final int defaultThreadLimit = getNumberOfProcessors() + 1;
  Integer threadLimit = java.security.AccessController.doPrivileged(
  new java.security.PrivilegedAction<Integer>() {
    @Override
    public Integer run() {
      return Integer.getInteger("j3d.threadLimit", defaultThreadLimit);
    }
  });

  cpuLimit = threadLimit.intValue();
  if (cpuLimit < 1)
      cpuLimit = 1;
  if (J3dDebug.debug || cpuLimit != defaultThreadLimit) {
      System.err.println("Java 3D: concurrent threadLimit = " +
             cpuLimit);
  }

  // Get the input device scheduler sampling time
  Integer samplingTime = java.security.AccessController.doPrivileged(
  new java.security.PrivilegedAction<Integer>() {
    @Override
    public Integer run() {
      return Integer.getInteger("j3d.deviceSampleTime", 0);
    }
  });

  if (samplingTime.intValue() > 0) {
      InputDeviceScheduler.samplingTime =
    samplingTime.intValue();
      System.err.println("Java 3D: Input device sampling time = "
             + samplingTime + " ms");
  }

  // Get the glslVertexAttrOffset
  final int defaultGLSLVertexAttrOffset = glslVertexAttrOffset;
  Integer vattrOffset = java.security.AccessController.doPrivileged(
  new java.security.PrivilegedAction<Integer>() {
    @Override
    public Integer run() {
      return Integer.getInteger("j3d.glslVertexAttrOffset",
          defaultGLSLVertexAttrOffset);
    }
  });

  glslVertexAttrOffset = vattrOffset.intValue();
        if (glslVertexAttrOffset < 1) {
            glslVertexAttrOffset = 1;
        }
  if (J3dDebug.debug || glslVertexAttrOffset != defaultGLSLVertexAttrOffset) {
      System.err.println("Java 3D: glslVertexAttrOffset = " +
             glslVertexAttrOffset);
  }

        // Issue 480 : Cache bounds returned by getBounds()
        cacheAutoComputedBounds =
                getBooleanProperty("j3d.cacheAutoComputeBounds",
                cacheAutoComputedBounds,
                "Cache AutoCompute Bounds, accelerates getBounds()");

        // Issue 544
        useBoxForGroupBounds =
                getBooleanProperty("j3d.useBoxForGroupBounds",
                useBoxForGroupBounds,
                "Use of BoundingBox for group geometric bounds");

        // Check for obsolete properties
        String[] obsoleteProps = {
            "j3d.backgroundtexture",
            "j3d.forceNormalized",
            "j3d.g2ddrawpixel",
            "j3d.simulatedMultiTexture",
            "j3d.useFreeLists",
        };
        for (int i = 0; i < obsoleteProps.length; i++) {
            if (getProperty(obsoleteProps[i]) != null) {
                System.err.println("Java 3D: " + obsoleteProps[i] + " property ignored");
            }
        }

  // Get the maximum Lights
  maxLights = Pipeline.getPipeline().getMaximumLights();

  // create the freelists
  FreeListManager.createFreeLists();

  // create an array canvas use registers
  // The 32 limit can be lifted once the
  // resourceXXXMasks in other classes
  // are change not to use integer.
  canvasIds = new boolean[32];
  for(int i=0; i<canvasIds.length; i++) {
      canvasIds[i] = false;
  }
        canvasFreeIndex = 0;
    }

    private static boolean initLogger(Logger logger, Level defaultLevel) {
        if (logger == null) {
            return false;
        }

        if (defaultLevel != null &&
                logger.getLevel() == null &&
                Logger.getLogger("j3d").getLevel() == null) {

            try {
                // Set default logger level rather than inheriting from system global
                logger.setLevel(defaultLevel);
            } catch (SecurityException ex) {
                System.err.println(ex);
                return false;
            }
        }

        return logger.isLoggable(Level.SEVERE);
    }

    // Called by the static initializer to initialize the loggers
    private static void initLoggers() {
        coreLogger = Logger.getLogger("j3d.core");
        devLogger = Logger.getLogger("j3d.developer");
        statsLogger = Logger.getLogger("j3d.stats");

        java.security.AccessController.doPrivileged(
            new java.security.PrivilegedAction<Object>() {
                @Override
                public Object run() {
                    coreLoggerEnabled = initLogger(coreLogger, null);
                    devLoggerEnabled = initLogger(devLogger, Level.OFF);
                    statsLoggerEnabled = initLogger(statsLogger, Level.OFF);
                    return null;
                }
        });
    }

    /**
     * Get the developer logger -- OFF by default
     *
     * WARNING - for probable incorrect or inconsistent api usage
     * INFO - for informational messages such as performance hints (less verbose than FINE)
     * FINE - for informational messages from inner loops
     * FINER - using default values which may not be optimal
     */
    static Logger getDevLogger() {
        return devLogger;
    }

    static boolean isDevLoggable(Level level) {
        return devLoggerEnabled && devLogger.isLoggable(level);
    }

    /**
     * Get the stats logger -- OFF by default
     *
     * WARNING - statistical anomalies
     * INFO - basic performance stats - not too verbose and minimally intrusive
     * FINE - somewhat verbose and intrusive
     * FINER - more verbose and intrusive
     * FINEST - most verbose and intrusive
     */
    static Logger getStatsLogger() {
        return statsLogger;
    }

    static boolean isStatsLoggable(Level level) {
        return statsLoggerEnabled && statsLogger.isLoggable(level);
    }

    /**
     * Get the core logger -- level is INFO by default
     *
     * SEVERE - Serious internal errors
     * WARNING - Possible internal errors or anomalies
     * INFO - General informational messages
     * FINE - Internal debugging information - somewhat verbose
     * FINER - Internal debugging information - more verbose
     * FINEST - Internal debugging information - most verbose
     */
    static Logger getCoreLogger() {
        return coreLogger;
    }

    static boolean isCoreLoggable(Level level) {
        return coreLoggerEnabled && coreLogger.isLoggable(level);
    }

private static String getProperty(final String prop) {
  return java.security.AccessController.doPrivileged(
    new java.security.PrivilegedAction<String>() {
      @Override
      public String run() {
        return System.getProperty(prop);
      }
    });
}

    static boolean getBooleanProperty(String prop,
                boolean defaultValue,
                String trueMsg,
                String falseMsg) {
  boolean value = defaultValue;
  String propValue = getProperty(prop);

  if (propValue != null) {
      value = Boolean.valueOf(propValue).booleanValue();
    if (J3dDebug.debug)
      System.err.println("Java 3D: " + (value ? trueMsg : falseMsg));
  }
  return value;
    }

    static boolean getBooleanProperty(String prop,
                boolean defaultValue,
                String msg) {
  return getBooleanProperty(prop,
          defaultValue,
          (msg + " enabled"),
          (msg + " disabled"));
    }

    /**
     * Method to create and initialize the rendering Pipeline object,
     * and to load the native libraries needed by Java 3D. This is
     * called by the static initializer in VirtualUniverse <i>before</i>
     * the MasterControl object is created.
     */
    static void loadLibraries() {
        assert !librariesLoaded;

        // Initialize the Pipeline object associated with the
        // renderer specified by the "j3d.rend" system property.
        //
        // XXXX : We should consider adding support for a more flexible,
        // dynamic selection scheme via an API call.

        // Default rendering pipeline is the JOGL pipeline
        Pipeline.Type pipelineType = Pipeline.Type.JOGL;

        final String rendStr = getProperty("j3d.rend");
        if (rendStr == null) {
            // Use default pipeline
        } else if (rendStr.equals("jogl")) {
            pipelineType = Pipeline.Type.JOGL;
        } else if (rendStr.equals("noop")) {
            pipelineType = Pipeline.Type.NOOP;
        } else {
            System.err.println("Java 3D: Unrecognized renderer: " + rendStr);
            // Use default pipeline
        }

        // Construct the singleton Pipeline instance
    Pipeline.createPipeline(pipelineType);

        librariesLoaded = true;
    }


    /**
     * Invoke from InputDeviceScheduler to create an
     * InputDeviceBlockingThread.
     */
    InputDeviceBlockingThread getInputDeviceBlockingThread(
             final InputDevice device) {

  return java.security.AccessController.doPrivileged(
    new java.security.PrivilegedAction<InputDeviceBlockingThread>() {
      @Override
      public InputDeviceBlockingThread run() {
        synchronized (rootThreadGroup) {
          InputDeviceBlockingThread thread = new InputDeviceBlockingThread(
              rootThreadGroup, device);
          thread.setPriority(threadPriority);
          return thread;
        }
      }
    });
    }

    /**
     * Set thread priority to all threads under Java3D thread group.
     */
    void setThreadPriority(final int pri) {
  synchronized (rootThreadGroup) {
      threadPriority = pri;
      java.security.AccessController.doPrivileged(
                new java.security.PrivilegedAction<Object>() {
                    @Override
                    public Object run() {
      Thread list[] = new
          Thread[rootThreadGroup.activeCount()];
      int count = rootThreadGroup.enumerate(list);
      for (int i=count-1; i >=0; i--) {
          list[i].setPriority(pri);
      }
      return null;
        }
      });
  }
    }


    /**
     * Return Java3D thread priority
     */
    int getThreadPriority() {
  return threadPriority;
    }

    /**
     * This returns the a unused renderer bit
     */
    int getRendererBit() {
        return (1 << rendererCount++);
    }


    /**
     * This returns the a unused renderer bit
     */
    int getRendererId() {
        return rendererCount++;
    }

    /**
     * This returns a context creation time stamp
     * Note: this has to be called under the contextCreationLock
     */
    long getContextTimeStamp() {
  return (++contextTimeStamp);
    }


    /**
     * This returns the a unused displayListId
     */
    Integer getDisplayListId() {
        return (Integer) FreeListManager.getObject(FreeListManager.DISPLAYLIST);
    }

    void freeDisplayListId(Integer id) {
  FreeListManager.freeObject(FreeListManager.DISPLAYLIST, id);
    }

    int getCanvasId() {
        int i;

  synchronized(canvasIdLock) {
      // Master control need to keep count itself
      for(i=canvasFreeIndex; i<canvasIds.length; i++) {
    if(canvasIds[i] == false)
        break;
      }

      if (i >= canvasIds.length) {
                throw new RuntimeException("Cannot render to more than 32 Canvas3Ds");
      }

      canvasIds[i] = true;
      canvasFreeIndex = i + 1;
  }

        return i;

    }

    void freeCanvasId(int canvasId) {
        // Valid range is [0, 31]
  synchronized(canvasIdLock) {

      canvasIds[canvasId] = false;
      if(canvasFreeIndex > canvasId) {
    canvasFreeIndex = canvasId;
      }
  }
    }


    /**
     * Create a Renderer if it is not already done so.
     * This is used for GraphicsConfigTemplate3D passing
     * graphics call to RequestRenderer, and for creating
     * an off-screen buffer for an off-screen Canvas3D.
     */
    private Renderer createRenderer(GraphicsConfiguration gc) {
  final GraphicsDevice gd = gc.getDevice();

  Renderer rdr = Screen3D.deviceRendererMap.get(gd);
  if (rdr != null) {
      return rdr;
  }


  java.security.AccessController.doPrivileged(
       new java.security.PrivilegedAction<Object>() {
                    @Override
                    public Object run() {
      Renderer r;
            synchronized (rootThreadGroup) {
          r = new Renderer(rootThreadGroup);
          r.initialize();
          r.setPriority(threadPriority);
          Screen3D.deviceRendererMap.put(gd, r);
      }
      return null;
       }
  });

  threadListsChanged = true;

  return Screen3D.deviceRendererMap.get(gd);
    }

    /**
     * Post the request in queue
     */
    void postRequest(Integer type, Object obj) {

  synchronized (mcThreadLock) {
      synchronized (requestObjList) {
    if (mcThread == null) {
        if ((type == ACTIVATE_VIEW) ||
      (type == GETBESTCONFIG) ||
      (type == SET_VIEW) ||
      (type == ISCONFIGSUPPORT) ||
      (type == SET_QUERYPROPERTIES) ||
      (type == SET_GRAPHICSCONFIG_FEATURES)) {
      createMasterControlThread();
      requestObjList.add(obj);
      requestTypeList.add(type);
      pendingRequest = true;
        } else if (type == EMPTY_UNIVERSE) {
      destroyUniverseThreads((VirtualUniverse) obj);
        } else if (type == STOP_VIEW) {
      View v = (View) obj;
      v.stopViewCount = -1;
      v.isRunning = false;
        } else if (type == STOP_RENDERER) {
      if (obj instanceof Canvas3D) {
          ((Canvas3D) obj).isRunningStatus = false;
      } else {
          ((Renderer) obj).userStop = true;
      }
        } else if (type == UNREGISTER_VIEW) {
      ((View) obj).doneUnregister = true;
        } else {
      requestObjList.add(obj);
      requestTypeList.add(type);
      pendingRequest = true;
        }
    } else {
        requestObjList.add(obj);
        requestTypeList.add(type);
        pendingRequest = true;
    }
      }
  }

  setWork();
    }




    /**
     * This procedure is invoked when isRunning is false.
     * Return true when there is no more pending request so that
     * Thread can terminate. Otherwise we have to recreate
     * the MC related threads.
     */
    boolean mcThreadDone() {
  synchronized (mcThreadLock) {
      synchronized (requestObjList) {
    if (!pendingRequest) {
        mcThread = null;
        if (renderingAttributesStructure.updateThread !=
      null) {
      renderingAttributesStructure.updateThread.finish();
      renderingAttributesStructure.updateThread =
          null;
        }
        renderingAttributesStructure = new RenderingAttributesStructure();
        if (timerThread != null) {
      timerThread.finish();
      timerThread = null;
        }
        if (notificationThread != null) {
      notificationThread.finish();
      notificationThread = null;
        }
        requestObjList.clear();
        requestTypeList.clear();
        return true;
    }
    running = true;
    createMCThreads();
    return false;
      }
  }
    }

    /**
     * This method increments and returns the next time value
     * timeLock must get before this procedure is invoked
     */
    final long getTime() {
  return (time++);
    }


    /**
     * This takes a given message and parses it out to the structures and
     * marks its time value.
     */
    void processMessage(J3dMessage message) {

        synchronized (timeLock) {
      message.time = getTime();
      sendMessage(message);
  }
  setWork();
    }

    /**
     * This takes an array of messages and parses them out to the structures and
     * marks the time value. Make sure, setWork() is done at the very end
     * to make sure all the messages will be processed in the same frame
     */
    void processMessage(J3dMessage[] messages) {

        synchronized (timeLock) {
      long time = getTime();

      for (int i = 0; i < messages.length; i++) {
    messages[i].time = time;
    sendMessage(messages[i]);
      }
  }
  setWork();
    }

    /**
     * This takes the specified notification message and sends it to the
     * notification thread for processing.
     */
    void sendNotification(J3dNotification notification) {
        notificationThread.addNotification(notification);
    }

    /**
     * Create and start the MasterControl Thread.
     */
    void createMasterControlThread() {
        // Issue 364: don't create master control thread if already created
        if (mcThread != null) {
            return;
        }

  running = true;
  workToDo = true;
  state = RUNNING;
  java.security.AccessController.doPrivileged(
      new java.security.PrivilegedAction<Object>() {
                @Override
                public Object run() {
        synchronized (rootThreadGroup) {
      mcThread = new
          MasterControlThread(rootThreadGroup);
      mcThread.setPriority(threadPriority);
        }
        return null;
    }
  });
    }

    // assuming the timeLock is already acquired

    /**
     * Send a message to another Java 3D thread.
     */
    void sendMessage(J3dMessage message) {

     synchronized (message) {
      VirtualUniverse u = message.universe;
      int targetThreads = message.threads;

            if (isCoreLoggable(Level.FINEST)) {
                dumpMessage("sendMessage", message);
            }

      if ((targetThreads & J3dThread.UPDATE_RENDERING_ATTRIBUTES) != 0) {
    renderingAttributesStructure.addMessage(message);
      }

      // GraphicsContext3D send message with universe = null
      if (u != null) {
    if ((targetThreads & J3dThread.UPDATE_GEOMETRY) != 0) {
        u.geometryStructure.addMessage(message);
    }
    if ((targetThreads & J3dThread.UPDATE_TRANSFORM) != 0) {
        u.transformStructure.addMessage(message);
    }
    if ((targetThreads & J3dThread.UPDATE_BEHAVIOR) != 0) {
        u.behaviorStructure.addMessage(message);
    }
    if ((targetThreads & J3dThread.UPDATE_SOUND) != 0) {
        u.soundStructure.addMessage(message);
    }
    if ((targetThreads & J3dThread.UPDATE_RENDERING_ENVIRONMENT) != 0) {
        u.renderingEnvironmentStructure.addMessage(message);
    }
      }

            if ((targetThreads & J3dThread.SOUND_SCHEDULER) != 0) {
                // Note that we don't check for active view
                if (message.view != null && message.view.soundScheduler != null ) {
                    // This make sure that message won't lost even
                    // though this view not yet register
                    message.view.soundScheduler.addMessage(message);
                } else {
        synchronized (views) {
      View v[] = (View []) views.toArray(false);
      int i = views.arraySize()-1;
      if (u == null) {
          while (i>=0) {
        v[i--].soundScheduler.addMessage(message);
          }
      } else {
          while (i>=0) {
        if (v[i].universe == u) {
            v[i].soundScheduler.addMessage(message);
        }
        i--;
          }
      }
        }
                }
            }

      if ((targetThreads & J3dThread.UPDATE_RENDER) != 0) {
    // Note that we don't check for active view
    if (message.view != null && message.view.renderBin != null) {
        // This make sure that message won't lost even
        // though this view not yet register
        message.view.renderBin.addMessage(message);
    } else {
        synchronized (views) {
      View v[] = (View []) views.toArray(false);
      int i = views.arraySize()-1;
      if (u == null) {
          while (i>=0) {
        v[i--].renderBin.addMessage(message);
          }
      }
      else {
          while (i>=0) {
        if (v[i].universe == u) {
            v[i].renderBin.addMessage(message);
        }
        i--;
          }
      }
        }
    }
      }

      if (message.getRefcount() == 0) {
    message.clear();
      }
    }
    }


    /**
     * Send a message to another Java 3D thread.
     * This variant is only call by TimerThread for Input Device Scheduler
     * or to redraw all View for RenderThread
     */
    void sendRunMessage(int targetThreads) {

  synchronized (timeLock) {

      long time = getTime();

      if ((targetThreads & J3dThread.INPUT_DEVICE_SCHEDULER) != 0) {
    synchronized (inputDeviceThreads) {
        InputDeviceScheduler ds[] = (InputDeviceScheduler [])
      inputDeviceThreads.toArray(false);
        for (int i=inputDeviceThreads.size()-1; i >=0; i--) {
      if (ds[i].physicalEnv.activeViewRef > 0) {
          ds[i].getThreadData().lastUpdateTime =
        time;
      }
        }

        // timerThread instance in MC will set to null in
        // destroyUniverseThreads() so we need to check if
        // TimerThread kick in to sendRunMessage() after that.
        // It happens because TimerThread is the only thread run
        // asychronizously with MasterControl thread.

        if (timerThread != null) {
      // Notify TimerThread to wakeup this procedure
      // again next time.
      timerThread.addInputDeviceSchedCond();
        }
    }
      }
      if ((targetThreads & J3dThread.RENDER_THREAD) != 0) {
    synchronized (renderThreadData) {
        J3dThreadData[] threads = (J3dThreadData [])
      renderThreadData.toArray(false);
        int i=renderThreadData.arraySize()-1;
        J3dThreadData thr;
        while (i>=0) {
      thr = threads[i--];
      if ( thr.view.renderBinReady) {
          thr.lastUpdateTime = time;
      }
        }
    }
      }
  }
  setWork();
    }

    /**
     * Send a message to another Java 3D thread.
     * This variant is only call by TimerThread for Sound Scheduler
     */
    void sendRunMessage(long waitTime, View view, int targetThreads) {

  synchronized (timeLock) {

      long time = getTime();

      if ((targetThreads & J3dThread.SOUND_SCHEDULER) != 0) {
    if (view.soundScheduler != null)  {
        view.soundScheduler.threadData.lastUpdateTime = time;
    }
    // wakeup this procedure next time
    // QUESTION: waitTime calculated some milliseconds BEFORE
    //     this methods getTime() called - shouldn't actual
    //     sound Complete time be passed by SoundScheduler
    // QUESTION: will this wake up only soundScheduler associated
    //     with this view?? (since only it's lastUpdateTime is set)
    //     or all soundSchedulers??
    timerThread.addSoundSchedCond(time+waitTime);
      }
  }
  setWork();
    }

    /**
     * Send a message to another Java 3D thread.
     * This variant is only called to update Render Thread
     */
    void sendRunMessage(View v, int targetThreads) {

  synchronized (timeLock) {
      long time = getTime();

      if ((targetThreads & J3dThread.RENDER_THREAD) != 0) {
    synchronized (renderThreadData) {
        J3dThreadData[] threads = (J3dThreadData [])
      renderThreadData.toArray(false);
        int i=renderThreadData.arraySize()-1;
        J3dThreadData thr;
        while (i>=0) {
      thr = threads[i--];
      if (thr.view == v && v.renderBinReady) {
          thr.lastUpdateTime = time;
      }
        }
    }
      }
  }
  setWork();
    }


    /**
     * This sends a run message to the given threads.
     */
    void sendRunMessage(VirtualUniverse u, int targetThreads) {
  // We don't sendRunMessage to update structure except Behavior

  synchronized (timeLock) {
      long time = getTime();

      if ((targetThreads & J3dThread.BEHAVIOR_SCHEDULER) != 0) {
    if (u.behaviorScheduler != null) {
        u.behaviorScheduler.getThreadData(null,
                  null).lastUpdateTime = time;
    }
      }

      if ((targetThreads & J3dThread.UPDATE_BEHAVIOR) != 0) {
    u.behaviorStructure.threadData.lastUpdateTime = time;
      }

      if ((targetThreads & J3dThread.UPDATE_GEOMETRY) != 0) {
    u.geometryStructure.threadData.lastUpdateTime = time;
      }

      if ((targetThreads & J3dThread.UPDATE_SOUND) != 0) {
    u.soundStructure.threadData.lastUpdateTime = time;
      }

      if ((targetThreads & J3dThread.SOUND_SCHEDULER) != 0) {
    synchronized (views) {
        View v[] = (View []) views.toArray(false);
        for (int i= views.arraySize()-1; i >=0; i--) {
      if ((v[i].soundScheduler != null) &&
          (v[i].universe == u)) {
          v[i].soundScheduler.threadData.lastUpdateTime = time;
      }
        }
    }
      }

      if ((targetThreads & J3dThread.RENDER_THREAD) != 0) {

    synchronized (renderThreadData) {
        J3dThreadData[] threads = (J3dThreadData [])
      renderThreadData.toArray(false);
        int i=renderThreadData.arraySize()-1;
        J3dThreadData thr;
        while (i>=0) {
      thr = threads[i--];
      if (thr.view.universe == u && thr.view.renderBinReady) {
          thr.lastUpdateTime = time;
      }
        }
    }
      }
  }

  setWork();
    }


    /**
     * Return a clone of View, we can't access
     * individual element of View after getting the size
     * in separate API call without synchronized views.
     */
    UnorderList cloneView() {
  return (UnorderList) views.clone();
    }

    /**
     * Return true if view is already registered with MC
     */
    boolean isRegistered(View view) {
  return views.contains(view);
    }

    /**
     * This snapshots the time values to be used for this iteration.
     * Note that this method is called without the timeLock held.
     * We must synchronize on timeLock to prevent updating
     * thread.lastUpdateTime from user thread in sendMessage()
     * or sendRunMessage().
     */
    private void updateTimeValues() {
        synchronized (timeLock) {
            int i=0;
            J3dThreadData lastThread=null;
            J3dThreadData thread=null;
            long lastTime = currentTime;

            currentTime = getTime();

            J3dThreadData threads[] = (J3dThreadData [])
                                    stateWorkThreads.toArray(false);
            int size = stateWorkThreads.arraySize();

            while (i<lastTransformStructureThread) {
                thread = threads[i++];

                if ((thread.lastUpdateTime > thread.lastRunTime) &&
                    !thread.thread.userStop) {
                    lastThread = thread;
                    thread.needsRun = true;
                    thread.threadOpts = J3dThreadData.CONT_THREAD;
                    thread.lastRunTime =  currentTime;
                } else {
                    thread.needsRun = false;
                }
            }

            if (lastThread != null) {
                lastThread.threadOpts =  J3dThreadData.WAIT_ALL_THREADS;
                lastThread = null;
            }

            while (i<lastStructureUpdateThread) {
                thread = threads[i++];
                if ((thread.lastUpdateTime > thread.lastRunTime) &&
                    !thread.thread.userStop) {
                    lastThread = thread;
                    thread.needsRun = true;
                    thread.threadOpts = J3dThreadData.CONT_THREAD;
                    thread.lastRunTime = currentTime;
                } else {
                    thread.needsRun = false;
                }
            }
            if (lastThread != null) {
                lastThread.threadOpts = J3dThreadData.WAIT_ALL_THREADS;
                lastThread = null;
            }

            while (i<size) {
                thread = threads[i++];
                if ((thread.lastUpdateTime > thread.lastRunTime) &&
                    !thread.thread.userStop) {
                    lastThread = thread;
                    thread.needsRun = true;
                    thread.threadOpts = J3dThreadData.CONT_THREAD;
                    thread.lastRunTime = currentTime;
                } else {
                    thread.needsRun = false;
                }
            }
            if (lastThread != null) {
                lastThread.threadOpts = J3dThreadData.WAIT_ALL_THREADS;
                lastThread = null;
            }


            threads = (J3dThreadData []) renderWorkThreads.toArray(false);
            size = renderWorkThreads.arraySize();
            View v;
            J3dThreadData lastRunThread = null;
            waitTimestamp++;
            sleepTime = 0L;

            boolean threadToRun = false; // Not currently used

            // Fix for Issue 12: loop through the list of threads, calling
            // computeCycleTime() exactly once per view. This ensures that
            // all threads for a given view see consistent values for
            // isMinCycleTimeAchieve and sleepTime.
            v = null;
            for (i=0; i<size; i++) {
                thread = threads[i];
                if (thread.view != v) {
                    thread.view.computeCycleTime();
                    // Set sleepTime to the value needed to satify the
                    // minimum cycle time of the slowest view
                    if (thread.view.sleepTime > sleepTime) {
                        sleepTime = thread.view.sleepTime;
                    }
                }
                v = thread.view;
            }

            v = null;
            for (i=0; i<size; i++) {
                thread = threads[i];
                if (thread.canvas == null) { // Only for swap thread
                    ((Object []) thread.threadArgs)[3] = null;
                }
                if ((thread.lastUpdateTime > thread.lastRunTime) &&
                    !thread.thread.userStop) {

                    if (thread.thread.lastWaitTimestamp == waitTimestamp) {
                        // This renderer thread is repeated. We must wait
                        // until all previous renderer threads done before
                        // allowing this thread to continue. Note that
                        // lastRunThread can't be null in this case.
                        waitTimestamp++;
                        if (thread.view != v) {
                            // A new View is start
                            v = thread.view;
                            threadToRun = true;
                            lastRunThread.threadOpts =
                                (J3dThreadData.STOP_TIMER |
                                 J3dThreadData.WAIT_ALL_THREADS);
                            ((Object []) lastRunThread.threadArgs)[3] = lastRunThread.view;
                            thread.threadOpts = (J3dThreadData.START_TIMER |
                                                 J3dThreadData.CONT_THREAD);
                        } else {
                            if ((lastRunThread.threadOpts &
                                 J3dThreadData.START_TIMER) != 0) {
                                lastRunThread.threadOpts =
                                    (J3dThreadData.START_TIMER |
                                     J3dThreadData.WAIT_ALL_THREADS);

                            } else {
                                lastRunThread.threadOpts =
                                    J3dThreadData.WAIT_ALL_THREADS;
                            }
                            thread.threadOpts = J3dThreadData.CONT_THREAD;

                        }
                    } else {
                        if (thread.view != v) {
                            v = thread.view;
                            threadToRun = true;
                            // Although the renderer thread is not
                            // repeated. We still need to wait all
                            // previous renderer threads if new View
                            // start.
                            if (lastRunThread != null) {
                                lastRunThread.threadOpts =
                                    (J3dThreadData.STOP_TIMER |
                                     J3dThreadData.WAIT_ALL_THREADS);
                                ((Object []) lastRunThread.threadArgs)[3]
                                    = lastRunThread.view;
                            }
                            thread.threadOpts = (J3dThreadData.START_TIMER |
                                                 J3dThreadData.CONT_THREAD);
                        } else {
                            thread.threadOpts = J3dThreadData.CONT_THREAD;
                        }
                    }
                    thread.thread.lastWaitTimestamp = waitTimestamp;
                    thread.needsRun = true;
                    thread.lastRunTime = currentTime;
                    lastRunThread = thread;
                } else {
                    thread.needsRun = false;
                }
            }


            if (lastRunThread != null) {
                lastRunThread.threadOpts =
                                    (J3dThreadData.STOP_TIMER |
                                     J3dThreadData.WAIT_ALL_THREADS|
                                     J3dThreadData.LAST_STOP_TIMER);
                lockGeometry = true;
                ((Object []) lastRunThread.threadArgs)[3] = lastRunThread.view;
            else {
                lockGeometry = false;
            }
        }

        // Issue 275 - go to sleep without holding timeLock
  // Sleep for the amount of time needed to satisfy the minimum
  // cycle time for all views.
  if (sleepTime > 0) {
      // System.err.println("MasterControl: sleep(" + sleepTime + ")");
      try {
    Thread.sleep(sleepTime);
      } catch (InterruptedException e) {
    System.err.println(e);
      }
      // System.err.println("MasterControl: done sleeping");
  }
    }

    private void createUpdateThread(J3dStructure structure) {
  final J3dStructure s = structure;

  if (s.updateThread == null) {
      java.security.AccessController.doPrivileged(
          new java.security.PrivilegedAction<Object>() {
                   @Override
                   public Object run() {
           synchronized (rootThreadGroup) {
                           s.updateThread = new StructureUpdateThread(
                             rootThreadGroup, s, s.threadType);
         s.updateThread.setPriority(threadPriority);
           }
           return null;
       }
      });
      s.updateThread.initialize();
      s.threadData.thread = s.updateThread;
      // This takes into accout for thread that just destroy and
      // create again. In this case the threadData may receive
      // message before the thread actually created. We don't want
      // the currentTime to overwrite the update time of which
      // is set by threadData when get message.
      s.threadData.lastUpdateTime = Math.max(currentTime,
               s.threadData.lastUpdateTime);
  }
    }

    private void emptyMessageList(J3dStructure structure, View v) {
  if (structure != null) {
      if (v == null) {
    if (structure.threadData != null) {
        structure.threadData.thread = null;
    }

    if (structure.updateThread != null) {
        structure.updateThread.structure = null;
    }
    structure.updateThread = null;
      }
      boolean otherViewExist = false;
      if ((v != null) && (v.universe != null)) {
    // Check if there is any other View register with the
    // same universe
    for (int i=views.size()-1; i >= 0; i--) {
        if (((View) views.get(i)).universe == v.universe) {
      otherViewExist = true;
      break;
        }
    }
      }


      UnorderList mlist = structure.messageList;
      // Note that message is add at the end of array
      synchronized (mlist) {
    int size = mlist.size();
    if (size > 0) {
        J3dMessage mess[] = (J3dMessage []) mlist.toArray(false);
        J3dMessage m;
        int i = 0;

        while (i < size) {
      m = mess[i];
      if ((v == null) || (m.view == v) ||
          ((m.view == null) && !otherViewExist)) {
          if (m.type == J3dMessage.INSERT_NODES) {
        // There is another View register request
        // immediately following, so no need
        // to remove message.
        break;
          }
          // Some other thread may still using this
          // message so we should not directly
          // add this message to free lists
          m.decRefcount();
          mlist.removeOrdered(i);
          size--;
      } else {
          i++;
      }
        }
    }
      }
  }
    }

    private void destroyUpdateThread(J3dStructure structure) {
  // If unregisterView message got before EMPTY_UNIVERSE
  // message, then updateThread is already set to null.
  if (structure.updateThread != null) {
      structure.updateThread.finish();
      structure.updateThread.structure = null;
      structure.updateThread = null;
  }
  structure.threadData.thread = null;
  structure.clearMessages();
    }

    /**
     * This register a View with MasterControl.
     * The View has at least one Canvas3D added to a container.
     */
    private void registerView(View v) {
  final VirtualUniverse univ = v.universe;

  if (views.contains(v) && regUniverseList.contains(univ)) {
      return// already register
  }

  if (timerThread == null) {
      // This handle the case when MC shutdown and restart in
      // a series of pending request
      running = true;
      createMCThreads();
  }
  // If viewId is null, assign one ..
  v.assignViewId();

  // Create thread if not done before
  createUpdateThread(univ.behaviorStructure);
  createUpdateThread(univ.geometryStructure);
  createUpdateThread(univ.soundStructure);
  createUpdateThread(univ.renderingEnvironmentStructure);
  createUpdateThread(univ.transformStructure);

  // create Behavior scheduler
  J3dThreadData threadData = null;

  if (univ.behaviorScheduler == null) {
      java.security.AccessController.doPrivileged(
    new java.security.PrivilegedAction<Object>() {
                       @Override
                       public Object run() {
         synchronized (rootThreadGroup) {
             univ.behaviorScheduler = new BehaviorScheduler(
                  rootThreadGroup, univ);
             univ.behaviorScheduler.setPriority(threadPriority);
         }
         return null;
           }
      });
      univ.behaviorScheduler.initialize();
      univ.behaviorScheduler.userStop = v.stopBehavior;
      threadData = univ.behaviorScheduler.getThreadData(null, null);
      threadData.thread = univ.behaviorScheduler;
      threadData.threadType = J3dThread.BEHAVIOR_SCHEDULER;
      threadData.lastUpdateTime = Math.max(currentTime,
             threadData.lastUpdateTime);
  }

  createUpdateThread(v.renderBin);
  createUpdateThread(v.soundScheduler);

  if (v.physicalEnvironment != null) {
      v.physicalEnvironment.addUser(v);
  }
  // create InputDeviceScheduler
  evaluatePhysicalEnv(v);

  regUniverseList.addUnique(univ);
  views.addUnique(v);
    }



    /**
     * This unregister a View with MasterControl.
     * The View no longer has any Canvas3Ds in a container.
     */
    private void unregisterView(View v) {

  if (!views.remove(v)) {
      v.active = false;
      v.doneUnregister = true;
      return; // already unregister
  }

  if (v.active) {
      viewDeactivate(v);
  }

  if(J3dDebug.devPhase) {
      J3dDebug.doDebug(J3dDebug.masterControl, J3dDebug.LEVEL_1,
           "MC: Destroy Sound Scheduler and RenderBin Update thread");
  }

  v.soundScheduler.updateThread.finish();
  v.renderBin.updateThread.finish();
  v.soundScheduler.updateThread = null;
  v.renderBin.updateThread = null;

  // remove VirtualUniverse related threads if Universe
  // is empty
        VirtualUniverse univ = v.universe;

  synchronized (timeLock) {
      // The reason we need to sync. with timeLock is because we
      // don't want user thread running sendMessage() to
      // dispatch it in different structure queue when
      // part of the structure list is empty at the same time.
      // This will cause inconsistence in the message reference
      // count.
      emptyMessageList(v.soundScheduler, v);
      emptyMessageList(v.renderBin, v);

      if (univ.isEmpty()) {
    destroyUniverseThreads(univ);
      else {
    emptyMessageList(univ.behaviorStructure, v);
    emptyMessageList(univ.geometryStructure, v);
    emptyMessageList(univ.soundStructure, v);
    emptyMessageList(univ.renderingEnvironmentStructure, v);
    emptyMessageList(univ.transformStructure, v);
      }
  }

  if (v.physicalEnvironment != null) {
      v.physicalEnvironment.removeUser(v);
  }

  // remove all InputDeviceScheduler if this is the last View
  ArrayList<PhysicalEnvironment> list = new ArrayList<PhysicalEnvironment>();
  for (Enumeration<PhysicalEnvironment> e = PhysicalEnvironment.physicalEnvMap.keys(); e.hasMoreElements();) {
    PhysicalEnvironment phyEnv = e.nextElement();
    InputDeviceScheduler sched = PhysicalEnvironment.physicalEnvMap.get(phyEnv);
    boolean phyEnvHasUser = false;
    for (int i = 0; i < phyEnv.users.size(); i++) {
      if (views.contains(phyEnv.users.get(i))) {
        // at least one registered view refer to it.
        phyEnvHasUser = true;
        break;
      }
    }

    if (!phyEnvHasUser) {
      if (J3dDebug.devPhase) {
        J3dDebug.doDebug(J3dDebug.masterControl, J3dDebug.LEVEL_1,
            "MC: Destroy InputDeviceScheduler thread "
                + sched);
      }
      sched.finish();
      phyEnv.inputsched = null;
      list.add(phyEnv);
    }
  }
  for (int i = 0; i < list.size(); i++) {
    PhysicalEnvironment.physicalEnvMap.remove(list.get(i));
  }

  freeContext(v);

  if (views.isEmpty()) {
      if(J3dDebug.devPhase) {
    J3dDebug.doDebug(J3dDebug.masterControl, J3dDebug.LEVEL_1,
         "MC: Destroy all Renderers");
      }
      // remove all Renderers if this is the last View
      for (Enumeration<Renderer> e = Screen3D.deviceRendererMap.elements();
     e.hasMoreElements(); ) {
    Renderer rdr = e.nextElement();
    Screen3D scr;

    rendererCleanupArgs[2] = REMOVEALLCTXS_CLEANUP;
    runMonitor(RUN_RENDERER_CLEANUP, null, null, null, rdr);
    scr = rdr.onScreen;
    if (scr != null) {
        if (scr.renderer != null) {
      rendererCleanupArgs[2] = REMOVEALLCTXS_CLEANUP;
      runMonitor(RUN_RENDERER_CLEANUP, null, null,
           null, scr.renderer);
      scr.renderer = null;
        }

    }
    scr = rdr.offScreen;
    if (scr != null) {
        if (scr.renderer != null) {
      rendererCleanupArgs[2] = REMOVEALLCTXS_CLEANUP;
      runMonitor(RUN_RENDERER_CLEANUP, null, null,
           null, scr.renderer);
      scr.renderer = null;
        }
    }
    rdr.onScreen = null;
    rdr.offScreen = null;
      }

      // cleanup ThreadData corresponds to the view in renderer
      for (Enumeration<Renderer> e = Screen3D.deviceRendererMap.elements();
     e.hasMoreElements(); ) {
        e.nextElement().cleanup();
      }
      // We have to reuse renderer even though MC exit
      // see bug 4363279
      //  Screen3D.deviceRendererMap.clear();

  } else {
      // cleanup ThreadData corresponds to the view in renderer
      for (Enumeration<Renderer> e = Screen3D.deviceRendererMap.elements();
     e.hasMoreElements(); ) {
    e.nextElement().cleanupView();
      }
  }


  freeMessageList.add(univ);
  freeMessageList.add(v);

  evaluateAllCanvases();
  stateWorkThreads.clear();
  renderWorkThreads.clear();
  requestRenderWorkThreads.clear();
  threadListsChanged = true;

  // This notify VirtualUniverse waitForMC() thread to continue
  v.doneUnregister = true;
    }


    /**
     * This procedure create MC thread that start together with MC.
     */
    void createMCThreads() {

  // There is only one renderingAttributesUpdate Thread globally
  createUpdateThread(renderingAttributesStructure);

  // Create timer thread
  java.security.AccessController.doPrivileged(
          new java.security.PrivilegedAction<Object>() {
              @Override
              public Object run() {
      synchronized (rootThreadGroup) {
          timerThread = new TimerThread(rootThreadGroup);
          timerThread.setPriority(threadPriority);
      }
      return null;
        }
  });
  timerThread.start();

        // Create notification thread
  java.security.AccessController.doPrivileged(
          new java.security.PrivilegedAction<Object>() {
              @Override
              public Object run() {
      synchronized (rootThreadGroup) {
          notificationThread = new NotificationThread(rootThreadGroup);
          notificationThread.setPriority(threadPriority);
      }
      return null;
        }
  });
  notificationThread.start();
    }

    /**
     * Destroy all VirtualUniverse related threads.
     * This procedure may call two times when Locale detach in a
     * live viewPlatform.
     */
    private void destroyUniverseThreads(VirtualUniverse univ) {

  if (regUniverseList.contains(univ)) {
      if (J3dDebug.devPhase) {
    J3dDebug.doDebug(J3dDebug.masterControl, J3dDebug.LEVEL_1,
         "MC: Destroy universe threads " + univ);
      }
      destroyUpdateThread(univ.behaviorStructure);
      destroyUpdateThread(univ.geometryStructure);
      destroyUpdateThread(univ.soundStructure);
      destroyUpdateThread(univ.renderingEnvironmentStructure);
      destroyUpdateThread(univ.transformStructure);
      univ.behaviorScheduler.finish();
      univ.behaviorScheduler.free();
      univ.behaviorScheduler = null;
      univ.initMCStructure();
      activeUniverseList.remove(univ);
      regUniverseList.remove(univ);
  } else {
      emptyMessageList(univ.behaviorStructure, null);
      emptyMessageList(univ.geometryStructure, null);
      emptyMessageList(univ.soundStructure, null);
      emptyMessageList(univ.renderingEnvironmentStructure, null);
      emptyMessageList(univ.transformStructure, null);
  }

  if (regUniverseList.isEmpty() && views.isEmpty()) {
      if(J3dDebug.devPhase) {
    J3dDebug.doDebug(J3dDebug.masterControl, J3dDebug.LEVEL_1,
         "MC: Destroy RenderingAttributes Update and Timer threads");
      }
      if (renderingAttributesStructure.updateThread != null) {
    renderingAttributesStructure.updateThread.finish();
    renderingAttributesStructure.updateThread = null;
      }
      renderingAttributesStructure.messageList.clear();
      renderingAttributesStructure.objList = new ArrayList<J3dMessage>();
      renderingAttributesStructure = new RenderingAttributesStructure();
      if (timerThread != null) {
    timerThread.finish();
    timerThread = null;
      }
            if (notificationThread != null) {
                notificationThread.finish();
                notificationThread = null;
            }

      // shouldn't all of these be synchronized ???
      synchronized (VirtualUniverse.mc.deviceScreenMap) {
    deviceScreenMap.clear();
      }

      mirrorObjects.clear();
      // Note: We should not clear the DISPLAYLIST/TEXTURE
      // list here because other structure may release them
      // later

      for(int i=0; i<canvasIds.length; i++) {
    canvasIds[i] = false;
      }
      canvasFreeIndex = 0;

      renderOnceList.clear();
      timestampUpdateList.clear();

      defaultRenderMethod = null;
      text3DRenderMethod = null;
      vertexArrayRenderMethod = null;
      displayListRenderMethod = null;
      compressedGeometryRenderMethod = null;
      orientedShape3DRenderMethod = null;
      // Terminate MC thread
      running = false;
  }
    }

    /**
     * Note that we have to go through all views instead of
     * evaluate only the canvas in a single view since each screen
     * may share by multiple view
     */
    private void evaluateAllCanvases() {

  synchronized (renderThreadData) {
      // synchronized to prevent lost message when
      // renderThreadData is clear

      // First remove all renderrenderThreadData
      renderThreadData.clear();

      // Second reset canvasCount to zero
      View viewArr[] = (View []) views.toArray(false);
      for (int i=views.size()-1; i>=0; i--) {
    viewArr[i].getCanvasList(true); // force canvas cache update
    Screen3D screens[] = viewArr[i].getScreens();
    for (int j=screens.length-1; j>=0; j--) {
        screens[j].canvasCount = 0;
    }
      }


      // Third create render thread and message thread
      for (int i=views.size()-1; i>=0; i--) {
    View v = viewArr[i];
    Canvas3D canvasList[][] = v.getCanvasList(false);
    if (!v.active) {
        continue;
    }

    for (int j=canvasList.length-1; j>=0; j--) {
        boolean added = false;

        for (int k=canvasList[j].length-1; k>=0; k--) {
      Canvas3D cv = canvasList[j][k];

      final Screen3D screen  = cv.screen;

      if (cv.active) {
          if (screen.canvasCount++ == 0) {
        // Create Renderer, one per screen
        if (screen.renderer == null) {
                              // get the renderer created for the graphics
              // device of the screen of the canvas
        // No need to synchronized since only
        // MC use it.
            Renderer rdr = Screen3D.deviceRendererMap.get(cv.screen.graphicsDevice);
            if (rdr == null) {
          java.security.AccessController.doPrivileged(
              new java.security.PrivilegedAction<Object>() {
                                              @Override
                                              public Object run() {

              synchronized (rootThreadGroup) {
                  screen.renderer
                = new Renderer(
                     rootThreadGroup);
                  screen.renderer.setPriority(threadPriority);
              }
                                                 return null;
                                          }
          });
          screen.renderer.initialize();
          Screen3D.deviceRendererMap.put(screen.graphicsDevice, screen.renderer);
            } else {
          screen.renderer = rdr;
            }
        }
          }
              // offScreen canvases will be handled by the
          // request renderer, so don't add offScreen canvas
          // the render list
                            //
                            // Issue 131: Automatic offscreen canvases need to
                            // be added to onscreen list. Special case.
                            //
                            // TODO KCR Issue 131: this should probably be
                            // changed to a list of screens since multiple
                            // off-screen canvases (either auto or manual) can
                            // be used by the same renderer
                if (!cv.manualRendering) {
        screen.renderer.onScreen = screen;
          } else {
        screen.renderer.offScreen = screen;
        continue;
          }

          if (!added) {
        // Swap message data thread, one per
        // screen only. Note that we don't set
        // lastUpdateTime for this thread so
        // that it won't run in the first round
              J3dThreadData renderData =
                  screen.renderer.getThreadData(v, null);
        renderThreadData.add(renderData);

        // only if renderBin is ready then we
        // update the lastUpdateTime to make it run
        if (v.renderBinReady) {
                    renderData.lastUpdateTime =
          Math.max(currentTime,
               renderData.lastUpdateTime);
        }
        added = true;
          }
          // Renderer message data thread
          J3dThreadData renderData =
                  screen.renderer.getThreadData(v, cv);
          renderThreadData.add(renderData);
          if (v.renderBinReady) {
                renderData.lastUpdateTime =
          Math.max(currentTime,
               renderData.lastUpdateTime);
          }
      }
        }
    }

      }
  }

  threadListsChanged = true;
    }

    private void evaluatePhysicalEnv(View v) {
  final PhysicalEnvironment env = v.physicalEnvironment;

  if (env.inputsched == null) {
      java.security.AccessController.doPrivileged(
     new java.security.PrivilegedAction<Object>() {
                      @Override
                      public Object run() {
        synchronized (rootThreadGroup) {
            env.inputsched = new InputDeviceScheduler(
              rootThreadGroup,
              env);
            env.inputsched.setPriority(threadPriority);
        }
        return null;
          }
      });
      env.inputsched.start();
      PhysicalEnvironment.physicalEnvMap.put(env, env.inputsched);
  }
  threadListsChanged = true;
    }

    final private void addToStateThreads(J3dThreadData threadData) {
  if (threadData.thread.active) {
      stateWorkThreads.add(threadData);
  }
    }


    private void assignNewPrimaryView(VirtualUniverse univ) {

  View currentPrimary = univ.getCurrentView();

  if (currentPrimary != null) {
      currentPrimary.primaryView = false;
  }

  View v[] = (View []) views.toArray(false);
  int nviews = views.size();
  for (int i=0; i<nviews; i++) {
      View view = v[i];
      if (view.active && view.isRunning &&
    (univ == view.universe)) {
    view.primaryView = true;
    univ.setCurrentView(view);
    return;
      }
  }
  univ.setCurrentView(null);
    }


    /**
     * This returns the default RenderMethod
     */
    RenderMethod getDefaultRenderMethod() {
  if (defaultRenderMethod == null) {
      defaultRenderMethod = new DefaultRenderMethod();
  }
  return defaultRenderMethod;
    }

    /**
     * This returns the text3d RenderMethod
     */
    RenderMethod getText3DRenderMethod() {
  if (text3DRenderMethod == null) {
      text3DRenderMethod = new Text3DRenderMethod();
  }
  return text3DRenderMethod;
    }


    /**
     * This returns the vertexArray RenderMethod
     */
    RenderMethod getVertexArrayRenderMethod() {
  if (vertexArrayRenderMethod == null) {
      vertexArrayRenderMethod = new VertexArrayRenderMethod();
  }
  return vertexArrayRenderMethod;
    }

    /**
     * This returns the displayList RenderMethod
     */
    RenderMethod getDisplayListRenderMethod() {
        if (displayListRenderMethod == null) {
            displayListRenderMethod = new DisplayListRenderMethod();
        }
        return displayListRenderMethod;
    }

    /**
     * This returns the compressed geometry RenderMethod
     */
    RenderMethod getCompressedGeometryRenderMethod() {
        if (compressedGeometryRenderMethod == null) {
            compressedGeometryRenderMethod =
    new CompressedGeometryRenderMethod();
        }
        return compressedGeometryRenderMethod;
    }

    /**
     * This returns the oriented shape3d RenderMethod
     */
    RenderMethod getOrientedShape3DRenderMethod() {
  if (orientedShape3DRenderMethod == null) {
      orientedShape3DRenderMethod = new OrientedShape3DRenderMethod();
  }
  return orientedShape3DRenderMethod;
    }

    /**
     * This notifies MasterControl that the given view has been activated
     */
    private void viewActivate(View v) {

  VirtualUniverse univ = v.universe;

  if (univ == null) {
      return;
  }

  if (!views.contains(v) || !regUniverseList.contains(univ)) {
      registerView(v);
  } else if (v.active) {
      evaluateAllCanvases();
      return;
  }

  if ((univ.activeViewCount == 0)) {
      univ.geometryStructure.resetConditionMet();
      univ.behaviorStructure.resetConditionMet();
  }

  if (v.isRunning) {
      numActiveViews++;
      univ.activeViewCount++;
      renderingAttributesStructure.updateThread.active = true;
      univ.transformStructure.updateThread.active = true;
      univ.geometryStructure.updateThread.active = true;
      univ.soundStructure.updateThread.active = true;
      univ.renderingEnvironmentStructure.updateThread.active = true;
  }
  univ.behaviorScheduler.active = true;
  univ.behaviorStructure.updateThread.active = true;


  activeUniverseList.addUnique(univ);

  if (v.isRunning) {
      v.soundScheduler.activate();
      v.renderBin.updateThread.active = true;
  }
  v.active = true;

  if (v.physicalEnvironment.activeViewRef++ == 0) {
      v.physicalEnvironment.inputsched.activate();
  }


  if (univ.getCurrentView() == null) {
      assignNewPrimaryView(univ);
  }

  evaluateAllCanvases();
  v.inRenderThreadData = true;
  threadListsChanged = true;
  // Notify GeometryStructure to query visible atom again
  // We should send message instead of just setting
  // v.vDirtyMask = View.VISIBILITY_POLICY_DIRTY;
  // since RenderBin may not run immediately next time.
  // In this case the dirty flag will lost since
  // updateViewCache() will reset it to 0.
  v.renderBin.reactivateView = true;
    }

    /**
     * Release context associate with view
     */
    private void freeContext(View v) {
  Canvas3D[][] canvasList = v.getCanvasList(false);

  for (int j=canvasList.length-1; j>=0; j--) {
      for (int k=canvasList[j].length-1; k>=0; k--) {
    Canvas3D cv = canvasList[j][k];
    if (!cv.validCanvas) {
        if ((cv.screen != null) &&
      (cv.screen.renderer != null)) {
      rendererCleanupArgs[1] = cv;
      rendererCleanupArgs[2] = FREECONTEXT_CLEANUP;
      runMonitor(RUN_RENDERER_CLEANUP, null, null, null,
           cv.screen.renderer);
      rendererCleanupArgs[1] = null;
        }
    }
      }
  }
    }

    /**
     * This notifies MasterControl that the given view has been deactivated
     */
    private void viewDeactivate(View v) {

  if (!views.contains(v) || !v.active) {
      v.active = false;
      evaluateAllCanvases();
      return;
  }

  VirtualUniverse univ = v.universe;

  if (v.isRunning) {
      // if stopView() invoke before, no need to decrement count
      --numActiveViews;
      --univ.activeViewCount;
  }

  if (numActiveViews == 0) {
      renderingAttributesStructure.updateThread.active = false;
  }

  if (univ.activeViewCount == 0) {
      // check if destroyUniverseThread invoked before
      if (univ.behaviorScheduler != null) {
    univ.behaviorScheduler.deactivate();
    univ.transformStructure.updateThread.active = false;
    univ.geometryStructure.updateThread.active = false;
    univ.behaviorStructure.updateThread.active = false;
    univ.soundStructure.updateThread.active = false;
    univ.renderingEnvironmentStructure.updateThread.active
        = false;
    activeUniverseList.remove(univ);
      }
  }

  v.soundScheduler.deactivate();
  v.renderBin.updateThread.active = false;
  v.active = false;
  if (--v.physicalEnvironment.activeViewRef == 0) {
      v.physicalEnvironment.inputsched.deactivate();
  }
  assignNewPrimaryView(univ);


  evaluateAllCanvases();

  freeContext(v);

  v.inRenderThreadData = false;
  threadListsChanged = true;
    }


   /**
     * This notifies MasterControl to start given view
     */
    private void startView(View v) {

  if (!views.contains(v) || v.isRunning || !v.active) {
      v.isRunning = true;
      return;
  }

  numActiveViews++;
  renderingAttributesStructure.updateThread.active = true;

  VirtualUniverse univ = v.universe;

  univ.activeViewCount++;
  univ.transformStructure.updateThread.active = true;
  univ.geometryStructure.updateThread.active = true;
  univ.soundStructure.updateThread.active = true;
  univ.renderingEnvironmentStructure.updateThread.active = true;
  v.renderBin.updateThread.active = true;
  v.soundScheduler.activate();
  v.isRunning = true;
  if (univ.getCurrentView() == null) {
      assignNewPrimaryView(univ);
  }
  threadListsChanged = true;
    }


   /**
     * This notifies MasterControl to stop given view
     */
    private void stopView(View v) {
  if (!views.contains(v) || !v.isRunning || !v.active) {
      v.isRunning = false;
      return;
  }

  if (--numActiveViews == 0) {
      renderingAttributesStructure.updateThread.active = false;
  }
  VirtualUniverse univ = v.universe;

  if (--univ.activeViewCount == 0) {
    univ.transformStructure.updateThread.active = false;
    univ.geometryStructure.updateThread.active = false;
    univ.renderingEnvironmentStructure.updateThread.active = false;
    univ.soundStructure.updateThread.active = false;
  }

  v.renderBin.updateThread.active = false;
  v.soundScheduler.deactivate();
  v.isRunning = false;
  assignNewPrimaryView(univ);
  threadListsChanged = true;
    }

    // Call from user thread
    void addInputDeviceScheduler(InputDeviceScheduler ds) {
  synchronized (inputDeviceThreads) {
      inputDeviceThreads.add(ds);
      if (inputDeviceThreads.size() == 1) {
    timerThread.addInputDeviceSchedCond();
      }
  }
  postRequest(INPUTDEVICE_CHANGE, null);
    }

    // Call from user thread
    void removeInputDeviceScheduler(InputDeviceScheduler ds) {
  inputDeviceThreads.remove(ds);
  postRequest(INPUTDEVICE_CHANGE, null);
    }

    /**
     * Add an object to the mirror object list
     */
    void addMirrorObject(ObjectUpdate o) {
  mirrorObjects.add(o);
    }

    /**
     * This updates any mirror objects.  It is called when threads
     * are done.
     */
    void updateMirrorObjects() {
  ObjectUpdate objs[] = (ObjectUpdate []) mirrorObjects.toArray(false);
  int sz = mirrorObjects.arraySize();

  for (int i = 0; i< sz; i++) {
      objs[i].updateObject();
  }
  mirrorObjects.clear();
    }


    /**
     * This fun little method does all the hard work of setting up the
     * work thread list.
     */
    private void updateWorkThreads() {

  stateWorkThreads.clear();
  renderWorkThreads.clear();
  requestRenderWorkThreads.clear();

  // First the global rendering attributes structure update
  if (numActiveViews > 0) {
      addToStateThreads(renderingAttributesStructure.getUpdateThreadData());
  }

  // Next, each of the transform structure updates
  VirtualUniverse universes[] = (VirtualUniverse [])
                           activeUniverseList.toArray(false);
  VirtualUniverse univ;
  int i;
  int size = activeUniverseList.arraySize();

  for (i=size-1; i>=0; i--) {
      addToStateThreads(universes[i].transformStructure.getUpdateThreadData());
  }
  lastTransformStructureThread = stateWorkThreads.size();

  // Next, the GeometryStructure, BehaviorStructure,
  //       RenderingEnvironmentStructure, and SoundStructure
  for (i=size-1; i>=0; i--) {
      univ = universes[i];
      addToStateThreads(univ.geometryStructure.getUpdateThreadData());
      addToStateThreads(univ.behaviorStructure.getUpdateThreadData());
      addToStateThreads(univ.renderingEnvironmentStructure.getUpdateThreadData());
      addToStateThreads(univ.soundStructure.getUpdateThreadData());
  }

  lastStructureUpdateThread = stateWorkThreads.size();

  // Next, the BehaviorSchedulers
  for (i=size-1; i>=0; i--) {
      addToStateThreads(universes[i].behaviorScheduler.
            getThreadData(null, null));
  }


  // Now InputDeviceScheduler

  InputDeviceScheduler ds[] = (InputDeviceScheduler [])
                                inputDeviceThreads.toArray(true);
  for (i=inputDeviceThreads.size()-1; i >=0; i--) {
      J3dThreadData threadData = ds[i].getThreadData();
      threadData.thread.active = true;
      addToStateThreads(threadData);
  }

  // Now the RenderBins and SoundSchedulers
  View viewArr[] = (View []) views.toArray(false);
  J3dThreadData thread;

  for (i=views.size()-1; i>=0; i--) {
      View v = viewArr[i];
      if (v.active && v.isRunning) {
    addToStateThreads(v.renderBin.getUpdateThreadData());
    addToStateThreads(v.soundScheduler.getUpdateThreadData());
          Canvas3D canvasList[][] = v.getCanvasList(false);
          int longestScreenList = v.getLongestScreenList();
    Object args[] = null;
    // renderer render
          for (int j=0; j<longestScreenList; j++) {
              for (int k=0; k < canvasList.length; k++) {
                  if (j < canvasList[k].length) {
                      Canvas3D cv = canvasList[k][j];
                            // Issue 131: setup renderer unless manualRendering
          if (cv.active && cv.isRunningStatus && !cv.manualRendering ) {
        if (cv.screen.renderer == null) {
            continue;
        }
        thread = cv.screen.renderer.getThreadData(v, cv);
        renderWorkThreads.add(thread);
        args =  (Object []) thread.threadArgs;
        args[0] = RENDER;
        args[1] = cv;
        args[2] = v;
          }
      }
        }
    }

    // renderer swap
          for (int j=0; j<canvasList.length; j++) {
        for (int k=0; k < canvasList[j].length; k++) {
      Canvas3D cv = canvasList[j][k];
      // create swap thread only if there is at
      // least one active canvas
                        // Issue 131: only if not manualRendering
      if (cv.active && cv.isRunningStatus && !cv.manualRendering) {
          if (cv.screen.renderer == null) {
        // Should not happen
        continue;
          }
          thread = cv.screen.renderer.getThreadData(v, null);
          renderWorkThreads.add(thread);
          args = (Object []) thread.threadArgs;
          args[0] = SWAP;
          args[1] = v;
          args[2] = canvasList[j];
          break;
      }
        }
    }
      }
  }

  thread = null;

  for (Enumeration<Renderer> e = Screen3D.deviceRendererMap.elements();
       e.hasMoreElements(); ) {
      Renderer rdr = e.nextElement();
      thread = rdr.getThreadData(null, null);
      requestRenderWorkThreads.add(thread);
      thread.threadOpts = J3dThreadData.CONT_THREAD;
      ((Object[]) thread.threadArgs)[0] = REQUESTRENDER;
  }

  if (thread != null) {
      thread.threadOpts |= J3dThreadData.WAIT_ALL_THREADS;
  }

  threadListsChanged = false;

  //   dumpWorkThreads();
    }


    void dumpWorkThreads() {
  System.err.println("-----------------------------");
  System.err.println("MasterControl/dumpWorkThreads");

  J3dThreadData threads[];
  int size = 0;

  for (int k=0; k<3; k++) {
      switch (k) {
      case 0:
    threads = (J3dThreadData []) stateWorkThreads.toArray(false);
    size = stateWorkThreads.arraySize();
    break;
      case 1:
    threads = (J3dThreadData []) renderWorkThreads.toArray(false);
    size = renderWorkThreads.arraySize();
    break;
      default:
    threads = (J3dThreadData []) requestRenderWorkThreads.toArray(false);
    size = requestRenderWorkThreads.arraySize();
    break;
      }

      for (int i=0; i<size; i++) {
    J3dThreadData thread = threads[i];
    System.err.println("Thread " + i + ": " + thread.thread);
    System.err.println("\tOps: " + thread.threadOpts);
    if (thread.threadArgs != null) {
        Object[] args = (Object[]) thread.threadArgs;
        System.err.print("\tArgs: ");
        for (int j=0; j<args.length; j++) {
      System.err.print(args[j] + " ");
        }
    }
    System.err.println("");
      }
  }
  System.err.println("-----------------------------");
    }


    /**
     * A convienence wrapper function for various parts of the system
     * to force MC to run.
     */
    final void setWork() {
  runMonitor(SET_WORK, null, null, null, null);
    }

    final void setWorkForRequestRenderer() {
        runMonitor(SET_WORK_FOR_REQUEST_RENDERER, null, null, null, null);
    }

    /**
     * Call from GraphicsConfigTemplate to evaluate current
     * capabilities using Renderer thread to invoke native
     * graphics library functions. This avoid MT-safe problem
     * when using thread directly invoke graphics functions.
     */
    void sendRenderMessage(GraphicsConfiguration gc,
         Object arg, Integer mtype) {
  Renderer rdr = createRenderer(gc);
  J3dMessage renderMessage = new J3dMessage();
  renderMessage.threads = J3dThread.RENDER_THREAD;
  renderMessage.type = J3dMessage.RENDER_IMMEDIATE;
  renderMessage.universe = null;
  renderMessage.view = null;
  renderMessage.args[0] = null;
  renderMessage.args[1] = arg;
  renderMessage.args[2] = mtype;
  rdr.rendererStructure.addMessage(renderMessage);
  setWorkForRequestRenderer();
    }

    // Issue for Issue 175
    // Pass DestroyCtxAndOffScreenBuffer to the Renderer thread for execution.
    void sendDestroyCtxAndOffScreenBuffer(Canvas3D c) {
        // Assertion check. Look for comment in sendCreateOffScreenBuffer.
        GraphicsDevice gd = c.graphicsConfiguration.getDevice();
  assert Screen3D.deviceRendererMap.get(gd) != null;

  synchronized (mcThreadLock) {
            // Issue 364: create master control thread if needed
      createMasterControlThread();
            assert mcThread != null;

      Renderer rdr = createRenderer(c.graphicsConfiguration);
      J3dMessage createMessage = new J3dMessage();
      createMessage.threads = J3dThread.RENDER_THREAD;
      createMessage.type = J3dMessage.DESTROY_CTX_AND_OFFSCREENBUFFER;
      createMessage.universe = null;
      createMessage.view = null;
      createMessage.args[0] = c;
            // Fix for issue 340: send display, drawable & ctx in msg
            createMessage.args[1] = Long.valueOf(0L);
            createMessage.args[2] = c.drawable;
            createMessage.args[3] = c.ctx;
      rdr.rendererStructure.addMessage(createMessage);
      synchronized (requestObjList) {
    setWorkForRequestRenderer();
    pendingRequest = true;
      }
        }
    }

    // Fix for Issue 18
    // Pass CreateOffScreenBuffer to the Renderer thread for execution.
    void sendCreateOffScreenBuffer(Canvas3D c) {
  // Assertion check that the renderer has already been created.
  // If it hasn't, this is very, very bad because it opens up
  // the possibility of an MT race condition since this method
  // can be called from the user's thread, possibly at the same
  // time as the MasterControl thread is trying to create a new
  // Renderer.  Fortunately, this should never happen since both
  // the GraphicsTemplate3D methods that return a valid Graphics
  // Configuration and the Canvas3D constructor will ultimately
  // cause a renderer to be created via sendRenderMessage().
  GraphicsDevice gd = c.graphicsConfiguration.getDevice();
  J3dDebug.doAssert((Screen3D.deviceRendererMap.get(gd) != null),
        "Screen3D.deviceRendererMap.get(gd) != null");

  synchronized (mcThreadLock) {
            // Create master control thread if needed
      createMasterControlThread();
            assert mcThread != null;

      // Fix for Issue 72 : call createRenderer rather than getting
      // the renderer from the canvas.screen object
      Renderer rdr = createRenderer(c.graphicsConfiguration);
      J3dMessage createMessage = new J3dMessage();
      createMessage.threads = J3dThread.RENDER_THREAD;
      createMessage.type = J3dMessage.CREATE_OFFSCREENBUFFER;
      createMessage.universe = null;
      createMessage.view = null;
      createMessage.args[0] = c;
      rdr.rendererStructure.addMessage(createMessage);
      synchronized (requestObjList) {
    setWorkForRequestRenderer();
    pendingRequest = true;
      }
  }
    }

    // Issue 347 - Pass AllocateCanvasId to the Renderer thread for execution
    void sendAllocateCanvasId(Canvas3D c) {
        synchronized (mcThreadLock) {
            // Issue 364: create master control thread if needed
            createMasterControlThread();
            assert mcThread != null;

            Renderer rdr = createRenderer(c.graphicsConfiguration);
            J3dMessage createMessage = new J3dMessage();
            createMessage.threads = J3dThread.RENDER_THREAD;
            createMessage.type = J3dMessage.ALLOCATE_CANVASID;
            createMessage.universe = null;
            createMessage.view = null;
            createMessage.args[0] = c;
            rdr.rendererStructure.addMessage(createMessage);
            synchronized (requestObjList) {
                setWorkForRequestRenderer();
                pendingRequest = true;
            }
        }
    }

    // Issue 347 - Pass AllocateCanvasId to the Renderer thread for execution
    void sendFreeCanvasId(Canvas3D c) {
        synchronized (mcThreadLock) {
            // Issue 364: create master control thread if needed
            createMasterControlThread();
            assert mcThread != null;

            Renderer rdr = createRenderer(c.graphicsConfiguration);
            J3dMessage createMessage = new J3dMessage();
            createMessage.threads = J3dThread.RENDER_THREAD;
            createMessage.type = J3dMessage.FREE_CANVASID;
            createMessage.universe = null;
            createMessage.view = null;
            createMessage.args[0] = c;
            rdr.rendererStructure.addMessage(createMessage);
            synchronized (requestObjList) {
                setWorkForRequestRenderer();
                pendingRequest = true;
            }
        }
    }


    /**
     * This is the MasterControl work method for Java 3D
     */
    void doWork() {
  runMonitor(CHECK_FOR_WORK, null, null, null, null);

  synchronized (timeLock) {
      synchronized (requestObjList) {
    if (pendingRequest) {
        handlePendingRequest();
    }
      }
  }

  if (!running) {
      return;
  }

  if (threadListsChanged) { // Check for new Threads
      updateWorkThreads();
  }

  synchronized (timeLock) {
      // This is neccesary to prevent updating
      // thread.lastUpdateTime from user thread
      // in sendMessage() or sendRunMessage()
      updateTimeValues();
  }

  //This is temporary until the view model is updated
  View v[] = (View []) views.toArray(false);
  for (int i=views.size()-1; i>=0; i--) {
      if (v[i].active) {
    v[i].updateViewCache();
    // update OrientedShape3D
    if ((v[i].viewCache.vcDirtyMask != 0 &&
         !v[i].renderBin.orientedRAs.isEmpty()) ||
        (v[i].renderBin.cachedDirtyOrientedRAs != null &&
         !v[i].renderBin.cachedDirtyOrientedRAs.isEmpty())) {
        v[i].renderBin.updateOrientedRAs();
    }
      }
  }

  runMonitor(RUN_THREADS, stateWorkThreads, renderWorkThreads,
       requestRenderWorkThreads, null);

  if (renderOnceList.size() > 0) {
      clearRenderOnceList();
  }

  manageMemory();

    }

    private void handlePendingRequest() {

  Object objs[];
  Integer types[];
  int size;
  boolean rendererRun = false;

  objs = requestObjList.toArray(false);
  types = (Integer []) requestTypeList.toArray(false);
  size = requestObjList.size();

  for (int i=0; i < size; i++) {
      // need to process request in order
      Integer type = types[i];
      Object o = objs[i];
           if (type == RESET_CANVAS) {
               Canvas3D cv = (Canvas3D) o;
    if ((cv.screen != null) &&
        (cv.screen.renderer != null)) {
        rendererCleanupArgs[1] = o;
        rendererCleanupArgs[2] = RESETCANVAS_CLEANUP;
        runMonitor(RUN_RENDERER_CLEANUP, null, null, null,
             cv.screen.renderer);
        rendererCleanupArgs[1] = null;
    }
               cv.reset();
         cv.view = null;
         cv.computeViewCache();
           }
     else if (type == ACTIVATE_VIEW) {
    viewActivate((View) o);
     }
     else if (type == DEACTIVATE_VIEW) {
    viewDeactivate((View) o);
      } else if (type == REEVALUATE_CANVAS) {
    evaluateAllCanvases();
      } else if (type == INPUTDEVICE_CHANGE) {
    inputDeviceThreads.clearMirror();
    threadListsChanged = true;
      } else if (type == START_VIEW) {
    startView((View) o);
      } else if (type == STOP_VIEW) {
    View v = (View) o;
    // Collision takes 3 rounds to finish its request
    if (++v.stopViewCount > 4) {
        v.stopViewCount = -1; // reset counter
        stopView(v);
    } else {
        tempViewList.add(v);
    }
      } else if (type == UNREGISTER_VIEW) {
    unregisterView((View) o);
      } else if (type == PHYSICAL_ENV_CHANGE) {
    evaluatePhysicalEnv((View) o);
      } else if (type == EMPTY_UNIVERSE) {
    // Issue 81: We need to process this message as long
    // as there are no views associated with this
    // universe. Previously, this message was ignored if
    // there were views associated with *any* universe,
    // which led to a memory / thread leak.
          boolean foundView = false;
    VirtualUniverse univ = (VirtualUniverse) o;
    View v[] = (View []) views.toArray(false);
    for (int j = views.size() - 1; j >= 0; j--) {
        if (v[j].universe == univ) {
      foundView = true;
      break;
        }
    }
    if (!foundView) {
        destroyUniverseThreads(univ);
        threadListsChanged = true;
    }
      } else if (type == START_RENDERER) {
                if (o instanceof Canvas3D) {
                    Canvas3D c3d = (Canvas3D) o;
                    if (!c3d.isFatalError()) {
                        c3d.isRunningStatus = true;
                    }
                } else {
                    ((Renderer) o).userStop = false;
                }
    threadListsChanged = true;
      } else if (type == STOP_RENDERER) {
    if (o instanceof Canvas3D) {
        ((Canvas3D) o).isRunningStatus = false;
    } else {
        ((Renderer) o).userStop = true;
    }
    threadListsChanged = true;
      } else if (type == RENDER_ONCE) {
    View v = (View) o;
    // temporary start View for renderonce
    // it will stop afterwards
    startView(v);
    renderOnceList.add(v);
    sendRunMessage(v, J3dThread.UPDATE_RENDER);
    threadListsChanged = true;
    rendererRun = true;
      } else if (type == FREE_CONTEXT) {
    Canvas3D cv = (Canvas3D ) ((Object []) o)[0];
    if ((cv.screen != null) &&
        (cv.screen.renderer != null)) {
        rendererCleanupArgs[1] = o;
        rendererCleanupArgs[2] = REMOVECTX_CLEANUP;
        runMonitor(RUN_RENDERER_CLEANUP, null, null, null,
             cv.screen.renderer);
        rendererCleanupArgs[1] = null;
    }
    rendererRun = true;
            } else if (type == FREE_DRAWING_SURFACE) {
                Pipeline.getPipeline().freeDrawingSurfaceNative(o);
            } else if (type == GETBESTCONFIG) {
    GraphicsConfiguration gc = ((GraphicsConfiguration [])
        ((GraphicsConfigTemplate3D) o).testCfg)[0];
    sendRenderMessage(gc, o, type);
    rendererRun = true;
      } else if (type == ISCONFIGSUPPORT) {
    GraphicsConfiguration  gc = (GraphicsConfiguration)
        ((GraphicsConfigTemplate3D) o).testCfg;
    sendRenderMessage(gc, o, type);
    rendererRun = true;
      } else if ((type == SET_GRAPHICSCONFIG_FEATURES) ||
           (type == SET_QUERYPROPERTIES)) {
    GraphicsConfiguration gc = ((Canvas3D)o).graphicsConfiguration;
    sendRenderMessage(gc, o, type);
    rendererRun = true;
      } else if (type == SET_VIEW) {
    Canvas3D cv = (Canvas3D) o;
    cv.view = cv.pendingView;
    cv.computeViewCache();
      }
  }

  // Do it only after all universe/View is register
  for (int i=0; i < size; i++) {
      Integer type = types[i];
      if (type == FREE_MESSAGE) {
    if (objs[i] instanceof VirtualUniverse) {
        VirtualUniverse u = (VirtualUniverse) objs[i];
        if (!regUniverseList.contains(u)) {
      emptyMessageList(u.behaviorStructure, null);
      emptyMessageList(u.geometryStructure, null);
      emptyMessageList(u.soundStructure, null);
      emptyMessageList(u.renderingEnvironmentStructure, null);
        }
    } else if (objs[i] instanceof View) {
        View v = (View) objs[i];
        if (!views.contains(v)) {
      emptyMessageList(v.soundScheduler, v);
      emptyMessageList(v.renderBin, v);
      if (v.resetUnivCount == v.universeCount) {
          v.reset();
          v.universe = null;
          if (running == false) {
        // MC is about to terminate

        /*
        // Don't free list cause there may
        // have some other thread returning ID
        // after it.
        FreeListManager.clearList(FreeListManager.DISPLAYLIST);
        FreeListManager.clearList(FreeListManager.TEXTURE2D);
        FreeListManager.clearList(FreeListManager.TEXTURE3D);

        synchronized (textureIdLock) {
            textureIdCount = 0;
        }
        */
          }
      }
        }
    }

      }

  }
  requestObjList.clear();
  requestTypeList.clear();

  size = tempViewList.size();
  if (size > 0) {
      if (running) {
    for (int i=0; i < size; i++) {
        requestTypeList.add(STOP_VIEW);
        requestObjList.add(tempViewList.get(i));
    }
    setWork();
      } else { // MC will shutdown
    for (int i=0; i < size; i++) {
        View v = (View) tempViewList.get(i);
        v.stopViewCount = -1;
        v.isRunning = false;
    }
      }
      tempViewList.clear();
      pendingRequest = true;
  } else {
      pendingRequest = rendererRun || (requestObjList.size() > 0);

  }

  size = freeMessageList.size();
  if (size > 0) {
      for (int i=0; i < size; i++) {
    requestTypeList.add(FREE_MESSAGE);
    requestObjList.add(freeMessageList.get(i));
      }
      pendingRequest = true;
      freeMessageList.clear();
  }
  if (!running && (renderOnceList.size() > 0)) {
      clearRenderOnceList();
  }

  if (pendingRequest) {
      setWork();
  }

  if (rendererRun || requestRenderWorkToDo) {
      running = true;
  }

    }

    private void clearRenderOnceList() {
  for (int i=renderOnceList.size()-1; i>=0; i--) {
      View v = (View) renderOnceList.get(i);
      v.renderOnceFinish = true;
      // stop after render once
      stopView(v);
  }
  renderOnceList.clear();
  threadListsChanged = true;

    }

    synchronized void runMonitor(int action,
         UnorderList stateThreadList,
         UnorderList renderThreadList,
         UnorderList requestRenderThreadList,
         J3dThread nthread) {

        switch (action) {
  case RUN_THREADS:
      int currentStateThread = 0;
      int currentRenderThread = 0;
      int currentRequestRenderThread = 0;
      View view;
      boolean done;
      J3dThreadData thread;
      J3dThreadData renderThreads[] = (J3dThreadData [])
                                 renderThreadList.toArray(false);
      J3dThreadData stateThreads[] = (J3dThreadData [])
                                stateThreadList.toArray(false);
      J3dThreadData requestRenderThreads[] = (J3dThreadData [])
                           requestRenderThreadList.toArray(false);
      int renderThreadSize = renderThreadList.arraySize();
      int stateThreadSize = stateThreadList.arraySize();
      int requestRenderThreadSize = requestRenderThreadList.arraySize();

      done = false;

      //lock all the needed geometry and image component
      View[] allView = (View []) views.toArray(false);
      View currentV;
      int i;

      if (lockGeometry)
      {
    for( i = views.arraySize()-1; i >= 0; i--) {
        currentV = allView[i];
        currentV.renderBin.lockGeometry();
    }
      }

      while (!done) {
    // First try a RenderThread
    while (!renderWaiting &&
           currentRenderThread != renderThreadSize) {
        thread = renderThreads[currentRenderThread++];
        if (!thread.needsRun) {
      continue;
        }
        if ((thread.threadOpts & J3dThreadData.START_TIMER) != 0) {
            view = (View)((Object[])thread.threadArgs)[2];
            view.frameNumber++;
            view.startTime = J3dClock.currentTimeMillis();
        }


        renderPending++;

        if (cpuLimit == 1) {
      thread.thread.args = (Object[])thread.threadArgs;
      thread.thread.doWork(currentTime);
        } else {
      threadPending++;
      thread.thread.runMonitor(J3dThread.RUN,
             currentTime,
             (Object[])thread.threadArgs);
        }

        if ((thread.threadOpts & J3dThreadData.STOP_TIMER) != 0) {
      view = (View)((Object[])thread.threadArgs)[3];
      timestampUpdateList.add(view);
        }

        if ((thread.threadOpts & J3dThreadData.LAST_STOP_TIMER) != 0) {
      // release lock on locked geometry and image component
      for( i = 0; i < views.arraySize(); i++) {
          currentV = allView[i];
          currentV.renderBin.releaseGeometry();
      }
        }

        if ((cpuLimit != 1) &&
      (thread.threadOpts &
       J3dThreadData.WAIT_ALL_THREADS) != 0) {

      renderWaiting = true;
        }


        if ((cpuLimit != 1) && (cpuLimit <= threadPending)) {
      state = WAITING_FOR_CPU;
      try {
          wait();
      } catch (InterruptedException e) {
          System.err.println(e);
      }
      state = RUNNING;
        }

    }
    // Now try state threads
    while (!stateWaiting &&
           currentStateThread != stateThreadSize) {
        thread = stateThreads[currentStateThread++];

        if (!thread.needsRun) {
      continue;
        }

        statePending++;

        if (cpuLimit == 1) {
      thread.thread.args = (Object[])thread.threadArgs;
      thread.thread.doWork(currentTime);
        } else {
      threadPending++;
      thread.thread.runMonitor(J3dThread.RUN,
             currentTime,
             (Object[])thread.threadArgs);
        }
        if (cpuLimit != 1 && (thread.threadOpts &
       J3dThreadData.WAIT_ALL_THREADS) != 0) {
      stateWaiting = true;
        }

        if ((cpuLimit != 1) && (cpuLimit <= threadPending)) {
      // Fix bug 4686766 - always allow
      // renderer thread to continue if not finish
      // geomLock can release for Behavior thread to
      // continue.
      if (currentRenderThread == renderThreadSize) {
          state = WAITING_FOR_CPU;
          try {
        wait();
          } catch (InterruptedException e) {
        System.err.println(e);
          }
          state = RUNNING;
      } else {
          // Run renderer thread next time
          break;
      }

        }
    }

    // Now try requestRender threads
                if (!renderWaiting &&
                     (currentRenderThread == renderThreadSize)) {
                    currentRequestRenderThread = 0;
                    while (!renderWaiting &&
                           (currentRequestRenderThread !=
                                requestRenderThreadSize)) {

                        thread =
         requestRenderThreads[currentRequestRenderThread++];

                        renderPending++;

                        if (cpuLimit == 1) {
                            thread.thread.args = (Object[])thread.threadArgs;
                            thread.thread.doWork(currentTime);
                        } else {
                            threadPending++;
                            thread.thread.runMonitor(J3dThread.RUN,
                                                currentTime,
                                                (Object[])thread.threadArgs);
                        }
                        if (cpuLimit != 1 && (thread.threadOpts &
                                J3dThreadData.WAIT_ALL_THREADS) != 0) {
                            renderWaiting = true;
                        }
                        if (cpuLimit != 1 && cpuLimit <= threadPending) {
                            state = WAITING_FOR_CPU;
                            try {
                                wait();
                            } catch (InterruptedException e) {
                                System.err.println(e);
                            }
          state = RUNNING;
                        }
                    }
                }

    if (cpuLimit != 1) {
        if ((renderWaiting &&
       (currentStateThread == stateThreadSize)) ||
              (stateWaiting &&
                currentRenderThread == renderThreadSize) ||
              (renderWaiting && stateWaiting)) {
      if (!requestRenderWorkToDo) {
                state = WAITING_FOR_THREADS;
                try {
              wait();
                } catch (InterruptedException e) {
              System.err.println(e);
                }
          state = RUNNING;
      }
      requestRenderWorkToDo = false;
        }
    }

                if ((currentStateThread == stateThreadSize) &&
                    (currentRenderThread == renderThreadSize) &&
                    (currentRequestRenderThread == requestRenderThreadSize) &&
        (threadPending == 0)) {
      for (int k = timestampUpdateList.size() - 1; k >= 0; k--) {
        View v = timestampUpdateList.get(k);
        v.setFrameTimingValues();
        v.universe.behaviorStructure.incElapsedFrames();
      }
        timestampUpdateList.clear();
        updateMirrorObjects();
        done = true;

                    if (isStatsLoggable(Level.INFO)) {
                        // Instrumentation of Java 3D renderer
                        logTimes();
                    }
    }
      }
      break;

  case THREAD_DONE:
      if (state != WAITING_FOR_RENDERER_CLEANUP) {

    threadPending--;
                assert threadPending >= 0 : ("threadPending = " + threadPending);
                if (nthread.type == J3dThread.RENDER_THREAD) {
        View v = (View) nthread.args[3];
        if (v != null) { // STOP_TIMER
      v.stopTime = J3dClock.currentTimeMillis();
        }

        if (--renderPending == 0) {
      renderWaiting = false;
        }
                    assert renderPending >= 0 : ("renderPending = " + renderPending);
    } else {
        if (--statePending == 0) {
      stateWaiting = false;
        }
                    assert statePending >= 0 : ("statePending = " + statePending);
    }
    if (state == WAITING_FOR_CPU || state == WAITING_FOR_THREADS) {
        notify();
    }
      } else {
    notify();
    state = RUNNING;
      }
      break;

  case CHECK_FOR_WORK:
      if (!workToDo) {
    state = SLEEPING;
                // NOTE: this could wakeup spuriously (see issue 279), but it
                // will not cause any problems.
    try {
        wait();
    } catch (InterruptedException e) {
        System.err.println(e);
    }
          state = RUNNING;
      }
      workToDo = false;
      break;

  case SET_WORK:
      workToDo = true;
      if (state == SLEEPING) {
    notify();
      }
      break;

        case SET_WORK_FOR_REQUEST_RENDERER:
      requestRenderWorkToDo = true;
      workToDo = true;
      if (state == WAITING_FOR_CPU || state == WAITING_FOR_THREADS ||
      state == SLEEPING) {
    notify();
      }
      break;

  case RUN_RENDERER_CLEANUP:
      nthread.runMonitor(J3dThread.RUN, currentTime,
             rendererCleanupArgs);
      state = WAITING_FOR_RENDERER_CLEANUP;
            // Issue 279 - loop until state is set to running
            while (state != RUNNING) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    System.err.println(e);
                }
            }
      break;

        default:
            // Should never get here
            assert false : "missing case in switch statement";
        }
    }

    // Static initializer
    static {
        // create ThreadGroup
        java.security.AccessController.doPrivileged(
            new java.security.PrivilegedAction<Object>() {
                @Override
                public Object run() {
                    ThreadGroup parent;
                    Thread thread = Thread.currentThread();
                    threadPriority = thread.getPriority();
                    rootThreadGroup = thread.getThreadGroup();
                    while ((parent = rootThreadGroup.getParent()) != null) {
                        rootThreadGroup = parent;
                    }
                    rootThreadGroup = new ThreadGroup(rootThreadGroup,
                            "Java3D");
                    // use the default maximum group priority
                    return null;
                }
        });

        // Initialize loggers
        try {
            initLoggers();
        } catch (RuntimeException ex) {
            System.err.println(ex);
        }
    }


    static String mtype[] = {
        "INSERT_NODES",
        "REMOVE_NODES",
        "RUN",
        "TRANSFORM_CHANGED",
        "UPDATE_VIEW",
        "STOP_THREAD",
        "COLORINGATTRIBUTES_CHANGED",
        "LINEATTRIBUTES_CHANGED",
        "POINTATTRIBUTES_CHANGED",
        "POLYGONATTRIBUTES_CHANGED",
        "RENDERINGATTRIBUTES_CHANGED",
        "TEXTUREATTRIBUTES_CHANGED",
        "TRANSPARENCYATTRIBUTES_CHANGED",
        "MATERIAL_CHANGED",
        "TEXCOORDGENERATION_CHANGED",
        "TEXTURE_CHANGED",
        "MORPH_CHANGED",
        "GEOMETRY_CHANGED",
        "APPEARANCE_CHANGED",
        "LIGHT_CHANGED",
        "BACKGROUND_CHANGED",
        "CLIP_CHANGED",
        "FOG_CHANGED",
        "BOUNDINGLEAF_CHANGED",
        "SHAPE3D_CHANGED",
        "TEXT3D_TRANSFORM_CHANGED",
        "TEXT3D_DATA_CHANGED",
        "SWITCH_CHANGED",
        "COND_MET",
        "BEHAVIOR_ENABLE",
        "BEHAVIOR_DISABLE",
        "INSERT_RENDERATOMS",
        "ORDERED_GROUP_INSERTED",
        "ORDERED_GROUP_REMOVED",
        "COLLISION_BOUND_CHANGED",
        "REGION_BOUND_CHANGED",
        "MODELCLIP_CHANGED",
        "BOUNDS_AUTO_COMPUTE_CHANGED",
        "SOUND_ATTRIB_CHANGED",
        "AURALATTRIBUTES_CHANGED",
        "SOUNDSCAPE_CHANGED",
        "ALTERNATEAPPEARANCE_CHANGED",
        "RENDER_OFFSCREEN",
        "RENDER_RETAINED",
        "RENDER_IMMEDIATE",
        "SOUND_STATE_CHANGED",
        "ORIENTEDSHAPE3D_CHANGED",
        "TEXTURE_UNIT_STATE_CHANGED",
        "UPDATE_VIEWPLATFORM",
        "BEHAVIOR_ACTIVATE",
        "GEOMETRYARRAY_CHANGED",
        "MEDIA_CONTAINER_CHANGED",
        "RESIZE_CANVAS",
        "TOGGLE_CANVAS",
        "IMAGE_COMPONENT_CHANGED",
        "SCHEDULING_INTERVAL_CHANGED",
        "VIEWSPECIFICGROUP_CHANGED",
        "VIEWSPECIFICGROUP_INIT",
        "VIEWSPECIFICGROUP_CLEAR",
        "ORDERED_GROUP_TABLE_CHANGED",
        "BEHAVIOR_REEVALUATE",
        "CREATE_OFFSCREENBUFFER",
        "DESTROY_CTX_AND_OFFSCREENBUFFER",
        "SHADER_ATTRIBUTE_CHANGED",
        "SHADER_ATTRIBUTE_SET_CHANGED",
        "SHADER_APPEARANCE_CHANGED",
        "ALLOCATE_CANVASID",
        "FREE_CANVASID",
    };

    private String dumpThreads(int threads) {
        StringBuffer strBuf = new StringBuffer();
        strBuf.append("threads:");
  //dump Threads type
        if ((threads & J3dThread.BEHAVIOR_SCHEDULER) != 0) {
            strBuf.append(" BEHAVIOR_SCHEDULER");
        }
        if ((threads & J3dThread.SOUND_SCHEDULER) != 0) {
            strBuf.append(" SOUND_SCHEDULER");
        }
        if ((threads & J3dThread.INPUT_DEVICE_SCHEDULER) != 0) {
            strBuf.append(" INPUT_DEVICE_SCHEDULER");
        }
        if ((threads & J3dThread.RENDER_THREAD) != 0) {
            strBuf.append(" RENDER_THREAD");
        }
        if ((threads & J3dThread.UPDATE_GEOMETRY) != 0) {
            strBuf.append(" UPDATE_GEOMETRY");
        }
        if ((threads & J3dThread.UPDATE_RENDER) != 0) {
            strBuf.append(" UPDATE_RENDER");
        }
        if ((threads & J3dThread.UPDATE_BEHAVIOR) != 0) {
            strBuf.append(" UPDATE_BEHAVIOR");
        }
        if ((threads & J3dThread.UPDATE_SOUND) != 0) {
            strBuf.append(" UPDATE_SOUND");
        }
        if ((threads & J3dThread.UPDATE_RENDERING_ATTRIBUTES) != 0) {
            strBuf.append(" UPDATE_RENDERING_ATTRIBUTES");
        }
        if ((threads & J3dThread.UPDATE_RENDERING_ENVIRONMENT) != 0) {
            strBuf.append(" UPDATE_RENDERING_ENVIRONMENT");
        }
        if ((threads & J3dThread.UPDATE_TRANSFORM) != 0) {
            strBuf.append(" UPDATE_TRANSFORM");
        }

        return strBuf.toString();
    }

    // Method to log the specified message. Note that the caller
    // should check for isCoreLoggable(FINEST) before calling
    private void dumpMessage(String str, J3dMessage m) {
        StringBuffer strBuf = new StringBuffer();
        strBuf.append(str).append(" ");
        if (m.type >= 0 && m.type < mtype.length) {
            strBuf.append(mtype[m.type]);
        } else {
            strBuf.append("<UNKNOWN>");
        }
        strBuf.append("  ").append(dumpThreads(m.threads));
        getCoreLogger().finest(strBuf.toString());
    }


    int frameCount = 0;
    private int frameCountCutoff = 100;

    private void manageMemory() {
  if (++frameCount > frameCountCutoff) {
      FreeListManager.manageLists();
      frameCount = 0;
  }
    }

    /**
     * Yields the current thread, by sleeping for a small amount of
     * time.  Unlike <code>Thread.yield()</code>, this method
     * guarantees that the current thread will yield to another thread
     * waiting to run.  It also ensures that the other threads will
     * run for at least a small amount of time before the current
     * thread runs again.
     */
    static final void threadYield() {
  // Note that we can't just use Thread.yield(), since it
  // doesn't guarantee that it will actually yield the thread
  // (and, in fact, it appears to be a no-op under Windows). So
  // we will sleep for 1 msec instead. Since most threads use
  // wait/notify, and only use this when they are waiting for
  // another thread to finish something, this shouldn't be a
  // performance concern.

  //Thread.yield();
  try {
      Thread.sleep(1);
  }
  catch (InterruptedException e) {
      // Do nothing, since we really don't care how long (or
      // even whether) we sleep
  }
    }

    // Return the number of available processors
    private int getNumberOfProcessors() {
        return Runtime.getRuntime().availableProcessors();
    }

    //
    // The following framework supports code instrumentation. To use it,
    // add code of the following form to areas that you want to enable for
    // timing:
    //
    //     long startTime = System.nanoTime();
    //     sortTransformGroups(tSize, tgs);
    //     long deltaTime = System.nanoTime() - startTime;
    //     VirtualUniverse.mc.recordTime(MasterControl.TimeType.XXXXX, deltaTime);
    //
    // where "XXXXX" is the enum representing the code segment being timed.
    // Additional enums can be defined for new subsystems.
    //

    static enum TimeType {
        TOTAL_FRAME,
        RENDER,
        BEHAVIOR,
        // TRANSFORM_UPDATE,
        // ...
    }

    private long[] statTimes = new long[TimeType.values().length];
    private int[] statCounts = new int[TimeType.values().length];
    private boolean[] statSeen = new boolean[TimeType.values().length];
    private int frameCycleTick = 0;
    private long frameCycleNumber = 0L;

    // Method to record times -- should not be called unless the stats logger
    // level is set to INFO or lower
    synchronized void recordTime(TimeType type, long deltaTime) {
        int idx = type.ordinal();
        statTimes[idx] += deltaTime;
        statCounts[idx]++;
        statSeen[idx] = true;
    }

    // Method to record times -- this is not called unless the stats logger
    // level is set to INFO or lower
    private synchronized void logTimes() {
        ++frameCycleNumber;
        if (++frameCycleTick >= 10) {
            StringBuffer strBuf = new StringBuffer();
            strBuf.append("----------------------------------------------\n").
                    append("    Frame Number = ").
                    append(frameCycleNumber).
                    append("\n");
            for (int i = 0; i < statTimes.length; i++) {
                if (statSeen[i]) {
                    strBuf.append("    ");
                    if (statCounts[i] > 0) {
                        strBuf.append(TimeType.values()[i]).
                                append(" [").
                                append(statCounts[i]).
                                append("] = ").
                                append((double)statTimes[i] / 1000000.0 / (double)statCounts[i]).
                                append(" msec per call\n");
                        statTimes[i] = 0L;
                        statCounts[i] = 0;
                    } else {
                        assert statTimes[i] == 0L;
                        strBuf.append(TimeType.values()[i]).
                                append(" [0] = 0.0 msec\n");
                    }
                }
            }
            getStatsLogger().info(strBuf.toString());
            frameCycleTick = 0;
        }
    }

}
TOP

Related Classes of javax.media.j3d.MasterControl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.