1 /* <lambda>null2 * Copyright 2022 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 androidx.graphics.opengl 18 19 import android.graphics.Bitmap 20 import android.graphics.Color 21 import android.graphics.ColorSpace 22 import android.graphics.Paint 23 import android.graphics.PixelFormat 24 import android.graphics.RenderNode 25 import android.graphics.SurfaceTexture 26 import android.hardware.HardwareBuffer 27 import android.media.Image 28 import android.media.ImageReader 29 import android.opengl.EGL14 30 import android.opengl.EGLSurface 31 import android.opengl.GLES20 32 import android.opengl.Matrix 33 import android.os.Build 34 import android.os.Handler 35 import android.os.HandlerThread 36 import android.view.PixelCopy 37 import android.view.Surface 38 import android.view.SurfaceHolder 39 import android.view.SurfaceView 40 import android.view.TextureView 41 import android.view.TextureView.SurfaceTextureListener 42 import androidx.annotation.RequiresApi 43 import androidx.annotation.WorkerThread 44 import androidx.graphics.SurfaceTextureRenderer 45 import androidx.graphics.isAllColor 46 import androidx.graphics.lowlatency.LineRenderer 47 import androidx.graphics.lowlatency.Rectangle 48 import androidx.graphics.opengl.egl.EGLManager 49 import androidx.graphics.opengl.egl.EGLSpec 50 import androidx.graphics.opengl.egl.supportsNativeAndroidFence 51 import androidx.graphics.surface.SurfaceControlUtils 52 import androidx.graphics.verifyQuadrants 53 import androidx.hardware.SyncFenceCompat 54 import androidx.lifecycle.Lifecycle.State 55 import androidx.test.core.app.ActivityScenario 56 import androidx.test.ext.junit.runners.AndroidJUnit4 57 import androidx.test.filters.SdkSuppress 58 import androidx.test.filters.SmallTest 59 import java.nio.IntBuffer 60 import java.util.concurrent.CountDownLatch 61 import java.util.concurrent.TimeUnit 62 import java.util.concurrent.atomic.AtomicBoolean 63 import java.util.concurrent.atomic.AtomicInteger 64 import java.util.concurrent.atomic.AtomicReference 65 import org.junit.Assert.assertEquals 66 import org.junit.Assert.assertFalse 67 import org.junit.Assert.assertNotNull 68 import org.junit.Assert.assertTrue 69 import org.junit.Assert.fail 70 import org.junit.Test 71 import org.junit.runner.RunWith 72 73 @RunWith(AndroidJUnit4::class) 74 @SmallTest 75 class GLRendererTest { 76 @Test 77 fun testStartAfterStop() { 78 with(GLRenderer()) { 79 start("thread1") 80 stop(true) 81 start("thread2") 82 stop(true) 83 } 84 } 85 86 @Test 87 fun testAttachBeforeStartThrows() { 88 try { 89 with(GLRenderer()) { 90 attach( 91 Surface(SurfaceTexture(17)), 92 10, 93 10, 94 object : GLRenderer.RenderCallback { 95 override fun onDrawFrame(eglManager: EGLManager) { 96 // NO-OP 97 } 98 } 99 ) 100 } 101 fail("Start should be called first") 102 } catch (exception: IllegalStateException) { 103 // Success, attach before call to start should fail 104 } 105 } 106 107 @Test 108 fun testRender() { 109 val latch = CountDownLatch(1) 110 val renderer = 111 object : GLRenderer.RenderCallback { 112 override fun onDrawFrame(eglManager: EGLManager) { 113 GLES20.glClearColor(1.0f, 0.0f, 1.0f, 1.0f) 114 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) 115 } 116 } 117 118 val width = 5 119 val height = 8 120 val reader = createImageReader(width, height) 121 val glRenderer = GLRenderer() 122 glRenderer.start() 123 124 val target = glRenderer.attach(reader.surface, width, height, renderer) 125 target.requestRender { latch.countDown() } 126 127 assertTrue(latch.await(3000, TimeUnit.MILLISECONDS)) 128 val targetColor = Color.argb(255, 255, 0, 255) 129 130 verifyImageContent(width, height, reader.acquireLatestImage(), targetColor) 131 132 target.detach(true) 133 134 glRenderer.stop(true) 135 } 136 137 @Test 138 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q) 139 fun testFrameBufferClose() { 140 val glRenderer = GLRenderer() 141 glRenderer.start() 142 try { 143 var currentFbo = -1 144 val executeLatch = CountDownLatch(1) 145 glRenderer.execute { 146 val buffer = 147 FrameBuffer( 148 EGLSpec.V14, 149 HardwareBuffer.create( 150 10, 151 10, 152 HardwareBuffer.RGBA_8888, 153 1, 154 HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE 155 ) 156 ) 157 buffer.makeCurrent() 158 buffer.close() 159 160 // After the buffer is closed, query which framebuffer is current. 161 // If the previously current FBO is deleted, the current framebuffer should be 0. 162 // As per OpenGL spec, an fbo binding with glBindFrameBuffer will remain active 163 // until a different framebuffer object is bound, or until the bound framebuffer 164 // is deleted with glDeleteFramebuffers 165 val tmp = IntBuffer.wrap(IntArray(1)) 166 GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, tmp) 167 currentFbo = tmp.get(0) 168 169 executeLatch.countDown() 170 } 171 assertTrue(executeLatch.await(3000, TimeUnit.MILLISECONDS)) 172 assertEquals(0, currentFbo) 173 } finally { 174 glRenderer.stop(true) 175 } 176 } 177 178 @Test 179 fun testExecuteHasEGLContext() { 180 val glRenderer = GLRenderer() 181 glRenderer.start() 182 try { 183 var hasContext = false 184 val contextLatch = CountDownLatch(1) 185 glRenderer.execute { 186 val eglContext = EGL14.eglGetCurrentContext() 187 hasContext = eglContext != null && eglContext != EGL14.EGL_NO_CONTEXT 188 contextLatch.countDown() 189 } 190 assertTrue(contextLatch.await(3000, TimeUnit.MILLISECONDS)) 191 assertTrue(hasContext) 192 } finally { 193 glRenderer.stop(true) 194 } 195 } 196 197 @Test 198 fun testDetachExecutesPendingRequests() { 199 val latch = CountDownLatch(1) 200 val renderer = 201 object : GLRenderer.RenderCallback { 202 override fun onDrawFrame(eglManager: EGLManager) { 203 GLES20.glClearColor(1.0f, 0.0f, 1.0f, 1.0f) 204 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) 205 } 206 } 207 208 val width = 5 209 val height = 8 210 val reader = createImageReader(width, height) 211 val glRenderer = GLRenderer() 212 glRenderer.start() 213 214 val target = glRenderer.attach(reader.surface, width, height, renderer) 215 target.requestRender { latch.countDown() } 216 target.detach(false) // RequestRender Call should still execute 217 218 assertTrue(latch.await(3000, TimeUnit.MILLISECONDS)) 219 220 val targetColor = Color.argb(255, 255, 0, 255) 221 verifyImageContent(width, height, reader.acquireLatestImage(), targetColor) 222 223 glRenderer.stop(true) 224 } 225 226 @Test 227 fun testStopExecutesPendingRequests() { 228 val latch = CountDownLatch(1) 229 val surfaceWidth = 5 230 val surfaceHeight = 8 231 val renderer = 232 object : GLRenderer.RenderCallback { 233 override fun onDrawFrame(eglManager: EGLManager) { 234 val size = eglManager.eglSpec.querySurfaceSize(eglManager.currentDrawSurface) 235 assertEquals(surfaceWidth, size.width) 236 assertEquals(surfaceHeight, size.height) 237 GLES20.glClearColor(1.0f, 0.0f, 1.0f, 1.0f) 238 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) 239 } 240 } 241 242 val reader = createImageReader(surfaceWidth, surfaceHeight) 243 val glRenderer = GLRenderer() 244 glRenderer.start() 245 246 val target = glRenderer.attach(reader.surface, surfaceWidth, surfaceHeight, renderer) 247 target.requestRender { latch.countDown() } 248 glRenderer.stop(false) // RequestRender call should still execute 249 250 assertTrue(latch.await(3000, TimeUnit.MILLISECONDS)) 251 252 val targetColor = Color.argb(255, 255, 0, 255) 253 verifyImageContent(surfaceWidth, surfaceHeight, reader.acquireLatestImage(), targetColor) 254 } 255 256 @Test 257 fun testDetachExecutesMultiplePendingRequests() { 258 val numRenders = 4 259 val latch = CountDownLatch(numRenders) 260 val renderCount = AtomicInteger(0) 261 val renderer = 262 object : GLRenderer.RenderCallback { 263 override fun onDrawFrame(eglManager: EGLManager) { 264 var red: Float = 0f 265 var green: Float = 0f 266 var blue: Float = 0f 267 when (renderCount.get()) { 268 1 -> { 269 red = 1f 270 } 271 2 -> { 272 green = 1f 273 } 274 3 -> { 275 blue = 1f 276 } 277 } 278 GLES20.glClearColor(red, green, blue, 1.0f) 279 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) 280 } 281 } 282 283 val width = 5 284 val height = 8 285 val reader = createImageReader(width, height) 286 val glRenderer = GLRenderer() 287 glRenderer.start() 288 289 val target = glRenderer.attach(reader.surface, width, height, renderer) 290 // Issuing multiple requestRender calls to ensure each of them are 291 // executed even when a detach call is made 292 repeat(numRenders) { 293 target.requestRender { 294 renderCount.incrementAndGet() 295 latch.countDown() 296 } 297 } 298 299 target.detach(false) // RequestRender calls should still execute 300 301 assertTrue(latch.await(3000, TimeUnit.MILLISECONDS)) 302 assertEquals(numRenders, renderCount.get()) 303 304 val targetColor = Color.argb(255, 0, 0, 255) 305 verifyImageContent(width, height, reader.acquireLatestImage(), targetColor) 306 307 glRenderer.stop(true) 308 } 309 310 @Test 311 fun testDetachCancelsPendingRequests() { 312 val latch = CountDownLatch(1) 313 val renderer = 314 object : GLRenderer.RenderCallback { 315 override fun onDrawFrame(eglManager: EGLManager) { 316 GLES20.glClearColor(1.0f, 0.0f, 1.0f, 1.0f) 317 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) 318 } 319 } 320 321 val width = 5 322 val height = 8 323 val reader = createImageReader(width, height) 324 val glRenderer = GLRenderer() 325 glRenderer.start() 326 327 val target = glRenderer.attach(reader.surface, width, height, renderer) 328 target.requestRender { latch.countDown() } 329 target.detach(false) // RequestRender Call should be cancelled 330 331 glRenderer.stop(true) 332 } 333 334 @Test 335 fun testMultipleAttachedSurfaces() { 336 val latch = CountDownLatch(2) 337 val renderer1 = 338 object : GLRenderer.RenderCallback { 339 340 override fun onDrawFrame(eglManager: EGLManager) { 341 GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f) 342 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) 343 } 344 } 345 346 val renderer2 = 347 object : GLRenderer.RenderCallback { 348 override fun onDrawFrame(eglManager: EGLManager) { 349 GLES20.glClearColor(0.0f, 0.0f, 1.0f, 1.0f) 350 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) 351 } 352 } 353 354 val width1 = 6 355 val height1 = 7 356 357 val width2 = 11 358 val height2 = 23 359 val reader1 = createImageReader(width1, height1) 360 361 val reader2 = createImageReader(width2, height2) 362 363 val glRenderer = GLRenderer() 364 glRenderer.start() 365 366 val target1 = glRenderer.attach(reader1.surface, width1, height1, renderer1) 367 val target2 = glRenderer.attach(reader2.surface, width2, height2, renderer2) 368 target1.requestRender { latch.countDown() } 369 target2.requestRender { latch.countDown() } 370 371 assertTrue(latch.await(3000, TimeUnit.MILLISECONDS)) 372 373 verifyImageContent( 374 width1, 375 height1, 376 reader1.acquireLatestImage(), 377 Color.argb(255, 255, 0, 0) 378 ) 379 verifyImageContent( 380 width2, 381 height2, 382 reader2.acquireLatestImage(), 383 Color.argb(255, 0, 0, 255) 384 ) 385 386 target1.detach(true) 387 target2.detach(true) 388 389 val attachLatch = CountDownLatch(1) 390 glRenderer.stop(true) { attachLatch.countDown() } 391 392 assertTrue(attachLatch.await(3000, TimeUnit.MILLISECONDS)) 393 } 394 395 private fun createImageReader(width: Int, height: Int): ImageReader = 396 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 397 ImageReader.newInstance( 398 width, 399 height, 400 PixelFormat.RGBA_8888, 401 1, 402 HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT 403 ) 404 } else { 405 ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 1) 406 } 407 408 /** 409 * Helper class for test methods that refer to APIs that may not exist on earlier API levels. 410 * This must be broken out into a separate class instead of being defined within the test class 411 * as the test runner will inspect all methods + parameter types in advance. If a parameter type 412 * does not exist on a particular API level, it will crash even if there are 413 * corresponding @SdkSuppress and @RequiresApi See https://b.corp.google.com/issues/221485597 414 */ 415 private fun verifyImageContent(width: Int, height: Int, image: Image, targetColor: Int) { 416 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 417 val bitmap = 418 Bitmap.wrapHardwareBuffer(image.hardwareBuffer!!, null)!!.copy( 419 Bitmap.Config.ARGB_8888, 420 false 421 ) 422 for (y in 0 until height) { 423 for (x in 0 until width) { 424 assertEquals("Index: $x, $y", targetColor, bitmap.getPixel(x, y)) 425 } 426 } 427 } else { 428 val plane = image.planes[0] 429 assertEquals(4, plane.pixelStride) 430 val rowPadding = plane.rowStride - plane.pixelStride * width 431 var offset = 0 432 for (y in 0 until height) { 433 for (x in 0 until width) { 434 val red = plane.buffer[offset].toInt() and 0xff 435 val green = plane.buffer[offset + 1].toInt() and 0xff 436 val blue = plane.buffer[offset + 2].toInt() and 0xff 437 val alpha = plane.buffer[offset + 3].toInt() and 0xff 438 val packedColor = Color.argb(alpha, red, green, blue) 439 assertEquals("Index: $x, $y", targetColor, packedColor) 440 offset += plane.pixelStride 441 } 442 offset += rowPadding 443 } 444 } 445 } 446 447 @Test 448 fun testExecute() { 449 val countDownLatch = CountDownLatch(1) 450 GLRenderer().apply { 451 start() 452 execute { countDownLatch.countDown() } 453 } 454 assertTrue(countDownLatch.await(3000, TimeUnit.MILLISECONDS)) 455 } 456 457 @Test 458 fun testNonStartedGLRendererIsNotRunning() { 459 assertFalse(GLRenderer().isRunning()) 460 } 461 462 @Test 463 fun testRepeatedStartAndStopRunningState() { 464 val glRenderer = GLRenderer() 465 assertFalse(glRenderer.isRunning()) 466 glRenderer.start() 467 assertTrue(glRenderer.isRunning()) 468 glRenderer.stop(true) 469 assertFalse(glRenderer.isRunning()) 470 glRenderer.start() 471 assertTrue(glRenderer.isRunning()) 472 glRenderer.stop(true) 473 assertFalse(glRenderer.isRunning()) 474 } 475 476 @Test 477 fun testMultipleSurfaceHolderDestroyCallbacks() { 478 val destroyLatch = CountDownLatch(1) 479 val renderer = GLRenderer().apply { start() } 480 val scenario = withGLTestActivity { 481 assertNotNull(surfaceView) 482 483 var renderTarget: GLRenderer.RenderTarget? = null 484 val callbacks = 485 object : SurfaceHolder.Callback { 486 override fun surfaceCreated(p0: SurfaceHolder) { 487 // no-op 488 } 489 490 override fun surfaceChanged(p0: SurfaceHolder, p1: Int, p2: Int, p3: Int) { 491 // no-op 492 } 493 494 override fun surfaceDestroyed(p0: SurfaceHolder) { 495 renderTarget?.detach(true) 496 destroyLatch.countDown() 497 } 498 } 499 surfaceView.holder.addCallback(callbacks) 500 renderTarget = renderer.attach(surfaceView, ColorRenderCallback(Color.RED)) 501 } 502 503 val tearDownLatch = CountDownLatch(1) 504 scenario.moveToState(State.DESTROYED) 505 assertTrue(destroyLatch.await(3000, TimeUnit.MILLISECONDS)) 506 renderer.stop(true) { tearDownLatch.countDown() } 507 assertTrue(tearDownLatch.await(3000, TimeUnit.MILLISECONDS)) 508 } 509 510 @Test 511 fun testMultipleTextureViewDestroyCallbacks() { 512 val destroyLatch = CountDownLatch(1) 513 val renderer = GLRenderer().apply { start() } 514 val scenario = withGLTestActivity { 515 assertNotNull(textureView) 516 517 val renderTarget = renderer.attach(textureView, ColorRenderCallback(Color.RED)) 518 val listener = textureView.surfaceTextureListener 519 textureView.surfaceTextureListener = 520 object : TextureView.SurfaceTextureListener { 521 override fun onSurfaceTextureAvailable(p0: SurfaceTexture, p1: Int, p2: Int) { 522 listener?.onSurfaceTextureAvailable(p0, p1, p2) 523 } 524 525 override fun onSurfaceTextureSizeChanged(p0: SurfaceTexture, p1: Int, p2: Int) { 526 listener?.onSurfaceTextureSizeChanged(p0, p1, p2) 527 } 528 529 override fun onSurfaceTextureDestroyed(p0: SurfaceTexture): Boolean { 530 renderTarget.detach(true) 531 listener?.onSurfaceTextureDestroyed(p0) 532 destroyLatch.countDown() 533 return true 534 } 535 536 override fun onSurfaceTextureUpdated(p0: SurfaceTexture) { 537 listener?.onSurfaceTextureUpdated(p0) 538 } 539 } 540 } 541 542 val tearDownLatch = CountDownLatch(1) 543 scenario.moveToState(State.DESTROYED) 544 assertTrue(destroyLatch.await(3000, TimeUnit.MILLISECONDS)) 545 renderer.stop(true) { tearDownLatch.countDown() } 546 assertTrue(tearDownLatch.await(3000, TimeUnit.MILLISECONDS)) 547 } 548 549 @Test 550 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) 551 fun testSurfaceViewAttach() { 552 withGLTestActivity { 553 assertNotNull(surfaceView) 554 555 val latch = CountDownLatch(1) 556 val glRenderer = GLRenderer().apply { start() } 557 val target = glRenderer.attach(surfaceView, ColorRenderCallback(Color.BLUE)) 558 559 target.requestRender { latch.countDown() } 560 561 assertTrue(latch.await(3000, TimeUnit.MILLISECONDS)) 562 563 val bitmap = 564 Bitmap.createBitmap( 565 GLTestActivity.TARGET_WIDTH, 566 GLTestActivity.TARGET_HEIGHT, 567 Bitmap.Config.ARGB_8888 568 ) 569 570 blockingPixelCopy(bitmap) { surfaceView.holder.surface } 571 572 assertTrue(bitmap.isAllColor(Color.BLUE)) 573 574 val stopLatch = CountDownLatch(1) 575 glRenderer.stop(true) { stopLatch.countDown() } 576 577 assertTrue(stopLatch.await(3000, TimeUnit.MILLISECONDS)) 578 // Assert that targets are detached when the GLRenderer is stopped 579 assertFalse(target.isAttached()) 580 } 581 } 582 583 @Test 584 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) 585 fun testSurfaceViewResumeRendersContent() { 586 var surfaceView: SurfaceView? = null 587 var glRenderer: GLRenderer? = null 588 var target: GLRenderer.RenderTarget? = null 589 val renderLatch = AtomicReference<CountDownLatch>(CountDownLatch(1)) 590 val targetColor = Color.BLUE 591 val scenario = 592 ActivityScenario.launch(GLTestActivity::class.java) 593 .moveToState(State.CREATED) 594 .onActivity { 595 surfaceView = it.surfaceView 596 597 assertNotNull(surfaceView) 598 599 glRenderer = GLRenderer().apply { start() } 600 target = 601 glRenderer!!.attach( 602 it.surfaceView, 603 ColorRenderCallback(targetColor) { renderLatch.get().countDown() } 604 ) 605 } 606 607 scenario.moveToState(State.RESUMED) 608 609 assertTrue(renderLatch.get().await(3000, TimeUnit.MILLISECONDS)) 610 611 val createLatch = CountDownLatch(1) 612 scenario.moveToState(State.CREATED).onActivity { createLatch.countDown() } 613 createLatch.await(3000, TimeUnit.MILLISECONDS) 614 615 renderLatch.set(CountDownLatch(1)) 616 scenario.moveToState(State.RESUMED) 617 618 assertTrue(renderLatch.get().await(3000, TimeUnit.MILLISECONDS)) 619 620 val coords = IntArray(2) 621 surfaceView!!.getLocationOnScreen(coords) 622 val surfaceWidth = surfaceView!!.width 623 val surfaceHeight = surfaceView!!.height 624 SurfaceControlUtils.validateOutput { bitmap -> 625 val pixel = bitmap.getPixel(coords[0] + surfaceWidth / 2, coords[1] + surfaceHeight / 2) 626 val red = Color.red(pixel) 627 val green = Color.green(pixel) 628 val blue = Color.blue(pixel) 629 630 val redExpected = Color.red(targetColor) 631 val greenExpected = Color.green(targetColor) 632 val blueExpected = Color.blue(targetColor) 633 634 Math.abs(red - redExpected) / 255f < 0.2f && 635 Math.abs(green - greenExpected) / 255f < 0.2f && 636 Math.abs(blue - blueExpected) / 255f < 0.2f 637 } 638 639 val stopLatch = CountDownLatch(1) 640 glRenderer!!.stop(true) { stopLatch.countDown() } 641 642 assertTrue(stopLatch.await(3000, TimeUnit.MILLISECONDS)) 643 // Assert that targets are detached when the GLRenderer is stopped 644 assertFalse(target!!.isAttached()) 645 } 646 647 @Test 648 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) 649 fun testTextureViewRendersAfterSurfaceTextureAvailableCallback() { 650 var textureView: TextureView? = null 651 var glRenderer: GLRenderer? = null 652 var target: GLRenderer.RenderTarget? = null 653 val renderLatch = AtomicReference<CountDownLatch>(CountDownLatch(1)) 654 val textureAvailableLatch = CountDownLatch(1) 655 val scenario = 656 ActivityScenario.launch(GLTestActivity::class.java) 657 .moveToState(State.CREATED) 658 .onActivity { 659 textureView = TextureView(it).apply { it.setContentView(this) } 660 661 assertFalse(textureView!!.isAvailable) 662 663 glRenderer = GLRenderer().apply { start() } 664 target = 665 glRenderer!!.attach( 666 textureView!!, 667 ColorRenderCallback(Color.BLUE) { renderLatch.get().countDown() } 668 ) 669 670 val listener = textureView!!.surfaceTextureListener 671 textureView!!.surfaceTextureListener = 672 object : SurfaceTextureListener { 673 override fun onSurfaceTextureAvailable( 674 surface: SurfaceTexture, 675 width: Int, 676 height: Int 677 ) { 678 listener?.onSurfaceTextureAvailable(surface, width, height) 679 textureAvailableLatch.countDown() 680 } 681 682 override fun onSurfaceTextureSizeChanged( 683 surface: SurfaceTexture, 684 width: Int, 685 height: Int 686 ) { 687 listener?.onSurfaceTextureSizeChanged(surface, width, height) 688 } 689 690 override fun onSurfaceTextureDestroyed( 691 surface: SurfaceTexture 692 ): Boolean { 693 return listener?.onSurfaceTextureDestroyed(surface) ?: true 694 } 695 696 override fun onSurfaceTextureUpdated(surface: SurfaceTexture) { 697 listener?.onSurfaceTextureUpdated(surface) 698 } 699 } 700 } 701 702 scenario.moveToState(State.RESUMED) 703 704 assertTrue(renderLatch.get().await(3000, TimeUnit.MILLISECONDS)) 705 706 assertTrue(textureAvailableLatch.await(3000, TimeUnit.MILLISECONDS)) 707 708 val coords = IntArray(2) 709 textureView!!.getLocationOnScreen(coords) 710 SurfaceControlUtils.validateOutput { bitmap -> 711 Color.BLUE == 712 bitmap.getPixel(coords[0] + bitmap.width / 2, coords[1] + bitmap.height / 2) 713 } 714 715 val stopLatch = CountDownLatch(1) 716 glRenderer!!.stop(true) { stopLatch.countDown() } 717 718 assertTrue(stopLatch.await(3000, TimeUnit.MILLISECONDS)) 719 // Assert that targets are detached when the GLRenderer is stopped 720 assertFalse(target!!.isAttached()) 721 } 722 723 @Test 724 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) 725 fun testTextureViewRendersAfterSurfaceTextureAvailable() { 726 var textureView: TextureView? = null 727 var glRenderer: GLRenderer? = null 728 var target: GLRenderer.RenderTarget? = null 729 val renderLatch = AtomicReference<CountDownLatch>(CountDownLatch(1)) 730 val scenario = 731 ActivityScenario.launch(GLTestActivity::class.java) 732 .moveToState(State.CREATED) 733 .onActivity { 734 textureView = it.textureView 735 736 assertNotNull(textureView) 737 738 glRenderer = GLRenderer().apply { start() } 739 target = glRenderer!!.attach(it.textureView, ColorRenderCallback(Color.BLUE)) 740 val listener = textureView!!.surfaceTextureListener 741 textureView!!.surfaceTextureListener = 742 object : SurfaceTextureListener { 743 override fun onSurfaceTextureAvailable( 744 surface: SurfaceTexture, 745 width: Int, 746 height: Int 747 ) { 748 listener?.onSurfaceTextureAvailable(surface, width, height) 749 } 750 751 override fun onSurfaceTextureSizeChanged( 752 surface: SurfaceTexture, 753 width: Int, 754 height: Int 755 ) { 756 listener?.onSurfaceTextureSizeChanged(surface, width, height) 757 } 758 759 override fun onSurfaceTextureDestroyed( 760 surface: SurfaceTexture 761 ): Boolean { 762 return listener?.onSurfaceTextureDestroyed(surface) ?: true 763 } 764 765 override fun onSurfaceTextureUpdated(surface: SurfaceTexture) { 766 listener?.onSurfaceTextureUpdated(surface) 767 renderLatch.get().countDown() 768 } 769 } 770 } 771 772 scenario.moveToState(State.RESUMED) 773 774 assertTrue(renderLatch.get().await(3000, TimeUnit.MILLISECONDS)) 775 776 val coords = IntArray(2) 777 textureView!!.getLocationOnScreen(coords) 778 SurfaceControlUtils.validateOutput { bitmap -> 779 Color.BLUE == 780 bitmap.getPixel( 781 coords[0] + textureView!!.width / 2, 782 coords[1] + textureView!!.height / 2 783 ) 784 } 785 786 val stopLatch = CountDownLatch(1) 787 glRenderer!!.stop(true) { stopLatch.countDown() } 788 789 assertTrue(stopLatch.await(3000, TimeUnit.MILLISECONDS)) 790 // Assert that targets are detached when the GLRenderer is stopped 791 assertFalse(target!!.isAttached()) 792 } 793 794 @Test 795 fun testTextureViewOnResizeCalled() { 796 withGLTestActivity { 797 assertNotNull(textureView) 798 val glRenderer = GLRenderer().apply { start() } 799 800 val resizeLatch = CountDownLatch(1) 801 val target = 802 glRenderer.attach( 803 textureView, 804 object : GLRenderer.RenderCallback { 805 override fun onDrawFrame(eglManager: EGLManager) { 806 val size = 807 eglManager.eglSpec.querySurfaceSize(eglManager.currentDrawSurface) 808 assertTrue(size.width > 0) 809 assertTrue(size.height > 0) 810 resizeLatch.countDown() 811 } 812 } 813 ) 814 target.requestRender() 815 816 assertTrue(resizeLatch.await(3000, TimeUnit.MILLISECONDS)) 817 818 val detachLatch = CountDownLatch(1) 819 target.detach(false) { detachLatch.countDown() } 820 assertTrue(detachLatch.await(3000, TimeUnit.MILLISECONDS)) 821 glRenderer.stop(true) 822 } 823 } 824 825 @Test 826 fun testSurfaceViewOnResizeCalled() { 827 withGLTestActivity { 828 assertNotNull(surfaceView) 829 val glRenderer = GLRenderer().apply { start() } 830 831 val resizeLatch = CountDownLatch(1) 832 val target = 833 glRenderer.attach( 834 surfaceView, 835 object : GLRenderer.RenderCallback { 836 override fun onDrawFrame(eglManager: EGLManager) { 837 val size = 838 eglManager.eglSpec.querySurfaceSize(eglManager.currentDrawSurface) 839 assertTrue(size.width > 0) 840 assertTrue(size.height > 0) 841 resizeLatch.countDown() 842 } 843 } 844 ) 845 target.requestRender() 846 847 assertTrue(resizeLatch.await(3000, TimeUnit.MILLISECONDS)) 848 849 val detachLatch = CountDownLatch(1) 850 target.detach(false) { detachLatch.countDown() } 851 assertTrue(detachLatch.await(3000, TimeUnit.MILLISECONDS)) 852 glRenderer.stop(true) 853 } 854 } 855 856 data class Size(val width: Int, val height: Int) 857 858 fun EGLSpec.querySurfaceSize(eglSurface: EGLSurface): Size { 859 val result = IntArray(1) 860 eglQuerySurface(eglSurface, EGL14.EGL_WIDTH, result, 0) 861 val width = result[0] 862 eglQuerySurface(eglSurface, EGL14.EGL_HEIGHT, result, 0) 863 val height = result[0] 864 return Size(width, height) 865 } 866 867 @Test 868 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N) 869 fun testTextureViewAttach() { 870 withGLTestActivity { 871 assertNotNull(textureView) 872 873 val latch = CountDownLatch(1) 874 val glRenderer = GLRenderer().apply { start() } 875 val target = glRenderer.attach(textureView, ColorRenderCallback(Color.BLUE)) 876 target.requestRender { latch.countDown() } 877 assertTrue(latch.await(3000, TimeUnit.MILLISECONDS)) 878 879 val bitmap = 880 Bitmap.createBitmap( 881 GLTestActivity.TARGET_WIDTH, 882 GLTestActivity.TARGET_HEIGHT, 883 Bitmap.Config.ARGB_8888 884 ) 885 886 blockingPixelCopy(bitmap) { Surface(textureView.surfaceTexture) } 887 assertTrue(bitmap.isAllColor(Color.BLUE)) 888 889 val stopLatch = CountDownLatch(1) 890 glRenderer.stop(true) { stopLatch.countDown() } 891 892 assertTrue(stopLatch.await(3000, TimeUnit.MILLISECONDS)) 893 // Assert that targets are detached when the GLRenderer is stopped 894 assertFalse(target.isAttached()) 895 } 896 } 897 898 @Test 899 fun testEGLContextCallbackInvoked() { 900 val createdLatch = CountDownLatch(1) 901 val destroyedLatch = CountDownLatch(1) 902 val createCount = AtomicInteger() 903 val destroyCount = AtomicInteger() 904 val callback = 905 object : GLRenderer.EGLContextCallback { 906 907 override fun onEGLContextCreated(eglManager: EGLManager) { 908 createCount.incrementAndGet() 909 createdLatch.countDown() 910 } 911 912 override fun onEGLContextDestroyed(eglManager: EGLManager) { 913 destroyCount.incrementAndGet() 914 destroyedLatch.countDown() 915 } 916 } 917 918 val glRenderer = GLRenderer().apply { start() } 919 glRenderer.registerEGLContextCallback(callback) 920 921 glRenderer 922 .attach(Surface(SurfaceTexture(12)), 10, 10, ColorRenderCallback(Color.RED)) 923 .requestRender() 924 925 assertTrue(createdLatch.await(3000, TimeUnit.MILLISECONDS)) 926 assertEquals(1, createCount.get()) 927 928 glRenderer.stop(true) 929 930 assertTrue(destroyedLatch.await(3000, TimeUnit.MILLISECONDS)) 931 assertEquals(1, destroyCount.get()) 932 } 933 934 @Test 935 fun testEGLContextCallbackInvokedBeforeStart() { 936 val createdLatch = CountDownLatch(1) 937 val destroyedLatch = CountDownLatch(1) 938 val createCount = AtomicInteger() 939 val destroyCount = AtomicInteger() 940 val callback = 941 object : GLRenderer.EGLContextCallback { 942 943 override fun onEGLContextCreated(eglManager: EGLManager) { 944 createCount.incrementAndGet() 945 createdLatch.countDown() 946 } 947 948 override fun onEGLContextDestroyed(eglManager: EGLManager) { 949 destroyCount.incrementAndGet() 950 destroyedLatch.countDown() 951 } 952 } 953 954 val glRenderer = GLRenderer() 955 // Adding a callback before the glRenderer is started should still 956 // deliver onEGLRendererCreated callbacks 957 glRenderer.registerEGLContextCallback(callback) 958 glRenderer.start() 959 960 glRenderer 961 .attach(Surface(SurfaceTexture(12)), 10, 10, ColorRenderCallback(Color.CYAN)) 962 .requestRender() 963 964 assertTrue(createdLatch.await(3000, TimeUnit.MILLISECONDS)) 965 assertEquals(1, createCount.get()) 966 967 glRenderer.stop(true) 968 969 assertTrue(destroyedLatch.await(3000, TimeUnit.MILLISECONDS)) 970 assertEquals(1, destroyCount.get()) 971 } 972 973 @Test 974 fun testEGLContextCallbackRemove() { 975 val createdLatch = CountDownLatch(1) 976 val destroyedLatch = CountDownLatch(1) 977 val createCount = AtomicInteger() 978 val destroyCount = AtomicInteger() 979 val callback = 980 object : GLRenderer.EGLContextCallback { 981 982 override fun onEGLContextCreated(eglManager: EGLManager) { 983 createCount.incrementAndGet() 984 createdLatch.countDown() 985 } 986 987 override fun onEGLContextDestroyed(eglManager: EGLManager) { 988 destroyCount.incrementAndGet() 989 } 990 } 991 992 val glRenderer = GLRenderer() 993 // Adding a callback before the glRenderer is started should still 994 // deliver onEGLRendererCreated callbacks 995 glRenderer.registerEGLContextCallback(callback) 996 glRenderer.start() 997 998 glRenderer 999 .attach(Surface(SurfaceTexture(12)), 10, 10, ColorRenderCallback(Color.CYAN)) 1000 .requestRender() 1001 1002 assertTrue(createdLatch.await(3000, TimeUnit.MILLISECONDS)) 1003 assertEquals(1, createCount.get()) 1004 1005 glRenderer.unregisterEGLContextCallback(callback) 1006 1007 glRenderer.stop(false) { destroyedLatch.countDown() } 1008 1009 assertTrue(destroyedLatch.await(3000, TimeUnit.MILLISECONDS)) 1010 assertEquals(0, destroyCount.get()) 1011 } 1012 1013 @Test 1014 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q) 1015 fun testRenderBufferTarget() { 1016 val width = 10 1017 val height = 10 1018 val renderLatch = CountDownLatch(1) 1019 val teardownLatch = CountDownLatch(1) 1020 val glRenderer = GLRenderer().apply { start() } 1021 var frameBuffer: FrameBuffer? = null 1022 1023 val supportsNativeFence = AtomicBoolean(false) 1024 glRenderer 1025 .createRenderTarget( 1026 width, 1027 height, 1028 object : GLRenderer.RenderCallback { 1029 1030 @WorkerThread 1031 override fun onDrawFrame(eglManager: EGLManager) { 1032 if (eglManager.supportsNativeAndroidFence()) { 1033 supportsNativeFence.set(true) 1034 var syncFenceCompat: SyncFenceCompat? = null 1035 try { 1036 val egl = eglManager.eglSpec 1037 val buffer = 1038 FrameBuffer( 1039 egl, 1040 HardwareBuffer.create( 1041 width, 1042 height, 1043 HardwareBuffer.RGBA_8888, 1044 1, 1045 HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE 1046 ) 1047 ) 1048 .also { frameBuffer = it } 1049 buffer.makeCurrent() 1050 GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f) 1051 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) 1052 GLES20.glFlush() 1053 syncFenceCompat = SyncFenceCompat.createNativeSyncFence() 1054 syncFenceCompat.await(TimeUnit.SECONDS.toNanos(3)) 1055 } finally { 1056 syncFenceCompat?.close() 1057 } 1058 } 1059 renderLatch.countDown() 1060 } 1061 } 1062 ) 1063 .requestRender() 1064 1065 var hardwareBuffer: HardwareBuffer? = null 1066 try { 1067 assertTrue(renderLatch.await(3000, TimeUnit.MILLISECONDS)) 1068 if (supportsNativeFence.get()) { 1069 hardwareBuffer = frameBuffer?.hardwareBuffer 1070 if (hardwareBuffer != null) { 1071 val colorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_SRGB) 1072 // Copy to non hardware bitmap to be able to sample pixels 1073 val bitmap = 1074 Bitmap.wrapHardwareBuffer(hardwareBuffer, colorSpace) 1075 ?.copy(Bitmap.Config.ARGB_8888, false) 1076 if (bitmap != null) { 1077 assertTrue(bitmap.isAllColor(Color.RED)) 1078 } else { 1079 fail("Unable to obtain Bitmap from hardware buffer") 1080 } 1081 } else { 1082 fail("Unable to obtain hardwarebuffer from FrameBuffer") 1083 } 1084 } 1085 } finally { 1086 hardwareBuffer?.close() 1087 glRenderer.stop(true) { teardownLatch.countDown() } 1088 assertTrue(teardownLatch.await(3000, TimeUnit.MILLISECONDS)) 1089 } 1090 } 1091 1092 @Test 1093 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q) 1094 fun testFrontBufferedRenderer() { 1095 val width = 10 1096 val height = 10 1097 val renderLatch = CountDownLatch(1) 1098 val teardownLatch = CountDownLatch(1) 1099 val glRenderer = GLRenderer().apply { start() } 1100 var frameBuffer: FrameBuffer? = null 1101 var status = false 1102 var supportsFence = false 1103 1104 val callbacks = 1105 object : FrameBufferRenderer.RenderCallback { 1106 1107 private val mOrthoMatrix = FloatArray(16) 1108 1109 override fun obtainFrameBuffer(egl: EGLSpec): FrameBuffer = 1110 FrameBuffer( 1111 egl, 1112 HardwareBuffer.create( 1113 width, 1114 height, 1115 HardwareBuffer.RGBA_8888, 1116 1, 1117 HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE 1118 ) 1119 ) 1120 .also { frameBuffer = it } 1121 1122 override fun onDraw(eglManager: EGLManager) { 1123 GLES20.glViewport(0, 0, width, height) 1124 Matrix.orthoM( 1125 mOrthoMatrix, 1126 0, 1127 0f, 1128 width.toFloat(), 1129 0f, 1130 height.toFloat(), 1131 -1f, 1132 1f 1133 ) 1134 Rectangle() 1135 .draw(mOrthoMatrix, Color.RED, 0f, 0f, width.toFloat(), height.toFloat()) 1136 supportsFence = eglManager.supportsNativeAndroidFence() 1137 } 1138 1139 override fun onDrawComplete( 1140 frameBuffer: FrameBuffer, 1141 syncFenceCompat: SyncFenceCompat? 1142 ) { 1143 status = syncFenceCompat?.await(3000) ?: true 1144 renderLatch.countDown() 1145 } 1146 } 1147 1148 glRenderer.createRenderTarget(width, height, FrameBufferRenderer(callbacks)).requestRender() 1149 1150 var hardwareBuffer: HardwareBuffer? = null 1151 try { 1152 assertTrue(renderLatch.await(3000, TimeUnit.MILLISECONDS)) 1153 if (supportsFence) { 1154 assert(status) 1155 } 1156 1157 hardwareBuffer = frameBuffer?.hardwareBuffer 1158 if (hardwareBuffer != null) { 1159 val colorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_SRGB) 1160 // Copy to non hardware bitmap to be able to sample pixels 1161 val bitmap = 1162 Bitmap.wrapHardwareBuffer(hardwareBuffer, colorSpace) 1163 ?.copy(Bitmap.Config.ARGB_8888, false) 1164 if (bitmap != null) { 1165 assertTrue(bitmap.isAllColor(Color.RED)) 1166 } else { 1167 fail("Unable to obtain Bitmap from hardware buffer") 1168 } 1169 } else { 1170 fail("Unable to obtain hardwarebuffer from FrameBuffer") 1171 } 1172 } finally { 1173 hardwareBuffer?.close() 1174 glRenderer.stop(true) { teardownLatch.countDown() } 1175 assertTrue(teardownLatch.await(3000, TimeUnit.MILLISECONDS)) 1176 } 1177 } 1178 1179 @Test 1180 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q) 1181 fun testQuadTextureRenderer() { 1182 val width = 10 1183 val height = 10 1184 val renderLatch = CountDownLatch(1) 1185 val teardownLatch = CountDownLatch(1) 1186 val glRenderer = GLRenderer().apply { start() } 1187 var frameBuffer: FrameBuffer? = null 1188 var status = false 1189 var supportsFence = false 1190 val frameHandlerThread = HandlerThread("frameAvailable").apply { start() } 1191 val frameHandler = Handler(frameHandlerThread.looper) 1192 val callbacks = 1193 object : FrameBufferRenderer.RenderCallback { 1194 1195 private val mOrthoMatrix = FloatArray(16) 1196 1197 override fun obtainFrameBuffer(egl: EGLSpec): FrameBuffer = 1198 FrameBuffer( 1199 egl, 1200 HardwareBuffer.create( 1201 width, 1202 height, 1203 HardwareBuffer.RGBA_8888, 1204 1, 1205 HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE 1206 ) 1207 ) 1208 .also { frameBuffer = it } 1209 1210 override fun onDraw(eglManager: EGLManager) { 1211 val texId = genTexture() 1212 val frameAvailableLatch = CountDownLatch(1) 1213 val surfaceTexture = 1214 SurfaceTexture(texId).apply { 1215 setDefaultBufferSize(width, height) 1216 setOnFrameAvailableListener( 1217 { frameAvailableLatch.countDown() }, 1218 frameHandler 1219 ) 1220 } 1221 1222 val surface = Surface(surfaceTexture) 1223 val canvas = surface.lockCanvas(null) 1224 canvas.save() 1225 val paint = Paint() 1226 // top left 1227 canvas.drawRect( 1228 0f, 1229 0f, 1230 width / 2f, 1231 height / 2f, 1232 paint.apply { color = Color.RED } 1233 ) 1234 // top right 1235 canvas.drawRect( 1236 width / 2f, 1237 0f, 1238 width.toFloat(), 1239 height / 2f, 1240 paint.apply { color = Color.BLUE } 1241 ) 1242 // bottom left 1243 canvas.drawRect( 1244 0f, 1245 height / 2f, 1246 width / 2f, 1247 height.toFloat(), 1248 paint.apply { color = Color.YELLOW } 1249 ) 1250 // bottom right 1251 canvas.drawRect( 1252 width / 2f, 1253 height / 2f, 1254 width.toFloat(), 1255 height.toFloat(), 1256 paint.apply { color = Color.GREEN } 1257 ) 1258 canvas.restore() 1259 surface.unlockCanvasAndPost(canvas) 1260 1261 assertTrue(frameAvailableLatch.await(3000, TimeUnit.MILLISECONDS)) 1262 1263 GLES20.glViewport(0, 0, width, height) 1264 Matrix.orthoM( 1265 mOrthoMatrix, 1266 0, 1267 0f, 1268 width.toFloat(), 1269 0f, 1270 height.toFloat(), 1271 -1f, 1272 1f 1273 ) 1274 val quadRenderer = 1275 QuadTextureRenderer().apply { setSurfaceTexture(surfaceTexture) } 1276 quadRenderer.draw(mOrthoMatrix, width.toFloat(), height.toFloat()) 1277 // See: b/236394768 Workaround for ANGLE issue where FBOs with HardwareBuffer 1278 GLES20.glFinish() 1279 supportsFence = eglManager.supportsNativeAndroidFence() 1280 quadRenderer.release() 1281 surface.release() 1282 surfaceTexture.release() 1283 deleteTexture(texId) 1284 } 1285 1286 override fun onDrawComplete( 1287 frameBuffer: FrameBuffer, 1288 syncFenceCompat: SyncFenceCompat? 1289 ) { 1290 status = syncFenceCompat?.await(3000) ?: true 1291 renderLatch.countDown() 1292 } 1293 } 1294 1295 glRenderer.createRenderTarget(width, height, FrameBufferRenderer(callbacks)).requestRender() 1296 1297 var hardwareBuffer: HardwareBuffer? = null 1298 try { 1299 assertTrue(renderLatch.await(3000, TimeUnit.MILLISECONDS)) 1300 if (supportsFence) { 1301 assertTrue(status) 1302 } 1303 1304 hardwareBuffer = frameBuffer?.hardwareBuffer 1305 if (hardwareBuffer != null) { 1306 val colorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_SRGB) 1307 // Copy to non hardware bitmap to be able to sample pixels 1308 val bitmap = 1309 Bitmap.wrapHardwareBuffer(hardwareBuffer, colorSpace) 1310 ?.copy(Bitmap.Config.ARGB_8888, false) 1311 if (bitmap != null) { 1312 bitmap.verifyQuadrants(Color.RED, Color.BLUE, Color.YELLOW, Color.GREEN) 1313 } else { 1314 fail("Unable to obtain Bitmap from hardware buffer") 1315 } 1316 } else { 1317 fail("Unable to obtain hardwarebuffer from FrameBuffer") 1318 } 1319 } finally { 1320 hardwareBuffer?.close() 1321 glRenderer.stop(true) { teardownLatch.countDown() } 1322 frameHandlerThread.quit() 1323 assertTrue(teardownLatch.await(3000, TimeUnit.MILLISECONDS)) 1324 } 1325 } 1326 1327 @Test 1328 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q) 1329 fun testRenderSurfaceTextureFromSurfaceTextureRenderer() { 1330 val width = 10 1331 val height = 10 1332 val renderLatch = CountDownLatch(1) 1333 val teardownLatch = CountDownLatch(1) 1334 val glRenderer = GLRenderer().apply { start() } 1335 var frameBuffer: FrameBuffer? = null 1336 var status = false 1337 var supportsFence = false 1338 val frameHandlerThread = HandlerThread("frameAvailable").apply { start() } 1339 val frameHandler = Handler(frameHandlerThread.looper) 1340 val renderNode = 1341 RenderNode("node").apply { 1342 setPosition(0, 0, width, height) 1343 val canvas = beginRecording() 1344 val paint = Paint() 1345 // top left 1346 canvas.drawRect(0f, 0f, width / 2f, height / 2f, paint.apply { color = Color.RED }) 1347 // top right 1348 canvas.drawRect( 1349 width / 2f, 1350 0f, 1351 width.toFloat(), 1352 height / 2f, 1353 paint.apply { color = Color.BLUE } 1354 ) 1355 // bottom left 1356 canvas.drawRect( 1357 0f, 1358 height / 2f, 1359 width / 2f, 1360 height.toFloat(), 1361 paint.apply { color = Color.YELLOW } 1362 ) 1363 // bottom right 1364 canvas.drawRect( 1365 width / 2f, 1366 height / 2f, 1367 width.toFloat(), 1368 height.toFloat(), 1369 paint.apply { color = Color.GREEN } 1370 ) 1371 endRecording() 1372 } 1373 val frameAvailableLatch = CountDownLatch(1) 1374 var surfaceTexture: SurfaceTexture? = null 1375 val surfaceTextureRenderer = 1376 SurfaceTextureRenderer(renderNode, width, height, frameHandler) { 1377 surfaceTexture = it 1378 frameAvailableLatch.countDown() 1379 } 1380 surfaceTextureRenderer.renderFrame() 1381 1382 val callbacks = 1383 object : FrameBufferRenderer.RenderCallback { 1384 1385 private val mOrthoMatrix = FloatArray(16) 1386 1387 override fun obtainFrameBuffer(egl: EGLSpec): FrameBuffer = 1388 FrameBuffer( 1389 egl, 1390 HardwareBuffer.create( 1391 width, 1392 height, 1393 HardwareBuffer.RGBA_8888, 1394 1, 1395 HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE 1396 ) 1397 ) 1398 .also { frameBuffer = it } 1399 1400 override fun onDraw(eglManager: EGLManager) { 1401 val texId = genTexture() 1402 assertTrue(frameAvailableLatch.await(3000, TimeUnit.MILLISECONDS)) 1403 assertNotNull(surfaceTexture) 1404 surfaceTexture!!.attachToGLContext(texId) 1405 1406 GLES20.glViewport(0, 0, width, height) 1407 Matrix.orthoM( 1408 mOrthoMatrix, 1409 0, 1410 0f, 1411 width.toFloat(), 1412 0f, 1413 height.toFloat(), 1414 -1f, 1415 1f 1416 ) 1417 val quadRenderer = 1418 QuadTextureRenderer().apply { setSurfaceTexture(surfaceTexture!!) } 1419 quadRenderer.draw(mOrthoMatrix, width.toFloat(), height.toFloat()) 1420 // See: b/236394768 Workaround for ANGLE issue where FBOs with HardwareBuffer 1421 GLES20.glFinish() 1422 supportsFence = eglManager.supportsNativeAndroidFence() 1423 quadRenderer.release() 1424 deleteTexture(texId) 1425 } 1426 1427 override fun onDrawComplete( 1428 frameBuffer: FrameBuffer, 1429 syncFenceCompat: SyncFenceCompat? 1430 ) { 1431 status = syncFenceCompat?.await(3000) ?: true 1432 renderLatch.countDown() 1433 } 1434 } 1435 1436 glRenderer.createRenderTarget(width, height, FrameBufferRenderer(callbacks)).requestRender() 1437 1438 var hardwareBuffer: HardwareBuffer? = null 1439 try { 1440 assertTrue(renderLatch.await(3000, TimeUnit.MILLISECONDS)) 1441 if (supportsFence) { 1442 assertTrue(status) 1443 } 1444 1445 hardwareBuffer = frameBuffer?.hardwareBuffer 1446 if (hardwareBuffer != null) { 1447 val colorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_SRGB) 1448 // Copy to non hardware bitmap to be able to sample pixels 1449 val bitmap = 1450 Bitmap.wrapHardwareBuffer(hardwareBuffer, colorSpace) 1451 ?.copy(Bitmap.Config.ARGB_8888, false) 1452 if (bitmap != null) { 1453 bitmap.verifyQuadrants(Color.RED, Color.BLUE, Color.YELLOW, Color.GREEN) 1454 } else { 1455 fail("Unable to obtain Bitmap from hardware buffer") 1456 } 1457 } else { 1458 fail("Unable to obtain hardwarebuffer from FrameBuffer") 1459 } 1460 } finally { 1461 surfaceTextureRenderer.release() 1462 hardwareBuffer?.close() 1463 glRenderer.stop(true) { teardownLatch.countDown() } 1464 frameHandlerThread.quit() 1465 assertTrue(teardownLatch.await(3000, TimeUnit.MILLISECONDS)) 1466 } 1467 } 1468 1469 @Test 1470 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q) 1471 fun testFrameBufferRendererWithSyncFence() { 1472 1473 val width = 10 1474 val height = 10 1475 val renderLatch = CountDownLatch(1) 1476 val teardownLatch = CountDownLatch(1) 1477 1478 val glRenderer = GLRenderer().apply { start() } 1479 var startTime = Long.MAX_VALUE 1480 var signalTime = 0L 1481 1482 var supportsFence = false 1483 val renderer = 1484 object : FrameBufferRenderer.RenderCallback, GLRenderer.EGLContextCallback { 1485 private val mMVPMatrix = FloatArray(16) 1486 private val mLines = FloatArray(4) 1487 private val mLineRenderer = LineRenderer() 1488 var mFrameBuffer: FrameBuffer? = null 1489 1490 @WorkerThread 1491 override fun onEGLContextCreated(eglManager: EGLManager) { 1492 mLineRenderer.initialize() 1493 } 1494 1495 @WorkerThread 1496 override fun onEGLContextDestroyed(eglManager: EGLManager) { 1497 mLineRenderer.release() 1498 } 1499 1500 @WorkerThread 1501 override fun obtainFrameBuffer(egl: EGLSpec): FrameBuffer { 1502 return if (mFrameBuffer != null) { 1503 mFrameBuffer!! 1504 } else { 1505 FrameBuffer( 1506 egl, 1507 HardwareBuffer.create( 1508 width, 1509 height, 1510 HardwareBuffer.RGBA_8888, 1511 1, 1512 HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE 1513 ) 1514 ) 1515 .also { mFrameBuffer = it } 1516 } 1517 } 1518 1519 @WorkerThread 1520 override fun onDraw(eglManager: EGLManager) { 1521 startTime = System.nanoTime() 1522 GLES20.glViewport(0, 0, width, height) 1523 assertEquals(GLES20.GL_NO_ERROR, GLES20.glGetError()) 1524 Matrix.orthoM(mMVPMatrix, 0, 0f, width.toFloat(), 0f, height.toFloat(), -1f, 1f) 1525 mLines[0] = 0f 1526 mLines[1] = 0f 1527 mLines[2] = 5f 1528 mLines[3] = 5f 1529 mLineRenderer.drawLines(mMVPMatrix, mLines) 1530 assertEquals(GLES20.GL_NO_ERROR, GLES20.glGetError()) 1531 1532 supportsFence = eglManager.supportsNativeAndroidFence() 1533 } 1534 1535 @WorkerThread 1536 override fun onDrawComplete( 1537 frameBuffer: FrameBuffer, 1538 syncFenceCompat: SyncFenceCompat? 1539 ) { 1540 if (supportsFence) { 1541 assertNotNull(syncFenceCompat) 1542 assertTrue(syncFenceCompat!!.isValid()) 1543 assertTrue(syncFenceCompat.await(3000)) 1544 signalTime = syncFenceCompat.getSignalTimeNanos() 1545 1546 assertTrue(syncFenceCompat.getSignalTimeNanos() < System.nanoTime()) 1547 assertTrue(syncFenceCompat.getSignalTimeNanos() > startTime) 1548 } 1549 renderLatch.countDown() 1550 1551 assertEquals(GLES20.GL_NO_ERROR, GLES20.glGetError()) 1552 } 1553 } 1554 1555 glRenderer.registerEGLContextCallback(renderer) 1556 val hwBufferRenderer = FrameBufferRenderer(renderer) 1557 val renderTarget = glRenderer.createRenderTarget(width, height, hwBufferRenderer) 1558 1559 renderTarget.requestRender() 1560 assertEquals(GLES20.GL_NO_ERROR, GLES20.glGetError()) 1561 1562 try { 1563 assertTrue(renderLatch.await(3000, TimeUnit.MILLISECONDS)) 1564 if (supportsFence) { 1565 assertTrue(startTime < signalTime) 1566 assertTrue(signalTime < System.nanoTime()) 1567 } 1568 } finally { 1569 glRenderer.stop(true) { teardownLatch.countDown() } 1570 assertTrue(teardownLatch.await(3000, TimeUnit.MILLISECONDS)) 1571 } 1572 } 1573 1574 /** 1575 * Helper method to create a GLTestActivity instance and progress it through the Activity 1576 * lifecycle to the resumed state so we can issue rendering commands into the corresponding 1577 * SurfaceView/TextureView 1578 */ 1579 private fun withGLTestActivity( 1580 block: GLTestActivity.() -> Unit 1581 ): ActivityScenario<GLTestActivity> = 1582 ActivityScenario.launch(GLTestActivity::class.java).moveToState(State.RESUMED).onActivity { 1583 block(it!!) 1584 } 1585 1586 /** 1587 * Helper RenderCallback that renders a solid color and invokes the provided CountdownLatch when 1588 * rendering is complete 1589 */ 1590 private class ColorRenderCallback(val targetColor: Int, val drawCallback: () -> Unit = {}) : 1591 GLRenderer.RenderCallback { 1592 1593 override fun onDrawFrame(eglManager: EGLManager) { 1594 GLES20.glClearColor( 1595 Color.red(targetColor) / 255f, 1596 Color.green(targetColor) / 255f, 1597 Color.blue(targetColor) / 255f, 1598 Color.alpha(targetColor) / 255f, 1599 ) 1600 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) 1601 GLES20.glFinish() 1602 drawCallback.invoke() 1603 } 1604 } 1605 1606 /** Helper method that synchronously blocks until the PixelCopy operation is complete */ 1607 @RequiresApi(Build.VERSION_CODES.N) 1608 private fun blockingPixelCopy(destBitmap: Bitmap, surfaceProvider: () -> Surface) { 1609 val copyLatch = CountDownLatch(1) 1610 val copyThread = HandlerThread("copyThread").apply { start() } 1611 val copyHandler = Handler(copyThread.looper) 1612 PixelCopy.request( 1613 surfaceProvider.invoke(), 1614 destBitmap, 1615 { copyResult -> 1616 assertEquals(PixelCopy.SUCCESS, copyResult) 1617 copyLatch.countDown() 1618 copyThread.quit() 1619 }, 1620 copyHandler 1621 ) 1622 assertTrue(copyLatch.await(3000, TimeUnit.MILLISECONDS)) 1623 } 1624 1625 private fun genTexture(): Int { 1626 val buffer = IntArray(1) 1627 GLES20.glGenTextures(1, buffer, 0) 1628 return buffer[0] 1629 } 1630 1631 private fun deleteTexture(texId: Int) { 1632 val buffer = IntArray(1) 1633 buffer[0] = texId 1634 GLES20.glDeleteTextures(1, buffer, 0) 1635 } 1636 } 1637