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