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.graphics.Bitmap 20 import android.graphics.BlendMode 21 import android.graphics.Canvas 22 import android.graphics.Color 23 import android.graphics.HardwareRenderer 24 import android.graphics.Matrix 25 import android.graphics.Paint 26 import android.graphics.RenderNode 27 import android.hardware.HardwareBuffer 28 import android.media.Image 29 import android.media.ImageReader 30 import android.os.Build 31 import android.util.Log 32 import androidx.annotation.RequiresApi 33 import androidx.core.util.Consumer 34 import androidx.graphics.CanvasBufferedRenderer.RenderResult.Companion.ERROR_UNKNOWN 35 import androidx.graphics.CanvasBufferedRenderer.RenderResult.Companion.SUCCESS 36 import androidx.graphics.lowlatency.BufferTransformHintResolver 37 import androidx.graphics.lowlatency.PreservedBufferContentsVerifier 38 import androidx.hardware.SyncFenceCompat 39 import androidx.hardware.SyncFenceV33 40 import java.util.concurrent.Executor 41 import java.util.concurrent.atomic.AtomicBoolean 42 import java.util.concurrent.locks.ReentrantLock 43 import kotlin.concurrent.withLock 44 45 @RequiresApi(Build.VERSION_CODES.Q) 46 internal class CanvasBufferedRendererV29( 47 private val mWidth: Int, 48 private val mHeight: Int, 49 private val mFormat: Int, 50 private val mUsage: Long, 51 private val mMaxBuffers: Int, 52 private val mPreservationConfig: Int, 53 ) : CanvasBufferedRenderer.Impl { 54 55 private var mPreservedRenderStrategy: PreservedRenderStrategy? = null 56 57 private var mImageReader: ImageReader? = null 58 59 private var mHardwareRenderer: HardwareRenderer? = null 60 61 private fun createImageReader(preserveStrategy: PreservedRenderStrategy?): ImageReader = 62 ImageReader.newInstance( 63 mWidth, 64 mHeight, 65 mFormat, 66 // If the device does not support preserving contents when we are rendering to a single 67 // buffer, use the fallback of leveraging 2 but redrawing the contents from the previous 68 // frame into the next frame 69 if (mMaxBuffers == 1 && preserveStrategy != null) { 70 preserveStrategy.maxImages 71 } else { 72 mMaxBuffers 73 }, 74 mUsage 75 ) 76 77 private fun createHardwareRenderer(imageReader: ImageReader): HardwareRenderer = 78 HardwareRenderer().apply { 79 // HardwareRenderer may preserve contents of the buffers if the isOpaque flag is true 80 // (see PreservedBufferContentsVerifier), otherwise it will clear contents across 81 // subsequent renders. 82 isOpaque = true 83 setContentRoot(mRootRenderNode) 84 setSurface(imageReader.surface) 85 start() 86 } 87 88 private val mRootRenderNode = 89 RenderNode("rootNode").apply { 90 setPosition(0, 0, mWidth, mHeight) 91 clipToBounds = false 92 } 93 94 private var mContentRoot: RenderNode? = null 95 private var mLightX: Float = 0f 96 private var mLightY: Float = 0f 97 private var mLightZ: Float = 0f 98 private var mLightRadius: Float = 0f 99 100 private var mAmbientShadowAlpha: Float = 0f 101 private var mSpotShadowAlpha: Float = 0f 102 103 private var mBufferTransform = BufferTransformHintResolver.UNKNOWN_TRANSFORM 104 private val mTransform = Matrix() 105 106 private var mPreserveContents = false 107 108 /** 109 * Lock used to provide thread safe access to the underlying pool that maps between outstanding 110 * HardwareBuffer instances and the Image it is associated with 111 */ 112 private val mBufferLock = ReentrantLock() 113 114 /** Condition used to signal when an Image is available after it was previously released */ 115 private val mBufferSignal = mBufferLock.newCondition() 116 117 /** 118 * Mapping of [HardwareBuffer] instances to the corresponding [Image] they are associated with. 119 * Because [ImageReader] allocates a new [Image] instance each time acquireNextImage is called, 120 * we cannot rely on the fact that the [ImageReader] will cycle through the same [Image] 121 * instances. So instead create a mapping of buffers to Images that will be added to and removed 122 * on each render. 123 */ 124 private val mAllocatedBuffers = HashMap<HardwareBuffer, Image>() 125 126 private fun closeBuffers() = 127 mBufferLock.withLock { 128 for (entry in mAllocatedBuffers) { 129 entry.key.close() // HardwareBuffer 130 entry.value.waitAndClose() // Image 131 } 132 mAllocatedBuffers.clear() 133 mBufferSignal.signal() 134 mImageReader?.close() 135 mImageReader = null 136 mHardwareRenderer?.let { renderer -> 137 renderer.stop() 138 renderer.destroy() 139 } 140 mHardwareRenderer = null 141 } 142 143 private val mIsReleased = AtomicBoolean(false) 144 145 override fun close() { 146 closeBuffers() 147 mRootRenderNode.discardDisplayList() 148 mIsReleased.set(true) 149 } 150 151 override fun isClosed(): Boolean = mIsReleased.get() 152 153 override fun draw( 154 request: CanvasBufferedRenderer.RenderRequest, 155 executor: Executor, 156 callback: Consumer<CanvasBufferedRenderer.RenderResult> 157 ) { 158 val transform = request.transform 159 val content = mContentRoot 160 // If we are redrawing contents from the previous scene then we must re-record the drawing 161 // drawing instructions to draw the updated bitmap 162 val forceRedraw = request.preserveContents || mPreserveContents 163 val shouldRedraw = 164 !mRootRenderNode.hasDisplayList() || transform != mBufferTransform || forceRedraw 165 if (shouldRedraw && content != null) { 166 recordContent(content, updateTransform(transform), request.preserveContents) 167 } 168 169 val lightX = mLightX 170 val lightY = mLightY 171 val lightZ = mLightZ 172 val lightRadius = mLightRadius 173 val ambientShadowAlpha = mAmbientShadowAlpha 174 val spotShadowAlpha = mSpotShadowAlpha 175 val preserveContents = request.preserveContents 176 executor.execute { 177 if (!isClosed()) { 178 mBufferLock.withLock { 179 var preservedRenderStrategy = mPreservedRenderStrategy 180 if (preserveContents && mMaxBuffers == 1 && preservedRenderStrategy == null) { 181 closeBuffers() 182 preservedRenderStrategy = createPreservationStrategy(mPreservationConfig) 183 mPreservedRenderStrategy = preservedRenderStrategy 184 } 185 val renderer = 186 obtainHardwareRenderer(obtainImageReader(preservedRenderStrategy)) 187 renderer.apply { 188 setLightSourceAlpha(ambientShadowAlpha, spotShadowAlpha) 189 setLightSourceGeometry(lightX, lightY, lightZ, lightRadius) 190 } 191 dispatchRender(executor, renderer, preservedRenderStrategy, callback) 192 } 193 } 194 } 195 } 196 197 private fun obtainImageReader(preserveStrategy: PreservedRenderStrategy?): ImageReader = 198 mImageReader ?: createImageReader(preserveStrategy).also { mImageReader = it } 199 200 private fun obtainHardwareRenderer(imageReader: ImageReader): HardwareRenderer = 201 mHardwareRenderer ?: createHardwareRenderer(imageReader).also { mHardwareRenderer = it } 202 203 private fun dispatchRender( 204 executor: Executor, 205 renderer: HardwareRenderer, 206 preservedRenderStrategy: PreservedRenderStrategy?, 207 callback: Consumer<CanvasBufferedRenderer.RenderResult> 208 ) { 209 with(renderer) { 210 var result = 0 211 val renderRequest = 212 createRenderRequest().setFrameCommitCallback(executor) { 213 acquireBuffer { buffer, fence -> 214 preservedRenderStrategy?.onRenderComplete(buffer, fence) 215 callback.accept( 216 CanvasBufferedRenderer.RenderResult( 217 buffer, 218 fence, 219 if (isSuccess(result)) SUCCESS else ERROR_UNKNOWN 220 ) 221 ) 222 if (mMaxBuffers == 1) { 223 releaseBuffer(buffer, fence) 224 } 225 } 226 } 227 result = renderRequest.syncAndDraw() 228 } 229 } 230 231 /** 232 * Helper method to determine if [HardwareRenderer.FrameRenderRequest.syncAndDraw] was 233 * successful. In this case we wait for the next buffer even if we miss the vsync. 234 */ 235 private fun isSuccess(result: Int) = 236 result == HardwareRenderer.SYNC_OK || result == HardwareRenderer.SYNC_FRAME_DROPPED 237 238 private fun updateTransform(transform: Int): Matrix { 239 mBufferTransform = transform 240 return BufferTransformHintResolver.configureTransformMatrix( 241 mTransform, 242 mWidth.toFloat(), 243 mHeight.toFloat(), 244 transform 245 ) 246 } 247 248 private fun recordContent( 249 contentNode: RenderNode, 250 transform: Matrix, 251 preserveContents: Boolean 252 ) { 253 val canvas = mRootRenderNode.beginRecording() 254 if (preserveContents) { 255 mBufferLock.withLock { mPreservedRenderStrategy?.restoreContents(canvas) } 256 } else { 257 canvas.drawColor(Color.BLACK, BlendMode.CLEAR) 258 } 259 canvas.save() 260 canvas.concat(transform) 261 canvas.drawRenderNode(contentNode) 262 canvas.restore() 263 mRootRenderNode.endRecording() 264 mPreserveContents = preserveContents 265 } 266 267 override fun setContentRoot(renderNode: RenderNode) { 268 mContentRoot = renderNode 269 mRootRenderNode.discardDisplayList() 270 } 271 272 override fun setLightSourceAlpha(ambientShadowAlpha: Float, spotShadowAlpha: Float) { 273 mAmbientShadowAlpha = ambientShadowAlpha 274 mSpotShadowAlpha = spotShadowAlpha 275 } 276 277 /** 278 * Acquires the next [Image] from the [ImageReader]. This method will block until the number of 279 * outstanding [Image]s acquired is below the maximum number of buffers specified by maxImages. 280 * This is because [ImageReader] will throw exceptions if an additional [Image] is acquired 281 * beyond the maximum amount of buffers. 282 */ 283 private inline fun acquireBuffer(block: (HardwareBuffer, SyncFenceCompat?) -> Unit) { 284 mBufferLock.withLock { 285 // Block until the number of outstanding Images is less than the maximum specified 286 val reader = mImageReader ?: return 287 while (mAllocatedBuffers.size >= reader.maxImages) { 288 mBufferSignal.await() 289 } 290 291 val image = reader.acquireNextImage() 292 if (image != null) { 293 // Be sure to call Image#getHardwareBuffer once as each call creates a new java 294 // object 295 // and we are relying on referential equality to map the HardwareBuffer back to the 296 // Image that it came from in order to close the Image when the buffer is released 297 val buffer = image.hardwareBuffer 298 if (buffer != null) { 299 // Insert a new mapping of hardware buffer to Image, closing any previous Image 300 // that maybe inserted for the hardware buffer 301 mAllocatedBuffers.put(buffer, image)?.waitAndClose() 302 val fence = image.getFenceCompat() 303 block(buffer, fence) 304 // If we are leveraging single buffered rendering, release the buffer right away 305 if (reader.maxImages == 1) { 306 releaseBuffer(buffer, fence) 307 } 308 } else { 309 // If we do not have a HardwareBuffer associated with this Image, close it 310 // and return null 311 image.waitAndClose() 312 } 313 } 314 } 315 } 316 317 override fun releaseBuffer(hardwareBuffer: HardwareBuffer, syncFence: SyncFenceCompat?) { 318 mBufferLock.withLock { 319 // Remove the mapping of HardwareBuffer to Image and close the Image associated with 320 // this HardwareBuffer instance 321 val image = mAllocatedBuffers.remove(hardwareBuffer) 322 if (image != null) { 323 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 324 ImageVerificationHelper.setFence(image, syncFence) 325 image.close() 326 } else { 327 image.waitAndClose() 328 } 329 } 330 mBufferSignal.signal() 331 } 332 } 333 334 override fun setLightSourceGeometry( 335 lightX: Float, 336 lightY: Float, 337 lightZ: Float, 338 lightRadius: Float 339 ) { 340 mLightX = lightX 341 mLightY = lightY 342 mLightZ = lightZ 343 mLightRadius = lightRadius 344 } 345 346 private fun Image.getFenceCompat(): SyncFenceCompat? = 347 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 348 ImageVerificationHelper.getFence(this) 349 } else { 350 null 351 } 352 353 private fun Image.waitAndClose() { 354 getFenceCompat()?.let { fence -> 355 fence.awaitForever() 356 fence.close() 357 } 358 close() 359 } 360 361 internal interface PreservedRenderStrategy { 362 val maxImages: Int 363 364 fun restoreContents(canvas: Canvas) 365 366 fun onRenderComplete(hardwareBuffer: HardwareBuffer, fence: SyncFenceCompat?) 367 } 368 369 internal class SingleBufferedStrategy : PreservedRenderStrategy { 370 override val maxImages = 1 371 372 override fun restoreContents(canvas: Canvas) { 373 // NO-OP HWUI preserves contents 374 } 375 376 override fun onRenderComplete(hardwareBuffer: HardwareBuffer, fence: SyncFenceCompat?) { 377 // NO-OP 378 } 379 } 380 381 internal class RedrawBufferStrategy( 382 // debugging flag used to simulate clearing of the canvas before 383 // restoring the contents 384 private val forceClear: Boolean = false 385 ) : PreservedRenderStrategy { 386 387 override val maxImages: Int = 2 388 389 private var mHardwareBuffer: HardwareBuffer? = null 390 private var mFence: SyncFenceCompat? = null 391 392 private val drawBitmapPaint = Paint().apply { blendMode = BlendMode.SRC } 393 394 override fun restoreContents(canvas: Canvas) { 395 if (forceClear) { 396 canvas.drawColor(Color.BLACK, BlendMode.CLEAR) 397 } 398 mHardwareBuffer?.let { buffer -> 399 mFence?.awaitForever() 400 val bitmap = 401 Bitmap.wrapHardwareBuffer(buffer, CanvasBufferedRenderer.DefaultColorSpace) 402 if (bitmap != null) { 403 canvas.save() 404 // Use blendMode=SRC to copy over every pixel from the old buffer, in case the 405 // newly obtained buffer was instantiated with garbage. If the 406 // RedrawBufferStrategy is needed, meaning the buffer contents are not preserved 407 // across renders, don't just assume that a fresh buffer will be cleared to all 408 // transparent pixels. 409 canvas.drawBitmap(bitmap, 0f, 0f, drawBitmapPaint) 410 canvas.restore() 411 } 412 } 413 } 414 415 override fun onRenderComplete(hardwareBuffer: HardwareBuffer, fence: SyncFenceCompat?) { 416 mHardwareBuffer = hardwareBuffer 417 mFence = fence 418 } 419 } 420 421 companion object { 422 const val TAG = "BufferRendererV29" 423 424 private val verifiedPreservation = AtomicBoolean(false) 425 private val supportsPreservation = AtomicBoolean(false) 426 427 internal fun createPreservationStrategy( 428 preservationStrategy: Int 429 ): PreservedRenderStrategy = 430 when (preservationStrategy) { 431 CanvasBufferedRenderer.USE_V29_IMPL_WITH_SINGLE_BUFFER -> { 432 Log.v(TAG, "Explicit usage of single buffered preservation strategy") 433 SingleBufferedStrategy() 434 } 435 CanvasBufferedRenderer.USE_V29_IMPL_WITH_REDRAW -> { 436 Log.v( 437 TAG, 438 "Explicit usage of double buffered redraw strategy " + "with force clear" 439 ) 440 RedrawBufferStrategy(true) 441 } 442 else -> { 443 if (!verifiedPreservation.getAndSet(true)) { 444 val verifier = PreservedBufferContentsVerifier() 445 supportsPreservation.set(verifier.supportsPreservedRenderedContent()) 446 verifier.release() 447 } 448 449 if (supportsPreservation.get()) { 450 Log.v(TAG, "Device supports persisted canvas optimizations") 451 SingleBufferedStrategy() 452 } else { 453 Log.w( 454 TAG, 455 "Warning, device DOES NOT support persisted canvas optimizations." 456 ) 457 RedrawBufferStrategy(false) 458 } 459 } 460 } 461 } 462 } 463 464 /** Helper class to avoid class verification failures */ 465 @RequiresApi(Build.VERSION_CODES.TIRAMISU) 466 internal class ImageVerificationHelper private constructor() { 467 companion object { 468 469 @RequiresApi(Build.VERSION_CODES.TIRAMISU) getFencenull470 fun getFence(image: Image): SyncFenceCompat = SyncFenceCompat(image.fence) 471 472 @RequiresApi(Build.VERSION_CODES.TIRAMISU) 473 fun setFence(image: Image, fence: SyncFenceCompat?) { 474 if (fence != null && fence.mImpl is SyncFenceV33) { 475 image.fence = fence.mImpl.mSyncFence 476 } 477 } 478 } 479 } 480