1 /*
2  * Copyright 2024 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 
17 package androidx.camera.core.processing.util;
18 
19 import static android.opengl.GLES11Ext.GL_TEXTURE_EXTERNAL_OES;
20 
21 import android.opengl.EGL14;
22 import android.opengl.EGLConfig;
23 import android.opengl.EGLDisplay;
24 import android.opengl.EGLSurface;
25 import android.opengl.GLES20;
26 import android.opengl.Matrix;
27 import android.util.Log;
28 import android.util.Size;
29 import android.view.Surface;
30 
31 import androidx.annotation.RestrictTo;
32 import androidx.camera.core.DynamicRange;
33 import androidx.camera.core.Logger;
34 import androidx.camera.core.processing.ShaderProvider;
35 import androidx.core.util.Preconditions;
36 
37 import org.jspecify.annotations.NonNull;
38 import org.jspecify.annotations.Nullable;
39 
40 import java.nio.ByteBuffer;
41 import java.nio.ByteOrder;
42 import java.nio.FloatBuffer;
43 import java.util.HashMap;
44 import java.util.Locale;
45 import java.util.Map;
46 import java.util.concurrent.atomic.AtomicBoolean;
47 import java.util.regex.Matcher;
48 import java.util.regex.Pattern;
49 
50 /**
51  * Utility class for OpenGL ES.
52  */
53 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
54 public final class GLUtils {
55 
56     /** Unknown version information. */
57     public static final String VERSION_UNKNOWN = "0.0";
58 
59     public static final String TAG = "GLUtils";
60 
61     public static final int EGL_GL_COLORSPACE_KHR = 0x309D;
62     public static final int EGL_GL_COLORSPACE_BT2020_HLG_EXT = 0x3540;
63 
64     public static final String VAR_TEXTURE_COORD = "vTextureCoord";
65     public static final String VAR_TEXTURE = "sTexture";
66     public static final int PIXEL_STRIDE = 4;
67     public static final int[] EMPTY_ATTRIBS = {EGL14.EGL_NONE};
68     public static final int[] HLG_SURFACE_ATTRIBS = {
69             EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_BT2020_HLG_EXT,
70             EGL14.EGL_NONE
71     };
72 
73     public static final String DEFAULT_VERTEX_SHADER = String.format(Locale.US,
74             "uniform mat4 uTexMatrix;\n"
75                     + "uniform mat4 uTransMatrix;\n"
76                     + "attribute vec4 aPosition;\n"
77                     + "attribute vec4 aTextureCoord;\n"
78                     + "varying vec2 %s;\n"
79                     + "void main() {\n"
80                     + "    gl_Position = uTransMatrix * aPosition;\n"
81                     + "    %s = (uTexMatrix * aTextureCoord).xy;\n"
82                     + "}\n", VAR_TEXTURE_COORD, VAR_TEXTURE_COORD);
83 
84     public static final String HDR_VERTEX_SHADER = String.format(Locale.US,
85             "#version 300 es\n"
86                     + "in vec4 aPosition;\n"
87                     + "in vec4 aTextureCoord;\n"
88                     + "uniform mat4 uTexMatrix;\n"
89                     + "uniform mat4 uTransMatrix;\n"
90                     + "out vec2 %s;\n"
91                     + "void main() {\n"
92                     + "  gl_Position = uTransMatrix * aPosition;\n"
93                     + "  %s = (uTexMatrix * aTextureCoord).xy;\n"
94                     + "}\n", VAR_TEXTURE_COORD, VAR_TEXTURE_COORD);
95 
96     public static final String BLANK_VERTEX_SHADER =
97             "uniform mat4 uTransMatrix;\n"
98                     + "attribute vec4 aPosition;\n"
99                     + "void main() {\n"
100                     + "    gl_Position = uTransMatrix * aPosition;\n"
101                     + "}\n";
102 
103     public static final String BLANK_FRAGMENT_SHADER =
104             "precision mediump float;\n"
105                     + "uniform float uAlphaScale;\n"
106                     + "void main() {\n"
107                     + "    gl_FragColor = vec4(0.0, 0.0, 0.0, uAlphaScale);\n"
108                     + "}\n";
109 
110     private static final ShaderProvider SHADER_PROVIDER_DEFAULT = new ShaderProvider() {
111         @Override
112         public @NonNull String createFragmentShader(@NonNull String samplerVarName,
113                 @NonNull String fragCoordsVarName) {
114             return String.format(Locale.US,
115                     "#extension GL_OES_EGL_image_external : require\n"
116                             + "precision mediump float;\n"
117                             + "varying vec2 %s;\n"
118                             + "uniform samplerExternalOES %s;\n"
119                             + "uniform float uAlphaScale;\n"
120                             + "void main() {\n"
121                             + "    vec4 src = texture2D(%s, %s);\n"
122                             + "    gl_FragColor = vec4(src.rgb, src.a * uAlphaScale);\n"
123                             + "}\n",
124                     fragCoordsVarName, samplerVarName, samplerVarName, fragCoordsVarName);
125         }
126     };
127 
128     private static final ShaderProvider SHADER_PROVIDER_HDR_DEFAULT = new ShaderProvider() {
129         @Override
130         public @NonNull String createFragmentShader(@NonNull String samplerVarName,
131                 @NonNull String fragCoordsVarName) {
132             return String.format(Locale.US,
133                     "#version 300 es\n"
134                             + "#extension GL_OES_EGL_image_external_essl3 : require\n"
135                             + "precision mediump float;\n"
136                             + "uniform samplerExternalOES %s;\n"
137                             + "uniform float uAlphaScale;\n"
138                             + "in vec2 %s;\n"
139                             + "out vec4 outColor;\n"
140                             + "\n"
141                             + "void main() {\n"
142                             + "  vec4 src = texture(%s, %s);\n"
143                             + "  outColor = vec4(src.rgb, src.a * uAlphaScale);\n"
144                             + "}",
145                     samplerVarName, fragCoordsVarName, samplerVarName, fragCoordsVarName);
146         }
147     };
148 
149     private static final ShaderProvider SHADER_PROVIDER_HDR_YUV = new ShaderProvider() {
150         @Override
151         public @NonNull String createFragmentShader(@NonNull String samplerVarName,
152                 @NonNull String fragCoordsVarName) {
153             return String.format(Locale.US,
154                     "#version 300 es\n"
155                             + "#extension GL_EXT_YUV_target : require\n"
156                             + "precision mediump float;\n"
157                             + "uniform __samplerExternal2DY2YEXT %s;\n"
158                             + "uniform float uAlphaScale;\n"
159                             + "in vec2 %s;\n"
160                             + "out vec4 outColor;\n"
161                             + "\n"
162                             + "vec3 yuvToRgb(vec3 yuv) {\n"
163                             + "  const vec3 yuvOffset = vec3(0.0625, 0.5, 0.5);\n"
164                             + "  const mat3 yuvToRgbColorMat = mat3(\n"
165                             + "    1.1689f, 1.1689f, 1.1689f,\n"
166                             + "    0.0000f, -0.1881f, 2.1502f,\n"
167                             + "    1.6853f, -0.6530f, 0.0000f\n"
168                             + "  );\n"
169                             + "  return clamp(yuvToRgbColorMat * (yuv - yuvOffset), 0.0, 1.0);\n"
170                             + "}\n"
171                             + "\n"
172                             + "void main() {\n"
173                             + "  vec3 srcYuv = texture(%s, %s).xyz;\n"
174                             + "  vec3 srcRgb = yuvToRgb(srcYuv);\n"
175                             + "  outColor = vec4(srcRgb, uAlphaScale);\n"
176                             + "}",
177                     samplerVarName, fragCoordsVarName, samplerVarName, fragCoordsVarName);
178         }
179     };
180 
181     public static final float[] VERTEX_COORDS = {
182             -1.0f, -1.0f,   // 0 bottom left
183             1.0f, -1.0f,    // 1 bottom right
184             -1.0f, 1.0f,   // 2 top left
185             1.0f, 1.0f,    // 3 top right
186     };
187     public static final FloatBuffer VERTEX_BUF = createFloatBuffer(VERTEX_COORDS);
188 
189     public static final float[] TEX_COORDS = {
190             0.0f, 0.0f,     // 0 bottom left
191             1.0f, 0.0f,     // 1 bottom right
192             0.0f, 1.0f,     // 2 top left
193             1.0f, 1.0f      // 3 top right
194     };
195     public static final FloatBuffer TEX_BUF = createFloatBuffer(TEX_COORDS);
196 
197     public static final int SIZEOF_FLOAT = 4;
198     public static final OutputSurface NO_OUTPUT_SURFACE =
199             OutputSurface.of(EGL14.EGL_NO_SURFACE, 0, 0);
200 
201     public enum InputFormat {
202         /**
203          * Input texture format is unknown.
204          *
205          * <p>When the input format is unknown, HDR content may require rendering blank frames
206          * since we are not sure what type of sampler can be used. For SDR content, it is
207          * typically safe to use samplerExternalOES since this can handle both RGB and YUV inputs
208          * for SDR content.
209          */
210         UNKNOWN,
211         /**
212          * Input texture format is the default format.
213          *
214          * <p>The texture format may be RGB or YUV. For SDR content, using samplerExternalOES is
215          * safe since it will be able to convert YUV to RGB automatically within the shader. For
216          * HDR content, the input is expected to be RGB.
217          */
218         DEFAULT,
219         /**
220          * Input format is explicitly YUV.
221          *
222          * <p>This needs to be specified for HDR content. Only __samplerExternal2DY2YEXT should be
223          * used for HDR YUV content as samplerExternalOES may not correctly convert to RGB.
224          */
225         YUV
226     }
227 
GLUtils()228     private GLUtils() {
229     }
230 
231     public abstract static class Program2D {
232         protected int mProgramHandle;
233         protected int mTransMatrixLoc = -1;
234         protected int mAlphaScaleLoc = -1;
235         protected int mPositionLoc = -1;
236 
Program2D(@onNull String vertexShaderSource, @NonNull String fragmentShaderSource)237         protected Program2D(@NonNull String vertexShaderSource,
238                 @NonNull String fragmentShaderSource) {
239             int vertexShader = -1;
240             int fragmentShader = -1;
241             int program = -1;
242             try {
243                 vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderSource);
244                 fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderSource);
245                 program = GLES20.glCreateProgram();
246                 checkGlErrorOrThrow("glCreateProgram");
247                 GLES20.glAttachShader(program, vertexShader);
248                 checkGlErrorOrThrow("glAttachShader");
249                 GLES20.glAttachShader(program, fragmentShader);
250                 checkGlErrorOrThrow("glAttachShader");
251                 GLES20.glLinkProgram(program);
252                 int[] linkStatus = new int[1];
253                 GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, /*offset=*/0);
254                 if (linkStatus[0] != GLES20.GL_TRUE) {
255                     throw new IllegalStateException(
256                             "Could not link program: " + GLES20.glGetProgramInfoLog(program));
257                 }
258                 mProgramHandle = program;
259             } catch (IllegalStateException | IllegalArgumentException e) {
260                 if (vertexShader != -1) {
261                     GLES20.glDeleteShader(vertexShader);
262                 }
263                 if (fragmentShader != -1) {
264                     GLES20.glDeleteShader(fragmentShader);
265                 }
266                 if (program != -1) {
267                     GLES20.glDeleteProgram(program);
268                 }
269                 throw e;
270             }
271 
272             loadLocations();
273         }
274 
275         /** Use this shader program */
use()276         public void use() {
277             // Select the program.
278             GLES20.glUseProgram(mProgramHandle);
279             checkGlErrorOrThrow("glUseProgram");
280 
281             // Enable the "aPosition" vertex attribute.
282             GLES20.glEnableVertexAttribArray(mPositionLoc);
283             checkGlErrorOrThrow("glEnableVertexAttribArray");
284 
285             // Connect vertexBuffer to "aPosition".
286             int coordsPerVertex = 2;
287             int vertexStride = 0;
288             GLES20.glVertexAttribPointer(mPositionLoc, coordsPerVertex, GLES20.GL_FLOAT,
289                     /*normalized=*/false, vertexStride, VERTEX_BUF);
290             checkGlErrorOrThrow("glVertexAttribPointer");
291 
292             // Set to default value for single camera case
293             updateTransformMatrix(create4x4IdentityMatrix());
294             updateAlpha(1.0f);
295         }
296 
297         /** Updates the global transform matrix */
updateTransformMatrix(float @NonNull [] transformMat)298         public void updateTransformMatrix(float @NonNull [] transformMat) {
299             GLES20.glUniformMatrix4fv(mTransMatrixLoc,
300                     /*count=*/1, /*transpose=*/false, transformMat,
301                     /*offset=*/0);
302             checkGlErrorOrThrow("glUniformMatrix4fv");
303         }
304 
305         /** Updates the alpha of the drawn frame */
updateAlpha(float alpha)306         public void updateAlpha(float alpha) {
307             GLES20.glUniform1f(mAlphaScaleLoc, alpha);
308             checkGlErrorOrThrow("glUniform1f");
309         }
310 
311         /**
312          * Delete the shader program
313          *
314          * <p>Once called, this program should no longer be used.
315          */
delete()316         public void delete() {
317             GLES20.glDeleteProgram(mProgramHandle);
318         }
319 
loadLocations()320         private void loadLocations() {
321             mPositionLoc = GLES20.glGetAttribLocation(mProgramHandle, "aPosition");
322             checkLocationOrThrow(mPositionLoc, "aPosition");
323             mTransMatrixLoc = GLES20.glGetUniformLocation(mProgramHandle, "uTransMatrix");
324             checkLocationOrThrow(mTransMatrixLoc, "uTransMatrix");
325             mAlphaScaleLoc = GLES20.glGetUniformLocation(mProgramHandle, "uAlphaScale");
326             checkLocationOrThrow(mAlphaScaleLoc, "uAlphaScale");
327         }
328     }
329 
330     public static class SamplerShaderProgram extends Program2D {
331         private int mSamplerLoc = -1;
332         private int mTexMatrixLoc = -1;
333         private int mTexCoordLoc = -1;
334 
SamplerShaderProgram( @onNull DynamicRange dynamicRange, @NonNull InputFormat inputFormat )335         public SamplerShaderProgram(
336                 @NonNull DynamicRange dynamicRange,
337                 @NonNull InputFormat inputFormat
338         ) {
339             this(dynamicRange, resolveDefaultShaderProvider(dynamicRange, inputFormat));
340         }
341 
SamplerShaderProgram( @onNull DynamicRange dynamicRange, @NonNull ShaderProvider shaderProvider)342         public SamplerShaderProgram(
343                 @NonNull DynamicRange dynamicRange,
344                 @NonNull ShaderProvider shaderProvider) {
345             super(dynamicRange.is10BitHdr() ? HDR_VERTEX_SHADER : DEFAULT_VERTEX_SHADER,
346                     getFragmentShaderSource(shaderProvider));
347 
348             loadLocations();
349         }
350 
351         @Override
use()352         public void use() {
353             super.use();
354             // Initialize the sampler to the correct texture unit offset
355             GLES20.glUniform1i(mSamplerLoc, 0);
356 
357             // Enable the "aTextureCoord" vertex attribute.
358             GLES20.glEnableVertexAttribArray(mTexCoordLoc);
359             checkGlErrorOrThrow("glEnableVertexAttribArray");
360 
361             // Connect texBuffer to "aTextureCoord".
362             int coordsPerTex = 2;
363             int texStride = 0;
364             GLES20.glVertexAttribPointer(mTexCoordLoc, coordsPerTex, GLES20.GL_FLOAT,
365                     /*normalized=*/false, texStride, TEX_BUF);
366             checkGlErrorOrThrow("glVertexAttribPointer");
367         }
368 
369         /** Updates the texture transform matrix */
updateTextureMatrix(float @NonNull [] textureMat)370         public void updateTextureMatrix(float @NonNull [] textureMat) {
371             GLES20.glUniformMatrix4fv(mTexMatrixLoc, /*count=*/1, /*transpose=*/false,
372                     textureMat, /*offset=*/0);
373             checkGlErrorOrThrow("glUniformMatrix4fv");
374         }
375 
loadLocations()376         private void loadLocations() {
377             super.loadLocations();
378             mSamplerLoc = GLES20.glGetUniformLocation(mProgramHandle, VAR_TEXTURE);
379             checkLocationOrThrow(mSamplerLoc, VAR_TEXTURE);
380             mTexCoordLoc = GLES20.glGetAttribLocation(mProgramHandle, "aTextureCoord");
381             checkLocationOrThrow(mTexCoordLoc, "aTextureCoord");
382             mTexMatrixLoc = GLES20.glGetUniformLocation(mProgramHandle, "uTexMatrix");
383             checkLocationOrThrow(mTexMatrixLoc, "uTexMatrix");
384         }
385 
resolveDefaultShaderProvider( @onNull DynamicRange dynamicRange, @Nullable InputFormat inputFormat)386         private static ShaderProvider resolveDefaultShaderProvider(
387                 @NonNull DynamicRange dynamicRange,
388                 @Nullable InputFormat inputFormat) {
389             if (dynamicRange.is10BitHdr()) {
390                 Preconditions.checkArgument(inputFormat != InputFormat.UNKNOWN,
391                         "No default sampler shader available for" + inputFormat);
392                 if (inputFormat == InputFormat.YUV) {
393                     return SHADER_PROVIDER_HDR_YUV;
394                 }
395                 return SHADER_PROVIDER_HDR_DEFAULT;
396             } else {
397                 return SHADER_PROVIDER_DEFAULT;
398             }
399         }
400     }
401 
402     public static class BlankShaderProgram extends Program2D {
BlankShaderProgram()403         public BlankShaderProgram() {
404             super(BLANK_VERTEX_SHADER, BLANK_FRAGMENT_SHADER);
405         }
406     }
407 
408     /**
409      * Creates an {@link EGLSurface}.
410      */
createWindowSurface(@onNull EGLDisplay eglDisplay, @NonNull EGLConfig eglConfig, @NonNull Surface surface, int @NonNull [] surfaceAttrib)411     public static @NonNull EGLSurface createWindowSurface(@NonNull EGLDisplay eglDisplay,
412             @NonNull EGLConfig eglConfig, @NonNull Surface surface, int @NonNull [] surfaceAttrib) {
413         // Create a window surface, and attach it to the Surface we received.
414         EGLSurface eglSurface = EGL14.eglCreateWindowSurface(eglDisplay, eglConfig, surface,
415                 surfaceAttrib, /*offset=*/0);
416         checkEglErrorOrThrow("eglCreateWindowSurface");
417         if (eglSurface == null) {
418             throw new IllegalStateException("surface was null");
419         }
420         return eglSurface;
421     }
422 
423     /**
424      * Creates the vertex or fragment shader.
425      */
loadShader(int shaderType, @NonNull String source)426     public static int loadShader(int shaderType, @NonNull String source) {
427         int shader = GLES20.glCreateShader(shaderType);
428         checkGlErrorOrThrow("glCreateShader type=" + shaderType);
429         GLES20.glShaderSource(shader, source);
430         GLES20.glCompileShader(shader);
431         int[] compiled = new int[1];
432         GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, /*offset=*/0);
433         if (compiled[0] == 0) {
434             Logger.w(TAG, "Could not compile shader: " + source);
435             String shaderLog = GLES20.glGetShaderInfoLog(shader);
436             GLES20.glDeleteShader(shader);
437             throw new IllegalStateException(
438                     "Could not compile shader type " + shaderType + ":" + shaderLog);
439         }
440         return shader;
441     }
442 
443     /**
444      * Queries the {@link EGLSurface} information.
445      */
querySurface(@onNull EGLDisplay eglDisplay, @NonNull EGLSurface eglSurface, int what)446     public static int querySurface(@NonNull EGLDisplay eglDisplay, @NonNull EGLSurface eglSurface,
447             int what) {
448         int[] value = new int[1];
449         EGL14.eglQuerySurface(eglDisplay, eglSurface, what, value, /*offset=*/0);
450         return value[0];
451     }
452 
453     /**
454      * Gets the size of {@link EGLSurface}.
455      */
getSurfaceSize(@onNull EGLDisplay eglDisplay, @NonNull EGLSurface eglSurface)456     public static @NonNull Size getSurfaceSize(@NonNull EGLDisplay eglDisplay,
457             @NonNull EGLSurface eglSurface) {
458         int width = querySurface(eglDisplay, eglSurface, EGL14.EGL_WIDTH);
459         int height = querySurface(eglDisplay, eglSurface, EGL14.EGL_HEIGHT);
460         return new Size(width, height);
461     }
462 
463     /**
464      * Creates a {@link FloatBuffer}.
465      */
createFloatBuffer(float @NonNull [] coords)466     public static @NonNull FloatBuffer createFloatBuffer(float @NonNull [] coords) {
467         ByteBuffer bb = ByteBuffer.allocateDirect(coords.length * SIZEOF_FLOAT);
468         bb.order(ByteOrder.nativeOrder());
469         FloatBuffer fb = bb.asFloatBuffer();
470         fb.put(coords);
471         fb.position(0);
472         return fb;
473     }
474 
475     /**
476      * Creates a new EGL pixel buffer surface.
477      */
478     @SuppressWarnings("SameParameterValue") // currently hard code width/height with 1/1
createPBufferSurface(@onNull EGLDisplay eglDisplay, @NonNull EGLConfig eglConfig, int width, int height)479     public static @NonNull EGLSurface createPBufferSurface(@NonNull EGLDisplay eglDisplay,
480             @NonNull EGLConfig eglConfig, int width, int height) {
481         int[] surfaceAttrib = {
482                 EGL14.EGL_WIDTH, width,
483                 EGL14.EGL_HEIGHT, height,
484                 EGL14.EGL_NONE
485         };
486         EGLSurface eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, eglConfig, surfaceAttrib,
487                 /*offset=*/0);
488         checkEglErrorOrThrow("eglCreatePbufferSurface");
489         if (eglSurface == null) {
490             throw new IllegalStateException("surface was null");
491         }
492         return eglSurface;
493     }
494 
495     /**
496      * Creates program objects based on shaders which are appropriate for each input format.
497      *
498      * <p>Each {@link InputFormat} may have different sampler requirements based on the dynamic
499      * range. For that reason, we create a separate program for each input format, and will switch
500      * to the program when the input format changes so we correctly sample the input texture
501      * (or no-op, in some cases).
502      */
createPrograms( @onNull DynamicRange dynamicRange, @NonNull Map<InputFormat, ShaderProvider> shaderProviderOverrides)503     public static @NonNull Map<InputFormat, Program2D> createPrograms(
504             @NonNull DynamicRange dynamicRange,
505             @NonNull Map<InputFormat, ShaderProvider> shaderProviderOverrides) {
506         HashMap<InputFormat, Program2D> programs = new HashMap<>();
507         for (InputFormat inputFormat : InputFormat.values()) {
508             ShaderProvider shaderProviderOverride = shaderProviderOverrides.get(inputFormat);
509             Program2D program;
510             if (shaderProviderOverride != null) {
511                 // Always use the overridden shader provider if present
512                 program = new SamplerShaderProgram(dynamicRange, shaderProviderOverride);
513             } else if (inputFormat == InputFormat.YUV || inputFormat == InputFormat.DEFAULT) {
514                 // Use a default sampler shader for DEFAULT or YUV
515                 program = new SamplerShaderProgram(dynamicRange, inputFormat);
516             } else {
517                 Preconditions.checkState(inputFormat == InputFormat.UNKNOWN,
518                         "Unhandled input format: " + inputFormat);
519                 if (dynamicRange.is10BitHdr()) {
520                     // InputFormat is UNKNOWN and we don't know if we need to use a
521                     // YUV-specific sampler for HDR. Use a blank shader program.
522                     program = new BlankShaderProgram();
523                 } else {
524                     // If we're not rendering HDR content, we can use the default sampler shader
525                     // program since it can handle both YUV and DEFAULT inputs when the format
526                     // is UNKNOWN.
527                     ShaderProvider defaultShaderProviderOverride =
528                             shaderProviderOverrides.get(InputFormat.DEFAULT);
529                     if (defaultShaderProviderOverride != null) {
530                         program = new SamplerShaderProgram(dynamicRange,
531                                 defaultShaderProviderOverride);
532                     } else {
533                         program = new SamplerShaderProgram(dynamicRange, InputFormat.DEFAULT);
534                     }
535                 }
536             }
537             Log.d(TAG, "Shader program for input format " + inputFormat + " created: "
538                     + program);
539             programs.put(inputFormat, program);
540         }
541         return programs;
542     }
543 
544     /**
545      * Creates a texture.
546      */
createTexture()547     public static int createTexture() {
548         int[] textures = new int[1];
549         GLES20.glGenTextures(1, textures, 0);
550         checkGlErrorOrThrow("glGenTextures");
551 
552         int texId = textures[0];
553         GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, texId);
554         checkGlErrorOrThrow("glBindTexture " + texId);
555 
556         GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
557                 GLES20.GL_LINEAR);
558         GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
559                 GLES20.GL_LINEAR);
560         GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,
561                 GLES20.GL_CLAMP_TO_EDGE);
562         GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,
563                 GLES20.GL_CLAMP_TO_EDGE);
564         checkGlErrorOrThrow("glTexParameter");
565         return texId;
566     }
567 
568     /**
569      * Creates a 4x4 identity matrix.
570      */
create4x4IdentityMatrix()571     public static float @NonNull [] create4x4IdentityMatrix() {
572         float[] matrix = new float[16];
573         Matrix.setIdentityM(matrix, /* smOffset= */ 0);
574         return matrix;
575     }
576 
577     /**
578      * Checks the location error.
579      */
checkLocationOrThrow(int location, @NonNull String label)580     public static void checkLocationOrThrow(int location, @NonNull String label) {
581         if (location < 0) {
582             throw new IllegalStateException("Unable to locate '" + label + "' in program");
583         }
584     }
585 
586     /**
587      * Checks the egl error and throw.
588      */
checkEglErrorOrThrow(@onNull String op)589     public static void checkEglErrorOrThrow(@NonNull String op) {
590         int error = EGL14.eglGetError();
591         if (error != EGL14.EGL_SUCCESS) {
592             throw new IllegalStateException(op + ": EGL error: 0x" + Integer.toHexString(error));
593         }
594     }
595 
596     /**
597      * Checks the gl error and throw.
598      */
checkGlErrorOrThrow(@onNull String op)599     public static void checkGlErrorOrThrow(@NonNull String op) {
600         int error = GLES20.glGetError();
601         if (error != GLES20.GL_NO_ERROR) {
602             throw new IllegalStateException(op + ": GL error 0x" + Integer.toHexString(error));
603         }
604     }
605 
606     /**
607      * Checks the egl error and log.
608      */
checkEglErrorOrLog(@onNull String op)609     public static void checkEglErrorOrLog(@NonNull String op) {
610         try {
611             checkEglErrorOrThrow(op);
612         } catch (IllegalStateException e) {
613             Logger.e(TAG, e.toString(), e);
614         }
615     }
616 
617     /**
618      * Checks the initialization status.
619      */
checkInitializedOrThrow(@onNull AtomicBoolean initialized, boolean shouldInitialized)620     public static void checkInitializedOrThrow(@NonNull AtomicBoolean initialized,
621             boolean shouldInitialized) {
622         boolean result = shouldInitialized == initialized.get();
623         String message = shouldInitialized ? "OpenGlRenderer is not initialized"
624                 : "OpenGlRenderer is already initialized";
625         Preconditions.checkState(result, message);
626     }
627 
628     /**
629      * Checks the gl thread.
630      */
checkGlThreadOrThrow(@ullable Thread thread)631     public static void checkGlThreadOrThrow(@Nullable Thread thread) {
632         Preconditions.checkState(thread == Thread.currentThread(),
633                 "Method call must be called on the GL thread.");
634     }
635 
636     /**
637      * Gets the gl version number.
638      */
getGlVersionNumber()639     public static @NonNull String getGlVersionNumber() {
640         // Logic adapted from CTS Egl14Utils:
641         // https://cs.android.com/android/platform/superproject/+/master:cts/tests/tests/opengl/src/android/opengl/cts/Egl14Utils.java;l=46;drc=1c705168ab5118c42e5831cd84871d51ff5176d1
642         String glVersion = GLES20.glGetString(GLES20.GL_VERSION);
643         Pattern pattern = Pattern.compile("OpenGL ES ([0-9]+)\\.([0-9]+).*");
644         Matcher matcher = pattern.matcher(glVersion);
645         if (matcher.find()) {
646             String major = Preconditions.checkNotNull(matcher.group(1));
647             String minor = Preconditions.checkNotNull(matcher.group(2));
648             return major + "." + minor;
649         }
650         return VERSION_UNKNOWN;
651     }
652 
653     /**
654      * Chooses the surface attributes for HDR 10bit.
655      */
chooseSurfaceAttrib(@onNull String eglExtensions, @NonNull DynamicRange dynamicRange)656     public static int @NonNull [] chooseSurfaceAttrib(@NonNull String eglExtensions,
657             @NonNull DynamicRange dynamicRange) {
658         int[] attribs = EMPTY_ATTRIBS;
659         if (dynamicRange.getEncoding() == DynamicRange.ENCODING_HLG) {
660             if (eglExtensions.contains("EGL_EXT_gl_colorspace_bt2020_hlg")) {
661                 attribs = HLG_SURFACE_ATTRIBS;
662             } else {
663                 Logger.w(TAG, "Dynamic range uses HLG encoding, but "
664                         + "device does not support EGL_EXT_gl_colorspace_bt2020_hlg."
665                         + "Fallback to default colorspace.");
666             }
667         }
668         // TODO(b/303675500): Add path for PQ (EGL_EXT_gl_colorspace_bt2020_pq) output for
669         //  HDR10/HDR10+
670         return attribs;
671     }
672 
673     /**
674      * Generates framebuffer object.
675      */
generateFbo()676     public static int generateFbo() {
677         int[] fbos = new int[1];
678         GLES20.glGenFramebuffers(1, fbos, 0);
679         checkGlErrorOrThrow("glGenFramebuffers");
680         return fbos[0];
681     }
682 
683     /**
684      * Generates texture.
685      */
generateTexture()686     public static int generateTexture() {
687         int[] textures = new int[1];
688         GLES20.glGenTextures(1, textures, 0);
689         checkGlErrorOrThrow("glGenTextures");
690         return textures[0];
691     }
692 
693     /**
694      * Deletes texture.
695      */
deleteTexture(int texture)696     public static void deleteTexture(int texture) {
697         int[] textures = {texture};
698         GLES20.glDeleteTextures(1, textures, 0);
699         checkGlErrorOrThrow("glDeleteTextures");
700     }
701 
702     /**
703      * Deletes framebuffer object.
704      */
deleteFbo(int fbo)705     public static void deleteFbo(int fbo) {
706         int[] fbos = {fbo};
707         GLES20.glDeleteFramebuffers(1, fbos, 0);
708         checkGlErrorOrThrow("glDeleteFramebuffers");
709     }
710 
getFragmentShaderSource(@onNull ShaderProvider shaderProvider)711     private static String getFragmentShaderSource(@NonNull ShaderProvider shaderProvider) {
712         // Throw IllegalArgumentException if the shader provider can not provide a valid
713         // fragment shader.
714         try {
715             String source = shaderProvider.createFragmentShader(VAR_TEXTURE, VAR_TEXTURE_COORD);
716             // A simple check to workaround custom shader doesn't contain required variable.
717             // See b/241193761.
718             if (source == null || !source.contains(VAR_TEXTURE_COORD) || !source.contains(
719                     VAR_TEXTURE)) {
720                 throw new IllegalArgumentException("Invalid fragment shader");
721             }
722             return source;
723         } catch (Throwable t) {
724             if (t instanceof IllegalArgumentException) {
725                 throw t;
726             }
727             throw new IllegalArgumentException("Unable retrieve fragment shader source", t);
728         }
729     }
730 }
731