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