• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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