1 /* 2 * 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.opengl 18 19 import android.annotation.SuppressLint 20 import android.hardware.HardwareBuffer 21 import android.opengl.EGLConfig 22 import android.opengl.EGLSurface 23 import android.opengl.GLES20 24 import android.os.Build 25 import android.util.Log 26 import android.view.Surface 27 import androidx.annotation.RequiresApi 28 import androidx.graphics.opengl.egl.EGLManager 29 import androidx.graphics.opengl.egl.EGLSpec 30 import androidx.hardware.SyncFenceCompat 31 import androidx.opengl.EGLExt 32 import androidx.opengl.EGLExt.Companion.EGL_ANDROID_NATIVE_FENCE_SYNC 33 import androidx.opengl.EGLExt.Companion.EGL_KHR_FENCE_SYNC 34 import java.util.concurrent.atomic.AtomicBoolean 35 36 /** 37 * [GLRenderer.RenderCallback] implementation that renders content into a frame buffer object backed 38 * by a [HardwareBuffer] object 39 * 40 * @param frameBufferRendererCallbacks Callbacks to provide a [FrameBuffer] instance to render into, 41 * draw method to render into the [FrameBuffer] as well as a subsequent callback to consume the 42 * contents of the [FrameBuffer] 43 * @param syncStrategy [SyncStrategy] used to determine when a fence is to be created to gate on 44 * consumption of the [FrameBuffer] instance. This determines if a [SyncFenceCompat] instance is 45 * provided in the [RenderCallback.onDrawComplete] depending on the use case. For example for 46 * front buffered rendering scenarios, it is possible that no [SyncFenceCompat] is provided in 47 * order to reduce latency within the rendering pipeline. 48 * 49 * This API can be used to render content into a [HardwareBuffer] directly and convert that to a 50 * bitmap with the following code snippet: 51 * ``` 52 * val glRenderer = GLRenderer().apply { start() } 53 * val callbacks = object : FrameBufferRenderer.RenderCallback { 54 * 55 * override fun obtainFrameBuffer(egl: EGLSpec): FrameBuffer = 56 * FrameBuffer( 57 * egl, 58 * HardwareBuffer.create( 59 * width, 60 * height, 61 * HardwareBuffer.RGBA_8888, 62 * 1, 63 * HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE 64 * ) 65 * ) 66 * 67 * override fun onDraw(eglManager: EGLManager) { 68 * // GL code 69 * } 70 * 71 * override fun onDrawComplete(frameBuffer: FrameBuffer, syncFenceCompat: SyncFenceCompat?) { 72 * syncFenceCompat?.awaitForever() 73 * val bitmap = Bitmap.wrapHardwareBuffer(frameBuffer.hardwareBuffer, 74 * ColorSpace.get(ColorSpace.Named.LINEAR_SRGB)) 75 * // bitmap operations 76 * } 77 * } 78 * 79 * glRenderer.createRenderTarget(width,height, FrameBufferRenderer(callbacks)).requestRender() 80 * ``` 81 */ 82 @RequiresApi(Build.VERSION_CODES.O) 83 class FrameBufferRenderer( 84 private val frameBufferRendererCallbacks: RenderCallback, 85 @SuppressLint("ListenerLast") private val syncStrategy: SyncStrategy = SyncStrategy.ALWAYS 86 ) : GLRenderer.RenderCallback { 87 88 private val mClear = AtomicBoolean(false) 89 onSurfaceCreatednull90 override fun onSurfaceCreated( 91 spec: EGLSpec, 92 config: EGLConfig, 93 surface: Surface, 94 width: Int, 95 height: Int 96 ): EGLSurface? = null 97 98 fun clear() { 99 mClear.set(true) 100 } 101 onDrawFramenull102 override fun onDrawFrame(eglManager: EGLManager) { 103 val egl = eglManager.eglSpec 104 val buffer = frameBufferRendererCallbacks.obtainFrameBuffer(egl) 105 var syncFenceCompat: SyncFenceCompat? = null 106 try { 107 buffer.makeCurrent() 108 if (mClear.getAndSet(false)) { 109 GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f) 110 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) 111 } else { 112 frameBufferRendererCallbacks.onDraw(eglManager) 113 } 114 115 syncFenceCompat = 116 if (eglManager.supportsNativeAndroidFence()) { 117 syncStrategy.createSyncFence(egl) 118 } else if (eglManager.isExtensionSupported(EGL_KHR_FENCE_SYNC)) { 119 // In this case the device only supports EGL sync objects but not creation 120 // of native SyncFence objects from an EGLSync. 121 // This usually occurs in emulator/cuttlefish instances as well as ChromeOS 122 // devices 123 // running ARC++. In this case fallback onto creating a sync object and waiting 124 // on it instead. 125 // TODO b/256217036 block on another thread instead of waiting here 126 val syncKhr = egl.eglCreateSyncKHR(EGLExt.EGL_SYNC_FENCE_KHR, null) 127 if (syncKhr != null) { 128 GLES20.glFlush() 129 val status = 130 egl.eglClientWaitSyncKHR( 131 syncKhr, 132 EGLExt.EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, 133 EGLExt.EGL_FOREVER_KHR 134 ) 135 if (status != EGLExt.EGL_CONDITION_SATISFIED_KHR) { 136 Log.w(TAG, "warning waiting on sync object: $status") 137 } 138 } else { 139 Log.w(TAG, "Unable to create EGLSync") 140 GLES20.glFinish() 141 } 142 null 143 } else { 144 Log.w(TAG, "Device does not support creation of any fences") 145 GLES20.glFinish() 146 null 147 } 148 } catch (exception: Exception) { 149 Log.w(TAG, "Error attempting to render to frame buffer: ${exception.message}") 150 } finally { 151 // At this point the HardwareBuffer has the contents of the GL rendering 152 // Create a surface Control transaction to dispatch this request 153 frameBufferRendererCallbacks.onDrawComplete(buffer, syncFenceCompat) 154 } 155 } 156 EGLManagernull157 private fun EGLManager.supportsNativeAndroidFence(): Boolean = 158 isExtensionSupported(EGL_KHR_FENCE_SYNC) && 159 isExtensionSupported(EGL_ANDROID_NATIVE_FENCE_SYNC) 160 161 /** Callbacks invoked to render content leveraging a [FrameBufferRenderer] */ 162 interface RenderCallback { 163 164 /** 165 * Obtain a [FrameBuffer] to render content into. The [FrameBuffer] obtained here is 166 * expected to be managed by the consumer of [FrameBufferRenderer]. That is implementations 167 * of this API are expected to be maintaining a reference to the returned [FrameBuffer] here 168 * and calling [FrameBuffer.close] where appropriate as the instance will not be released by 169 * [FrameBufferRenderer]. 170 * 171 * @param egl EGLSpec that is utilized within creation of the [FrameBuffer] object 172 */ 173 @SuppressLint("CallbackMethodName") fun obtainFrameBuffer(egl: EGLSpec): FrameBuffer 174 175 /** 176 * Draw contents into the [HardwareBuffer]. Before this method is invoked the [FrameBuffer] 177 * instance returned in [obtainFrameBuffer] is made current 178 */ 179 fun onDraw(eglManager: EGLManager) 180 181 /** 182 * Callback when [onDraw] is complete and the contents of the draw are reflected in the 183 * corresponding [HardwareBuffer]. 184 * 185 * @param frameBuffer [FrameBuffer] that content is rendered into. The frameBuffer should 186 * not be consumed unless the syncFenceCompat is signalled or the fence is null. This is 187 * the same [FrameBuffer] instance returned in [obtainFrameBuffer] 188 * @param syncFenceCompat [SyncFenceCompat] is used to determine when rendering is done in 189 * [onDraw] and reflected within the given frameBuffer. 190 */ 191 fun onDrawComplete(frameBuffer: FrameBuffer, syncFenceCompat: SyncFenceCompat?) 192 } 193 194 private companion object { 195 private const val TAG = "FrameBufferRenderer" 196 } 197 } 198 199 /** 200 * A strategy class for deciding how to utilize [SyncFenceCompat] within 201 * [FrameBufferRenderer.RenderCallback]. SyncStrategy provides default strategies for usage: 202 * 203 * [SyncStrategy.ALWAYS] will always create a [SyncFenceCompat] to pass into the render callbacks 204 * for [FrameBufferRenderer] 205 */ 206 interface SyncStrategy { 207 /** 208 * Conditionally generates a [SyncFenceCompat] based upon implementation. 209 * 210 * @param eglSpec an [EGLSpec] object to dictate the version of EGL and make EGL calls. 211 */ createSyncFencenull212 fun createSyncFence(eglSpec: EGLSpec): SyncFenceCompat? 213 214 companion object { 215 /** [SyncStrategy] that will always create a [SyncFenceCompat] object */ 216 @JvmField 217 val ALWAYS = 218 object : SyncStrategy { 219 override fun createSyncFence(eglSpec: EGLSpec): SyncFenceCompat? { 220 return SyncFenceCompat.createNativeSyncFence() 221 } 222 } 223 } 224 } 225