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.annotation.TargetApi;
14 import android.graphics.SurfaceTexture;
15 import android.opengl.EGL14;
16 import android.opengl.EGLConfig;
17 import android.opengl.EGLContext;
18 import android.opengl.EGLDisplay;
19 import android.opengl.EGLExt;
20 import android.opengl.EGLSurface;
21 import android.os.Build;
22 import androidx.annotation.Nullable;
23 import android.view.Surface;
24 
25 /**
26  * Holds EGL state and utility methods for handling an EGL14 EGLContext, an EGLDisplay,
27  * and an EGLSurface.
28  */
29 @SuppressWarnings("ReferenceEquality") // We want to compare to EGL14 constants.
30 @TargetApi(18)
31 class EglBase14Impl implements EglBase14 {
32   private static final String TAG = "EglBase14Impl";
33   private static final int EGLExt_SDK_VERSION = Build.VERSION_CODES.JELLY_BEAN_MR2;
34   private static final int CURRENT_SDK_VERSION = Build.VERSION.SDK_INT;
35   private EGLContext eglContext;
36   @Nullable private EGLConfig eglConfig;
37   private EGLDisplay eglDisplay;
38   private EGLSurface eglSurface = EGL14.EGL_NO_SURFACE;
39 
40   // EGL 1.4 is supported from API 17. But EGLExt that is used for setting presentation
41   // time stamp on a surface is supported from 18 so we require 18.
isEGL14Supported()42   public static boolean isEGL14Supported() {
43     Logging.d(TAG,
44         "SDK version: " + CURRENT_SDK_VERSION
45             + ". isEGL14Supported: " + (CURRENT_SDK_VERSION >= EGLExt_SDK_VERSION));
46     return (CURRENT_SDK_VERSION >= EGLExt_SDK_VERSION);
47   }
48 
49   public static class Context implements EglBase14.Context {
50     private final EGLContext egl14Context;
51 
52     @Override
getRawContext()53     public EGLContext getRawContext() {
54       return egl14Context;
55     }
56 
57     @Override
58     @SuppressWarnings("deprecation")
59     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
getNativeEglContext()60     public long getNativeEglContext() {
61       return CURRENT_SDK_VERSION >= Build.VERSION_CODES.LOLLIPOP ? egl14Context.getNativeHandle()
62                                                                  : egl14Context.getHandle();
63     }
64 
Context(android.opengl.EGLContext eglContext)65     public Context(android.opengl.EGLContext eglContext) {
66       this.egl14Context = eglContext;
67     }
68   }
69 
70   // Create a new context with the specified config type, sharing data with sharedContext.
71   // |sharedContext| may be null.
EglBase14Impl(EGLContext sharedContext, int[] configAttributes)72   public EglBase14Impl(EGLContext sharedContext, int[] configAttributes) {
73     eglDisplay = getEglDisplay();
74     eglConfig = getEglConfig(eglDisplay, configAttributes);
75     final int openGlesVersion = EglBase.getOpenGlesVersionFromConfig(configAttributes);
76     Logging.d(TAG, "Using OpenGL ES version " + openGlesVersion);
77     eglContext = createEglContext(sharedContext, eglDisplay, eglConfig, openGlesVersion);
78   }
79 
80   // Create EGLSurface from the Android Surface.
81   @Override
createSurface(Surface surface)82   public void createSurface(Surface surface) {
83     createSurfaceInternal(surface);
84   }
85 
86   // Create EGLSurface from the Android SurfaceTexture.
87   @Override
createSurface(SurfaceTexture surfaceTexture)88   public void createSurface(SurfaceTexture surfaceTexture) {
89     createSurfaceInternal(surfaceTexture);
90   }
91 
92   // Create EGLSurface from either Surface or SurfaceTexture.
createSurfaceInternal(Object surface)93   private void createSurfaceInternal(Object surface) {
94     if (!(surface instanceof Surface) && !(surface instanceof SurfaceTexture)) {
95       throw new IllegalStateException("Input must be either a Surface or SurfaceTexture");
96     }
97     checkIsNotReleased();
98     if (eglSurface != EGL14.EGL_NO_SURFACE) {
99       throw new RuntimeException("Already has an EGLSurface");
100     }
101     int[] surfaceAttribs = {EGL14.EGL_NONE};
102     eglSurface = EGL14.eglCreateWindowSurface(eglDisplay, eglConfig, surface, surfaceAttribs, 0);
103     if (eglSurface == EGL14.EGL_NO_SURFACE) {
104       throw new RuntimeException(
105           "Failed to create window surface: 0x" + Integer.toHexString(EGL14.eglGetError()));
106     }
107   }
108 
109   @Override
createDummyPbufferSurface()110   public void createDummyPbufferSurface() {
111     createPbufferSurface(1, 1);
112   }
113 
114   @Override
createPbufferSurface(int width, int height)115   public void createPbufferSurface(int width, int height) {
116     checkIsNotReleased();
117     if (eglSurface != EGL14.EGL_NO_SURFACE) {
118       throw new RuntimeException("Already has an EGLSurface");
119     }
120     int[] surfaceAttribs = {EGL14.EGL_WIDTH, width, EGL14.EGL_HEIGHT, height, EGL14.EGL_NONE};
121     eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, eglConfig, surfaceAttribs, 0);
122     if (eglSurface == EGL14.EGL_NO_SURFACE) {
123       throw new RuntimeException("Failed to create pixel buffer surface with size " + width + "x"
124           + height + ": 0x" + Integer.toHexString(EGL14.eglGetError()));
125     }
126   }
127 
128   @Override
getEglBaseContext()129   public Context getEglBaseContext() {
130     return new Context(eglContext);
131   }
132 
133   @Override
hasSurface()134   public boolean hasSurface() {
135     return eglSurface != EGL14.EGL_NO_SURFACE;
136   }
137 
138   @Override
surfaceWidth()139   public int surfaceWidth() {
140     final int widthArray[] = new int[1];
141     EGL14.eglQuerySurface(eglDisplay, eglSurface, EGL14.EGL_WIDTH, widthArray, 0);
142     return widthArray[0];
143   }
144 
145   @Override
surfaceHeight()146   public int surfaceHeight() {
147     final int heightArray[] = new int[1];
148     EGL14.eglQuerySurface(eglDisplay, eglSurface, EGL14.EGL_HEIGHT, heightArray, 0);
149     return heightArray[0];
150   }
151 
152   @Override
releaseSurface()153   public void releaseSurface() {
154     if (eglSurface != EGL14.EGL_NO_SURFACE) {
155       EGL14.eglDestroySurface(eglDisplay, eglSurface);
156       eglSurface = EGL14.EGL_NO_SURFACE;
157     }
158   }
159 
checkIsNotReleased()160   private void checkIsNotReleased() {
161     if (eglDisplay == EGL14.EGL_NO_DISPLAY || eglContext == EGL14.EGL_NO_CONTEXT
162         || eglConfig == null) {
163       throw new RuntimeException("This object has been released");
164     }
165   }
166 
167   @Override
release()168   public void release() {
169     checkIsNotReleased();
170     releaseSurface();
171     detachCurrent();
172     EGL14.eglDestroyContext(eglDisplay, eglContext);
173     EGL14.eglReleaseThread();
174     EGL14.eglTerminate(eglDisplay);
175     eglContext = EGL14.EGL_NO_CONTEXT;
176     eglDisplay = EGL14.EGL_NO_DISPLAY;
177     eglConfig = null;
178   }
179 
180   @Override
makeCurrent()181   public void makeCurrent() {
182     checkIsNotReleased();
183     if (eglSurface == EGL14.EGL_NO_SURFACE) {
184       throw new RuntimeException("No EGLSurface - can't make current");
185     }
186     synchronized (EglBase.lock) {
187       if (!EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
188         throw new RuntimeException(
189             "eglMakeCurrent failed: 0x" + Integer.toHexString(EGL14.eglGetError()));
190       }
191     }
192   }
193 
194   // Detach the current EGL context, so that it can be made current on another thread.
195   @Override
detachCurrent()196   public void detachCurrent() {
197     synchronized (EglBase.lock) {
198       if (!EGL14.eglMakeCurrent(
199               eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT)) {
200         throw new RuntimeException(
201             "eglDetachCurrent failed: 0x" + Integer.toHexString(EGL14.eglGetError()));
202       }
203     }
204   }
205 
206   @Override
swapBuffers()207   public void swapBuffers() {
208     checkIsNotReleased();
209     if (eglSurface == EGL14.EGL_NO_SURFACE) {
210       throw new RuntimeException("No EGLSurface - can't swap buffers");
211     }
212     synchronized (EglBase.lock) {
213       EGL14.eglSwapBuffers(eglDisplay, eglSurface);
214     }
215   }
216 
217   @Override
swapBuffers(long timeStampNs)218   public void swapBuffers(long timeStampNs) {
219     checkIsNotReleased();
220     if (eglSurface == EGL14.EGL_NO_SURFACE) {
221       throw new RuntimeException("No EGLSurface - can't swap buffers");
222     }
223     synchronized (EglBase.lock) {
224       // See
225       // https://android.googlesource.com/platform/frameworks/native/+/tools_r22.2/opengl/specs/EGL_ANDROID_presentation_time.txt
226       EGLExt.eglPresentationTimeANDROID(eglDisplay, eglSurface, timeStampNs);
227       EGL14.eglSwapBuffers(eglDisplay, eglSurface);
228     }
229   }
230 
231   // Return an EGLDisplay, or die trying.
getEglDisplay()232   private static EGLDisplay getEglDisplay() {
233     EGLDisplay eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
234     if (eglDisplay == EGL14.EGL_NO_DISPLAY) {
235       throw new RuntimeException(
236           "Unable to get EGL14 display: 0x" + Integer.toHexString(EGL14.eglGetError()));
237     }
238     int[] version = new int[2];
239     if (!EGL14.eglInitialize(eglDisplay, version, 0, version, 1)) {
240       throw new RuntimeException(
241           "Unable to initialize EGL14: 0x" + Integer.toHexString(EGL14.eglGetError()));
242     }
243     return eglDisplay;
244   }
245 
246   // Return an EGLConfig, or die trying.
getEglConfig(EGLDisplay eglDisplay, int[] configAttributes)247   private static EGLConfig getEglConfig(EGLDisplay eglDisplay, int[] configAttributes) {
248     EGLConfig[] configs = new EGLConfig[1];
249     int[] numConfigs = new int[1];
250     if (!EGL14.eglChooseConfig(
251             eglDisplay, configAttributes, 0, configs, 0, configs.length, numConfigs, 0)) {
252       throw new RuntimeException(
253           "eglChooseConfig failed: 0x" + Integer.toHexString(EGL14.eglGetError()));
254     }
255     if (numConfigs[0] <= 0) {
256       throw new RuntimeException("Unable to find any matching EGL config");
257     }
258     final EGLConfig eglConfig = configs[0];
259     if (eglConfig == null) {
260       throw new RuntimeException("eglChooseConfig returned null");
261     }
262     return eglConfig;
263   }
264 
265   // Return an EGLConfig, or die trying.
createEglContext(@ullable EGLContext sharedContext, EGLDisplay eglDisplay, EGLConfig eglConfig, int openGlesVersion)266   private static EGLContext createEglContext(@Nullable EGLContext sharedContext,
267       EGLDisplay eglDisplay, EGLConfig eglConfig, int openGlesVersion) {
268     if (sharedContext != null && sharedContext == EGL14.EGL_NO_CONTEXT) {
269       throw new RuntimeException("Invalid sharedContext");
270     }
271     int[] contextAttributes = {EGL14.EGL_CONTEXT_CLIENT_VERSION, openGlesVersion, EGL14.EGL_NONE};
272     EGLContext rootContext = sharedContext == null ? EGL14.EGL_NO_CONTEXT : sharedContext;
273     final EGLContext eglContext;
274     synchronized (EglBase.lock) {
275       eglContext = EGL14.eglCreateContext(eglDisplay, eglConfig, rootContext, contextAttributes, 0);
276     }
277     if (eglContext == EGL14.EGL_NO_CONTEXT) {
278       throw new RuntimeException(
279           "Failed to create EGL context: 0x" + Integer.toHexString(EGL14.eglGetError()));
280     }
281     return eglContext;
282   }
283 }
284