1 /**
2  * Copyright 2012 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 
29 package jogamp.opengl.egl;
30 
31 import java.nio.IntBuffer;
32 import java.util.Iterator;
33 
34 import com.jogamp.nativewindow.AbstractGraphicsDevice;
35 import com.jogamp.nativewindow.NativeSurface;
36 import com.jogamp.nativewindow.NativeWindowFactory;
37 import com.jogamp.nativewindow.ToolkitLock;
38 import com.jogamp.opengl.GLException;
39 
40 import jogamp.opengl.Debug;
41 
42 import com.jogamp.common.ExceptionUtils;
43 import com.jogamp.common.nio.Buffers;
44 import com.jogamp.common.util.LongObjectHashMap;
45 import com.jogamp.nativewindow.egl.EGLGraphicsDevice;
46 import com.jogamp.opengl.egl.EGL;
47 
48 /**
49  * This implementation provides recursive calls to
50  * {@link EGL#eglInitialize(long, IntBuffer, IntBuffer)} and {@link EGL#eglTerminate(long)},
51  * where <code>eglInitialize(..)</code> is issued only for the 1st call per <code>eglDisplay</code>
52  * and <code>eglTerminate(..)</code> is issued only for the last call.
53  * <p>
54  * This class is required, due to implementation bugs within EGL where {@link EGL#eglTerminate(long)}
55  * does not mark the resource for deletion when still in use, bug releases them immediately.
56  * </p>
57  */
58 public class EGLDisplayUtil {
59     private static final boolean DEBUG = Debug.debug("EGLDisplayUtil");
60     private static boolean useSingletonEGLDisplay = false;
61     private static EGLDisplayRef singletonEGLDisplay = null;
62 
63     private static class EGLDisplayRef {
64         final long eglDisplay;
65         final Throwable createdStack;
66         int initRefCount;
67 
68         /**
69          * Returns an already opened {@link EGLDisplayRef} or opens a new {@link EGLDisplayRef}.
70          * <p>
71          * Opened {@link EGLDisplayRef}s are mapped against their <code>eglDisplay</code> handle.
72          * </p>
73          * <p>
74          * Method utilizes {@link EGLDisplayRef}'s reference counter, i.e. increases it.
75          * </p>
76          * <p>
77          * An {@link EGLDisplayRef} is <i>opened</i> via {@link EGL#eglInitialize(long, IntBuffer, IntBuffer)}.
78          * </p>
79          */
getOrCreateOpened(final long eglDisplay, final IntBuffer major, final IntBuffer minor)80         static EGLDisplayRef getOrCreateOpened(final long eglDisplay, final IntBuffer major, final IntBuffer minor) {
81             final EGLDisplayRef o = (EGLDisplayRef) openEGLDisplays.get(eglDisplay);
82             if( null == o ) {
83                 final boolean ok = EGL.eglInitialize(eglDisplay, major, minor);
84                 if( DEBUG ) {
85                     System.err.println("EGLDisplayUtil.EGL.eglInitialize 0x"+Long.toHexString(eglDisplay)+" -> "+ok);
86                 }
87                 if( ok ) {
88                     final EGLDisplayRef n = new EGLDisplayRef(eglDisplay);
89                     openEGLDisplays.put(eglDisplay, n);
90                     n.initRefCount++;
91                     if( DEBUG ) {
92                         System.err.println("EGLDisplayUtil.EGL.eglInitialize "+n);
93                     }
94                     if( null == singletonEGLDisplay ) {
95                         singletonEGLDisplay = n;
96                     }
97                     return n;
98                 } else {
99                     return null;
100                 }
101             } else {
102                 o.initRefCount++;
103                 return o;
104             }
105         }
106 
107         /**
108          * Closes an already opened {@link EGLDisplayRef}.
109          * <p>
110          * Method decreases a reference counter and closes the {@link EGLDisplayRef} if it reaches zero.
111          * </p>
112          * <p>
113          * An {@link EGLDisplayRef} is <i>closed</i> via {@link EGL#eglTerminate(long)}.
114          * </p>
115          */
closeOpened(final long eglDisplay, final boolean[] res)116         static EGLDisplayRef closeOpened(final long eglDisplay, final boolean[] res) {
117             final EGLDisplayRef o = (EGLDisplayRef) openEGLDisplays.get(eglDisplay);
118             res[0] = true;
119             if( null != o ) {
120                 if( 0 < o.initRefCount ) { // no negative refCount
121                     o.initRefCount--;
122                     if( 0 == o.initRefCount ) {
123                         final boolean ok = EGL.eglTerminate(eglDisplay);
124                         if( DEBUG ) {
125                             System.err.println("EGLDisplayUtil.EGL.eglTerminate 0x"+Long.toHexString(eglDisplay)+" -> "+ok);
126                             System.err.println("EGLDisplayUtil.EGL.eglTerminate "+o);
127                         }
128                         res[0] = ok;
129                         if( o == singletonEGLDisplay ) {
130                             singletonEGLDisplay = null;
131                         }
132                     }
133                 }
134                 if( 0 >= o.initRefCount ) {
135                     openEGLDisplays.remove(eglDisplay);
136                 }
137             }
138             return o;
139         }
140 
EGLDisplayRef(final long eglDisplay)141         private EGLDisplayRef(final long eglDisplay) {
142             this.eglDisplay = eglDisplay;
143             this.initRefCount = 0;
144             this.createdStack = DEBUG ? new Throwable() : null;
145         }
146 
147         @Override
toString()148         public String toString() {
149             return "EGLDisplayRef[0x"+Long.toHexString(eglDisplay)+": refCnt "+initRefCount+"]";
150         }
151     }
152     private static final LongObjectHashMap openEGLDisplays;
153 
154     static {
155         openEGLDisplays = new LongObjectHashMap();
156         openEGLDisplays.setKeyNotFoundValue(null);
157     }
158 
159     /**
160      * @return number of unclosed EGL Displays.<br>
161      */
shutdown(final boolean verbose)162     public static int shutdown(final boolean verbose) {
163         if(DEBUG || verbose || openEGLDisplays.size() > 0 ) {
164             System.err.println("EGLDisplayUtil.EGLDisplays: Shutdown (open: "+openEGLDisplays.size()+")");
165             if(DEBUG) {
166                 ExceptionUtils.dumpStack(System.err);
167             }
168             if( openEGLDisplays.size() > 0) {
169                 dumpOpenDisplayConnections();
170             }
171         }
172         return openEGLDisplays.size();
173     }
174 
dumpOpenDisplayConnections()175     public static void dumpOpenDisplayConnections() {
176         System.err.println("EGLDisplayUtil: Open EGL Display Connections: "+openEGLDisplays.size());
177         int i=0;
178         for(final Iterator<LongObjectHashMap.Entry> iter = openEGLDisplays.iterator(); iter.hasNext(); i++) {
179             final LongObjectHashMap.Entry e = iter.next();
180             final EGLDisplayRef v = (EGLDisplayRef) e.value;
181             System.err.println("EGLDisplayUtil: Open["+i+"]: 0x"+Long.toHexString(e.key)+": "+v);
182             if(null != v.createdStack) {
183                 v.createdStack.printStackTrace();
184             }
185         }
186     }
187 
setSingletonEGLDisplayOnly(final boolean v)188     /* pp */ static synchronized void setSingletonEGLDisplayOnly(final boolean v) { useSingletonEGLDisplay = v; }
189 
eglGetDisplay(final long nativeDisplay_id)190     private static synchronized long eglGetDisplay(final long nativeDisplay_id)  {
191         if( useSingletonEGLDisplay && null != singletonEGLDisplay ) {
192             if(DEBUG) {
193                 System.err.println("EGLDisplayUtil.eglGetDisplay.s: eglDisplay("+EGLContext.toHexString(nativeDisplay_id)+"): "+
194                                    EGLContext.toHexString(singletonEGLDisplay.eglDisplay)+
195                                    ", "+((EGL.EGL_NO_DISPLAY != singletonEGLDisplay.eglDisplay)?"OK":"Failed")+", singletonEGLDisplay "+singletonEGLDisplay+" (use "+useSingletonEGLDisplay+")");
196             }
197             return singletonEGLDisplay.eglDisplay;
198         }
199         final long eglDisplay = EGL.eglGetDisplay(nativeDisplay_id);
200         if(DEBUG) {
201             System.err.println("EGLDisplayUtil.eglGetDisplay.X: eglDisplay("+EGLContext.toHexString(nativeDisplay_id)+"): "+
202                                EGLContext.toHexString(eglDisplay)+
203                                ", "+((EGL.EGL_NO_DISPLAY != eglDisplay)?"OK":"Failed")+", singletonEGLDisplay "+singletonEGLDisplay+" (use "+useSingletonEGLDisplay+")");
204         }
205         return eglDisplay;
206     }
207 
208     /**
209      * @param eglDisplay
210      * @param major
211      * @param minor
212      * @return true if the eglDisplay is valid and it's reference counter becomes one and {@link EGL#eglInitialize(long, IntBuffer, IntBuffer)} was successful, otherwise false
213      *
214      * @see EGL#eglInitialize(long, IntBuffer, IntBuffer)
215      */
eglInitialize(final long eglDisplay, final int[] major, final int[] minor)216     private static synchronized boolean eglInitialize(final long eglDisplay, final int[] major, final int[] minor)  {
217         if( EGL.EGL_NO_DISPLAY == eglDisplay) {
218             return false;
219         }
220         final EGLDisplayRef d = EGLDisplayRef.getOrCreateOpened(eglDisplay, _eglMajorVersion, _eglMinorVersion);
221         final int _major = _eglMajorVersion.get(0);
222         final int _minor = _eglMinorVersion.get(0);
223         if( null != major && null != minor ) {
224             if( null != d ) {
225                 major[0] = _major;
226                 minor[0] = _minor;
227             } else {
228                 major[0] = 0;
229                 minor[0] = 0;
230             }
231         }
232         if(DEBUG) {
233             System.err.println("EGLDisplayUtil.eglInitialize("+EGLContext.toHexString(eglDisplay)+" ...): "+d+" = "+(null != d)+", eglVersion "+_major+"."+_minor+", singletonEGLDisplay "+singletonEGLDisplay+" (use "+useSingletonEGLDisplay+")");
234             // Thread.dumpStack();
235         }
236         return null != d;
237     }
238     private static final IntBuffer _eglMajorVersion = Buffers.newDirectIntBuffer(1);
239     private static final IntBuffer _eglMinorVersion = Buffers.newDirectIntBuffer(1);
240 
241     /**
242      * @param nativeDisplayID
243      * @param eglDisplay array of size 1 holding return value if successful, otherwise {@link EGL#EGL_NO_DISPLAY}.
244      * @param eglErr array of size 1 holding the EGL error value as retrieved by {@link EGL#eglGetError()} if not successful.
245      * @param major
246      * @param minor
247      * @return {@link EGL#EGL_SUCCESS} if successful, otherwise {@link EGL#EGL_BAD_DISPLAY} if {@link #eglGetDisplay(long)} failed
248      *         or {@link EGL#EGL_NOT_INITIALIZED} if {@link #eglInitialize(long, IntBuffer, IntBuffer)} failed.
249      *
250      * @see #eglGetDisplay(long)
251      * @see #eglInitialize(long, IntBuffer, IntBuffer)
252      */
eglGetDisplayAndInitialize(final long nativeDisplayID, final long[] eglDisplay, final int[] eglErr, final int[] major, final int[] minor)253     private static synchronized int eglGetDisplayAndInitialize(final long nativeDisplayID, final long[] eglDisplay, final int[] eglErr, final int[] major, final int[] minor) {
254         eglDisplay[0] = EGL.EGL_NO_DISPLAY;
255         final long _eglDisplay = eglGetDisplay( nativeDisplayID );
256         if ( EGL.EGL_NO_DISPLAY == _eglDisplay ) {
257             eglErr[0] = EGL.eglGetError();
258             return EGL.EGL_BAD_DISPLAY;
259         }
260         if ( !eglInitialize( _eglDisplay, major, minor) ) {
261             eglErr[0] = EGL.eglGetError();
262             return EGL.EGL_NOT_INITIALIZED;
263         }
264         eglDisplay[0] = _eglDisplay;
265         return EGL.EGL_SUCCESS;
266     }
267 
268     /**
269      * Attempts to {@link #eglGetDisplayAndInitialize(long, long[], int[], IntBuffer, IntBuffer)} with given <code>nativeDisplayID</code>.
270      * If this fails, method retries with <code>nativeDisplayID</code> {@link EGL#EGL_DEFAULT_DISPLAY} - the fallback mechanism.
271      * The actual used <code>nativeDisplayID</code> is returned in it's in/out array.
272      *
273      * @throws GLException if {@link EGL#eglGetDisplay(long)} or {@link EGL#eglInitialize(long, int[], int, int[], int)} fails incl fallback
274      * @param nativeDisplayID in/out array of size 1, passing the requested nativeVisualID, may return a different revised nativeVisualID handle
275      * @param major
276      * @param minor
277      * @return the initialized EGL display ID
278      * @throws GLException if not successful
279      */
eglGetDisplayAndInitialize(final long[] nativeDisplayID, final int[] major, final int[] minor)280     private static synchronized long eglGetDisplayAndInitialize(final long[] nativeDisplayID, final int[] major, final int[] minor) {
281         final long[] eglDisplay = new long[1];
282         final int[] eglError = new int[1];
283         int eglRes = EGLDisplayUtil.eglGetDisplayAndInitialize(nativeDisplayID[0], eglDisplay, eglError, major, minor);
284         if( EGL.EGL_SUCCESS == eglRes ) {
285             return eglDisplay[0];
286         }
287         if( EGL.EGL_DEFAULT_DISPLAY != nativeDisplayID[0] ) { // fallback to DEGAULT_DISPLAY
288             if(DEBUG) {
289                 System.err.println("EGLDisplayUtil.eglGetAndInitDisplay failed with native "+EGLContext.toHexString(nativeDisplayID[0])+", error "+EGLContext.toHexString(eglRes)+"/"+EGLContext.toHexString(eglError[0])+" - fallback!");
290             }
291             eglRes = EGLDisplayUtil.eglGetDisplayAndInitialize(EGL.EGL_DEFAULT_DISPLAY, eglDisplay, eglError, major, minor);
292             if( EGL.EGL_SUCCESS == eglRes ) {
293                 nativeDisplayID[0] = EGL.EGL_DEFAULT_DISPLAY;
294                 return eglDisplay[0];
295             }
296         }
297         throw new GLException("Failed to created/initialize EGL display incl. fallback default: native "+EGLContext.toHexString(nativeDisplayID[0])+", error "+EGLContext.toHexString(eglRes)+"/"+EGLContext.toHexString(eglError[0]));
298     }
299 
300     /**
301      * @param eglDisplay the EGL display handle
302      * @return true if the eglDisplay is valid and it's reference counter becomes zero and {@link EGL#eglTerminate(long)} was successful, otherwise false
303      */
eglTerminate(final long eglDisplay)304     private static synchronized boolean eglTerminate(final long eglDisplay)  {
305         if( EGL.EGL_NO_DISPLAY == eglDisplay) {
306             return false;
307         }
308         final boolean[] res = new boolean[1];
309         final EGLDisplayRef d = EGLDisplayRef.closeOpened(eglDisplay, res);
310         if(DEBUG) {
311             System.err.println("EGLDisplayUtil.eglTerminate.X("+EGLContext.toHexString(eglDisplay)+" ...): "+d+" = "+res[0]+", singletonEGLDisplay "+singletonEGLDisplay+" (use "+useSingletonEGLDisplay+")");
312             // Thread.dumpStack();
313         }
314         return res[0];
315     }
316 
317     private static final EGLGraphicsDevice.EGLDisplayLifecycleCallback eglLifecycleCallback = new EGLGraphicsDevice.EGLDisplayLifecycleCallback() {
318         @Override
319         public long eglGetAndInitDisplay(final long[] nativeDisplayID, final int[] major, final int[] minor) {
320             return eglGetDisplayAndInitialize(nativeDisplayID, major, minor);
321         }
322         @Override
323         public void eglTerminate(final long eglDisplayHandle) {
324             EGLDisplayUtil.eglTerminate(eglDisplayHandle);
325         }
326     };
327 
328     /**
329      * Returns an uninitialized {@link EGLGraphicsDevice}. User needs to issue {@link EGLGraphicsDevice#open()} before usage.
330      * <p>
331      * Using {@link #eglGetDisplayAndInitialize(long[])} for the {@link EGLGraphicsDevice#open()} implementation
332      * and {@link #eglTerminate(long)} for {@link EGLGraphicsDevice#close()}.
333      * </p>
334      * <p>
335      * Using the default {@link ToolkitLock}, via {@link NativeWindowFactory#getDefaultToolkitLock(String, long)}.
336      * </p>
337      * @param nativeDisplayID
338      * @param connection
339      * @param unitID
340      * @return an uninitialized {@link EGLGraphicsDevice}
341      */
eglCreateEGLGraphicsDevice(final long nativeDisplayID, final String connection, final int unitID)342     public static EGLGraphicsDevice eglCreateEGLGraphicsDevice(final long nativeDisplayID, final String connection, final int unitID)  {
343         return new EGLGraphicsDevice(nativeDisplayID, EGL.EGL_NO_DISPLAY, connection, unitID, eglLifecycleCallback);
344     }
345 
346     /**
347      * Returns an uninitialized {@link EGLGraphicsDevice}. User needs to issue {@link EGLGraphicsDevice#open()} before usage.
348      * <p>
349      * Using {@link #eglGetDisplayAndInitialize(long[])} for the {@link EGLGraphicsDevice#open()} implementation
350      * and {@link #eglTerminate(long)} for {@link EGLGraphicsDevice#close()}.
351      * </p>
352      * <p>
353      * Using the default {@link ToolkitLock}, via {@link NativeWindowFactory#getDefaultToolkitLock(String, long)}.
354      * </p>
355      * @param adevice
356      * @return an uninitialized {@link EGLGraphicsDevice}
357      */
eglCreateEGLGraphicsDevice(final AbstractGraphicsDevice aDevice)358     public static EGLGraphicsDevice eglCreateEGLGraphicsDevice(final AbstractGraphicsDevice aDevice)  {
359         return new EGLGraphicsDevice(aDevice, EGL.EGL_NO_DISPLAY, eglLifecycleCallback);
360     }
361 
362     /**
363      * Returns an uninitialized {@link EGLGraphicsDevice}. User needs to issue {@link EGLGraphicsDevice#open()} before usage.
364      * <p>
365      * Using {@link #eglGetDisplayAndInitialize(long[])} for the {@link EGLGraphicsDevice#open()} implementation
366      * and {@link #eglTerminate(long)} for {@link EGLGraphicsDevice#close()}.
367      * </p>
368      * <p>
369      * Using the default {@link ToolkitLock}, via {@link NativeWindowFactory#getDefaultToolkitLock(String, long)}.
370      * </p>
371      * @param surface
372      * @return an uninitialized EGLGraphicsDevice
373      */
eglCreateEGLGraphicsDevice(final NativeSurface surface)374     public static EGLGraphicsDevice eglCreateEGLGraphicsDevice(final NativeSurface surface)  {
375         final long nativeDisplayID;
376         if( NativeWindowFactory.TYPE_WINDOWS == NativeWindowFactory.getNativeWindowType(false) ) {
377             nativeDisplayID = surface.getSurfaceHandle(); // don't even ask ..
378         } else {
379             nativeDisplayID = surface.getDisplayHandle(); // 0 == EGL.EGL_DEFAULT_DISPLAY
380         }
381         final AbstractGraphicsDevice adevice = surface.getGraphicsConfiguration().getScreen().getDevice();
382         return new EGLGraphicsDevice(nativeDisplayID, EGL.EGL_NO_DISPLAY, adevice.getConnection(), adevice.getUnitID(), eglLifecycleCallback);
383     }
384 }
385