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