/* * libjingle * Copyright 2015 Google Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.webrtc; import android.graphics.SurfaceTexture; import android.view.SurfaceHolder; import org.webrtc.Logging; 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 EGLContext, an EGLDisplay, and an EGLSurface. */ public final class EglBase { private static final String TAG = "EglBase"; // These constants are taken from EGL14.EGL_OPENGL_ES2_BIT and EGL14.EGL_CONTEXT_CLIENT_VERSION. // https://android.googlesource.com/platform/frameworks/base/+/master/opengl/java/android/opengl/EGL14.java // This is similar to how GlSurfaceView does: // http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/opengl/GLSurfaceView.java#760 private static final int EGL_OPENGL_ES2_BIT = 4; private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; // Android-specific extension. private static final int EGL_RECORDABLE_ANDROID = 0x3142; private final EGL10 egl; private EGLContext eglContext; private ConfigType configType; private EGLConfig eglConfig; private EGLDisplay eglDisplay; private EGLSurface eglSurface = EGL10.EGL_NO_SURFACE; // EGLConfig constructor type. Influences eglChooseConfig arguments. public static enum ConfigType { // No special parameters. PLAIN, // Configures with EGL_SURFACE_TYPE = EGL_PBUFFER_BIT. PIXEL_BUFFER, // Configures with EGL_RECORDABLE_ANDROID = 1. // Discourages EGL from using pixel formats that cannot efficiently be // converted to something usable by the video encoder. RECORDABLE } // Create root context without any EGLSurface or parent EGLContext. This can be used for branching // new contexts that share data. public EglBase() { this(EGL10.EGL_NO_CONTEXT, ConfigType.PLAIN); } // Create a new context with the specified config type, sharing data with sharedContext. public EglBase(EGLContext sharedContext, ConfigType configType) { this.egl = (EGL10) EGLContext.getEGL(); this.configType = configType; eglDisplay = getEglDisplay(); eglConfig = getEglConfig(eglDisplay, configType); eglContext = createEglContext(sharedContext, eglDisplay, eglConfig); } // Create EGLSurface from the Android SurfaceHolder. public void createSurface(SurfaceHolder surfaceHolder) { createSurfaceInternal(surfaceHolder); } // Create EGLSurface from the Android SurfaceTexture. 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 (configType == ConfigType.PIXEL_BUFFER) { Logging.w(TAG, "This EGL context is configured for PIXEL_BUFFER, but uses regular Surface"); } if (eglSurface != EGL10.EGL_NO_SURFACE) { throw new RuntimeException("Already has an EGLSurface"); } int[] surfaceAttribs = {EGL10.EGL_NONE}; eglSurface = egl.eglCreateWindowSurface(eglDisplay, eglConfig, nativeWindow, surfaceAttribs); if (eglSurface == EGL10.EGL_NO_SURFACE) { throw new RuntimeException("Failed to create window surface"); } } // Create dummy 1x1 pixel buffer surface so the context can be made current. public void createDummyPbufferSurface() { createPbufferSurface(1, 1); } public void createPbufferSurface(int width, int height) { checkIsNotReleased(); if (configType != ConfigType.PIXEL_BUFFER) { throw new RuntimeException( "This EGL context is not configured to use a pixel buffer: " + configType); } if (eglSurface != EGL10.EGL_NO_SURFACE) { throw new RuntimeException("Already has an EGLSurface"); } int[] surfaceAttribs = {EGL10.EGL_WIDTH, width, EGL10.EGL_HEIGHT, height, EGL10.EGL_NONE}; eglSurface = egl.eglCreatePbufferSurface(eglDisplay, eglConfig, surfaceAttribs); if (eglSurface == EGL10.EGL_NO_SURFACE) { throw new RuntimeException("Failed to create pixel buffer surface"); } } public EGLContext getContext() { return eglContext; } public boolean hasSurface() { return eglSurface != EGL10.EGL_NO_SURFACE; } public int surfaceWidth() { final int widthArray[] = new int[1]; egl.eglQuerySurface(eglDisplay, eglSurface, EGL10.EGL_WIDTH, widthArray); return widthArray[0]; } public int surfaceHeight() { final int heightArray[] = new int[1]; egl.eglQuerySurface(eglDisplay, eglSurface, EGL10.EGL_HEIGHT, heightArray); return heightArray[0]; } public void releaseSurface() { if (eglSurface != EGL10.EGL_NO_SURFACE) { egl.eglDestroySurface(eglDisplay, eglSurface); eglSurface = EGL10.EGL_NO_SURFACE; } } private void checkIsNotReleased() { if (eglDisplay == EGL10.EGL_NO_DISPLAY || eglContext == EGL10.EGL_NO_CONTEXT || eglConfig == null) { throw new RuntimeException("This object has been released"); } } public void release() { checkIsNotReleased(); releaseSurface(); detachCurrent(); egl.eglDestroyContext(eglDisplay, eglContext); egl.eglTerminate(eglDisplay); eglContext = EGL10.EGL_NO_CONTEXT; eglDisplay = EGL10.EGL_NO_DISPLAY; eglConfig = null; } public void makeCurrent() { checkIsNotReleased(); if (eglSurface == EGL10.EGL_NO_SURFACE) { throw new RuntimeException("No EGLSurface - can't make current"); } if (!egl.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) { throw new RuntimeException("eglMakeCurrent failed"); } } // Detach the current EGL context, so that it can be made current on another thread. public void detachCurrent() { if (!egl.eglMakeCurrent( eglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT)) { throw new RuntimeException("eglMakeCurrent failed"); } } public void swapBuffers() { checkIsNotReleased(); if (eglSurface == EGL10.EGL_NO_SURFACE) { throw new RuntimeException("No EGLSurface - can't swap buffers"); } egl.eglSwapBuffers(eglDisplay, eglSurface); } // Return an EGLDisplay, or die trying. private EGLDisplay getEglDisplay() { EGLDisplay eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); if (eglDisplay == EGL10.EGL_NO_DISPLAY) { throw new RuntimeException("Unable to get EGL10 display"); } int[] version = new int[2]; if (!egl.eglInitialize(eglDisplay, version)) { throw new RuntimeException("Unable to initialize EGL10"); } return eglDisplay; } // Return an EGLConfig, or die trying. private EGLConfig getEglConfig(EGLDisplay eglDisplay, ConfigType configType) { // Always RGB888, GLES2. int[] configAttributes = { EGL10.EGL_RED_SIZE, 8, EGL10.EGL_GREEN_SIZE, 8, EGL10.EGL_BLUE_SIZE, 8, EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL10.EGL_NONE, 0, // Allocate dummy fields for specific options. EGL10.EGL_NONE }; // Fill in dummy fields based on configType. switch (configType) { case PLAIN: break; case PIXEL_BUFFER: configAttributes[configAttributes.length - 3] = EGL10.EGL_SURFACE_TYPE; configAttributes[configAttributes.length - 2] = EGL10.EGL_PBUFFER_BIT; break; case RECORDABLE: configAttributes[configAttributes.length - 3] = EGL_RECORDABLE_ANDROID; configAttributes[configAttributes.length - 2] = 1; break; default: throw new IllegalArgumentException(); } EGLConfig[] configs = new EGLConfig[1]; int[] numConfigs = new int[1]; if (!egl.eglChooseConfig( eglDisplay, configAttributes, configs, configs.length, numConfigs)) { throw new RuntimeException("Unable to find RGB888 " + configType + " EGL config"); } return configs[0]; } // Return an EGLConfig, or die trying. private EGLContext createEglContext( EGLContext sharedContext, EGLDisplay eglDisplay, EGLConfig eglConfig) { int[] contextAttributes = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE}; EGLContext eglContext = egl.eglCreateContext(eglDisplay, eglConfig, sharedContext, contextAttributes); if (eglContext == EGL10.EGL_NO_CONTEXT) { throw new RuntimeException("Failed to create EGL context"); } return eglContext; } }