1 /*
2  * Copyright (C) 2022 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 android.graphics;
18 
19 import android.annotation.FloatRange;
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.graphics.ColorSpace.Named;
24 import android.hardware.HardwareBuffer;
25 import android.hardware.SyncFence;
26 import android.view.SurfaceControl;
27 
28 import libcore.util.NativeAllocationRegistry;
29 
30 import java.lang.annotation.Retention;
31 import java.lang.annotation.RetentionPolicy;
32 import java.util.concurrent.Executor;
33 import java.util.function.Consumer;
34 
35 /**
36  * <p>Creates an instance of a hardware-accelerated renderer. This is used to render a scene built
37  * from {@link RenderNode}s to an output {@link HardwareBuffer}. There can be as many
38  * HardwareBufferRenderer instances as desired.</p>
39  *
40  * <h3>Resources & lifecycle</h3>
41  *
42  * <p>All HardwareBufferRenderer and {@link HardwareRenderer} instances share a common render
43  * thread. Therefore HardwareBufferRenderer will share common resources and GPU utilization with
44  * hardware accelerated rendering initiated by the UI thread of an application.
45  * The render thread contains the GPU context & resources necessary to do GPU-accelerated
46  * rendering. As such, the first HardwareBufferRenderer created comes with the cost of also creating
47  * the associated GPU contexts, however each incremental HardwareBufferRenderer thereafter is fairly
48  * cheap. The expected usage is to have a HardwareBufferRenderer instance for every active {@link
49  * HardwareBuffer}.</p>
50  *
51  * This is useful in situations where a scene built with {@link RenderNode}s can be consumed
52  * directly by the system compositor through
53  * {@link SurfaceControl.Transaction#setBuffer(SurfaceControl, HardwareBuffer)}.
54  *
55  * HardwareBufferRenderer will never clear contents before each draw invocation so previous contents
56  * in the {@link HardwareBuffer} target will be preserved across renders.
57  */
58 public class HardwareBufferRenderer implements AutoCloseable {
59 
60     private static final ColorSpace DEFAULT_COLORSPACE = ColorSpace.get(Named.SRGB);
61 
62     private static class HardwareBufferRendererHolder {
63         public static final NativeAllocationRegistry REGISTRY =
64                 NativeAllocationRegistry.createMalloced(
65                     HardwareBufferRenderer.class.getClassLoader(), nGetFinalizer());
66     }
67 
68     private final HardwareBuffer mHardwareBuffer;
69     private final RenderRequest mRenderRequest;
70     private final RenderNode mRootNode;
71     private final Runnable mCleaner;
72 
73     private long mProxy;
74 
75     /**
76      * Creates a new instance of {@link HardwareBufferRenderer} with the provided {@link
77      * HardwareBuffer} as the output of the rendered scene.
78      */
HardwareBufferRenderer(@onNull HardwareBuffer buffer)79     public HardwareBufferRenderer(@NonNull HardwareBuffer buffer) {
80         RenderNode rootNode = RenderNode.adopt(nCreateRootRenderNode());
81         rootNode.setClipToBounds(false);
82         mProxy = nCreateHardwareBufferRenderer(buffer, rootNode.mNativeRenderNode);
83         mCleaner = HardwareBufferRendererHolder.REGISTRY.registerNativeAllocation(this, mProxy);
84         mRenderRequest = new RenderRequest();
85         mRootNode = rootNode;
86         mHardwareBuffer = buffer;
87     }
88 
89     /**
90      * Sets the content root to render. It is not necessary to call this whenever the content
91      * recording changes. Any mutations to the RenderNode content, or any of the RenderNodes
92      * contained within the content node, will be applied whenever a new {@link RenderRequest} is
93      * issued via {@link #obtainRenderRequest()} and {@link RenderRequest#draw(Executor,
94      * Consumer)}.
95      *
96      * @param content The content to set as the root RenderNode. If null the content root is removed
97      * and the renderer will draw nothing.
98      */
setContentRoot(@ullable RenderNode content)99     public void setContentRoot(@Nullable RenderNode content) {
100         RecordingCanvas canvas = mRootNode.beginRecording();
101         if (content != null) {
102             canvas.drawRenderNode(content);
103         }
104         mRootNode.endRecording();
105     }
106 
107     /**
108      * Returns a {@link RenderRequest} that can be used to render into the provided {@link
109      * HardwareBuffer}. This is used to synchronize the RenderNode content provided by {@link
110      * #setContentRoot(RenderNode)}.
111      *
112      * @return An instance of {@link RenderRequest}. The instance may be reused for every frame, so
113      * the caller should not hold onto it for longer than a single render request.
114      */
115     @NonNull
obtainRenderRequest()116     public RenderRequest obtainRenderRequest() {
117         mRenderRequest.reset();
118         return mRenderRequest;
119     }
120 
121     /**
122      * Returns if the {@link HardwareBufferRenderer} has already been closed. That is
123      * {@link HardwareBufferRenderer#close()} has been invoked.
124      * @return True if the {@link HardwareBufferRenderer} has been closed, false otherwise.
125      */
isClosed()126     public boolean isClosed() {
127         return mProxy == 0L;
128     }
129 
130     /**
131      * Releases the resources associated with this {@link HardwareBufferRenderer} instance. **Note**
132      * this does not call {@link HardwareBuffer#close()} on the provided {@link HardwareBuffer}
133      * instance
134      */
135     @Override
close()136     public void close() {
137         // Note we explicitly call this only here to clean-up potential animator state
138         // This is not done as part of the NativeAllocationRegistry as it would invoke animator
139         // callbacks on the wrong thread
140         nDestroyRootRenderNode(mRootNode.mNativeRenderNode);
141         if (mProxy != 0L) {
142             mCleaner.run();
143             mProxy = 0L;
144         }
145     }
146 
147     /**
148      * Sets the center of the light source. The light source point controls the directionality and
149      * shape of shadows rendered by RenderNode Z & elevation.
150      *
151      * <p>The light source should be setup both as part of initial configuration, and whenever
152      * the window moves to ensure the light source stays anchored in display space instead of in
153      * window space.
154      *
155      * <p>This must be set at least once along with {@link #setLightSourceAlpha(float, float)}
156      * before shadows will work.
157      *
158      * @param lightX The X position of the light source. If unsure, a reasonable default
159      * is 'displayWidth / 2f - windowLeft'.
160      * @param lightY The Y position of the light source. If unsure, a reasonable default
161      * is '0 - windowTop'
162      * @param lightZ The Z position of the light source. Must be >= 0. If unsure, a reasonable
163      * default is 600dp.
164      * @param lightRadius The radius of the light source. Smaller radius will have sharper edges,
165      * larger radius will have softer shadows. If unsure, a reasonable default is 800 dp.
166      */
setLightSourceGeometry( float lightX, float lightY, @FloatRange(from = 0f) float lightZ, @FloatRange(from = 0f) float lightRadius )167     public void setLightSourceGeometry(
168             float lightX,
169             float lightY,
170             @FloatRange(from = 0f) float lightZ,
171             @FloatRange(from = 0f) float lightRadius
172     ) {
173         validateFinite(lightX, "lightX");
174         validateFinite(lightY, "lightY");
175         validatePositive(lightZ, "lightZ");
176         validatePositive(lightRadius, "lightRadius");
177         nSetLightGeometry(mProxy, lightX, lightY, lightZ, lightRadius);
178     }
179 
180     /**
181      * Configures the ambient & spot shadow alphas. This is the alpha used when the shadow has max
182      * alpha, and ramps down from the values provided to zero.
183      *
184      * <p>These values are typically provided by the current theme, see
185      * {@link android.R.attr#spotShadowAlpha} and {@link android.R.attr#ambientShadowAlpha}.
186      *
187      * <p>This must be set at least once along with
188      * {@link #setLightSourceGeometry(float, float, float, float)} before shadows will work.
189      *
190      * @param ambientShadowAlpha The alpha for the ambient shadow. If unsure, a reasonable default
191      * is 0.039f.
192      * @param spotShadowAlpha The alpha for the spot shadow. If unsure, a reasonable default is
193      * 0.19f.
194      */
setLightSourceAlpha(@loatRangefrom = 0.0f, to = 1.0f) float ambientShadowAlpha, @FloatRange(from = 0.0f, to = 1.0f) float spotShadowAlpha)195     public void setLightSourceAlpha(@FloatRange(from = 0.0f, to = 1.0f) float ambientShadowAlpha,
196             @FloatRange(from = 0.0f, to = 1.0f) float spotShadowAlpha) {
197         validateAlpha(ambientShadowAlpha, "ambientShadowAlpha");
198         validateAlpha(spotShadowAlpha, "spotShadowAlpha");
199         nSetLightAlpha(mProxy, ambientShadowAlpha, spotShadowAlpha);
200     }
201 
202     /**
203      * Class that contains data regarding the result of the render request.
204      * Consumers are to wait on the provided {@link SyncFence} before consuming the HardwareBuffer
205      * provided to {@link HardwareBufferRenderer} as well as verify that the status returned by
206      * {@link RenderResult#getStatus()} returns {@link RenderResult#SUCCESS}.
207      */
208     public static final class RenderResult {
209 
210         /**
211          * Render request was completed successfully
212          */
213         public static final int SUCCESS = 0;
214 
215         /**
216          * Render request failed with an unknown error
217          */
218         public static final int ERROR_UNKNOWN = 1;
219 
220         /** @hide **/
221         @IntDef(value = {SUCCESS, ERROR_UNKNOWN})
222         @Retention(RetentionPolicy.SOURCE)
223         public @interface RenderResultStatus{}
224 
225         private final SyncFence mFence;
226         private final int mResultStatus;
227 
RenderResult(@onNull SyncFence fence, @RenderResultStatus int resultStatus)228         private RenderResult(@NonNull SyncFence fence, @RenderResultStatus int resultStatus) {
229             mFence = fence;
230             mResultStatus = resultStatus;
231         }
232 
233         @NonNull
getFence()234         public SyncFence getFence() {
235             return mFence;
236         }
237 
238         @RenderResultStatus
getStatus()239         public int getStatus() {
240             return mResultStatus;
241         }
242     }
243 
244     /**
245      * Sets the parameters that can be used to control a render request for a {@link
246      * HardwareBufferRenderer}. This is not thread-safe and must not be held on to for longer than a
247      * single request.
248      */
249     public final class RenderRequest {
250 
251         private ColorSpace mColorSpace = DEFAULT_COLORSPACE;
252         private int mTransform = SurfaceControl.BUFFER_TRANSFORM_IDENTITY;
253 
RenderRequest()254         private RenderRequest() { }
255 
256         /**
257          * Syncs the RenderNode tree to the render thread and requests content to be drawn. This
258          * {@link RenderRequest} instance should no longer be used after calling this method. The
259          * system internally may reuse instances of {@link RenderRequest} to reduce allocation
260          * churn.
261          *
262          * @param executor Executor used to deliver callbacks
263          * @param renderCallback Callback invoked when rendering is complete. This includes a
264          * {@link RenderResult} that provides a {@link SyncFence} that should be waited upon for
265          * completion before consuming the rendered output in the provided {@link HardwareBuffer}
266          * instance.
267          *
268          * @throws IllegalStateException if attempt to draw is made when
269          * {@link HardwareBufferRenderer#isClosed()} returns true
270          */
draw( @onNull Executor executor, @NonNull Consumer<RenderResult> renderCallback )271         public void draw(
272                 @NonNull Executor executor,
273                 @NonNull Consumer<RenderResult> renderCallback
274         ) {
275             Consumer<RenderResult> wrapped = consumable -> executor.execute(
276                     () -> renderCallback.accept(consumable));
277             if (!isClosed()) {
278                 int renderWidth;
279                 int renderHeight;
280                 if (mTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_90
281                         || mTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_270) {
282                     renderWidth = mHardwareBuffer.getHeight();
283                     renderHeight = mHardwareBuffer.getWidth();
284                 } else {
285                     renderWidth = mHardwareBuffer.getWidth();
286                     renderHeight = mHardwareBuffer.getHeight();
287                 }
288 
289                 nRender(
290                         mProxy,
291                         mTransform,
292                         renderWidth,
293                         renderHeight,
294                         mColorSpace.getNativeInstance(),
295                         wrapped);
296             } else {
297                 throw new IllegalStateException("Attempt to draw with a HardwareBufferRenderer "
298                     + "instance that has already been closed");
299             }
300         }
301 
reset()302         private void reset() {
303             mColorSpace = DEFAULT_COLORSPACE;
304             mTransform = SurfaceControl.BUFFER_TRANSFORM_IDENTITY;
305         }
306 
307         /**
308          * Configures the color space which the content should be rendered in. This affects
309          * how the framework will interpret the color at each pixel. The color space provided here
310          * must be non-null, RGB based and leverage an ICC parametric curve. The min/max values
311          * of the components should not reduce the numerical range compared to the previously
312          * assigned color space. If left unspecified, the default color space of SRGB will be used.
313          *
314          * @param colorSpace The color space the content should be rendered in. If null is provided
315          * the default of SRGB will be used.
316          */
317         @NonNull
setColorSpace(@ullable ColorSpace colorSpace)318         public RenderRequest setColorSpace(@Nullable ColorSpace colorSpace) {
319             if (colorSpace == null) {
320                 mColorSpace = DEFAULT_COLORSPACE;
321             } else {
322                 mColorSpace = colorSpace;
323             }
324             return this;
325         }
326 
327         /**
328          * Specifies a transform to be applied before content is rendered. This is useful
329          * for pre-rotating content for the current display orientation to increase performance
330          * of displaying the associated buffer. This transformation will also adjust the light
331          * source position for the specified rotation.
332          * @see SurfaceControl.Transaction#setBufferTransform(SurfaceControl, int)
333          */
334         @NonNull
setBufferTransform( @urfaceControl.BufferTransform int bufferTransform)335         public RenderRequest setBufferTransform(
336                 @SurfaceControl.BufferTransform int bufferTransform) {
337             boolean validTransform = bufferTransform == SurfaceControl.BUFFER_TRANSFORM_IDENTITY
338                     || bufferTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_90
339                     || bufferTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_180
340                     || bufferTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_270;
341             if (validTransform) {
342                 mTransform = bufferTransform;
343             } else {
344                 throw new IllegalArgumentException("Invalid transform provided, must be one of"
345                     + "the SurfaceControl.BufferTransform values");
346             }
347             return this;
348         }
349     }
350 
351     /**
352      * @hide
353      */
354     /* package */
nRender(long renderer, int transform, int width, int height, long colorSpace, Consumer<RenderResult> callback)355     static native int nRender(long renderer, int transform, int width, int height, long colorSpace,
356             Consumer<RenderResult> callback);
357 
nCreateRootRenderNode()358     private static native long nCreateRootRenderNode();
359 
nDestroyRootRenderNode(long rootRenderNode)360     private static native void nDestroyRootRenderNode(long rootRenderNode);
361 
nCreateHardwareBufferRenderer(HardwareBuffer buffer, long rootRenderNode)362     private static native long nCreateHardwareBufferRenderer(HardwareBuffer buffer,
363             long rootRenderNode);
364 
nSetLightGeometry(long bufferRenderer, float lightX, float lightY, float lightZ, float radius)365     private static native void nSetLightGeometry(long bufferRenderer, float lightX, float lightY,
366             float lightZ, float radius);
367 
nSetLightAlpha(long nativeProxy, float ambientShadowAlpha, float spotShadowAlpha)368     private static native void nSetLightAlpha(long nativeProxy, float ambientShadowAlpha,
369             float spotShadowAlpha);
370 
nGetFinalizer()371     private static native long nGetFinalizer();
372 
373     // Called by native
invokeRenderCallback( @onNull Consumer<RenderResult> callback, int fd, int status )374     private static void invokeRenderCallback(
375             @NonNull Consumer<RenderResult> callback,
376             int fd,
377             int status
378     ) {
379         callback.accept(new RenderResult(SyncFence.adopt(fd), status));
380     }
381 
validateAlpha(float alpha, String argumentName)382     private static void validateAlpha(float alpha, String argumentName) {
383         if (!(alpha >= 0.0f && alpha <= 1.0f)) {
384             throw new IllegalArgumentException(argumentName + " must be a valid alpha, "
385                 + alpha + " is not in the range of 0.0f to 1.0f");
386         }
387     }
388 
validateFinite(float f, String argumentName)389     private static void validateFinite(float f, String argumentName) {
390         if (!Float.isFinite(f)) {
391             throw new IllegalArgumentException(argumentName + " must be finite, given=" + f);
392         }
393     }
394 
validatePositive(float f, String argumentName)395     private static void validatePositive(float f, String argumentName) {
396         if (!(Float.isFinite(f) && f >= 0.0f)) {
397             throw new IllegalArgumentException(argumentName
398                 + " must be a finite positive, given=" + f);
399         }
400     }
401 }
402