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.BlendMode
20 import android.graphics.Canvas
21 import android.graphics.Color
22 import android.graphics.ColorSpace
23 import android.graphics.RenderNode
24 import android.hardware.HardwareBuffer
25 import android.os.Build
26 import androidx.annotation.RequiresApi
27 import androidx.annotation.WorkerThread
28 import androidx.graphics.CanvasBufferedRenderer
29 import androidx.graphics.RenderQueue
30 import androidx.graphics.utils.HandlerThreadExecutor
31 import androidx.hardware.HardwareBufferFormat
32 import androidx.hardware.SyncFenceCompat
33 import java.util.concurrent.Executor
34 import java.util.concurrent.atomic.AtomicBoolean
35 
36 /**
37  * Class to provide an abstraction around implementations for a low latency hardware accelerated
38  * [Canvas] that provides a [HardwareBuffer] with the [Canvas] rendered scene
39  */
40 @RequiresApi(Build.VERSION_CODES.Q)
41 internal class SingleBufferedCanvasRenderer<T>(
42     private val width: Int,
43     private val height: Int,
44     bufferWidth: Int,
45     bufferHeight: Int,
46     @HardwareBufferFormat bufferFormat: Int,
47     private val transformHint: Int,
48     handlerThread: HandlerThreadExecutor,
49     private val callbacks: RenderCallbacks<T>
50 ) {
51 
52     interface RenderCallbacks<T> {
53         @WorkerThread fun render(canvas: Canvas, width: Int, height: Int, param: T)
54 
55         @WorkerThread
56         fun onBufferReady(hardwareBuffer: HardwareBuffer, syncFenceCompat: SyncFenceCompat?)
57 
58         @WorkerThread
59         fun onBufferCancelled(hardwareBuffer: HardwareBuffer, syncFenceCompat: SyncFenceCompat?) {
60             // NO-OP
61         }
62     }
63 
64     constructor(
65         width: Int,
66         height: Int,
67         bufferTransformer: BufferTransformer,
68         handlerThread: HandlerThreadExecutor,
69         callbacks: RenderCallbacks<T>
70     ) : this(
71         width,
72         height,
73         bufferTransformer.bufferWidth,
74         bufferTransformer.bufferHeight,
75         HardwareBuffer.RGBA_8888,
76         bufferTransformer.computedTransform,
77         handlerThread,
78         callbacks
79     )
80 
81     private val mRenderNode =
82         RenderNode("node").apply {
83             setPosition(
84                 0,
85                 0,
86                 this@SingleBufferedCanvasRenderer.width,
87                 this@SingleBufferedCanvasRenderer.height
88             )
89         }
90 
91     private val mRenderQueue =
92         RenderQueue(
93             handlerThread,
94             object : RenderQueue.FrameProducer {
95                 override fun renderFrame(
96                     executor: Executor,
97                     requestComplete: (HardwareBuffer, SyncFenceCompat?) -> Unit
98                 ) {
99                     mHardwareBufferRenderer.obtainRenderRequest().apply {
100                         if (transformHint != BufferTransformHintResolver.UNKNOWN_TRANSFORM) {
101                             setBufferTransform(transformHint)
102                         }
103                         preserveContents(true)
104                         setColorSpace(this@SingleBufferedCanvasRenderer.colorSpace)
105                         drawAsync(executor) { result ->
106                             requestComplete.invoke(result.hardwareBuffer, result.fence)
107                         }
108                     }
109                 }
110             },
111             object : RenderQueue.FrameCallback {
112                 override fun onFrameComplete(
113                     hardwareBuffer: HardwareBuffer,
114                     fence: SyncFenceCompat?
115                 ) {
116                     callbacks.onBufferReady(hardwareBuffer, fence)
117                 }
118 
119                 override fun onFrameCancelled(
120                     hardwareBuffer: HardwareBuffer,
121                     fence: SyncFenceCompat?
122                 ) {
123                     callbacks.onBufferCancelled(hardwareBuffer, fence)
124                 }
125             }
126         )
127 
128     private val mVisibleFlag = AtomicBoolean(false)
129 
130     private fun tearDown() {
131         mHardwareBufferRenderer.close()
132     }
133 
134     private val mHardwareBufferRenderer =
135         CanvasBufferedRenderer.Builder(bufferWidth, bufferHeight)
136             .setUsageFlags(FrontBufferUtils.obtainHardwareBufferUsageFlags())
137             .setMaxBuffers(1)
138             .setBufferFormat(bufferFormat)
139             .build()
140             .apply { setContentRoot(mRenderNode) }
141 
142     private val mPendingParams = ArrayList<T>()
143 
144     private inner class DrawParamRequest(val param: T) : RenderQueue.Request {
145 
146         override fun onEnqueued() {
147             mPendingParams.add(param)
148         }
149 
150         override fun execute() {
151             val canvas = mRenderNode.beginRecording()
152             for (pendingParam in mPendingParams) {
153                 callbacks.render(canvas, width, height, pendingParam)
154             }
155             mPendingParams.clear()
156             mRenderNode.endRecording()
157         }
158 
159         override fun onComplete() {
160             // NO-OP
161         }
162 
163         override val id: Int = RENDER
164     }
165 
166     private inner class ClearRequest(val clearRequest: (() -> Unit)?) : RenderQueue.Request {
167         override fun execute() {
168             val canvas = mRenderNode.beginRecording()
169             canvas.drawColor(Color.BLACK, BlendMode.CLEAR)
170             mRenderNode.endRecording()
171         }
172 
173         override fun onComplete() {
174             clearRequest?.invoke()
175         }
176 
177         override fun isMergeable(): Boolean = clearRequest == null
178 
179         override val id: Int = CLEAR
180     }
181 
182     private val defaultClearRequest = ClearRequest(null)
183 
184     /** Render into the [HardwareBuffer] with the given parameter and bounds */
185     fun render(param: T) {
186         mRenderQueue.enqueue(DrawParamRequest(param))
187     }
188 
189     /**
190      * Flag to indicate whether or not the contents of the [SingleBufferedCanvasRenderer] are
191      * visible. This is used to help internal state to determine appropriate synchronization
192      */
193     var isVisible: Boolean
194         get() = mVisibleFlag.get()
195         set(value) {
196             mVisibleFlag.set(value)
197         }
198 
199     /** Configure the color space that the content is rendered with */
200     var colorSpace: ColorSpace = CanvasBufferedRenderer.DefaultColorSpace
201 
202     /**
203      * Releases resources associated with [SingleBufferedCanvasRenderer] instance. Attempts to use
204      * this object after it is closed will be ignored
205      */
206     fun release(cancelPending: Boolean, onReleaseComplete: (() -> Unit)? = null) {
207         mRenderQueue.release(cancelPending) {
208             onReleaseComplete?.invoke()
209             tearDown()
210         }
211     }
212 
213     /** Clear the contents of the [HardwareBuffer] */
214     fun clear(clearComplete: (() -> Unit)? = null) {
215         val clearRequest =
216             if (clearComplete == null) {
217                 defaultClearRequest
218             } else {
219                 ClearRequest(clearComplete)
220             }
221         mRenderQueue.enqueue(clearRequest)
222     }
223 
224     /** Cancel all pending render requests */
225     fun cancelPending() {
226         mRenderQueue.cancelPending()
227     }
228 
229     private companion object {
230         const val RENDER = 0
231         const val CLEAR = 1
232     }
233 }
234