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 com.android.test.hwui;
18 
19 import android.animation.ObjectAnimator;
20 import android.animation.ValueAnimator;
21 import android.app.Activity;
22 import android.content.res.Resources;
23 import android.graphics.Bitmap;
24 import android.graphics.BitmapFactory;
25 import android.graphics.SurfaceTexture;
26 import android.opengl.GLUtils;
27 import android.os.Bundle;
28 import android.os.Environment;
29 import android.util.Log;
30 import android.view.Gravity;
31 import android.view.TextureView;
32 import android.view.View;
33 import android.view.ViewGroup;
34 import android.widget.FrameLayout;
35 
36 import javax.microedition.khronos.egl.EGL10;
37 import javax.microedition.khronos.egl.EGLConfig;
38 import javax.microedition.khronos.egl.EGLContext;
39 import javax.microedition.khronos.egl.EGLDisplay;
40 import javax.microedition.khronos.egl.EGLSurface;
41 import javax.microedition.khronos.opengles.GL;
42 import java.io.BufferedOutputStream;
43 import java.io.File;
44 import java.io.FileNotFoundException;
45 import java.io.FileOutputStream;
46 import java.io.IOException;
47 import java.nio.ByteBuffer;
48 import java.nio.ByteOrder;
49 import java.nio.FloatBuffer;
50 
51 import static android.opengl.GLES20.*;
52 
53 @SuppressWarnings({"UnusedDeclaration"})
54 public class GLTextureViewActivity extends Activity implements TextureView.SurfaceTextureListener {
55     private RenderThread mRenderThread;
56     private TextureView mTextureView;
57 
58     @Override
onCreate(Bundle savedInstanceState)59     protected void onCreate(Bundle savedInstanceState) {
60         super.onCreate(savedInstanceState);
61 
62         mTextureView = new TextureView(this);
63         mTextureView.setSurfaceTextureListener(this);
64         mTextureView.setOnClickListener(new View.OnClickListener() {
65             @Override
66             public void onClick(View v) {
67                 Bitmap b = mTextureView.getBitmap(800, 800);
68                 BufferedOutputStream out = null;
69                 try {
70                     File dump = new File(Environment.getExternalStorageDirectory(), "out.png");
71                     out = new BufferedOutputStream(new FileOutputStream(dump));
72                     b.compress(Bitmap.CompressFormat.PNG, 100, out);
73                 } catch (FileNotFoundException e) {
74                     e.printStackTrace();
75                 } finally {
76                     if (out != null) try {
77                         out.close();
78                     } catch (IOException e) {
79                         e.printStackTrace();
80                     }
81                 }
82             }
83         });
84 
85         setContentView(mTextureView, new FrameLayout.LayoutParams(
86                 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT,
87                 Gravity.CENTER));
88     }
89 
90     @Override
onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)91     public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
92         mRenderThread = new RenderThread(getResources(), surface);
93         mRenderThread.start();
94 
95         mTextureView.setCameraDistance(5000);
96 
97         ObjectAnimator animator = ObjectAnimator.ofFloat(mTextureView, "rotationY", 0.0f, 360.0f);
98         animator.setRepeatMode(ObjectAnimator.REVERSE);
99         animator.setRepeatCount(ObjectAnimator.INFINITE);
100         animator.setDuration(4000);
101         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
102             @Override
103             public void onAnimationUpdate(ValueAnimator animation) {
104                 mTextureView.invalidate();
105             }
106         });
107         animator.start();
108     }
109 
110     @Override
onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)111     public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
112     }
113 
114     @Override
onSurfaceTextureDestroyed(SurfaceTexture surface)115     public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
116         mRenderThread.finish();
117         try {
118             mRenderThread.join();
119         } catch (InterruptedException e) {
120             Log.e(RenderThread.LOG_TAG, "Could not wait for render thread");
121         }
122         return true;
123     }
124 
125     @Override
onSurfaceTextureUpdated(SurfaceTexture surface)126     public void onSurfaceTextureUpdated(SurfaceTexture surface) {
127     }
128 
129     private static class RenderThread extends Thread {
130         private static final String LOG_TAG = "GLTextureView";
131 
132         static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
133         static final int EGL_OPENGL_ES2_BIT = 4;
134 
135         private volatile boolean mFinished;
136 
137         private final Resources mResources;
138         private final SurfaceTexture mSurface;
139 
140         private EGL10 mEgl;
141         private EGLDisplay mEglDisplay;
142         private EGLConfig mEglConfig;
143         private EGLContext mEglContext;
144         private EGLSurface mEglSurface;
145         private GL mGL;
146 
RenderThread(Resources resources, SurfaceTexture surface)147         RenderThread(Resources resources, SurfaceTexture surface) {
148             mResources = resources;
149             mSurface = surface;
150         }
151 
152         private static final String sSimpleVS =
153                 "attribute vec4 position;\n" +
154                 "attribute vec2 texCoords;\n" +
155                 "varying vec2 outTexCoords;\n" +
156                 "\nvoid main(void) {\n" +
157                 "    outTexCoords = texCoords;\n" +
158                 "    gl_Position = position;\n" +
159                 "}\n\n";
160         private static final String sSimpleFS =
161                 "precision mediump float;\n\n" +
162                 "varying vec2 outTexCoords;\n" +
163                 "uniform sampler2D texture;\n" +
164                 "\nvoid main(void) {\n" +
165                 "    gl_FragColor = texture2D(texture, outTexCoords);\n" +
166                 "}\n\n";
167 
168         private static final int FLOAT_SIZE_BYTES = 4;
169         private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
170         private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
171         private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
172         private final float[] mTriangleVerticesData = {
173                 // X, Y, Z, U, V
174                 -1.0f, -1.0f, 0.0f, 0.0f, 0.0f,
175                  1.0f, -1.0f, 0.0f, 1.0f, 0.0f,
176                 -1.0f,  1.0f, 0.0f, 0.0f, 1.0f,
177                  1.0f,  1.0f, 0.0f, 1.0f, 1.0f,
178         };
179 
180         @Override
run()181         public void run() {
182             initGL();
183 
184             FloatBuffer triangleVertices = ByteBuffer.allocateDirect(mTriangleVerticesData.length
185                     * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
186             triangleVertices.put(mTriangleVerticesData).position(0);
187 
188             int texture = loadTexture(R.drawable.large_photo);
189             int program = buildProgram(sSimpleVS, sSimpleFS);
190 
191             int attribPosition = glGetAttribLocation(program, "position");
192             checkGlError();
193 
194             int attribTexCoords = glGetAttribLocation(program, "texCoords");
195             checkGlError();
196 
197             int uniformTexture = glGetUniformLocation(program, "texture");
198             checkGlError();
199 
200             glBindTexture(GL_TEXTURE_2D, texture);
201             checkGlError();
202 
203             glUseProgram(program);
204             checkGlError();
205 
206             glEnableVertexAttribArray(attribPosition);
207             checkGlError();
208 
209             glEnableVertexAttribArray(attribTexCoords);
210             checkGlError();
211 
212             glUniform1i(uniformTexture, 0);
213             checkGlError();
214 
215             // drawQuad
216             triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
217             glVertexAttribPointer(attribPosition, 3, GL_FLOAT, false,
218                     TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
219             checkGlError();
220 
221             triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
222             glVertexAttribPointer(attribTexCoords, 3, GL_FLOAT, false,
223                     TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
224             checkGlError();
225 
226             glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
227             checkGlError();
228 
229             while (!mFinished) {
230                 checkCurrent();
231 
232                 glClear(GL_COLOR_BUFFER_BIT);
233                 checkGlError();
234 
235                 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
236                 checkGlError();
237 
238                 if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
239                     throw new RuntimeException("Cannot swap buffers");
240                 }
241                 checkEglError();
242 
243                 try {
244                     Thread.sleep(2000);
245                 } catch (InterruptedException e) {
246                     // Ignore
247                 }
248             }
249 
250             finishGL();
251         }
252 
loadTexture(int resource)253         private int loadTexture(int resource) {
254             int[] textures = new int[1];
255 
256             glActiveTexture(GL_TEXTURE0);
257             glGenTextures(1, textures, 0);
258             checkGlError();
259 
260             int texture = textures[0];
261             glBindTexture(GL_TEXTURE_2D, texture);
262             checkGlError();
263 
264             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
265             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
266 
267             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
268             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
269 
270             Bitmap bitmap = BitmapFactory.decodeResource(mResources, resource);
271 
272             GLUtils.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap, GL_UNSIGNED_BYTE, 0);
273             checkGlError();
274 
275             bitmap.recycle();
276 
277             return texture;
278         }
279 
buildProgram(String vertex, String fragment)280         private static int buildProgram(String vertex, String fragment) {
281             int vertexShader = buildShader(vertex, GL_VERTEX_SHADER);
282             if (vertexShader == 0) return 0;
283 
284             int fragmentShader = buildShader(fragment, GL_FRAGMENT_SHADER);
285             if (fragmentShader == 0) return 0;
286 
287             int program = glCreateProgram();
288             glAttachShader(program, vertexShader);
289             checkGlError();
290 
291             glAttachShader(program, fragmentShader);
292             checkGlError();
293 
294             glLinkProgram(program);
295             checkGlError();
296 
297             int[] status = new int[1];
298             glGetProgramiv(program, GL_LINK_STATUS, status, 0);
299             if (status[0] != GL_TRUE) {
300                 String error = glGetProgramInfoLog(program);
301                 Log.d(LOG_TAG, "Error while linking program:\n" + error);
302                 glDeleteShader(vertexShader);
303                 glDeleteShader(fragmentShader);
304                 glDeleteProgram(program);
305                 return 0;
306             }
307 
308             return program;
309         }
310 
buildShader(String source, int type)311         private static int buildShader(String source, int type) {
312             int shader = glCreateShader(type);
313 
314             glShaderSource(shader, source);
315             checkGlError();
316 
317             glCompileShader(shader);
318             checkGlError();
319 
320             int[] status = new int[1];
321             glGetShaderiv(shader, GL_COMPILE_STATUS, status, 0);
322             if (status[0] != GL_TRUE) {
323                 String error = glGetShaderInfoLog(shader);
324                 Log.d(LOG_TAG, "Error while compiling shader:\n" + error);
325                 glDeleteShader(shader);
326                 return 0;
327             }
328 
329             return shader;
330         }
331 
checkEglError()332         private void checkEglError() {
333             int error = mEgl.eglGetError();
334             if (error != EGL10.EGL_SUCCESS) {
335                 Log.w(LOG_TAG, "EGL error = 0x" + Integer.toHexString(error));
336             }
337         }
338 
checkGlError()339         private static void checkGlError() {
340             int error = glGetError();
341             if (error != GL_NO_ERROR) {
342                 Log.w(LOG_TAG, "GL error = 0x" + Integer.toHexString(error));
343             }
344         }
345 
finishGL()346         private void finishGL() {
347             mEgl.eglDestroyContext(mEglDisplay, mEglContext);
348             mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
349         }
350 
checkCurrent()351         private void checkCurrent() {
352             if (!mEglContext.equals(mEgl.eglGetCurrentContext()) ||
353                     !mEglSurface.equals(mEgl.eglGetCurrentSurface(EGL10.EGL_DRAW))) {
354                 if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
355                     throw new RuntimeException("eglMakeCurrent failed "
356                             + GLUtils.getEGLErrorString(mEgl.eglGetError()));
357                 }
358             }
359         }
360 
initGL()361         private void initGL() {
362             mEgl = (EGL10) EGLContext.getEGL();
363 
364             mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
365             if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
366                 throw new RuntimeException("eglGetDisplay failed "
367                         + GLUtils.getEGLErrorString(mEgl.eglGetError()));
368             }
369 
370             int[] version = new int[2];
371             if (!mEgl.eglInitialize(mEglDisplay, version)) {
372                 throw new RuntimeException("eglInitialize failed " +
373                         GLUtils.getEGLErrorString(mEgl.eglGetError()));
374             }
375 
376             mEglConfig = chooseEglConfig();
377             if (mEglConfig == null) {
378                 throw new RuntimeException("eglConfig not initialized");
379             }
380 
381             mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
382 
383             mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, mSurface, null);
384 
385             if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
386                 int error = mEgl.eglGetError();
387                 if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
388                     Log.e(LOG_TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
389                     return;
390                 }
391                 throw new RuntimeException("createWindowSurface failed "
392                         + GLUtils.getEGLErrorString(error));
393             }
394 
395             if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
396                 throw new RuntimeException("eglMakeCurrent failed "
397                         + GLUtils.getEGLErrorString(mEgl.eglGetError()));
398             }
399 
400             mGL = mEglContext.getGL();
401         }
402 
403 
createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig)404         EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
405             int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
406             return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
407         }
408 
chooseEglConfig()409         private EGLConfig chooseEglConfig() {
410             int[] configsCount = new int[1];
411             EGLConfig[] configs = new EGLConfig[1];
412             int[] configSpec = getConfig();
413             if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) {
414                 throw new IllegalArgumentException("eglChooseConfig failed " +
415                         GLUtils.getEGLErrorString(mEgl.eglGetError()));
416             } else if (configsCount[0] > 0) {
417                 return configs[0];
418             }
419             return null;
420         }
421 
getConfig()422         private static int[] getConfig() {
423             return new int[] {
424                     EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
425                     EGL10.EGL_RED_SIZE, 8,
426                     EGL10.EGL_GREEN_SIZE, 8,
427                     EGL10.EGL_BLUE_SIZE, 8,
428                     EGL10.EGL_ALPHA_SIZE, 8,
429                     EGL10.EGL_DEPTH_SIZE, 0,
430                     EGL10.EGL_STENCIL_SIZE, 0,
431                     EGL10.EGL_NONE
432             };
433         }
434 
finish()435         void finish() {
436             mFinished = true;
437         }
438     }
439 }
440