1 /*
2  * 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.SurfaceTexture
20 import android.opengl.EGL14
21 import android.opengl.EGLConfig
22 import android.opengl.EGLSurface
23 import android.view.Surface
24 import android.view.SurfaceHolder
25 import android.view.SurfaceView
26 import android.view.TextureView
27 import androidx.annotation.WorkerThread
28 import androidx.graphics.opengl.egl.EGLConfigAttributes
29 import androidx.graphics.opengl.egl.EGLManager
30 import androidx.graphics.opengl.egl.EGLSpec
31 import java.util.concurrent.CountDownLatch
32 import java.util.concurrent.atomic.AtomicInteger
33 
34 /**
35  * Class responsible for coordination of requests to render into surfaces using OpenGL. This creates
36  * a backing thread to handle EGL dependencies and draw leveraging OpenGL across multiple
37  * [android.view.Surface] instances that can be attached and detached throughout the lifecycle of an
38  * application. Usage of this class is recommended to be done on the UI thread.
39  *
40  * @param eglSpecFactory Callback invoked to determine the EGL spec version to use for EGL
41  *   management. This is invoked on the GL Thread
42  * @param eglConfigFactory Callback invoked to determine the appropriate EGLConfig used to create
43  *   the EGL context. This is invoked on the GL Thread
44  */
45 // GL is the industry standard for referencing OpenGL vs Gl (lowercase l)
46 class GLRenderer(
<lambda>null47     eglSpecFactory: () -> EGLSpec = { EGLSpec.V14 },
<lambda>null48     eglConfigFactory: EGLManager.() -> EGLConfig = {
49         // 8 bit channels should always be supported
50         loadConfig(EGLConfigAttributes.RGBA_8888)
51             ?: throw IllegalStateException(
52                 "Unable to obtain config for 8 bit EGL " + "configuration"
53             )
54     }
55 ) {
56 
57     /**
58      * Factory method to determine which [EGLSpec] the underlying [EGLManager] implementation uses
59      */
60     private val mEglSpecFactory: () -> EGLSpec = eglSpecFactory
61 
62     /**
63      * Factory method used to create the corresponding EGLConfig used to create the EGLRenderer used
64      * by [EGLManager]
65      */
66     private val mEglConfigFactory: EGLManager.() -> EGLConfig = eglConfigFactory
67 
68     /** GLThread used to manage EGL dependencies, create EGLSurfaces and draw content */
69     private var mGLThread: GLThread? = null
70 
71     /** Collection of [RenderTarget] instances that are managed by the GLRenderer */
72     private val mRenderTargets = ArrayList<RenderTarget>()
73 
74     /**
75      * Collection of callbacks to be invoked when the EGL dependencies are initialized or torn down
76      */
77     private val mEglContextCallback = HashSet<EGLContextCallback>()
78 
79     /**
80      * Removes the corresponding [RenderTarget] from management of the GLThread. This destroys the
81      * EGLSurface associated with this surface and subsequent requests to render into the surface
82      * with the provided token are ignored.
83      *
84      * If the [cancelPending] flag is set to true, any queued request to render that has not started
85      * yet is cancelled. However, if this is invoked in the middle of the frame being rendered, it
86      * will continue to process the current frame.
87      *
88      * Additionally if this flag is false, all pending requests to render will be processed before
89      * the [RenderTarget] is detached.
90      *
91      * Note the detach operation will only occur if the GLRenderer is started, that is if
92      * [isRunning] returns true. Otherwise this is a no-op. GLRenderer will automatically detach all
93      * [RenderTarget] instances as part of its teardown process.
94      */
95     @JvmOverloads
detachnull96     fun detach(
97         target: RenderTarget,
98         cancelPending: Boolean,
99         @WorkerThread onDetachComplete: ((RenderTarget) -> Unit)? = null
100     ) {
101         if (mRenderTargets.contains(target)) {
102             detachInternal(target, cancelPending) {
103                 // WorkerThread
104                 target.release()
105                 target.onDetach.invoke()
106                 onDetachComplete?.invoke(target)
107             }
108             mRenderTargets.remove(target)
109         }
110     }
111 
detachInternalnull112     internal fun detachInternal(
113         target: RenderTarget,
114         cancelPending: Boolean,
115         @WorkerThread onDetachComplete: ((RenderTarget) -> Unit)? = null
116     ) {
117         val runnable =
118             if (onDetachComplete != null) {
119                 Runnable { onDetachComplete.invoke(target) }
120             } else {
121                 null
122             }
123         mGLThread?.detachSurface(target.token, cancelPending, runnable)
124     }
125 
126     /**
127      * Determines if the GLThread has been started. That is [start] has been invoked on this
128      * GLRenderer instance without a corresponding call to [stop].
129      */
isRunningnull130     fun isRunning(): Boolean = mGLThread != null
131 
132     /**
133      * Starts the GLThread. After this method is called, consumers can attempt to attach
134      * [android.view.Surface] instances through [attach] as well as schedule content to be drawn
135      * through [requestRender]
136      *
137      * @param name Optional name to provide to the GLThread
138      * @throws IllegalStateException if EGLConfig with desired attributes cannot be created
139      */
140     @JvmOverloads
141     fun start(
142         name: String = "GLThread",
143     ) {
144         if (mGLThread == null) {
145             GLThread.log("starting thread...")
146             mGLThread =
147                 GLThread(name, mEglSpecFactory, mEglConfigFactory).apply {
148                     start()
149                     if (!mEglContextCallback.isEmpty()) {
150                         // Add a copy of the current collection as new entries to
151                         // mEglContextCallback
152                         // could be mistakenly added multiple times.
153                         this.addEGLCallbacks(ArrayList<EGLContextCallback>(mEglContextCallback))
154                     }
155                 }
156         }
157     }
158 
159     /**
160      * Mark the corresponding surface session with the given token as dirty to schedule a call to
161      * [RenderCallback#onDrawFrame]. If there is already a queued request to render into the
162      * provided surface with the specified token, this request is ignored.
163      *
164      * Note the render operation will only occur if the GLRenderer is started, that is if
165      * [isRunning] returns true. Otherwise this is a no-op.
166      *
167      * @param target RenderTarget to be re-rendered
168      * @param onRenderComplete Optional callback invoked on the backing thread after the frame has
169      *   been rendered.
170      */
171     @JvmOverloads
requestRendernull172     fun requestRender(target: RenderTarget, onRenderComplete: ((RenderTarget) -> Unit)? = null) {
173         val token = target.token
174         val callbackRunnable =
175             if (onRenderComplete != null) {
176                 Runnable { onRenderComplete.invoke(target) }
177             } else {
178                 null
179             }
180         mGLThread?.requestRender(token, callbackRunnable)
181     }
182 
183     /**
184      * Resize the corresponding surface associated with the RenderTarget to the specified width and
185      * height and re-render. This will destroy the EGLSurface created by
186      * [RenderCallback.onSurfaceCreated] and invoke it again with the updated dimensions. An
187      * optional callback is invoked on the backing thread after the resize operation is complete.
188      *
189      * Note the resize operation will only occur if the GLRenderer is started, that is if
190      * [isRunning] returns true. Otherwise this is a no-op.
191      *
192      * @param target RenderTarget to be resized
193      * @param width Updated width of the corresponding surface
194      * @param height Updated height of the corresponding surface
195      * @param onResizeComplete Optional callback invoked on the backing thread when the resize
196      *   operation is complete
197      */
198     @JvmOverloads
resizenull199     fun resize(
200         target: RenderTarget,
201         width: Int,
202         height: Int,
203         onResizeComplete: ((RenderTarget) -> Unit)? = null
204     ) {
205         val token = target.token
206         val callbackRunnable =
207             if (onResizeComplete != null) {
208                 Runnable { onResizeComplete.invoke(target) }
209             } else {
210                 null
211             }
212         mGLThread?.resizeSurface(token, width, height, callbackRunnable)
213     }
214 
215     /**
216      * Queue a [Runnable] to be executed on the GL rendering thread. Note it is important that this
217      * [Runnable] does not block otherwise it can stall the GL thread. The EGLContext will be
218      * created after [start] is invoked and before the runnable is executed.
219      *
220      * @param runnable Runnable to be executed
221      */
executenull222     fun execute(runnable: Runnable) {
223         mGLThread?.execute(runnable)
224     }
225 
226     /**
227      * Stop the corresponding GL thread. This destroys all EGLSurfaces as well as any other EGL
228      * dependencies. All queued requests that have not been processed yet are cancelled.
229      *
230      * Note the stop operation will only occur if the GLRenderer was previously started, that is
231      * [isRunning] returns true. Otherwise this is a no-op.
232      *
233      * @param cancelPending If true, all pending requests are cancelled and the backing thread is
234      *   torn down immediately. If false, all pending requests are processed first before tearing
235      *   down the backing thread. Subsequent requests made after this call are ignored.
236      * @param onStop Optional callback invoked on the backing thread after it is torn down.
237      */
238     @JvmOverloads
stopnull239     fun stop(cancelPending: Boolean, onStop: ((GLRenderer) -> Unit)? = null) {
240         GLThread.log("stopping thread...")
241         // Make a copy of the render targets to call cleanup operations on to avoid potential
242         // concurrency issues.
243         // This method will clear the existing collection and we do not want to potentially tear
244         // down a target that was attached after a subsequent call to start if the tear down
245         // callback execution is delayed if previously pending requests have not been cancelled
246         // (i.e. cancelPending is false)
247         val renderTargets = ArrayList(mRenderTargets)
248         mGLThread?.tearDown(cancelPending) {
249             // No need to call target.detach as this callback is invoked after
250             // the dependencies are cleaned up
251             for (target in renderTargets) {
252                 target.release()
253                 target.onDetach.invoke()
254             }
255             onStop?.invoke(this@GLRenderer)
256         }
257         mGLThread = null
258         mRenderTargets.clear()
259     }
260 
261     /**
262      * Add an [EGLContextCallback] to receive callbacks for construction and destruction of EGL
263      * dependencies.
264      *
265      * These callbacks are invoked on the backing thread.
266      */
registerEGLContextCallbacknull267     fun registerEGLContextCallback(callback: EGLContextCallback) {
268         mEglContextCallback.add(callback)
269         mGLThread?.addEGLCallback(callback)
270     }
271 
272     /**
273      * Remove [EGLContextCallback] to no longer receive callbacks for construction and destruction
274      * of EGL dependencies.
275      *
276      * These callbacks are invoked on the backing thread
277      */
unregisterEGLContextCallbacknull278     fun unregisterEGLContextCallback(callback: EGLContextCallback) {
279         mEglContextCallback.remove(callback)
280         mGLThread?.removeEGLCallback(callback)
281     }
282 
283     /**
284      * Callbacks invoked when the GL dependencies are created and destroyed. These are logical
285      * places to setup and tear down any dependencies that are used for drawing content within a
286      * frame (ex. compiling shaders)
287      */
288     interface EGLContextCallback {
289 
290         /**
291          * Callback invoked on the backing thread after EGL dependencies are initialized. This is
292          * guaranteed to be invoked before any instance of [RenderCallback.onSurfaceCreated] is
293          * called. This will be invoked after [GLRenderer.start].
294          */
295         // Suppressing CallbackMethodName due to b/238939160
296         @Suppress("CallbackMethodName")
297         @WorkerThread
onEGLContextCreatednull298         fun onEGLContextCreated(eglManager: EGLManager)
299 
300         /**
301          * Callback invoked on the backing thread before EGL dependencies are about to be torn down.
302          * This is invoked after [GLRenderer.stop] is processed.
303          */
304         // Suppressing CallbackMethodName due to b/238939160
305         @Suppress("CallbackMethodName")
306         @WorkerThread
307         fun onEGLContextDestroyed(eglManager: EGLManager)
308     }
309 
310     @JvmDefaultWithCompatibility
311     /**
312      * Interface used for creating an [EGLSurface] with a user defined configuration from the
313      * provided surface as well as a callback used to render content into the surface for a given
314      * frame
315      */
316     interface RenderCallback {
317         /**
318          * Used to create a corresponding [EGLSurface] from the provided [android.view.Surface]
319          * instance. This enables consumers to configure the corresponding [EGLSurface] they wish to
320          * render into. The [EGLSurface] created here is guaranteed to be the current surface before
321          * [onDrawFrame] is called. That is, implementations of onDrawFrame do not need to call
322          * eglMakeCurrent on this [EGLSurface].
323          *
324          * This method is invoked on the GL thread.
325          *
326          * The default implementation will create a window surface with EGL_WIDTH and EGL_HEIGHT set
327          * to [width] and [height] respectively. Implementations can override this method to provide
328          * additional [EGLConfigAttributes] for this surface (ex. [EGL14.EGL_SINGLE_BUFFER].
329          *
330          * Implementations can return null to indicate the default surface should be used. This is
331          * helpful in situations where content is to be rendered within a frame buffer object
332          * instead of to an [EGLSurface]
333          *
334          * @param spec EGLSpec used to create the corresponding EGLSurface
335          * @param config EGLConfig used to create the corresponding EGLSurface
336          * @param surface [android.view.Surface] used to create an EGLSurface from
337          * @param width Desired width of the surface to create
338          * @param height Desired height of the surface to create
339          */
340         @WorkerThread
341         fun onSurfaceCreated(
342             spec: EGLSpec,
343             config: EGLConfig,
344             surface: Surface,
345             width: Int,
346             height: Int
347         ): EGLSurface? =
348             // Always default to creating an EGL window surface
349             // Despite having access to the width and height here, do not explicitly
350             // pass in EGLConfigAttributes specifying the EGL_WIDTH and EGL_HEIGHT parameters
351             // as those are not accepted parameters for eglCreateWindowSurface but they are
352             // for other EGL Surface factory methods such as eglCreatePBufferSurface
353             // See accepted parameters here:
354             // https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglCreateWindowSurface.xhtml
355             // and here
356             // https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglCreatePbufferSurface.xhtml
357             spec.eglCreateWindowSurface(config, surface, null)
358 
359         /**
360          * Callback used to issue OpenGL drawing commands into the [EGLSurface] created in
361          * [onSurfaceCreated]. This [EGLSurface] is guaranteed to be current before this callback is
362          * invoked and [EGLManager.swapAndFlushBuffers] will be invoked afterwards. If additional
363          * scratch [EGLSurface]s are used here it is up to the implementation of this method to
364          * ensure that the proper surfaces are made current and the appropriate swap buffers call is
365          * made
366          *
367          * This method is invoked on the backing thread
368          *
369          * @param eglManager Handle to EGL dependencies
370          */
371         @WorkerThread fun onDrawFrame(eglManager: EGLManager)
372     }
373 
374     /**
375      * Adds the [android.view.Surface] to be managed by the GLThread. A corresponding [EGLSurface]
376      * is created on the GLThread as well as a callback for rendering into the surface through
377      * [RenderCallback]. Unlike the other [attach] methods that consume a [SurfaceView] or
378      * [TextureView], this method does not handle any lifecycle callbacks associated with the target
379      * surface. Therefore it is up to the consumer to properly setup/teardown resources associated
380      * with this surface.
381      *
382      * @param surface Target surface to be managed by the backing thread
383      * @param width Desired width of the [surface]
384      * @param height Desired height of the [surface]
385      * @param renderer Callbacks used to create a corresponding [EGLSurface] from the given surface
386      *   as well as render content into the created [EGLSurface]
387      * @return [RenderTarget] used for subsequent requests to communicate with the provided Surface
388      *   (ex. [requestRender] or [detach]).
389      * @throws IllegalStateException If this method was called when the GLThread has not started
390      *   (i.e. start has not been called)
391      */
attachnull392     fun attach(surface: Surface, width: Int, height: Int, renderer: RenderCallback): RenderTarget {
393         val thread = mGLThread
394         if (thread != null) {
395             val token = sToken.getAndIncrement()
396             thread.attachSurface(token, surface, width, height, renderer)
397             return RenderTarget(token, this).also { mRenderTargets.add(it) }
398         } else {
399             throw IllegalStateException("GLThread not started, did you forget to call start?")
400         }
401     }
402 
403     /**
404      * Creates a new [RenderTarget] without a corresponding [android.view.Surface]. This avoids
405      * creation of an [EGLSurface] which is useful in scenarios where only rendering to a frame
406      * buffer object is required.
407      *
408      * @param width Desired width of the [RenderTarget]
409      * @param height Desired height of the [RenderTarget]
410      * @param renderer Callbacks used to issue OpenGL commands to the [RenderTarget]
411      * @return [RenderTarget] used for subsequent requests to render through
412      *   [RenderTarget.requestRender] or to remove itself from the [GLRenderer] through
413      *   [RenderTarget.detach]
414      * @throws IllegalStateException If this method was called when the GLThread has not started
415      *   (i.e. start has not been called)
416      */
createRenderTargetnull417     fun createRenderTarget(width: Int, height: Int, renderer: RenderCallback): RenderTarget {
418         val thread = mGLThread
419         if (thread != null) {
420             val token = sToken.getAndIncrement()
421             thread.attachSurface(token, null, width, height, renderer)
422             return RenderTarget(token, this).also { mRenderTargets.add(it) }
423         } else {
424             throw IllegalStateException("GLThread not started, did you forget to call start?")
425         }
426     }
427 
428     /**
429      * Adds the [android.view.Surface] provided by the given [SurfaceView] to be managed by the
430      * backing thread.
431      *
432      * A corresponding [EGLSurface] is created on the GLThread as well as a callback for rendering
433      * into the surface through [RenderCallback].
434      *
435      * This method automatically configures a [SurfaceHolder.Callback] used to attach the
436      * [android.view.Surface] when the underlying [SurfaceHolder] that contains the surface is
437      * available. Similarly this surface will be detached from [GLRenderer] when the surface
438      * provided by the [SurfaceView] is destroyed (i.e. [SurfaceHolder.Callback.surfaceDestroyed] is
439      * called.
440      *
441      * If the [android.view.Surface] is already available by the time this method is invoked, it is
442      * attached synchronously.
443      *
444      * @param surfaceView SurfaceView that provides the surface to be rendered by the backing thread
445      * @param renderer callbacks used to create a corresponding [EGLSurface] from the given surface
446      *   as well as render content into the created [EGLSurface]
447      * @return [RenderTarget] used for subsequent requests to communicate with the provided Surface
448      *   (ex. [requestRender] or [detach]).
449      * @throws IllegalStateException If this method was called when the GLThread has not started
450      *   (i.e. start has not been called)
451      */
attachnull452     fun attach(surfaceView: SurfaceView, renderer: RenderCallback): RenderTarget {
453         val thread = mGLThread
454         if (thread != null) {
455             val token = sToken.getAndIncrement()
456             val holder = surfaceView.holder
457             val callback =
458                 object : SurfaceHolder.Callback2 {
459 
460                     var isAttached = false
461 
462                     /**
463                      * Optional condition that maybe used if we are issuing a blocking call to
464                      * render in [SurfaceHolder.Callback2.surfaceRedrawNeeded] In this case we need
465                      * to signal the condition of either the request to render has completed, or if
466                      * the RenderTarget has been detached and the pending render request is
467                      * cancelled.
468                      */
469                     @Volatile var renderLatch: CountDownLatch? = null
470 
471                     /**
472                      * [CountDownLatch] used when issuing a blocking call to
473                      * [SurfaceHolder.Callback.surfaceDestroyed] In this case we need to signal the
474                      * condition of either the request to detach has completed in case the
475                      * GLRenderer has been forcefully stopped via [GLRenderer.stop] with the cancel
476                      * pending flag set to true.
477                      */
478                     val detachLatch: CountDownLatch = CountDownLatch(1)
479 
480                     fun onDetachComplete() {
481                         isAttached = false
482                         // Countdown in case we have been detached while waiting for a render
483                         // to be completed
484                         renderLatch?.countDown()
485                         detachLatch.countDown()
486                     }
487 
488                     val renderTarget =
489                         RenderTarget(token, this@GLRenderer) @WorkerThread {
490                             // SurfaceHolder.add/remove callback is thread safe
491                             holder.removeCallback(this)
492                             onDetachComplete()
493                         }
494 
495                     override fun surfaceRedrawNeeded(p0: SurfaceHolder) {
496                         // If the [RenderTarget] has already been detached then skip rendering
497                         if (detachLatch.count > 0) {
498                             val latch = CountDownLatch(1).also { renderLatch = it }
499                             // Request a render and block until the rendering is complete
500                             // surfaceRedrawNeeded is invoked on older API levels and is replaced
501                             // with
502                             // surfaceRedrawNeededAsync for newer API levels which is non-blocking
503                             renderTarget.requestRender @WorkerThread { latch.countDown() }
504                             latch.await()
505                             renderLatch = null
506                         }
507                     }
508 
509                     override fun surfaceRedrawNeededAsync(
510                         holder: SurfaceHolder,
511                         drawingFinished: Runnable
512                     ) {
513                         renderTarget.requestRender { drawingFinished.run() }
514                     }
515 
516                     override fun surfaceCreated(holder: SurfaceHolder) {
517                         // NO-OP wait until surfaceChanged which is guaranteed to be called and also
518                         // provides the appropriate width height of the surface
519                     }
520 
521                     override fun surfaceChanged(
522                         holder: SurfaceHolder,
523                         format: Int,
524                         width: Int,
525                         height: Int
526                     ) {
527                         if (!isAttached) {
528                             thread.attachSurface(token, holder.surface, width, height, renderer)
529                             isAttached = true
530                         } else {
531                             renderTarget.resize(width, height)
532                         }
533                         renderTarget.requestRender()
534                     }
535 
536                     override fun surfaceDestroyed(holder: SurfaceHolder) {
537                         // Issue a request to detach the [RenderTarget]. Even if it was
538                         // previously detached this request is a no-op and the corresponding
539                         // [CountDownLatch] will signal when the [RenderTarget] detachment is
540                         // complete
541                         // or instantaneously if it was already detached
542                         renderTarget.detachInternal(true) { onDetachComplete() }
543                         detachLatch.await()
544                     }
545                 }
546             holder.addCallback(callback)
547             if (holder.surface != null && holder.surface.isValid) {
548                 thread.attachSurface(
549                     token,
550                     holder.surface,
551                     surfaceView.width,
552                     surfaceView.height,
553                     renderer
554                 )
555             }
556             mRenderTargets.add(callback.renderTarget)
557             return callback.renderTarget
558         } else {
559             throw IllegalStateException("GLThread not started, did you forget to call start?")
560         }
561     }
562 
563     /**
564      * Adds the [android.view.Surface] provided by the given [TextureView] to be managed by the
565      * backing thread.
566      *
567      * A corresponding [EGLSurface] is created on the GLThread as well as a callback for rendering
568      * into the surface through [RenderCallback].
569      *
570      * This method automatically configures a [TextureView.SurfaceTextureListener] used to create a
571      * [android.view.Surface] when the underlying [SurfaceTexture] is available. Similarly this
572      * surface will be detached from [GLRenderer] if the underlying [SurfaceTexture] is destroyed
573      * (i.e. [TextureView.SurfaceTextureListener.onSurfaceTextureDestroyed] is called.
574      *
575      * If the [SurfaceTexture] is already available by the time this method is called, then it is
576      * attached synchronously.
577      *
578      * @param textureView TextureView that provides the surface to be rendered into on the GLThread
579      * @param renderer callbacks used to create a corresponding [EGLSurface] from the given surface
580      *   as well as render content into the created [EGLSurface]
581      * @return [RenderTarget] used for subsequent requests to communicate with the provided Surface
582      *   (ex. [requestRender] or [detach]).
583      * @throws IllegalStateException If this method was called when the GLThread has not started
584      *   (i.e. start has not been called)
585      */
attachnull586     fun attach(textureView: TextureView, renderer: RenderCallback): RenderTarget {
587         val thread = mGLThread
588         if (thread != null) {
589             val token = sToken.getAndIncrement()
590             val detachLatch = CountDownLatch(1)
591             val renderTarget =
592                 RenderTarget(token, this) @WorkerThread {
593                     textureView.handler?.post { textureView.surfaceTextureListener = null }
594                     detachLatch.countDown()
595                 }
596             textureView.surfaceTextureListener =
597                 object : TextureView.SurfaceTextureListener {
598                     override fun onSurfaceTextureAvailable(
599                         surfaceTexture: SurfaceTexture,
600                         width: Int,
601                         height: Int
602                     ) {
603                         thread.attachSurface(
604                             token,
605                             Surface(surfaceTexture),
606                             width,
607                             height,
608                             renderer
609                         )
610                         renderTarget.requestRender()
611                     }
612 
613                     override fun onSurfaceTextureSizeChanged(
614                         texture: SurfaceTexture,
615                         width: Int,
616                         height: Int
617                     ) {
618                         renderTarget.resize(width, height)
619                         renderTarget.requestRender()
620                     }
621 
622                     override fun onSurfaceTextureDestroyed(p0: SurfaceTexture): Boolean {
623                         // Issue a request to detach the [RenderTarget]. Even if it was
624                         // previously detached this request is a no-op and the corresponding
625                         // [CountDownLatch] will signal when the [RenderTarget] detachment is
626                         // complete
627                         // or instantaneously if it was already detached
628                         renderTarget.detachInternal(true) { detachLatch.countDown() }
629                         detachLatch.await()
630                         return true
631                     }
632 
633                     override fun onSurfaceTextureUpdated(p0: SurfaceTexture) {
634                         // NO-OP
635                     }
636                 }
637             if (textureView.isAvailable) {
638                 thread.attachSurface(
639                     token,
640                     Surface(textureView.surfaceTexture),
641                     textureView.width,
642                     textureView.height,
643                     renderer
644                 )
645                 renderTarget.requestRender()
646             }
647             mRenderTargets.add(renderTarget)
648             return renderTarget
649         } else {
650             throw IllegalStateException("GLThread not started, did you forget to call start?")
651         }
652     }
653 
654     /** Handle to a [android.view.Surface] that is given to [GLRenderer] to handle rendering. */
655     class RenderTarget
656     internal constructor(
657         internal val token: Int,
658         glManager: GLRenderer,
<lambda>null659         @WorkerThread internal val onDetach: () -> Unit = {}
660     ) {
661 
662         @Volatile private var mManager: GLRenderer? = glManager
663 
releasenull664         internal fun release() {
665             mManager = null
666         }
667 
668         /**
669          * Request that this [RenderTarget] should have its contents redrawn. This consumes an
670          * optional callback that is invoked on the backing thread when the rendering is completed.
671          *
672          * Note the render operation will only occur if the RenderTarget is attached, that is
673          * [isAttached] returns true. If the [RenderTarget] is detached or the [GLRenderer] that
674          * created this RenderTarget is stopped, this is a no-op.
675          *
676          * @param onRenderComplete Optional callback called on the backing thread when rendering is
677          *   finished
678          */
679         @JvmOverloads
requestRendernull680         fun requestRender(@WorkerThread onRenderComplete: ((RenderTarget) -> Unit)? = null) {
681             mManager?.requestRender(this@RenderTarget, onRenderComplete)
682         }
683 
684         /**
685          * Determines if the current RenderTarget is attached to GLRenderer. This is true until
686          * [detach] has been called. If the RenderTarget is no longer in an attached state (i.e.
687          * this returns false). Subsequent calls to [requestRender] will be ignored.
688          */
isAttachednull689         fun isAttached(): Boolean = mManager != null
690 
691         /**
692          * Resize the RenderTarget to the specified width and height. This will destroy the
693          * EGLSurface created by [RenderCallback.onSurfaceCreated] and invoke it again with the
694          * updated dimensions. An optional callback is invoked on the backing thread after the
695          * resize operation is complete
696          *
697          * Note the resize operation will only occur if the RenderTarget is attached, that is
698          * [isAttached] returns true. If the [RenderTarget] is detached or the [GLRenderer] that
699          * created this RenderTarget is stopped, this is a no-op.
700          *
701          * @param width New target width to resize the RenderTarget
702          * @param height New target height to resize the RenderTarget
703          * @param onResizeComplete Optional callback invoked after the resize is complete
704          */
705         @JvmOverloads
706         fun resize(
707             width: Int,
708             height: Int,
709             @WorkerThread onResizeComplete: ((RenderTarget) -> Unit)? = null
710         ) {
711             mManager?.resize(this, width, height, onResizeComplete)
712         }
713 
714         /**
715          * Removes the corresponding [RenderTarget] from management of the GLThread. This destroys
716          * the EGLSurface associated with this surface and subsequent requests to render into the
717          * surface with the provided token are ignored.
718          *
719          * If the [cancelPending] flag is set to true, any queued request to render that has not
720          * started yet is cancelled. However, if this is invoked in the middle of the frame being
721          * rendered, it will continue to process the current frame.
722          *
723          * Additionally if this flag is false, all pending requests to render will be processed
724          * before the [RenderTarget] is detached.
725          *
726          * This is a convenience method around [GLRenderer.detach]
727          *
728          * Note the detach operation will only occur if the RenderTarget is attached, that is
729          * [isAttached] returns true. If the [RenderTarget] is detached or the [GLRenderer] that
730          * created this RenderTarget is stopped, this is a no-op.
731          */
732         @JvmOverloads
detachnull733         fun detach(cancelPending: Boolean, onDetachComplete: ((RenderTarget) -> Unit)? = null) {
734             mManager?.detach(this, cancelPending, onDetachComplete)
735         }
736 
detachInternalnull737         internal fun detachInternal(
738             cancelPending: Boolean,
739             onDetachComplete: ((RenderTarget) -> Unit)? = null
740         ) {
741             mManager?.detachInternal(this, cancelPending, onDetachComplete)
742         }
743     }
744 
745     companion object {
746         /** Counter used to issue unique identifiers for surfaces that are managed by GLRenderer */
747         private val sToken = AtomicInteger()
748     }
749 }
750