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