1 /* <lambda>null2 * Copyright (C) 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 com.google.android.wallpaper.weathereffects.graphics 18 19 import android.graphics.Bitmap 20 import android.graphics.ColorSpace 21 import android.graphics.HardwareBufferRenderer 22 import android.graphics.RecordingCanvas 23 import android.graphics.RenderEffect 24 import android.graphics.RenderNode 25 import android.hardware.HardwareBuffer 26 import androidx.annotation.VisibleForTesting 27 import java.time.Duration 28 import java.util.concurrent.Executor 29 import java.util.concurrent.Executors 30 31 /** A wrapper that handles drawing into a [HardwareBuffer] and releasing resources. */ 32 class FrameBuffer(width: Int, height: Int, format: Int = HardwareBuffer.RGBA_8888) { 33 34 private val buffer = 35 HardwareBuffer.create( 36 width, 37 height, 38 format, 39 /* layers = */ 1, 40 // USAGE_GPU_SAMPLED_IMAGE: buffer will be read by the GPU 41 // USAGE_GPU_COLOR_OUTPUT: buffer will be written by the GPU 42 /* usage= */ HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE or 43 HardwareBuffer.USAGE_GPU_COLOR_OUTPUT 44 ) 45 private val renderer = HardwareBufferRenderer(buffer) 46 private val node = 47 RenderNode("content").also { 48 it.setPosition(0, 0, width, height) 49 renderer.setContentRoot(it) 50 } 51 52 private val executor = Executors.newFixedThreadPool(/* nThreads = */ 1) 53 @VisibleForTesting val colorSpace = ColorSpace.get(ColorSpace.Named.SRGB) 54 55 /** 56 * Recording drawing commands. 57 * 58 * @return RecordingCanvas 59 */ 60 fun beginDrawing(): RecordingCanvas { 61 return node.beginRecording() 62 } 63 64 /** Ends drawing. Must be paired with [beginDrawing]. */ 65 fun endDrawing() { 66 node.endRecording() 67 } 68 69 /** Closes the [FrameBuffer]. */ 70 fun close() { 71 buffer.close() 72 renderer.close() 73 executor.shutdown() 74 } 75 76 /** 77 * Invokes the [onImageReady] callback when the new image is acquired, which is associated with 78 * the frame buffer. 79 * 80 * @param onImageReady callback that will be called once the image is ready. 81 * @param callbackExecutor executor to use to trigger the callback. Likely to be the main 82 * executor. 83 */ 84 fun tryObtainingImage(onImageReady: (image: Bitmap) -> Unit, callbackExecutor: Executor) { 85 if (renderer.isClosed) return 86 renderer.obtainRenderRequest().setColorSpace(colorSpace).draw(executor) { result -> 87 if (result.status == HardwareBufferRenderer.RenderResult.SUCCESS) { 88 result.fence.await(Duration.ofMillis(RESULT_FENCE_TIME_OUT)) 89 if (!buffer.isClosed) { 90 Bitmap.wrapHardwareBuffer(buffer, colorSpace)?.let { 91 callbackExecutor.execute { onImageReady.invoke(it) } 92 } 93 } 94 } 95 } 96 } 97 98 /** 99 * Configure the [FrameBuffer] to apply to this RenderNode. This will apply a visual effect to 100 * the end result of the contents of this RenderNode before it is drawn into the destination. 101 * 102 * @param renderEffect to be applied to the [FrameBuffer]. Passing null clears all previously 103 * configured RenderEffects. 104 */ 105 fun setRenderEffect(renderEffect: RenderEffect?) = node.setRenderEffect(renderEffect) 106 107 companion object { 108 const val RESULT_FENCE_TIME_OUT = 3000L 109 } 110 } 111