/*
* 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.GLES20
import android.opengl.Matrix
import android.os.Build
import android.util.Log
import android.view.SurfaceView
import androidx.annotation.RequiresApi
import androidx.annotation.WorkerThread
import androidx.graphics.opengl.GLRenderer
import androidx.graphics.opengl.egl.EGLManager
import androidx.graphics.opengl.egl.EGLSpec
import androidx.graphics.surface.SurfaceControlCompat
import androidx.graphics.surface.SurfaceControlCompat.Companion.BUFFER_TRANSFORM_ROTATE_270
import androidx.graphics.surface.SurfaceControlCompat.Companion.BUFFER_TRANSFORM_ROTATE_90
import androidx.opengl.EGLExt.Companion.EGL_ANDROID_NATIVE_FENCE_SYNC
import androidx.opengl.EGLExt.Companion.EGL_KHR_FENCE_SYNC
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.Executors
/**
* Class responsible for supporting a "front buffered" rendering system. This allows for lower
* latency graphics by leveraging a combination of front buffered and double buffered content
* layers.
* Active content is rendered first into the front buffered layer which is simultaneously being
* presented to the display. Periodically content is rendered into the double buffered layer which
* will have more traditional latency guarantees, however, minimize the impact of visual artifacts
* due to graphical tearing.
*
* @param surfaceView Target SurfaceView to act as the parent rendering layer for double buffered
* content
* @param callback Callbacks used to render into front and double buffered layers as well as
* configuring [SurfaceControlCompat.Transaction]s for controlling these layers in addition to
* other [SurfaceControlCompat] instances that must be updated atomically within the user
* interface. These callbacks are invoked on the backing GL Thread.
* @param glRenderer Optional [GLRenderer] instance that this [GLFrontBufferedRenderer] should use
* for coordinating requests to render content. If this parameter is specified, the caller
* of this API is responsible for releasing resources on [GLRenderer] by calling
* [GLRenderer.stop]. Otherwise [GLFrontBufferedRenderer] will create and manage its own
* [GLRenderer] internally and will automatically release its resources within
* [GLFrontBufferedRenderer.release]
*/
@RequiresApi(Build.VERSION_CODES.Q)
@Suppress("AcronymName")
class GLFrontBufferedRenderer<T> @JvmOverloads constructor(
surfaceView: SurfaceView,
callback: Callback<T>,
@Suppress("ListenerLast")
glRenderer: GLRenderer? = null,
) {
/**
* [ParentRenderLayer] used to contain both the front and double buffered layers
*/
private val mParentRenderLayer: ParentRenderLayer<T> = SurfaceViewRenderLayer(surfaceView)
/**
* Callbacks invoked to render into the front and double buffered layers in addition to
* providing consumers an opportunity to specify any potential additional interactions that must
* be synchronized with the [SurfaceControlCompat.Transaction] to show/hide visibility of the
* front buffered layer as well as updating double buffered layers
*/
private val mCallback = object : Callback<T> by callback {
@WorkerThread
override fun onDoubleBufferedLayerRenderComplete(
frontBufferedLayerSurfaceControl: SurfaceControlCompat,
transaction: SurfaceControlCompat.Transaction
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
transaction.addTransactionCommittedListener(mExecutor, mCommittedListener)
} else {
clearFrontBuffer()
}
mFrontBufferSyncStrategy.isVisible = false
callback.onDoubleBufferedLayerRenderComplete(
frontBufferedLayerSurfaceControl,
transaction
)
}
@WorkerThread
override fun onFrontBufferedLayerRenderComplete(
frontBufferedLayerSurfaceControl: SurfaceControlCompat,
transaction: SurfaceControlCompat.Transaction
) {
mFrontBufferSyncStrategy.isVisible = true
callback.onFrontBufferedLayerRenderComplete(
frontBufferedLayerSurfaceControl,
transaction
)
}
}
private val mExecutor = Executors.newSingleThreadExecutor()
private val mCommittedListener = object : SurfaceControlCompat.TransactionCommittedListener {
override fun onTransactionCommitted() {
clearFrontBuffer()
}
}
internal fun clearFrontBuffer() {
mFrontBufferedLayerRenderer?.clear()
mFrontBufferedRenderTarget?.requestRender()
}
/**
* [GLRenderer.EGLContextCallback]s used to release the corresponding [FrameBufferPool]
* if the [GLRenderer] is torn down.
* This is especially helpful if a [GLRenderer] is being provided and shared across other
* [GLRenderer.RenderTarget] instances in which case it can be released externally from
* [GLFrontBufferedRenderer]
*/
private val mContextCallbacks = object : GLRenderer.EGLContextCallback {
override fun onEGLContextCreated(eglManager: EGLManager) {
with(eglManager) {
val supportsEglFences = isExtensionSupported(EGL_KHR_FENCE_SYNC)
val supportsAndroidFences = isExtensionSupported(EGL_ANDROID_NATIVE_FENCE_SYNC)
Log.d(TAG, "Supports KHR_FENCE_SYNC: $supportsEglFences")
Log.d(TAG, "Supports ANDROID_NATIVE_FENCE_SYNC: $supportsAndroidFences")
}
}
override fun onEGLContextDestroyed(eglManager: EGLManager) {
mBufferPool?.let { releaseBuffers(it) }
}
}
/**
* [ParentRenderLayer] callbacks used to be alerted of changes to the size of the parent
* render layer which acts as a signal to teardown some internal resources and recreate them
* with the updated dimensions
*/
private val mParentLayerCallback = object :
ParentRenderLayer.Callback<T> {
override fun onSizeChanged(width: Int, height: Int) {
update(width, height)
}
override fun onLayerDestroyed() {
detachTargets(true)
}
override fun obtainDoubleBufferedLayerParams(): MutableCollection<T>? =
mSegments.poll()
override fun getFrontBufferedLayerSurfaceControl(): SurfaceControlCompat? =
mFrontBufferedLayerSurfaceControl
override fun getFrameBufferPool(): FrameBufferPool? =
mBufferPool
}
/**
* Queue of parameters to be consumed in [Callback.onDrawFrontBufferedLayer] with the parameter
* provided in [renderFrontBufferedLayer]
*/
private val mActiveSegment = ParamQueue<T>()
/**
* Collection of parameters to be consumed in [Callback.onDoubleBufferedLayerRenderComplete]
* with the parameters defined in consecutive calls to [renderFrontBufferedLayer].
* Once the corresponding [Callback.onDoubleBufferedLayerRenderComplete] callback is invoked,
* this collection is cleared and new parameters are added to it with consecutive calls to
* [renderFrontBufferedLayer].
*/
private val mSegments = ConcurrentLinkedQueue<MutableCollection<T>>()
/**
* [FrameBuffer] used for rendering into the front buffered layer. This buffer is persisted
* across frames as part of front buffered rendering and is not expected to be released again
* after the corresponding [SurfaceControlCompat.Transaction] that submits this buffer is
* applied as per the implementation of "scan line racing" that is done for front buffered
* rendering
*/
private var mFrontLayerBuffer: FrameBuffer? = null
/**
* [FrameBufferPool] used to cycle through [FrameBuffer] instances that are released when
* the [HardwareBuffer] within the [FrameBuffer] is already displayed by the hardware
* compositor
*/
private var mBufferPool: FrameBufferPool? = null
/**
* [GLRenderer.RenderCallback] used for drawing into the front buffered layer
*/
private var mFrontBufferedLayerRenderer: FrameBufferRenderer? = null
/**
* [SurfaceControlCompat] used to configure buffers and visibility of the front buffered layer
*/
private var mFrontBufferedLayerSurfaceControl: SurfaceControlCompat? = null
/**
* [FrontBufferSyncStrategy] used for [FrameBufferRenderer] to conditionally decide
* when to create a [SyncFenceCompat] for transaction calls.
*/
private val mFrontBufferSyncStrategy: FrontBufferSyncStrategy
/**
* Width of the layers to render. Only if the size changes to we re-initialize the internal
* state of the [GLFrontBufferedRenderer]
*/
private var mWidth = -1
/**
* Height of the layers to render. Only if the size changes do we re-initialize the internal
* state of the [GLFrontBufferedRenderer]
*/
private var mHeight = -1
/**
* [GLRenderer] used to issue requests to render into front/double buffered layers
*/
private val mGLRenderer: GLRenderer
/**
* Flag indicating if the [GLRenderer] being used was created internally within
* [GLFrontBufferedRenderer] as opposed to being provided by the consumer.
* If the former, then the [GLFrontBufferedRenderer] is responsible for stopping/releasing this
* [GLRenderer] in the [release] method. If this is being provided, then we should not be
* releasing this [GLRenderer] as it maybe used by other consumers.
* In this case, only the front/double buffered [GLRenderer.RenderTarget]s are detached.
*/
private val mIsManagingGLRenderer: Boolean
/**
* [GLRenderer.RenderTarget] used to issue requests to render into the front buffered layer
*/
private var mFrontBufferedRenderTarget: GLRenderer.RenderTarget? = null
/**
* [GLRenderer.RenderTarget] used to issue requests to render into the double buffered layer
*/
private var mDoubleBufferedLayerRenderTarget: GLRenderer.RenderTarget? = null
/**
* Flag to determine if the [GLFrontBufferedRenderer] has previously been released. If this flag
* is true, then subsequent requests to [renderFrontBufferedLayer], [commit], and [release] are
* ignored.
*/
private var mIsReleased = false
/**
* Cached value to store what [HardwareBuffer] usage flags are supported on the device.
*/
private val mHardwareBufferUsageFlags: Long
/**
* Calculates the corresponding projection based on buffer transform hints
*/
private val mBufferTransform = BufferTransformer()
init {
mParentRenderLayer.setParentLayerCallbacks(mParentLayerCallback)
val renderer = if (glRenderer == null) {
// If we have not been provided a [GLRenderer] then we should create/start one ourselves
mIsManagingGLRenderer = true
GLRenderer().apply { start() }
} else {
// ... otherwise use the [GLRenderer] that is being provided for us
mIsManagingGLRenderer = false
glRenderer
}
renderer.registerEGLContextCallback(mContextCallbacks)
mGLRenderer = renderer
mHardwareBufferUsageFlags = obtainHardwareBufferUsageFlags()
mFrontBufferSyncStrategy = FrontBufferSyncStrategy(mHardwareBufferUsageFlags)
}
internal fun update(width: Int, height: Int) {
if (mWidth != width || mHeight != height && isValid()) {
mDoubleBufferedLayerRenderTarget?.detach(true)
val doubleBufferTarget = mParentRenderLayer.createRenderTarget(mGLRenderer, mCallback)
mFrontBufferedLayerSurfaceControl?.release()
val frontBufferedSurfaceControl = SurfaceControlCompat.Builder()
.setName("FrontBufferedSurfaceControl")
.apply {
mParentRenderLayer.setParent(this)
}
.build()
val transformHint = mParentRenderLayer.getBufferTransformHint()
val bufferWidth: Int
val bufferHeight: Int
if (transformHint == BUFFER_TRANSFORM_ROTATE_90 ||
transformHint == BUFFER_TRANSFORM_ROTATE_270
) {
bufferWidth = height
bufferHeight = width
} else {
bufferWidth = width
bufferHeight = height
}
// Create buffer pool for the multi-buffered layer
// The flags here are identical to those used for buffers in the front buffered layer
// except USAGE_FRONT_BUFFER is not specified
val bufferPool = FrameBufferPool(
bufferWidth,
bufferHeight,
format = HardwareBuffer.RGBA_8888,
usage = BaseFlags,
maxPoolSize = 4
)
val previousBufferPool = mBufferPool
mFrontBufferedRenderTarget?.detach(true) {
if (previousBufferPool != null) {
releaseBuffers(previousBufferPool)
}
}
val frontBufferedLayerRenderer =
createFrontBufferedLayerRenderer(
frontBufferedSurfaceControl,
width,
height,
bufferWidth,
bufferHeight,
transformHint,
mHardwareBufferUsageFlags
)
mFrontBufferedRenderTarget = mGLRenderer.createRenderTarget(
width,
height,
frontBufferedLayerRenderer
)
mFrontBufferedLayerRenderer = frontBufferedLayerRenderer
mFrontBufferedLayerSurfaceControl = frontBufferedSurfaceControl
mDoubleBufferedLayerRenderTarget = doubleBufferTarget
mBufferPool = bufferPool
mWidth = width
mHeight = height
}
}
/**
* Determines whether or not the [GLFrontBufferedRenderer] is in a valid state. That is the
* [release] method has not been called.
* If this returns false, then subsequent calls to [renderFrontBufferedLayer], [commit], and
* [release] are ignored
*
* @return `true` if this [GLFrontBufferedRenderer] has been released, `false` otherwise
*/
fun isValid(): Boolean = !mIsReleased
/**
* Render content to the front buffered layer providing optional parameters to be consumed in
* [Callback.onDrawFrontBufferedLayer].
* Additionally the parameter provided here will also be consumed in
* [Callback.onDrawDoubleBufferedLayer]
* when the corresponding [commit] method is invoked, which will include all [param]s in each
* call made to this method up to the corresponding [commit] call.
*
* If this [GLFrontBufferedRenderer] has been released, that is [isValid] returns `false`, this
* call is ignored.
*
* @param param Optional parameter to be consumed when rendering content into the commit layer
*/
fun renderFrontBufferedLayer(param: T) {
if (isValid()) {
mActiveSegment.add(param)
mFrontBufferedRenderTarget?.requestRender()
} else {
Log.w(
TAG, "Attempt to render to front buffered layer when " +
"GLFrontBufferedRenderer has been released"
)
}
}
/**
* Clears the contents of both the front and double buffered layers. This triggers a call to
* [Callback.onDoubleBufferedLayerRenderComplete] and hides the front buffered layer.
*/
fun clear() {
clearParamQueues()
mFrontBufferedLayerRenderer?.clear()
mParentRenderLayer.clear()
}
/**
* Requests to render the entire scene to the double buffered layer and schedules a call to
* [Callback.onDoubleBufferedLayerRenderComplete]. The parameters provided to
* [Callback.onDoubleBufferedLayerRenderComplete] will include each argument provided to every
* [renderFrontBufferedLayer] call since the last call to [commit] has been made.
*
* If this [GLFrontBufferedRenderer] has been released, that is [isValid] returns `false`,
* this call is ignored.
*/
fun commit() {
if (isValid()) {
mSegments.add(mActiveSegment.release())
mDoubleBufferedLayerRenderTarget?.requestRender()
} else {
Log.w(
TAG, "Attempt to render to the double buffered layer when " +
"GLFrontBufferedRenderer has been released"
)
}
}
/**
* Helper method used to detach the front and multi buffered render targets as well as
* release SurfaceControl instances
*/
internal fun detachTargets(cancelPending: Boolean, onReleaseComplete: (() -> Unit)? = null) {
// Wrap the callback into a separate lambda to ensure it is invoked only after
// both the front and double buffered layer target renderers are detached
var callbackCount = 0
var expectedCount = 0
if (mFrontBufferedRenderTarget?.isAttached() == true) {
expectedCount++
}
if (mDoubleBufferedLayerRenderTarget?.isAttached() == true) {
expectedCount++
}
val frontBufferedLayerSurfaceControl = mFrontBufferedLayerSurfaceControl
val wrappedCallback: (GLRenderer.RenderTarget) -> Unit = {
callbackCount++
if (callbackCount >= expectedCount) {
mBufferPool?.let { releaseBuffers(it) }
clearParamQueues()
frontBufferedLayerSurfaceControl?.let {
val transaction = SurfaceControlCompat.Transaction()
.reparent(it, null)
mParentRenderLayer.release(transaction)
transaction.commit()
it.release()
}
onReleaseComplete?.invoke()
}
}
mFrontBufferedLayerSurfaceControl = null
mFrontBufferedRenderTarget?.detach(cancelPending, wrappedCallback)
mDoubleBufferedLayerRenderTarget?.detach(cancelPending, wrappedCallback)
mFrontBufferedRenderTarget = null
mDoubleBufferedLayerRenderTarget = null
mWidth = -1
mHeight = -1
}
/**
* Releases the [GLFrontBufferedRenderer] and provides an optional callback that is invoked when
* the [GLFrontBufferedRenderer] is fully torn down. If the [cancelPending] flag is true, all
* pending requests to render into the front or double buffered layers will be processed before
* the [GLFrontBufferedRenderer] is torn down. Otherwise all in process requests are ignored.
* If the [GLFrontBufferedRenderer] is already released, that is [isValid] returns `false`, this
* method does nothing.
*
* @param cancelPending Flag indicating that requests to render should be processed before
* the [GLFrontBufferedRenderer] is released
* @param onReleaseComplete Optional callback invoked when the [GLFrontBufferedRenderer] has
* been released. This callback is invoked on the backing GLThread
*/
@JvmOverloads
fun release(cancelPending: Boolean, onReleaseComplete: (() -> Unit)? = null) {
if (!isValid()) {
Log.w(TAG, "Attempt to release GLFrontbufferedRenderer that is already released")
return
}
detachTargets(cancelPending, onReleaseComplete)
mGLRenderer.unregisterEGLContextCallback(mContextCallbacks)
if (mIsManagingGLRenderer) {
// If we are managing the GLRenderer that we created ourselves
// do not cancel pending operations as we will miss callbacks that we are
// expecting above to properly teardown resources
// Instead rely on the cancel pending flags for detaching the front/double buffered
// render targets instead
mGLRenderer.stop(false)
}
mExecutor.shutdown()
mIsReleased = true
}
private fun createFrontBufferedLayerRenderer(
frontBufferedLayerSurfaceControl: SurfaceControlCompat,
width: Int,
height: Int,
bufferWidth: Int,
bufferHeight: Int,
transformHint: Int,
usageFlags: Long
): FrameBufferRenderer {
val inverseTransform = mBufferTransform.invertBufferTransform(transformHint)
mBufferTransform.computeTransform(width, height, inverseTransform)
return FrameBufferRenderer(
object : FrameBufferRenderer.RenderCallback {
private fun createFrontBufferLayer(usageFlags: Long): HardwareBuffer {
return HardwareBuffer.create(
bufferWidth,
bufferHeight,
HardwareBuffer.RGBA_8888,
1,
usageFlags
)
}
@WorkerThread
override fun obtainFrameBuffer(egl: EGLSpec): FrameBuffer {
var buffer = mFrontLayerBuffer
if (buffer == null) {
// Allocate and persist a FrameBuffer instance across frames
buffer = FrameBuffer(
egl,
createFrontBufferLayer(usageFlags)
).also {
mFrontLayerBuffer = it
}
}
return buffer
}
@WorkerThread
override fun onDraw(eglManager: EGLManager) {
mActiveSegment.next { param ->
mCallback.onDrawFrontBufferedLayer(
eglManager,
mBufferTransform.glWidth,
mBufferTransform.glHeight,
mBufferTransform.transform,
param
)
}
}
@WorkerThread
override fun onDrawComplete(
frameBuffer: FrameBuffer,
syncFenceCompat: SyncFenceCompat?
) {
val transaction = SurfaceControlCompat.Transaction()
// Make this layer the top most layer
.setLayer(frontBufferedLayerSurfaceControl, Integer.MAX_VALUE)
.setBuffer(
frontBufferedLayerSurfaceControl,
frameBuffer.hardwareBuffer,
syncFenceCompat
)
.setVisibility(frontBufferedLayerSurfaceControl, true)
if (transformHint != BufferTransformHintResolver.UNKNOWN_TRANSFORM) {
transaction.setBufferTransform(
frontBufferedLayerSurfaceControl,
inverseTransform
)
}
mParentRenderLayer.buildReparentTransaction(
frontBufferedLayerSurfaceControl, transaction
)
mCallback.onFrontBufferedLayerRenderComplete(
frontBufferedLayerSurfaceControl,
transaction
)
transaction.commit()
}
},
mFrontBufferSyncStrategy
)
}
private fun clearParamQueues() {
mActiveSegment.clear()
mSegments.clear()
}
/**
* Release the buffers associated with the front buffered layer as well as the
* [FrameBufferPool]
*/
internal fun releaseBuffers(pool: FrameBufferPool) {
mFrontLayerBuffer?.close()
mFrontLayerBuffer = null
pool.close()
}
companion object {
internal const val TAG = "GLFrontBufferedRenderer"
// Leverage the same value as HardwareBuffer.USAGE_COMPOSER_OVERLAY.
// While this constant was introduced in the SDK in the Android T release, it has
// been available within the NDK as part of
// AHardwareBuffer_UsageFlags#AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY for quite some time.
// This flag is required for usage of ASurfaceTransaction#setBuffer
// Use a separate constant with the same value to avoid SDK warnings of accessing the
// newly added constant in the SDK.
// See:
// developer.android.com/ndk/reference/group/a-hardware-buffer#ahardwarebuffer_usageflags
private const val USAGE_COMPOSER_OVERLAY: Long = 2048L
/**
* Flags that are expected to be supported on all [HardwareBuffer] instances
*/
internal const val BaseFlags =
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE or
HardwareBuffer.USAGE_GPU_COLOR_OUTPUT or
USAGE_COMPOSER_OVERLAY
internal fun obtainHardwareBufferUsageFlags(): Long =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
UsageFlagsVerificationHelper.obtainUsageFlagsV33()
} else {
BaseFlags
}
}
/**
* Provides callbacks for consumers to draw into the front and double buffered layers as well as
* provide opportunities to synchronize [SurfaceControlCompat.Transaction]s to submit the layers
* to the hardware compositor.
*/
interface Callback<T> {
/**
* Callback invoked to render content into the front buffered layer 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 bufferWidth Width of the buffer that is being rendered into. This can be different
* than the corresponding dimensions of the [SurfaceView] provided to the
* [GLFrontBufferedRenderer] 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].
* @param bufferHeight Height of the buffer that is being rendered into. This can be different
* than the corresponding dimensions of the [SurfaceView] provided to the
* [GLFrontBufferedRenderer] 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].
* @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
* bufferWidth.toFloat(), // right
* 0f, // bottom
* bufferHeight.toFloat(), // top
* -1f, // near
* 1f, // far
* )
* val result = FloatArray(16)
* Matrix.multiplyMM(result, 0, myMatrix, 0, transform, 0)
* ```
* @param param optional parameter provided the corresponding
* [GLFrontBufferedRenderer.renderFrontBufferedLayer] method that triggered this request to render
* into the front buffered layer
*/
@WorkerThread
fun onDrawFrontBufferedLayer(
eglManager: EGLManager,
bufferWidth: Int,
bufferHeight: Int,
transform: FloatArray,
param: T
)
/**
* Callback invoked to render content into the doubled buffered layer with the specified
* parameters.
* @param eglManager [EGLManager] useful in configuring EGL objects to be used when issuing
* OpenGL commands to render into the double buffered layer
* @param bufferWidth Width of the buffer that is being rendered into. This can be different
* than the corresponding dimensions of the [SurfaceView] provided to the
* [GLFrontBufferedRenderer] 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].
* @param bufferHeight Height of the buffer that is being rendered into. This can be different
* than the corresponding dimensions of the [SurfaceView] provided to the
* [GLFrontBufferedRenderer] 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].
* @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
* bufferWidth.toFloat(), // right
* 0f, // bottom
* bufferHeight.toFloat(), // top
* -1f, // near
* 1f, // far
* )
* val result = FloatArray(16)
* Matrix.multiplyMM(result, 0, myMatrix, 0, transform, 0)
* ```
* @param params optional parameter provided to render the entire scene into the double
* buffered layer.
* This is a collection of all parameters provided in consecutive invocations to
* [GLFrontBufferedRenderer.renderFrontBufferedLayer] since the last call to
* [GLFrontBufferedRenderer.commit] has been made. After [GLFrontBufferedRenderer.commit]
* is invoked, this collection is cleared and new
* parameters are added on each subsequent call to
* [GLFrontBufferedRenderer.renderFrontBufferedLayer].
*
* Consider the following example:
*
* myFrontBufferedRenderer.renderFrontBufferedLayer(1)
* myFrontBufferedRenderer.renderFrontBufferedLayer(2)
* myFrontBufferedRenderer.renderFrontBufferedLayer(3)
* myFrontBufferedRenderer.commit()
*
* This will generate a callback to this method with the params collection containing values
* [1, 2, 3]
*
* myFrontBufferedRenderer.renderFrontBufferedLayer(4)
* myFrontBufferedRenderer.renderFrontBufferedLayer(5)
* myFrontBufferedRenderer.commit()
*
* This will generate a callback to this method with the params collection containing values
* [4, 5]
*
* By default [GLES20.glViewport] is invoked with the correct dimensions of the buffer that
* is being rendered into taking into account pre-rotation transformations
*/
@WorkerThread
fun onDrawDoubleBufferedLayer(
eglManager: EGLManager,
bufferWidth: Int,
bufferHeight: Int,
transform: FloatArray,
params: Collection<T>
)
/**
* Optional callback invoked when rendering to the front buffered layer is complete but
* before the buffers are 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 frontBufferedLayerSurfaceControl Handle to the [SurfaceControlCompat] where the
* front buffered layer content is drawn. 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.
*/
@WorkerThread
fun onFrontBufferedLayerRenderComplete(
frontBufferedLayerSurfaceControl: SurfaceControlCompat,
transaction: SurfaceControlCompat.Transaction
) {
// Default implementation is a no-op
}
/**
* Optional callback invoked when rendering to the double buffered layer is complete but
* before the buffers are 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 frontBufferedLayerSurfaceControl Handle to the [SurfaceControlCompat] where the
* front buffered layer content is drawn. 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 double buffered layer.
*/
@WorkerThread
fun onDoubleBufferedLayerRenderComplete(
frontBufferedLayerSurfaceControl: SurfaceControlCompat,
transaction: SurfaceControlCompat.Transaction
) {
// Default implementation is a no-op
}
}
}
/**
* Helper class to avoid class verification failures
*/
@RequiresApi(Build.VERSION_CODES.Q)
internal class UsageFlagsVerificationHelper private constructor() {
companion object {
/**
* Helper method to determine if a particular HardwareBuffer usage flag is supported.
* Even though the FRONT_BUFFER_USAGE and COMPOSER_OVERLAY flags are introduced in
* Android T, not all devices may support this flag. So we conduct a capability query
* with a sample 1x1 HardwareBuffer with the provided flag to see if it is compatible
*/
// Suppressing WrongConstant warnings as we are leveraging a constant with the same value
// as HardwareBuffer.USAGE_COMPOSER_OVERLAY to avoid SDK checks as the constant has been
// supported in the NDK for several platform releases.
// See:
// developer.android.com/ndk/reference/group/a-hardware-buffer#ahardwarebuffer_usageflags
@SuppressLint("WrongConstant")
@RequiresApi(Build.VERSION_CODES.Q)
@androidx.annotation.DoNotInline
internal fun isSupported(flag: Long): Boolean =
HardwareBuffer.isSupported(
1, // width
1, // height
HardwareBuffer.RGBA_8888, // format
1, // layers
GLFrontBufferedRenderer.BaseFlags or flag
)
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@androidx.annotation.DoNotInline
fun obtainUsageFlagsV33(): Long {
// First verify if the front buffer usage flag is supported along with the
// "usage composer overlay" flag that was introduced in API level
return if (isSupported(HardwareBuffer.USAGE_FRONT_BUFFER)) {
GLFrontBufferedRenderer.BaseFlags or HardwareBuffer.USAGE_FRONT_BUFFER
} else {
GLFrontBufferedRenderer.BaseFlags
}
}
}
}