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