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