1 /*
2  * Copyright (C) 2012 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.media.filterfw;
18 
19 import android.graphics.RectF;
20 import android.opengl.GLES20;
21 import android.util.Log;
22 
23 import androidx.media.filterfw.geometry.Quad;
24 
25 import java.nio.ByteBuffer;
26 import java.nio.ByteOrder;
27 import java.nio.FloatBuffer;
28 import java.util.Arrays;
29 import java.util.HashMap;
30 
31 /**
32  * Convenience class to perform GL shader operations on image data.
33  * <p>
34  * The ImageShader class greatly simplifies the task of running GL shader language kernels over
35  * Frame data buffers that contain RGBA image data.
36  * </p><p>
37  * TODO: More documentation
38  * </p>
39  */
40 public class ImageShader {
41 
42     private int mProgram = 0;
43     private boolean mClearsOutput = false;
44     private float[] mClearColor = { 0f, 0f, 0f, 0f };
45     private boolean mBlendEnabled = false;
46     private int mSFactor = GLES20.GL_SRC_ALPHA;
47     private int mDFactor = GLES20.GL_ONE_MINUS_SRC_ALPHA;
48     private int mDrawMode = GLES20.GL_TRIANGLE_STRIP;
49     private int mVertexCount = 4;
50     private int mBaseTexUnit = GLES20.GL_TEXTURE0;
51     private int mClearBuffers = GLES20.GL_COLOR_BUFFER_BIT;
52     private float[] mSourceCoords = new float[] { 0f, 0f, 1f, 0f, 0f, 1f, 1f, 1f };
53     private float[] mTargetCoords = new float[] { -1f, -1f, 1f, -1f, -1f, 1f, 1f, 1f };
54 
55     private HashMap<String, ProgramUniform> mUniforms;
56     private HashMap<String, VertexAttribute> mAttributes = new HashMap<String, VertexAttribute>();
57 
58     private final static int FLOAT_SIZE = 4;
59 
60     private final static String mDefaultVertexShader =
61         "attribute vec4 a_position;\n" +
62         "attribute vec2 a_texcoord;\n" +
63         "varying vec2 v_texcoord;\n" +
64         "void main() {\n" +
65         "  gl_Position = a_position;\n" +
66         "  v_texcoord = a_texcoord;\n" +
67         "}\n";
68 
69     private final static String mIdentityShader =
70         "precision mediump float;\n" +
71         "uniform sampler2D tex_sampler_0;\n" +
72         "varying vec2 v_texcoord;\n" +
73         "void main() {\n" +
74         "  gl_FragColor = texture2D(tex_sampler_0, v_texcoord);\n" +
75         "}\n";
76 
77     private static class VertexAttribute {
78         private String mName;
79         private boolean mIsConst;
80         private int mIndex;
81         private boolean mShouldNormalize;
82         private int mOffset;
83         private int mStride;
84         private int mComponents;
85         private int mType;
86         private int mVbo;
87         private int mLength;
88         private FloatBuffer mValues;
89 
VertexAttribute(String name, int index)90         public VertexAttribute(String name, int index) {
91             mName = name;
92             mIndex = index;
93             mLength = -1;
94         }
95 
set(boolean normalize, int stride, int components, int type, float[] values)96         public void set(boolean normalize, int stride, int components, int type, float[] values) {
97             mIsConst = false;
98             mShouldNormalize = normalize;
99             mStride = stride;
100             mComponents = components;
101             mType = type;
102             mVbo = 0;
103             if (mLength != values.length){
104                 initBuffer(values);
105                 mLength = values.length;
106             }
107             copyValues(values);
108         }
109 
set(boolean normalize, int offset, int stride, int components, int type, int vbo)110         public void set(boolean normalize, int offset, int stride, int components, int type,
111                 int vbo){
112             mIsConst = false;
113             mShouldNormalize = normalize;
114             mOffset = offset;
115             mStride = stride;
116             mComponents = components;
117             mType = type;
118             mVbo = vbo;
119             mValues = null;
120         }
121 
push()122         public boolean push() {
123             if (mIsConst) {
124                 switch (mComponents) {
125                     case 1:
126                         GLES20.glVertexAttrib1fv(mIndex, mValues);
127                         break;
128                     case 2:
129                         GLES20.glVertexAttrib2fv(mIndex, mValues);
130                         break;
131                     case 3:
132                         GLES20.glVertexAttrib3fv(mIndex, mValues);
133                         break;
134                     case 4:
135                         GLES20.glVertexAttrib4fv(mIndex, mValues);
136                         break;
137                     default:
138                         return false;
139                 }
140                 GLES20.glDisableVertexAttribArray(mIndex);
141             } else {
142                 if (mValues != null) {
143                     // Note that we cannot do any size checking here, as the correct component
144                     // count depends on the drawing step. GL should catch such errors then, and
145                     // we will report them to the user.
146                     GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
147                     GLES20.glVertexAttribPointer(mIndex,
148                                                  mComponents,
149                                                  mType,
150                                                  mShouldNormalize,
151                                                  mStride,
152                                                  mValues);
153                 } else {
154                     GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVbo);
155                     GLES20.glVertexAttribPointer(mIndex,
156                                                  mComponents,
157                                                  mType,
158                                                  mShouldNormalize,
159                                                  mStride,
160                                                  mOffset);
161                 }
162                 GLES20.glEnableVertexAttribArray(mIndex);
163             }
164             GLToolbox.checkGlError("Set vertex-attribute values");
165             return true;
166         }
167 
168         @Override
toString()169         public String toString() {
170             return mName;
171         }
172 
initBuffer(float[] values)173         private void initBuffer(float[] values) {
174             mValues = ByteBuffer.allocateDirect(values.length * FLOAT_SIZE)
175                 .order(ByteOrder.nativeOrder()).asFloatBuffer();
176         }
177 
copyValues(float[] values)178         private void copyValues(float[] values) {
179             mValues.put(values).position(0);
180         }
181 
182     }
183 
184     private static final class ProgramUniform {
185         private String mName;
186         private int mLocation;
187         private int mType;
188         private int mSize;
189 
ProgramUniform(int program, int index)190         public ProgramUniform(int program, int index) {
191             int[] len = new int[1];
192             GLES20.glGetProgramiv(program, GLES20.GL_ACTIVE_UNIFORM_MAX_LENGTH, len, 0);
193 
194             int[] type = new int[1];
195             int[] size = new int[1];
196             byte[] name = new byte[len[0]];
197             int[] ignore = new int[1];
198 
199             GLES20.glGetActiveUniform(program, index, len[0], ignore, 0, size, 0, type, 0, name, 0);
200             mName = new String(name, 0, strlen(name));
201             mLocation = GLES20.glGetUniformLocation(program, mName);
202             mType = type[0];
203             mSize = size[0];
204             GLToolbox.checkGlError("Initializing uniform");
205         }
206 
getName()207         public String getName() {
208             return mName;
209         }
210 
getType()211         public int getType() {
212             return mType;
213         }
214 
getLocation()215         public int getLocation() {
216             return mLocation;
217         }
218 
getSize()219         public int getSize() {
220             return mSize;
221         }
222     }
223 
ImageShader(String fragmentShader)224     public ImageShader(String fragmentShader) {
225         mProgram = createProgram(mDefaultVertexShader, fragmentShader);
226         scanUniforms();
227     }
228 
ImageShader(String vertexShader, String fragmentShader)229     public ImageShader(String vertexShader, String fragmentShader) {
230         mProgram = createProgram(vertexShader, fragmentShader);
231         scanUniforms();
232     }
233 
createIdentity()234     public static ImageShader createIdentity() {
235         return new ImageShader(mIdentityShader);
236     }
237 
createIdentity(String vertexShader)238     public static ImageShader createIdentity(String vertexShader) {
239         return new ImageShader(vertexShader, mIdentityShader);
240     }
241 
renderTextureToTarget(TextureSource texture, RenderTarget target, int width, int height)242     public static void renderTextureToTarget(TextureSource texture,
243                                              RenderTarget target,
244                                              int width,
245                                              int height) {
246         ImageShader shader = RenderTarget.currentTarget().getIdentityShader();
247         shader.process(texture, target, width, height);
248     }
249 
process(FrameImage2D input, FrameImage2D output)250     public void process(FrameImage2D input, FrameImage2D output) {
251         TextureSource texSource = input.lockTextureSource();
252         RenderTarget renderTarget = output.lockRenderTarget();
253         processMulti(new TextureSource[] { texSource },
254                      renderTarget,
255                      output.getWidth(),
256                      output.getHeight());
257         input.unlock();
258         output.unlock();
259     }
260 
processMulti(FrameImage2D[] inputs, FrameImage2D output)261     public void processMulti(FrameImage2D[] inputs, FrameImage2D output) {
262         TextureSource[] texSources = new TextureSource[inputs.length];
263         for (int i = 0; i < inputs.length; ++i) {
264             texSources[i] = inputs[i].lockTextureSource();
265         }
266         RenderTarget renderTarget = output.lockRenderTarget();
267         processMulti(texSources,
268                      renderTarget,
269                      output.getWidth(),
270                      output.getHeight());
271         for (FrameImage2D input : inputs) {
272             input.unlock();
273         }
274         output.unlock();
275     }
276 
process(TextureSource texture, RenderTarget target, int width, int height)277     public void process(TextureSource texture, RenderTarget target, int width, int height) {
278         processMulti(new TextureSource[] { texture }, target, width, height);
279     }
280 
processMulti(TextureSource[] sources, RenderTarget target, int width, int height)281     public void processMulti(TextureSource[] sources, RenderTarget target, int width, int height) {
282         GLToolbox.checkGlError("Unknown Operation");
283         checkExecutable();
284         checkTexCount(sources.length);
285         focusTarget(target, width, height);
286         pushShaderState();
287         bindInputTextures(sources);
288         render();
289     }
290 
processNoInput(FrameImage2D output)291     public void processNoInput(FrameImage2D output) {
292         RenderTarget renderTarget = output.lockRenderTarget();
293         processNoInput(renderTarget, output.getWidth(), output.getHeight());
294         output.unlock();
295     }
296 
processNoInput(RenderTarget target, int width, int height)297     public void processNoInput(RenderTarget target, int width, int height) {
298         processMulti(new TextureSource[] {}, target, width, height);
299     }
300 
getUniformLocation(String name)301     public int getUniformLocation(String name) {
302         return getProgramUniform(name, true).getLocation();
303     }
304 
getAttributeLocation(String name)305     public int getAttributeLocation(String name) {
306         if (name.equals(positionAttributeName()) || name.equals(texCoordAttributeName())) {
307             Log.w("ImageShader", "Attempting to access internal attribute '" + name
308                 + "' directly!");
309         }
310         int loc = GLES20.glGetAttribLocation(mProgram, name);
311         if (loc < 0) {
312             throw new RuntimeException("Unknown attribute '" + name + "' in shader program!");
313         }
314         return loc;
315     }
316 
setUniformValue(String uniformName, int value)317     public void setUniformValue(String uniformName, int value) {
318         useProgram();
319         int uniformHandle = getUniformLocation(uniformName);
320         GLES20.glUniform1i(uniformHandle, value);
321         GLToolbox.checkGlError("Set uniform value (" + uniformName + ")");
322     }
323 
setUniformValue(String uniformName, float value)324     public void setUniformValue(String uniformName, float value) {
325         useProgram();
326         int uniformHandle = getUniformLocation(uniformName);
327         GLES20.glUniform1f(uniformHandle, value);
328         GLToolbox.checkGlError("Set uniform value (" + uniformName + ")");
329     }
330 
setUniformValue(String uniformName, int[] values)331     public void setUniformValue(String uniformName, int[] values) {
332         ProgramUniform uniform = getProgramUniform(uniformName, true);
333         useProgram();
334         int len = values.length;
335         switch (uniform.getType()) {
336             case GLES20.GL_INT:
337                 checkUniformAssignment(uniform, len, 1);
338                 GLES20.glUniform1iv(uniform.getLocation(), len, values, 0);
339                 break;
340             case GLES20.GL_INT_VEC2:
341                 checkUniformAssignment(uniform, len, 2);
342                 GLES20.glUniform2iv(uniform.getLocation(), len / 2, values, 0);
343                 break;
344             case GLES20.GL_INT_VEC3:
345                 checkUniformAssignment(uniform, len, 3);
346                 GLES20.glUniform2iv(uniform.getLocation(), len / 3, values, 0);
347                 break;
348             case GLES20.GL_INT_VEC4:
349                 checkUniformAssignment(uniform, len, 4);
350                 GLES20.glUniform2iv(uniform.getLocation(), len / 4, values, 0);
351                 break;
352             default:
353                 throw new RuntimeException("Cannot assign int-array to incompatible uniform type "
354                     + "for uniform '" + uniformName + "'!");
355         }
356         GLToolbox.checkGlError("Set uniform value (" + uniformName + ")");
357     }
358 
359 
setUniformValue(String uniformName, float[] values)360     public void setUniformValue(String uniformName, float[] values) {
361         ProgramUniform uniform = getProgramUniform(uniformName, true);
362         useProgram();
363         int len = values.length;
364         switch (uniform.getType()) {
365             case GLES20.GL_FLOAT:
366                 checkUniformAssignment(uniform, len, 1);
367                 GLES20.glUniform1fv(uniform.getLocation(), len, values, 0);
368                 break;
369             case GLES20.GL_FLOAT_VEC2:
370                 checkUniformAssignment(uniform, len, 2);
371                 GLES20.glUniform2fv(uniform.getLocation(), len / 2, values, 0);
372                 break;
373             case GLES20.GL_FLOAT_VEC3:
374                 checkUniformAssignment(uniform, len, 3);
375                 GLES20.glUniform3fv(uniform.getLocation(), len / 3, values, 0);
376                 break;
377             case GLES20.GL_FLOAT_VEC4:
378                 checkUniformAssignment(uniform, len, 4);
379                 GLES20.glUniform4fv(uniform.getLocation(), len / 4, values, 0);
380                 break;
381             case GLES20.GL_FLOAT_MAT2:
382                 checkUniformAssignment(uniform, len, 4);
383                 GLES20.glUniformMatrix2fv(uniform.getLocation(), len / 4, false, values, 0);
384                 break;
385             case GLES20.GL_FLOAT_MAT3:
386                 checkUniformAssignment(uniform, len, 9);
387                 GLES20.glUniformMatrix3fv(uniform.getLocation(), len / 9, false, values, 0);
388                 break;
389             case GLES20.GL_FLOAT_MAT4:
390                 checkUniformAssignment(uniform, len, 16);
391                 GLES20.glUniformMatrix4fv(uniform.getLocation(), len / 16, false, values, 0);
392                 break;
393             default:
394                 throw new RuntimeException("Cannot assign float-array to incompatible uniform type "
395                     + "for uniform '" + uniformName + "'!");
396         }
397         GLToolbox.checkGlError("Set uniform value (" + uniformName + ")");
398     }
399 
setAttributeValues(String attributeName, float[] data, int components)400     public void setAttributeValues(String attributeName, float[] data, int components) {
401         VertexAttribute attr = getProgramAttribute(attributeName, true);
402         attr.set(false, FLOAT_SIZE * components, components, GLES20.GL_FLOAT, data);
403     }
404 
setAttributeValues(String attributeName, int vbo, int type, int components, int stride, int offset, boolean normalize)405     public void setAttributeValues(String attributeName, int vbo, int type, int components,
406                                    int stride, int offset, boolean normalize) {
407         VertexAttribute attr = getProgramAttribute(attributeName, true);
408         attr.set(normalize, offset, stride, components, type, vbo);
409     }
410 
setSourceRect(float x, float y, float width, float height)411     public void setSourceRect(float x, float y, float width, float height) {
412         setSourceCoords(new float[] { x, y, x + width, y, x, y + height, x + width, y + height });
413     }
414 
setSourceRect(RectF rect)415     public void setSourceRect(RectF rect) {
416         setSourceRect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
417     }
418 
setSourceQuad(Quad quad)419     public void setSourceQuad(Quad quad) {
420         setSourceCoords(new float[] { quad.topLeft().x,     quad.topLeft().y,
421                                       quad.topRight().x,    quad.topRight().y,
422                                       quad.bottomLeft().x,  quad.bottomLeft().y,
423                                       quad.bottomRight().x, quad.bottomRight().y });
424     }
425 
setSourceCoords(float[] coords)426     public void setSourceCoords(float[] coords) {
427         if (coords.length != 8) {
428             throw new IllegalArgumentException("Expected 8 coordinates as source coordinates but "
429                 + "got " + coords.length + " coordinates!");
430         }
431         mSourceCoords = Arrays.copyOf(coords, 8);
432     }
433 
setSourceTransform(float[] matrix)434     public void setSourceTransform(float[] matrix) {
435         if (matrix.length != 16) {
436             throw new IllegalArgumentException("Expected 4x4 matrix for source transform!");
437         }
438         setSourceCoords(new float[] {
439             matrix[12],
440             matrix[13],
441 
442             matrix[0] + matrix[12],
443             matrix[1] + matrix[13],
444 
445             matrix[4] + matrix[12],
446             matrix[5] + matrix[13],
447 
448             matrix[0] + matrix[4] + matrix[12],
449             matrix[1] + matrix[5] + matrix[13],
450         });
451     }
452 
setTargetRect(float x, float y, float width, float height)453     public void setTargetRect(float x, float y, float width, float height) {
454         setTargetCoords(new float[] { x, y,
455                                       x + width, y,
456                                       x, y + height,
457                                       x + width, y + height });
458     }
459 
setTargetRect(RectF rect)460     public void setTargetRect(RectF rect) {
461         setTargetCoords(new float[] { rect.left,    rect.top,
462                                       rect.right,   rect.top,
463                                       rect.left,    rect.bottom,
464                                       rect.right,   rect.bottom });
465     }
466 
setTargetQuad(Quad quad)467     public void setTargetQuad(Quad quad) {
468         setTargetCoords(new float[] { quad.topLeft().x,     quad.topLeft().y,
469                                       quad.topRight().x,    quad.topRight().y,
470                                       quad.bottomLeft().x,  quad.bottomLeft().y,
471                                       quad.bottomRight().x, quad.bottomRight().y });
472     }
473 
setTargetCoords(float[] coords)474     public void setTargetCoords(float[] coords) {
475         if (coords.length != 8) {
476             throw new IllegalArgumentException("Expected 8 coordinates as target coordinates but "
477                 + "got " + coords.length + " coordinates!");
478         }
479         mTargetCoords = new float[8];
480         for (int i = 0; i < 8; ++i) {
481             mTargetCoords[i] = coords[i] * 2f - 1f;
482         }
483     }
484 
setTargetTransform(float[] matrix)485     public void setTargetTransform(float[] matrix) {
486         if (matrix.length != 16) {
487             throw new IllegalArgumentException("Expected 4x4 matrix for target transform!");
488         }
489         setTargetCoords(new float[] {
490             matrix[12],
491             matrix[13],
492 
493             matrix[0] + matrix[12],
494             matrix[1] + matrix[13],
495 
496             matrix[4] + matrix[12],
497             matrix[5] + matrix[13],
498 
499             matrix[0] + matrix[4] + matrix[12],
500             matrix[1] + matrix[5] + matrix[13],
501         });
502     }
503 
setClearsOutput(boolean clears)504     public void setClearsOutput(boolean clears) {
505         mClearsOutput = clears;
506     }
507 
getClearsOutput()508     public boolean getClearsOutput() {
509         return mClearsOutput;
510     }
511 
setClearColor(float[] rgba)512     public void setClearColor(float[] rgba) {
513         mClearColor = rgba;
514     }
515 
getClearColor()516     public float[] getClearColor() {
517         return mClearColor;
518     }
519 
setClearBufferMask(int bufferMask)520     public void setClearBufferMask(int bufferMask) {
521         mClearBuffers = bufferMask;
522     }
523 
getClearBufferMask()524     public int getClearBufferMask() {
525         return mClearBuffers;
526     }
527 
setBlendEnabled(boolean enable)528     public void setBlendEnabled(boolean enable) {
529         mBlendEnabled = enable;
530     }
531 
getBlendEnabled()532     public boolean getBlendEnabled() {
533         return mBlendEnabled;
534     }
535 
setBlendFunc(int sFactor, int dFactor)536     public void setBlendFunc(int sFactor, int dFactor) {
537         mSFactor = sFactor;
538         mDFactor = dFactor;
539     }
540 
setDrawMode(int drawMode)541     public void setDrawMode(int drawMode) {
542         mDrawMode = drawMode;
543     }
544 
getDrawMode()545     public int getDrawMode() {
546         return mDrawMode;
547     }
548 
setVertexCount(int count)549     public void setVertexCount(int count) {
550         mVertexCount = count;
551     }
552 
getVertexCount()553     public int getVertexCount() {
554         return mVertexCount;
555     }
556 
setBaseTextureUnit(int baseTexUnit)557     public void setBaseTextureUnit(int baseTexUnit) {
558         mBaseTexUnit = baseTexUnit;
559     }
560 
baseTextureUnit()561     public int baseTextureUnit() {
562         return mBaseTexUnit;
563     }
564 
texCoordAttributeName()565     public String texCoordAttributeName() {
566         return "a_texcoord";
567     }
568 
positionAttributeName()569     public String positionAttributeName() {
570         return "a_position";
571     }
572 
inputTextureUniformName(int index)573     public String inputTextureUniformName(int index) {
574         return "tex_sampler_" + index;
575     }
576 
maxTextureUnits()577     public static int maxTextureUnits() {
578         return GLES20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS;
579     }
580 
581     @Override
finalize()582     protected void finalize() throws Throwable {
583         GLES20.glDeleteProgram(mProgram);
584     }
585 
pushShaderState()586     protected void pushShaderState() {
587         useProgram();
588         updateSourceCoordAttribute();
589         updateTargetCoordAttribute();
590         pushAttributes();
591         if (mClearsOutput) {
592             GLES20.glClearColor(mClearColor[0], mClearColor[1], mClearColor[2], mClearColor[3]);
593             GLES20.glClear(mClearBuffers);
594         }
595         if (mBlendEnabled) {
596             GLES20.glEnable(GLES20.GL_BLEND);
597             GLES20.glBlendFunc(mSFactor, mDFactor);
598         } else {
599             GLES20.glDisable(GLES20.GL_BLEND);
600         }
601         GLToolbox.checkGlError("Set render variables");
602     }
603 
focusTarget(RenderTarget target, int width, int height)604     private void focusTarget(RenderTarget target, int width, int height) {
605         target.focus();
606         GLES20.glViewport(0, 0, width, height);
607         GLToolbox.checkGlError("glViewport");
608     }
609 
bindInputTextures(TextureSource[] sources)610     private void bindInputTextures(TextureSource[] sources) {
611         for (int i = 0; i < sources.length; ++i) {
612             // Activate texture unit i
613             GLES20.glActiveTexture(baseTextureUnit() + i);
614 
615             // Bind texture
616             sources[i].bind();
617 
618             // Assign the texture uniform in the shader to unit i
619             int texUniform = GLES20.glGetUniformLocation(mProgram, inputTextureUniformName(i));
620             if (texUniform >= 0) {
621                 GLES20.glUniform1i(texUniform, i);
622             } else {
623                 throw new RuntimeException("Shader does not seem to support " + sources.length
624                     + " number of input textures! Missing uniform " + inputTextureUniformName(i)
625                     + "!");
626             }
627             GLToolbox.checkGlError("Binding input texture " + i);
628         }
629     }
630 
pushAttributes()631     private void pushAttributes() {
632         for (VertexAttribute attr : mAttributes.values()) {
633             if (!attr.push()) {
634                 throw new RuntimeException("Unable to assign attribute value '" + attr + "'!");
635             }
636         }
637         GLToolbox.checkGlError("Push Attributes");
638     }
639 
updateSourceCoordAttribute()640     private void updateSourceCoordAttribute() {
641         // If attribute does not exist, simply do nothing (may be custom shader).
642         VertexAttribute attr = getProgramAttribute(texCoordAttributeName(), false);
643         // A non-null value of mSourceCoords indicates new values to be set.
644         if (mSourceCoords != null && attr != null) {
645             // Upload new source coordinates to GPU
646             attr.set(false, FLOAT_SIZE * 2, 2, GLES20.GL_FLOAT, mSourceCoords);
647         }
648         // Do not set again (even if failed, to not cause endless attempts)
649         mSourceCoords = null;
650     }
651 
updateTargetCoordAttribute()652     private void updateTargetCoordAttribute() {
653         // If attribute does not exist, simply do nothing (may be custom shader).
654         VertexAttribute attr = getProgramAttribute(positionAttributeName(), false);
655         // A non-null value of mTargetCoords indicates new values to be set.
656         if (mTargetCoords != null && attr != null) {
657             // Upload new target coordinates to GPU
658             attr.set(false, FLOAT_SIZE * 2, 2, GLES20.GL_FLOAT, mTargetCoords);
659         }
660         // Do not set again (even if failed, to not cause endless attempts)
661         mTargetCoords = null;
662     }
663 
render()664     private void render() {
665         GLES20.glDrawArrays(mDrawMode, 0, mVertexCount);
666         GLToolbox.checkGlError("glDrawArrays");
667     }
668 
checkExecutable()669     private void checkExecutable() {
670         if (mProgram == 0) {
671             throw new RuntimeException("Attempting to execute invalid shader-program!");
672         }
673     }
674 
useProgram()675     private void useProgram() {
676         GLES20.glUseProgram(mProgram);
677         GLToolbox.checkGlError("glUseProgram");
678     }
679 
checkTexCount(int count)680     private static void checkTexCount(int count) {
681         if (count > maxTextureUnits()) {
682             throw new RuntimeException("Number of textures passed (" + count + ") exceeds the "
683                 + "maximum number of allowed texture units (" + maxTextureUnits() + ")!");
684         }
685     }
686 
loadShader(int shaderType, String source)687     private static int loadShader(int shaderType, String source) {
688         int shader = GLES20.glCreateShader(shaderType);
689         if (shader != 0) {
690             GLES20.glShaderSource(shader, source);
691             GLES20.glCompileShader(shader);
692             int[] compiled = new int[1];
693             GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
694             if (compiled[0] == 0) {
695                 String info = GLES20.glGetShaderInfoLog(shader);
696                 GLES20.glDeleteShader(shader);
697                 shader = 0;
698                 throw new RuntimeException("Could not compile shader " + shaderType + ":" + info);
699             }
700         }
701         return shader;
702     }
703 
createProgram(String vertexSource, String fragmentSource)704     private static int createProgram(String vertexSource, String fragmentSource) {
705         int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
706         if (vertexShader == 0) {
707             throw new RuntimeException("Could not create shader-program as vertex shader "
708                 + "could not be compiled!");
709         }
710         int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
711         if (pixelShader == 0) {
712             throw new RuntimeException("Could not create shader-program as fragment shader "
713                 + "could not be compiled!");
714         }
715 
716         int program = GLES20.glCreateProgram();
717         if (program != 0) {
718             GLES20.glAttachShader(program, vertexShader);
719             GLToolbox.checkGlError("glAttachShader");
720             GLES20.glAttachShader(program, pixelShader);
721             GLToolbox.checkGlError("glAttachShader");
722             GLES20.glLinkProgram(program);
723             int[] linkStatus = new int[1];
724             GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
725             if (linkStatus[0] != GLES20.GL_TRUE) {
726                 String info = GLES20.glGetProgramInfoLog(program);
727                 GLES20.glDeleteProgram(program);
728                 program = 0;
729                 throw new RuntimeException("Could not link program: " + info);
730             }
731         }
732 
733         GLES20.glDeleteShader(vertexShader);
734         GLES20.glDeleteShader(pixelShader);
735 
736         return program;
737     }
738 
scanUniforms()739     private void scanUniforms() {
740         int uniformCount[] = new int [1];
741         GLES20.glGetProgramiv(mProgram, GLES20.GL_ACTIVE_UNIFORMS, uniformCount, 0);
742         if (uniformCount[0] > 0) {
743             mUniforms = new HashMap<String, ProgramUniform>(uniformCount[0]);
744             for (int i = 0; i < uniformCount[0]; ++i) {
745                 ProgramUniform uniform = new ProgramUniform(mProgram, i);
746                 mUniforms.put(uniform.getName(), uniform);
747             }
748         }
749     }
750 
getProgramUniform(String name, boolean required)751     private ProgramUniform getProgramUniform(String name, boolean required) {
752         ProgramUniform result = mUniforms.get(name);
753         if (result == null && required) {
754             throw new IllegalArgumentException("Unknown uniform '" + name + "'!");
755         }
756         return result;
757     }
758 
getProgramAttribute(String name, boolean required)759     private VertexAttribute getProgramAttribute(String name, boolean required) {
760         VertexAttribute result = mAttributes.get(name);
761         if (result == null) {
762             int handle = GLES20.glGetAttribLocation(mProgram, name);
763             if (handle >= 0) {
764                 result = new VertexAttribute(name, handle);
765                 mAttributes.put(name, result);
766             } else if (required) {
767                 throw new IllegalArgumentException("Unknown attribute '" + name + "'!");
768             }
769         }
770         return result;
771     }
772 
checkUniformAssignment(ProgramUniform uniform, int values, int components)773     private void checkUniformAssignment(ProgramUniform uniform, int values, int components) {
774         if (values % components != 0) {
775             throw new RuntimeException("Size mismatch: Attempting to assign values of size "
776                 + values + " to uniform '" + uniform.getName() + "' (must be multiple of "
777                 + components + ")!");
778         } else if (uniform.getSize() != values / components) {
779             throw new RuntimeException("Size mismatch: Cannot assign " + values + " values to "
780                 + "uniform '" + uniform.getName() + "'!");
781         }
782     }
783 
strlen(byte[] strVal)784     private static int strlen(byte[] strVal) {
785         for (int i = 0; i < strVal.length; ++i) {
786             if (strVal[i] == '\0') {
787                 return i;
788             }
789         }
790         return strVal.length;
791     }
792 }
793 
794