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