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