1 /* 2 * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * - Redistribution of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 11 * - Redistribution in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * Neither the name of Sun Microsystems, Inc. or the names of 16 * contributors may be used to endorse or promote products derived from 17 * this software without specific prior written permission. 18 * 19 * This software is provided "AS IS," without a warranty of any kind. ALL 20 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, 21 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A 22 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN 23 * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR 24 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR 25 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR 26 * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR 27 * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE 28 * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, 29 * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF 30 * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 31 * 32 * You acknowledge that this software is not designed or intended for use 33 * in the design, construction, operation or maintenance of any nuclear 34 * facility. 35 * 36 * Sun gratefully acknowledges that this software was originally authored 37 * and developed by Kenneth Bradley Russell and Christopher John Kline. 38 */ 39 40 package jogamp.opengl; 41 42 import java.lang.reflect.InvocationTargetException; 43 import java.util.ArrayList; 44 import java.util.List; 45 46 import com.jogamp.common.ExceptionUtils; 47 import com.jogamp.common.util.InterruptSource; 48 import com.jogamp.common.util.InterruptedRuntimeException; 49 import com.jogamp.opengl.GLContext; 50 51 /** Singleton thread upon which all OpenGL work is performed by 52 default. Unfortunately many vendors' OpenGL drivers are not really 53 thread-safe and stability is much improved by performing OpenGL 54 work on at most one thread. This is the default behavior of the 55 GLAutoDrawable implementations according to the {@link 56 com.jogamp.opengl.Threading Threading} class. The GLWorkerThread 57 replaces the original AWT event queue thread-based mechanism for 58 two reasons: first, more than one AWT event queue thread may be 59 spawned, for example if a dialog is being shown; second, it avoids 60 blocking the AWT event queue thread during OpenGL rendering. */ 61 62 public class GLWorkerThread { 63 private static volatile boolean started; 64 private static volatile Thread thread; 65 private static Object lock; 66 private static volatile boolean shouldTerminate; 67 private static volatile Throwable exception; 68 69 // The Runnable to execute immediately on the worker thread 70 private static volatile Runnable work; 71 // Queue of Runnables to be asynchronously invoked 72 private static List<Runnable> queue = new ArrayList<Runnable>(); 73 74 /** Should only be called by Threading class if creation of the 75 GLWorkerThread was requested via the opengl.1thread system 76 property. <br> 77 * Start the GLWorkerThread iff not started yet! 78 */ start()79 public static void start() { 80 if (!started) { // volatile: ok 81 synchronized (GLWorkerThread.class) { 82 if (!started) { 83 lock = new Object(); 84 final WorkerRunnable worker = new WorkerRunnable(); 85 thread = new InterruptSource.Thread(null, worker, "JOGL-GLWorkerThread-"); 86 thread.setDaemon(true); 87 started = true; 88 synchronized (lock) { 89 thread.start(); 90 try { 91 while(!worker.isRunning) { 92 lock.wait(); 93 } 94 } catch (final InterruptedException e) { 95 throw new InterruptedRuntimeException(e); 96 } 97 } 98 99 /* 100 101 // Note: it appears that there is a bug in NVidia's current 102 // drivers where if a context was ever made current on a 103 // given thread and that thread has exited before program 104 // exit, a crash occurs in the drivers. Releasing the 105 // context from the given thread does not work around the 106 // problem. 107 // 108 // For the time being, we're going to work around this 109 // problem by not terminating the GLWorkerThread. In theory, 110 // shutting down the GLWorkerThread cleanly could be a good 111 // general solution to the problem of needing to 112 // cooperatively terminate all Animators at program exit. 113 // 114 // It appears that this doesn't even work around all of the 115 // kinds of crashes. Causing the context to be unilaterally 116 // released from the GLWorkerThread after each invocation 117 // seems to work around all of the kinds of crashes seen. 118 // 119 // These appear to be similar to the kinds of crashes seen 120 // when the Java2D/OpenGL pipeline terminates, and those are 121 // a known issue being fixed, so presumably these will be 122 // fixed in NVidia's next driver set. 123 124 // Install shutdown hook to terminate daemon thread more or 125 // less cooperatively 126 AccessController.doPrivileged(new PrivilegedAction() { 127 public Object run() { 128 Runtime.getRuntime().addShutdownHook(new InterruptSource.Thread() { 129 public void run() { 130 Object lockTemp = lock; 131 if (lockTemp == null) { 132 // Already terminating (?) 133 return; 134 } 135 synchronized (lockTemp) { 136 shouldTerminate = true; 137 lockTemp.notifyAll(); 138 try { 139 lockTemp.wait(500); 140 } catch (InterruptedException e) { 141 } 142 } 143 } 144 }); 145 return null; 146 } 147 }); 148 149 */ 150 151 } else { 152 throw new RuntimeException(getThreadName()+": Should not start GLWorkerThread twice"); 153 } 154 } 155 } 156 } 157 invoke(final boolean wait, final Runnable runnable)158 public static void invoke(final boolean wait, final Runnable runnable) 159 throws InvocationTargetException, InterruptedException { 160 if(wait) { 161 invokeAndWait(runnable); 162 } else { 163 invokeLater(runnable); 164 } 165 } 166 invokeAndWait(final Runnable runnable)167 public static void invokeAndWait(final Runnable runnable) 168 throws InvocationTargetException, InterruptedException { 169 if (!started) { 170 throw new RuntimeException(getThreadName()+": May not invokeAndWait on worker thread without starting it first"); 171 } 172 173 final Object lockTemp = lock; 174 if (lockTemp == null) { 175 return; // Terminating 176 } 177 178 synchronized (lockTemp) { 179 if (thread == null) { 180 // Terminating 181 return; 182 } 183 184 work = runnable; 185 lockTemp.notifyAll(); 186 while( null != work ) { 187 lockTemp.wait(); 188 } 189 if (exception != null) { 190 final Throwable localException = exception; 191 exception = null; 192 throw new InvocationTargetException(localException); 193 } 194 } 195 } 196 invokeLater(final Runnable runnable)197 public static void invokeLater(final Runnable runnable) { 198 if (!started) { 199 throw new RuntimeException(getThreadName()+": May not invokeLater on worker thread without starting it first"); 200 } 201 202 final Object lockTemp = lock; 203 if (lockTemp == null) { 204 return; // Terminating 205 } 206 207 synchronized (lockTemp) { 208 if (thread == null) { 209 // Terminating 210 return; 211 } 212 213 queue.add(runnable); 214 lockTemp.notifyAll(); 215 } 216 } 217 218 /** Indicates whether the OpenGL worker thread was started, i.e., 219 whether it is currently in use. */ isStarted()220 public static boolean isStarted() { 221 return started; 222 } 223 224 /** Indicates whether the current thread is the OpenGL worker 225 thread. */ isWorkerThread()226 public static boolean isWorkerThread() { 227 return (Thread.currentThread() == thread); 228 } 229 getThreadName()230 protected static String getThreadName() { return Thread.currentThread().getName(); } 231 232 static class WorkerRunnable implements Runnable { 233 volatile boolean isRunning = false; 234 235 @Override run()236 public void run() { 237 // Notify starting thread that we're ready 238 synchronized (lock) { 239 isRunning = true; 240 lock.notifyAll(); 241 } 242 243 while (!shouldTerminate) { 244 synchronized (lock) { 245 while (!shouldTerminate && 246 (work == null) && 247 queue.isEmpty()) { 248 try { 249 // Avoid race conditions with wanting to release contexts on this thread 250 lock.wait(1000); 251 } catch (final InterruptedException e) { 252 throw new InterruptedRuntimeException(e); 253 } 254 255 if (GLContext.getCurrent() != null) { 256 // Test later to see whether we need to release this context 257 break; 258 } 259 } 260 261 if (shouldTerminate) { 262 lock.notifyAll(); 263 thread = null; 264 lock = null; 265 return; 266 } 267 268 if (work != null) { 269 try { 270 work.run(); 271 } catch (final Throwable t) { 272 exception = t; 273 } finally { 274 work = null; 275 lock.notifyAll(); 276 } 277 } 278 279 while (!queue.isEmpty()) { 280 try { 281 final Runnable curAsync = queue.remove(0); 282 curAsync.run(); 283 } catch (final Throwable t) { 284 ExceptionUtils.dumpThrowable("suppressed", t); // Noncancelable 285 } 286 } 287 288 // See about releasing current context 289 final GLContext curContext = GLContext.getCurrent(); 290 if (curContext != null && 291 (curContext instanceof GLContextImpl)) { 292 final GLContextImpl impl = (GLContextImpl) curContext; 293 if (impl.hasWaiters()) { 294 impl.release(); 295 } 296 } 297 } 298 } 299 isRunning = false; 300 } 301 } 302 } 303