script-astra/Android/Sdk/sources/android-35/android/graphics/HardwareBufferRenderer.java
localadmin 4380f00a78 init
2025-01-20 18:15:20 +03:00

402 lines
16 KiB
Java

/*
* 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;
/**
* <p>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.</p>
*
* <h3>Resources & lifecycle</h3>
*
* <p>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}.</p>
*
* 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.
*
* <p>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.
*
* <p>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.
*
* <p>These values are typically provided by the current theme, see
* {@link android.R.attr#spotShadowAlpha} and {@link android.R.attr#ambientShadowAlpha}.
*
* <p>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<RenderResult> renderCallback
) {
Consumer<RenderResult> wrapped = consumable -> executor.execute(
() -> renderCallback.accept(consumable));
if (!isClosed()) {
int renderWidth;
int renderHeight;
if (mTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_90
|| mTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_270) {
renderWidth = mHardwareBuffer.getHeight();
renderHeight = mHardwareBuffer.getWidth();
} else {
renderWidth = mHardwareBuffer.getWidth();
renderHeight = mHardwareBuffer.getHeight();
}
nRender(
mProxy,
mTransform,
renderWidth,
renderHeight,
mColorSpace.getNativeInstance(),
wrapped);
} else {
throw new IllegalStateException("Attempt to draw with a HardwareBufferRenderer "
+ "instance that has already been closed");
}
}
private void reset() {
mColorSpace = DEFAULT_COLORSPACE;
mTransform = SurfaceControl.BUFFER_TRANSFORM_IDENTITY;
}
/**
* Configures the color space which the content should be rendered in. This affects
* how the framework will interpret the color at each pixel. The color space provided here
* must be non-null, RGB based and leverage an ICC parametric curve. The min/max values
* of the components should not reduce the numerical range compared to the previously
* assigned color space. If left unspecified, the default color space of SRGB will be used.
*
* @param colorSpace The color space the content should be rendered in. If null is provided
* the default of SRGB will be used.
*/
@NonNull
public RenderRequest setColorSpace(@Nullable ColorSpace colorSpace) {
if (colorSpace == null) {
mColorSpace = DEFAULT_COLORSPACE;
} else {
mColorSpace = colorSpace;
}
return this;
}
/**
* Specifies a transform to be applied before content is rendered. This is useful
* for pre-rotating content for the current display orientation to increase performance
* of displaying the associated buffer. This transformation will also adjust the light
* source position for the specified rotation.
* @see SurfaceControl.Transaction#setBufferTransform(SurfaceControl, int)
*/
@NonNull
public RenderRequest setBufferTransform(
@SurfaceControl.BufferTransform int bufferTransform) {
boolean validTransform = bufferTransform == SurfaceControl.BUFFER_TRANSFORM_IDENTITY
|| bufferTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_90
|| bufferTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_180
|| bufferTransform == SurfaceControl.BUFFER_TRANSFORM_ROTATE_270;
if (validTransform) {
mTransform = bufferTransform;
} else {
throw new IllegalArgumentException("Invalid transform provided, must be one of"
+ "the SurfaceControl.BufferTransform values");
}
return this;
}
}
/**
* @hide
*/
/* package */
static native int nRender(long renderer, int transform, int width, int height, long colorSpace,
Consumer<RenderResult> callback);
private static native long nCreateRootRenderNode();
private static native void nDestroyRootRenderNode(long rootRenderNode);
private static native long nCreateHardwareBufferRenderer(HardwareBuffer buffer,
long rootRenderNode);
private static native void nSetLightGeometry(long bufferRenderer, float lightX, float lightY,
float lightZ, float radius);
private static native void nSetLightAlpha(long nativeProxy, float ambientShadowAlpha,
float spotShadowAlpha);
private static native long nGetFinalizer();
// Called by native
private static void invokeRenderCallback(
@NonNull Consumer<RenderResult> callback,
int fd,
int status
) {
callback.accept(new RenderResult(SyncFence.adopt(fd), status));
}
private static void validateAlpha(float alpha, String argumentName) {
if (!(alpha >= 0.0f && alpha <= 1.0f)) {
throw new IllegalArgumentException(argumentName + " must be a valid alpha, "
+ alpha + " is not in the range of 0.0f to 1.0f");
}
}
private static void validateFinite(float f, String argumentName) {
if (!Float.isFinite(f)) {
throw new IllegalArgumentException(argumentName + " must be finite, given=" + f);
}
}
private static void validatePositive(float f, String argumentName) {
if (!(Float.isFinite(f) && f >= 0.0f)) {
throw new IllegalArgumentException(argumentName
+ " must be a finite positive, given=" + f);
}
}
}