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