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 com.sun.opengl.impl;
41 
42 import java.lang.reflect.InvocationTargetException;
43 import java.security.*;
44 import java.util.*;
45 import javax.media.opengl.*;
46 
47 /** Singleton thread upon which all OpenGL work is performed by
48     default. Unfortunately many vendors' OpenGL drivers are not really
49     thread-safe and stability is much improved by performing OpenGL
50     work on at most one thread. This is the default behavior of the
51     GLAutoDrawable implementations according to the {@link
52     javax.media.opengl.Threading Threading} class. The GLWorkerThread
53     replaces the original AWT event queue thread-based mechanism for
54     two reasons: first, more than one AWT event queue thread may be
55     spawned, for example if a dialog is being shown; second, it avoids
56     blocking the AWT event queue thread during OpenGL rendering. */
57 
58 public class GLWorkerThread {
59   private static volatile boolean started;
60   private static volatile Thread thread;
61   private static Object lock;
62   private static volatile boolean shouldTerminate;
63   private static volatile Throwable exception;
64 
65   // The Runnable to execute immediately on the worker thread
66   private static volatile Runnable work;
67   // Queue of Runnables to be asynchronously invoked
68   private static List queue = new LinkedList();
69 
70   /** Should only be called by Threading class if creation of the
71       GLWorkerThread was requested via the opengl.1thread system
72       property. */
start()73   public static void start() {
74     if (!started) {
75       synchronized (GLWorkerThread.class) {
76         if (!started) {
77           lock = new Object();
78           thread = new Thread(new WorkerRunnable(),
79                               "JOGL GLWorkerThread");
80           thread.setDaemon(true);
81           started = true;
82           synchronized (lock) {
83             thread.start();
84             try {
85               lock.wait();
86             } catch (InterruptedException e) {
87             }
88           }
89 
90           /*
91 
92           // Note: it appears that there is a bug in NVidia's current
93           // drivers where if a context was ever made current on a
94           // given thread and that thread has exited before program
95           // exit, a crash occurs in the drivers. Releasing the
96           // context from the given thread does not work around the
97           // problem.
98           //
99           // For the time being, we're going to work around this
100           // problem by not terminating the GLWorkerThread. In theory,
101           // shutting down the GLWorkerThread cleanly could be a good
102           // general solution to the problem of needing to
103           // cooperatively terminate all Animators at program exit.
104           //
105           // It appears that this doesn't even work around all of the
106           // kinds of crashes. Causing the context to be unilaterally
107           // released from the GLWorkerThread after each invocation
108           // seems to work around all of the kinds of crashes seen.
109           //
110           // These appear to be similar to the kinds of crashes seen
111           // when the Java2D/OpenGL pipeline terminates, and those are
112           // a known issue being fixed, so presumably these will be
113           // fixed in NVidia's next driver set.
114 
115           // Install shutdown hook to terminate daemon thread more or
116           // less cooperatively
117           AccessController.doPrivileged(new PrivilegedAction() {
118               public Object run() {
119                 Runtime.getRuntime().addShutdownHook(new Thread() {
120                     public void run() {
121                       Object lockTemp = lock;
122                       if (lockTemp == null) {
123                         // Already terminating (?)
124                         return;
125                       }
126                       synchronized (lockTemp) {
127                         shouldTerminate = true;
128                         lockTemp.notifyAll();
129                         try {
130                           lockTemp.wait(500);
131                         } catch (InterruptedException e) {
132                         }
133                       }
134                     }
135                   });
136                 return null;
137               }
138             });
139 
140           */
141 
142         } else {
143           throw new RuntimeException("Should not start GLWorkerThread twice");
144         }
145       }
146     }
147   }
148 
invokeAndWait(Runnable runnable)149   public static void invokeAndWait(Runnable runnable)
150     throws InvocationTargetException, InterruptedException {
151     if (!started) {
152       throw new RuntimeException("May not invokeAndWait on worker thread without starting it first");
153     }
154 
155     Object lockTemp = lock;
156     if (lockTemp == null) {
157       return; // Terminating
158     }
159 
160     synchronized (lockTemp) {
161       if (thread == null) {
162         // Terminating
163         return;
164       }
165 
166       work = runnable;
167       lockTemp.notifyAll();
168       lockTemp.wait();
169       if (exception != null) {
170         Throwable localException = exception;
171         exception = null;
172         throw new InvocationTargetException(localException);
173       }
174     }
175   }
176 
invokeLater(Runnable runnable)177   public static void invokeLater(Runnable runnable) {
178     if (!started) {
179       throw new RuntimeException("May not invokeLater on worker thread without starting it first");
180     }
181 
182     Object lockTemp = lock;
183     if (lockTemp == null) {
184       return; // Terminating
185     }
186 
187     synchronized (lockTemp) {
188       if (thread == null) {
189         // Terminating
190         return;
191       }
192 
193       queue.add(runnable);
194       lockTemp.notifyAll();
195     }
196   }
197 
198   /** Indicates whether the OpenGL worker thread was started, i.e.,
199       whether it is currently in use. */
isStarted()200   public static boolean isStarted() {
201     return started;
202   }
203 
204   /** Indicates whether the current thread is the OpenGL worker
205       thread. */
isWorkerThread()206   public static boolean isWorkerThread() {
207     return (Thread.currentThread() == thread);
208   }
209 
210   static class WorkerRunnable implements Runnable {
run()211     public void run() {
212       // Notify starting thread that we're ready
213       synchronized (lock) {
214         lock.notifyAll();
215       }
216 
217       while (!shouldTerminate) {
218         synchronized (lock) {
219           while (!shouldTerminate &&
220                  (work == null) &&
221                  queue.isEmpty()) {
222             try {
223               // Avoid race conditions with wanting to release contexts on this thread
224               lock.wait(1000);
225             } catch (InterruptedException e) {
226             }
227 
228             if (GLContext.getCurrent() != null) {
229               // Test later to see whether we need to release this context
230               break;
231             }
232           }
233 
234           if (shouldTerminate) {
235             lock.notifyAll();
236             thread = null;
237             lock = null;
238             return;
239           }
240 
241           if (work != null) {
242             try {
243               work.run();
244             } catch (Throwable t) {
245               exception = t;
246             } finally {
247               work = null;
248               lock.notifyAll();
249             }
250           }
251 
252           while (!queue.isEmpty()) {
253             try {
254               Runnable curAsync = (Runnable) queue.remove(0);
255               curAsync.run();
256             } catch (Throwable t) {
257               System.out.println("Exception occurred on JOGL OpenGL worker thread:");
258               t.printStackTrace();
259             }
260           }
261 
262           // See about releasing current context
263           GLContext curContext = GLContext.getCurrent();
264           if (curContext != null &&
265               (curContext instanceof GLContextImpl)) {
266             GLContextImpl impl = (GLContextImpl) curContext;
267             if (impl.hasWaiters()) {
268               impl.release();
269             }
270           }
271         }
272       }
273     }
274   }
275 }
276