From 75a69082065ff5980ce1901ca392589e6a313745 Mon Sep 17 00:00:00 2001 From: kimvde Date: Fri, 27 Aug 2021 19:21:29 +0100 Subject: [PATCH] Add methods needed by Transformer to GlUtil This is to add a step to the Transformer transcoding video pipeline to copy from a surface to another using OpenGL. PiperOrigin-RevId: 393391005 --- RELEASENOTES.md | 2 + .../exoplayer2/ExoPlayerLibraryInfo.java | 3 - .../android/exoplayer2/util/GlUtil.java | 213 ++++++++++++++++-- 3 files changed, 202 insertions(+), 16 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 6cb050724d..365efac3f2 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -11,6 +11,8 @@ * Deprecate `SimpleExoPlayer.Builder`. Use `ExoPlayer.Builder` instead. * Fix track selection in `StyledPlayerControlView` when using `ForwardingPlayer`. + * Remove `ExoPlayerLibraryInfo.GL_ASSERTIONS_ENABLED`. Use + `GlUtil.glAssertionsEnabled` instead. * Extractors: * Support TS packets without PTS flag ([#9294](https://github.com/google/ExoPlayer/issues/9294)). diff --git a/library/common/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java b/library/common/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java index 198ec23817..cafe4f5b04 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java @@ -57,9 +57,6 @@ public final class ExoPlayerLibraryInfo { */ public static final boolean ASSERTIONS_ENABLED = true; - /** Whether an exception should be thrown in case of an OpenGl error. */ - public static final boolean GL_ASSERTIONS_ENABLED = false; - /** * Whether the library was compiled with {@link com.google.android.exoplayer2.util.TraceUtil} * trace enabled. diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/GlUtil.java b/library/common/src/main/java/com/google/android/exoplayer2/util/GlUtil.java index b41b6fd2b0..09fc24add7 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/GlUtil.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/GlUtil.java @@ -20,13 +20,17 @@ import static android.opengl.GLU.gluErrorString; import android.content.Context; import android.content.pm.PackageManager; import android.opengl.EGL14; +import android.opengl.EGLConfig; +import android.opengl.EGLContext; import android.opengl.EGLDisplay; +import android.opengl.EGLSurface; import android.opengl.GLES11Ext; import android.opengl.GLES20; import android.text.TextUtils; +import androidx.annotation.DoNotInline; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -37,6 +41,17 @@ import javax.microedition.khronos.egl.EGL10; /** GL utilities. */ public final class GlUtil { + /** Thrown when an OpenGL error occurs and {@link #glAssertionsEnabled} is {@code true}. */ + public static final class GlException extends RuntimeException { + /** Creates an instance with the specified error message. */ + public GlException(String message) { + super(message); + } + } + + /** Thrown when the required EGL version is not supported by the device. */ + public static final class UnsupportedEglVersionException extends Exception {} + /** * GL attribute, which can be attached to a buffer with {@link Attribute#setBuffer(float[], int)}. */ @@ -185,7 +200,7 @@ public final class GlUtil { } if (texId == 0) { - throw new IllegalStateException("call setSamplerTexId before bind"); + throw new IllegalStateException("Call setSamplerTexId before bind."); } GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + unit); if (type == GLES11Ext.GL_SAMPLER_EXTERNAL_OES) { @@ -193,7 +208,7 @@ public final class GlUtil { } else if (type == GLES20.GL_SAMPLER_2D) { GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId); } else { - throw new IllegalStateException("unexpected uniform type: " + type); + throw new IllegalStateException("Unexpected uniform type: " + type); } GLES20.glUniform1i(location, unit); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); @@ -206,6 +221,9 @@ public final class GlUtil { } } + /** Whether to throw a {@link GlException} in case of an OpenGL error. */ + public static boolean glAssertionsEnabled = false; + private static final String TAG = "GlUtil"; private static final String EXTENSION_PROTECTED_CONTENT = "EGL_EXT_protected_content"; @@ -254,9 +272,38 @@ public final class GlUtil { return eglExtensions != null && eglExtensions.contains(EXTENSION_SURFACELESS_CONTEXT); } + /** Returns an initialized default {@link EGLDisplay}. */ + @RequiresApi(17) + public static EGLDisplay createEglDisplay() { + return Api17.createEglDisplay(); + } + /** - * If there is an OpenGl error, logs the error and if {@link - * ExoPlayerLibraryInfo#GL_ASSERTIONS_ENABLED} is true throws a {@link RuntimeException}. + * Returns a new {@link EGLContext} for the specified {@link EGLDisplay}. + * + * @throws UnsupportedEglVersionException If the device does not support EGL version 2. {@code + * eglDisplay} is terminated before the exception is thrown in this case. + */ + @RequiresApi(17) + public static EGLContext createEglContext(EGLDisplay eglDisplay) + throws UnsupportedEglVersionException { + return Api17.createEglContext(eglDisplay); + } + + /** + * Returns a new {@link EGLSurface} wrapping the specified {@code surface}. + * + * @param eglDisplay The {@link EGLDisplay} to attach the surface to. + * @param surface The surface to wrap; must be a surface, surface texture or surface holder. + */ + @RequiresApi(17) + public static EGLSurface getEglSurface(EGLDisplay eglDisplay, Object surface) { + return Api17.getEglSurface(eglDisplay, surface); + } + + /** + * If there is an OpenGl error, logs the error and if {@link #glAssertionsEnabled} is true throws + * a {@link GlException}. */ public static void checkGlError() { int lastError = GLES20.GL_NO_ERROR; @@ -265,11 +312,21 @@ public final class GlUtil { Log.e(TAG, "glError " + gluErrorString(error)); lastError = error; } - if (ExoPlayerLibraryInfo.GL_ASSERTIONS_ENABLED && lastError != GLES20.GL_NO_ERROR) { - throw new RuntimeException("glError " + gluErrorString(lastError)); + if (lastError != GLES20.GL_NO_ERROR) { + throwGlException("glError " + gluErrorString(lastError)); } } + /** + * Makes the specified {@code surface} the render target, using a viewport of {@code width} by + * {@code height} pixels. + */ + @RequiresApi(17) + public static void focusSurface( + EGLDisplay eglDisplay, EGLContext eglContext, EGLSurface surface, int width, int height) { + Api17.focusSurface(eglDisplay, eglContext, surface, width, height); + } + /** * Builds a GL shader program from vertex and fragment shader code. * @@ -303,7 +360,7 @@ public final class GlUtil { int[] linkStatus = new int[] {GLES20.GL_FALSE}; GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); if (linkStatus[0] != GLES20.GL_TRUE) { - throwGlError("Unable to link shader program: \n" + GLES20.glGetProgramInfoLog(program)); + throwGlException("Unable to link shader program: \n" + GLES20.glGetProgramInfoLog(program)); } checkGlError(); @@ -315,7 +372,7 @@ public final class GlUtil { int[] attributeCount = new int[1]; GLES20.glGetProgramiv(program, GLES20.GL_ACTIVE_ATTRIBUTES, attributeCount, 0); if (attributeCount[0] != 2) { - throw new IllegalStateException("expected two attributes"); + throw new IllegalStateException("Expected two attributes."); } Attribute[] attributes = new Attribute[attributeCount[0]]; @@ -338,6 +395,27 @@ public final class GlUtil { return uniforms; } + /** + * Deletes a GL texture. + * + * @param textureId The ID of the texture to delete. + */ + public static void deleteTexture(int textureId) { + int[] textures = new int[] {textureId}; + GLES20.glDeleteTextures(1, textures, 0); + checkGlError(); + } + + /** + * Destroys the {@link EGLContext} identified by the provided {@link EGLDisplay} and {@link + * EGLContext}. + */ + @RequiresApi(17) + public static void destroyEglContext( + @Nullable EGLDisplay eglDisplay, @Nullable EGLContext eglContext) { + Api17.destroyEglContext(eglDisplay, eglContext); + } + /** * Allocates a FloatBuffer with the given data. * @@ -385,7 +463,7 @@ public final class GlUtil { int[] result = new int[] {GLES20.GL_FALSE}; GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, result, 0); if (result[0] != GLES20.GL_TRUE) { - throwGlError(GLES20.glGetShaderInfoLog(shader) + ", source: " + source); + throwGlException(GLES20.glGetShaderInfoLog(shader) + ", source: " + source); } GLES20.glAttachShader(program, shader); @@ -393,10 +471,16 @@ public final class GlUtil { checkGlError(); } - private static void throwGlError(String errorMsg) { + private static void throwGlException(String errorMsg) { Log.e(TAG, errorMsg); - if (ExoPlayerLibraryInfo.GL_ASSERTIONS_ENABLED) { - throw new RuntimeException(errorMsg); + if (glAssertionsEnabled) { + throw new GlException(errorMsg); + } + } + + private static void checkEglException(boolean expression, String errorMessage) { + if (!expression) { + throwGlException(errorMessage); } } @@ -409,4 +493,107 @@ public final class GlUtil { } return strVal.length; } + + @RequiresApi(17) + private static final class Api17 { + private Api17() {} + + @DoNotInline + public static EGLDisplay createEglDisplay() { + EGLDisplay eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); + checkEglException(!eglDisplay.equals(EGL14.EGL_NO_DISPLAY), "No EGL display."); + int[] major = new int[1]; + int[] minor = new int[1]; + if (!EGL14.eglInitialize(eglDisplay, major, 0, minor, 0)) { + throwGlException("Error in eglInitialize."); + } + checkGlError(); + return eglDisplay; + } + + @DoNotInline + public static EGLContext createEglContext(EGLDisplay eglDisplay) + throws UnsupportedEglVersionException { + int[] contextAttributes = {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE}; + EGLContext eglContext = + EGL14.eglCreateContext( + eglDisplay, getEglConfig(eglDisplay), EGL14.EGL_NO_CONTEXT, contextAttributes, 0); + if (eglContext == null) { + EGL14.eglTerminate(eglDisplay); + throw new UnsupportedEglVersionException(); + } + checkGlError(); + return eglContext; + } + + @DoNotInline + public static EGLSurface getEglSurface(EGLDisplay eglDisplay, Object surface) { + return EGL14.eglCreateWindowSurface( + eglDisplay, getEglConfig(eglDisplay), surface, new int[] {EGL14.EGL_NONE}, 0); + } + + @DoNotInline + public static void focusSurface( + EGLDisplay eglDisplay, EGLContext eglContext, EGLSurface surface, int width, int height) { + int[] fbos = new int[1]; + GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, fbos, 0); + int noFbo = 0; + if (fbos[0] != noFbo) { + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, noFbo); + } + EGL14.eglMakeCurrent(eglDisplay, surface, surface, eglContext); + GLES20.glViewport(0, 0, width, height); + } + + @DoNotInline + public static void destroyEglContext( + @Nullable EGLDisplay eglDisplay, @Nullable EGLContext eglContext) { + if (eglDisplay == null) { + return; + } + EGL14.eglMakeCurrent( + eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT); + int error = EGL14.eglGetError(); + checkEglException(error == EGL14.EGL_SUCCESS, "Error releasing context: " + error); + if (eglContext != null) { + EGL14.eglDestroyContext(eglDisplay, eglContext); + error = EGL14.eglGetError(); + checkEglException(error == EGL14.EGL_SUCCESS, "Error destroying context: " + error); + } + EGL14.eglReleaseThread(); + error = EGL14.eglGetError(); + checkEglException(error == EGL14.EGL_SUCCESS, "Error releasing thread: " + error); + EGL14.eglTerminate(eglDisplay); + error = EGL14.eglGetError(); + checkEglException(error == EGL14.EGL_SUCCESS, "Error terminating display: " + error); + } + + @DoNotInline + private static EGLConfig getEglConfig(EGLDisplay eglDisplay) { + int redSize = 8; + int greenSize = 8; + int blueSize = 8; + int alphaSize = 8; + int depthSize = 0; + int stencilSize = 0; + int[] defaultConfiguration = + new int[] { + EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, + EGL14.EGL_RED_SIZE, redSize, + EGL14.EGL_GREEN_SIZE, greenSize, + EGL14.EGL_BLUE_SIZE, blueSize, + EGL14.EGL_ALPHA_SIZE, alphaSize, + EGL14.EGL_DEPTH_SIZE, depthSize, + EGL14.EGL_STENCIL_SIZE, stencilSize, + EGL14.EGL_NONE + }; + int[] configsCount = new int[1]; + EGLConfig[] eglConfigs = new EGLConfig[1]; + if (!EGL14.eglChooseConfig( + eglDisplay, defaultConfiguration, 0, eglConfigs, 0, 1, configsCount, 0)) { + throwGlException("eglChooseConfig failed."); + } + return eglConfigs[0]; + } + } }