1 /* 2 * Copyright (C) 2011 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.opengl.cts; 18 19 import android.content.Context; 20 import android.graphics.Bitmap; 21 import android.graphics.Color; 22 import android.opengl.GLES20; 23 import android.opengl.GLSurfaceView; 24 import android.opengl.GLUtils; 25 import android.opengl.Matrix; 26 import android.util.Log; 27 28 import java.nio.ByteBuffer; 29 import java.nio.ByteOrder; 30 import java.nio.FloatBuffer; 31 import java.util.concurrent.CountDownLatch; 32 import java.util.concurrent.TimeUnit; 33 34 import javax.microedition.khronos.egl.EGLConfig; 35 import javax.microedition.khronos.opengles.GL10; 36 37 class CompressedTextureSurfaceView extends GLSurfaceView { 38 CompressedTextureRender mRenderer; 39 CompressedTextureSurfaceView(Context context, Bitmap base, CompressedTextureLoader.Texture compressed)40 public CompressedTextureSurfaceView(Context context, 41 Bitmap base, 42 CompressedTextureLoader.Texture compressed) { 43 super(context); 44 45 setEGLContextClientVersion(2); 46 mRenderer = new CompressedTextureRender(base, compressed); 47 setRenderer(mRenderer); 48 setRenderMode(RENDERMODE_WHEN_DIRTY); 49 } 50 getTestPassed()51 public boolean getTestPassed() throws InterruptedException { 52 return mRenderer.getTestPassed(); 53 } 54 55 private static class CompressedTextureRender implements GLSurfaceView.Renderer { 56 private static String TAG = "CompressedTextureRender"; 57 58 private static final int ALLOWED_DELTA = 25; 59 private static final int FBO_PIXEL_SIZE_BYTES = 4; 60 private static final int FLOAT_SIZE_BYTES = 4; 61 private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES; 62 private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0; 63 private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3; 64 private final float[] mTriangleVerticesData = { 65 // X, Y, Z, U, V 66 -1.0f, -1.0f, 0, 0.f, 0.f, 67 1.0f, -1.0f, 0, 1.f, 0.f, 68 -1.0f, 1.0f, 0, 0.f, 1.f, 69 1.0f, 1.0f, 0, 1.f, 1.f, 70 }; 71 72 private FloatBuffer mTriangleVertices; 73 74 private final String mVertexShader = 75 "uniform mat4 uMVPMatrix;\n" + 76 "attribute vec4 aPosition;\n" + 77 "attribute vec4 aTextureCoord;\n" + 78 "varying vec2 vTextureCoord;\n" + 79 "void main() {\n" + 80 " gl_Position = uMVPMatrix * aPosition;\n" + 81 " vTextureCoord = aTextureCoord.xy;\n" + 82 "}\n"; 83 84 private final String mFragmentShader = 85 "precision mediump float;\n" + 86 "varying vec2 vTextureCoord;\n" + 87 "uniform sampler2D sTexture;\n" + 88 "void main() {\n" + 89 " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + 90 "}\n"; 91 92 private float[] mMVPMatrix = new float[16]; 93 private float[] mSTMatrix = new float[16]; 94 95 private int mProgram; 96 private int mTextureID; 97 private int muMVPMatrixHandle; 98 private int maPositionHandle; 99 private int maTextureHandle; 100 101 private int mColorTargetID; 102 private int mFrameBufferObjectID; 103 104 private boolean mTestPassed; 105 private CountDownLatch mDoneSignal; 106 107 Bitmap mBaseTexture; 108 CompressedTextureLoader.Texture mCompressedTexture; 109 110 int mWidth; 111 int mHeight; 112 113 ByteBuffer mReadBackBuffer; 114 getTestPassed()115 boolean getTestPassed() throws InterruptedException { 116 if (!mDoneSignal.await(2000L, TimeUnit.MILLISECONDS)) { 117 throw new IllegalStateException("Coudn't finish drawing frames!"); 118 } 119 120 return mTestPassed; 121 } 122 CompressedTextureRender(Bitmap base, CompressedTextureLoader.Texture compressed)123 public CompressedTextureRender(Bitmap base, 124 CompressedTextureLoader.Texture compressed) { 125 mBaseTexture = base; 126 mCompressedTexture = compressed; 127 mTriangleVertices = ByteBuffer.allocateDirect( 128 mTriangleVerticesData.length * FLOAT_SIZE_BYTES) 129 .order(ByteOrder.nativeOrder()).asFloatBuffer(); 130 mTriangleVertices.put(mTriangleVerticesData).position(0); 131 132 Matrix.setIdentityM(mSTMatrix, 0); 133 134 int byteBufferSize = mBaseTexture.getWidth() * 135 mBaseTexture.getHeight() * 136 FBO_PIXEL_SIZE_BYTES; 137 mReadBackBuffer = ByteBuffer.allocateDirect(byteBufferSize); 138 139 mDoneSignal = new CountDownLatch(1); 140 } 141 renderQuad(int textureID)142 private void renderQuad(int textureID) { 143 GLES20.glUseProgram(mProgram); 144 checkGlError("glUseProgram"); 145 146 GLES20.glActiveTexture(GLES20.GL_TEXTURE0); 147 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureID); 148 149 mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET); 150 GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false, 151 TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices); 152 checkGlError("glVertexAttribPointer maPosition"); 153 GLES20.glEnableVertexAttribArray(maPositionHandle); 154 checkGlError("glEnableVertexAttribArray maPositionHandle"); 155 156 mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET); 157 GLES20.glVertexAttribPointer(maTextureHandle, 2, GLES20.GL_FLOAT, false, 158 TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices); 159 checkGlError("glVertexAttribPointer maTextureHandle"); 160 GLES20.glEnableVertexAttribArray(maTextureHandle); 161 checkGlError("glEnableVertexAttribArray maTextureHandle"); 162 163 Matrix.setIdentityM(mMVPMatrix, 0); 164 GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0); 165 166 GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); 167 checkGlError("glDrawArrays"); 168 } 169 getUnsignedByte(byte val)170 private int getUnsignedByte(byte val) { 171 return 0xFF & ((int)val); 172 } 173 comparePixel(int x, int y)174 private boolean comparePixel(int x, int y) { 175 int w = mBaseTexture.getWidth(); 176 int sampleStart = (y * w + x) * FBO_PIXEL_SIZE_BYTES; 177 178 int R = getUnsignedByte(mReadBackBuffer.get(sampleStart)); 179 int G = getUnsignedByte(mReadBackBuffer.get(sampleStart + 1)); 180 int B = getUnsignedByte(mReadBackBuffer.get(sampleStart + 2)); 181 182 int original = mBaseTexture.getPixel(x, y); 183 184 int deltaR = Math.abs(R - Color.red(original)); 185 int deltaG = Math.abs(G - Color.green(original)); 186 int deltaB = Math.abs(B - Color.blue(original)); 187 188 if (deltaR <= ALLOWED_DELTA && 189 deltaG <= ALLOWED_DELTA && 190 deltaB <= ALLOWED_DELTA) { 191 return true; 192 } 193 194 Log.i("PIXEL DELTA", "R: " + deltaR + " G: " + deltaG + " B: " + deltaB); 195 196 return false; 197 } 198 comparePixels()199 private void comparePixels() { 200 int w = mBaseTexture.getWidth(); 201 int h = mBaseTexture.getWidth(); 202 int wOver4 = w / 4; 203 int hOver4 = h / 4; 204 205 // Sample 4 points in the image. Test is designed so that 206 // sample areas are low frequency and easy to compare 207 boolean sample1Matches = comparePixel(wOver4, hOver4); 208 boolean sample2Matches = comparePixel(wOver4 * 3, hOver4); 209 boolean sample3Matches = comparePixel(wOver4, hOver4 * 3); 210 boolean sample4Matches = comparePixel(wOver4 * 3, hOver4 * 3); 211 212 mTestPassed = sample1Matches && sample2Matches && sample3Matches && sample4Matches; 213 mDoneSignal.countDown(); 214 } 215 onDrawFrame(GL10 glUnused)216 public void onDrawFrame(GL10 glUnused) { 217 if (mProgram == 0) { 218 return; 219 } 220 221 GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBufferObjectID); 222 GLES20.glViewport(0, 0, mBaseTexture.getWidth(), mBaseTexture.getHeight()); 223 GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f); 224 renderQuad(mTextureID); 225 GLES20.glReadPixels(0, 0, mBaseTexture.getWidth(), mBaseTexture.getHeight(), 226 GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mReadBackBuffer); 227 comparePixels(); 228 GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); 229 230 GLES20.glViewport(0, 0, mWidth, mHeight); 231 GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f); 232 GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT); 233 234 renderQuad(mColorTargetID); 235 236 GLES20.glFinish(); 237 } 238 onSurfaceChanged(GL10 glUnused, int width, int height)239 public void onSurfaceChanged(GL10 glUnused, int width, int height) { 240 mWidth = width; 241 mHeight = height; 242 } 243 setupSamplers()244 private void setupSamplers() { 245 GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, 246 GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); 247 GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, 248 GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); 249 GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, 250 GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); 251 GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, 252 GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); 253 } 254 initFBO()255 private void initFBO() { 256 int[] textures = new int[1]; 257 GLES20.glGenTextures(1, textures, 0); 258 259 mColorTargetID = textures[0]; 260 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mColorTargetID); 261 checkGlError("glBindTexture mColorTargetID"); 262 setupSamplers(); 263 GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, 264 mBaseTexture.getWidth(), mBaseTexture.getHeight(), 0, 265 GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null); 266 checkGlError("glTexImage2D mColorTargetID"); 267 268 GLES20.glGenFramebuffers(1, textures, 0); 269 mFrameBufferObjectID = textures[0]; 270 GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBufferObjectID); 271 272 GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, 273 GLES20.GL_TEXTURE_2D, mColorTargetID, 0); 274 275 int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER); 276 if(status != GLES20.GL_FRAMEBUFFER_COMPLETE) { 277 throw new RuntimeException("Failed to initialize framebuffer object"); 278 } 279 280 GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); 281 } 282 onSurfaceCreated(GL10 glUnused, EGLConfig config)283 public void onSurfaceCreated(GL10 glUnused, EGLConfig config) { 284 if (mCompressedTexture != null && !mCompressedTexture.isSupported()) { 285 mTestPassed = true; 286 mDoneSignal.countDown(); 287 return; 288 } 289 290 initFBO(); 291 292 mProgram = createProgram(mVertexShader, mFragmentShader); 293 if (mProgram == 0) { 294 return; 295 } 296 maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition"); 297 checkGlError("glGetAttribLocation aPosition"); 298 if (maPositionHandle == -1) { 299 throw new RuntimeException("Could not get attrib location for aPosition"); 300 } 301 maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord"); 302 checkGlError("glGetAttribLocation aTextureCoord"); 303 if (maTextureHandle == -1) { 304 throw new RuntimeException("Could not get attrib location for aTextureCoord"); 305 } 306 307 muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix"); 308 checkGlError("glGetUniformLocation uMVPMatrix"); 309 if (muMVPMatrixHandle == -1) { 310 throw new RuntimeException("Could not get attrib location for uMVPMatrix"); 311 } 312 313 int[] textures = new int[1]; 314 GLES20.glGenTextures(1, textures, 0); 315 316 mTextureID = textures[0]; 317 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureID); 318 checkGlError("glBindTexture mTextureID"); 319 setupSamplers(); 320 321 if (mCompressedTexture == null) { 322 GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, mBaseTexture, 0); 323 checkGlError("texImage2D mBaseTexture"); 324 } else { 325 GLES20.glCompressedTexImage2D(GLES20.GL_TEXTURE_2D, 326 0, 327 mCompressedTexture.getFormat(), 328 mCompressedTexture.getWidth(), 329 mCompressedTexture.getHeight(), 330 0, 331 mCompressedTexture.getData().remaining(), 332 mCompressedTexture.getData()); 333 checkGlError("glCompressedTexImage2D mTextureID"); 334 } 335 } 336 loadShader(int shaderType, String source)337 private int loadShader(int shaderType, String source) { 338 int shader = GLES20.glCreateShader(shaderType); 339 if (shader != 0) { 340 GLES20.glShaderSource(shader, source); 341 GLES20.glCompileShader(shader); 342 int[] compiled = new int[1]; 343 GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); 344 if (compiled[0] == 0) { 345 Log.e(TAG, "Could not compile shader " + shaderType + ":"); 346 Log.e(TAG, GLES20.glGetShaderInfoLog(shader)); 347 GLES20.glDeleteShader(shader); 348 shader = 0; 349 } 350 } 351 return shader; 352 } 353 createProgram(String vertexSource, String fragmentSource)354 private int createProgram(String vertexSource, String fragmentSource) { 355 int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); 356 if (vertexShader == 0) { 357 return 0; 358 } 359 int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); 360 if (pixelShader == 0) { 361 return 0; 362 } 363 364 int program = GLES20.glCreateProgram(); 365 if (program != 0) { 366 GLES20.glAttachShader(program, vertexShader); 367 checkGlError("glAttachShader"); 368 GLES20.glAttachShader(program, pixelShader); 369 checkGlError("glAttachShader"); 370 GLES20.glLinkProgram(program); 371 int[] linkStatus = new int[1]; 372 GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); 373 if (linkStatus[0] != GLES20.GL_TRUE) { 374 Log.e(TAG, "Could not link program: "); 375 Log.e(TAG, GLES20.glGetProgramInfoLog(program)); 376 GLES20.glDeleteProgram(program); 377 program = 0; 378 } 379 } 380 return program; 381 } 382 checkGlError(String op)383 private void checkGlError(String op) { 384 int error; 385 while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { 386 Log.e(TAG, op + ": glError " + error); 387 throw new RuntimeException(op + ": glError " + error); 388 } 389 } 390 391 } // End of class CompressedTextureRender. 392 393 } // End of class CompressedTextureSurfaceView. 394