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.lowlatency
18 
19 import android.graphics.Canvas
20 import android.graphics.RenderNode
21 import android.graphics.SurfaceTexture
22 import android.os.Build
23 import android.os.Handler
24 import android.os.HandlerThread
25 import androidx.annotation.AnyThread
26 import androidx.annotation.RequiresApi
27 import androidx.annotation.WorkerThread
28 import androidx.graphics.SurfaceTextureRenderer
29 import androidx.graphics.utils.post
30 
31 /**
32  * Class responsible for the producing side of SurfaceTextures that are rendered with content
33  * provided from a canvas. This class handles proxying all requests to an internal thread as well as
34  * throttles production of frames based on consumption rate.
35  */
36 @RequiresApi(Build.VERSION_CODES.Q)
37 internal class TextureProducer<T>(val width: Int, val height: Int, val callbacks: Callbacks<T>) {
38 
39     interface Callbacks<T> {
40         fun onTextureAvailable(texture: SurfaceTexture)
41 
42         fun render(canvas: Canvas, width: Int, height: Int, param: T)
43     }
44 
45     private var mIsReleasing = false
46     private val mParams = ArrayList<T>()
47     private var mPendingRenders = 0
48     private val mProducerThread = HandlerThread("producerThread").apply { start() }
49     private val mProducerHandler = Handler(mProducerThread.looper)
50 
51     private val mCancelPendingRunnable = Runnable { mParams.clear() }
52 
53     @WorkerThread // ProducerThread
54     private fun teardown(releaseCallback: (() -> Unit)? = null) {
55         releaseCallback?.invoke()
56         mSurfaceTextureRenderer.release()
57         mProducerThread.quit()
58     }
59 
60     @WorkerThread // ProducerThread
61     private fun isPendingRendering() = mParams.isNotEmpty() || mPendingRenders > 0
62 
63     private val mRenderNode =
64         RenderNode("node").apply {
65             setPosition(0, 0, this@TextureProducer.width, this@TextureProducer.height)
66         }
67 
68     private inline fun RenderNode.record(block: (Canvas) -> Unit) {
69         val canvas = beginRecording()
70         block(canvas)
71         endRecording()
72     }
73 
74     private val mSurfaceTextureRenderer =
75         SurfaceTextureRenderer(mRenderNode, width, height, mProducerHandler) { texture ->
76             callbacks.onTextureAvailable(texture)
77         }
78 
79     @WorkerThread // ProducerThread
80     private fun doRender() {
81         if (mPendingRenders < MAX_PENDING_RENDERS) {
82             if (mParams.isNotEmpty()) {
83                 mRenderNode.record { canvas ->
84                     for (p in mParams) {
85                         callbacks.render(canvas, width, height, p)
86                     }
87                 }
88                 mParams.clear()
89                 mPendingRenders++
90                 mSurfaceTextureRenderer.renderFrame()
91             }
92         }
93     }
94 
95     @AnyThread
96     fun requestRender(param: T) {
97         mProducerHandler.post(RENDER) {
98             if (!mIsReleasing) {
99                 mParams.add(param)
100                 doRender()
101             }
102         }
103     }
104 
105     @AnyThread
106     fun cancelPending() {
107         mProducerHandler.removeCallbacksAndMessages(RENDER)
108         mProducerHandler.post(CANCEL_PENDING, mCancelPendingRunnable)
109     }
110 
111     @AnyThread
112     fun markTextureConsumed() {
113         mProducerHandler.post(TEXTURE_CONSUMED) {
114             mPendingRenders--
115             if (mIsReleasing && !isPendingRendering()) {
116                 teardown()
117             } else {
118                 doRender()
119             }
120         }
121     }
122 
123     @AnyThread
124     fun execute(runnable: Runnable) {
125         mProducerHandler.post(runnable)
126     }
127 
128     @AnyThread
129     fun remove(runnable: Runnable) {
130         mProducerHandler.removeCallbacks(runnable)
131     }
132 
133     @AnyThread
134     fun release(cancelPending: Boolean, onReleaseComplete: (() -> Unit)? = null) {
135         if (cancelPending) {
136             cancelPending()
137         }
138         mProducerHandler.post(RELEASE) {
139             mIsReleasing = true
140             if (!isPendingRendering()) {
141                 teardown(onReleaseComplete)
142             }
143         }
144     }
145 
146     private companion object {
147         /**
148          * Constant to indicate a request to render new content into a SurfaceTexture for
149          * consumption.
150          */
151         const val RENDER = 0
152 
153         /** Constant to indicate that a previously produced frame has been consumed. */
154         const val TEXTURE_CONSUMED = 1
155 
156         /**
157          * Cancel all pending requests to render and clear all parameters that are to be consumed
158          * for an upcoming frame
159          */
160         const val CANCEL_PENDING = 2
161 
162         /** Release the resources associated with this [TextureProducer] instance */
163         const val RELEASE = 3
164 
165         /**
166          * Maximum number of frames to produce before the producer pauses. Subsequent attempts to
167          * render will batch parameters and continue to produce frames when the consumer signals
168          * that the corresponding textures have been consumed.
169          */
170         const val MAX_PENDING_RENDERS = 2
171     }
172 }
173