1 /*
<lambda>null2  * Copyright 2023 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
18 
19 import android.graphics.HardwareRenderer
20 import android.graphics.RenderNode
21 import android.graphics.SurfaceTexture
22 import android.os.Build
23 import android.os.Handler
24 import android.util.Log
25 import android.view.Surface
26 import androidx.annotation.RequiresApi
27 
28 /** Class that handles drawing content of a RenderNode into a SurfaceTexture */
29 @RequiresApi(Build.VERSION_CODES.Q)
30 internal class SurfaceTextureRenderer(
31     /** Target RenderNode of the content that is to be drawn */
32     private val renderNode: RenderNode,
33 
34     /** Width of the SurfaceTexture */
35     width: Int,
36 
37     /** Height of the SurfaceTexture */
38     height: Int,
39 
40     /** Handler used to send SurfaceTexture#OnFrameAvailableListener callbacks */
41     private val handler: Handler,
42 
43     /** Callback invoked when a new image frame is available on the underlying SurfaceTexture */
44     private val frameAvailable: (SurfaceTexture) -> Unit
45 ) {
46 
47     // Workaround: b/272751501
48     // For some reason, SurfaceTexture instances that are created in detached mode get gc'ed
49     // prematurely after the application is idle for ~10 or more seconds. This issue appears in
50     // multiple versions of Android. However, if we use a subclass of SurfaceTexture we seem to
51     // not run into this. It appears that there is some internal package name checking of
52     // android.graphics.SurfaceTexture API within jni in the platform and using a subclass seems
53     // to prevent work around this issue.
54     //
55     // An alternative solution is to create a SurfaceTexture in attached mode by providing a
56     // placeholder texture identifier then having the consuming GLThread call detachFromGLContext
57     // and attachToGLContext with a freshly created texture id.
58     //
59     // Currently we go with the original option as it may not be explicit to implementations that
60     // the initial detach is necessary here.
61     private class RenderSurfaceTexture(singleBufferMode: Boolean) :
62         SurfaceTexture(singleBufferMode)
63 
64     private var mIsReleased = false
65 
66     private val mSurfaceTexture =
67         RenderSurfaceTexture(false).apply {
68             setDefaultBufferSize(width, height)
69             setOnFrameAvailableListener(
70                 { surfaceTexture -> frameAvailable(surfaceTexture) },
71                 handler
72             )
73         }
74 
75     private val mTextureSurface = Surface(mSurfaceTexture)
76     private val mHardwareRenderer =
77         HardwareRenderer().apply {
78             setSurface(mTextureSurface)
79             setContentRoot(renderNode)
80             start()
81         }
82 
83     fun renderFrame() {
84         if (!mIsReleased) {
85             mHardwareRenderer.apply { createRenderRequest().setWaitForPresent(false).syncAndDraw() }
86         } else {
87             Log.w(
88                 TAG,
89                 "Attempt to renderFrame when SurfaceTextureRenderer has already " + "been released"
90             )
91         }
92     }
93 
94     /**
95      * Releases all resources of the SurfaceTextureRenderer instances. Attempts to use this object
96      * after this call has been made will be ignored.
97      */
98     fun release() {
99         if (!mIsReleased) {
100             mHardwareRenderer.stop()
101             mHardwareRenderer.destroy()
102             mTextureSurface.release()
103             if (!mSurfaceTexture.isReleased) {
104                 mSurfaceTexture.release()
105             }
106             mIsReleased = true
107         } else {
108             Log.w(
109                 TAG,
110                 "Attempt to release a SurfaceTextureRenderer that has " + "already been released"
111             )
112         }
113     }
114 
115     companion object {
116         private val TAG = "SurfaceTextureRenderer"
117     }
118 }
119