1 /*
2  * Copyright (C) 2016 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.view.cts;
18 
19 import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
20 import static android.opengl.GLES20.glClear;
21 import static android.opengl.GLES20.glClearColor;
22 import static android.opengl.GLES20.glFinish;
23 
24 import android.app.Activity;
25 import android.content.pm.ActivityInfo;
26 import android.graphics.Bitmap;
27 import android.graphics.Color;
28 import android.graphics.ColorSpace;
29 import android.graphics.Matrix;
30 import android.graphics.SurfaceTexture;
31 import android.opengl.GLUtils;
32 import android.os.Bundle;
33 import android.os.Handler;
34 import android.os.HandlerThread;
35 import android.view.TextureView;
36 import android.view.TextureView.SurfaceTextureListener;
37 import android.view.View;
38 import android.view.ViewGroup.LayoutParams;
39 import android.widget.FrameLayout;
40 
41 import java.util.concurrent.CountDownLatch;
42 import java.util.concurrent.TimeUnit;
43 import java.util.concurrent.TimeoutException;
44 
45 import javax.microedition.khronos.egl.EGL10;
46 import javax.microedition.khronos.egl.EGLConfig;
47 import javax.microedition.khronos.egl.EGLContext;
48 import javax.microedition.khronos.egl.EGLDisplay;
49 import javax.microedition.khronos.egl.EGLSurface;
50 
51 public class TextureViewCtsActivity extends Activity implements SurfaceTextureListener {
52     private final static long TIME_OUT_MS = 10000;
53     private final Object mLock = new Object();
54 
55     private View mPreview;
56     private TextureView mTextureView;
57     private HandlerThread mGLThreadLooper;
58     private Handler mGLThread;
59     private CountDownLatch mEnterAnimationFence = new CountDownLatch(1);
60 
61     private SurfaceTexture mSurface;
62     private int mSurfaceWidth;
63     private int mSurfaceHeight;
64     private int mSurfaceUpdatedCount;
65 
66     private int mEglColorSpace = 0;
67     private boolean mIsEGLWideGamut = false;
68     private boolean mEGLExtensionUnsupported = false;
69 
70     static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
71     static final int EGL_OPENGL_ES2_BIT = 4;
72     static final int EGL_GL_COLORSPACE_KHR = 0x309D;
73     static final int EGL_COLOR_COMPONENT_TYPE_EXT = 0x3339;
74     static final int EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT = 0x333B;
75 
76     private EGL10 mEgl;
77     private EGLDisplay mEglDisplay;
78     private EGLConfig mEglConfig;
79     private EGLContext mEglContext;
80     private EGLSurface mEglSurface;
81 
82     @Override
onCreate(Bundle savedInstanceState)83     protected void onCreate(Bundle savedInstanceState) {
84         super.onCreate(savedInstanceState);
85         if (mGLThreadLooper == null) {
86             mGLThreadLooper = new HandlerThread("GLThread");
87             mGLThreadLooper.start();
88             mGLThread = new Handler(mGLThreadLooper.getLooper());
89         }
90 
91         View preview = new View(this);
92         preview.setBackgroundColor(Color.WHITE);
93         mPreview = preview;
94         mTextureView = new TextureView(this);
95         mTextureView.setSurfaceTextureListener(this);
96 
97         FrameLayout content = new FrameLayout(this);
98         content.setBackgroundColor(Color.BLACK);
99         content.addView(mTextureView,
100                 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
101         content.addView(mPreview,
102                 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
103 
104         setContentView(content);
105     }
106 
107     @Override
onDestroy()108     protected void onDestroy() {
109         super.onDestroy();
110         try {
111             runOnGLThread(this::doFinishGL);
112         } catch (Throwable t) {
113             throw new RuntimeException(t);
114         }
115     }
116 
117     @Override
onEnterAnimationComplete()118     public void onEnterAnimationComplete() {
119         super.onEnterAnimationComplete();
120         mEnterAnimationFence.countDown();
121     }
122 
waitForEnterAnimationComplete()123     public void waitForEnterAnimationComplete() throws TimeoutException, InterruptedException {
124         if (!mEnterAnimationFence.await(TIME_OUT_MS, TimeUnit.MILLISECONDS)) {
125             throw new TimeoutException();
126         }
127     }
128 
setWideColorGamut()129     public boolean setWideColorGamut() throws Throwable {
130         CountDownLatch fence = new CountDownLatch(1);
131         RunSignalAndCatch wrapper = new RunSignalAndCatch(() -> {
132             this.getWindow().setColorMode(ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT);
133         }, fence);
134         runOnUiThread(wrapper);
135         if (!fence.await(TIME_OUT_MS, TimeUnit.MILLISECONDS)) {
136             throw new TimeoutException();
137         }
138         if (wrapper.error != null) {
139             throw wrapper.error;
140         }
141         return this.getWindow().getColorMode() == ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT;
142     }
143 
getContents(Bitmap.Config config, ColorSpace colorSpace)144     public Bitmap getContents(Bitmap.Config config, ColorSpace colorSpace) throws Throwable {
145         CountDownLatch fence = new CountDownLatch(1);
146         final Bitmap bitmap = Bitmap.createBitmap(this.getWindow().getDecorView().getWidth(),
147                 this.getWindow().getDecorView().getHeight(), config, true, colorSpace);
148         RunSignalAndCatch wrapper = new RunSignalAndCatch(() -> {
149             this.getTextureView().getBitmap(bitmap);
150         }, fence);
151         runOnUiThread(wrapper);
152         if (!fence.await(TIME_OUT_MS, TimeUnit.MILLISECONDS)) {
153             throw new TimeoutException();
154         }
155         if (wrapper.error != null) {
156             throw wrapper.error;
157         }
158         return bitmap;
159     }
160 
161     private class RunSignalAndCatch implements Runnable {
162         public Throwable error;
163         private Runnable mRunnable;
164         private CountDownLatch mFence;
165 
RunSignalAndCatch(Runnable run, CountDownLatch fence)166         RunSignalAndCatch(Runnable run, CountDownLatch fence) {
167             mRunnable = run;
168             mFence = fence;
169         }
170 
171         @Override
run()172         public void run() {
173             try {
174                 mRunnable.run();
175             } catch (Throwable t) {
176                 error = t;
177             } finally {
178                 mFence.countDown();
179             }
180         }
181     }
182 
runOnGLThread(Runnable r)183     private void runOnGLThread(Runnable r) throws Throwable {
184         CountDownLatch fence = new CountDownLatch(1);
185         RunSignalAndCatch wrapper = new RunSignalAndCatch(r, fence);
186         mGLThread.post(wrapper);
187         if (!fence.await(TIME_OUT_MS, TimeUnit.MILLISECONDS)) {
188             throw new TimeoutException();
189         }
190         if (wrapper.error != null) {
191             throw wrapper.error;
192         }
193     }
194 
getTextureView()195     public TextureView getTextureView() {
196         return mTextureView;
197     }
198 
waitForSurface()199     public void waitForSurface() throws InterruptedException {
200         synchronized (mLock) {
201             while (mSurface == null) {
202                 mLock.wait(TIME_OUT_MS);
203             }
204         }
205     }
206 
initGLExtensionUnsupported()207     public boolean initGLExtensionUnsupported() {
208         return mEGLExtensionUnsupported;
209     }
210 
initGl()211     public void initGl() throws Throwable {
212         initGl(0, false);
213     }
214 
initGl(int eglColorSpace, boolean useHalfFloat)215     public void initGl(int eglColorSpace, boolean useHalfFloat) throws Throwable {
216         if (mEglSurface != null) {
217             if (eglColorSpace != mEglColorSpace || useHalfFloat != mIsEGLWideGamut) {
218                 throw new RuntimeException("Cannot change config after initialization");
219             }
220             return;
221         }
222         mEglColorSpace = eglColorSpace;
223         mIsEGLWideGamut = useHalfFloat;
224         mEGLExtensionUnsupported = false;
225         runOnGLThread(mDoInitGL);
226     }
227 
drawColor(int color)228     public void drawColor(int color) throws Throwable {
229         drawColor(Color.red(color) / 255.0f,
230                 Color.green(color) / 255.0f,
231                 Color.blue(color) / 255.0f,
232                 Color.alpha(color) / 255.0f);
233     }
234 
drawColor(float red, float green, float blue, float alpha)235     public void drawColor(float red, float green, float blue, float alpha) throws Throwable {
236         runOnGLThread(() -> {
237             glClearColor(red, green, blue, alpha);
238             glClear(GL_COLOR_BUFFER_BIT);
239             if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
240                 throw new RuntimeException("Cannot swap buffers");
241             }
242         });
243     }
244 
245     interface DrawFrame {
drawFrame(int width, int height)246         void drawFrame(int width, int height);
247     }
248 
drawFrame(Matrix transform, DrawFrame callback)249     public void drawFrame(Matrix transform, DrawFrame callback) throws Throwable {
250         CountDownLatch fence = new CountDownLatch(1);
251         runOnUiThread(() -> {
252             mTextureView.setTransform(transform);
253             fence.countDown();
254         });
255         waitForEnterAnimationComplete();
256         waitForSurface();
257         initGl();
258         fence.await();
259         int surfaceUpdateCount = mSurfaceUpdatedCount;
260         runOnGLThread(() -> {
261             callback.drawFrame(mSurfaceWidth, mSurfaceHeight);
262             glFinish();
263             if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
264                 throw new RuntimeException("Cannot swap buffers");
265             }
266         });
267         waitForSurfaceUpdateCount(surfaceUpdateCount + 1);
268     }
269 
270     private static final Matrix IDENTITY = new Matrix();
271 
drawFrame(DrawFrame callback)272     public void drawFrame(DrawFrame callback) throws Throwable {
273         drawFrame(IDENTITY, callback);
274     }
275 
waitForSurfaceUpdateCount(int updateCount)276     public int waitForSurfaceUpdateCount(int updateCount) throws InterruptedException {
277         synchronized (mLock) {
278             while (updateCount > mSurfaceUpdatedCount) {
279                 mLock.wait(TIME_OUT_MS);
280             }
281             return mSurfaceUpdatedCount;
282         }
283     }
284 
removeCover()285     public void removeCover() {
286         mPreview.setVisibility(View.GONE);
287     }
288 
doFinishGL()289     private void doFinishGL() {
290         if (mEglSurface != null) {
291             mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
292             mEglSurface = null;
293         }
294         if (mEglContext != null) {
295             mEgl.eglDestroyContext(mEglDisplay, mEglContext);
296             mEglContext = null;
297         }
298         if (mEglDisplay != null) {
299             mEgl.eglTerminate(mEglDisplay);
300         }
301     }
302 
303     @Override
onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)304     public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
305         synchronized (mLock) {
306             mSurface = surface;
307             mSurfaceWidth = width;
308             mSurfaceHeight = height;
309             mLock.notifyAll();
310         }
311     }
312 
313     @Override
onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)314     public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
315     }
316 
317     @Override
onSurfaceTextureDestroyed(SurfaceTexture surface)318     public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
319         synchronized (mLock) {
320             mSurface = null;
321             mLock.notifyAll();
322         }
323         return true;
324     }
325 
326     @Override
onSurfaceTextureUpdated(SurfaceTexture surface)327     public void onSurfaceTextureUpdated(SurfaceTexture surface) {
328         synchronized (mLock) {
329             mSurfaceUpdatedCount++;
330             mLock.notifyAll();
331         }
332     }
333 
334     private Runnable mDoInitGL = new Runnable() {
335         @Override
336         public void run() {
337             mEgl = (EGL10) EGLContext.getEGL();
338 
339             mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
340             if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
341                 throw new RuntimeException("eglGetDisplay failed "
342                         + GLUtils.getEGLErrorString(mEgl.eglGetError()));
343             }
344 
345             int[] version = new int[2];
346             if (!mEgl.eglInitialize(mEglDisplay, version)) {
347                 throw new RuntimeException("eglInitialize failed " +
348                         GLUtils.getEGLErrorString(mEgl.eglGetError()));
349             }
350 
351             // check extensions but still attempt to run the test, if the test fails then we check
352             // mEGLExtensionUnsupported to determine if the failure was expected.
353             String extensions = mEgl.eglQueryString(mEglDisplay, EGL10.EGL_EXTENSIONS);
354             if (mEglColorSpace != 0) {
355                 String eglColorSpaceString = null;
356                 switch (mEglColorSpace) {
357                     case TextureViewTest.EGL_GL_COLORSPACE_DISPLAY_P3_EXT:
358                         eglColorSpaceString = "EGL_EXT_gl_colorspace_display_p3";
359                         break;
360                     case TextureViewTest.EGL_GL_COLORSPACE_DISPLAY_P3_LINEAR_EXT:
361                         eglColorSpaceString = "EGL_EXT_gl_colorspace_display_p3_linear";
362                         break;
363                     case TextureViewTest.EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT:
364                         eglColorSpaceString = "EGL_EXT_gl_colorspace_display_p3_passthrough";
365                         break;
366                     case TextureViewTest.EGL_GL_COLORSPACE_SRGB_KHR:
367                         eglColorSpaceString = "EGL_KHR_gl_colorspace";
368                         break;
369                     case TextureViewTest.EGL_GL_COLORSPACE_SCRGB_EXT:
370                         eglColorSpaceString = "EGL_EXT_gl_colorspace_scrgb";
371                         break;
372                     case TextureViewTest.EGL_GL_COLORSPACE_SCRGB_LINEAR_EXT:
373                         eglColorSpaceString = "EGL_EXT_gl_colorspace_scrgb_linear";
374                         break;
375                     case TextureViewTest.EGL_GL_COLORSPACE_LINEAR_KHR:
376                         eglColorSpaceString = "EGL_KHR_gl_colorspace";
377                         break;
378                     default:
379                         throw new RuntimeException("Unknown eglColorSpace: " + mEglColorSpace);
380                 }
381                 if (!extensions.contains(eglColorSpaceString)) {
382                     mEGLExtensionUnsupported = true;
383                 }
384             }
385             if (mIsEGLWideGamut && !extensions.contains("EXT_pixel_format_float")) {
386                 mEGLExtensionUnsupported = true;
387             }
388             // If the extension is present but the device doesn't claim to have a wide color gamut
389             // display then it might not return any actual float formats.
390             if (mIsEGLWideGamut && !mEGLExtensionUnsupported
391                     && !getWindowManager().getDefaultDisplay().isWideColorGamut()) {
392                 mEGLExtensionUnsupported = true;
393             }
394 
395             mEglConfig = chooseEglConfig();
396             if (mEglConfig == null) {
397                 throw new RuntimeException("eglConfig not initialized");
398             }
399 
400             mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
401 
402             mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig,
403                     mSurface, (mEglColorSpace == 0) ? null :
404                             new int[] { EGL_GL_COLORSPACE_KHR, mEglColorSpace, EGL10.EGL_NONE });
405 
406             if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
407                 int error = mEgl.eglGetError();
408                 throw new RuntimeException("createWindowSurface failed "
409                         + GLUtils.getEGLErrorString(error));
410             }
411 
412             if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
413                 throw new RuntimeException("eglMakeCurrent failed "
414                         + GLUtils.getEGLErrorString(mEgl.eglGetError()));
415             }
416         }
417     };
418 
createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig)419     EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
420         int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
421         return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
422     }
423 
chooseEglConfig()424     private EGLConfig chooseEglConfig() {
425         int[] configsCount = new int[1];
426         EGLConfig[] configs = new EGLConfig[1];
427         int[] configSpec = getConfig();
428         if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) {
429             throw new IllegalArgumentException("eglChooseConfig failed " +
430                     GLUtils.getEGLErrorString(mEgl.eglGetError()));
431         } else if (configsCount[0] > 0) {
432             return configs[0];
433         }
434         return null;
435     }
436 
getConfig()437     private int[] getConfig() {
438         if (mIsEGLWideGamut) {
439             return new int[]{
440                     EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
441                     EGL_COLOR_COMPONENT_TYPE_EXT, EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT,
442                     EGL10.EGL_RED_SIZE, 16,
443                     EGL10.EGL_GREEN_SIZE, 16,
444                     EGL10.EGL_BLUE_SIZE, 16,
445                     EGL10.EGL_ALPHA_SIZE, 16,
446                     EGL10.EGL_DEPTH_SIZE, 0,
447                     EGL10.EGL_STENCIL_SIZE, 0,
448                     EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT,
449                     EGL10.EGL_NONE
450             };
451         } else {
452             return new int[]{
453                     EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
454                     EGL10.EGL_RED_SIZE, 8,
455                     EGL10.EGL_GREEN_SIZE, 8,
456                     EGL10.EGL_BLUE_SIZE, 8,
457                     EGL10.EGL_ALPHA_SIZE, 8,
458                     EGL10.EGL_DEPTH_SIZE, 0,
459                     EGL10.EGL_STENCIL_SIZE, 0,
460                     EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT,
461                     EGL10.EGL_NONE
462             };
463         }
464     }
465 }
466