/* * 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 deviceScreenMap = new Hashtable(); // 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 timestampUpdateList = new ArrayList(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() { @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() { @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() { @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() { @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() { @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 before * 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 } // Java 3D cannot run in headless mode unless using the noop renderer if (java.awt.GraphicsEnvironment.isHeadless() && pipelineType != Pipeline.Type.NOOP) { throw new java.awt.HeadlessException(); } // 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() { @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() { @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) { 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() { @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() { @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 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 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 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 sleepTime) { sleepTime = thread.view.sleepTime; } } v = thread.view; } v = null; for (i=0; i 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() { @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() { @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 list = new ArrayList(); for (Enumeration 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 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 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 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() { @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() { @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(); 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=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() { @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() { @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=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 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=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() { @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(""); } 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 Thread.yield(), 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; } } }