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