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