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