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