| /* | |
| * Copyright 2015 The WebRTC project authors. All Rights Reserved. | |
| * | |
| * Use of this source code is governed by a BSD-style license | |
| * that can be found in the LICENSE file in the root of the source | |
| * tree. An additional intellectual property rights grant can be found | |
| * in the file PATENTS. All contributing project authors may | |
| * be found in the AUTHORS file in the root of the source tree. | |
| */ | |
| package org.webrtc; | |
| import android.graphics.Canvas; | |
| import android.graphics.Rect; | |
| import android.graphics.SurfaceTexture; | |
| import android.opengl.GLException; | |
| import android.view.Surface; | |
| import android.view.SurfaceHolder; | |
| import androidx.annotation.Nullable; | |
| import javax.microedition.khronos.egl.EGL10; | |
| import javax.microedition.khronos.egl.EGLConfig; | |
| import javax.microedition.khronos.egl.EGLContext; | |
| import javax.microedition.khronos.egl.EGLDisplay; | |
| import javax.microedition.khronos.egl.EGLSurface; | |
| /** | |
| * Holds EGL state and utility methods for handling an egl 1.0 EGLContext, an EGLDisplay, | |
| * and an EGLSurface. | |
| */ | |
| class EglBase10Impl implements EglBase10 { | |
| private static final String TAG = "EglBase10Impl"; | |
| // This constant is taken from EGL14.EGL_CONTEXT_CLIENT_VERSION. | |
| private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; | |
| private static final EglConnection EGL_NO_CONNECTION = new EglConnection(); | |
| private EGLSurface eglSurface = EGL10.EGL_NO_SURFACE; | |
| private EglConnection eglConnection; | |
| // EGL wrapper for an actual EGLContext. | |
| private static class Context implements EglBase10.Context { | |
| private final EGL10 egl; | |
| private final EGLContext eglContext; | |
| private final EGLConfig eglContextConfig; | |
| @Override | |
| public EGLContext getRawContext() { | |
| return eglContext; | |
| } | |
| @Override | |
| public long getNativeEglContext() { | |
| EGLContext previousContext = egl.eglGetCurrentContext(); | |
| EGLDisplay currentDisplay = egl.eglGetCurrentDisplay(); | |
| EGLSurface previousDrawSurface = egl.eglGetCurrentSurface(EGL10.EGL_DRAW); | |
| EGLSurface previousReadSurface = egl.eglGetCurrentSurface(EGL10.EGL_READ); | |
| EGLSurface tempEglSurface = null; | |
| if (currentDisplay == EGL10.EGL_NO_DISPLAY) { | |
| currentDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); | |
| } | |
| try { | |
| if (previousContext != eglContext) { | |
| int[] surfaceAttribs = {EGL10.EGL_WIDTH, 1, EGL10.EGL_HEIGHT, 1, EGL10.EGL_NONE}; | |
| tempEglSurface = | |
| egl.eglCreatePbufferSurface(currentDisplay, eglContextConfig, surfaceAttribs); | |
| if (!egl.eglMakeCurrent(currentDisplay, tempEglSurface, tempEglSurface, eglContext)) { | |
| throw new GLException(egl.eglGetError(), | |
| "Failed to make temporary EGL surface active: " + egl.eglGetError()); | |
| } | |
| } | |
| return nativeGetCurrentNativeEGLContext(); | |
| } finally { | |
| if (tempEglSurface != null) { | |
| egl.eglMakeCurrent( | |
| currentDisplay, previousDrawSurface, previousReadSurface, previousContext); | |
| egl.eglDestroySurface(currentDisplay, tempEglSurface); | |
| } | |
| } | |
| } | |
| public Context(EGL10 egl, EGLContext eglContext, EGLConfig eglContextConfig) { | |
| this.egl = egl; | |
| this.eglContext = eglContext; | |
| this.eglContextConfig = eglContextConfig; | |
| } | |
| } | |
| public static class EglConnection implements EglBase10.EglConnection { | |
| private final EGL10 egl; | |
| private final EGLContext eglContext; | |
| private final EGLDisplay eglDisplay; | |
| private final EGLConfig eglConfig; | |
| private final RefCountDelegate refCountDelegate; | |
| private EGLSurface currentSurface = EGL10.EGL_NO_SURFACE; | |
| public EglConnection(EGLContext sharedContext, int[] configAttributes) { | |
| egl = (EGL10) EGLContext.getEGL(); | |
| eglDisplay = getEglDisplay(egl); | |
| eglConfig = getEglConfig(egl, eglDisplay, configAttributes); | |
| final int openGlesVersion = EglBase.getOpenGlesVersionFromConfig(configAttributes); | |
| Logging.d(TAG, "Using OpenGL ES version " + openGlesVersion); | |
| eglContext = createEglContext(egl, sharedContext, eglDisplay, eglConfig, openGlesVersion); | |
| // Ref count delegate with release callback. | |
| refCountDelegate = new RefCountDelegate(() -> { | |
| synchronized (EglBase.lock) { | |
| egl.eglMakeCurrent( | |
| eglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); | |
| } | |
| egl.eglDestroyContext(eglDisplay, eglContext); | |
| egl.eglTerminate(eglDisplay); | |
| currentSurface = EGL10.EGL_NO_SURFACE; | |
| }); | |
| } | |
| // Returns a "null" EglConnection. Useful to represent a released instance with default values. | |
| private EglConnection() { | |
| egl = (EGL10) EGLContext.getEGL(); | |
| eglContext = EGL10.EGL_NO_CONTEXT; | |
| eglDisplay = EGL10.EGL_NO_DISPLAY; | |
| eglConfig = null; | |
| refCountDelegate = new RefCountDelegate(() -> {}); | |
| } | |
| @Override | |
| public void retain() { | |
| refCountDelegate.retain(); | |
| } | |
| @Override | |
| public void release() { | |
| refCountDelegate.release(); | |
| } | |
| @Override | |
| public EGL10 getEgl() { | |
| return egl; | |
| } | |
| @Override | |
| public EGLContext getContext() { | |
| return eglContext; | |
| } | |
| @Override | |
| public EGLDisplay getDisplay() { | |
| return eglDisplay; | |
| } | |
| @Override | |
| public EGLConfig getConfig() { | |
| return eglConfig; | |
| } | |
| public void makeCurrent(EGLSurface eglSurface) { | |
| if (egl.eglGetCurrentContext() == eglContext && currentSurface == eglSurface) { | |
| return; | |
| } | |
| synchronized (EglBase.lock) { | |
| if (!egl.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) { | |
| throw new GLException(egl.eglGetError(), | |
| "eglMakeCurrent failed: 0x" + Integer.toHexString(egl.eglGetError())); | |
| } | |
| } | |
| currentSurface = eglSurface; | |
| } | |
| public void detachCurrent() { | |
| synchronized (EglBase.lock) { | |
| if (!egl.eglMakeCurrent( | |
| eglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT)) { | |
| throw new GLException(egl.eglGetError(), | |
| "eglDetachCurrent failed: 0x" + Integer.toHexString(egl.eglGetError())); | |
| } | |
| } | |
| currentSurface = EGL10.EGL_NO_SURFACE; | |
| } | |
| } | |
| // Create a new context with the specified config type, sharing data with sharedContext. | |
| public EglBase10Impl(EGLContext sharedContext, int[] configAttributes) { | |
| this.eglConnection = new EglConnection(sharedContext, configAttributes); | |
| } | |
| public EglBase10Impl(EglConnection eglConnection) { | |
| this.eglConnection = eglConnection; | |
| this.eglConnection.retain(); | |
| } | |
| @Override | |
| public void createSurface(Surface surface) { | |
| // We have to wrap Surface in a SurfaceHolder because for some reason eglCreateWindowSurface | |
| // couldn't actually take a Surface object until API 17. Older versions fortunately just call | |
| // SurfaceHolder.getSurface(), so we'll do that. No other methods are relevant. | |
| class FakeSurfaceHolder implements SurfaceHolder { | |
| private final Surface surface; | |
| FakeSurfaceHolder(Surface surface) { | |
| this.surface = surface; | |
| } | |
| @Override | |
| public void addCallback(Callback callback) {} | |
| @Override | |
| public void removeCallback(Callback callback) {} | |
| @Override | |
| public boolean isCreating() { | |
| return false; | |
| } | |
| @Deprecated | |
| @Override | |
| public void setType(int i) {} | |
| @Override | |
| public void setFixedSize(int i, int i2) {} | |
| @Override | |
| public void setSizeFromLayout() {} | |
| @Override | |
| public void setFormat(int i) {} | |
| @Override | |
| public void setKeepScreenOn(boolean b) {} | |
| @Nullable | |
| @Override | |
| public Canvas lockCanvas() { | |
| return null; | |
| } | |
| @Nullable | |
| @Override | |
| public Canvas lockCanvas(Rect rect) { | |
| return null; | |
| } | |
| @Override | |
| public void unlockCanvasAndPost(Canvas canvas) {} | |
| @Nullable | |
| @Override | |
| public Rect getSurfaceFrame() { | |
| return null; | |
| } | |
| @Override | |
| public Surface getSurface() { | |
| return surface; | |
| } | |
| } | |
| createSurfaceInternal(new FakeSurfaceHolder(surface)); | |
| } | |
| // Create EGLSurface from the Android SurfaceTexture. | |
| @Override | |
| public void createSurface(SurfaceTexture surfaceTexture) { | |
| createSurfaceInternal(surfaceTexture); | |
| } | |
| // Create EGLSurface from either a SurfaceHolder or a SurfaceTexture. | |
| private void createSurfaceInternal(Object nativeWindow) { | |
| if (!(nativeWindow instanceof SurfaceHolder) && !(nativeWindow instanceof SurfaceTexture)) { | |
| throw new IllegalStateException("Input must be either a SurfaceHolder or SurfaceTexture"); | |
| } | |
| checkIsNotReleased(); | |
| if (eglSurface != EGL10.EGL_NO_SURFACE) { | |
| throw new RuntimeException("Already has an EGLSurface"); | |
| } | |
| EGL10 egl = eglConnection.getEgl(); | |
| int[] surfaceAttribs = {EGL10.EGL_NONE}; | |
| eglSurface = egl.eglCreateWindowSurface( | |
| eglConnection.getDisplay(), eglConnection.getConfig(), nativeWindow, surfaceAttribs); | |
| if (eglSurface == EGL10.EGL_NO_SURFACE) { | |
| throw new GLException(egl.eglGetError(), | |
| "Failed to create window surface: 0x" + Integer.toHexString(egl.eglGetError())); | |
| } | |
| } | |
| // Create dummy 1x1 pixel buffer surface so the context can be made current. | |
| @Override | |
| public void createDummyPbufferSurface() { | |
| createPbufferSurface(1, 1); | |
| } | |
| @Override | |
| public void createPbufferSurface(int width, int height) { | |
| checkIsNotReleased(); | |
| if (eglSurface != EGL10.EGL_NO_SURFACE) { | |
| throw new RuntimeException("Already has an EGLSurface"); | |
| } | |
| EGL10 egl = eglConnection.getEgl(); | |
| int[] surfaceAttribs = {EGL10.EGL_WIDTH, width, EGL10.EGL_HEIGHT, height, EGL10.EGL_NONE}; | |
| eglSurface = egl.eglCreatePbufferSurface( | |
| eglConnection.getDisplay(), eglConnection.getConfig(), surfaceAttribs); | |
| if (eglSurface == EGL10.EGL_NO_SURFACE) { | |
| throw new GLException(egl.eglGetError(), | |
| "Failed to create pixel buffer surface with size " + width + "x" + height + ": 0x" | |
| + Integer.toHexString(egl.eglGetError())); | |
| } | |
| } | |
| @Override | |
| public org.webrtc.EglBase.Context getEglBaseContext() { | |
| return new Context( | |
| eglConnection.getEgl(), eglConnection.getContext(), eglConnection.getConfig()); | |
| } | |
| @Override | |
| public boolean hasSurface() { | |
| return eglSurface != EGL10.EGL_NO_SURFACE; | |
| } | |
| @Override | |
| public int surfaceWidth() { | |
| final int widthArray[] = new int[1]; | |
| eglConnection.getEgl().eglQuerySurface( | |
| eglConnection.getDisplay(), eglSurface, EGL10.EGL_WIDTH, widthArray); | |
| return widthArray[0]; | |
| } | |
| @Override | |
| public int surfaceHeight() { | |
| final int heightArray[] = new int[1]; | |
| eglConnection.getEgl().eglQuerySurface( | |
| eglConnection.getDisplay(), eglSurface, EGL10.EGL_HEIGHT, heightArray); | |
| return heightArray[0]; | |
| } | |
| @Override | |
| public void releaseSurface() { | |
| if (eglSurface != EGL10.EGL_NO_SURFACE) { | |
| eglConnection.getEgl().eglDestroySurface(eglConnection.getDisplay(), eglSurface); | |
| eglSurface = EGL10.EGL_NO_SURFACE; | |
| } | |
| } | |
| private void checkIsNotReleased() { | |
| if (eglConnection == EGL_NO_CONNECTION) { | |
| throw new RuntimeException("This object has been released"); | |
| } | |
| } | |
| @Override | |
| public void release() { | |
| checkIsNotReleased(); | |
| releaseSurface(); | |
| eglConnection.release(); | |
| eglConnection = EGL_NO_CONNECTION; | |
| } | |
| @Override | |
| public void makeCurrent() { | |
| checkIsNotReleased(); | |
| if (eglSurface == EGL10.EGL_NO_SURFACE) { | |
| throw new RuntimeException("No EGLSurface - can't make current"); | |
| } | |
| eglConnection.makeCurrent(eglSurface); | |
| } | |
| // Detach the current EGL context, so that it can be made current on another thread. | |
| @Override | |
| public void detachCurrent() { | |
| eglConnection.detachCurrent(); | |
| } | |
| @Override | |
| public void swapBuffers() { | |
| checkIsNotReleased(); | |
| if (eglSurface == EGL10.EGL_NO_SURFACE) { | |
| throw new RuntimeException("No EGLSurface - can't swap buffers"); | |
| } | |
| synchronized (EglBase.lock) { | |
| eglConnection.getEgl().eglSwapBuffers(eglConnection.getDisplay(), eglSurface); | |
| } | |
| } | |
| @Override | |
| public void swapBuffers(long timeStampNs) { | |
| // Setting presentation time is not supported for EGL 1.0. | |
| swapBuffers(); | |
| } | |
| // Return an EGLDisplay, or die trying. | |
| private static EGLDisplay getEglDisplay(EGL10 egl) { | |
| EGLDisplay eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); | |
| if (eglDisplay == EGL10.EGL_NO_DISPLAY) { | |
| throw new GLException(egl.eglGetError(), | |
| "Unable to get EGL10 display: 0x" + Integer.toHexString(egl.eglGetError())); | |
| } | |
| int[] version = new int[2]; | |
| if (!egl.eglInitialize(eglDisplay, version)) { | |
| throw new GLException(egl.eglGetError(), | |
| "Unable to initialize EGL10: 0x" + Integer.toHexString(egl.eglGetError())); | |
| } | |
| return eglDisplay; | |
| } | |
| // Return an EGLConfig, or die trying. | |
| private static EGLConfig getEglConfig(EGL10 egl, EGLDisplay eglDisplay, int[] configAttributes) { | |
| EGLConfig[] configs = new EGLConfig[1]; | |
| int[] numConfigs = new int[1]; | |
| if (!egl.eglChooseConfig(eglDisplay, configAttributes, configs, configs.length, numConfigs)) { | |
| throw new GLException( | |
| egl.eglGetError(), "eglChooseConfig failed: 0x" + Integer.toHexString(egl.eglGetError())); | |
| } | |
| if (numConfigs[0] <= 0) { | |
| throw new RuntimeException("Unable to find any matching EGL config"); | |
| } | |
| final EGLConfig eglConfig = configs[0]; | |
| if (eglConfig == null) { | |
| throw new RuntimeException("eglChooseConfig returned null"); | |
| } | |
| return eglConfig; | |
| } | |
| // Return an EGLConfig, or die trying. | |
| private static EGLContext createEglContext(EGL10 egl, @Nullable EGLContext sharedContext, | |
| EGLDisplay eglDisplay, EGLConfig eglConfig, int openGlesVersion) { | |
| if (sharedContext != null && sharedContext == EGL10.EGL_NO_CONTEXT) { | |
| throw new RuntimeException("Invalid sharedContext"); | |
| } | |
| int[] contextAttributes = {EGL_CONTEXT_CLIENT_VERSION, openGlesVersion, EGL10.EGL_NONE}; | |
| EGLContext rootContext = sharedContext == null ? EGL10.EGL_NO_CONTEXT : sharedContext; | |
| final EGLContext eglContext; | |
| synchronized (EglBase.lock) { | |
| eglContext = egl.eglCreateContext(eglDisplay, eglConfig, rootContext, contextAttributes); | |
| } | |
| if (eglContext == EGL10.EGL_NO_CONTEXT) { | |
| throw new GLException(egl.eglGetError(), | |
| "Failed to create EGL context: 0x" + Integer.toHexString(egl.eglGetError())); | |
| } | |
| return eglContext; | |
| } | |
| private static native long nativeGetCurrentNativeEGLContext(); | |
| } |