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