/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.graphics; import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.ColorSpace.Named; import android.hardware.HardwareBuffer; import android.hardware.SyncFence; import android.view.SurfaceControl; import libcore.util.NativeAllocationRegistry; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.concurrent.Executor; import java.util.function.Consumer; /** *
Creates an instance of a hardware-accelerated renderer. This is used to render a scene built * from {@link RenderNode}s to an output {@link HardwareBuffer}. There can be as many * HardwareBufferRenderer instances as desired.
* *All HardwareBufferRenderer and {@link HardwareRenderer} instances share a common render * thread. Therefore HardwareBufferRenderer will share common resources and GPU utilization with * hardware accelerated rendering initiated by the UI thread of an application. * The render thread contains the GPU context & resources necessary to do GPU-accelerated * rendering. As such, the first HardwareBufferRenderer created comes with the cost of also creating * the associated GPU contexts, however each incremental HardwareBufferRenderer thereafter is fairly * cheap. The expected usage is to have a HardwareBufferRenderer instance for every active {@link * HardwareBuffer}.
* * This is useful in situations where a scene built with {@link RenderNode}s can be consumed * directly by the system compositor through * {@link SurfaceControl.Transaction#setBuffer(SurfaceControl, HardwareBuffer)}. * * HardwareBufferRenderer will never clear contents before each draw invocation so previous contents * in the {@link HardwareBuffer} target will be preserved across renders. */ public class HardwareBufferRenderer implements AutoCloseable { private static final ColorSpace DEFAULT_COLORSPACE = ColorSpace.get(Named.SRGB); private static class HardwareBufferRendererHolder { public static final NativeAllocationRegistry REGISTRY = NativeAllocationRegistry.createMalloced( HardwareBufferRenderer.class.getClassLoader(), nGetFinalizer()); } private final HardwareBuffer mHardwareBuffer; private final RenderRequest mRenderRequest; private final RenderNode mRootNode; private final Runnable mCleaner; private long mProxy; /** * Creates a new instance of {@link HardwareBufferRenderer} with the provided {@link * HardwareBuffer} as the output of the rendered scene. */ public HardwareBufferRenderer(@NonNull HardwareBuffer buffer) { RenderNode rootNode = RenderNode.adopt(nCreateRootRenderNode()); rootNode.setClipToBounds(false); mProxy = nCreateHardwareBufferRenderer(buffer, rootNode.mNativeRenderNode); mCleaner = HardwareBufferRendererHolder.REGISTRY.registerNativeAllocation(this, mProxy); mRenderRequest = new RenderRequest(); mRootNode = rootNode; mHardwareBuffer = buffer; } /** * Sets the content root to render. It is not necessary to call this whenever the content * recording changes. Any mutations to the RenderNode content, or any of the RenderNodes * contained within the content node, will be applied whenever a new {@link RenderRequest} is * issued via {@link #obtainRenderRequest()} and {@link RenderRequest#draw(Executor, * Consumer)}. * * @param content The content to set as the root RenderNode. If null the content root is removed * and the renderer will draw nothing. */ public void setContentRoot(@Nullable RenderNode content) { RecordingCanvas canvas = mRootNode.beginRecording(); if (content != null) { canvas.drawRenderNode(content); } mRootNode.endRecording(); } /** * Returns a {@link RenderRequest} that can be used to render into the provided {@link * HardwareBuffer}. This is used to synchronize the RenderNode content provided by {@link * #setContentRoot(RenderNode)}. * * @return An instance of {@link RenderRequest}. The instance may be reused for every frame, so * the caller should not hold onto it for longer than a single render request. */ @NonNull public RenderRequest obtainRenderRequest() { mRenderRequest.reset(); return mRenderRequest; } /** * Returns if the {@link HardwareBufferRenderer} has already been closed. That is * {@link HardwareBufferRenderer#close()} has been invoked. * @return True if the {@link HardwareBufferRenderer} has been closed, false otherwise. */ public boolean isClosed() { return mProxy == 0L; } /** * Releases the resources associated with this {@link HardwareBufferRenderer} instance. **Note** * this does not call {@link HardwareBuffer#close()} on the provided {@link HardwareBuffer} * instance */ @Override public void close() { // Note we explicitly call this only here to clean-up potential animator state // This is not done as part of the NativeAllocationRegistry as it would invoke animator // callbacks on the wrong thread nDestroyRootRenderNode(mRootNode.mNativeRenderNode); if (mProxy != 0L) { mCleaner.run(); mProxy = 0L; } } /** * Sets the center of the light source. The light source point controls the directionality and * shape of shadows rendered by RenderNode Z & elevation. * *The light source should be setup both as part of initial configuration, and whenever * the window moves to ensure the light source stays anchored in display space instead of in * window space. * *
This must be set at least once along with {@link #setLightSourceAlpha(float, float)} * before shadows will work. * * @param lightX The X position of the light source. If unsure, a reasonable default * is 'displayWidth / 2f - windowLeft'. * @param lightY The Y position of the light source. If unsure, a reasonable default * is '0 - windowTop' * @param lightZ The Z position of the light source. Must be >= 0. If unsure, a reasonable * default is 600dp. * @param lightRadius The radius of the light source. Smaller radius will have sharper edges, * larger radius will have softer shadows. If unsure, a reasonable default is 800 dp. */ public void setLightSourceGeometry( float lightX, float lightY, @FloatRange(from = 0f) float lightZ, @FloatRange(from = 0f) float lightRadius ) { validateFinite(lightX, "lightX"); validateFinite(lightY, "lightY"); validatePositive(lightZ, "lightZ"); validatePositive(lightRadius, "lightRadius"); nSetLightGeometry(mProxy, lightX, lightY, lightZ, lightRadius); } /** * Configures the ambient & spot shadow alphas. This is the alpha used when the shadow has max * alpha, and ramps down from the values provided to zero. * *
These values are typically provided by the current theme, see * {@link android.R.attr#spotShadowAlpha} and {@link android.R.attr#ambientShadowAlpha}. * *
This must be set at least once along with
* {@link #setLightSourceGeometry(float, float, float, float)} before shadows will work.
*
* @param ambientShadowAlpha The alpha for the ambient shadow. If unsure, a reasonable default
* is 0.039f.
* @param spotShadowAlpha The alpha for the spot shadow. If unsure, a reasonable default is
* 0.19f.
*/
public void setLightSourceAlpha(@FloatRange(from = 0.0f, to = 1.0f) float ambientShadowAlpha,
@FloatRange(from = 0.0f, to = 1.0f) float spotShadowAlpha) {
validateAlpha(ambientShadowAlpha, "ambientShadowAlpha");
validateAlpha(spotShadowAlpha, "spotShadowAlpha");
nSetLightAlpha(mProxy, ambientShadowAlpha, spotShadowAlpha);
}
/**
* Class that contains data regarding the result of the render request.
* Consumers are to wait on the provided {@link SyncFence} before consuming the HardwareBuffer
* provided to {@link HardwareBufferRenderer} as well as verify that the status returned by
* {@link RenderResult#getStatus()} returns {@link RenderResult#SUCCESS}.
*/
public static final class RenderResult {
/**
* Render request was completed successfully
*/
public static final int SUCCESS = 0;
/**
* Render request failed with an unknown error
*/
public static final int ERROR_UNKNOWN = 1;
/** @hide **/
@IntDef(value = {SUCCESS, ERROR_UNKNOWN})
@Retention(RetentionPolicy.SOURCE)
public @interface RenderResultStatus{}
private final SyncFence mFence;
private final int mResultStatus;
private RenderResult(@NonNull SyncFence fence, @RenderResultStatus int resultStatus) {
mFence = fence;
mResultStatus = resultStatus;
}
@NonNull
public SyncFence getFence() {
return mFence;
}
@RenderResultStatus
public int getStatus() {
return mResultStatus;
}
}
/**
* Sets the parameters that can be used to control a render request for a {@link
* HardwareBufferRenderer}. This is not thread-safe and must not be held on to for longer than a
* single request.
*/
public final class RenderRequest {
private ColorSpace mColorSpace = DEFAULT_COLORSPACE;
private int mTransform = SurfaceControl.BUFFER_TRANSFORM_IDENTITY;
private RenderRequest() { }
/**
* Syncs the RenderNode tree to the render thread and requests content to be drawn. This
* {@link RenderRequest} instance should no longer be used after calling this method. The
* system internally may reuse instances of {@link RenderRequest} to reduce allocation
* churn.
*
* @param executor Executor used to deliver callbacks
* @param renderCallback Callback invoked when rendering is complete. This includes a
* {@link RenderResult} that provides a {@link SyncFence} that should be waited upon for
* completion before consuming the rendered output in the provided {@link HardwareBuffer}
* instance.
*
* @throws IllegalStateException if attempt to draw is made when
* {@link HardwareBufferRenderer#isClosed()} returns true
*/
public void draw(
@NonNull Executor executor,
@NonNull Consumer