1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package org.mozilla.thirdparty.com.google.android.exoplayer2.util; 17 18 import static android.opengl.GLU.gluErrorString; 19 20 import android.annotation.TargetApi; 21 import android.content.Context; 22 import android.content.pm.PackageManager; 23 import android.opengl.EGL14; 24 import android.opengl.EGLDisplay; 25 import android.opengl.GLES11Ext; 26 import android.opengl.GLES20; 27 import android.text.TextUtils; 28 import androidx.annotation.Nullable; 29 import org.mozilla.thirdparty.com.google.android.exoplayer2.C; 30 import org.mozilla.thirdparty.com.google.android.exoplayer2.ExoPlayerLibraryInfo; 31 import java.nio.Buffer; 32 import java.nio.ByteBuffer; 33 import java.nio.ByteOrder; 34 import java.nio.FloatBuffer; 35 import java.nio.IntBuffer; 36 import javax.microedition.khronos.egl.EGL10; 37 38 /** GL utilities. */ 39 public final class GlUtil { 40 41 /** 42 * GL attribute, which can be attached to a buffer with {@link Attribute#setBuffer(float[], int)}. 43 */ 44 public static final class Attribute { 45 46 /** The name of the attribute in the GLSL sources. */ 47 public final String name; 48 49 private final int index; 50 private final int location; 51 52 @Nullable private Buffer buffer; 53 private int size; 54 55 /** 56 * Creates a new GL attribute. 57 * 58 * @param program The identifier of a compiled and linked GLSL shader program. 59 * @param index The index of the attribute. After this instance has been constructed, the name 60 * of the attribute is available via the {@link #name} field. 61 */ Attribute(int program, int index)62 public Attribute(int program, int index) { 63 int[] len = new int[1]; 64 GLES20.glGetProgramiv(program, GLES20.GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, len, 0); 65 66 int[] type = new int[1]; 67 int[] size = new int[1]; 68 byte[] nameBytes = new byte[len[0]]; 69 int[] ignore = new int[1]; 70 71 GLES20.glGetActiveAttrib(program, index, len[0], ignore, 0, size, 0, type, 0, nameBytes, 0); 72 name = new String(nameBytes, 0, strlen(nameBytes)); 73 location = GLES20.glGetAttribLocation(program, name); 74 this.index = index; 75 } 76 77 /** 78 * Configures {@link #bind()} to attach vertices in {@code buffer} (each of size {@code size} 79 * elements) to this {@link Attribute}. 80 * 81 * @param buffer Buffer to bind to this attribute. 82 * @param size Number of elements per vertex. 83 */ setBuffer(float[] buffer, int size)84 public void setBuffer(float[] buffer, int size) { 85 this.buffer = createBuffer(buffer); 86 this.size = size; 87 } 88 89 /** 90 * Sets the vertex attribute to whatever was attached via {@link #setBuffer(float[], int)}. 91 * 92 * <p>Should be called before each drawing call. 93 */ bind()94 public void bind() { 95 Buffer buffer = Assertions.checkNotNull(this.buffer, "call setBuffer before bind"); 96 GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); 97 GLES20.glVertexAttribPointer( 98 location, 99 size, // count 100 GLES20.GL_FLOAT, // type 101 false, // normalize 102 0, // stride 103 buffer); 104 GLES20.glEnableVertexAttribArray(index); 105 checkGlError(); 106 } 107 } 108 109 /** 110 * GL uniform, which can be attached to a sampler using {@link Uniform#setSamplerTexId(int, int)}. 111 */ 112 public static final class Uniform { 113 114 /** The name of the uniform in the GLSL sources. */ 115 public final String name; 116 117 private final int location; 118 private final int type; 119 private final float[] value; 120 121 private int texId; 122 private int unit; 123 124 /** 125 * Creates a new GL uniform. 126 * 127 * @param program The identifier of a compiled and linked GLSL shader program. 128 * @param index The index of the uniform. After this instance has been constructed, the name of 129 * the uniform is available via the {@link #name} field. 130 */ Uniform(int program, int index)131 public Uniform(int program, int index) { 132 int[] len = new int[1]; 133 GLES20.glGetProgramiv(program, GLES20.GL_ACTIVE_UNIFORM_MAX_LENGTH, len, 0); 134 135 int[] type = new int[1]; 136 int[] size = new int[1]; 137 byte[] name = new byte[len[0]]; 138 int[] ignore = new int[1]; 139 140 GLES20.glGetActiveUniform(program, index, len[0], ignore, 0, size, 0, type, 0, name, 0); 141 this.name = new String(name, 0, strlen(name)); 142 location = GLES20.glGetUniformLocation(program, this.name); 143 this.type = type[0]; 144 145 value = new float[1]; 146 } 147 148 /** 149 * Configures {@link #bind()} to use the specified {@code texId} for this sampler uniform. 150 * 151 * @param texId The GL texture identifier from which to sample. 152 * @param unit The GL texture unit index. 153 */ setSamplerTexId(int texId, int unit)154 public void setSamplerTexId(int texId, int unit) { 155 this.texId = texId; 156 this.unit = unit; 157 } 158 159 /** Configures {@link #bind()} to use the specified float {@code value} for this uniform. */ setFloat(float value)160 public void setFloat(float value) { 161 this.value[0] = value; 162 } 163 164 /** 165 * Sets the uniform to whatever value was passed via {@link #setSamplerTexId(int, int)} or 166 * {@link #setFloat(float)}. 167 * 168 * <p>Should be called before each drawing call. 169 */ bind()170 public void bind() { 171 if (type == GLES20.GL_FLOAT) { 172 GLES20.glUniform1fv(location, 1, value, 0); 173 checkGlError(); 174 return; 175 } 176 177 if (texId == 0) { 178 throw new IllegalStateException("call setSamplerTexId before bind"); 179 } 180 GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + unit); 181 if (type == GLES11Ext.GL_SAMPLER_EXTERNAL_OES) { 182 GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId); 183 } else if (type == GLES20.GL_SAMPLER_2D) { 184 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId); 185 } else { 186 throw new IllegalStateException("unexpected uniform type: " + type); 187 } 188 GLES20.glUniform1i(location, unit); 189 GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); 190 GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); 191 GLES20.glTexParameteri( 192 GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); 193 GLES20.glTexParameteri( 194 GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); 195 checkGlError(); 196 } 197 } 198 199 private static final String TAG = "GlUtil"; 200 201 private static final String EXTENSION_PROTECTED_CONTENT = "EGL_EXT_protected_content"; 202 private static final String EXTENSION_SURFACELESS_CONTEXT = "EGL_KHR_surfaceless_context"; 203 204 /** Class only contains static methods. */ GlUtil()205 private GlUtil() {} 206 207 /** 208 * Returns whether creating a GL context with {@value EXTENSION_PROTECTED_CONTENT} is possible. If 209 * {@code true}, the device supports a protected output path for DRM content when using GL. 210 */ 211 @TargetApi(24) isProtectedContentExtensionSupported(Context context)212 public static boolean isProtectedContentExtensionSupported(Context context) { 213 if (Util.SDK_INT < 24) { 214 return false; 215 } 216 if (Util.SDK_INT < 26 && ("samsung".equals(Util.MANUFACTURER) || "XT1650".equals(Util.MODEL))) { 217 // Samsung devices running Nougat are known to be broken. See 218 // https://github.com/google/ExoPlayer/issues/3373 and [Internal: b/37197802]. 219 // Moto Z XT1650 is also affected. See 220 // https://github.com/google/ExoPlayer/issues/3215. 221 return false; 222 } 223 if (Util.SDK_INT < 26 224 && !context 225 .getPackageManager() 226 .hasSystemFeature(PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) { 227 // Pre API level 26 devices were not well tested unless they supported VR mode. 228 return false; 229 } 230 231 EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); 232 @Nullable String eglExtensions = EGL14.eglQueryString(display, EGL10.EGL_EXTENSIONS); 233 return eglExtensions != null && eglExtensions.contains(EXTENSION_PROTECTED_CONTENT); 234 } 235 236 /** 237 * Returns whether creating a GL context with {@value EXTENSION_SURFACELESS_CONTEXT} is possible. 238 */ 239 @TargetApi(17) isSurfacelessContextExtensionSupported()240 public static boolean isSurfacelessContextExtensionSupported() { 241 if (Util.SDK_INT < 17) { 242 return false; 243 } 244 EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); 245 @Nullable String eglExtensions = EGL14.eglQueryString(display, EGL10.EGL_EXTENSIONS); 246 return eglExtensions != null && eglExtensions.contains(EXTENSION_SURFACELESS_CONTEXT); 247 } 248 249 /** 250 * If there is an OpenGl error, logs the error and if {@link 251 * ExoPlayerLibraryInfo#GL_ASSERTIONS_ENABLED} is true throws a {@link RuntimeException}. 252 */ checkGlError()253 public static void checkGlError() { 254 int lastError = GLES20.GL_NO_ERROR; 255 int error; 256 while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { 257 Log.e(TAG, "glError " + gluErrorString(error)); 258 lastError = error; 259 } 260 if (ExoPlayerLibraryInfo.GL_ASSERTIONS_ENABLED && lastError != GLES20.GL_NO_ERROR) { 261 throw new RuntimeException("glError " + gluErrorString(lastError)); 262 } 263 } 264 265 /** 266 * Builds a GL shader program from vertex and fragment shader code. 267 * 268 * @param vertexCode GLES20 vertex shader program as arrays of strings. Strings are joined by 269 * adding a new line character in between each of them. 270 * @param fragmentCode GLES20 fragment shader program as arrays of strings. Strings are joined by 271 * adding a new line character in between each of them. 272 * @return GLES20 program id. 273 */ compileProgram(String[] vertexCode, String[] fragmentCode)274 public static int compileProgram(String[] vertexCode, String[] fragmentCode) { 275 return compileProgram(TextUtils.join("\n", vertexCode), TextUtils.join("\n", fragmentCode)); 276 } 277 278 /** 279 * Builds a GL shader program from vertex and fragment shader code. 280 * 281 * @param vertexCode GLES20 vertex shader program. 282 * @param fragmentCode GLES20 fragment shader program. 283 * @return GLES20 program id. 284 */ compileProgram(String vertexCode, String fragmentCode)285 public static int compileProgram(String vertexCode, String fragmentCode) { 286 int program = GLES20.glCreateProgram(); 287 checkGlError(); 288 289 // Add the vertex and fragment shaders. 290 addShader(GLES20.GL_VERTEX_SHADER, vertexCode, program); 291 addShader(GLES20.GL_FRAGMENT_SHADER, fragmentCode, program); 292 293 // Link and check for errors. 294 GLES20.glLinkProgram(program); 295 int[] linkStatus = new int[] {GLES20.GL_FALSE}; 296 GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); 297 if (linkStatus[0] != GLES20.GL_TRUE) { 298 throwGlError("Unable to link shader program: \n" + GLES20.glGetProgramInfoLog(program)); 299 } 300 checkGlError(); 301 302 return program; 303 } 304 305 /** Returns the {@link Attribute}s in the specified {@code program}. */ getAttributes(int program)306 public static Attribute[] getAttributes(int program) { 307 int[] attributeCount = new int[1]; 308 GLES20.glGetProgramiv(program, GLES20.GL_ACTIVE_ATTRIBUTES, attributeCount, 0); 309 if (attributeCount[0] != 2) { 310 throw new IllegalStateException("expected two attributes"); 311 } 312 313 Attribute[] attributes = new Attribute[attributeCount[0]]; 314 for (int i = 0; i < attributeCount[0]; i++) { 315 attributes[i] = new Attribute(program, i); 316 } 317 return attributes; 318 } 319 320 /** Returns the {@link Uniform}s in the specified {@code program}. */ getUniforms(int program)321 public static Uniform[] getUniforms(int program) { 322 int[] uniformCount = new int[1]; 323 GLES20.glGetProgramiv(program, GLES20.GL_ACTIVE_UNIFORMS, uniformCount, 0); 324 325 Uniform[] uniforms = new Uniform[uniformCount[0]]; 326 for (int i = 0; i < uniformCount[0]; i++) { 327 uniforms[i] = new Uniform(program, i); 328 } 329 330 return uniforms; 331 } 332 333 /** 334 * Allocates a FloatBuffer with the given data. 335 * 336 * @param data Used to initialize the new buffer. 337 */ createBuffer(float[] data)338 public static FloatBuffer createBuffer(float[] data) { 339 return (FloatBuffer) createBuffer(data.length).put(data).flip(); 340 } 341 342 /** 343 * Allocates a FloatBuffer. 344 * 345 * @param capacity The new buffer's capacity, in floats. 346 */ createBuffer(int capacity)347 public static FloatBuffer createBuffer(int capacity) { 348 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(capacity * C.BYTES_PER_FLOAT); 349 return byteBuffer.order(ByteOrder.nativeOrder()).asFloatBuffer(); 350 } 351 352 /** 353 * Creates a GL_TEXTURE_EXTERNAL_OES with default configuration of GL_LINEAR filtering and 354 * GL_CLAMP_TO_EDGE wrapping. 355 */ createExternalTexture()356 public static int createExternalTexture() { 357 int[] texId = new int[1]; 358 GLES20.glGenTextures(1, IntBuffer.wrap(texId)); 359 GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId[0]); 360 GLES20.glTexParameteri( 361 GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); 362 GLES20.glTexParameteri( 363 GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); 364 GLES20.glTexParameteri( 365 GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); 366 GLES20.glTexParameteri( 367 GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); 368 checkGlError(); 369 return texId[0]; 370 } 371 addShader(int type, String source, int program)372 private static void addShader(int type, String source, int program) { 373 int shader = GLES20.glCreateShader(type); 374 GLES20.glShaderSource(shader, source); 375 GLES20.glCompileShader(shader); 376 377 int[] result = new int[] {GLES20.GL_FALSE}; 378 GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, result, 0); 379 if (result[0] != GLES20.GL_TRUE) { 380 throwGlError(GLES20.glGetShaderInfoLog(shader) + ", source: " + source); 381 } 382 383 GLES20.glAttachShader(program, shader); 384 GLES20.glDeleteShader(shader); 385 checkGlError(); 386 } 387 throwGlError(String errorMsg)388 private static void throwGlError(String errorMsg) { 389 Log.e(TAG, errorMsg); 390 if (ExoPlayerLibraryInfo.GL_ASSERTIONS_ENABLED) { 391 throw new RuntimeException(errorMsg); 392 } 393 } 394 395 /** Returns the length of the null-terminated string in {@code strVal}. */ strlen(byte[] strVal)396 private static int strlen(byte[] strVal) { 397 for (int i = 0; i < strVal.length; ++i) { 398 if (strVal[i] == '\0') { 399 return i; 400 } 401 } 402 return strVal.length; 403 } 404 } 405