1 /*
<lambda>null2  * 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.opengl.EGL14
20 import android.opengl.EGLConfig
21 import android.opengl.EGLSurface
22 import android.os.Handler
23 import android.os.HandlerThread
24 import android.util.Log
25 import android.view.Surface
26 import androidx.annotation.AnyThread
27 import androidx.annotation.WorkerThread
28 import androidx.graphics.opengl.GLRenderer.EGLContextCallback
29 import androidx.graphics.opengl.GLRenderer.RenderCallback
30 import androidx.graphics.opengl.egl.EGLManager
31 import androidx.graphics.opengl.egl.EGLSpec
32 import androidx.graphics.utils.post
33 import java.util.concurrent.atomic.AtomicBoolean
34 
35 /**
36  * Thread responsible for management of EGL dependencies, setup and teardown of EGLSurface instances
37  * as well as delivering callbacks to draw a frame
38  */
39 internal class GLThread(
40     name: String = "GLThread",
41     private val mEglSpecFactory: () -> EGLSpec,
42     private val mEglConfigFactory: EGLManager.() -> EGLConfig,
43 ) : HandlerThread(name) {
44 
45     // Accessed on internal HandlerThread
46     private val mIsTearingDown = AtomicBoolean(false)
47     private var mEglManager: EGLManager? = null
48     private val mSurfaceSessions = HashMap<Int, SurfaceSession>()
49     private var mHandler: Handler? = null
50     private val mEglContextCallback = HashSet<EGLContextCallback>()
51 
52     override fun start() {
53         super.start()
54         mHandler =
55             Handler(looper).apply {
56                 // Create an EGLContext right after starting in order to have one ready on a call to
57                 // GLRenderer#execute
58                 post { obtainEGLManager() }
59             }
60     }
61 
62     /**
63      * Adds the given [android.view.Surface] to be managed by the GLThread. A corresponding
64      * [EGLSurface] is created on the GLThread as well as a callback for rendering into the surface
65      * through [RenderCallback].
66      *
67      * @param surface intended to be be rendered into on the GLThread
68      * @param width Desired width of the [surface]
69      * @param height Desired height of the [surface]
70      * @param renderer callbacks used to create a corresponding [EGLSurface] from the given surface
71      *   as well as render content into the created [EGLSurface]
72      * @return Identifier used for subsequent requests to communicate with the provided Surface (ex.
73      *   [requestRender] or [detachSurface]
74      */
75     @AnyThread
76     fun attachSurface(
77         token: Int,
78         surface: Surface?,
79         width: Int,
80         height: Int,
81         renderer: RenderCallback
82     ) {
83         withHandler {
84             post(token) {
85                 attachSurfaceSessionInternal(
86                     SurfaceSession(token, surface, renderer).apply {
87                         this.width = width
88                         this.height = height
89                     }
90                 )
91             }
92         }
93     }
94 
95     @AnyThread
96     fun resizeSurface(token: Int, width: Int, height: Int, callback: Runnable? = null) {
97         withHandler {
98             post(token) {
99                 resizeSurfaceSessionInternal(token, width, height)
100                 requestRenderInternal(token)
101                 callback?.run()
102             }
103         }
104     }
105 
106     @AnyThread
107     fun addEGLCallbacks(callbacks: ArrayList<EGLContextCallback>) {
108         withHandler {
109             post {
110                 mEglContextCallback.addAll(callbacks)
111                 mEglManager?.let {
112                     for (callback in callbacks) {
113                         callback.onEGLContextCreated(it)
114                     }
115                 }
116             }
117         }
118     }
119 
120     @AnyThread
121     fun addEGLCallback(callbacks: EGLContextCallback) {
122         withHandler {
123             post {
124                 mEglContextCallback.add(callbacks)
125                 // If EGL dependencies are already initialized, immediately invoke
126                 // the added callback
127                 mEglManager?.let { callbacks.onEGLContextCreated(it) }
128             }
129         }
130     }
131 
132     @AnyThread
133     fun removeEGLCallback(callbacks: EGLContextCallback) {
134         withHandler { post { mEglContextCallback.remove(callbacks) } }
135     }
136 
137     @AnyThread fun execute(runnable: Runnable) = withHandler { post(runnable) }
138 
139     /**
140      * Removes the corresponding [android.view.Surface] from management of the GLThread. This
141      * destroys the EGLSurface associated with this surface and subsequent requests to render into
142      * the surface with the provided token are ignored. Any queued request to render to the
143      * corresponding [SurfaceSession] that has not started yet is cancelled. However, if this is
144      * invoked in the middle of the frame being rendered, it will continue to process the current
145      * frame.
146      */
147     @AnyThread
148     fun detachSurface(token: Int, cancelPending: Boolean, callback: Runnable?) {
149         log("dispatching request to detach surface w/ token: $token")
150         withHandler {
151             if (cancelPending) {
152                 removeCallbacksAndMessages(token)
153             }
154             post(token) { detachSurfaceSessionInternal(token, callback) }
155         }
156     }
157 
158     /**
159      * Cancel all pending requests to all currently managed [SurfaceSession] instances, destroy all
160      * EGLSurfaces, teardown EGLManager and quit this thread
161      */
162     @AnyThread
163     fun tearDown(cancelPending: Boolean, callback: Runnable?) {
164         withHandler {
165             if (cancelPending) {
166                 removeCallbacksAndMessages(null)
167             }
168             post { releaseResourcesInternalAndQuit(callback) }
169             mIsTearingDown.set(true)
170         }
171     }
172 
173     /**
174      * Mark the corresponding surface session with the given token as dirty to schedule a call to
175      * [RenderCallback#onDrawFrame]. If there is already a queued request to render into the
176      * provided surface with the specified token, this request is ignored.
177      */
178     @AnyThread
179     fun requestRender(token: Int, callback: Runnable? = null) {
180         log("dispatching request to render for token: $token")
181         withHandler {
182             post(token) {
183                 requestRenderInternal(token)
184                 callback?.run()
185             }
186         }
187     }
188 
189     /**
190      * Lazily creates an [EGLManager] instance from the given [mEglSpecFactory] used to determine
191      * the configuration. This result is cached across calls unless [tearDown] has been called.
192      */
193     @WorkerThread
194     fun obtainEGLManager(): EGLManager =
195         mEglManager
196             ?: EGLManager(mEglSpecFactory.invoke()).also {
197                 it.initialize()
198                 val config = mEglConfigFactory.invoke(it)
199                 it.createContext(config)
200                 for (callback in mEglContextCallback) {
201                     callback.onEGLContextCreated(it)
202                 }
203                 mEglManager = it
204             }
205 
206     @WorkerThread
207     private fun disposeSurfaceSession(session: SurfaceSession) {
208         val eglSurface = session.eglSurface
209         if (eglSurface != null && eglSurface != EGL14.EGL_NO_SURFACE) {
210             obtainEGLManager().eglSpec.eglDestroySurface(eglSurface)
211             session.eglSurface = null
212         }
213     }
214 
215     /**
216      * Helper method to obtain the cached EGLSurface for the given [SurfaceSession], creating one if
217      * it does not previously exist
218      */
219     @WorkerThread
220     private fun obtainEGLSurfaceForSession(session: SurfaceSession): EGLSurface? {
221         return if (session.eglSurface != null) {
222             session.eglSurface
223         } else {
224             createEGLSurfaceForSession(
225                     session.surface,
226                     session.width,
227                     session.height,
228                     session.surfaceRenderer
229                 )
230                 .also { session.eglSurface = it }
231         }
232     }
233 
234     /**
235      * Helper method to create the corresponding EGLSurface from the [SurfaceSession] instance
236      * Consumers are expected to teardown the previously existing EGLSurface instance if it exists
237      */
238     @WorkerThread
239     private fun createEGLSurfaceForSession(
240         surface: Surface?,
241         width: Int,
242         height: Int,
243         surfaceRenderer: RenderCallback
244     ): EGLSurface? {
245         with(obtainEGLManager()) {
246             return if (surface != null) {
247                 surfaceRenderer.onSurfaceCreated(
248                     eglSpec,
249                     // Successful creation of EGLManager ensures non null EGLConfig
250                     eglConfig!!,
251                     surface,
252                     width,
253                     height
254                 )
255             } else {
256                 null
257             }
258         }
259     }
260 
261     @WorkerThread
262     private fun releaseResourcesInternalAndQuit(callback: Runnable?) {
263         val eglManager = obtainEGLManager()
264         for (session in mSurfaceSessions) {
265             disposeSurfaceSession(session.value)
266         }
267         callback?.run()
268         mSurfaceSessions.clear()
269         for (eglCallback in mEglContextCallback) {
270             eglCallback.onEGLContextDestroyed(eglManager)
271         }
272         mEglContextCallback.clear()
273         eglManager.release()
274         mEglManager = null
275         quit()
276     }
277 
278     @WorkerThread
279     private fun requestRenderInternal(token: Int) {
280         log("requesting render for token: $token")
281         mSurfaceSessions[token]?.let { surfaceSession ->
282             val eglManager = obtainEGLManager()
283             val eglSurface = obtainEGLSurfaceForSession(surfaceSession)
284             if (eglSurface != null) {
285                 eglManager.makeCurrent(eglSurface)
286             } else {
287                 eglManager.makeCurrent(eglManager.defaultSurface)
288             }
289 
290             val width = surfaceSession.width
291             val height = surfaceSession.height
292             if (width > 0 && height > 0) {
293                 surfaceSession.surfaceRenderer.onDrawFrame(eglManager)
294             }
295 
296             if (eglSurface != null) {
297                 eglManager.swapAndFlushBuffers()
298             }
299         }
300     }
301 
302     @WorkerThread
303     private fun attachSurfaceSessionInternal(surfaceSession: SurfaceSession) {
304         mSurfaceSessions[surfaceSession.surfaceToken] = surfaceSession
305     }
306 
307     @WorkerThread
308     private fun resizeSurfaceSessionInternal(token: Int, width: Int, height: Int) {
309         mSurfaceSessions[token]?.let { surfaceSession ->
310             surfaceSession.apply {
311                 this.width = width
312                 this.height = height
313             }
314             disposeSurfaceSession(surfaceSession)
315             obtainEGLSurfaceForSession(surfaceSession)
316         }
317     }
318 
319     @WorkerThread
320     private fun detachSurfaceSessionInternal(token: Int, callback: Runnable?) {
321         val session = mSurfaceSessions.remove(token)
322         if (session != null) {
323             disposeSurfaceSession(session)
324         }
325         callback?.run()
326     }
327 
328     /**
329      * Helper method that issues a callback on the handler instance for this thread ensuring proper
330      * nullability checks are handled. This assumes that that [GLRenderer.start] has been called
331      * before attempts to interact with the corresponding Handler are made with this method
332      */
333     private inline fun withHandler(block: Handler.() -> Unit) {
334         val handler =
335             mHandler ?: throw IllegalStateException("Did you forget to call GLThread.start()?")
336         if (!mIsTearingDown.get()) {
337             block(handler)
338         }
339     }
340 
341     companion object {
342 
343         private const val DEBUG = true
344         private const val TAG = "GLThread"
345 
346         internal fun log(msg: String) {
347             if (DEBUG) {
348                 Log.v(TAG, msg)
349             }
350         }
351     }
352 
353     private class SurfaceSession(
354         /**
355          * Identifier used to lookup the mapping of this surface session. Consumers are expected to
356          * provide this identifier to operate on the corresponding surface to either request a frame
357          * be rendered or to remove this Surface
358          */
359         val surfaceToken: Int,
360 
361         /**
362          * Target surface to render into. Can be null for situations where GL is used to render into
363          * a frame buffer object provided from an AHardwareBuffer instance. In these cases the
364          * actual surface is never used.
365          */
366         val surface: Surface?,
367 
368         /**
369          * Callback used to create an EGLSurface from the provided surface as well as render content
370          * to the surface
371          */
372         val surfaceRenderer: RenderCallback
373     ) {
374         /**
375          * Lazily created + cached [EGLSurface] after [RenderCallback.onSurfaceCreated] is invoked.
376          * This is only modified on the backing thread
377          */
378         var eglSurface: EGLSurface? = null
379 
380         /** Target width of the [surface]. This is only modified on the backing thread */
381         var width: Int = 0
382 
383         /** Target height of the [surface]. This is only modified on the backing thread */
384         var height: Int = 0
385     }
386 }
387