1 /*
2  *  Copyright 2015 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 package org.webrtc;
12 
13 import android.graphics.Canvas;
14 import android.graphics.Rect;
15 import android.graphics.SurfaceTexture;
16 import androidx.annotation.Nullable;
17 import android.view.Surface;
18 import android.view.SurfaceHolder;
19 import javax.microedition.khronos.egl.EGL10;
20 import javax.microedition.khronos.egl.EGLConfig;
21 import javax.microedition.khronos.egl.EGLContext;
22 import javax.microedition.khronos.egl.EGLDisplay;
23 import javax.microedition.khronos.egl.EGLSurface;
24 
25 /**
26  * Holds EGL state and utility methods for handling an egl 1.0 EGLContext, an EGLDisplay,
27  * and an EGLSurface.
28  */
29 class EglBase10Impl implements EglBase10 {
30   private static final String TAG = "EglBase10Impl";
31   // This constant is taken from EGL14.EGL_CONTEXT_CLIENT_VERSION.
32   private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
33 
34   private final EGL10 egl;
35   private EGLContext eglContext;
36   @Nullable private EGLConfig eglConfig;
37   private EGLDisplay eglDisplay;
38   private EGLSurface eglSurface = EGL10.EGL_NO_SURFACE;
39 
40   // EGL wrapper for an actual EGLContext.
41   private static class Context implements EglBase10.Context {
42     private final EGLContext eglContext;
43 
44     @Override
getRawContext()45     public EGLContext getRawContext() {
46       return eglContext;
47     }
48 
49     @Override
getNativeEglContext()50     public long getNativeEglContext() {
51       // TODO(magjed): Implement. There is no easy way of getting the native context for EGL 1.0. We
52       // need to make sure to have an EglSurface, then make the context current using that surface,
53       // and then call into JNI and call the native version of eglGetCurrentContext. Then we need to
54       // restore the state and return the native context.
55       return 0 /* EGL_NO_CONTEXT */;
56     }
57 
Context(EGLContext eglContext)58     public Context(EGLContext eglContext) {
59       this.eglContext = eglContext;
60     }
61   }
62 
63   // Create a new context with the specified config type, sharing data with sharedContext.
EglBase10Impl(EGLContext sharedContext, int[] configAttributes)64   public EglBase10Impl(EGLContext sharedContext, int[] configAttributes) {
65     this.egl = (EGL10) EGLContext.getEGL();
66     eglDisplay = getEglDisplay();
67     eglConfig = getEglConfig(eglDisplay, configAttributes);
68     final int openGlesVersion = EglBase.getOpenGlesVersionFromConfig(configAttributes);
69     Logging.d(TAG, "Using OpenGL ES version " + openGlesVersion);
70     eglContext = createEglContext(sharedContext, eglDisplay, eglConfig, openGlesVersion);
71   }
72 
73   @Override
createSurface(Surface surface)74   public void createSurface(Surface surface) {
75     /**
76      * We have to wrap Surface in a SurfaceHolder because for some reason eglCreateWindowSurface
77      * couldn't actually take a Surface object until API 17. Older versions fortunately just call
78      * SurfaceHolder.getSurface(), so we'll do that. No other methods are relevant.
79      */
80     class FakeSurfaceHolder implements SurfaceHolder {
81       private final Surface surface;
82 
83       FakeSurfaceHolder(Surface surface) {
84         this.surface = surface;
85       }
86 
87       @Override
88       public void addCallback(Callback callback) {}
89 
90       @Override
91       public void removeCallback(Callback callback) {}
92 
93       @Override
94       public boolean isCreating() {
95         return false;
96       }
97 
98       @Deprecated
99       @Override
100       public void setType(int i) {}
101 
102       @Override
103       public void setFixedSize(int i, int i2) {}
104 
105       @Override
106       public void setSizeFromLayout() {}
107 
108       @Override
109       public void setFormat(int i) {}
110 
111       @Override
112       public void setKeepScreenOn(boolean b) {}
113 
114       @Nullable
115       @Override
116       public Canvas lockCanvas() {
117         return null;
118       }
119 
120       @Nullable
121       @Override
122       public Canvas lockCanvas(Rect rect) {
123         return null;
124       }
125 
126       @Override
127       public void unlockCanvasAndPost(Canvas canvas) {}
128 
129       @Nullable
130       @Override
131       public Rect getSurfaceFrame() {
132         return null;
133       }
134 
135       @Override
136       public Surface getSurface() {
137         return surface;
138       }
139     }
140 
141     createSurfaceInternal(new FakeSurfaceHolder(surface));
142   }
143 
144   // Create EGLSurface from the Android SurfaceTexture.
145   @Override
createSurface(SurfaceTexture surfaceTexture)146   public void createSurface(SurfaceTexture surfaceTexture) {
147     createSurfaceInternal(surfaceTexture);
148   }
149 
150   // Create EGLSurface from either a SurfaceHolder or a SurfaceTexture.
createSurfaceInternal(Object nativeWindow)151   private void createSurfaceInternal(Object nativeWindow) {
152     if (!(nativeWindow instanceof SurfaceHolder) && !(nativeWindow instanceof SurfaceTexture)) {
153       throw new IllegalStateException("Input must be either a SurfaceHolder or SurfaceTexture");
154     }
155     checkIsNotReleased();
156     if (eglSurface != EGL10.EGL_NO_SURFACE) {
157       throw new RuntimeException("Already has an EGLSurface");
158     }
159     int[] surfaceAttribs = {EGL10.EGL_NONE};
160     eglSurface = egl.eglCreateWindowSurface(eglDisplay, eglConfig, nativeWindow, surfaceAttribs);
161     if (eglSurface == EGL10.EGL_NO_SURFACE) {
162       throw new RuntimeException(
163           "Failed to create window surface: 0x" + Integer.toHexString(egl.eglGetError()));
164     }
165   }
166 
167   // Create dummy 1x1 pixel buffer surface so the context can be made current.
168   @Override
createDummyPbufferSurface()169   public void createDummyPbufferSurface() {
170     createPbufferSurface(1, 1);
171   }
172 
173   @Override
createPbufferSurface(int width, int height)174   public void createPbufferSurface(int width, int height) {
175     checkIsNotReleased();
176     if (eglSurface != EGL10.EGL_NO_SURFACE) {
177       throw new RuntimeException("Already has an EGLSurface");
178     }
179     int[] surfaceAttribs = {EGL10.EGL_WIDTH, width, EGL10.EGL_HEIGHT, height, EGL10.EGL_NONE};
180     eglSurface = egl.eglCreatePbufferSurface(eglDisplay, eglConfig, surfaceAttribs);
181     if (eglSurface == EGL10.EGL_NO_SURFACE) {
182       throw new RuntimeException("Failed to create pixel buffer surface with size " + width + "x"
183           + height + ": 0x" + Integer.toHexString(egl.eglGetError()));
184     }
185   }
186 
187   @Override
getEglBaseContext()188   public org.webrtc.EglBase.Context getEglBaseContext() {
189     return new Context(eglContext);
190   }
191 
192   @Override
hasSurface()193   public boolean hasSurface() {
194     return eglSurface != EGL10.EGL_NO_SURFACE;
195   }
196 
197   @Override
surfaceWidth()198   public int surfaceWidth() {
199     final int widthArray[] = new int[1];
200     egl.eglQuerySurface(eglDisplay, eglSurface, EGL10.EGL_WIDTH, widthArray);
201     return widthArray[0];
202   }
203 
204   @Override
surfaceHeight()205   public int surfaceHeight() {
206     final int heightArray[] = new int[1];
207     egl.eglQuerySurface(eglDisplay, eglSurface, EGL10.EGL_HEIGHT, heightArray);
208     return heightArray[0];
209   }
210 
211   @Override
releaseSurface()212   public void releaseSurface() {
213     if (eglSurface != EGL10.EGL_NO_SURFACE) {
214       egl.eglDestroySurface(eglDisplay, eglSurface);
215       eglSurface = EGL10.EGL_NO_SURFACE;
216     }
217   }
218 
checkIsNotReleased()219   private void checkIsNotReleased() {
220     if (eglDisplay == EGL10.EGL_NO_DISPLAY || eglContext == EGL10.EGL_NO_CONTEXT
221         || eglConfig == null) {
222       throw new RuntimeException("This object has been released");
223     }
224   }
225 
226   @Override
release()227   public void release() {
228     checkIsNotReleased();
229     releaseSurface();
230     detachCurrent();
231     egl.eglDestroyContext(eglDisplay, eglContext);
232     egl.eglTerminate(eglDisplay);
233     eglContext = EGL10.EGL_NO_CONTEXT;
234     eglDisplay = EGL10.EGL_NO_DISPLAY;
235     eglConfig = null;
236   }
237 
238   @Override
makeCurrent()239   public void makeCurrent() {
240     checkIsNotReleased();
241     if (eglSurface == EGL10.EGL_NO_SURFACE) {
242       throw new RuntimeException("No EGLSurface - can't make current");
243     }
244     synchronized (EglBase.lock) {
245       if (!egl.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
246         throw new RuntimeException(
247             "eglMakeCurrent failed: 0x" + Integer.toHexString(egl.eglGetError()));
248       }
249     }
250   }
251 
252   // Detach the current EGL context, so that it can be made current on another thread.
253   @Override
detachCurrent()254   public void detachCurrent() {
255     synchronized (EglBase.lock) {
256       if (!egl.eglMakeCurrent(
257               eglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT)) {
258         throw new RuntimeException(
259             "eglDetachCurrent failed: 0x" + Integer.toHexString(egl.eglGetError()));
260       }
261     }
262   }
263 
264   @Override
swapBuffers()265   public void swapBuffers() {
266     checkIsNotReleased();
267     if (eglSurface == EGL10.EGL_NO_SURFACE) {
268       throw new RuntimeException("No EGLSurface - can't swap buffers");
269     }
270     synchronized (EglBase.lock) {
271       egl.eglSwapBuffers(eglDisplay, eglSurface);
272     }
273   }
274 
275   @Override
swapBuffers(long timeStampNs)276   public void swapBuffers(long timeStampNs) {
277     // Setting presentation time is not supported for EGL 1.0.
278     swapBuffers();
279   }
280 
281   // Return an EGLDisplay, or die trying.
getEglDisplay()282   private EGLDisplay getEglDisplay() {
283     EGLDisplay eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
284     if (eglDisplay == EGL10.EGL_NO_DISPLAY) {
285       throw new RuntimeException(
286           "Unable to get EGL10 display: 0x" + Integer.toHexString(egl.eglGetError()));
287     }
288     int[] version = new int[2];
289     if (!egl.eglInitialize(eglDisplay, version)) {
290       throw new RuntimeException(
291           "Unable to initialize EGL10: 0x" + Integer.toHexString(egl.eglGetError()));
292     }
293     return eglDisplay;
294   }
295 
296   // Return an EGLConfig, or die trying.
getEglConfig(EGLDisplay eglDisplay, int[] configAttributes)297   private EGLConfig getEglConfig(EGLDisplay eglDisplay, int[] configAttributes) {
298     EGLConfig[] configs = new EGLConfig[1];
299     int[] numConfigs = new int[1];
300     if (!egl.eglChooseConfig(eglDisplay, configAttributes, configs, configs.length, numConfigs)) {
301       throw new RuntimeException(
302           "eglChooseConfig failed: 0x" + Integer.toHexString(egl.eglGetError()));
303     }
304     if (numConfigs[0] <= 0) {
305       throw new RuntimeException("Unable to find any matching EGL config");
306     }
307     final EGLConfig eglConfig = configs[0];
308     if (eglConfig == null) {
309       throw new RuntimeException("eglChooseConfig returned null");
310     }
311     return eglConfig;
312   }
313 
314   // Return an EGLConfig, or die trying.
createEglContext(@ullable EGLContext sharedContext, EGLDisplay eglDisplay, EGLConfig eglConfig, int openGlesVersion)315   private EGLContext createEglContext(@Nullable EGLContext sharedContext, EGLDisplay eglDisplay,
316       EGLConfig eglConfig, int openGlesVersion) {
317     if (sharedContext != null && sharedContext == EGL10.EGL_NO_CONTEXT) {
318       throw new RuntimeException("Invalid sharedContext");
319     }
320     int[] contextAttributes = {EGL_CONTEXT_CLIENT_VERSION, openGlesVersion, EGL10.EGL_NONE};
321     EGLContext rootContext = sharedContext == null ? EGL10.EGL_NO_CONTEXT : sharedContext;
322     final EGLContext eglContext;
323     synchronized (EglBase.lock) {
324       eglContext = egl.eglCreateContext(eglDisplay, eglConfig, rootContext, contextAttributes);
325     }
326     if (eglContext == EGL10.EGL_NO_CONTEXT) {
327       throw new RuntimeException(
328           "Failed to create EGL context: 0x" + Integer.toHexString(egl.eglGetError()));
329     }
330     return eglContext;
331   }
332 }
333