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.graphics.SurfaceTexture 20 import android.opengl.EGL14 21 import android.opengl.EGLConfig 22 import android.opengl.EGLSurface 23 import android.view.Surface 24 import android.view.SurfaceHolder 25 import android.view.SurfaceView 26 import android.view.TextureView 27 import androidx.annotation.WorkerThread 28 import androidx.graphics.opengl.egl.EGLConfigAttributes 29 import androidx.graphics.opengl.egl.EGLManager 30 import androidx.graphics.opengl.egl.EGLSpec 31 import java.util.concurrent.CountDownLatch 32 import java.util.concurrent.atomic.AtomicInteger 33 34 /** 35 * Class responsible for coordination of requests to render into surfaces using OpenGL. This creates 36 * a backing thread to handle EGL dependencies and draw leveraging OpenGL across multiple 37 * [android.view.Surface] instances that can be attached and detached throughout the lifecycle of an 38 * application. Usage of this class is recommended to be done on the UI thread. 39 * 40 * @param eglSpecFactory Callback invoked to determine the EGL spec version to use for EGL 41 * management. This is invoked on the GL Thread 42 * @param eglConfigFactory Callback invoked to determine the appropriate EGLConfig used to create 43 * the EGL context. This is invoked on the GL Thread 44 */ 45 // GL is the industry standard for referencing OpenGL vs Gl (lowercase l) 46 class GLRenderer( <lambda>null47 eglSpecFactory: () -> EGLSpec = { EGLSpec.V14 }, <lambda>null48 eglConfigFactory: EGLManager.() -> EGLConfig = { 49 // 8 bit channels should always be supported 50 loadConfig(EGLConfigAttributes.RGBA_8888) 51 ?: throw IllegalStateException( 52 "Unable to obtain config for 8 bit EGL " + "configuration" 53 ) 54 } 55 ) { 56 57 /** 58 * Factory method to determine which [EGLSpec] the underlying [EGLManager] implementation uses 59 */ 60 private val mEglSpecFactory: () -> EGLSpec = eglSpecFactory 61 62 /** 63 * Factory method used to create the corresponding EGLConfig used to create the EGLRenderer used 64 * by [EGLManager] 65 */ 66 private val mEglConfigFactory: EGLManager.() -> EGLConfig = eglConfigFactory 67 68 /** GLThread used to manage EGL dependencies, create EGLSurfaces and draw content */ 69 private var mGLThread: GLThread? = null 70 71 /** Collection of [RenderTarget] instances that are managed by the GLRenderer */ 72 private val mRenderTargets = ArrayList<RenderTarget>() 73 74 /** 75 * Collection of callbacks to be invoked when the EGL dependencies are initialized or torn down 76 */ 77 private val mEglContextCallback = HashSet<EGLContextCallback>() 78 79 /** 80 * Removes the corresponding [RenderTarget] from management of the GLThread. This destroys the 81 * EGLSurface associated with this surface and subsequent requests to render into the surface 82 * with the provided token are ignored. 83 * 84 * If the [cancelPending] flag is set to true, any queued request to render that has not started 85 * yet is cancelled. However, if this is invoked in the middle of the frame being rendered, it 86 * will continue to process the current frame. 87 * 88 * Additionally if this flag is false, all pending requests to render will be processed before 89 * the [RenderTarget] is detached. 90 * 91 * Note the detach operation will only occur if the GLRenderer is started, that is if 92 * [isRunning] returns true. Otherwise this is a no-op. GLRenderer will automatically detach all 93 * [RenderTarget] instances as part of its teardown process. 94 */ 95 @JvmOverloads detachnull96 fun detach( 97 target: RenderTarget, 98 cancelPending: Boolean, 99 @WorkerThread onDetachComplete: ((RenderTarget) -> Unit)? = null 100 ) { 101 if (mRenderTargets.contains(target)) { 102 detachInternal(target, cancelPending) { 103 // WorkerThread 104 target.release() 105 target.onDetach.invoke() 106 onDetachComplete?.invoke(target) 107 } 108 mRenderTargets.remove(target) 109 } 110 } 111 detachInternalnull112 internal fun detachInternal( 113 target: RenderTarget, 114 cancelPending: Boolean, 115 @WorkerThread onDetachComplete: ((RenderTarget) -> Unit)? = null 116 ) { 117 val runnable = 118 if (onDetachComplete != null) { 119 Runnable { onDetachComplete.invoke(target) } 120 } else { 121 null 122 } 123 mGLThread?.detachSurface(target.token, cancelPending, runnable) 124 } 125 126 /** 127 * Determines if the GLThread has been started. That is [start] has been invoked on this 128 * GLRenderer instance without a corresponding call to [stop]. 129 */ isRunningnull130 fun isRunning(): Boolean = mGLThread != null 131 132 /** 133 * Starts the GLThread. After this method is called, consumers can attempt to attach 134 * [android.view.Surface] instances through [attach] as well as schedule content to be drawn 135 * through [requestRender] 136 * 137 * @param name Optional name to provide to the GLThread 138 * @throws IllegalStateException if EGLConfig with desired attributes cannot be created 139 */ 140 @JvmOverloads 141 fun start( 142 name: String = "GLThread", 143 ) { 144 if (mGLThread == null) { 145 GLThread.log("starting thread...") 146 mGLThread = 147 GLThread(name, mEglSpecFactory, mEglConfigFactory).apply { 148 start() 149 if (!mEglContextCallback.isEmpty()) { 150 // Add a copy of the current collection as new entries to 151 // mEglContextCallback 152 // could be mistakenly added multiple times. 153 this.addEGLCallbacks(ArrayList<EGLContextCallback>(mEglContextCallback)) 154 } 155 } 156 } 157 } 158 159 /** 160 * Mark the corresponding surface session with the given token as dirty to schedule a call to 161 * [RenderCallback#onDrawFrame]. If there is already a queued request to render into the 162 * provided surface with the specified token, this request is ignored. 163 * 164 * Note the render operation will only occur if the GLRenderer is started, that is if 165 * [isRunning] returns true. Otherwise this is a no-op. 166 * 167 * @param target RenderTarget to be re-rendered 168 * @param onRenderComplete Optional callback invoked on the backing thread after the frame has 169 * been rendered. 170 */ 171 @JvmOverloads requestRendernull172 fun requestRender(target: RenderTarget, onRenderComplete: ((RenderTarget) -> Unit)? = null) { 173 val token = target.token 174 val callbackRunnable = 175 if (onRenderComplete != null) { 176 Runnable { onRenderComplete.invoke(target) } 177 } else { 178 null 179 } 180 mGLThread?.requestRender(token, callbackRunnable) 181 } 182 183 /** 184 * Resize the corresponding surface associated with the RenderTarget to the specified width and 185 * height and re-render. This will destroy the EGLSurface created by 186 * [RenderCallback.onSurfaceCreated] and invoke it again with the updated dimensions. An 187 * optional callback is invoked on the backing thread after the resize operation is complete. 188 * 189 * Note the resize operation will only occur if the GLRenderer is started, that is if 190 * [isRunning] returns true. Otherwise this is a no-op. 191 * 192 * @param target RenderTarget to be resized 193 * @param width Updated width of the corresponding surface 194 * @param height Updated height of the corresponding surface 195 * @param onResizeComplete Optional callback invoked on the backing thread when the resize 196 * operation is complete 197 */ 198 @JvmOverloads resizenull199 fun resize( 200 target: RenderTarget, 201 width: Int, 202 height: Int, 203 onResizeComplete: ((RenderTarget) -> Unit)? = null 204 ) { 205 val token = target.token 206 val callbackRunnable = 207 if (onResizeComplete != null) { 208 Runnable { onResizeComplete.invoke(target) } 209 } else { 210 null 211 } 212 mGLThread?.resizeSurface(token, width, height, callbackRunnable) 213 } 214 215 /** 216 * Queue a [Runnable] to be executed on the GL rendering thread. Note it is important that this 217 * [Runnable] does not block otherwise it can stall the GL thread. The EGLContext will be 218 * created after [start] is invoked and before the runnable is executed. 219 * 220 * @param runnable Runnable to be executed 221 */ executenull222 fun execute(runnable: Runnable) { 223 mGLThread?.execute(runnable) 224 } 225 226 /** 227 * Stop the corresponding GL thread. This destroys all EGLSurfaces as well as any other EGL 228 * dependencies. All queued requests that have not been processed yet are cancelled. 229 * 230 * Note the stop operation will only occur if the GLRenderer was previously started, that is 231 * [isRunning] returns true. Otherwise this is a no-op. 232 * 233 * @param cancelPending If true, all pending requests are cancelled and the backing thread is 234 * torn down immediately. If false, all pending requests are processed first before tearing 235 * down the backing thread. Subsequent requests made after this call are ignored. 236 * @param onStop Optional callback invoked on the backing thread after it is torn down. 237 */ 238 @JvmOverloads stopnull239 fun stop(cancelPending: Boolean, onStop: ((GLRenderer) -> Unit)? = null) { 240 GLThread.log("stopping thread...") 241 // Make a copy of the render targets to call cleanup operations on to avoid potential 242 // concurrency issues. 243 // This method will clear the existing collection and we do not want to potentially tear 244 // down a target that was attached after a subsequent call to start if the tear down 245 // callback execution is delayed if previously pending requests have not been cancelled 246 // (i.e. cancelPending is false) 247 val renderTargets = ArrayList(mRenderTargets) 248 mGLThread?.tearDown(cancelPending) { 249 // No need to call target.detach as this callback is invoked after 250 // the dependencies are cleaned up 251 for (target in renderTargets) { 252 target.release() 253 target.onDetach.invoke() 254 } 255 onStop?.invoke(this@GLRenderer) 256 } 257 mGLThread = null 258 mRenderTargets.clear() 259 } 260 261 /** 262 * Add an [EGLContextCallback] to receive callbacks for construction and destruction of EGL 263 * dependencies. 264 * 265 * These callbacks are invoked on the backing thread. 266 */ registerEGLContextCallbacknull267 fun registerEGLContextCallback(callback: EGLContextCallback) { 268 mEglContextCallback.add(callback) 269 mGLThread?.addEGLCallback(callback) 270 } 271 272 /** 273 * Remove [EGLContextCallback] to no longer receive callbacks for construction and destruction 274 * of EGL dependencies. 275 * 276 * These callbacks are invoked on the backing thread 277 */ unregisterEGLContextCallbacknull278 fun unregisterEGLContextCallback(callback: EGLContextCallback) { 279 mEglContextCallback.remove(callback) 280 mGLThread?.removeEGLCallback(callback) 281 } 282 283 /** 284 * Callbacks invoked when the GL dependencies are created and destroyed. These are logical 285 * places to setup and tear down any dependencies that are used for drawing content within a 286 * frame (ex. compiling shaders) 287 */ 288 interface EGLContextCallback { 289 290 /** 291 * Callback invoked on the backing thread after EGL dependencies are initialized. This is 292 * guaranteed to be invoked before any instance of [RenderCallback.onSurfaceCreated] is 293 * called. This will be invoked after [GLRenderer.start]. 294 */ 295 // Suppressing CallbackMethodName due to b/238939160 296 @Suppress("CallbackMethodName") 297 @WorkerThread onEGLContextCreatednull298 fun onEGLContextCreated(eglManager: EGLManager) 299 300 /** 301 * Callback invoked on the backing thread before EGL dependencies are about to be torn down. 302 * This is invoked after [GLRenderer.stop] is processed. 303 */ 304 // Suppressing CallbackMethodName due to b/238939160 305 @Suppress("CallbackMethodName") 306 @WorkerThread 307 fun onEGLContextDestroyed(eglManager: EGLManager) 308 } 309 310 @JvmDefaultWithCompatibility 311 /** 312 * Interface used for creating an [EGLSurface] with a user defined configuration from the 313 * provided surface as well as a callback used to render content into the surface for a given 314 * frame 315 */ 316 interface RenderCallback { 317 /** 318 * Used to create a corresponding [EGLSurface] from the provided [android.view.Surface] 319 * instance. This enables consumers to configure the corresponding [EGLSurface] they wish to 320 * render into. The [EGLSurface] created here is guaranteed to be the current surface before 321 * [onDrawFrame] is called. That is, implementations of onDrawFrame do not need to call 322 * eglMakeCurrent on this [EGLSurface]. 323 * 324 * This method is invoked on the GL thread. 325 * 326 * The default implementation will create a window surface with EGL_WIDTH and EGL_HEIGHT set 327 * to [width] and [height] respectively. Implementations can override this method to provide 328 * additional [EGLConfigAttributes] for this surface (ex. [EGL14.EGL_SINGLE_BUFFER]. 329 * 330 * Implementations can return null to indicate the default surface should be used. This is 331 * helpful in situations where content is to be rendered within a frame buffer object 332 * instead of to an [EGLSurface] 333 * 334 * @param spec EGLSpec used to create the corresponding EGLSurface 335 * @param config EGLConfig used to create the corresponding EGLSurface 336 * @param surface [android.view.Surface] used to create an EGLSurface from 337 * @param width Desired width of the surface to create 338 * @param height Desired height of the surface to create 339 */ 340 @WorkerThread 341 fun onSurfaceCreated( 342 spec: EGLSpec, 343 config: EGLConfig, 344 surface: Surface, 345 width: Int, 346 height: Int 347 ): EGLSurface? = 348 // Always default to creating an EGL window surface 349 // Despite having access to the width and height here, do not explicitly 350 // pass in EGLConfigAttributes specifying the EGL_WIDTH and EGL_HEIGHT parameters 351 // as those are not accepted parameters for eglCreateWindowSurface but they are 352 // for other EGL Surface factory methods such as eglCreatePBufferSurface 353 // See accepted parameters here: 354 // https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglCreateWindowSurface.xhtml 355 // and here 356 // https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglCreatePbufferSurface.xhtml 357 spec.eglCreateWindowSurface(config, surface, null) 358 359 /** 360 * Callback used to issue OpenGL drawing commands into the [EGLSurface] created in 361 * [onSurfaceCreated]. This [EGLSurface] is guaranteed to be current before this callback is 362 * invoked and [EGLManager.swapAndFlushBuffers] will be invoked afterwards. If additional 363 * scratch [EGLSurface]s are used here it is up to the implementation of this method to 364 * ensure that the proper surfaces are made current and the appropriate swap buffers call is 365 * made 366 * 367 * This method is invoked on the backing thread 368 * 369 * @param eglManager Handle to EGL dependencies 370 */ 371 @WorkerThread fun onDrawFrame(eglManager: EGLManager) 372 } 373 374 /** 375 * Adds the [android.view.Surface] to be managed by the GLThread. A corresponding [EGLSurface] 376 * is created on the GLThread as well as a callback for rendering into the surface through 377 * [RenderCallback]. Unlike the other [attach] methods that consume a [SurfaceView] or 378 * [TextureView], this method does not handle any lifecycle callbacks associated with the target 379 * surface. Therefore it is up to the consumer to properly setup/teardown resources associated 380 * with this surface. 381 * 382 * @param surface Target surface to be managed by the backing thread 383 * @param width Desired width of the [surface] 384 * @param height Desired height of the [surface] 385 * @param renderer Callbacks used to create a corresponding [EGLSurface] from the given surface 386 * as well as render content into the created [EGLSurface] 387 * @return [RenderTarget] used for subsequent requests to communicate with the provided Surface 388 * (ex. [requestRender] or [detach]). 389 * @throws IllegalStateException If this method was called when the GLThread has not started 390 * (i.e. start has not been called) 391 */ attachnull392 fun attach(surface: Surface, width: Int, height: Int, renderer: RenderCallback): RenderTarget { 393 val thread = mGLThread 394 if (thread != null) { 395 val token = sToken.getAndIncrement() 396 thread.attachSurface(token, surface, width, height, renderer) 397 return RenderTarget(token, this).also { mRenderTargets.add(it) } 398 } else { 399 throw IllegalStateException("GLThread not started, did you forget to call start?") 400 } 401 } 402 403 /** 404 * Creates a new [RenderTarget] without a corresponding [android.view.Surface]. This avoids 405 * creation of an [EGLSurface] which is useful in scenarios where only rendering to a frame 406 * buffer object is required. 407 * 408 * @param width Desired width of the [RenderTarget] 409 * @param height Desired height of the [RenderTarget] 410 * @param renderer Callbacks used to issue OpenGL commands to the [RenderTarget] 411 * @return [RenderTarget] used for subsequent requests to render through 412 * [RenderTarget.requestRender] or to remove itself from the [GLRenderer] through 413 * [RenderTarget.detach] 414 * @throws IllegalStateException If this method was called when the GLThread has not started 415 * (i.e. start has not been called) 416 */ createRenderTargetnull417 fun createRenderTarget(width: Int, height: Int, renderer: RenderCallback): RenderTarget { 418 val thread = mGLThread 419 if (thread != null) { 420 val token = sToken.getAndIncrement() 421 thread.attachSurface(token, null, width, height, renderer) 422 return RenderTarget(token, this).also { mRenderTargets.add(it) } 423 } else { 424 throw IllegalStateException("GLThread not started, did you forget to call start?") 425 } 426 } 427 428 /** 429 * Adds the [android.view.Surface] provided by the given [SurfaceView] to be managed by the 430 * backing thread. 431 * 432 * A corresponding [EGLSurface] is created on the GLThread as well as a callback for rendering 433 * into the surface through [RenderCallback]. 434 * 435 * This method automatically configures a [SurfaceHolder.Callback] used to attach the 436 * [android.view.Surface] when the underlying [SurfaceHolder] that contains the surface is 437 * available. Similarly this surface will be detached from [GLRenderer] when the surface 438 * provided by the [SurfaceView] is destroyed (i.e. [SurfaceHolder.Callback.surfaceDestroyed] is 439 * called. 440 * 441 * If the [android.view.Surface] is already available by the time this method is invoked, it is 442 * attached synchronously. 443 * 444 * @param surfaceView SurfaceView that provides the surface to be rendered by the backing thread 445 * @param renderer callbacks used to create a corresponding [EGLSurface] from the given surface 446 * as well as render content into the created [EGLSurface] 447 * @return [RenderTarget] used for subsequent requests to communicate with the provided Surface 448 * (ex. [requestRender] or [detach]). 449 * @throws IllegalStateException If this method was called when the GLThread has not started 450 * (i.e. start has not been called) 451 */ attachnull452 fun attach(surfaceView: SurfaceView, renderer: RenderCallback): RenderTarget { 453 val thread = mGLThread 454 if (thread != null) { 455 val token = sToken.getAndIncrement() 456 val holder = surfaceView.holder 457 val callback = 458 object : SurfaceHolder.Callback2 { 459 460 var isAttached = false 461 462 /** 463 * Optional condition that maybe used if we are issuing a blocking call to 464 * render in [SurfaceHolder.Callback2.surfaceRedrawNeeded] In this case we need 465 * to signal the condition of either the request to render has completed, or if 466 * the RenderTarget has been detached and the pending render request is 467 * cancelled. 468 */ 469 @Volatile var renderLatch: CountDownLatch? = null 470 471 /** 472 * [CountDownLatch] used when issuing a blocking call to 473 * [SurfaceHolder.Callback.surfaceDestroyed] In this case we need to signal the 474 * condition of either the request to detach has completed in case the 475 * GLRenderer has been forcefully stopped via [GLRenderer.stop] with the cancel 476 * pending flag set to true. 477 */ 478 val detachLatch: CountDownLatch = CountDownLatch(1) 479 480 fun onDetachComplete() { 481 isAttached = false 482 // Countdown in case we have been detached while waiting for a render 483 // to be completed 484 renderLatch?.countDown() 485 detachLatch.countDown() 486 } 487 488 val renderTarget = 489 RenderTarget(token, this@GLRenderer) @WorkerThread { 490 // SurfaceHolder.add/remove callback is thread safe 491 holder.removeCallback(this) 492 onDetachComplete() 493 } 494 495 override fun surfaceRedrawNeeded(p0: SurfaceHolder) { 496 // If the [RenderTarget] has already been detached then skip rendering 497 if (detachLatch.count > 0) { 498 val latch = CountDownLatch(1).also { renderLatch = it } 499 // Request a render and block until the rendering is complete 500 // surfaceRedrawNeeded is invoked on older API levels and is replaced 501 // with 502 // surfaceRedrawNeededAsync for newer API levels which is non-blocking 503 renderTarget.requestRender @WorkerThread { latch.countDown() } 504 latch.await() 505 renderLatch = null 506 } 507 } 508 509 override fun surfaceRedrawNeededAsync( 510 holder: SurfaceHolder, 511 drawingFinished: Runnable 512 ) { 513 renderTarget.requestRender { drawingFinished.run() } 514 } 515 516 override fun surfaceCreated(holder: SurfaceHolder) { 517 // NO-OP wait until surfaceChanged which is guaranteed to be called and also 518 // provides the appropriate width height of the surface 519 } 520 521 override fun surfaceChanged( 522 holder: SurfaceHolder, 523 format: Int, 524 width: Int, 525 height: Int 526 ) { 527 if (!isAttached) { 528 thread.attachSurface(token, holder.surface, width, height, renderer) 529 isAttached = true 530 } else { 531 renderTarget.resize(width, height) 532 } 533 renderTarget.requestRender() 534 } 535 536 override fun surfaceDestroyed(holder: SurfaceHolder) { 537 // Issue a request to detach the [RenderTarget]. Even if it was 538 // previously detached this request is a no-op and the corresponding 539 // [CountDownLatch] will signal when the [RenderTarget] detachment is 540 // complete 541 // or instantaneously if it was already detached 542 renderTarget.detachInternal(true) { onDetachComplete() } 543 detachLatch.await() 544 } 545 } 546 holder.addCallback(callback) 547 if (holder.surface != null && holder.surface.isValid) { 548 thread.attachSurface( 549 token, 550 holder.surface, 551 surfaceView.width, 552 surfaceView.height, 553 renderer 554 ) 555 } 556 mRenderTargets.add(callback.renderTarget) 557 return callback.renderTarget 558 } else { 559 throw IllegalStateException("GLThread not started, did you forget to call start?") 560 } 561 } 562 563 /** 564 * Adds the [android.view.Surface] provided by the given [TextureView] to be managed by the 565 * backing thread. 566 * 567 * A corresponding [EGLSurface] is created on the GLThread as well as a callback for rendering 568 * into the surface through [RenderCallback]. 569 * 570 * This method automatically configures a [TextureView.SurfaceTextureListener] used to create a 571 * [android.view.Surface] when the underlying [SurfaceTexture] is available. Similarly this 572 * surface will be detached from [GLRenderer] if the underlying [SurfaceTexture] is destroyed 573 * (i.e. [TextureView.SurfaceTextureListener.onSurfaceTextureDestroyed] is called. 574 * 575 * If the [SurfaceTexture] is already available by the time this method is called, then it is 576 * attached synchronously. 577 * 578 * @param textureView TextureView that provides the surface to be rendered into on the GLThread 579 * @param renderer callbacks used to create a corresponding [EGLSurface] from the given surface 580 * as well as render content into the created [EGLSurface] 581 * @return [RenderTarget] used for subsequent requests to communicate with the provided Surface 582 * (ex. [requestRender] or [detach]). 583 * @throws IllegalStateException If this method was called when the GLThread has not started 584 * (i.e. start has not been called) 585 */ attachnull586 fun attach(textureView: TextureView, renderer: RenderCallback): RenderTarget { 587 val thread = mGLThread 588 if (thread != null) { 589 val token = sToken.getAndIncrement() 590 val detachLatch = CountDownLatch(1) 591 val renderTarget = 592 RenderTarget(token, this) @WorkerThread { 593 textureView.handler?.post { textureView.surfaceTextureListener = null } 594 detachLatch.countDown() 595 } 596 textureView.surfaceTextureListener = 597 object : TextureView.SurfaceTextureListener { 598 override fun onSurfaceTextureAvailable( 599 surfaceTexture: SurfaceTexture, 600 width: Int, 601 height: Int 602 ) { 603 thread.attachSurface( 604 token, 605 Surface(surfaceTexture), 606 width, 607 height, 608 renderer 609 ) 610 renderTarget.requestRender() 611 } 612 613 override fun onSurfaceTextureSizeChanged( 614 texture: SurfaceTexture, 615 width: Int, 616 height: Int 617 ) { 618 renderTarget.resize(width, height) 619 renderTarget.requestRender() 620 } 621 622 override fun onSurfaceTextureDestroyed(p0: SurfaceTexture): Boolean { 623 // Issue a request to detach the [RenderTarget]. Even if it was 624 // previously detached this request is a no-op and the corresponding 625 // [CountDownLatch] will signal when the [RenderTarget] detachment is 626 // complete 627 // or instantaneously if it was already detached 628 renderTarget.detachInternal(true) { detachLatch.countDown() } 629 detachLatch.await() 630 return true 631 } 632 633 override fun onSurfaceTextureUpdated(p0: SurfaceTexture) { 634 // NO-OP 635 } 636 } 637 if (textureView.isAvailable) { 638 thread.attachSurface( 639 token, 640 Surface(textureView.surfaceTexture), 641 textureView.width, 642 textureView.height, 643 renderer 644 ) 645 renderTarget.requestRender() 646 } 647 mRenderTargets.add(renderTarget) 648 return renderTarget 649 } else { 650 throw IllegalStateException("GLThread not started, did you forget to call start?") 651 } 652 } 653 654 /** Handle to a [android.view.Surface] that is given to [GLRenderer] to handle rendering. */ 655 class RenderTarget 656 internal constructor( 657 internal val token: Int, 658 glManager: GLRenderer, <lambda>null659 @WorkerThread internal val onDetach: () -> Unit = {} 660 ) { 661 662 @Volatile private var mManager: GLRenderer? = glManager 663 releasenull664 internal fun release() { 665 mManager = null 666 } 667 668 /** 669 * Request that this [RenderTarget] should have its contents redrawn. This consumes an 670 * optional callback that is invoked on the backing thread when the rendering is completed. 671 * 672 * Note the render operation will only occur if the RenderTarget is attached, that is 673 * [isAttached] returns true. If the [RenderTarget] is detached or the [GLRenderer] that 674 * created this RenderTarget is stopped, this is a no-op. 675 * 676 * @param onRenderComplete Optional callback called on the backing thread when rendering is 677 * finished 678 */ 679 @JvmOverloads requestRendernull680 fun requestRender(@WorkerThread onRenderComplete: ((RenderTarget) -> Unit)? = null) { 681 mManager?.requestRender(this@RenderTarget, onRenderComplete) 682 } 683 684 /** 685 * Determines if the current RenderTarget is attached to GLRenderer. This is true until 686 * [detach] has been called. If the RenderTarget is no longer in an attached state (i.e. 687 * this returns false). Subsequent calls to [requestRender] will be ignored. 688 */ isAttachednull689 fun isAttached(): Boolean = mManager != null 690 691 /** 692 * Resize the RenderTarget to the specified width and height. This will destroy the 693 * EGLSurface created by [RenderCallback.onSurfaceCreated] and invoke it again with the 694 * updated dimensions. An optional callback is invoked on the backing thread after the 695 * resize operation is complete 696 * 697 * Note the resize operation will only occur if the RenderTarget is attached, that is 698 * [isAttached] returns true. If the [RenderTarget] is detached or the [GLRenderer] that 699 * created this RenderTarget is stopped, this is a no-op. 700 * 701 * @param width New target width to resize the RenderTarget 702 * @param height New target height to resize the RenderTarget 703 * @param onResizeComplete Optional callback invoked after the resize is complete 704 */ 705 @JvmOverloads 706 fun resize( 707 width: Int, 708 height: Int, 709 @WorkerThread onResizeComplete: ((RenderTarget) -> Unit)? = null 710 ) { 711 mManager?.resize(this, width, height, onResizeComplete) 712 } 713 714 /** 715 * Removes the corresponding [RenderTarget] from management of the GLThread. This destroys 716 * the EGLSurface associated with this surface and subsequent requests to render into the 717 * surface with the provided token are ignored. 718 * 719 * If the [cancelPending] flag is set to true, any queued request to render that has not 720 * started yet is cancelled. However, if this is invoked in the middle of the frame being 721 * rendered, it will continue to process the current frame. 722 * 723 * Additionally if this flag is false, all pending requests to render will be processed 724 * before the [RenderTarget] is detached. 725 * 726 * This is a convenience method around [GLRenderer.detach] 727 * 728 * Note the detach operation will only occur if the RenderTarget is attached, that is 729 * [isAttached] returns true. If the [RenderTarget] is detached or the [GLRenderer] that 730 * created this RenderTarget is stopped, this is a no-op. 731 */ 732 @JvmOverloads detachnull733 fun detach(cancelPending: Boolean, onDetachComplete: ((RenderTarget) -> Unit)? = null) { 734 mManager?.detach(this, cancelPending, onDetachComplete) 735 } 736 detachInternalnull737 internal fun detachInternal( 738 cancelPending: Boolean, 739 onDetachComplete: ((RenderTarget) -> Unit)? = null 740 ) { 741 mManager?.detachInternal(this, cancelPending, onDetachComplete) 742 } 743 } 744 745 companion object { 746 /** Counter used to issue unique identifiers for surfaces that are managed by GLRenderer */ 747 private val sToken = AtomicInteger() 748 } 749 } 750