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