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.annotation.SuppressLint
20 import android.graphics.BlendMode
21 import android.graphics.Canvas
22 import android.graphics.Color
23 import android.graphics.ColorSpace
24 import android.graphics.RenderNode
25 import android.hardware.HardwareBuffer
26 import android.os.Build
27 import android.util.Log
28 import android.view.SurfaceHolder
29 import android.view.SurfaceView
30 import androidx.annotation.RequiresApi
31 import androidx.annotation.WorkerThread
32 import androidx.graphics.CanvasBufferedRenderer
33 import androidx.graphics.surface.SurfaceControlCompat
34 import androidx.graphics.utils.HandlerThreadExecutor
35 import androidx.hardware.HardwareBufferFormat
36 import androidx.hardware.SyncFenceCompat
37 import java.util.Collections
38 import java.util.concurrent.CountDownLatch
39 import java.util.concurrent.atomic.AtomicBoolean
40 import java.util.concurrent.atomic.AtomicInteger
41 import kotlin.math.max
42 
43 /**
44  * Class responsible for supporting a "front buffered" rendering system. This allows for lower
45  * latency graphics by leveraging a combination of front buffered and multi buffered content layers.
46  * Active content is rendered first into the front buffered layer which is simultaneously being
47  * presented to the display. Periodically content is rendered into the multi buffered layer which
48  * will have more traditional latency guarantees, however, minimizes the impact of visual artifacts
49  * due to graphical tearing.
50  *
51  * @param surfaceView Target SurfaceView to act as the parent rendering layer for multi buffered
52  *   content
53  * @param callback Callbacks used to render into front and multi buffered layers as well as
54  *   configuring [SurfaceControlCompat.Transaction]s for controlling these layers in addition to
55  *   other [SurfaceControlCompat] instances that must be updated atomically within the user
56  *   interface. These callbacks are invoked on an internal rendering thread. The templated type here
57  *   is consumer defined to represent the data structures to be consumed for rendering within
58  *   [Callback.onDrawFrontBufferedLayer] and [Callback.onDrawMultiBufferedLayer] and are provided by
59  *   the [CanvasFrontBufferedRenderer.renderFrontBufferedLayer] and
60  *   [CanvasFrontBufferedRenderer.renderMultiBufferedLayer] methods.
61  * @param bufferFormat format of the underlying buffers being rendered into by
62  *   [CanvasFrontBufferedRenderer]. The particular valid combinations for a given Android version
63  *   and implementation should be documented by that version. [HardwareBuffer.RGBA_8888] and
64  *   [HardwareBuffer.RGBX_8888] are guaranteed to be supported. However, consumers are recommended
65  *   to query the desired HardwareBuffer configuration using [HardwareBuffer.isSupported]. The
66  *   default is [HardwareBuffer.RGBA_8888].
67  */
68 @RequiresApi(Build.VERSION_CODES.Q)
69 class CanvasFrontBufferedRenderer<T>
70 @JvmOverloads
71 constructor(
72     surfaceView: SurfaceView,
73     callback: Callback<T>,
74     @HardwareBufferFormat val bufferFormat: Int = HardwareBuffer.RGBA_8888
75 ) {
76 
77     /** Target SurfaceView for rendering */
78     private var mSurfaceView: SurfaceView? = null
79 
80     private var mCallback: Callback<T>? = null
81 
82     /**
83      * Executor used to deliver callbacks for rendering as well as issuing surface control
84      * transactions
85      */
86     private val mHandlerThread = HandlerThreadExecutor("CanvasRenderThread")
87 
88     /** RenderNode used to render multi buffered content */
89     private var mMultiBufferedRenderNode: RenderNode? = null
90 
91     /**
92      * Renderer used to draw [RenderNode] into a [HardwareBuffer] that is used to configure the
93      * parent SurfaceControl that represents the multi-buffered scene
94      */
95     private var mMultiBufferedCanvasRenderer: CanvasBufferedRenderer? = null
96 
97     /**
98      * Renderer used to draw the front buffer content into a HardwareBuffer instance that is
99      * preserved across frames
100      */
101     private var mPersistedCanvasRenderer: SingleBufferedCanvasRenderer<T>? = null
102 
103     /**
104      * [SurfaceControlCompat] used to configure buffers and visibility of the front buffered layer
105      */
106     private var mFrontBufferSurfaceControl: SurfaceControlCompat? = null
107 
108     /**
109      * [SurfaceControlCompat] used to configure buffers and visibility of the multi-buffered layer
110      */
111     private var mParentSurfaceControl: SurfaceControlCompat? = null
112 
113     /**
114      * Queue of parameters to be consumed in [Callback.onDrawFrontBufferedLayer] with the parameter
115      * provided in [renderFrontBufferedLayer]. When [commit] is invoked the collection is used to
116      * render the multi-buffered scene and is subsequently cleared
117      */
118     private var mParams = ParamQueue<T>()
119 
120     /**
121      * Flag to determine if the [CanvasFrontBufferedRenderer] has previously been released. If this
122      * flag is true, then subsequent requests to [renderFrontBufferedLayer],
123      * [renderMultiBufferedLayer], [commit], and [release] are ignored.
124      */
125     private var mIsReleased = false
126 
127     /**
128      * Flag to determine if a request to clear the front buffer content is pending. This should only
129      * be accessed on the background thread
130      */
131     private val mPendingClear = AtomicBoolean(true)
132 
133     /**
134      * Runnable executed on the GLThread to update [FrontBufferSyncStrategy.isVisible] as well as
135      * hide the SurfaceControl associated with the front buffered layer
136      */
137     private val mCancelRunnable = Runnable {
138         mPersistedCanvasRenderer?.isVisible = false
139         mFrontBufferSurfaceControl?.let { frontBufferSurfaceControl ->
140             SurfaceControlCompat.Transaction()
141                 .setVisibility(frontBufferSurfaceControl, false)
142                 .commit()
143         }
144     }
145 
146     /** Current HardwareBuffer that is being presented by the multi buffered layer */
147     private var mCurrentMultiBuffer: HardwareBuffer? = null
148 
149     @Volatile private var mFrontBufferReleaseFence: SyncFenceCompat? = null
150     private val mCommitCount = AtomicInteger(0)
151     private var mColorSpace: ColorSpace = CanvasBufferedRenderer.DefaultColorSpace
152     private var mInverse = BufferTransformHintResolver.UNKNOWN_TRANSFORM
153     private var mWidth = -1
154     private var mHeight = -1
155     private var mTransform = BufferTransformHintResolver.UNKNOWN_TRANSFORM
156     private val mTransformResolver = BufferTransformHintResolver()
157     private val mHolderCallback =
158         object : SurfaceHolder.Callback2 {
159 
160             override fun surfaceCreated(p0: SurfaceHolder) {
161                 // NO-OP
162             }
163 
164             override fun surfaceChanged(
165                 holder: SurfaceHolder,
166                 format: Int,
167                 width: Int,
168                 height: Int
169             ) {
170                 mSurfaceView?.let { update(it, width, height) }
171             }
172 
173             override fun surfaceDestroyed(p0: SurfaceHolder) {
174                 releaseInternal(true)
175             }
176 
177             override fun surfaceRedrawNeeded(holder: SurfaceHolder) {
178                 val latch = CountDownLatch(1)
179                 renderMultiBufferedLayerInternal { latch.countDown() }
180                 latch.await()
181             }
182 
183             override fun surfaceRedrawNeededAsync(
184                 holder: SurfaceHolder,
185                 drawingFinished: Runnable
186             ) {
187                 renderMultiBufferedLayerInternal(callback = drawingFinished)
188             }
189         }
190 
191     init {
192         mSurfaceView = surfaceView
193         mCallback = callback
194         surfaceView.holder.addCallback(mHolderCallback)
195         with(surfaceView.holder) {
196             if (surface != null && surface.isValid) {
197                 update(surfaceView, surfaceView.width, surfaceView.height)
198                 renderMultiBufferedLayerInternal()
199             }
200         }
201     }
202 
203     internal fun update(surfaceView: SurfaceView, width: Int, height: Int) {
204         if (width <= 0 || height <= 0) {
205             Log.w(
206                 TAG,
207                 "Invalid dimensions provided, width and height must be > 0. " +
208                     "width: $width height: $height"
209             )
210             return
211         }
212         val transformHint = mTransformResolver.getBufferTransformHint(surfaceView)
213         if ((mTransform != transformHint || mWidth != width || mHeight != height) && isValid()) {
214             releaseInternal(true)
215 
216             val bufferTransform = BufferTransformer()
217             val inverse = bufferTransform.invertBufferTransform(transformHint)
218             bufferTransform.computeTransform(width, height, inverse)
219             val bufferWidth = bufferTransform.bufferWidth
220             val bufferHeight = bufferTransform.bufferHeight
221 
222             val parentSurfaceControl =
223                 SurfaceControlCompat.Builder()
224                     .setParent(surfaceView)
225                     .setName("MultiBufferedLayer")
226                     .build()
227                     .apply {
228                         // SurfaceControl is not visible by default so make it visible right
229                         // after creation
230                         SurfaceControlCompat.Transaction().setVisibility(this, true).commit()
231                     }
232 
233             val frontBufferSurfaceControl =
234                 SurfaceControlCompat.Builder()
235                     .setParent(parentSurfaceControl)
236                     .setName("FrontBufferedLayer")
237                     .build()
238 
239             FrontBufferUtils.configureFrontBufferLayerFrameRate(frontBufferSurfaceControl)?.commit()
240 
241             var singleBufferedCanvasRenderer: SingleBufferedCanvasRenderer<T>? = null
242             singleBufferedCanvasRenderer =
243                 SingleBufferedCanvasRenderer(
244                         width,
245                         height,
246                         bufferWidth,
247                         bufferHeight,
248                         bufferFormat,
249                         inverse,
250                         mHandlerThread,
251                         object : SingleBufferedCanvasRenderer.RenderCallbacks<T> {
252 
253                             override fun render(canvas: Canvas, width: Int, height: Int, param: T) {
254                                 if (mPendingClear.compareAndSet(true, false)) {
255                                     mFrontBufferReleaseFence?.let { fence ->
256                                         fence.awaitForever()
257                                         fence.close()
258                                         mFrontBufferReleaseFence = null
259                                     }
260                                     canvas.drawColor(Color.BLACK, BlendMode.CLEAR)
261                                 }
262                                 mCallback?.onDrawFrontBufferedLayer(canvas, width, height, param)
263                             }
264 
265                             @SuppressLint("WrongConstant")
266                             override fun onBufferReady(
267                                 hardwareBuffer: HardwareBuffer,
268                                 syncFenceCompat: SyncFenceCompat?
269                             ) {
270                                 if (frontBufferSurfaceControl.isValid()) {
271                                     val transaction =
272                                         SurfaceControlCompat.Transaction()
273                                             .setLayer(frontBufferSurfaceControl, Integer.MAX_VALUE)
274                                             .setBuffer(
275                                                 frontBufferSurfaceControl,
276                                                 hardwareBuffer,
277                                                 if (
278                                                     singleBufferedCanvasRenderer?.isVisible == true
279                                                 ) {
280                                                     null
281                                                 } else {
282                                                     syncFenceCompat
283                                                 }
284                                             ) { releaseFence ->
285                                                 mFrontBufferReleaseFence?.close()
286                                                 mFrontBufferReleaseFence = releaseFence
287                                             }
288                                             .setVisibility(frontBufferSurfaceControl, true)
289                                             .reparent(
290                                                 frontBufferSurfaceControl,
291                                                 parentSurfaceControl
292                                             )
293                                     if (
294                                         transformHint !=
295                                             BufferTransformHintResolver.UNKNOWN_TRANSFORM
296                                     ) {
297                                         transaction.setBufferTransform(
298                                             frontBufferSurfaceControl,
299                                             transformHint
300                                         )
301                                     }
302                                     mCallback?.onFrontBufferedLayerRenderComplete(
303                                         frontBufferSurfaceControl,
304                                         transaction
305                                     )
306                                     transaction.commit()
307                                     singleBufferedCanvasRenderer?.isVisible = true
308                                 }
309                                 syncFenceCompat?.close()
310                             }
311                         }
312                     )
313                     .apply { colorSpace = mColorSpace }
314 
315             val renderNode = RenderNode("node").apply { setPosition(0, 0, width, height) }
316 
317             mMultiBufferedCanvasRenderer =
318                 CanvasBufferedRenderer.Builder(bufferWidth, bufferHeight)
319                     .setUsageFlags(FrontBufferUtils.BaseFlags)
320                     .setBufferFormat(bufferFormat)
321                     .build()
322                     .apply { setContentRoot(renderNode) }
323 
324             mMultiBufferedRenderNode = renderNode
325             mFrontBufferSurfaceControl = frontBufferSurfaceControl
326             mPersistedCanvasRenderer = singleBufferedCanvasRenderer
327             mParentSurfaceControl = parentSurfaceControl
328             mTransform = transformHint
329             mWidth = width
330             mHeight = height
331             mInverse = inverse
332         }
333     }
334 
335     /**
336      * Configures the [ColorSpace] that the content should be rendered with for the front and multi
337      * buffered layers. This parameter is only consumed on Android U and above. For older API levels
338      * this is ignored.
339      */
340     var colorSpace: ColorSpace
341         get() = mColorSpace
342         set(value) {
343             mColorSpace = value
344             mPersistedCanvasRenderer?.colorSpace = value
345         }
346 
347     /**
348      * Render content to the front buffered layer providing optional parameters to be consumed in
349      * [Callback.onDrawFrontBufferedLayer]. Additionally the parameter provided here will also be
350      * consumed in [Callback.onDrawMultiBufferedLayer] when the corresponding [commit] method is
351      * invoked, which will include all [param]s in each call made to this method up to the
352      * corresponding [commit] call.
353      *
354      * If this [CanvasFrontBufferedRenderer] has been released, that is [isValid] returns `false`,
355      * this call is ignored.
356      *
357      * @param param Optional parameter to be consumed when rendering content into the commit layer
358      */
359     fun renderFrontBufferedLayer(param: T) {
360         if (isValid()) {
361             mParams.add(param)
362             if (!isCommitting()) {
363                 flushPendingFrontBufferRenders()
364             }
365         } else {
366             Log.w(
367                 TAG,
368                 "Attempt to render to front buffered layer when " +
369                     "CanvasFrontBufferedRenderer has been released"
370             )
371         }
372     }
373 
374     private fun isCommitting() = mCommitCount.get() != 0
375 
376     private fun flushPendingFrontBufferRenders() {
377         mParams.flush { p -> mPersistedCanvasRenderer?.render(p) }
378     }
379 
380     /**
381      * Requests to render to the multi buffered layer. This schedules a call to
382      * [Callback.onDrawMultiBufferedLayer] with the parameters provided. If the front buffered layer
383      * is visible, this will hide this layer after rendering to the multi buffered layer is
384      * complete. This is equivalent to calling
385      * [CanvasFrontBufferedRenderer.renderFrontBufferedLayer] for each parameter provided in the
386      * collection followed by a single call to [CanvasFrontBufferedRenderer.commit]. This is useful
387      * for re-rendering the multi buffered scene when the corresponding Activity is being resumed
388      * from the background in which the contents should be re-drawn. Additionally this allows for
389      * applications to decide to dynamically render to either front or multi buffered layers.
390      *
391      * If this [CanvasFrontBufferedRenderer] has been released, that is [isValid] returns 'false',
392      * this call is ignored.
393      *
394      * @param params Parameters that to be consumed when rendering to the multi buffered layer.
395      *   These parameters will be provided in the corresponding call to
396      *   [Callback.onDrawMultiBufferedLayer]
397      */
398     fun renderMultiBufferedLayer(params: Collection<T>) {
399         renderMultiBufferedLayerInternal(params)
400     }
401 
402     /**
403      * Helper method to commit contents to the multi buffered layer invoking an optional callback
404      * when rendering is complete
405      */
406     internal fun renderMultiBufferedLayerInternal(
407         params: Collection<T> = Collections.emptyList(),
408         callback: Runnable? = null
409     ) {
410         if (isValid()) {
411             mParams.addAll(params)
412             commitInternal(callback)
413         } else {
414             Log.w(
415                 TAG,
416                 "Attempt to render to the multi buffered layer when " +
417                     "CanvasFrontBufferedRenderer has been released"
418             )
419         }
420     }
421 
422     /**
423      * Determines whether or not the [CanvasFrontBufferedRenderer] is in a valid state. That is the
424      * [release] method has not been called. If this returns false, then subsequent calls to
425      * [renderFrontBufferedLayer], [renderMultiBufferedLayer], [commit], and [release] are ignored
426      *
427      * @return `true` if this [CanvasFrontBufferedRenderer] has been released, `false` otherwise
428      */
429     fun isValid() = !mIsReleased
430 
431     @SuppressLint("WrongConstant")
432     internal fun setParentSurfaceControlBuffer(
433         frontBufferSurfaceControl: SurfaceControlCompat?,
434         parentSurfaceControl: SurfaceControlCompat?,
435         persistedCanvasRenderer: SingleBufferedCanvasRenderer<T>?,
436         multiBufferedCanvasRenderer: CanvasBufferedRenderer,
437         transform: Int,
438         buffer: HardwareBuffer,
439         fence: SyncFenceCompat?
440     ) {
441         if (
442             frontBufferSurfaceControl != null &&
443                 frontBufferSurfaceControl.isValid() &&
444                 parentSurfaceControl != null &&
445                 parentSurfaceControl.isValid()
446         ) {
447             mCurrentMultiBuffer = buffer
448             persistedCanvasRenderer?.isVisible = false
449             val transaction =
450                 SurfaceControlCompat.Transaction()
451                     .setVisibility(frontBufferSurfaceControl, false)
452                     // Set a null buffer here so that the original front buffer's release callback
453                     // gets invoked and we can clear the content of the front buffer
454                     .setBuffer(frontBufferSurfaceControl, null)
455                     .setVisibility(parentSurfaceControl, true)
456                     .setBuffer(parentSurfaceControl, buffer, fence) { releaseFence ->
457                         mPendingClear.set(true)
458                         val result = mCommitCount.updateAndGet { value -> max(value - 1, 0) }
459                         if (result != 0) {
460                             mSurfaceView?.post { commitInternal() }
461                         } else {
462                             flushPendingFrontBufferRenders()
463                         }
464                         multiBufferedCanvasRenderer.releaseBuffer(buffer, releaseFence)
465                     }
466 
467             if (transform != BufferTransformHintResolver.UNKNOWN_TRANSFORM) {
468                 transaction.setBufferTransform(parentSurfaceControl, transform)
469             }
470             mCallback?.onMultiBufferedLayerRenderComplete(
471                 frontBufferSurfaceControl,
472                 parentSurfaceControl,
473                 transaction
474             )
475             transaction.commit()
476         }
477     }
478 
479     /**
480      * Clears the contents of both the front and multi buffered layers. This triggers a call to
481      * [Callback.onMultiBufferedLayerRenderComplete] and hides the front buffered layer.
482      */
483     @SuppressWarnings("WrongConstant")
484     fun clear() {
485         if (isValid()) {
486             mParams.clear()
487             val persistedCanvasRenderer =
488                 mPersistedCanvasRenderer?.apply {
489                     cancelPending()
490                     clear()
491                 }
492             val transform = mTransform
493             val inverse = mInverse
494             val frontBufferSurfaceControl = mFrontBufferSurfaceControl
495             val parentSurfaceControl = mParentSurfaceControl
496             val multiBufferedCanvasRenderer = mMultiBufferedCanvasRenderer
497             val targetColorSpace = mColorSpace
498             mHandlerThread.execute {
499                 multiBufferedCanvasRenderer?.let { multiBufferRenderer ->
500                     with(multiBufferRenderer) {
501                         mMultiBufferedRenderNode?.let { renderNode ->
502                             val canvas = renderNode.beginRecording()
503                             canvas.drawColor(Color.BLACK, BlendMode.CLEAR)
504                             renderNode.endRecording()
505                         }
506 
507                         obtainRenderRequest()
508                             .apply {
509                                 if (inverse != BufferTransformHintResolver.UNKNOWN_TRANSFORM) {
510                                     setBufferTransform(inverse)
511                                 }
512                             }
513                             .setColorSpace(targetColorSpace)
514                             .drawAsync(mHandlerThread) { result ->
515                                 setParentSurfaceControlBuffer(
516                                     frontBufferSurfaceControl,
517                                     parentSurfaceControl,
518                                     persistedCanvasRenderer,
519                                     multiBufferRenderer,
520                                     transform,
521                                     result.hardwareBuffer,
522                                     result.fence
523                                 )
524                             }
525                     }
526                 }
527             }
528         } else {
529             Log.w(
530                 TAG,
531                 "Attempt to clear front buffer after CanvasFrontBufferRenderer " +
532                     "has been released"
533             )
534         }
535     }
536 
537     /**
538      * Requests to render the entire scene to the multi buffered layer and schedules a call to
539      * [Callback.onDrawMultiBufferedLayer]. The parameters provided to
540      * [Callback.onDrawMultiBufferedLayer] will include each argument provided to every
541      * [renderFrontBufferedLayer] call since the last call to [commit] has been made. When rendering
542      * to the multi-buffered layer is complete, this synchronously hides the front buffer and
543      * updates the multi buffered layer.
544      *
545      * If this [CanvasFrontBufferedRenderer] has been released, that is [isValid] returns `false`,
546      * this call is ignored.
547      */
548     fun commit() {
549         if (mCommitCount.getAndIncrement() == 0) {
550             commitInternal()
551         }
552     }
553 
554     /**
555      * Helper method to commit contents to the multi buffered layer, invoking an optional callback
556      * on completion
557      */
558     @SuppressWarnings("WrongConstant")
559     private fun commitInternal(onComplete: Runnable? = null) {
560         if (isValid()) {
561             val persistedCanvasRenderer = mPersistedCanvasRenderer?.apply { cancelPending() }
562             val params = mParams.release()
563             val width = mWidth
564             val height = mHeight
565             val frontBufferSurfaceControl = mFrontBufferSurfaceControl
566             val parentSurfaceControl = mParentSurfaceControl
567             val multiBufferedCanvasRenderer = mMultiBufferedCanvasRenderer
568             val inverse = mInverse
569             val transform = mTransform
570             val targetColorSpace = mColorSpace
571             mHandlerThread.execute {
572                 multiBufferedCanvasRenderer?.let { multiBufferedRenderer ->
573                     with(multiBufferedRenderer) {
574                         mMultiBufferedRenderNode?.let { renderNode ->
575                             val canvas = renderNode.beginRecording()
576                             mCallback?.onDrawMultiBufferedLayer(canvas, width, height, params)
577                             renderNode.endRecording()
578                         }
579 
580                         params.clear()
581                         obtainRenderRequest()
582                             .apply {
583                                 if (inverse != BufferTransformHintResolver.UNKNOWN_TRANSFORM) {
584                                     setBufferTransform(inverse)
585                                 }
586                             }
587                             .setColorSpace(targetColorSpace)
588                             .drawAsync(mHandlerThread) { result ->
589                                 setParentSurfaceControlBuffer(
590                                     frontBufferSurfaceControl,
591                                     parentSurfaceControl,
592                                     persistedCanvasRenderer,
593                                     multiBufferedCanvasRenderer,
594                                     transform,
595                                     result.hardwareBuffer,
596                                     result.fence
597                                 )
598                                 onComplete?.run()
599                             }
600                     }
601                 }
602             }
603         } else {
604             Log.w(
605                 TAG,
606                 "Attempt to render to the multi buffered layer when " +
607                     "CanvasFrontBufferedRenderer has been released"
608             )
609         }
610     }
611 
612     /**
613      * Requests to cancel rendering and hides the front buffered layer. Unlike [commit], this does
614      * not schedule a call to render into the multi buffered layer. This is useful in palm rejection
615      * use cases, where some initial touch events might be processed before a corresponding cancel
616      * event is received indicating the touch gesture is coming from a palm rather than intentional
617      * user input. In the case where MotionEvent#getAction returns ACTION_CANCEL, this is to be
618      * invoked.
619      *
620      * If this [GLFrontBufferedRenderer] has been released, that is [isValid] returns `false`, this
621      * call is ignored.
622      */
623     fun cancel() {
624         if (isValid()) {
625             mParams.clear()
626             mPersistedCanvasRenderer?.cancelPending()
627             mHandlerThread.execute(mCancelRunnable)
628             mPersistedCanvasRenderer?.clear()
629         } else {
630             Log.w(
631                 TAG,
632                 "Attempt to cancel rendering to front buffer after " +
633                     "CanvasFrontBufferRenderer has been released"
634             )
635         }
636     }
637 
638     internal fun releaseInternal(cancelPending: Boolean, releaseCallback: (() -> Unit)? = null) {
639         val renderer = mPersistedCanvasRenderer
640         if (renderer != null) {
641             // Store a local copy of the corresponding SurfaceControls and renderers to make sure
642             // the release callback is not invoked on potentially newly created dependencies
643             // if we are in the middle of a render request and we get a surface changed event
644             val frontBufferSurfaceControl = mFrontBufferSurfaceControl
645             val parentSurfaceControl = mParentSurfaceControl
646             val multiBufferRenderer = mMultiBufferedCanvasRenderer
647             val renderNode = mMultiBufferedRenderNode
648 
649             mFrontBufferSurfaceControl = null
650             mParentSurfaceControl = null
651             mPersistedCanvasRenderer = null
652             mMultiBufferedCanvasRenderer = null
653             mMultiBufferedRenderNode = null
654             mWidth = -1
655             mHeight = -1
656             mTransform = BufferTransformHintResolver.UNKNOWN_TRANSFORM
657 
658             renderer.release(cancelPending) {
659                 mCurrentMultiBuffer?.close()
660                 mCurrentMultiBuffer = null
661                 val reparentTransaction = SurfaceControlCompat.Transaction()
662                 if (frontBufferSurfaceControl?.isValid() == true) {
663                     reparentTransaction.reparent(frontBufferSurfaceControl, null)
664                 }
665                 if (parentSurfaceControl?.isValid() == true) {
666                     reparentTransaction.reparent(parentSurfaceControl, null)
667                 }
668                 reparentTransaction.commit()
669                 reparentTransaction.close()
670                 frontBufferSurfaceControl?.release()
671                 parentSurfaceControl?.release()
672                 multiBufferRenderer?.close()
673                 renderNode?.discardDisplayList()
674                 releaseCallback?.invoke()
675             }
676         } else if (releaseCallback != null) {
677             mHandlerThread.execute(releaseCallback)
678         }
679     }
680 
681     /**
682      * Releases the [CanvasFrontBufferedRenderer]. In process requests are ignored. If the
683      * [CanvasFrontBufferedRenderer] is already released, that is [isValid] returns `false`, this
684      * method does nothing.
685      */
686     @JvmOverloads
687     fun release(cancelPending: Boolean, onReleaseComplete: (() -> Unit)? = null) {
688         if (!mIsReleased) {
689             mSurfaceView?.holder?.removeCallback(mHolderCallback)
690             mSurfaceView = null
691             releaseInternal(cancelPending) {
692                 mCallback = null
693                 onReleaseComplete?.invoke()
694                 mHandlerThread.quit()
695             }
696             mIsReleased = true
697         }
698     }
699 
700     /**
701      * Provides callbacks for consumers to draw into the front and multi buffered layers as well as
702      * provide opportunities to synchronize [SurfaceControlCompat.Transaction]s to submit the layers
703      * to the hardware compositor.
704      */
705     @JvmDefaultWithCompatibility
706     interface Callback<T> {
707 
708         /**
709          * Callback invoked to render content into the front buffered layer with the specified
710          * parameters.
711          *
712          * @param canvas [Canvas] used to issue drawing instructions into the front buffered layer
713          * @param bufferWidth Width of the buffer that is being rendered into.
714          * @param bufferHeight Height of the buffer that is being rendered into.
715          * @param param optional parameter provided the corresponding
716          *   [CanvasFrontBufferedRenderer.renderFrontBufferedLayer] method that triggered this
717          *   request to render into the front buffered layer
718          */
719         @WorkerThread
720         fun onDrawFrontBufferedLayer(canvas: Canvas, bufferWidth: Int, bufferHeight: Int, param: T)
721 
722         /**
723          * Callback invoked to render content into the front buffered layer with the specified
724          * parameters.
725          *
726          * @param canvas [Canvas] used to issue drawing instructions into the front buffered layer
727          * @param bufferWidth Width of the buffer that is being rendered into.
728          * @param bufferHeight Height of the buffer that is being rendered into.
729          * @param params optional parameter provided to render the entire scene into the multi
730          *   buffered layer. This is a collection of all parameters provided in consecutive
731          *   invocations to [CanvasFrontBufferedRenderer.renderFrontBufferedLayer] since the last
732          *   call to [CanvasFrontBufferedRenderer.commit] has been made. After
733          *   [CanvasFrontBufferedRenderer.commit] is invoked, this collection is cleared and new
734          *   parameters are added on each subsequent call to
735          *   [CanvasFrontBufferedRenderer.renderFrontBufferedLayer]
736          */
737         @WorkerThread
738         fun onDrawMultiBufferedLayer(
739             canvas: Canvas,
740             bufferWidth: Int,
741             bufferHeight: Int,
742             params: Collection<T>
743         )
744 
745         /**
746          * Optional callback invoked when rendering to the front buffered layer is complete but
747          * before the buffers are submitted to the hardware compositor. This provides consumers a
748          * mechanism for synchronizing the transaction with other [SurfaceControlCompat] objects
749          * that maybe rendered within the scene.
750          *
751          * @param frontBufferedLayerSurfaceControl Handle to the [SurfaceControlCompat] where the
752          *   front buffered layer content is drawn. This can be used to configure various properties
753          *   of the [SurfaceControlCompat] like z-ordering or visibility with the corresponding
754          *   [SurfaceControlCompat.Transaction].
755          * @param transaction Current [SurfaceControlCompat.Transaction] to apply updated buffered
756          *   content to the front buffered layer.
757          */
758         @WorkerThread
759         fun onFrontBufferedLayerRenderComplete(
760             frontBufferedLayerSurfaceControl: SurfaceControlCompat,
761             transaction: SurfaceControlCompat.Transaction
762         ) {
763             // Default implementation is a no-op
764         }
765 
766         /**
767          * Optional callback invoked when rendering to the multi buffered layer is complete but
768          * before the buffers are submitted to the hardware compositor. This provides consumers a
769          * mechanism for synchronizing the transaction with other [SurfaceControlCompat] objects
770          * that maybe rendered within the scene.
771          *
772          * @param frontBufferedLayerSurfaceControl Handle to the [SurfaceControlCompat] where the
773          *   front buffered layer content is drawn. This can be used to configure various properties
774          *   of the [SurfaceControlCompat] like z-ordering or visibility with the corresponding
775          *   [SurfaceControlCompat.Transaction].
776          * @param multiBufferedLayerSurfaceControl Handle to the [SurfaceControlCompat] where the
777          *   multi-buffered layer content is drawn. This can be used to configure various properties
778          *   of the [SurfaceControlCompat] like z-ordering or visibility with the corresponding
779          *   [SurfaceControlCompat.Transaction].
780          * @param transaction Current [SurfaceControlCompat.Transaction] to apply updated buffered
781          *   content to the multi buffered layer.
782          */
783         @WorkerThread
784         fun onMultiBufferedLayerRenderComplete(
785             frontBufferedLayerSurfaceControl: SurfaceControlCompat,
786             multiBufferedLayerSurfaceControl: SurfaceControlCompat,
787             transaction: SurfaceControlCompat.Transaction
788         ) {
789             // Default implementation is a no-op
790         }
791     }
792 
793     internal companion object {
794 
795         internal const val TAG = "LowLatencyCanvas"
796     }
797 }
798