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.Bitmap
20 import android.graphics.BlendMode
21 import android.graphics.Color
22 import android.graphics.HardwareRenderer
23 import android.graphics.Paint
24 import android.graphics.RenderNode
25 import android.media.ImageReader
26 import android.os.Build
27 import androidx.annotation.RequiresApi
28 import androidx.graphics.CanvasBufferedRenderer
29 import java.util.concurrent.CountDownLatch
30 import java.util.concurrent.Executors
31 
32 /**
33  * For some devices, setting [HardwareRenderer.isOpaque] to true with [ImageReader.getMaxImages] set
34  * to 1 will end up preserving the contents of the buffers across renders. For low latency rendering
35  * this is ideal in order to only draw the deltas of content across render requests. However, not
36  * all devices support this optimization and will clear the contents regardless before each render
37  * request. This class is used to verify if the device does support preservation of contents across
38  * renders and can be used to signal for a fallback solution that will re-render the scene before
39  * proceeding for consistency.
40  */
41 @RequiresApi(Build.VERSION_CODES.Q)
42 internal class PreservedBufferContentsVerifier {
43 
44     private val executor = Executors.newSingleThreadExecutor()
45     private val paint = Paint()
46 
47     private val renderNode =
48         RenderNode("testNode").apply { setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT) }
49 
50     private val multiBufferedRenderer =
51         CanvasBufferedRenderer.Builder(TEST_WIDTH, TEST_HEIGHT)
52             .setMaxBuffers(1)
53             .setImpl(CanvasBufferedRenderer.USE_V29_IMPL_WITH_SINGLE_BUFFER)
54             .build()
55             .apply { setContentRoot(renderNode) }
56 
57     /**
58      * Executes a test rendering to verify if contents are preserved across renders. This is
59      * accomplished by the following steps:
60      * 1) Issue an initial render that clears the contents and draws green on the left
61      * 2) Issue an additional render that draws blue on the right side
62      * 3) Draw red using the dst over blend mode to render contents underneath
63      *
64      * If this device does support preserving content the result will have green pixels on the left
65      * hand side and blue on the right.
66      *
67      * If this device **does not** support preserving content, then the left hand side will be red
68      * as the red pixels would be rendered underneath transparent content if the buffer was cleared
69      * in advance.
70      */
71     fun supportsPreservedRenderedContent(): Boolean {
72         var canvas = renderNode.beginRecording()
73         // Ensure clear pixels before proceeding
74         canvas.drawColor(Color.BLACK, BlendMode.CLEAR)
75         canvas.drawRect(
76             0f,
77             0f,
78             TEST_WIDTH / 2f,
79             TEST_HEIGHT.toFloat(),
80             paint.apply { color = Color.GREEN }
81         )
82         renderNode.endRecording()
83 
84         val firstRenderLatch = CountDownLatch(1)
85         multiBufferedRenderer.obtainRenderRequest().preserveContents(true).drawAsync(executor) { _
86             ->
87             firstRenderLatch.countDown()
88         }
89 
90         firstRenderLatch.await()
91 
92         canvas = renderNode.beginRecording()
93         canvas.drawRect(
94             TEST_WIDTH / 2f,
95             0f,
96             TEST_WIDTH.toFloat(),
97             TEST_HEIGHT.toFloat(),
98             paint.apply { color = Color.BLUE }
99         )
100         // Draw red underneath the existing content
101         canvas.drawColor(Color.RED, BlendMode.DST_OVER)
102         renderNode.endRecording()
103 
104         var bitmap: Bitmap? = null
105         val secondRenderLatch = CountDownLatch(1)
106         multiBufferedRenderer.obtainRenderRequest().preserveContents(true).drawAsync(executor) {
107             result ->
108             result.fence?.awaitForever()
109             bitmap =
110                 Bitmap.wrapHardwareBuffer(
111                     result.hardwareBuffer,
112                     CanvasBufferedRenderer.DefaultColorSpace
113                 )
114             secondRenderLatch.countDown()
115         }
116         secondRenderLatch.await()
117 
118         val hardwareBitmap = bitmap
119         return if (hardwareBitmap != null) {
120             val copyBitmap = hardwareBitmap.copy(Bitmap.Config.ARGB_8888, false)
121             val result =
122                 copyBitmap.getPixel(0, 0) == Color.GREEN &&
123                     copyBitmap.getPixel(TEST_WIDTH - 1, TEST_HEIGHT - 1) == Color.BLUE
124             result
125         } else {
126             false
127         }
128     }
129 
130     fun release() {
131         executor.shutdownNow()
132         multiBufferedRenderer.close()
133         renderNode.discardDisplayList()
134     }
135 
136     companion object {
137         const val TEST_WIDTH = 2
138         const val TEST_HEIGHT = 2
139     }
140 }
141