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