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.hardware.HardwareBuffer
20 import androidx.annotation.WorkerThread
21 import androidx.graphics.utils.HandlerThreadExecutor
22 import androidx.hardware.SyncFenceCompat
23 import java.util.concurrent.Executor
24 import java.util.concurrent.atomic.AtomicBoolean
25 
26 /**
27  * Helper class to handle processing of event queues between the provided [HandlerThreadExecutor]
28  * and the [FrameProducer] which may be executing on different threads. This provides helper
29  * facilities to guarantee cancellation of requests and proper queueing of pending requests while
30  * the [FrameProducer] is in the middle of generating a frame.
31  */
32 internal class RenderQueue(
33     private val handlerThread: HandlerThreadExecutor,
34     private val frameProducer: FrameProducer,
35     private val frameCallback: FrameCallback
36 ) {
37 
38     /**
39      * Callbacks invoked when new frames are produced or if a frame is generated for a request that
40      * has been cancelled.
41      */
42     interface FrameCallback {
43         fun onFrameComplete(hardwareBuffer: HardwareBuffer, fence: SyncFenceCompat?)
44 
45         fun onFrameCancelled(hardwareBuffer: HardwareBuffer, fence: SyncFenceCompat?)
46     }
47 
48     /**
49      * Interface to represent a [FrameProducer] this can either be backed by a
50      * [android.graphics.HardwareRenderer] or [android.graphics.HardwareBufferRenderer] depending on
51      * the API level.
52      */
53     interface FrameProducer {
54         fun renderFrame(
55             executor: Executor,
56             requestComplete: (HardwareBuffer, SyncFenceCompat?) -> Unit
57         )
58     }
59 
60     /**
61      * Request to be executed by the [RenderQueue] this provides callbacks that are invoked when the
62      * request is initially queued as well as when to be executed before a frame is generated. This
63      * supports batching operations if the [FrameProducer] is busy.
64      */
65     interface Request {
66 
67         /** Callback invoked when the request is enqueued but before a frame is generated */
68         @WorkerThread fun onEnqueued() {}
69 
70         /**
71          * Callback invoked when the request is about to be processed as part of a the next frame
72          */
73         @WorkerThread fun execute()
74 
75         /** Callback invoked when the request is completed */
76         @WorkerThread fun onComplete()
77 
78         /**
79          * Flag to determine if multiple requests of the same type can be merged together. If a
80          * request is mergeable and the [id] mattches then subsequent
81          */
82         fun isMergeable(): Boolean = true
83 
84         /** Identifier for a request type to determine if the request can be batched */
85         val id: Int
86     }
87 
88     /** Flag to determine if all pending requests should be cancelled */
89     private val mIsCancelling = AtomicBoolean(false)
90 
91     /** Queue of pending requests that are executed whenever the [FrameProducer] is idle */
92     private val mRequests = ArrayDeque<Request>()
93 
94     /**
95      * Determines if the [FrameProducer] is in the middle of rendering a frame. This is accessed on
96      * the underlying HandlerThread only
97      */
98     private var mRequestPending = false
99 
100     /**
101      * Callback invoked when the [RenderQueue] is to be released. This will be invoked when there
102      * are no more pending requests to process
103      */
104     private var mReleaseCallback: (() -> Unit)? = null
105 
106     /** Flag to determine if we are in the middle of releasing the [RenderQueue] */
107     private val mIsReleasing = AtomicBoolean(false)
108 
109     /** Enqueues a request to be executed by the provided [FrameProducer] */
110     fun enqueue(request: Request) {
111         if (!mIsReleasing.get()) {
112             handlerThread.post(this) {
113                 request.onEnqueued()
114                 executeRequest(request)
115             }
116         }
117     }
118 
119     /**
120      * Cancels all pending requests. If a frame is the in the middle of being rendered,
121      * [FrameCallback.onFrameCancelled] will be invoked upon completion
122      */
123     fun cancelPending() {
124         if (!mIsReleasing.get()) {
125             mIsCancelling.set(true)
126             handlerThread.removeCallbacksAndMessages(this)
127             handlerThread.post(cancelRequestsRunnable)
128         }
129     }
130 
131     /**
132      * Configures a release callback to be invoked. If there are no pending requests, this will get
133      * invoked immediately on the [HandlerThreadExecutor]. Otherwise the callback is preserved and
134      * invoked after there are no more pending requests. After this method is invoked, no subsequent
135      * requests will be processed and this [RenderQueue] instance can no longer be used.
136      */
137     fun release(cancelPending: Boolean, onReleaseComplete: (() -> Unit)?) {
138         if (!mIsReleasing.get()) {
139             if (cancelPending) {
140                 cancelPending()
141             }
142             handlerThread.post {
143                 mReleaseCallback = onReleaseComplete
144                 val pendingRequest = isPendingRequest()
145                 if (!pendingRequest) {
146                     executeReleaseCallback()
147                 }
148             }
149             mIsReleasing.set(true)
150         }
151     }
152 
153     /** Determines if there are any pending requests or a frame is waiting to be produced */
154     private fun isPendingRequest() = mRequestPending || mRequests.isNotEmpty()
155 
156     /**
157      * Helper method that will execute a request on the [FrameProducer] if there is not a previous
158      * request pending. If there is a pending request, this will add it to an internal queue that
159      * will be dequeued when the request is completed.
160      */
161     @WorkerThread
162     private fun executeRequest(request: Request) {
163         if (!mRequestPending) {
164             mRequestPending = true
165             request.execute()
166             frameProducer.renderFrame(handlerThread) { hardwareBuffer, syncFenceCompat ->
167                 mRequestPending = false
168                 if (!mIsCancelling.getAndSet(false)) {
169                     frameCallback.onFrameComplete(hardwareBuffer, syncFenceCompat)
170                 } else {
171                     frameCallback.onFrameCancelled(hardwareBuffer, syncFenceCompat)
172                 }
173                 request.onComplete()
174                 if (mRequests.isNotEmpty()) {
175                     // Execute any pending requests that were queued while waiting for the
176                     // previous frame to render
177                     executeRequest(mRequests.removeFirst())
178                 } else if (mIsReleasing.get()) {
179                     executeReleaseCallback()
180                 }
181             }
182         } else {
183             // If the last request matches the type that we are adding, then batch the request
184             // i.e. don't add it to the queue as the previous request will handle batching.
185             val pendingRequest = mRequests.lastOrNull()
186             if (
187                 !request.isMergeable() || pendingRequest == null || pendingRequest.id != request.id
188             ) {
189                 mRequests.add(request)
190             }
191         }
192     }
193 
194     /** Returns true if [release] has been invoked */
195     fun isReleased(): Boolean = mIsReleasing.get()
196 
197     /** Invokes the release callback if one is previously configured and discards it */
198     private fun executeReleaseCallback() {
199         mReleaseCallback?.invoke()
200         mReleaseCallback = null
201     }
202 
203     /** Runnable executed when requests are to be cancelled */
204     private val cancelRequestsRunnable = Runnable {
205         mRequests.clear()
206         // Only reset the cancel flag if there is no current frame render request pending
207         // Otherwise when the frame is completed we will update the flag in the corresponding
208         // callback
209         if (!mRequestPending) {
210             mIsCancelling.set(false)
211         }
212     }
213 }
214