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