Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright (C) 2013 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 android.media.cts;
     18 
     19 import java.io.File;
     20 import java.io.FileOutputStream;
     21 import java.io.IOException;
     22 import java.nio.ByteBuffer;
     23 import java.nio.ByteOrder;
     24 import java.nio.FloatBuffer;
     25 
     26 import android.graphics.Bitmap;
     27 import android.graphics.SurfaceTexture;
     28 import android.opengl.GLES11Ext;
     29 import android.opengl.GLES20;
     30 import android.opengl.Matrix;
     31 import android.util.Log;
     32 
     33 
     34 /**
     35  * Code for rendering a texture onto a surface using OpenGL ES 2.0.
     36  */
     37 class TextureRender {
     38     private static final String TAG = "TextureRender";
     39 
     40     private static final int FLOAT_SIZE_BYTES = 4;
     41     private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
     42     private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
     43     private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
     44     private final float[] mTriangleVerticesData = {
     45         // X, Y, Z, U, V
     46         -1.0f, -1.0f, 0, 0.f, 0.f,
     47          1.0f, -1.0f, 0, 1.f, 0.f,
     48         -1.0f,  1.0f, 0, 0.f, 1.f,
     49          1.0f,  1.0f, 0, 1.f, 1.f,
     50     };
     51 
     52     private FloatBuffer mTriangleVertices;
     53 
     54     private static final String VERTEX_SHADER =
     55             "uniform mat4 uMVPMatrix;\n" +
     56             "uniform mat4 uSTMatrix;\n" +
     57             "attribute vec4 aPosition;\n" +
     58             "attribute vec4 aTextureCoord;\n" +
     59             "varying vec2 vTextureCoord;\n" +
     60             "void main() {\n" +
     61             "  gl_Position = uMVPMatrix * aPosition;\n" +
     62             "  vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
     63             "}\n";
     64 
     65     private static final String FRAGMENT_SHADER =
     66             "#extension GL_OES_EGL_image_external : require\n" +
     67             "precision mediump float;\n" +      // highp here doesn't seem to matter
     68             "varying vec2 vTextureCoord;\n" +
     69             "uniform samplerExternalOES sTexture;\n" +
     70             "void main() {\n" +
     71             "  gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
     72             "}\n";
     73 
     74     private float[] mMVPMatrix = new float[16];
     75     private float[] mSTMatrix = new float[16];
     76 
     77     private int mProgram;
     78     private int mTextureID = -12345;
     79     private int muMVPMatrixHandle;
     80     private int muSTMatrixHandle;
     81     private int maPositionHandle;
     82     private int maTextureHandle;
     83 
     84     public TextureRender() {
     85         mTriangleVertices = ByteBuffer.allocateDirect(
     86             mTriangleVerticesData.length * FLOAT_SIZE_BYTES)
     87                 .order(ByteOrder.nativeOrder()).asFloatBuffer();
     88         mTriangleVertices.put(mTriangleVerticesData).position(0);
     89 
     90         Matrix.setIdentityM(mSTMatrix, 0);
     91     }
     92 
     93     public int getTextureId() {
     94         return mTextureID;
     95     }
     96 
     97     public void drawFrame(SurfaceTexture st) {
     98         checkGlError("onDrawFrame start");
     99         st.getTransformMatrix(mSTMatrix);
    100 
    101         GLES20.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
    102         GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
    103 
    104         GLES20.glUseProgram(mProgram);
    105         checkGlError("glUseProgram");
    106 
    107         GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
    108         GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
    109 
    110         mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
    111         GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false,
    112             TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
    113         checkGlError("glVertexAttribPointer maPosition");
    114         GLES20.glEnableVertexAttribArray(maPositionHandle);
    115         checkGlError("glEnableVertexAttribArray maPositionHandle");
    116 
    117         mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
    118         GLES20.glVertexAttribPointer(maTextureHandle, 2, GLES20.GL_FLOAT, false,
    119             TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
    120         checkGlError("glVertexAttribPointer maTextureHandle");
    121         GLES20.glEnableVertexAttribArray(maTextureHandle);
    122         checkGlError("glEnableVertexAttribArray maTextureHandle");
    123 
    124         Matrix.setIdentityM(mMVPMatrix, 0);
    125         GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);
    126         GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0);
    127 
    128         GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
    129         checkGlError("glDrawArrays");
    130         GLES20.glFinish();
    131     }
    132 
    133     /**
    134      * Initializes GL state.  Call this after the EGL surface has been created and made current.
    135      */
    136     public void surfaceCreated() {
    137         mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
    138         if (mProgram == 0) {
    139             throw new RuntimeException("failed creating program");
    140         }
    141         maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
    142         checkGlError("glGetAttribLocation aPosition");
    143         if (maPositionHandle == -1) {
    144             throw new RuntimeException("Could not get attrib location for aPosition");
    145         }
    146         maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
    147         checkGlError("glGetAttribLocation aTextureCoord");
    148         if (maTextureHandle == -1) {
    149             throw new RuntimeException("Could not get attrib location for aTextureCoord");
    150         }
    151 
    152         muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
    153         checkGlError("glGetUniformLocation uMVPMatrix");
    154         if (muMVPMatrixHandle == -1) {
    155             throw new RuntimeException("Could not get attrib location for uMVPMatrix");
    156         }
    157 
    158         muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix");
    159         checkGlError("glGetUniformLocation uSTMatrix");
    160         if (muSTMatrixHandle == -1) {
    161             throw new RuntimeException("Could not get attrib location for uSTMatrix");
    162         }
    163 
    164 
    165         int[] textures = new int[1];
    166         GLES20.glGenTextures(1, textures, 0);
    167 
    168         mTextureID = textures[0];
    169         GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
    170         checkGlError("glBindTexture mTextureID");
    171 
    172         GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
    173                 GLES20.GL_NEAREST);
    174         GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
    175                 GLES20.GL_LINEAR);
    176         GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,
    177                 GLES20.GL_CLAMP_TO_EDGE);
    178         GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,
    179                 GLES20.GL_CLAMP_TO_EDGE);
    180         checkGlError("glTexParameter");
    181     }
    182 
    183     /**
    184      * Replaces the fragment shader.
    185      */
    186     public void changeFragmentShader(String fragmentShader) {
    187         GLES20.glDeleteProgram(mProgram);
    188         mProgram = createProgram(VERTEX_SHADER, fragmentShader);
    189         if (mProgram == 0) {
    190             throw new RuntimeException("failed creating program");
    191         }
    192     }
    193 
    194     private int loadShader(int shaderType, String source) {
    195         int shader = GLES20.glCreateShader(shaderType);
    196         checkGlError("glCreateShader type=" + shaderType);
    197         GLES20.glShaderSource(shader, source);
    198         GLES20.glCompileShader(shader);
    199         int[] compiled = new int[1];
    200         GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
    201         if (compiled[0] == 0) {
    202             Log.e(TAG, "Could not compile shader " + shaderType + ":");
    203             Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader));
    204             GLES20.glDeleteShader(shader);
    205             shader = 0;
    206         }
    207         return shader;
    208     }
    209 
    210     private int createProgram(String vertexSource, String fragmentSource) {
    211         int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
    212         if (vertexShader == 0) {
    213             return 0;
    214         }
    215         int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
    216         if (pixelShader == 0) {
    217             return 0;
    218         }
    219 
    220         int program = GLES20.glCreateProgram();
    221         checkGlError("glCreateProgram");
    222         if (program == 0) {
    223             Log.e(TAG, "Could not create program");
    224         }
    225         GLES20.glAttachShader(program, vertexShader);
    226         checkGlError("glAttachShader");
    227         GLES20.glAttachShader(program, pixelShader);
    228         checkGlError("glAttachShader");
    229         GLES20.glLinkProgram(program);
    230         int[] linkStatus = new int[1];
    231         GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
    232         if (linkStatus[0] != GLES20.GL_TRUE) {
    233             Log.e(TAG, "Could not link program: ");
    234             Log.e(TAG, GLES20.glGetProgramInfoLog(program));
    235             GLES20.glDeleteProgram(program);
    236             program = 0;
    237         }
    238         return program;
    239     }
    240 
    241     public void checkGlError(String op) {
    242         int error;
    243         while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
    244             Log.e(TAG, op + ": glError " + error);
    245             throw new RuntimeException(op + ": glError " + error);
    246         }
    247     }
    248 
    249     /**
    250      * Saves the current frame to disk as a PNG image.  Frame starts from (0,0).
    251      * <p>
    252      * Useful for debugging.
    253      */
    254     public static void saveFrame(String filename, int width, int height) {
    255         // glReadPixels gives us a ByteBuffer filled with what is essentially big-endian RGBA
    256         // data (i.e. a byte of red, followed by a byte of green...).  We need an int[] filled
    257         // with native-order ARGB data to feed to Bitmap.
    258         //
    259         // If we implement this as a series of buf.get() calls, we can spend 2.5 seconds just
    260         // copying data around for a 720p frame.  It's better to do a bulk get() and then
    261         // rearrange the data in memory.  (For comparison, the PNG compress takes about 500ms
    262         // for a trivial frame.)
    263         //
    264         // So... we set the ByteBuffer to little-endian, which should turn the bulk IntBuffer
    265         // get() into a straight memcpy on most Android devices.  Our ints will hold ABGR data.
    266         // Swapping B and R gives us ARGB.  We need about 30ms for the bulk get(), and another
    267         // 270ms for the color swap.
    268         //
    269         // Making this even more interesting is the upside-down nature of GL, which means we
    270         // may want to flip the image vertically here.
    271 
    272         ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4);
    273         buf.order(ByteOrder.LITTLE_ENDIAN);
    274         GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);
    275         buf.rewind();
    276 
    277         int pixelCount = width * height;
    278         int[] colors = new int[pixelCount];
    279         buf.asIntBuffer().get(colors);
    280         for (int i = 0; i < pixelCount; i++) {
    281             int c = colors[i];
    282             colors[i] = (c & 0xff00ff00) | ((c & 0x00ff0000) >> 16) | ((c & 0x000000ff) << 16);
    283         }
    284 
    285         FileOutputStream fos = null;
    286         try {
    287             fos = new FileOutputStream(filename);
    288             Bitmap bmp = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888);
    289             bmp.compress(Bitmap.CompressFormat.PNG, 90, fos);
    290             bmp.recycle();
    291         } catch (IOException ioe) {
    292             throw new RuntimeException("Failed to write file " + filename, ioe);
    293         } finally {
    294             try {
    295                 if (fos != null) fos.close();
    296             } catch (IOException ioe2) {
    297                 throw new RuntimeException("Failed to close file " + filename, ioe2);
    298             }
    299         }
    300         Log.d(TAG, "Saved " + width + "x" + height + " frame as '" + filename + "'");
    301     }
    302 }
    303