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