• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 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 package com.google.jetpackcamera.core.camera.effects
17 
18 import android.graphics.SurfaceTexture
19 import android.opengl.EGL14
20 import android.opengl.EGLConfig
21 import android.opengl.EGLExt
22 import android.opengl.EGLSurface
23 import android.util.Size
24 import android.view.Surface
25 import androidx.camera.core.SurfaceOutput
26 import androidx.camera.core.SurfaceProcessor
27 import androidx.camera.core.SurfaceRequest
28 import androidx.graphics.opengl.GLRenderer
29 import androidx.graphics.opengl.egl.EGLManager
30 import androidx.graphics.opengl.egl.EGLSpec
31 import com.google.jetpackcamera.core.common.RefCounted
32 import kotlin.coroutines.coroutineContext
33 import kotlinx.coroutines.CompletableDeferred
34 import kotlinx.coroutines.CoroutineScope
35 import kotlinx.coroutines.CoroutineStart
36 import kotlinx.coroutines.Dispatchers
37 import kotlinx.coroutines.Job
38 import kotlinx.coroutines.Runnable
39 import kotlinx.coroutines.SupervisorJob
40 import kotlinx.coroutines.async
41 import kotlinx.coroutines.cancel
42 import kotlinx.coroutines.coroutineScope
43 import kotlinx.coroutines.ensureActive
44 import kotlinx.coroutines.flow.MutableStateFlow
45 import kotlinx.coroutines.flow.collectLatest
46 import kotlinx.coroutines.flow.filterNot
47 import kotlinx.coroutines.flow.filterNotNull
48 import kotlinx.coroutines.flow.onCompletion
49 import kotlinx.coroutines.flow.update
50 import kotlinx.coroutines.launch
51 
52 private const val TIMESTAMP_UNINITIALIZED = -1L
53 
54 /**
55  * This is a [SurfaceProcessor] that passes on the same content from the input
56  * surface to the output surface. Used to make a copies of surfaces.
57  */
58 class CopyingSurfaceProcessor(coroutineScope: CoroutineScope) : SurfaceProcessor {
59 
60     private val inputSurfaceFlow = MutableStateFlow<SurfaceRequestScope?>(null)
61     private val outputSurfaceFlow = MutableStateFlow<SurfaceOutputScope?>(null)
62 
63     init {
64         coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) {
65             inputSurfaceFlow
66                 .filterNotNull()
67                 .collectLatest { surfaceRequestScope ->
68                     surfaceRequestScope.withSurfaceRequest { surfaceRequest ->
69 
70                         val renderCallbacks = ShaderCopy(surfaceRequest.dynamicRange)
71                         renderCallbacks.renderWithSurfaceRequest(surfaceRequest)
72                     }
73                 }
74         }
75     }
76 
77     private suspend fun RenderCallbacks.renderWithSurfaceRequest(surfaceRequest: SurfaceRequest) =
78         coroutineScope inputScope@{
79             var currentTimestamp = TIMESTAMP_UNINITIALIZED
80             val surfaceTextureRef = RefCounted<SurfaceTexture> {
81                 it.release()
82             }
83             val textureTransform = FloatArray(16)
84 
85             val frameUpdateFlow = MutableStateFlow(0)
86 
87             val initializeCallback = object : GLRenderer.EGLContextCallback {
88 
89                 override fun onEGLContextCreated(eglManager: EGLManager) {
90                     initRenderer()
91 
92                     val surfaceTex = createSurfaceTexture(
93                         surfaceRequest.resolution.width,
94                         surfaceRequest.resolution.height
95                     )
96 
97                     // Initialize the reference counted surface texture
98                     surfaceTextureRef.initialize(surfaceTex)
99 
100                     surfaceTex.setOnFrameAvailableListener {
101                         // Increment frame counter
102                         frameUpdateFlow.update { it + 1 }
103                     }
104 
105                     val inputSurface = Surface(surfaceTex)
106                     surfaceRequest.provideSurface(inputSurface, Runnable::run) { result ->
107                         inputSurface.release()
108                         surfaceTextureRef.release()
109                         this@inputScope.cancel(
110                             "Input surface no longer receiving frames: $result"
111                         )
112                     }
113                 }
114 
115                 override fun onEGLContextDestroyed(eglManager: EGLManager) {
116                     // no-op
117                 }
118             }
119 
120             val glRenderer = GLRenderer(
121                 eglSpecFactory = provideEGLSpec,
122                 eglConfigFactory = initConfig
123             )
124             glRenderer.registerEGLContextCallback(initializeCallback)
125             glRenderer.start(glThreadName)
126 
127             val inputRenderTarget = glRenderer.createRenderTarget(
128                 surfaceRequest.resolution.width,
129                 surfaceRequest.resolution.height,
130                 object : GLRenderer.RenderCallback {
131 
132                     override fun onDrawFrame(eglManager: EGLManager) {
133                         surfaceTextureRef.acquire()?.also {
134                             try {
135                                 currentTimestamp =
136                                     if (currentTimestamp == TIMESTAMP_UNINITIALIZED) {
137                                         // Don't perform any updates on first draw,
138                                         // we're only setting up the context.
139                                         0
140                                     } else {
141                                         it.updateTexImage()
142                                         it.getTransformMatrix(textureTransform)
143                                         it.timestamp
144                                     }
145                             } finally {
146                                 surfaceTextureRef.release()
147                             }
148                         }
149                     }
150                 }
151             )
152 
153             // Create the context and initialize the input. This will call RenderTarget.onDrawFrame,
154             // but we won't actually update the frame since this triggers adding the frame callback.
155             // All subsequent updates will then happen through frameUpdateFlow.
156             // This should be updated when https://issuetracker.google.com/331968279 is resolved.
157             inputRenderTarget.requestRender()
158 
159             // Connect the onConnectToInput callback with the onDisconnectFromInput
160             // Should only be called on worker thread
161             var connectedToInput = false
162 
163             // Should only be called on worker thread
164             val onConnectToInput: () -> Boolean = {
165                 connectedToInput = surfaceTextureRef.acquire() != null
166                 connectedToInput
167             }
168 
169             // Should only be called on worker thread
170             val onDisconnectFromInput: () -> Unit = {
171                 if (connectedToInput) {
172                     surfaceTextureRef.release()
173                     connectedToInput = false
174                 }
175             }
176 
177             // Wait for output surfaces
178             outputSurfaceFlow
179                 .onCompletion {
180                     glRenderer.stop(cancelPending = false)
181                     glRenderer.unregisterEGLContextCallback(initializeCallback)
182                 }.filterNotNull()
183                 .collectLatest { surfaceOutputScope ->
184                     surfaceOutputScope.withSurfaceOutput { refCountedSurface,
185                                                            size,
186                                                            updateTransformMatrix ->
187                         // If we can't acquire the surface, then the surface output is already
188                         // closed, so we'll return and wait for the next output surface.
189                         val outputSurface =
190                             refCountedSurface.acquire() ?: return@withSurfaceOutput
191 
192                         val surfaceTransform = FloatArray(16)
193                         val outputRenderTarget = glRenderer.attach(
194                             outputSurface,
195                             size.width,
196                             size.height,
197                             object : GLRenderer.RenderCallback {
198 
199                                 override fun onSurfaceCreated(
200                                     spec: EGLSpec,
201                                     config: EGLConfig,
202                                     surface: Surface,
203                                     width: Int,
204                                     height: Int
205                                 ): EGLSurface? {
206                                     return if (onConnectToInput()) {
207                                         createOutputSurface(spec, config, surface, width, height)
208                                     } else {
209                                         null
210                                     }
211                                 }
212 
213                                 override fun onDrawFrame(eglManager: EGLManager) {
214                                     val currentDrawSurface = eglManager.currentDrawSurface
215                                     if (currentDrawSurface != eglManager.defaultSurface) {
216                                         updateTransformMatrix(
217                                             surfaceTransform,
218                                             textureTransform
219                                         )
220 
221                                         drawFrame(
222                                             size.width,
223                                             size.height,
224                                             surfaceTransform
225                                         )
226 
227                                         // Set timestamp
228                                         val display =
229                                             EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)
230                                         EGLExt.eglPresentationTimeANDROID(
231                                             display,
232                                             eglManager.currentDrawSurface,
233                                             currentTimestamp
234                                         )
235                                     }
236                                 }
237                             }
238                         )
239 
240                         frameUpdateFlow
241                             .onCompletion {
242                                 outputRenderTarget.detach(cancelPending = false) {
243                                     onDisconnectFromInput()
244                                     refCountedSurface.release()
245                                 }
246                             }.filterNot { it == 0 } // Don't attempt render on frame count 0
247                             .collectLatest {
248                                 inputRenderTarget.requestRender()
249                                 outputRenderTarget.requestRender()
250                             }
251                     }
252                 }
253         }
254 
255     override fun onInputSurface(surfaceRequest: SurfaceRequest) {
256         val newScope = SurfaceRequestScope(surfaceRequest)
257         inputSurfaceFlow.update { old ->
258             old?.cancel("New SurfaceRequest received.")
259             newScope
260         }
261     }
262 
263     override fun onOutputSurface(surfaceOutput: SurfaceOutput) {
264         val newScope = SurfaceOutputScope(surfaceOutput)
265         outputSurfaceFlow.update { old ->
266             old?.cancel("New SurfaceOutput received.")
267             newScope
268         }
269     }
270 }
271 
272 interface RenderCallbacks {
273     val glThreadName: String
274     val provideEGLSpec: () -> EGLSpec
275     val initConfig: EGLManager.() -> EGLConfig
276     val initRenderer: () -> Unit
277     val createSurfaceTexture: (width: Int, height: Int) -> SurfaceTexture
278     val createOutputSurface: (
279         eglSpec: EGLSpec,
280         config: EGLConfig,
281         surface: Surface,
282         width: Int,
283         height: Int
284     ) -> EGLSurface
285     val drawFrame: (outputWidth: Int, outputHeight: Int, surfaceTransform: FloatArray) -> Unit
286 }
287 
288 private class SurfaceOutputScope(val surfaceOutput: SurfaceOutput) {
289     private val surfaceLifecycleJob = SupervisorJob()
<lambda>null290     private val refCountedSurface = RefCounted<Surface>(onRelease = {
291         surfaceOutput.close()
292     }).apply {
293         // Ensure we don't release until after `initialize` has completed by deferring
294         // the release.
295         val deferredRelease = CompletableDeferred<Unit>()
296         initialize(
<lambda>null297             surfaceOutput.getSurface(Runnable::run) {
298                 deferredRelease.complete(Unit)
299             }
300         )
<lambda>null301         CoroutineScope(Dispatchers.Unconfined).launch {
302             deferredRelease.await()
303             surfaceLifecycleJob.cancel("SurfaceOutput close requested.")
304             this@apply.release()
305         }
306     }
307 
withSurfaceOutputnull308     suspend fun <R> withSurfaceOutput(
309         block: suspend CoroutineScope.(
310             surface: RefCounted<Surface>,
311             surfaceSize: Size,
312             updateTransformMatrix: (updated: FloatArray, original: FloatArray) -> Unit
313         ) -> R
314     ): R {
315         return CoroutineScope(coroutineContext + Job(surfaceLifecycleJob)).async(
316             start = CoroutineStart.UNDISPATCHED
317         ) {
318             ensureActive()
319             block(
320                 refCountedSurface,
321                 surfaceOutput.size,
322                 surfaceOutput::updateTransformMatrix
323             )
324         }.await()
325     }
326 
cancelnull327     fun cancel(message: String? = null) {
328         message?.apply { surfaceLifecycleJob.cancel(message) } ?: surfaceLifecycleJob.cancel()
329     }
330 }
331 
332 private class SurfaceRequestScope(private val surfaceRequest: SurfaceRequest) {
333     private val requestLifecycleJob = SupervisorJob()
334 
335     init {
<lambda>null336         surfaceRequest.addRequestCancellationListener(Runnable::run) {
337             requestLifecycleJob.cancel("SurfaceRequest cancelled.")
338         }
339     }
340 
withSurfaceRequestnull341     suspend fun <R> withSurfaceRequest(
342         block: suspend CoroutineScope.(
343             surfaceRequest: SurfaceRequest
344         ) -> R
345     ): R {
346         return CoroutineScope(coroutineContext + Job(requestLifecycleJob)).async(
347             start = CoroutineStart.UNDISPATCHED
348         ) {
349             ensureActive()
350             block(surfaceRequest)
351         }.await()
352     }
353 
cancelnull354     fun cancel(message: String? = null) {
355         message?.apply { requestLifecycleJob.cancel(message) } ?: requestLifecycleJob.cancel()
356         // Attempt to tell frame producer we will not provide a surface. This may fail (silently)
357         // if surface was already provided or the producer has cancelled the request, in which
358         // case we don't have to do anything.
359         surfaceRequest.willNotProvideSurface()
360     }
361 }
362