1 /* 2 * Copyright 2018 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 package org.webrtc; 12 13 import android.opengl.GLES11Ext; 14 import android.opengl.GLES20; 15 import androidx.annotation.Nullable; 16 import java.nio.FloatBuffer; 17 18 /** 19 * Helper class to implement an instance of RendererCommon.GlDrawer that can accept multiple input 20 * sources (OES, RGB, or YUV) using a generic fragment shader as input. The generic fragment shader 21 * should sample pixel values from the function "sample" that will be provided by this class and 22 * provides an abstraction for the input source type (OES, RGB, or YUV). The texture coordinate 23 * variable name will be "tc" and the texture matrix in the vertex shader will be "tex_mat". The 24 * simplest possible generic shader that just draws pixel from the frame unmodified looks like: 25 * void main() { 26 * gl_FragColor = sample(tc); 27 * } 28 * This class covers the cases for most simple shaders and generates the necessary boiler plate. 29 * Advanced shaders can always implement RendererCommon.GlDrawer directly. 30 */ 31 class GlGenericDrawer implements RendererCommon.GlDrawer { 32 /** 33 * The different shader types representing different input sources. YUV here represents three 34 * separate Y, U, V textures. 35 */ 36 public static enum ShaderType { OES, RGB, YUV } 37 38 /** 39 * The shader callbacks is used to customize behavior for a GlDrawer. It provides a hook to set 40 * uniform variables in the shader before a frame is drawn. 41 */ 42 public static interface ShaderCallbacks { 43 /** 44 * This callback is called when a new shader has been compiled and created. It will be called 45 * for the first frame as well as when the shader type is changed. This callback can be used to 46 * do custom initialization of the shader that only needs to happen once. 47 */ onNewShader(GlShader shader)48 void onNewShader(GlShader shader); 49 50 /** 51 * This callback is called before rendering a frame. It can be used to do custom preparation of 52 * the shader that needs to happen every frame. 53 */ onPrepareShader(GlShader shader, float[] texMatrix, int frameWidth, int frameHeight, int viewportWidth, int viewportHeight)54 void onPrepareShader(GlShader shader, float[] texMatrix, int frameWidth, int frameHeight, 55 int viewportWidth, int viewportHeight); 56 } 57 58 private static final String INPUT_VERTEX_COORDINATE_NAME = "in_pos"; 59 private static final String INPUT_TEXTURE_COORDINATE_NAME = "in_tc"; 60 private static final String TEXTURE_MATRIX_NAME = "tex_mat"; 61 private static final String DEFAULT_VERTEX_SHADER_STRING = "varying vec2 tc;\n" 62 + "attribute vec4 in_pos;\n" 63 + "attribute vec4 in_tc;\n" 64 + "uniform mat4 tex_mat;\n" 65 + "void main() {\n" 66 + " gl_Position = in_pos;\n" 67 + " tc = (tex_mat * in_tc).xy;\n" 68 + "}\n"; 69 70 // Vertex coordinates in Normalized Device Coordinates, i.e. (-1, -1) is bottom-left and (1, 1) 71 // is top-right. 72 private static final FloatBuffer FULL_RECTANGLE_BUFFER = GlUtil.createFloatBuffer(new float[] { 73 -1.0f, -1.0f, // Bottom left. 74 1.0f, -1.0f, // Bottom right. 75 -1.0f, 1.0f, // Top left. 76 1.0f, 1.0f, // Top right. 77 }); 78 79 // Texture coordinates - (0, 0) is bottom-left and (1, 1) is top-right. 80 private static final FloatBuffer FULL_RECTANGLE_TEXTURE_BUFFER = 81 GlUtil.createFloatBuffer(new float[] { 82 0.0f, 0.0f, // Bottom left. 83 1.0f, 0.0f, // Bottom right. 84 0.0f, 1.0f, // Top left. 85 1.0f, 1.0f, // Top right. 86 }); 87 createFragmentShaderString(String genericFragmentSource, ShaderType shaderType)88 static String createFragmentShaderString(String genericFragmentSource, ShaderType shaderType) { 89 final StringBuilder stringBuilder = new StringBuilder(); 90 if (shaderType == ShaderType.OES) { 91 stringBuilder.append("#extension GL_OES_EGL_image_external : require\n"); 92 } 93 stringBuilder.append("precision mediump float;\n"); 94 stringBuilder.append("varying vec2 tc;\n"); 95 96 if (shaderType == ShaderType.YUV) { 97 stringBuilder.append("uniform sampler2D y_tex;\n"); 98 stringBuilder.append("uniform sampler2D u_tex;\n"); 99 stringBuilder.append("uniform sampler2D v_tex;\n"); 100 101 // Add separate function for sampling texture. 102 // yuv_to_rgb_mat is inverse of the matrix defined in YuvConverter. 103 stringBuilder.append("vec4 sample(vec2 p) {\n"); 104 stringBuilder.append(" float y = texture2D(y_tex, p).r * 1.16438;\n"); 105 stringBuilder.append(" float u = texture2D(u_tex, p).r;\n"); 106 stringBuilder.append(" float v = texture2D(v_tex, p).r;\n"); 107 stringBuilder.append(" return vec4(y + 1.59603 * v - 0.874202,\n"); 108 stringBuilder.append(" y - 0.391762 * u - 0.812968 * v + 0.531668,\n"); 109 stringBuilder.append(" y + 2.01723 * u - 1.08563, 1);\n"); 110 stringBuilder.append("}\n"); 111 stringBuilder.append(genericFragmentSource); 112 } else { 113 final String samplerName = shaderType == ShaderType.OES ? "samplerExternalOES" : "sampler2D"; 114 stringBuilder.append("uniform ").append(samplerName).append(" tex;\n"); 115 116 // Update the sampling function in-place. 117 stringBuilder.append(genericFragmentSource.replace("sample(", "texture2D(tex, ")); 118 } 119 120 return stringBuilder.toString(); 121 } 122 123 private final String genericFragmentSource; 124 private final String vertexShader; 125 private final ShaderCallbacks shaderCallbacks; 126 @Nullable private ShaderType currentShaderType; 127 @Nullable private GlShader currentShader; 128 private int inPosLocation; 129 private int inTcLocation; 130 private int texMatrixLocation; 131 GlGenericDrawer(String genericFragmentSource, ShaderCallbacks shaderCallbacks)132 public GlGenericDrawer(String genericFragmentSource, ShaderCallbacks shaderCallbacks) { 133 this(DEFAULT_VERTEX_SHADER_STRING, genericFragmentSource, shaderCallbacks); 134 } 135 GlGenericDrawer( String vertexShader, String genericFragmentSource, ShaderCallbacks shaderCallbacks)136 public GlGenericDrawer( 137 String vertexShader, String genericFragmentSource, ShaderCallbacks shaderCallbacks) { 138 this.vertexShader = vertexShader; 139 this.genericFragmentSource = genericFragmentSource; 140 this.shaderCallbacks = shaderCallbacks; 141 } 142 143 // Visible for testing. createShader(ShaderType shaderType)144 GlShader createShader(ShaderType shaderType) { 145 return new GlShader( 146 vertexShader, createFragmentShaderString(genericFragmentSource, shaderType)); 147 } 148 149 /** 150 * Draw an OES texture frame with specified texture transformation matrix. Required resources are 151 * allocated at the first call to this function. 152 */ 153 @Override drawOes(int oesTextureId, float[] texMatrix, int frameWidth, int frameHeight, int viewportX, int viewportY, int viewportWidth, int viewportHeight)154 public void drawOes(int oesTextureId, float[] texMatrix, int frameWidth, int frameHeight, 155 int viewportX, int viewportY, int viewportWidth, int viewportHeight) { 156 prepareShader( 157 ShaderType.OES, texMatrix, frameWidth, frameHeight, viewportWidth, viewportHeight); 158 // Bind the texture. 159 GLES20.glActiveTexture(GLES20.GL_TEXTURE0); 160 GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, oesTextureId); 161 // Draw the texture. 162 GLES20.glViewport(viewportX, viewportY, viewportWidth, viewportHeight); 163 GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); 164 // Unbind the texture as a precaution. 165 GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0); 166 } 167 168 /** 169 * Draw a RGB(A) texture frame with specified texture transformation matrix. Required resources 170 * are allocated at the first call to this function. 171 */ 172 @Override drawRgb(int textureId, float[] texMatrix, int frameWidth, int frameHeight, int viewportX, int viewportY, int viewportWidth, int viewportHeight)173 public void drawRgb(int textureId, float[] texMatrix, int frameWidth, int frameHeight, 174 int viewportX, int viewportY, int viewportWidth, int viewportHeight) { 175 prepareShader( 176 ShaderType.RGB, texMatrix, frameWidth, frameHeight, viewportWidth, viewportHeight); 177 // Bind the texture. 178 GLES20.glActiveTexture(GLES20.GL_TEXTURE0); 179 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId); 180 // Draw the texture. 181 GLES20.glViewport(viewportX, viewportY, viewportWidth, viewportHeight); 182 GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); 183 // Unbind the texture as a precaution. 184 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); 185 } 186 187 /** 188 * Draw a YUV frame with specified texture transformation matrix. Required resources are allocated 189 * at the first call to this function. 190 */ 191 @Override drawYuv(int[] yuvTextures, float[] texMatrix, int frameWidth, int frameHeight, int viewportX, int viewportY, int viewportWidth, int viewportHeight)192 public void drawYuv(int[] yuvTextures, float[] texMatrix, int frameWidth, int frameHeight, 193 int viewportX, int viewportY, int viewportWidth, int viewportHeight) { 194 prepareShader( 195 ShaderType.YUV, texMatrix, frameWidth, frameHeight, viewportWidth, viewportHeight); 196 // Bind the textures. 197 for (int i = 0; i < 3; ++i) { 198 GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i); 199 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[i]); 200 } 201 // Draw the textures. 202 GLES20.glViewport(viewportX, viewportY, viewportWidth, viewportHeight); 203 GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); 204 // Unbind the textures as a precaution. 205 for (int i = 0; i < 3; ++i) { 206 GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i); 207 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); 208 } 209 } 210 prepareShader(ShaderType shaderType, float[] texMatrix, int frameWidth, int frameHeight, int viewportWidth, int viewportHeight)211 private void prepareShader(ShaderType shaderType, float[] texMatrix, int frameWidth, 212 int frameHeight, int viewportWidth, int viewportHeight) { 213 final GlShader shader; 214 if (shaderType.equals(currentShaderType)) { 215 // Same shader type as before, reuse exising shader. 216 shader = currentShader; 217 } else { 218 // Allocate new shader. 219 currentShaderType = shaderType; 220 if (currentShader != null) { 221 currentShader.release(); 222 } 223 shader = createShader(shaderType); 224 currentShader = shader; 225 226 shader.useProgram(); 227 // Set input texture units. 228 if (shaderType == ShaderType.YUV) { 229 GLES20.glUniform1i(shader.getUniformLocation("y_tex"), 0); 230 GLES20.glUniform1i(shader.getUniformLocation("u_tex"), 1); 231 GLES20.glUniform1i(shader.getUniformLocation("v_tex"), 2); 232 } else { 233 GLES20.glUniform1i(shader.getUniformLocation("tex"), 0); 234 } 235 236 GlUtil.checkNoGLES2Error("Create shader"); 237 shaderCallbacks.onNewShader(shader); 238 texMatrixLocation = shader.getUniformLocation(TEXTURE_MATRIX_NAME); 239 inPosLocation = shader.getAttribLocation(INPUT_VERTEX_COORDINATE_NAME); 240 inTcLocation = shader.getAttribLocation(INPUT_TEXTURE_COORDINATE_NAME); 241 } 242 243 shader.useProgram(); 244 245 // Upload the vertex coordinates. 246 GLES20.glEnableVertexAttribArray(inPosLocation); 247 GLES20.glVertexAttribPointer(inPosLocation, /* size= */ 2, 248 /* type= */ GLES20.GL_FLOAT, /* normalized= */ false, /* stride= */ 0, 249 FULL_RECTANGLE_BUFFER); 250 251 // Upload the texture coordinates. 252 GLES20.glEnableVertexAttribArray(inTcLocation); 253 GLES20.glVertexAttribPointer(inTcLocation, /* size= */ 2, 254 /* type= */ GLES20.GL_FLOAT, /* normalized= */ false, /* stride= */ 0, 255 FULL_RECTANGLE_TEXTURE_BUFFER); 256 257 // Upload the texture transformation matrix. 258 GLES20.glUniformMatrix4fv( 259 texMatrixLocation, 1 /* count= */, false /* transpose= */, texMatrix, 0 /* offset= */); 260 261 // Do custom per-frame shader preparation. 262 shaderCallbacks.onPrepareShader( 263 shader, texMatrix, frameWidth, frameHeight, viewportWidth, viewportHeight); 264 GlUtil.checkNoGLES2Error("Prepare shader"); 265 } 266 267 /** 268 * Release all GLES resources. This needs to be done manually, otherwise the resources are leaked. 269 */ 270 @Override release()271 public void release() { 272 if (currentShader != null) { 273 currentShader.release(); 274 currentShader = null; 275 currentShaderType = null; 276 } 277 } 278 } 279