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