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