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