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.lowlatency
18 
19 import android.hardware.HardwareBuffer
20 import android.opengl.GLES20
21 import android.opengl.Matrix
22 import android.os.Build
23 import android.util.Log
24 import android.view.SurfaceHolder
25 import android.view.SurfaceView
26 import androidx.annotation.RequiresApi
27 import androidx.annotation.WorkerThread
28 import androidx.graphics.lowlatency.FrontBufferUtils.Companion.obtainHardwareBufferUsageFlags
29 import androidx.graphics.opengl.FrameBuffer
30 import androidx.graphics.opengl.FrameBufferPool
31 import androidx.graphics.opengl.FrameBufferRenderer
32 import androidx.graphics.opengl.GLFrameBufferRenderer
33 import androidx.graphics.opengl.GLRenderer
34 import androidx.graphics.opengl.egl.EGLManager
35 import androidx.graphics.surface.SurfaceControlCompat
36 import androidx.hardware.HardwareBufferFormat
37 import androidx.hardware.SyncFenceCompat
38 import androidx.opengl.EGLExt.Companion.EGL_ANDROID_NATIVE_FENCE_SYNC
39 import androidx.opengl.EGLExt.Companion.EGL_KHR_FENCE_SYNC
40 import java.util.concurrent.ConcurrentLinkedQueue
41 import java.util.concurrent.CountDownLatch
42 import java.util.concurrent.atomic.AtomicBoolean
43 import java.util.concurrent.atomic.AtomicInteger
44 import kotlin.math.max
45 
46 /**
47  * Class responsible for supporting a "front buffered" rendering system. This allows for lower
48  * latency graphics by leveraging a combination of front buffered and multi buffered content layers.
49  * Active content is rendered first into the front buffered layer which is simultaneously being
50  * presented to the display. Periodically content is rendered into the multi buffered layer which
51  * will have more traditional latency guarantees, however, minimize the impact of visual artifacts
52  * due to graphical tearing.
53  *
54  * @param surfaceView Target SurfaceView to act as the parent rendering layer for multi buffered
55  *   content
56  * @param callback Callbacks used to render into front and multi buffered layers as well as
57  *   configuring [SurfaceControlCompat.Transaction]s for controlling these layers in addition to
58  *   other [SurfaceControlCompat] instances that must be updated atomically within the user
59  *   interface. These callbacks are invoked on the backing GL Thread.
60  * @param glRenderer Optional [GLRenderer] instance that this [GLFrontBufferedRenderer] should use
61  *   for coordinating requests to render content. If this parameter is specified, the caller of this
62  *   API is responsible for releasing resources on [GLRenderer] by calling [GLRenderer.stop].
63  *   Otherwise [GLFrontBufferedRenderer] will create and manage its own [GLRenderer] internally and
64  *   will automatically release its resources within [GLFrontBufferedRenderer.release]
65  * @param bufferFormat format of the underlying buffers being rendered into by
66  *   [GLFrontBufferedRenderer]. The set of valid formats is implementation-specific and may depend
67  *   on additional EGL extensions. The particular valid combinations for a given Android version and
68  *   implementation should be documented by that version. [HardwareBuffer.RGBA_8888] and
69  *   [HardwareBuffer.RGBX_8888] are guaranteed to be supported. However, consumers are recommended
70  *   to query the desired HardwareBuffer configuration using [HardwareBuffer.isSupported]. The
71  *   default is [HardwareBuffer.RGBA_8888].
72  *
73  * See: khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_get_native_client_buffer.txt and
74  * https://developer.android.com/reference/android/hardware/HardwareBuffer
75  */
76 @RequiresApi(Build.VERSION_CODES.Q)
77 class GLFrontBufferedRenderer<T>
78 @JvmOverloads
79 constructor(
80     surfaceView: SurfaceView,
81     callback: Callback<T>,
82     @Suppress("ListenerLast") glRenderer: GLRenderer? = null,
83     @HardwareBufferFormat val bufferFormat: Int = HardwareBuffer.RGBA_8888
84 ) {
85 
86     private val mFrontBufferedCallbacks =
87         object : GLFrameBufferRenderer.Callback {
88             override fun onDrawFrame(
89                 eglManager: EGLManager,
90                 width: Int,
91                 height: Int,
92                 bufferInfo: BufferInfo,
93                 transform: FloatArray
94             ) {
95                 if (mPendingClear.compareAndSet(true, false)) {
96                     waitForFrontBufferFence()
97                     clearContents(width, height)
98                 }
99                 val result = mPendingRenderCount.updateAndGet { value -> max(value - 1, 0) }
100                 mActiveSegment.next { param ->
101                     callback.onDrawFrontBufferedLayer(
102                         eglManager,
103                         width,
104                         height,
105                         bufferInfo,
106                         transform,
107                         param
108                     )
109                 }
110                 if (result > 0) {
111                     mFrontBufferedRenderer?.render()
112                 }
113             }
114 
115             override fun onDrawComplete(
116                 targetSurfaceControl: SurfaceControlCompat,
117                 transaction: SurfaceControlCompat.Transaction,
118                 frameBuffer: FrameBuffer,
119                 syncFence: SyncFenceCompat?
120             ) {
121                 mFrontBufferSyncStrategy.isVisible = true
122                 mFrontLayerBuffer = frameBuffer
123                 transaction.setLayer(targetSurfaceControl, Integer.MAX_VALUE)
124                 transaction.setBuffer(
125                     targetSurfaceControl,
126                     frameBuffer.hardwareBuffer,
127                     syncFence
128                 ) { releaseFence ->
129                     mFrontBufferReleaseFence?.close()
130                     mFrontBufferReleaseFence = releaseFence
131                 }
132                 callback.onFrontBufferedLayerRenderComplete(targetSurfaceControl, transaction)
133             }
134         }
135 
136     /**
137      * AtomicInteger to determine if there is a pending request to commit content to the multi
138      * buffered layer
139      */
140     private val mCommitCount = AtomicInteger(0)
141 
142     @Volatile private var mFrontBufferReleaseFence: SyncFenceCompat? = null
143 
144     private fun waitForFrontBufferFence() {
145         mFrontBufferReleaseFence?.let { fence ->
146             fence.awaitForever()
147             mFrontBufferReleaseFence = null
148         }
149     }
150 
151     private fun clearContents(viewportWidth: Int, viewportHeight: Int) {
152         GLES20.glViewport(0, 0, viewportWidth, viewportHeight)
153         GLES20.glClearColor(0f, 0f, 0f, 0f)
154         GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
155         GLES20.glFlush()
156     }
157 
158     // GLThread
159     private val mClearFrontBufferRunnable = Runnable {
160         mFrontLayerBuffer?.let { frameBuffer ->
161             if (mPendingClear.compareAndSet(true, false)) {
162                 if (!frameBuffer.isClosed) {
163                     frameBuffer.makeCurrent()
164                     waitForFrontBufferFence()
165                     with(frameBuffer) { clearContents(hardwareBuffer.width, hardwareBuffer.height) }
166                 }
167             }
168         }
169     }
170 
171     /**
172      * [GLRenderer.EGLContextCallback]s used to release the corresponding [FrameBufferPool] if the
173      * [GLRenderer] is torn down. This is especially helpful if a [GLRenderer] is being provided and
174      * shared across other [GLRenderer.RenderTarget] instances in which case it can be released
175      * externally from [GLFrontBufferedRenderer]
176      */
177     private val mContextCallbacks =
178         object : GLRenderer.EGLContextCallback {
179             override fun onEGLContextCreated(eglManager: EGLManager) {
180                 with(eglManager) {
181                     val supportsEglFences = isExtensionSupported(EGL_KHR_FENCE_SYNC)
182                     val supportsAndroidFences = isExtensionSupported(EGL_ANDROID_NATIVE_FENCE_SYNC)
183                     Log.d(TAG, "Supports KHR_FENCE_SYNC: $supportsEglFences")
184                     Log.d(TAG, "Supports ANDROID_NATIVE_FENCE_SYNC: $supportsAndroidFences")
185                 }
186             }
187 
188             override fun onEGLContextDestroyed(eglManager: EGLManager) {
189                 // NO-OP
190             }
191         }
192 
193     private val mMultiBufferedRenderCallbacks =
194         object : GLFrameBufferRenderer.Callback {
195             override fun onDrawFrame(
196                 eglManager: EGLManager,
197                 width: Int,
198                 height: Int,
199                 bufferInfo: BufferInfo,
200                 transform: FloatArray
201             ) {
202                 GLES20.glViewport(0, 0, bufferInfo.width, bufferInfo.height)
203                 GLES20.glClearColor(0f, 0f, 0f, 0f)
204                 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
205                 callback.onDrawMultiBufferedLayer(
206                     eglManager,
207                     width,
208                     height,
209                     bufferInfo,
210                     transform,
211                     mSegments.poll() ?: emptyList()
212                 )
213             }
214 
215             override fun onDrawComplete(
216                 targetSurfaceControl: SurfaceControlCompat,
217                 transaction: SurfaceControlCompat.Transaction,
218                 frameBuffer: FrameBuffer,
219                 syncFence: SyncFenceCompat?
220             ) {
221                 mFrontBufferSyncStrategy.isVisible = false
222                 transaction.apply {
223                     mFrontBufferedLayerSurfaceControl?.let { frontSurfaceControl ->
224                         setVisibility(frontSurfaceControl, false)
225                         setBuffer(frontSurfaceControl, null)
226                         callback.onMultiBufferedLayerRenderComplete(
227                             frontSurfaceControl,
228                             targetSurfaceControl,
229                             transaction
230                         )
231                     }
232                 }
233             }
234 
235             override fun onBufferReleased(
236                 frameBuffer: FrameBuffer,
237                 releaseFence: SyncFenceCompat?
238             ) {
239                 if (isValid()) {
240                     mPendingClear.set(true)
241                     val result = mCommitCount.updateAndGet { value -> max(value - 1, 0) }
242                     if (result != 0) {
243                         commitInternal()
244                     } else if (mPendingRenderCount.get() > 0) {
245                         mFrontBufferedRenderer?.render()
246                     }
247                     if (!mFrontBufferSyncStrategy.isVisible) {
248                         mClearFrontBufferRunnable.run()
249                     }
250                 }
251             }
252         }
253 
254     private val mSurfaceCallbacks =
255         object : SurfaceHolder.Callback2 {
256             override fun surfaceCreated(holder: SurfaceHolder) {
257                 // NO-OP
258             }
259 
260             override fun surfaceChanged(
261                 holder: SurfaceHolder,
262                 format: Int,
263                 width: Int,
264                 height: Int
265             ) {
266                 mSurfaceView?.let { surfaceView -> update(surfaceView, width, height) }
267             }
268 
269             override fun surfaceDestroyed(p0: SurfaceHolder) {
270                 detachTargets(true)
271             }
272 
273             override fun surfaceRedrawNeeded(p0: SurfaceHolder) {
274                 val countDownLatch = CountDownLatch(1)
275                 requestDraw { countDownLatch.countDown() }
276                 countDownLatch.await()
277             }
278 
279             override fun surfaceRedrawNeededAsync(
280                 holder: SurfaceHolder,
281                 drawingFinished: Runnable
282             ) {
283                 requestDraw(drawingFinished)
284             }
285 
286             fun requestDraw(onComplete: Runnable) {
287                 val multiBufferedRenderer = mMultiBufferedRenderer
288                 if (multiBufferedRenderer != null) {
289                     val eglCallback =
290                         object : GLRenderer.EGLContextCallback {
291                             override fun onEGLContextCreated(eglManager: EGLManager) {
292                                 // NO-OP
293                             }
294 
295                             override fun onEGLContextDestroyed(eglManager: EGLManager) {
296                                 onComplete.run()
297                                 mGLRenderer.unregisterEGLContextCallback(this)
298                             }
299                         }
300                     mGLRenderer.registerEGLContextCallback(eglCallback)
301                     multiBufferedRenderer.render()
302                     mGLRenderer.execute {
303                         onComplete.run()
304                         mGLRenderer.unregisterEGLContextCallback(eglCallback)
305                     }
306                 } else {
307                     onComplete.run()
308                 }
309             }
310         }
311 
312     /**
313      * Flag to determine if a request to clear the front buffer content is pending. This should only
314      * be accessed on the GLThread
315      */
316     private val mPendingClear = AtomicBoolean(false)
317 
318     /**
319      * Count of pending requests to render to the front buffer while content is being committed to
320      * the multi buffered layer
321      */
322     private val mPendingRenderCount = AtomicInteger(0)
323 
324     /** SurfaceView that hosts both the multi buffered and front buffered SurfaceControls */
325     private var mSurfaceView: SurfaceView? = surfaceView
326 
327     /**
328      * Runnable executed on the GLThread to update [FrontBufferSyncStrategy.isVisible] as well as
329      * hide the SurfaceControl associated with the front buffered layer
330      */
331     private val mCancelRunnable = Runnable {
332         mPendingClear.set(true)
333         mFrontBufferSyncStrategy.isVisible = false
334         mFrontBufferedLayerSurfaceControl?.let { frontBufferSurfaceControl ->
335             SurfaceControlCompat.Transaction()
336                 .setVisibility(frontBufferSurfaceControl, false)
337                 .commit()
338         }
339     }
340 
341     /**
342      * Queue of parameters to be consumed in [Callback.onDrawFrontBufferedLayer] with the parameter
343      * provided in [renderFrontBufferedLayer]
344      */
345     private val mActiveSegment = ParamQueue<T>()
346 
347     /**
348      * Collection of parameters to be consumed in [Callback.onMultiBufferedLayerRenderComplete] with
349      * the parameters defined in consecutive calls to [renderFrontBufferedLayer]. Once the
350      * corresponding [Callback.onMultiBufferedLayerRenderComplete] callback is invoked, this
351      * collection is cleared and new parameters are added to it with consecutive calls to
352      * [renderFrontBufferedLayer].
353      */
354     private val mSegments = ConcurrentLinkedQueue<Collection<T>>()
355 
356     /**
357      * [FrameBuffer] used for rendering into the front buffered layer. This buffer is persisted
358      * across frames as part of front buffered rendering and is not expected to be released again
359      * after the corresponding [SurfaceControlCompat.Transaction] that submits this buffer is
360      * applied as per the implementation of "scan line racing" that is done for front buffered
361      * rendering
362      */
363     private var mFrontLayerBuffer: FrameBuffer? = null
364 
365     /**
366      * [SurfaceControlCompat] used to configure buffers and visibility of the multi buffered layer
367      */
368     private var mMultiBufferedLayerSurfaceControl: SurfaceControlCompat? = null
369 
370     /**
371      * [SurfaceControlCompat] used to configure buffers and visibility of the front buffered layer
372      */
373     private var mFrontBufferedLayerSurfaceControl: SurfaceControlCompat? = null
374 
375     /**
376      * [FrontBufferSyncStrategy] used for [FrameBufferRenderer] to conditionally decide when to
377      * create a [SyncFenceCompat] for transaction calls.
378      */
379     private val mFrontBufferSyncStrategy: FrontBufferSyncStrategy
380 
381     /**
382      * Width of the layers to render. Only if the size changes to we re-initialize the internal
383      * state of the [GLFrontBufferedRenderer]
384      */
385     private var mWidth = -1
386 
387     /**
388      * Height of the layers to render. Only if the size changes do we re-initialize the internal
389      * state of the [GLFrontBufferedRenderer]
390      */
391     private var mHeight = -1
392 
393     /** Current transform to apply to pre-rotate content */
394     private var mTransform = BufferTransformHintResolver.UNKNOWN_TRANSFORM
395 
396     /** [GLRenderer] used to issue requests to render into front/multi buffered layers */
397     private val mGLRenderer: GLRenderer
398 
399     /**
400      * Flag indicating if the [GLRenderer] being used was created internally within
401      * [GLFrontBufferedRenderer] as opposed to being provided by the consumer. If the former, then
402      * the [GLFrontBufferedRenderer] is responsible for stopping/releasing this [GLRenderer] in the
403      * [release] method. If this is being provided, then we should not be releasing this
404      * [GLRenderer] as it maybe used by other consumers. In this case, only the front/multi buffered
405      * [GLRenderer.RenderTarget]s are detached.
406      */
407     private val mIsManagingGLRenderer: Boolean
408 
409     /** [GLFrameBufferRenderer] used to issue requests to render into the front buffered layer */
410     private var mFrontBufferedRenderer: GLFrameBufferRenderer? = null
411 
412     /** [GLFrameBufferRenderer] used to issue requests to render into the multi buffered layer */
413     private var mMultiBufferedRenderer: GLFrameBufferRenderer? = null
414 
415     /**
416      * Flag to determine if the [GLFrontBufferedRenderer] has previously been released. If this flag
417      * is true, then subsequent requests to [renderFrontBufferedLayer], [commit], and [release] are
418      * ignored.
419      */
420     private var mIsReleased = false
421 
422     init {
423         val renderer =
424             if (glRenderer == null) {
425                 // If we have not been provided a [GLRenderer] then we should create/start one
426                 // ourselves
427                 mIsManagingGLRenderer = true
428                 GLRenderer().apply { start() }
429             } else {
430                 // ... otherwise use the [GLRenderer] that is being provided for us
431                 mIsManagingGLRenderer = false
432                 if (!glRenderer.isRunning()) {
433                     throw IllegalStateException(
434                         "The provided GLRenderer must be running prior to " +
435                             "creation of GLFrontBufferedRenderer, " +
436                             "did you forget to call GLRenderer#start()?"
437                     )
438                 }
439                 glRenderer
440             }
441         renderer.registerEGLContextCallback(mContextCallbacks)
442 
443         mGLRenderer = renderer
444 
445         mFrontBufferSyncStrategy = FrontBufferSyncStrategy(obtainHardwareBufferUsageFlags())
446 
447         mSurfaceView = surfaceView
448         surfaceView.holder?.let { holder ->
449             holder.addCallback(mSurfaceCallbacks)
450             if (holder.surface != null && holder.surface.isValid) {
451                 update(surfaceView, surfaceView.width, surfaceView.height)
452             }
453         }
454     }
455 
456     internal fun update(surfaceView: SurfaceView, width: Int, height: Int) {
457         if (width <= 0 || height <= 0) {
458             Log.w(
459                 TAG,
460                 "Invalid dimensions provided, width and height must be > 0. " +
461                     "width: $width height: $height"
462             )
463             return
464         }
465         val transformHint = BufferTransformHintResolver().getBufferTransformHint(surfaceView)
466         if ((mTransform != transformHint || mWidth != width || mHeight != height) && isValid()) {
467             detachTargets(true)
468 
469             val parentSurfaceControl =
470                 SurfaceControlCompat.Builder()
471                     .setParent(surfaceView)
472                     .setName("MultiBufferedSurfaceControl")
473                     .build()
474 
475             val frontSurfaceControl =
476                 SurfaceControlCompat.Builder()
477                     .setParent(parentSurfaceControl)
478                     .setName("FrontBufferedSurfaceControl")
479                     .build()
480 
481             FrontBufferUtils.configureFrontBufferLayerFrameRate(frontSurfaceControl)?.commit()
482 
483             val multiBufferedRenderer =
484                 GLFrameBufferRenderer.Builder(
485                         parentSurfaceControl,
486                         width,
487                         height,
488                         transformHint,
489                         mMultiBufferedRenderCallbacks
490                     )
491                     .setGLRenderer(mGLRenderer)
492                     .setUsageFlags(FrontBufferUtils.BaseFlags)
493                     .setBufferFormat(bufferFormat)
494                     .build()
495 
496             val frontBufferedRenderer =
497                 GLFrameBufferRenderer.Builder(
498                         frontSurfaceControl,
499                         width,
500                         height,
501                         transformHint,
502                         mFrontBufferedCallbacks
503                     )
504                     .setGLRenderer(mGLRenderer)
505                     .setMaxBuffers(1)
506                     .setUsageFlags(obtainHardwareBufferUsageFlags())
507                     .setBufferFormat(bufferFormat)
508                     .setSyncStrategy(mFrontBufferSyncStrategy)
509                     .build()
510 
511             mFrontBufferedRenderer = frontBufferedRenderer
512             mFrontBufferedLayerSurfaceControl = frontSurfaceControl
513             mMultiBufferedLayerSurfaceControl = parentSurfaceControl
514             mMultiBufferedRenderer = multiBufferedRenderer
515             mWidth = width
516             mHeight = height
517             mTransform = transformHint
518         }
519     }
520 
521     /**
522      * Determines whether or not the [GLFrontBufferedRenderer] is in a valid state. That is the
523      * [release] method has not been called. If this returns false, then subsequent calls to
524      * [renderFrontBufferedLayer], [commit], and [release] are ignored
525      *
526      * @return `true` if this [GLFrontBufferedRenderer] has been released, `false` otherwise
527      */
528     fun isValid(): Boolean = !mIsReleased
529 
530     /**
531      * Render content to the front buffered layer providing optional parameters to be consumed in
532      * [Callback.onDrawFrontBufferedLayer]. Additionally the parameter provided here will also be
533      * consumed in [Callback.onDrawMultiBufferedLayer] when the corresponding [commit] method is
534      * invoked, which will include all [param]s in each call made to this method up to the
535      * corresponding [commit] call.
536      *
537      * If this [GLFrontBufferedRenderer] has been released, that is [isValid] returns `false`, this
538      * call is ignored.
539      *
540      * @param param Optional parameter to be consumed when rendering content into the commit layer
541      */
542     fun renderFrontBufferedLayer(param: T) {
543         if (isValid()) {
544             mActiveSegment.add(param)
545             if (mCommitCount.get() == 0) {
546                 mFrontBufferedRenderer?.render()
547             } else {
548                 mPendingRenderCount.incrementAndGet()
549             }
550         } else {
551             Log.w(
552                 TAG,
553                 "Attempt to render to front buffered layer when " +
554                     "GLFrontBufferedRenderer has been released"
555             )
556         }
557     }
558 
559     /**
560      * Requests to render to the multi buffered layer. This schedules a call to
561      * [Callback.onDrawMultiBufferedLayer] with the parameters provided. If the front buffered layer
562      * is visible, this will hide this layer after rendering to the multi buffered layer is
563      * complete. This is equivalent to calling [GLFrontBufferedRenderer.renderFrontBufferedLayer]
564      * for each parameter provided in the collection followed by a single call to
565      * [GLFrontBufferedRenderer.commit]. This is useful for re-rendering the multi buffered scene
566      * when the corresponding Activity is being resumed from the background in which the contents
567      * should be re-drawn. Additionally this allows for applications to decide to dynamically render
568      * to either front or multi buffered layers.
569      *
570      * If this [GLFrontBufferedRenderer] has been released, that is [isValid] returns 'false', this
571      * call is ignored.
572      *
573      * @param params Parameters that to be consumed when rendering to the multi buffered layer.
574      *   These parameters will be provided in the corresponding call to
575      *   [Callback.onDrawMultiBufferedLayer]
576      */
577     fun renderMultiBufferedLayer(params: Collection<T>) {
578         if (isValid()) {
579             mSegments.add(params)
580             mMultiBufferedRenderer?.render()
581         } else {
582             Log.w(
583                 TAG,
584                 "Attempt to render to the multi buffered layer when " +
585                     "GLFrontBufferedRenderer has been released"
586             )
587         }
588     }
589 
590     /**
591      * Clears the contents of both the front and multi buffered layers. This triggers a call to
592      * [Callback.onMultiBufferedLayerRenderComplete] and hides the front buffered layer.
593      */
594     fun clear() {
595         clearParamQueues()
596         mPendingClear.set(true)
597         mMultiBufferedRenderer?.render()
598     }
599 
600     /**
601      * Requests to render the entire scene to the multi buffered layer and schedules a call to
602      * [Callback.onDrawMultiBufferedLayer]. The parameters provided to
603      * [Callback.onDrawMultiBufferedLayer] will include each argument provided to every
604      * [renderFrontBufferedLayer] call since the last call to [commit] has been made.
605      *
606      * If this [GLFrontBufferedRenderer] has been released, that is [isValid] returns `false`, this
607      * call is ignored.
608      */
609     fun commit() {
610         if (mCommitCount.getAndIncrement() == 0) {
611             commitInternal()
612         }
613     }
614 
615     private fun commitInternal() {
616         if (isValid()) {
617             mPendingRenderCount.set(0)
618             mSegments.add(if (mActiveSegment.isEmpty()) emptyList() else mActiveSegment.release())
619             mMultiBufferedRenderer?.render()
620         } else {
621             Log.w(
622                 TAG,
623                 "Attempt to render to the multi buffered layer when " +
624                     "GLFrontBufferedRenderer has been released"
625             )
626         }
627     }
628 
629     /**
630      * Requests to cancel rendering and hides the front buffered layer. Unlike [commit], this does
631      * not schedule a call to render into the multi buffered layer.
632      *
633      * If this [GLFrontBufferedRenderer] has been released, that is [isValid] returns `false`, this
634      * call is ignored.
635      */
636     fun cancel() {
637         if (isValid()) {
638             mActiveSegment.clear()
639             mPendingRenderCount.set(0)
640             mGLRenderer.execute(mCancelRunnable)
641         } else {
642             Log.w(
643                 TAG,
644                 "Attempt to cancel rendering to front buffer after " +
645                     "GLFrontBufferedRenderer has been released"
646             )
647         }
648     }
649 
650     /**
651      * Queue a [Runnable] to be executed on the GL rendering thread. Note it is important this
652      * [Runnable] does not block otherwise it can stall the GL thread.
653      *
654      * @param runnable to be executed
655      */
656     fun execute(runnable: Runnable) {
657         if (isValid()) {
658             mGLRenderer.execute(runnable)
659         } else {
660             Log.w(
661                 TAG,
662                 "Attempt to execute runnable after GLFrontBufferedRenderer has " + "been released"
663             )
664         }
665     }
666 
667     /**
668      * Helper method used to detach the front and multi buffered render targets as well as release
669      * SurfaceControl instances
670      */
671     internal fun detachTargets(cancelPending: Boolean, onReleaseComplete: (() -> Unit)? = null) {
672         // If we're not cancelling the pending renders, dispatch requested but deferred renders
673         // immediately so they end up run before the thread is actually canceled.
674         if (!cancelPending) {
675             // This is the count of pending commits including an already-in-flight one, so update
676             // first to count and commit the remaining ones.
677             while (mCommitCount.updateAndGet { value -> max(value - 1, 0) } > 0) {
678                 commitInternal()
679             }
680             // This is the count of deferred front-layer renders, so we update after to get the
681             // current count. Note that if there was a deferred commit, any deferred front-layer
682             // renders are preempted; the call to commitInternal sets mPendingRenderCount to 0.
683             while (mPendingRenderCount.getAndUpdate { value -> max(value - 1, 0) } > 0) {
684                 mFrontBufferedRenderer?.render()
685             }
686         }
687         mMultiBufferedRenderer?.release(cancelPending)
688         mFrontBufferedRenderer?.release(cancelPending)
689         val frontSc = mFrontBufferedLayerSurfaceControl
690         val parentSc = mMultiBufferedLayerSurfaceControl
691         mGLRenderer.execute {
692             // This is deferred until all the GL callbacks not canceled by release are complete.
693             clearParamQueues()
694             if (frontSc != null && frontSc.isValid() && parentSc != null && parentSc.isValid()) {
695                 SurfaceControlCompat.Transaction()
696                     .reparent(frontSc, null)
697                     .reparent(parentSc, null)
698                     .commit()
699                 frontSc.release()
700                 parentSc.release()
701             }
702             mFrontBufferReleaseFence?.let { fence ->
703                 fence.awaitForever()
704                 fence.close()
705                 mFrontBufferReleaseFence = null
706             }
707             mFrontLayerBuffer?.close()
708             onReleaseComplete?.invoke()
709         }
710         mMultiBufferedRenderer = null
711         mFrontBufferedRenderer = null
712         mFrontBufferedLayerSurfaceControl = null
713         mMultiBufferedLayerSurfaceControl = null
714         mWidth = -1
715         mHeight = -1
716     }
717 
718     /**
719      * Releases the [GLFrontBufferedRenderer] and provides an optional callback that is invoked when
720      * the [GLFrontBufferedRenderer] is fully torn down. If the [cancelPending] flag is false, all
721      * pending requests to render into the front or multi buffered layers will be processed before
722      * the [GLFrontBufferedRenderer] is torn down. Otherwise, all in progress requests are ignored.
723      * If the [GLFrontBufferedRenderer] is already released, that is [isValid] returns `false`, this
724      * method does nothing.
725      *
726      * @param cancelPending If true, pending render requests are cancelled immediately. If false,
727      *   pending render requests are completed before releasing the renderer.
728      * @param onReleaseComplete Optional callback invoked when the [GLFrontBufferedRenderer] has
729      *   been released. This callback is invoked on the backing GLThread
730      */
731     @JvmOverloads
732     fun release(cancelPending: Boolean, onReleaseComplete: (() -> Unit)? = null) {
733         if (!isValid()) {
734             Log.w(TAG, "Attempt to release GLFrontBufferedRenderer that is already released")
735             return
736         }
737 
738         detachTargets(cancelPending, onReleaseComplete)
739 
740         mGLRenderer.unregisterEGLContextCallback(mContextCallbacks)
741         if (mIsManagingGLRenderer) {
742             // If we are managing the GLRenderer that we created ourselves
743             // do not cancel pending operations as we will miss callbacks that we are
744             // expecting above to properly teardown resources
745             // Instead rely on the cancel pending flags for detaching the front/multi buffered
746             // render targets instead
747             mGLRenderer.stop(false)
748         }
749 
750         mSurfaceView?.holder?.removeCallback(mSurfaceCallbacks)
751         mSurfaceView = null
752 
753         mIsReleased = true
754     }
755 
756     private fun clearParamQueues() {
757         mActiveSegment.clear()
758         mCommitCount.set(0)
759         mSegments.clear()
760         mPendingRenderCount.set(0)
761     }
762 
763     internal companion object {
764 
765         internal const val TAG = "GLFrontBufferedRenderer"
766     }
767 
768     @JvmDefaultWithCompatibility
769     /**
770      * Provides callbacks for consumers to draw into the front and multi buffered layers as well as
771      * provide opportunities to synchronize [SurfaceControlCompat.Transaction]s to submit the layers
772      * to the hardware compositor.
773      */
774     interface Callback<T> {
775 
776         /**
777          * Callback invoked to render content into the front buffered layer with the specified
778          * parameters.
779          *
780          * @param eglManager [EGLManager] useful in configuring EGL objects to be used when issuing
781          *   OpenGL commands to render into the front buffered layer
782          * @param width Logical width of the content to render. This dimension matches what is
783          *   provided from [SurfaceHolder.Callback.surfaceChanged]
784          * @param height Logical height of the content to render. This dimension matches what is
785          *   provided from [SurfaceHolder.Callback.surfaceChanged]
786          * @param bufferInfo [BufferInfo] about the buffer that is being rendered into. This
787          *   includes the width and height of the buffer which can be different than the
788          *   corresponding dimensions of the [SurfaceView] provided to the [GLFrontBufferedRenderer]
789          *   as pre-rotation can occasionally swap width and height parameters in order to avoid GPU
790          *   composition to rotate content. This should be used as input to [GLES20.glViewport].
791          *   Additionally this also contains a frame buffer identifier that can be used to retarget
792          *   rendering operations to the original destination after rendering into intermediate
793          *   scratch buffers.
794          * @param transform Matrix that should be applied to the rendering in this callback. This
795          *   should be consumed as input to any vertex shader implementations. Buffers are
796          *   pre-rotated in advance in order to avoid unnecessary overhead of GPU composition to
797          *   rotate content in the same install orientation of the display. This is a 4 x 4 matrix
798          *   is represented as a flattened array of 16 floating point values. Consumers are expected
799          *   to leverage [Matrix.multiplyMM] with this parameter alongside any additional
800          *   transformations that are to be applied. For example:
801          * ```
802          * val myMatrix = FloatArray(16)
803          * Matrix.orthoM(
804          *      myMatrix, // matrix
805          *      0, // offset starting index into myMatrix
806          *      0f, // left
807          *      bufferInfo.bufferWidth.toFloat(), // right
808          *      0f, // bottom
809          *      bufferInfo.bufferHeight.toFloat(), // top
810          *      -1f, // near
811          *      1f, // far
812          * )
813          * val result = FloatArray(16)
814          * Matrix.multiplyMM(result, 0, myMatrix, 0, transform, 0)
815          * ```
816          *
817          * @param param optional parameter provided the corresponding
818          *   [GLFrontBufferedRenderer.renderFrontBufferedLayer] method that triggered this request
819          *   to render into the front buffered layer
820          */
821         @WorkerThread
822         fun onDrawFrontBufferedLayer(
823             eglManager: EGLManager,
824             width: Int,
825             height: Int,
826             bufferInfo: BufferInfo,
827             transform: FloatArray,
828             param: T
829         )
830 
831         /**
832          * Callback invoked to render content into the multi buffered layer with the specified
833          * parameters.
834          *
835          * @param eglManager [EGLManager] useful in configuring EGL objects to be used when issuing
836          *   OpenGL commands to render into the multi buffered layer
837          * @param width Logical width of the content to render. This dimension matches what is
838          *   provided from [SurfaceHolder.Callback.surfaceChanged]
839          * @param height Logical height of the content to render. This dimension matches what is
840          *   provided from [SurfaceHolder.Callback.surfaceChanged]
841          * @param bufferInfo [BufferInfo] about the buffer that is being rendered into. This
842          *   includes the width and height of the buffer which can be different than the
843          *   corresponding dimensions of the [SurfaceView] provided to the [GLFrontBufferedRenderer]
844          *   as pre-rotation can occasionally swap width and height parameters in order to avoid GPU
845          *   composition to rotate content. This should be used as input to [GLES20.glViewport].
846          *   Additionally this also contains a frame buffer identifier that can be used to retarget
847          *   rendering operations to the original destination after rendering into intermediate
848          *   scratch buffers.
849          * @param transform Matrix that should be applied to the rendering in this callback. This
850          *   should be consumed as input to any vertex shader implementations. Buffers are
851          *   pre-rotated in advance in order to avoid unnecessary overhead of GPU composition to
852          *   rotate content in the same install orientation of the display. This is a 4 x 4 matrix
853          *   is represented as a flattened array of 16 floating point values. Consumers are expected
854          *   to leverage [Matrix.multiplyMM] with this parameter alongside any additional
855          *   transformations that are to be applied. For example:
856          * ```
857          * val myMatrix = FloatArray(16)
858          * Matrix.orthoM(
859          *      myMatrix, // matrix
860          *      0, // offset starting index into myMatrix
861          *      0f, // left
862          *      bufferInfo.bufferWidth.toFloat(), // right
863          *      0f, // bottom
864          *      bufferInfo.bufferHeight.toFloat(), // top
865          *      -1f, // near
866          *      1f, // far
867          * )
868          * val result = FloatArray(16)
869          * Matrix.multiplyMM(result, 0, myMatrix, 0, transform, 0)
870          * ```
871          *
872          * @param params optional parameter provided to render the entire scene into the multi
873          *   buffered layer. This is a collection of all parameters provided in consecutive
874          *   invocations to [GLFrontBufferedRenderer.renderFrontBufferedLayer] since the last call
875          *   to [GLFrontBufferedRenderer.commit] has been made. After
876          *   [GLFrontBufferedRenderer.commit] is invoked, this collection is cleared and new
877          *   parameters are added on each subsequent call to
878          *   [GLFrontBufferedRenderer.renderFrontBufferedLayer].
879          *
880          * Consider the following example:
881          *
882          * myFrontBufferedRenderer.renderFrontBufferedLayer(1)
883          * myFrontBufferedRenderer.renderFrontBufferedLayer(2)
884          * myFrontBufferedRenderer.renderFrontBufferedLayer(3) myFrontBufferedRenderer.commit()
885          *
886          * This will generate a callback to this method with the params collection containing values
887          * [1, 2, 3]
888          *
889          * myFrontBufferedRenderer.renderFrontBufferedLayer(4)
890          * myFrontBufferedRenderer.renderFrontBufferedLayer(5) myFrontBufferedRenderer.commit()
891          *
892          * This will generate a callback to this method with the params collection containing values
893          * [4, 5]
894          *
895          * By default [GLES20.glViewport] is invoked with the correct dimensions of the buffer that
896          * is being rendered into taking into account pre-rotation transformations
897          */
898         @WorkerThread
899         fun onDrawMultiBufferedLayer(
900             eglManager: EGLManager,
901             width: Int,
902             height: Int,
903             bufferInfo: BufferInfo,
904             transform: FloatArray,
905             params: Collection<T>
906         )
907 
908         /**
909          * Optional callback invoked when rendering to the front buffered layer is complete but
910          * before the buffers are submitted to the hardware compositor. This provides consumers a
911          * mechanism for synchronizing the transaction with other [SurfaceControlCompat] objects
912          * that maybe rendered within the scene.
913          *
914          * @param frontBufferedLayerSurfaceControl Handle to the [SurfaceControlCompat] where the
915          *   front buffered layer content is drawn. This can be used to configure various properties
916          *   of the [SurfaceControlCompat] like z-ordering or visibility with the corresponding
917          *   [SurfaceControlCompat.Transaction].
918          * @param transaction Current [SurfaceControlCompat.Transaction] to apply updated buffered
919          *   content to the front buffered layer.
920          */
921         @WorkerThread
922         fun onFrontBufferedLayerRenderComplete(
923             frontBufferedLayerSurfaceControl: SurfaceControlCompat,
924             transaction: SurfaceControlCompat.Transaction
925         ) {
926             // Default implementation is a no-op
927         }
928 
929         /**
930          * Optional callback invoked when rendering to the multi buffered layer is complete but
931          * before the buffers are submitted to the hardware compositor. This provides consumers a
932          * mechanism for synchronizing the transaction with other [SurfaceControlCompat] objects
933          * that maybe rendered within the scene.
934          *
935          * @param frontBufferedLayerSurfaceControl Handle to the [SurfaceControlCompat] where the
936          *   front buffered layer content is drawn. This can be used to configure various properties
937          *   of the [SurfaceControlCompat] like z-ordering or visibility with the corresponding
938          *   [SurfaceControlCompat.Transaction].
939          * @param multiBufferedLayerSurfaceControl Handle to the [SurfaceControlCompat] where the
940          *   front buffered layer content is drawn. This can be used to configure various properties
941          *   of the [SurfaceControlCompat] like z-ordering or visibility with the corresponding
942          *   [SurfaceControlCompat.Transaction].
943          * @param transaction Current [SurfaceControlCompat.Transaction] to apply updated buffered
944          *   content to the multi buffered layer.
945          */
946         @WorkerThread
947         fun onMultiBufferedLayerRenderComplete(
948             frontBufferedLayerSurfaceControl: SurfaceControlCompat,
949             multiBufferedLayerSurfaceControl: SurfaceControlCompat,
950             transaction: SurfaceControlCompat.Transaction
951         ) {
952             // Default implementation is a no-op
953         }
954     }
955 }
956