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
17package androidx.media.filterfw;
18
19import android.graphics.RectF;
20import android.opengl.GLES20;
21import android.util.Log;
22
23import androidx.media.filterfw.geometry.Quad;
24
25import java.nio.ByteBuffer;
26import java.nio.ByteOrder;
27import java.nio.FloatBuffer;
28import java.util.Arrays;
29import 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 */
40public 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
90        public VertexAttribute(String name, int index) {
91            mName = name;
92            mIndex = index;
93            mLength = -1;
94        }
95
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
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
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
169        public String toString() {
170            return mName;
171        }
172
173        private void initBuffer(float[] values) {
174            mValues = ByteBuffer.allocateDirect(values.length * FLOAT_SIZE)
175                .order(ByteOrder.nativeOrder()).asFloatBuffer();
176        }
177
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
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
207        public String getName() {
208            return mName;
209        }
210
211        public int getType() {
212            return mType;
213        }
214
215        public int getLocation() {
216            return mLocation;
217        }
218
219        public int getSize() {
220            return mSize;
221        }
222    }
223
224    public ImageShader(String fragmentShader) {
225        mProgram = createProgram(mDefaultVertexShader, fragmentShader);
226        scanUniforms();
227    }
228
229    public ImageShader(String vertexShader, String fragmentShader) {
230        mProgram = createProgram(vertexShader, fragmentShader);
231        scanUniforms();
232    }
233
234    public static ImageShader createIdentity() {
235        return new ImageShader(mIdentityShader);
236    }
237
238    public static ImageShader createIdentity(String vertexShader) {
239        return new ImageShader(vertexShader, mIdentityShader);
240    }
241
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
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
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
277    public void process(TextureSource texture, RenderTarget target, int width, int height) {
278        processMulti(new TextureSource[] { texture }, target, width, height);
279    }
280
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
291    public void processNoInput(FrameImage2D output) {
292        RenderTarget renderTarget = output.lockRenderTarget();
293        processNoInput(renderTarget, output.getWidth(), output.getHeight());
294        output.unlock();
295    }
296
297    public void processNoInput(RenderTarget target, int width, int height) {
298        processMulti(new TextureSource[] {}, target, width, height);
299    }
300
301    public int getUniformLocation(String name) {
302        return getProgramUniform(name, true).getLocation();
303    }
304
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
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
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
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
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
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
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
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
415    public void setSourceRect(RectF rect) {
416        setSourceRect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
417    }
418
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
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
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
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
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
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
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
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
504    public void setClearsOutput(boolean clears) {
505        mClearsOutput = clears;
506    }
507
508    public boolean getClearsOutput() {
509        return mClearsOutput;
510    }
511
512    public void setClearColor(float[] rgba) {
513        mClearColor = rgba;
514    }
515
516    public float[] getClearColor() {
517        return mClearColor;
518    }
519
520    public void setClearBufferMask(int bufferMask) {
521        mClearBuffers = bufferMask;
522    }
523
524    public int getClearBufferMask() {
525        return mClearBuffers;
526    }
527
528    public void setBlendEnabled(boolean enable) {
529        mBlendEnabled = enable;
530    }
531
532    public boolean getBlendEnabled() {
533        return mBlendEnabled;
534    }
535
536    public void setBlendFunc(int sFactor, int dFactor) {
537        mSFactor = sFactor;
538        mDFactor = dFactor;
539    }
540
541    public void setDrawMode(int drawMode) {
542        mDrawMode = drawMode;
543    }
544
545    public int getDrawMode() {
546        return mDrawMode;
547    }
548
549    public void setVertexCount(int count) {
550        mVertexCount = count;
551    }
552
553    public int getVertexCount() {
554        return mVertexCount;
555    }
556
557    public void setBaseTextureUnit(int baseTexUnit) {
558        mBaseTexUnit = baseTexUnit;
559    }
560
561    public int baseTextureUnit() {
562        return mBaseTexUnit;
563    }
564
565    public String texCoordAttributeName() {
566        return "a_texcoord";
567    }
568
569    public String positionAttributeName() {
570        return "a_position";
571    }
572
573    public String inputTextureUniformName(int index) {
574        return "tex_sampler_" + index;
575    }
576
577    public static int maxTextureUnits() {
578        return GLES20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS;
579    }
580
581    @Override
582    protected void finalize() throws Throwable {
583        GLES20.glDeleteProgram(mProgram);
584    }
585
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
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
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
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
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
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
664    private void render() {
665        GLES20.glDrawArrays(mDrawMode, 0, mVertexCount);
666        GLToolbox.checkGlError("glDrawArrays");
667    }
668
669    private void checkExecutable() {
670        if (mProgram == 0) {
671            throw new RuntimeException("Attempting to execute invalid shader-program!");
672        }
673    }
674
675    private void useProgram() {
676        GLES20.glUseProgram(mProgram);
677        GLToolbox.checkGlError("glUseProgram");
678    }
679
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
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
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
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
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
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
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
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