diff options
Diffstat (limited to 'src/javax/media/j3d/MasterControl.java')
-rw-r--r-- | src/javax/media/j3d/MasterControl.java | 3748 |
1 files changed, 3748 insertions, 0 deletions
diff --git a/src/javax/media/j3d/MasterControl.java b/src/javax/media/j3d/MasterControl.java new file mode 100644 index 0000000..0d7af66 --- /dev/null +++ b/src/javax/media/j3d/MasterControl.java @@ -0,0 +1,3748 @@ +/* + * 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; + } + } + +} |