GLFrameBufferRenderer.kt

/*
 * Copyright 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.graphics.opengl

import android.hardware.HardwareBuffer
import android.opengl.GLES20
import android.opengl.Matrix
import android.os.Build
import android.util.Log
import android.view.SurfaceHolder
import android.view.SurfaceView
import androidx.annotation.IntRange
import androidx.annotation.RequiresApi
import androidx.annotation.WorkerThread
import androidx.graphics.lowlatency.BufferInfo
import androidx.graphics.lowlatency.BufferTransformHintResolver
import androidx.graphics.lowlatency.BufferTransformer
import androidx.graphics.opengl.egl.EGLManager
import androidx.graphics.opengl.egl.EGLSpec
import androidx.graphics.surface.SurfaceControlCompat
import androidx.hardware.DefaultFlags
import androidx.hardware.DefaultNumBuffers
import androidx.hardware.HardwareBufferFormat
import androidx.hardware.HardwareBufferUsage
import androidx.hardware.SyncFenceCompat
import java.lang.IllegalArgumentException
import java.lang.IllegalStateException
import java.util.concurrent.CountDownLatch

/**
 * Class responsible for supporting rendering to frame buffer objects that are backed by
 * [HardwareBuffer] instances. This provides for more flexibility in OpenGL rendering as it supports
 * configuration of the number of buffers within the underlying swap chain, pixel format of the
 * buffers as well as fine grained control over synchronization of buffer content.
 */
