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