1 /**
2  * Copyright 2010 JogAmp Community. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without modification, are
5  * permitted provided that the following conditions are met:
6  *
7  *    1. Redistributions of source code must retain the above copyright notice, this list of
8  *       conditions and the following disclaimer.
9  *
10  *    2. Redistributions in binary form must reproduce the above copyright notice, this list
11  *       of conditions and the following disclaimer in the documentation and/or other materials
12  *       provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
15  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
16  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
22  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  *
24  * The views and conclusions contained in the software and documentation are those of the
25  * authors and should not be interpreted as representing official policies, either expressed
26  * or implied, of JogAmp Community.
27  */
28 package jogamp.opengl;
29 
30 import java.util.Collection;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.Iterator;
34 import java.util.Map;
35 
36 import com.jogamp.nativewindow.AbstractGraphicsDevice;
37 import com.jogamp.nativewindow.AbstractGraphicsScreen;
38 import com.jogamp.opengl.GLProfile;
39 
40 import com.jogamp.common.ExceptionUtils;
41 import com.jogamp.common.util.InterruptSource;
42 import com.jogamp.common.util.InterruptedRuntimeException;
43 import com.jogamp.common.util.SourcedInterruptedException;
44 import com.jogamp.opengl.GLRendererQuirks;
45 
46 public class SharedResourceRunner implements Runnable {
47     protected static final boolean DEBUG = GLDrawableImpl.DEBUG;
48 
49     public static interface Resource {
isAvailable()50       boolean isAvailable();
getDevice()51       AbstractGraphicsDevice getDevice();
getScreen()52       AbstractGraphicsScreen getScreen();
getDrawable()53       GLDrawableImpl getDrawable();
getContext()54       GLContextImpl getContext();
getRendererQuirks(GLProfile glp)55       GLRendererQuirks getRendererQuirks(GLProfile glp);
56     }
57 
58     public static interface Implementation {
59         /**
60          * <p>
61          * Called within synchronized block.
62          * </p>
63          * @param device for creation a {@link AbstractGraphicsDevice} instance.
64          * @return <code>true</code> if the device supports all protocols required for the implementation, otherwise <code>false</code>.
65          */
isDeviceSupported(final AbstractGraphicsDevice device)66         boolean isDeviceSupported(final AbstractGraphicsDevice device);
67 
68         /**
69          * <p>
70          * Called within synchronized block.
71          * </p>
72          * @param device for creation a {@link AbstractGraphicsDevice} instance.
73          * @return A new shared resource instance
74          */
createSharedResource(final AbstractGraphicsDevice device)75         Resource createSharedResource(final AbstractGraphicsDevice device);
76 
77         /** Called within synchronized block. */
releaseSharedResource(Resource shared)78         void releaseSharedResource(Resource shared);
79         /** Called within synchronized block. */
clear()80         void clear();
81 
82         /** Called within synchronized block. */
mapPut(final AbstractGraphicsDevice device, final Resource resource)83         Resource mapPut(final AbstractGraphicsDevice device, final Resource resource);
84         /** Called within synchronized block. */
mapGet(final AbstractGraphicsDevice device)85         Resource mapGet(final AbstractGraphicsDevice device);
86         /** Called within synchronized block. */
mapValues()87         Collection<Resource> mapValues();
88     }
89     public static abstract class AImplementation implements Implementation {
90         private final HashMap<String /* uniqueId */, SharedResourceRunner.Resource> sharedMap = new HashMap<String, SharedResourceRunner.Resource>();
91         /** Called within synchronized block. Use w/ care! */
getSharedMap()92         public Map<String /* uniqueId */, SharedResourceRunner.Resource> getSharedMap() {
93             return sharedMap;
94         }
95         @Override
clear()96         public final void clear() {
97             sharedMap.clear();
98         }
99         @Override
mapPut(final AbstractGraphicsDevice device, final SharedResourceRunner.Resource resource)100         public final SharedResourceRunner.Resource mapPut(final AbstractGraphicsDevice device, final SharedResourceRunner.Resource resource) {
101             return sharedMap.put(device.getUniqueID(), resource);
102         }
103         @Override
mapGet(final AbstractGraphicsDevice device)104         public final SharedResourceRunner.Resource mapGet(final AbstractGraphicsDevice device) {
105             return sharedMap.get(device.getUniqueID());
106         }
107         @Override
mapValues()108         public final Collection<SharedResourceRunner.Resource> mapValues() {
109             return sharedMap.values();
110         }
111     }
112 
113     final HashSet<String> devicesTried = new HashSet<String>();
114     final Implementation impl;
115 
116     Thread thread;
117     boolean running;
118     boolean ready;
119     boolean shouldRelease;
120     AbstractGraphicsDevice initDevice;
121     AbstractGraphicsDevice releaseDevice;
122 
getDeviceTried(final AbstractGraphicsDevice device)123     private boolean getDeviceTried(final AbstractGraphicsDevice device) { // synchronized call
124         return devicesTried.contains(device.getUniqueID());
125     }
addDeviceTried(final AbstractGraphicsDevice device)126     private void addDeviceTried(final AbstractGraphicsDevice device) { // synchronized call
127         devicesTried.add(device.getUniqueID());
128     }
removeDeviceTried(final AbstractGraphicsDevice device)129     private void removeDeviceTried(final AbstractGraphicsDevice device) { // synchronized call
130         devicesTried.remove(device.getUniqueID());
131     }
132 
SharedResourceRunner(final Implementation impl)133     public SharedResourceRunner(final Implementation impl) {
134         this.impl = impl;
135         resetState();
136     }
137 
resetState()138     private void resetState() { // synchronized call
139         devicesTried.clear();
140         thread = null;
141         ready = false;
142         running = false;
143         shouldRelease = false;
144         initDevice = null;
145         releaseDevice = null;
146     }
147 
148     /**
149      * Start the shared resource runner thread, if not running.
150      * <p>
151      * Validate the thread upfront and release all related resource if it was killed.
152      * </p>
153      *
154      * @return the shared resource runner thread.
155      */
start()156     public Thread start() {
157         synchronized (this) {
158             if(null != thread && !thread.isAlive()) {
159                 // thread was killed unrecognized ..
160                 if (DEBUG) {
161                     System.err.println("SharedResourceRunner.start() - dead-old-thread cleanup - "+getThreadName());
162                 }
163                 releaseSharedResources();
164                 thread = null;
165                 running = false;
166             }
167             if( null == thread ) {
168                 if (DEBUG) {
169                     System.err.println("SharedResourceRunner.start() - start new Thread - "+getThreadName());
170                 }
171                 resetState();
172                 thread = new InterruptSource.Thread(null, this, getThreadName()+"-SharedResourceRunner");
173                 thread.setDaemon(true); // Allow JVM to exit, even if this one is running
174                 thread.start();
175                 try {
176                     while (!running) {
177                         this.wait();
178                     }
179                 } catch (final InterruptedException ex) {
180                     // Cleanup
181                     shouldRelease = true;
182                     this.notifyAll();
183                     throw new InterruptedRuntimeException(ex);
184                 }
185             }
186         }
187         return thread;
188     }
189 
stop()190     public void stop() {
191         synchronized (this) {
192             if(null != thread) {
193                 if (DEBUG) {
194                     System.err.println("SharedResourceRunner.stop() - "+getThreadName());
195                 }
196                 synchronized (this) {
197                     shouldRelease = true;
198                     this.notifyAll();
199                     try {
200                         while (running) {
201                             this.wait();
202                         }
203                     } catch (final InterruptedException ex) {
204                         throw new InterruptedRuntimeException(ex);
205                     }
206                 }
207             }
208         }
209     }
210 
getOrCreateShared(final AbstractGraphicsDevice device)211     public SharedResourceRunner.Resource getOrCreateShared(final AbstractGraphicsDevice device) {
212         SharedResourceRunner.Resource sr = null;
213         if(null != device) {
214             synchronized (this) {
215                 start();
216                 sr = impl.mapGet(device);
217                 if (null == sr) {
218                     if ( !getDeviceTried(device) ) {
219                         addDeviceTried(device);
220                         if (DEBUG) {
221                             System.err.println("SharedResourceRunner.getOrCreateShared() " + device + ": trying - "+getThreadName());
222                             ExceptionUtils.dumpStack(System.err);
223                         }
224                         if ( impl.isDeviceSupported(device) ) {
225                             try {
226                                 doAndWait(device, null);
227                             } catch (final InterruptedException ex) {
228                                 throw new InterruptedRuntimeException(ex);
229                             }
230                             sr = impl.mapGet(device);
231                         }
232                         if (DEBUG) {
233                             System.err.println("SharedResourceRunner.getOrCreateShared() " + device + ": "+ ( ( null != sr ) ? "success" : "failed" ) +" - "+getThreadName());
234                         }
235                     }
236                 }
237             }
238         }
239         return sr;
240     }
241 
releaseShared(final AbstractGraphicsDevice device)242     public SharedResourceRunner.Resource releaseShared(final AbstractGraphicsDevice device) {
243         SharedResourceRunner.Resource sr = null;
244         if(null != device) {
245             synchronized (this) {
246                 sr = impl.mapGet(device);
247                 if (null != sr) {
248                     removeDeviceTried(device);
249                     if (DEBUG) {
250                         System.err.println("SharedResourceRunner.releaseShared() " + device + ": trying - "+getThreadName());
251                     }
252                     try {
253                         doAndWait(null, device);
254                     } catch (final InterruptedException ex) {
255                         throw new InterruptedRuntimeException(ex);
256                     }
257                     if (DEBUG) {
258                         System.err.println("SharedResourceRunner.releaseShared() " + device + ": done - "+getThreadName());
259                     }
260                 }
261             }
262         }
263         return sr;
264     }
265 
doAndWait(final AbstractGraphicsDevice initDevice, final AbstractGraphicsDevice releaseDevice)266     private final void doAndWait(final AbstractGraphicsDevice initDevice, final AbstractGraphicsDevice releaseDevice) throws InterruptedException {
267         synchronized (this) {
268             // wait until thread becomes ready to init new device,
269             // pass the device and release the sync
270             final String threadName = getThreadName();
271             if (DEBUG) {
272                 System.err.println("SharedResourceRunner.doAndWait() START init: " + initDevice + ", release: "+releaseDevice+" - "+threadName);
273             }
274             try {
275                 while (!ready && running) {
276                     this.wait();
277                 }
278                 if (DEBUG) {
279                     System.err.println("SharedResourceRunner.doAndWait() set command: " + initDevice + ", release: "+releaseDevice+" - "+threadName);
280                 }
281                 this.initDevice = initDevice;
282                 this.releaseDevice = releaseDevice;
283                 this.notifyAll();
284 
285                 // wait until thread has init/released the device
286                 while ( running && ( !ready || null != this.initDevice || null != this.releaseDevice ) ) {
287                     this.wait();
288                 }
289             } catch (final InterruptedException ex) {
290                 final InterruptedException ex2 = SourcedInterruptedException.wrap(ex);
291                 if (DEBUG) {
292                     System.err.println("SharedResourceRunner.doAndWait() INTERRUPT init: " + initDevice + ", release: "+releaseDevice+" - "+threadName);
293                     ExceptionUtils.dumpThrowable("", ex2);
294                 }
295                 // Cleanup initDevice due to exception!
296                 final AbstractGraphicsDevice _initDevice = this.initDevice;
297                 if( null != _initDevice ) {
298                     if (DEBUG) {
299                         System.err.println("SharedResourceRunner.doAndWait() Cleanup init: " + _initDevice + " -> release: "+this.releaseDevice+" - "+threadName);
300                     }
301                     this.releaseDevice = _initDevice;
302                     this.initDevice = null;
303                     this.notifyAll();
304                 }
305                 throw ex2;
306             }
307             if (DEBUG) {
308                 System.err.println("SharedResourceRunner.doAndWait() END init: " + initDevice + ", release: "+releaseDevice+" - "+threadName);
309             }
310         }
311         // done
312     }
313 
314     @Override
run()315     public final void run() {
316         final String threadName = getThreadName();
317 
318         if (DEBUG) {
319             System.err.println("SharedResourceRunner.run(): STARTED - " + threadName);
320         }
321 
322         synchronized (this) {
323             running = true;
324 
325             while (!shouldRelease) {
326                 try {
327                     // wait until call-thread issues stop or init/released a device
328                     ready = true;
329                     if (DEBUG) {
330                         System.err.println("SharedResourceRunner.run(): READY - " + threadName);
331                     }
332                     notifyAll();
333                     while ( !shouldRelease && null == initDevice && null == releaseDevice ) {
334                         this.wait();
335                     }
336                 } catch (final InterruptedException ex) {
337                     shouldRelease = true;
338                     ExceptionUtils.dumpThrowable("handled", SourcedInterruptedException.wrap(ex)); // cancelable
339                 }
340                 ready = false;
341 
342                 if (!shouldRelease) {
343                     if (DEBUG) {
344                         System.err.println("SharedResourceRunner.run(): WOKE UP for device connection init: " + initDevice +
345                                            ", release: " + releaseDevice + " - " + threadName);
346                     }
347                     if(null != initDevice) {
348                         if (DEBUG) {
349                             System.err.println("SharedResourceRunner.run(): create Shared for: " + initDevice + " - " + threadName);
350                         }
351                         Resource sr = null;
352                         try {
353                             sr = impl.createSharedResource(initDevice);
354                         } catch (final Exception e) {
355                             ExceptionUtils.dumpThrowable("handled", e);
356                         }
357                         if (null != sr) {
358                             impl.mapPut(initDevice, sr);
359                         }
360                     }
361                     if(null != releaseDevice) {
362                         if (DEBUG) {
363                             System.err.println("SharedResourceRunner.run(): release Shared for: " + releaseDevice + " - " + threadName);
364                         }
365                         final Resource sr = impl.mapGet(releaseDevice);
366                         if (null != sr) {
367                             try {
368                                 impl.releaseSharedResource(sr);
369                             } catch (final Exception e) {
370                                 ExceptionUtils.dumpThrowable("handled", e);
371                             } finally {
372                                 impl.mapPut(releaseDevice, null);
373                             }
374                         }
375                     }
376                 }
377                 initDevice = null;
378                 releaseDevice = null;
379             }
380 
381             if (DEBUG) {
382                 System.err.println("SharedResourceRunner.run(): RELEASE START - " + threadName);
383             }
384 
385             releaseSharedResources();
386 
387             if (DEBUG) {
388                 System.err.println("SharedResourceRunner.run(): RELEASE END - " + threadName);
389             }
390 
391             shouldRelease = false;
392             running = false;
393             thread = null;
394             notifyAll();
395         }
396     }
397 
releaseSharedResources()398     private void releaseSharedResources() { // synchronized call
399         devicesTried.clear();
400         final Collection<Resource> sharedResources = impl.mapValues();
401         for (final Iterator<Resource> iter = sharedResources.iterator(); iter.hasNext();) {
402             try {
403                 impl.releaseSharedResource(iter.next());
404             } catch (final Throwable t) {
405                 ExceptionUtils.dumpThrowable("", t);
406             }
407         }
408         impl.clear();
409     }
410 
getThreadName()411     protected static String getThreadName() { return Thread.currentThread().getName(); }
412 }
413