@RequiresApi(Build.VERSION_CODES.Q)
@Suppress("AcronymName")
class GLFrameBufferRenderer internal constructor(
    private val surfaceControlProvider: SurfaceControlProvider,
    callback: Callback,
    private val mFormat: Int,
    private val mUsage: Long,
    private val mMaxBuffers: Int,
    private val mSyncStrategy: SyncStrategy,
    glRenderer: GLRenderer?
) {

    /**
     * Builder used to create a [GLFrameBufferRenderer] with various configurations
     */
    class Builder {
        private var mBufferFormat = HardwareBuffer.RGBA_8888
        private var mUsageFlags = DefaultFlags
        private var mMaxBuffers = DefaultNumBuffers
        private var mGLRenderer: GLRenderer? = null
        private var mSyncStrategy: SyncStrategy = SyncStrategy.ALWAYS
        private val mSurfaceControlProvider: SurfaceControlProvider
        private val mCallback: Callback

        /**
         * Create a new [GLFrameBufferRenderer.Builder] with the provided [SurfaceView] to
         * be the parent of the [SurfaceControlCompat] that is presented on screen.
         * @param surfaceView SurfaceView to be the parent of the [SurfaceControlCompat] instance
         * used for presenting rendered content on screen
         * @param callback Callback used to render content within the corresponding buffers as well
         * as optionally configuring [SurfaceControlCompat.Transaction] to present contents to the
         * display
         */
        constructor(surfaceView: SurfaceView, callback: Callback) {
            mSurfaceControlProvider = SurfaceViewProvider(surfaceView)
            mCallback = callback
        }

        /**
         * Creates a new [GLFrameBufferRenderer.Builder] with the provided [SurfaceControlCompat]
         * as the parent [SurfaceControlCompat] for presenting contents to the display.
         *
         * It is the responsibility of the caller to release the provided [SurfaceControlCompat]
         * instance as the [GLFrameBufferRenderer] will consume but not release it.
         *
         * @param parentSurfaceControl Parent [SurfaceControlCompat] instance.
         * @param width Logical width of the content to render. This dimension matches what is
         * provided from [SurfaceHolder.Callback.surfaceChanged].
         * @param height Logical height of the content to render. This dimension matches what is
         * provided from [SurfaceHolder.Callback.surfaceChanged].
         * @param transformHint Hint used to specify how to pre-rotate content to optimize
         * consumption of content by the display without having to introduce an additional GPU pass
         * to handle rotation.
         */
        internal constructor(
            parentSurfaceControl: SurfaceControlCompat,
            width: Int,
            height: Int,
            transformHint: Int,
            callback: Callback
        ) {
            mSurfaceControlProvider = DefaultSurfaceControlProvider(
                parentSurfaceControl,
                width,
                height,
                transformHint
            )
            mCallback = callback
        }

        /**
         * Specify the [SyncStrategy] used for determining when to create [SyncFenceCompat]
         * objects in order to handle synchronization. The [SyncFenceCompat] instance created
         * according to the algorithm specified in the provided [SyncStrategy] will be passed
         * to the corresponding [SurfaceControlCompat.Transaction.setBuffer] call in order to
         * ensure the underlying buffer is not presented by the display until the fence signals.
         *
         * @param syncStrategy [SyncStrategy] used to determine when to create synchronization
         * boundaries for buffer consumption. The default is [SyncStrategy.ALWAYS], indicating a
         * fence should always be created after a request to render has been made.
         *
         * @return The builder instance
         */
        fun setSyncStrategy(syncStrategy: SyncStrategy): Builder {
            mSyncStrategy = syncStrategy
            return this
        }

        /**
         * Specify the buffer format of the underlying buffers being rendered into by the created
         * [GLFrameBufferRenderer]. The set of valid formats is implementation-specific and may
         * depend on additional EGL extensions. The particular valid combinations for a given
         * Android version and implementation should be documented by that version.
         *
         * [HardwareBuffer.RGBA_8888] and [HardwareBuffer.RGBX_8888] are guaranteed to be supported.
         * However, consumers are recommended to query the desired HardwareBuffer configuration
         * using [HardwareBuffer.isSupported].
         *
         * See:
         * khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_get_native_client_buffer.txt
         *
         * @param format Pixel format of the buffers to be rendered into. The default is RGBA_8888.
         *
         * @return The builder instance
         */
        fun setBufferFormat(@HardwareBufferFormat format: Int): Builder {
            mBufferFormat = format
            return this
        }

        /**
         * Specify the maximum number of buffers used within the swap chain of the
         * [GLFrameBufferRenderer].
         * If 1 is specified, then the created [GLFrameBufferRenderer] is running in "single buffer
         * mode". In this case consumption of the buffer content would need to be coordinated with
         * the [SyncFenceCompat] instance specified by the corresponding [SyncStrategy] algorithm
         * @see [setSyncStrategy].
         *
         * @param numBuffers The number of buffers within the swap chain to be consumed by the
         * created [GLFrameBufferRenderer]. This must be greater than zero. The default number
         * of buffers used is 3.
         *
         * @return The builder instance
         */
        fun setMaxBuffers(@IntRange(from = 1, to = 64) numBuffers: Int): Builder {
            require(numBuffers > 0) { "Must have at least 1 buffer" }
            mMaxBuffers = numBuffers
            return this
        }

        /**
         * Specify the usage flags to be configured on the underlying [HardwareBuffer] instances
         * created by the [GLFrameBufferRenderer].
         *
         * @param usageFlags Usage flags to be configured on the created [HardwareBuffer] instances
         * that the [GLFrameBufferRenderer] will render into. Must be one of [HardwareBufferUsage].
         * Note that the provided flags here are combined with the following mandatory default flags,
         * [HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE], [HardwareBuffer.USAGE_GPU_COLOR_OUTPUT] and
         * [HardwareBuffer.USAGE_COMPOSER_OVERLAY]
         *
         * @return The builder instance
         */
        fun setUsageFlags(@HardwareBufferUsage usageFlags: Long): Builder {
            mUsageFlags = usageFlags or DefaultFlags
            return this
        }

        /**
         * Configure the [GLRenderer] instance to be used by the [GLFrameBufferRenderer].
         * By default this parameter is null indicating that the [GLFrameBufferRenderer] will
         * create and manage its own [GLRenderer]. This is useful to share the same OpenGL
         * resources and thread across multiple [GLFrameBufferRenderer] instances.
         *
         * @param glRenderer The [GLRenderer] used for leveraging OpenGL resources including the
         * GL thread
         *
         * @return The builder instance
         */
        @Suppress("AcronymName")
        fun setGLRenderer(glRenderer: GLRenderer?): Builder {
            mGLRenderer = glRenderer
            return this
        }

        /**
         * Create the [GLFrameBufferRenderer] with the specified parameters on this [Builder]
         * instance
         *
         * @return The newly created [GLFrameBufferRenderer]
         */
        fun build(): GLFrameBufferRenderer {
            return GLFrameBufferRenderer(
                mSurfaceControlProvider,
                mCallback,
                mBufferFormat,
                mUsageFlags,
                mMaxBuffers,
                mSyncStrategy,
                mGLRenderer
            )
        }
    }

    private var mSurfaceControl: SurfaceControlCompat? = null
    private var mBufferPool: FrameBufferPool? = null
    private var mRenderTarget: GLRenderer.RenderTarget? = null

    private val mIsManagingGLRenderer: Boolean
    private var mIsReleased = false
    private val mContextCallbacks = object : GLRenderer.EGLContextCallback {
        override fun onEGLContextCreated(eglManager: EGLManager) {
            // NO-OP
        }

        override fun onEGLContextDestroyed(eglManager: EGLManager) {
            mBufferPool?.close()
        }
    }

    private val mGLRenderer: GLRenderer

    init {
        if (mMaxBuffers < 1) {
            throw IllegalArgumentException("FrameBufferRenderer must have at least 1 buffer")
        }
        val renderer = if (glRenderer == null) {
            mIsManagingGLRenderer = true
            GLRenderer().apply { start() }
        } else {
            mIsManagingGLRenderer = false
            if (!glRenderer.isRunning()) {
                throw IllegalStateException("The provided GLRenderer must be running prior to " +
                    "creation of GLFrameBufferRenderer, " +
                    "did you forget to call GLRenderer#start()?")
            }
            glRenderer
        }
        renderer.registerEGLContextCallback(mContextCallbacks)
        mGLRenderer = renderer
        surfaceControlProvider.createSurfaceControl(object : SurfaceControlProvider.Callback {
            override fun onSurfaceControlDestroyed() {
                detachTargets(true)
            }

            override fun onSurfaceControlCreated(
                surfaceControl: SurfaceControlCompat,
                width: Int,
                height: Int,
                bufferTransformer: BufferTransformer,
                inverseTransform: Int
            ) {
                val frameBufferPool = FrameBufferPool(
                    bufferTransformer.bufferWidth,
                    bufferTransformer.bufferHeight,
                    this@GLFrameBufferRenderer.mFormat,
                    mUsage,
                    mMaxBuffers
                )
                val renderCallback = createFrameBufferRenderer(
                    surfaceControl,
                    inverseTransform,
                    bufferTransformer,
                    frameBufferPool,
                    callback
                )
                mBufferPool = frameBufferPool
                mSurfaceControl = surfaceControl
                mRenderTarget = renderer.createRenderTarget(width, height, renderCallback)
            }

            override fun requestRender(renderComplete: Runnable?) {
                drawAsync(renderComplete)
            }
        })
    }

    /**
     * Queue a [Runnable] to be executed on the GL rendering thread. Note it is important
     * this [Runnable] does not block otherwise it can stall the GL thread.
     *
     * @param runnable to be executed
     */
    fun execute(runnable: Runnable) {
        if (isValid()) {
            mGLRenderer.execute(runnable)
        } else {
            Log.w(TAG, "Attempt to execute runnable after " +
                    "GLFrameBufferRenderer has been released")
        }
    }

    /**
     * Returns the [HardwareBufferFormat] of the buffers that are being rendered into by this
     * [GLFrameBufferRenderer]
     */
    @HardwareBufferFormat
    val bufferFormat: Int
        get() = mFormat

    /**
     * Returns the current usage flag hints of the buffers that are being rendered into by this
     * [GLFrameBufferRenderer]
     */
    @HardwareBufferUsage
    val usageFlags: Long
        get() = mUsage

    /**
     * Returns the [GLRenderer] used for issuing requests to render into the underlying buffers
     * with OpenGL.
     */
    val glRenderer: GLRenderer
        @Suppress("AcronymName")
        @JvmName("getGLRenderer")
        get() = mGLRenderer

    /**
     * Returns the [SyncStrategy] used for determining when to create [SyncFenceCompat]
     * objects in order to handle synchronization. The [SyncFenceCompat] instance created
     * according to the algorithm specified in the provided [SyncStrategy] will be passed
     * to the corresponding [SurfaceControlCompat.Transaction.setBuffer] call in order to
     * ensure the underlying buffer is not presented by the display until the fence signals.
     */
    val syncStrategy: SyncStrategy
        get() = mSyncStrategy

    /**
     * Returns the number of buffers within the swap chain used for rendering with this
     * [GLFrameBufferRenderer]
     */
    val maxBuffers: Int
        get() = mMaxBuffers

    private var mCurrentFrameBuffer: FrameBuffer? = null

    internal fun createFrameBufferRenderer(
        surfaceControl: SurfaceControlCompat,
        inverseTransform: Int,
        bufferTransformer: BufferTransformer,
        frameBufferPool: FrameBufferPool,
        callback: Callback
    ): FrameBufferRenderer = FrameBufferRenderer(
        object : FrameBufferRenderer.RenderCallback {

            private val width = bufferTransformer.logicalWidth
            private val height = bufferTransformer.logicalHeight

            private val bufferInfo = BufferInfo().apply {
                this.width = bufferTransformer.bufferWidth
                this.height = bufferTransformer.bufferHeight
            }

            override fun obtainFrameBuffer(egl: EGLSpec): FrameBuffer {
                val currentFrameBuffer = mCurrentFrameBuffer
                // Single buffer mode if we already allocated 1 buffer just return the previous one
                return if (mMaxBuffers == 1 && currentFrameBuffer != null) {
                    currentFrameBuffer
                } else {
                    frameBufferPool.obtain(egl).also {
                        bufferInfo.frameBufferId = it.frameBuffer
                        mCurrentFrameBuffer = it
                    }
                }
            }

            override fun onDraw(eglManager: EGLManager) {
                val buffer = mCurrentFrameBuffer
                if (buffer != null && !buffer.isClosed) {
                    callback.onDrawFrame(
                        eglManager,
                        width,
                        height,
                        bufferInfo,
                        bufferTransformer.transform
                    )
                }
            }

            override fun onDrawComplete(
                frameBuffer: FrameBuffer,
                syncFenceCompat: SyncFenceCompat?
            ) {
                if (surfaceControl.isValid() && !frameBuffer.isClosed) {
                    val transaction = SurfaceControlCompat.Transaction()
                        .setVisibility(surfaceControl, true)
                        .setBuffer(surfaceControl, frameBuffer.hardwareBuffer, syncFenceCompat) {
                                releaseFence ->
                            if (mGLRenderer.isRunning()) {
                                mGLRenderer.execute {
                                    callback.onBufferReleased(frameBuffer, releaseFence)
                                }
                            }
                            if (mMaxBuffers > 1 || frameBufferPool.isClosed) {
                                // Release the previous buffer only if we are not in single buffered
                                // mode
                                frameBufferPool.release(frameBuffer, releaseFence)
                            }
                        }
                    if (inverseTransform != BufferTransformHintResolver.UNKNOWN_TRANSFORM) {
                        transaction.setBufferTransform(surfaceControl, inverseTransform)
                    }
                    callback.onDrawComplete(
                        surfaceControl,
                        transaction,
                        frameBuffer,
                        syncFenceCompat
                    )
                    transaction.commit()
                }
            }
        },
        mSyncStrategy
    )

    internal fun drawAsync(onComplete: Runnable? = null) {
        val renderTarget = mRenderTarget
        val renderer = mGLRenderer
        if (renderTarget != null && renderer.isRunning()) {
            // Register a callback in case the GLRenderer is torn down while we are waiting
            // for rendering to complete. In this case invoke the drawFinished callback
            // either if the render is complete or if the GLRenderer is torn down, whatever
            // comes first
            val eglContextCallback = object : GLRenderer.EGLContextCallback {
                override fun onEGLContextCreated(eglManager: EGLManager) {
                    // NO-OP
                }

                override fun onEGLContextDestroyed(eglManager: EGLManager) {
                    onComplete?.run()
                    renderer.unregisterEGLContextCallback(this)
                }
            }
            renderer.registerEGLContextCallback(eglContextCallback)
            mRenderTarget?.requestRender {
                onComplete?.run()
                renderer.unregisterEGLContextCallback(eglContextCallback)
            }
        } else {
            onComplete?.run()
        }
    }

    /**
     * Release resources associated with the [GLFrameBufferRenderer]. After this method is invoked,
     * the [GLFrameBufferRenderer] is in an invalid state and can no longer handle rendering
     * content.
     *
     * @param cancelPending Flag to indicate that in process requests should be completed before
     * the [GLFrameBufferRenderer] is released.
     * @param onReleaseCallback Optional callback to be invoked on the underlying OpenGL thread when
     * releasing resources has been completed
     */
    @JvmOverloads
    fun release(cancelPending: Boolean, onReleaseCallback: (() -> Unit)? = null) {
        if (!mIsReleased) {
            detachTargets(cancelPending, onReleaseCallback)
            surfaceControlProvider.release()

            mGLRenderer.unregisterEGLContextCallback(mContextCallbacks)
            if (mIsManagingGLRenderer) {
                mGLRenderer.stop(false)
            }

            mIsReleased = true
        } else {
            Log.w(TAG, "Attempt to release already released GLFrameBufferRenderer")
        }
    }

    internal fun detachTargets(cancelPending: Boolean, onReleaseComplete: (() -> Unit)? = null) {
        val frameBufferPool = mBufferPool
        val renderTarget = mRenderTarget
        renderTarget?.detach(cancelPending)

        mGLRenderer.execute {
            mCurrentFrameBuffer?.let { buffer -> frameBufferPool?.release(buffer) }
            frameBufferPool?.close()
            onReleaseComplete?.invoke()
        }
        mBufferPool = null
        mSurfaceControl = null
        mRenderTarget = null
    }

    /**
     * Determines whether or not the [GLFrameBufferRenderer] is in a valid state. That is the
     * [release] method has not been called.
     * If this returns false, then subsequent calls to [render], and
     * [release] are ignored
     *
     * @return `true` if this [GLFrameBufferRenderer] has been released, `false` otherwise
     */
    fun isValid(): Boolean = !mIsReleased

    /**
     * Render content to a buffer and present the result to the display.
     *
     * If this [GLFrameBufferRenderer] has been released, that is [isValid] returns `false`, this
     * call is ignored.
     */
    fun render() {
        if (!mIsReleased) {
            mRenderTarget?.requestRender()
        } else {
            Log.w(TAG, "renderer is released, ignoring request")
        }
    }

    /**
     * [GLFrameBufferRenderer] callbacks that are invoked to render OpenGL content within the
     * underlying buffers. This includes an optional callback to be used to configure the
     * underlying [SurfaceControlCompat.Transaction] used to present content to the display
     */
    interface Callback {

        /**
         * Callback invoked on the thread backed by the [GLRenderer] to render content into a
         * buffer with the specified parameters.
         * @param eglManager [EGLManager] useful in configuring EGL objects to be used when issuing
         * OpenGL commands to render into the front buffered layer
         * @param width Logical width of the content to render. This dimension matches what is
         * provided from [SurfaceHolder.Callback.surfaceChanged]
         * @param height Logical height of the content to render. This dimension matches what is
         * provided from [SurfaceHolder.Callback.surfaceChanged]
         * @param bufferInfo [BufferInfo] about the buffer that is being rendered into. This
         * includes the width and height of the buffer which can be different than the corresponding
         * dimensions of the [SurfaceView] provided to the [GLFrameBufferRenderer] as pre-rotation
         * can occasionally swap width and height parameters in order to avoid GPU composition to
         * rotate content. This should be used as input to [GLES20.glViewport].
         * Additionally this also contains a frame buffer identifier that can be used to retarget
         * rendering operations to the original destination after rendering into intermediate
         * scratch buffers.
         * @param transform Matrix that should be applied to the rendering in this callback.
         * This should be consumed as input to any vertex shader implementations. Buffers are
         * pre-rotated in advance in order to avoid unnecessary overhead of GPU composition to
         * rotate content in the same install orientation of the display.
         * This is a 4 x 4 matrix is represented as a flattened array of 16 floating point values.
         * Consumers are expected to leverage [Matrix.multiplyMM] with this parameter alongside
         * any additional transformations that are to be applied.
         * For example:
         * ```
         * val myMatrix = FloatArray(16)
         * Matrix.orthoM(
         *      myMatrix, // matrix
         *      0, // offset starting index into myMatrix
         *      0f, // left
         *      bufferInfo.width.toFloat(), // right
         *      0f, // bottom
         *      bufferInfo.height.toFloat(), // top
         *      -1f, // near
         *      1f, // far
         * )
         * val result = FloatArray(16)
         * Matrix.multiplyMM(result, 0, myMatrix, 0, transform, 0)
         * ```
         *
         * @sample androidx.graphics.core.samples.glFrameBufferSample
         */
        @WorkerThread
        fun onDrawFrame(
            eglManager: EGLManager,
            width: Int,
            height: Int,
            bufferInfo: BufferInfo,
            transform: FloatArray
        )

        /**
         * Optional callback invoked the thread backed by the [GLRenderer] when rendering to a
         * buffer is complete but before the buffer is submitted to the hardware compositor.
         * This provides consumers a mechanism for synchronizing the transaction with other
         * [SurfaceControlCompat] objects that maybe rendered within the scene.
         *
         * @param targetSurfaceControl Handle to the [SurfaceControlCompat] where the
         * buffer is presented. This can be used to configure various properties
         * of the [SurfaceControlCompat] like z-ordering or visibility with the corresponding
         * [SurfaceControlCompat.Transaction].
         * @param transaction Current [SurfaceControlCompat.Transaction] to apply updated buffered
         * content to the front buffered layer.
         * @param frameBuffer The buffer that has been rendered into and is ready to be displayed.
         * The [HardwareBuffer] backing this [FrameBuffer] is already configured to be presented
         * for the targetSurfaceControl. That is [SurfaceControlCompat.Transaction.setBuffer] is
         * already invoked with the given [HardwareBuffer] and optional [SyncFenceCompat] instance
         * before this method is invoked.
         * @param syncFence Optional [SyncFenceCompat] is used to determine when rendering
         * is done and reflected within the given frameBuffer.
         */
        @WorkerThread
        fun onDrawComplete(
            targetSurfaceControl: SurfaceControlCompat,
            transaction: SurfaceControlCompat.Transaction,
            frameBuffer: FrameBuffer,
            syncFence: SyncFenceCompat?
        ) {
            // NO-OP
        }

        /**
         * Optional callback invoked the thread backed by the [GLRenderer] when the provided
         * framebuffer is released. That is the given [FrameBuffer] instance is no longer being
         * presented and is not visible.
         * @param frameBuffer The buffer that is no longer being presented and has returned to the
         * buffer allocation pool
         * @param releaseFence Optional fence that must be waited upon before the [FrameBuffer] can
         * be reused. The framework will invoke this callback early to improve performance and
         * signal the fence when it is ready to be re-used.
         */
        @WorkerThread
        fun onBufferReleased(frameBuffer: FrameBuffer, releaseFence: SyncFenceCompat?) {
            // NO-OP
        }
    }

    /**
     * Provider interface used to delegate creation and potential lifecycle callbacks associated
     * with the corresponding [SurfaceControlCompat] instance
     */
    internal interface SurfaceControlProvider {

        /**
         * Request a [SurfaceControlCompat] to be created and invokes the corresponding callback
         * when the created [SurfaceControlCompat] instance is ready for consumption
         */
        fun createSurfaceControl(callback: Callback)

        /**
         * Release resources associated with the [SurfaceControlProvider]
         */
        fun release()

        /**
         * Callbacks invoked by the [SurfaceControlProvider] to consumers of the created
         * [SurfaceControlCompat] instance
         */
        interface Callback {

            /**
             * Callback invoked when resources associated with the created [SurfaceControlCompat]
             * instance should be destroyed
             */
            fun onSurfaceControlDestroyed()

            /**
             * Callback invoked when the [SurfaceControlCompat] is created. This includes
             * the logical width/height as well as a [BufferTransformer] instance that provides
             * the metadata necessary to pre-rotate content
             */
            fun onSurfaceControlCreated(
                surfaceControl: SurfaceControlCompat,
                width: Int,
                height: Int,
                bufferTransformer: BufferTransformer,
                inverseTransform: Int
            )

            /**
             * Requests the consumer of the [SurfaceControlCompat] instance to render content
             * to be presented by the [SurfaceControlCompat] instance and invoke a callback when
             * rendering is complete.
             */
            fun requestRender(renderComplete: Runnable? = null)
        }
    }

    /**
     * Default [SurfaceControlProvider] instance that returns the dependencies given to it
     * to the consumer of the [SurfaceControlCompat]
     */
    internal class DefaultSurfaceControlProvider(
        private val surfaceControl: SurfaceControlCompat,
        private val width: Int,
        private val height: Int,
        private val transformHint: Int,
    ) : SurfaceControlProvider {

        private val bufferTransformer = BufferTransformer()

        private var mSurfaceControlCallback: SurfaceControlProvider.Callback? = null

        override fun createSurfaceControl(callback: SurfaceControlProvider.Callback) {
            val inverse = bufferTransformer.invertBufferTransform(transformHint)
            bufferTransformer.computeTransform(width, height, inverse)
            callback.onSurfaceControlCreated(
                surfaceControl,
                width,
                height,
                bufferTransformer,
                inverse
            )
            mSurfaceControlCallback = callback
        }

        override fun release() {
            // NO-OP
        }
    }

    /**
     * [SurfaceControlProvider] instance that creates a [SurfaceControlCompat] instance with the
     * provided SurfaceView as the parent of the created [SurfaceControlCompat]. This implementation
     * handles all lifecycle callbacks associated with the underlying SurfaceHolder.Callback
     * attached to the holder on the given SurfaceView.
     */
    internal class SurfaceViewProvider(
        private var surfaceView: SurfaceView?
    ) : SurfaceControlProvider {
        private val mTransformResolver = BufferTransformHintResolver()

        private var mSurfaceControl: SurfaceControlCompat? = null
        private var mSurfaceHolderCallback: SurfaceHolder.Callback2? = null
        private var mSurfaceControlCallback: SurfaceControlProvider.Callback? = null

        internal fun createSurfaceControl(
            surfaceView: SurfaceView,
            callback: SurfaceControlProvider.Callback
        ) {
            destroySurfaceControl(callback)

            val width = surfaceView.width
            val height = surfaceView.height
            val transformHint = mTransformResolver.getBufferTransformHint(surfaceView)
            val bufferTransformer = BufferTransformer()
            val inverse = bufferTransformer.invertBufferTransform(transformHint)
            bufferTransformer.computeTransform(surfaceView.width, surfaceView.height, inverse)
            val surfaceControl = SurfaceControlCompat.Builder()
                .setName("GLFrameBufferRendererTarget")
                .setParent(surfaceView)
                .build()

            callback.onSurfaceControlCreated(
                surfaceControl,
                width,
                height,
                bufferTransformer,
                inverse
            )

            mSurfaceControl = surfaceControl
            mSurfaceControlCallback = callback
        }

        override fun createSurfaceControl(callback: SurfaceControlProvider.Callback) {
            surfaceView?.let { target ->
                val surfaceHolderCallback = object : SurfaceHolder.Callback2 {
                    override fun surfaceCreated(holder: SurfaceHolder) {
                        // NO-OP wait for surfaceChanged callback
                    }

                    override fun surfaceChanged(
                        holder: SurfaceHolder,
                        surfaceFormat: Int,
                        width: Int,
                        height: Int
                    ) {
                        createSurfaceControl(target, callback)
                    }

                    override fun surfaceDestroyed(p0: SurfaceHolder) {
                        destroySurfaceControl(callback)
                    }

                    override fun surfaceRedrawNeeded(p0: SurfaceHolder) {
                        val latch = CountDownLatch(1)
                        callback.requestRender { latch.countDown() }
                        latch.await()
                    }

                    override fun surfaceRedrawNeededAsync(
                        holder: SurfaceHolder,
                        drawingFinished: Runnable
                    ) {
                        callback.requestRender(drawingFinished)
                    }
                }
                val holder = target.holder
                holder.addCallback(surfaceHolderCallback)
                if (holder.surface != null && holder.surface.isValid) {
                    createSurfaceControl(target, callback)
                }
                mSurfaceHolderCallback = surfaceHolderCallback
            }
        }

        fun destroySurfaceControl(callback: SurfaceControlProvider.Callback) {
            callback.onSurfaceControlDestroyed()
            releaseSurfaceControl()
        }

        private fun releaseSurfaceControl() {
            mSurfaceControl?.let { surfaceControl ->
                if (surfaceControl.isValid()) {
                    SurfaceControlCompat.Transaction()
                        .reparent(surfaceControl, null)
                        .commit()
                    surfaceControl.release()
                }
                mSurfaceControl = null
            }
        }

        override fun release() {
            releaseSurfaceControl()
            surfaceView?.holder?.removeCallback(mSurfaceHolderCallback)
            surfaceView = null
        }
    }

    internal companion object {
        internal val TAG = "GLFrameBufferRenderer"
    }
}