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