FrameBufferRenderer.kt
/*
* Copyright 2022 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.lowlatency
import android.annotation.SuppressLint
import android.hardware.HardwareBuffer
import android.opengl.EGLConfig
import android.opengl.EGLSurface
import android.opengl.GLES20
import android.os.Build
import android.view.Surface
import androidx.annotation.RequiresApi
import androidx.graphics.opengl.GLRenderer
import androidx.graphics.opengl.egl.EGLManager
import androidx.graphics.opengl.egl.EGLSpec
import java.util.concurrent.atomic.AtomicBoolean
/**
* [GLRenderer.RenderCallback] implementation that renders content into a frame buffer object
* backed by a [HardwareBuffer] object
*/
@RequiresApi(Build.VERSION_CODES.O)
class FrameBufferRenderer(
private val frameBufferRendererCallbacks: RenderCallback,
@SuppressLint("ListenerLast") private val syncStrategy: SyncStrategy = SyncStrategy.ALWAYS
) : GLRenderer.RenderCallback {
private val mClear = AtomicBoolean(false)
override fun onSurfaceCreated(
spec: EGLSpec,
config: EGLConfig,
surface: Surface,
width: Int,
height: Int
): EGLSurface? = null
fun clear() {
mClear.set(true)
}
override fun onDrawFrame(eglManager: EGLManager) {
val egl = eglManager.eglSpec
val buffer = frameBufferRendererCallbacks.obtainFrameBuffer(egl)
var syncFenceCompat: SyncFenceCompat? = null
try {
buffer.makeCurrent()
if (mClear.getAndSet(false)) {
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f)
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
} else {
frameBufferRendererCallbacks.onDraw(eglManager)
}
syncFenceCompat = syncStrategy.createSyncFence(egl)
// At this point the HardwareBuffer has the contents of the GL rendering
// Create a surface Control transaction to dispatch this request
frameBufferRendererCallbacks.onDrawComplete(buffer, syncFenceCompat)
} finally {
syncFenceCompat?.close()
}
}
/**
* Callbacks invoked to render content leveraging a [FrameBufferRenderer]
*/
interface RenderCallback {
/**
* Obtain a [FrameBuffer] to render content into. The [FrameBuffer] obtained here
* is expected to be managed by the consumer of [FrameBufferRenderer]. That is
* callers of this API are expected to be maintaining a reference to the returned
* [FrameBuffer] here and calling [FrameBuffer.close] where appropriate as the instance
* will not be released by [FrameBufferRenderer].
*
* @param egl EGLSpec that is utilized within creation of the [FrameBuffer] object
*/
@SuppressLint("CallbackMethodName")
fun obtainFrameBuffer(egl: EGLSpec): FrameBuffer
/**
* Draw contents into the [HardwareBuffer]
*/
fun onDraw(eglManager: EGLManager)
/**
* Callback when [onDraw] is complete and the contents of the draw
* are reflected in the corresponding [HardwareBuffer].
*
* @param frameBuffer [FrameBuffer] that content is rendered into. The frameBuffer
* should not be consumed unless the syncFenceCompat is signalled or the fence is null.
* @param syncFenceCompat [SyncFenceCompat] is used to determine when rendering
* is done in [onDraw] and reflected within the given frameBuffer.
*/
fun onDrawComplete(frameBuffer: FrameBuffer, syncFenceCompat: SyncFenceCompat?)
}
}
/**
* A strategy class for deciding how to utilize [SyncFenceCompat] within
* [FrameBufferRenderer.RenderCallback]. SyncStrategy provides default strategies for
* usage:
*
* [SyncStrategy.ALWAYS] will always create a [SyncFenceCompat] to pass into the render
* callbacks for [FrameBufferRenderer]
*/
interface SyncStrategy {
/**
* Conditionally generates a [SyncFenceCompat] based upon implementation.
*
* @param eglSpec an [EGLSpec] object to dictate the version of EGL and make EGL calls.
*/
fun createSyncFence(eglSpec: EGLSpec): SyncFenceCompat?
companion object {
/**
* [SyncStrategy] that will always create a [SyncFenceCompat] object
*/
@JvmField
val ALWAYS = object : SyncStrategy {
override fun createSyncFence(eglSpec: EGLSpec): SyncFenceCompat? {
return eglSpec.createNativeSyncFence()
}
}
}
}
/**
* [SyncStrategy] implementation that optimizes for front buffered rendering use cases.
* More specifically this attempts to avoid unnecessary synchronization overhead
* wherever possible.
*
* This will always provide a fence if the corresponding layer transitions from
* an invisible to a visible state. If the layer is already visible and front
* buffer usage flags are support on the device, then no fence is provided. If this
* flag is not supported, then a fence is created to ensure contents
* are flushed to the single buffer.
*
* @param usageFlags usage flags that describe the [HardwareBuffer] that is used as the destination
* for rendering content within [FrameBufferRenderer]. The usage flags can be obtained via
* [HardwareBuffer.getUsage] or by passing in the same flags from [HardwareBuffer.create]
*/
class FrontBufferSyncStrategy(
usageFlags: Long
) : SyncStrategy {
private val supportsFrontBufferUsage = (usageFlags and HardwareBuffer.USAGE_FRONT_BUFFER) != 0L
private var mFrontBufferVisible: Boolean = false
/**
* Tells whether the corresponding front buffer layer is visible in its current state or not.
* Utilize this to dictate when a [SyncFenceCompat] will be created when using
* [createSyncFence].
*/
var isVisible
get() = mFrontBufferVisible
set(visibility) {
mFrontBufferVisible = visibility
}
/**
* Creates a [SyncFenceCompat] based on various conditions.
* If the layer is changing from invisible to visible, a fence is provided.
* If the layer is already visible and front buffer usage flag is supported on the device, then
* no fence is provided.
* If front buffer usage is not supported, then a fence is created and destroyed to flush
* contents to screen.
*/
@RequiresApi(Build.VERSION_CODES.KITKAT)
override fun createSyncFence(eglSpec: EGLSpec): SyncFenceCompat? {
return if (!isVisible) {
eglSpec.createNativeSyncFence()
} else if (supportsFrontBufferUsage) {
return null
} else {
val fence = eglSpec.createNativeSyncFence()
fence.close()
return null
}
}
}