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