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