Make HardwareRenderer public API
Bug: 123661129 Test: HardwareRenderer CTS tests Change-Id: Ic7ff69c9489d00e3f525eec761a84d06cf81be7a
This commit is contained in:
@@ -14099,6 +14099,34 @@ package android.graphics {
|
||||
ctor @Deprecated public EmbossMaskFilter(float[], float, float, float);
|
||||
}
|
||||
|
||||
public class HardwareRenderer {
|
||||
ctor public HardwareRenderer();
|
||||
method public void clearContent();
|
||||
method public android.graphics.HardwareRenderer.FrameRenderRequest createRenderRequest();
|
||||
method public void destroy();
|
||||
method public boolean isOpaque();
|
||||
method public void notifyFramePending();
|
||||
method public void setContentRoot(@Nullable android.graphics.RenderNode);
|
||||
method public void setLightSourceAlpha(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float);
|
||||
method public void setLightSourceGeometry(float, float, float, float);
|
||||
method public void setName(String);
|
||||
method public void setOpaque(boolean);
|
||||
method public void setStopped(boolean);
|
||||
method public void setSurface(@Nullable android.view.Surface);
|
||||
field public static final int SYNC_CONTEXT_IS_STOPPED = 4; // 0x4
|
||||
field public static final int SYNC_FRAME_DROPPED = 8; // 0x8
|
||||
field public static final int SYNC_LOST_SURFACE_REWARD_IF_FOUND = 2; // 0x2
|
||||
field public static final int SYNC_OK = 0; // 0x0
|
||||
field public static final int SYNC_REDRAW_REQUESTED = 1; // 0x1
|
||||
}
|
||||
|
||||
public final class HardwareRenderer.FrameRenderRequest {
|
||||
method public android.graphics.HardwareRenderer.FrameRenderRequest setFrameCommitCallback(@NonNull java.util.concurrent.Executor, @NonNull Runnable);
|
||||
method public android.graphics.HardwareRenderer.FrameRenderRequest setVsyncTime(long);
|
||||
method public android.graphics.HardwareRenderer.FrameRenderRequest setWaitForPresent(boolean);
|
||||
method public int syncAndDraw();
|
||||
}
|
||||
|
||||
public final class ImageDecoder implements java.lang.AutoCloseable {
|
||||
method public void close();
|
||||
method @AnyThread @NonNull public static android.graphics.ImageDecoder.Source createSource(@NonNull android.content.res.Resources, int);
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.graphics.Paint;
|
||||
import android.graphics.RecordingCanvas;
|
||||
import android.graphics.RenderNode;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import com.android.internal.util.VirtualRefBasePtr;
|
||||
@@ -84,6 +85,7 @@ public class RenderNodeAnimator extends Animator {
|
||||
|
||||
private VirtualRefBasePtr mNativePtr;
|
||||
|
||||
private Handler mHandler;
|
||||
private RenderNode mTarget;
|
||||
private View mViewTarget;
|
||||
private int mRenderProperty = -1;
|
||||
@@ -222,6 +224,9 @@ public class RenderNodeAnimator extends Animator {
|
||||
private void moveToRunningState() {
|
||||
mState = STATE_RUNNING;
|
||||
if (mNativePtr != null) {
|
||||
if (mHandler == null) {
|
||||
mHandler = new Handler();
|
||||
}
|
||||
nStart(mNativePtr.get());
|
||||
}
|
||||
notifyStartListeners();
|
||||
@@ -497,7 +502,7 @@ public class RenderNodeAnimator extends Animator {
|
||||
// Called by native
|
||||
@UnsupportedAppUsage
|
||||
private static void callOnFinished(RenderNodeAnimator animator) {
|
||||
animator.onFinished();
|
||||
animator.mHandler.post(animator::onFinished);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -453,7 +453,7 @@ public final class ThreadedRenderer extends HardwareRenderer {
|
||||
*/
|
||||
void destroyHardwareResources(View view) {
|
||||
destroyResources(view);
|
||||
destroyHardwareResources();
|
||||
clearContent();
|
||||
}
|
||||
|
||||
private static void destroyResources(View view) {
|
||||
@@ -735,7 +735,9 @@ public final class ThreadedRenderer extends HardwareRenderer {
|
||||
if (callback != null) {
|
||||
setFrameCallback(callback);
|
||||
}
|
||||
syncAndDrawFrame(vsync);
|
||||
createRenderRequest()
|
||||
.setVsyncTime(vsync)
|
||||
.syncAndDraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3404,21 +3404,25 @@ public final class ViewRootImpl implements ViewParent,
|
||||
.captureFrameCommitCallbacks();
|
||||
if (mReportNextDraw) {
|
||||
usingAsyncReport = true;
|
||||
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback((long frameNr) -> {
|
||||
// TODO: Use the frame number
|
||||
pendingDrawFinished();
|
||||
if (commitCallbacks != null) {
|
||||
for (int i = 0; i < commitCallbacks.size(); i++) {
|
||||
commitCallbacks.get(i).run();
|
||||
}
|
||||
}
|
||||
});
|
||||
final Handler handler = mAttachInfo.mHandler;
|
||||
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback((long frameNr) ->
|
||||
handler.post(() -> {
|
||||
// TODO: Use the frame number
|
||||
pendingDrawFinished();
|
||||
if (commitCallbacks != null) {
|
||||
for (int i = 0; i < commitCallbacks.size(); i++) {
|
||||
commitCallbacks.get(i).run();
|
||||
}
|
||||
}
|
||||
}));
|
||||
} else if (commitCallbacks != null && commitCallbacks.size() > 0) {
|
||||
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback((long frameNr) -> {
|
||||
for (int i = 0; i < commitCallbacks.size(); i++) {
|
||||
commitCallbacks.get(i).run();
|
||||
}
|
||||
});
|
||||
final Handler handler = mAttachInfo.mHandler;
|
||||
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback((long frameNr) ->
|
||||
handler.post(() -> {
|
||||
for (int i = 0; i < commitCallbacks.size(); i++) {
|
||||
commitCallbacks.get(i).run();
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
|
||||
#include <private/EGL/cache.h>
|
||||
|
||||
#include <utils/Looper.h>
|
||||
#include <utils/RefBase.h>
|
||||
#include <utils/StrongPointer.h>
|
||||
#include <utils/Timers.h>
|
||||
@@ -144,52 +143,22 @@ private:
|
||||
uint32_t mRequestId;
|
||||
};
|
||||
|
||||
class RenderingException : public MessageHandler {
|
||||
class FrameCompleteWrapper : public LightRefBase<FrameCompleteWrapper> {
|
||||
public:
|
||||
RenderingException(JavaVM* vm, const std::string& message)
|
||||
: mVm(vm)
|
||||
, mMessage(message) {
|
||||
}
|
||||
|
||||
virtual void handleMessage(const Message&) {
|
||||
throwException(mVm, mMessage);
|
||||
}
|
||||
|
||||
static void throwException(JavaVM* vm, const std::string& message) {
|
||||
JNIEnv* env = getenv(vm);
|
||||
jniThrowException(env, "java/lang/IllegalStateException", message.c_str());
|
||||
}
|
||||
|
||||
private:
|
||||
JavaVM* mVm;
|
||||
std::string mMessage;
|
||||
};
|
||||
|
||||
class FrameCompleteWrapper : public MessageHandler {
|
||||
public:
|
||||
FrameCompleteWrapper(JNIEnv* env, jobject jobject) {
|
||||
mLooper = Looper::getForThread();
|
||||
LOG_ALWAYS_FATAL_IF(!mLooper.get(), "Must create runnable on a Looper thread!");
|
||||
explicit FrameCompleteWrapper(JNIEnv* env, jobject jobject) {
|
||||
env->GetJavaVM(&mVm);
|
||||
mObject = env->NewGlobalRef(jobject);
|
||||
LOG_ALWAYS_FATAL_IF(!mObject, "Failed to make global ref");
|
||||
}
|
||||
|
||||
virtual ~FrameCompleteWrapper() {
|
||||
~FrameCompleteWrapper() {
|
||||
releaseObject();
|
||||
}
|
||||
|
||||
void postFrameComplete(int64_t frameNr) {
|
||||
void onFrameComplete(int64_t frameNr) {
|
||||
if (mObject) {
|
||||
mFrameNr = frameNr;
|
||||
mLooper->sendMessage(this, 0);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void handleMessage(const Message&) {
|
||||
if (mObject) {
|
||||
ATRACE_FORMAT("frameComplete %" PRId64, mFrameNr);
|
||||
getenv(mVm)->CallVoidMethod(mObject, gFrameCompleteCallback.onFrameComplete, mFrameNr);
|
||||
ATRACE_FORMAT("frameComplete %" PRId64, frameNr);
|
||||
getenv(mVm)->CallVoidMethod(mObject, gFrameCompleteCallback.onFrameComplete, frameNr);
|
||||
releaseObject();
|
||||
}
|
||||
}
|
||||
@@ -197,8 +166,6 @@ public:
|
||||
private:
|
||||
JavaVM* mVm;
|
||||
jobject mObject;
|
||||
sp<Looper> mLooper;
|
||||
int64_t mFrameNr = -1;
|
||||
|
||||
void releaseObject() {
|
||||
if (mObject) {
|
||||
@@ -211,16 +178,14 @@ private:
|
||||
class RootRenderNode : public RenderNode, ErrorHandler {
|
||||
public:
|
||||
explicit RootRenderNode(JNIEnv* env) : RenderNode() {
|
||||
mLooper = Looper::getForThread();
|
||||
LOG_ALWAYS_FATAL_IF(!mLooper.get(),
|
||||
"Must create RootRenderNode on a thread with a looper!");
|
||||
env->GetJavaVM(&mVm);
|
||||
}
|
||||
|
||||
virtual ~RootRenderNode() {}
|
||||
|
||||
virtual void onError(const std::string& message) override {
|
||||
mLooper->sendMessage(new RenderingException(mVm, message), 0);
|
||||
JNIEnv* env = getenv(mVm);
|
||||
jniThrowException(env, "java/lang/IllegalStateException", message.c_str());
|
||||
}
|
||||
|
||||
virtual void prepareTree(TreeInfo& info) override {
|
||||
@@ -249,14 +214,6 @@ public:
|
||||
info.errorHandler = nullptr;
|
||||
}
|
||||
|
||||
void sendMessage(const sp<MessageHandler>& handler) {
|
||||
mLooper->sendMessage(handler, 0);
|
||||
}
|
||||
|
||||
void sendMessageDelayed(const sp<MessageHandler>& handler, nsecs_t delayInMs) {
|
||||
mLooper->sendMessageDelayed(ms2ns(delayInMs), handler, 0);
|
||||
}
|
||||
|
||||
void attachAnimatingNode(RenderNode* animatingNode) {
|
||||
mPendingAnimatingRenderNodes.push_back(animatingNode);
|
||||
}
|
||||
@@ -404,7 +361,6 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
sp<Looper> mLooper;
|
||||
JavaVM* mVm;
|
||||
std::vector< sp<RenderNode> > mPendingAnimatingRenderNodes;
|
||||
std::set< sp<PropertyValuesAnimatorSet> > mPendingVectorDrawableAnimators;
|
||||
@@ -435,7 +391,9 @@ private:
|
||||
// the onFinished callback will then be ignored.
|
||||
sp<FinishAndInvokeListener> message
|
||||
= new FinishAndInvokeListener(anim);
|
||||
sendMessageDelayed(message, remainingTimeInMs);
|
||||
auto looper = Looper::getForThread();
|
||||
LOG_ALWAYS_FATAL_IF(looper == nullptr, "Not on a looper thread?");
|
||||
looper->sendMessageDelayed(ms2ns(remainingTimeInMs), message, 0);
|
||||
anim->clearOneShotListener();
|
||||
}
|
||||
}
|
||||
@@ -463,7 +421,6 @@ public:
|
||||
virtual void runRemainingAnimations(TreeInfo& info) {
|
||||
AnimationContext::runRemainingAnimations(info);
|
||||
mRootNode->runVectorDrawableAnimators(this, info);
|
||||
postOnFinishedEvents();
|
||||
}
|
||||
|
||||
virtual void pauseAnimators() override {
|
||||
@@ -471,27 +428,16 @@ public:
|
||||
}
|
||||
|
||||
virtual void callOnFinished(BaseRenderNodeAnimator* animator, AnimationListener* listener) {
|
||||
OnFinishedEvent event(animator, listener);
|
||||
mOnFinishedEvents.push_back(event);
|
||||
listener->onAnimationFinished(animator);
|
||||
}
|
||||
|
||||
virtual void destroy() {
|
||||
AnimationContext::destroy();
|
||||
mRootNode->detachAnimators();
|
||||
postOnFinishedEvents();
|
||||
}
|
||||
|
||||
private:
|
||||
sp<RootRenderNode> mRootNode;
|
||||
std::vector<OnFinishedEvent> mOnFinishedEvents;
|
||||
|
||||
void postOnFinishedEvents() {
|
||||
if (mOnFinishedEvents.size()) {
|
||||
sp<InvokeAnimationListeners> message
|
||||
= new InvokeAnimationListeners(mOnFinishedEvents);
|
||||
mRootNode->sendMessage(message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class ContextFactoryImpl : public IContextFactory {
|
||||
@@ -958,7 +904,7 @@ static void android_view_ThreadedRenderer_setFrameCompleteCallback(JNIEnv* env,
|
||||
} else {
|
||||
sp<FrameCompleteWrapper> wrapper = new FrameCompleteWrapper{env, callback};
|
||||
proxy->setFrameCompleteCallback([wrapper](int64_t frameNr) {
|
||||
wrapper->postFrameComplete(frameNr);
|
||||
wrapper->onFrameComplete(frameNr);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import android.annotation.FloatRange;
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.TestApi;
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.os.IBinder;
|
||||
@@ -28,13 +27,16 @@ import android.os.ParcelFileDescriptor;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.util.Log;
|
||||
import android.util.TimeUtils;
|
||||
import android.view.FrameMetricsObserver;
|
||||
import android.view.IGraphicsStats;
|
||||
import android.view.IGraphicsStatsCallback;
|
||||
import android.view.NativeVectorDrawableAnimator;
|
||||
import android.view.PixelCopy;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.TextureLayer;
|
||||
import android.view.animation.AnimationUtils;
|
||||
|
||||
import com.android.internal.util.VirtualRefBasePtr;
|
||||
|
||||
@@ -42,6 +44,7 @@ import java.io.File;
|
||||
import java.io.FileDescriptor;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import sun.misc.Cleaner;
|
||||
|
||||
@@ -50,13 +53,8 @@ import sun.misc.Cleaner;
|
||||
* from {@link RenderNode}'s to an output {@link android.view.Surface}. There can be as many
|
||||
* HardwareRenderer instances as desired.</p>
|
||||
*
|
||||
* <h3>Threading</h3>
|
||||
* <p>HardwareRenderer is not thread safe. An instance of a HardwareRenderer must only be
|
||||
* created & used from a single thread. It does not matter what thread is used, however
|
||||
* it must have a {@link android.os.Looper}. Multiple instances do not have to share the same
|
||||
* thread, although they can.</p>
|
||||
*
|
||||
* <h3>Resources & lifecycle</h3>
|
||||
*
|
||||
* <p>All HardwareRenderer instances share a common render thread. The render thread contains
|
||||
* the GPU context & resources necessary to do GPU-accelerated rendering. As such, the first
|
||||
* HardwareRenderer created comes with the cost of also creating the associated GPU contexts,
|
||||
@@ -64,6 +62,7 @@ import sun.misc.Cleaner;
|
||||
* is to have a HardwareRenderer instance for every active {@link Surface}. For example
|
||||
* when an Activity shows a Dialog the system internally will use 2 hardware renderers, both
|
||||
* of which may be drawing at the same time.</p>
|
||||
*
|
||||
* <p>NOTE: Due to the shared, cooperative nature of the render thread it is critical that
|
||||
* any {@link Surface} used must have a prompt, reliable consuming side. System-provided
|
||||
* consumers such as {@link android.view.SurfaceView},
|
||||
@@ -73,8 +72,6 @@ import sun.misc.Cleaner;
|
||||
* it is the app's responsibility to ensure that they consume updates promptly and rapidly.
|
||||
* Failure to do so will cause the render thread to stall on that surface, blocking all
|
||||
* HardwareRenderer instances.</p>
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class HardwareRenderer {
|
||||
private static final String LOG_TAG = "HardwareRenderer";
|
||||
@@ -89,18 +86,18 @@ public class HardwareRenderer {
|
||||
* The renderer is requesting a redraw. This can occur if there's an animation that's running
|
||||
* in the RenderNode tree and the hardware renderer is unable to self-animate.
|
||||
*
|
||||
* If this is returned from syncAndDrawFrame the expectation is that syncAndDrawFrame
|
||||
* <p>If this is returned from syncAndDraw the expectation is that syncAndDraw
|
||||
* will be called again on the next vsync signal.
|
||||
*/
|
||||
public static final int SYNC_REDRAW_REQUESTED = 1 << 0;
|
||||
|
||||
/**
|
||||
* The hardware renderer no longer has a valid {@link android.view.Surface} to render to.
|
||||
* This can happen if {@link Surface#destroy()} was called. The user should no longer
|
||||
* attempt to call syncAndDrawFrame until a new surface has been provided by calling
|
||||
* This can happen if {@link Surface#release()} was called. The user should no longer
|
||||
* attempt to call syncAndDraw until a new surface has been provided by calling
|
||||
* setSurface.
|
||||
*
|
||||
* Spoiler: the reward is GPU-accelerated drawing, better find that Surface!
|
||||
* <p>Spoiler: the reward is GPU-accelerated drawing, better find that Surface!
|
||||
*/
|
||||
public static final int SYNC_LOST_SURFACE_REWARD_IF_FOUND = 1 << 1;
|
||||
|
||||
@@ -119,6 +116,7 @@ public class HardwareRenderer {
|
||||
*/
|
||||
public static final int SYNC_FRAME_DROPPED = 1 << 3;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(value = {
|
||||
SYNC_OK, SYNC_REDRAW_REQUESTED, SYNC_LOST_SURFACE_REWARD_IF_FOUND,
|
||||
SYNC_CONTEXT_IS_STOPPED, SYNC_FRAME_DROPPED})
|
||||
@@ -153,7 +151,6 @@ public class HardwareRenderer {
|
||||
protected RenderNode mRootNode;
|
||||
private boolean mOpaque = true;
|
||||
private boolean mForceDark = false;
|
||||
private FrameInfo mScratchInfo;
|
||||
private boolean mIsWideGamut = false;
|
||||
|
||||
/**
|
||||
@@ -175,14 +172,14 @@ public class HardwareRenderer {
|
||||
* Destroys the rendering context of this HardwareRenderer. This destroys the resources
|
||||
* associated with this renderer and releases the currently set {@link Surface}.
|
||||
*
|
||||
* The renderer may be restored from this state by setting a new {@link Surface}, setting
|
||||
* <p>The renderer may be restored from this state by setting a new {@link Surface}, setting
|
||||
* new rendering content with {@link #setContentRoot(RenderNode)}, and resuming
|
||||
* rendering with {@link #syncAndDrawFrame(long)}.
|
||||
* rendering by issuing a new {@link FrameRenderRequest}.
|
||||
*
|
||||
* It is suggested to call this in response to callbacks such as
|
||||
* <p>It is suggested to call this in response to callbacks such as
|
||||
* {@link android.view.SurfaceHolder.Callback#surfaceDestroyed(SurfaceHolder)}.
|
||||
*
|
||||
* Note that if there are any outstanding frame commit callbacks they may end up never being
|
||||
* <p>Note that if there are any outstanding frame commit callbacks they may never being
|
||||
* invoked if the frame was deferred to a later vsync.
|
||||
*/
|
||||
public void destroy() {
|
||||
@@ -204,14 +201,14 @@ public class HardwareRenderer {
|
||||
* Sets the center of the light source. The light source point controls the directionality
|
||||
* and shape of shadows rendered by RenderNode Z & elevation.
|
||||
*
|
||||
* The platform's recommendation is to set lightX to 'displayWidth / 2f - windowLeft', set
|
||||
* <p>The platform's recommendation is to set lightX to 'displayWidth / 2f - windowLeft', set
|
||||
* lightY to 0 - windowTop, lightZ set to 600dp, and lightRadius to 800dp.
|
||||
*
|
||||
* The light source should be setup both as part of initial configuration, and whenever
|
||||
* <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.
|
||||
*
|
||||
* This must be set at least once along with {@link #setLightSourceAlpha(float, float)}
|
||||
* <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
|
||||
@@ -233,10 +230,10 @@ public class HardwareRenderer {
|
||||
* 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
|
||||
* <p>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
|
||||
* <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
|
||||
@@ -254,8 +251,8 @@ public class HardwareRenderer {
|
||||
/**
|
||||
* 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 RenderNode's
|
||||
* contained within the content node, will be applied whenever {@link #syncAndDrawFrame(long)}
|
||||
* is called.
|
||||
* contained within the content node, will be applied whenever a new {@link FrameRenderRequest}
|
||||
* is issued via {@link #createRenderRequest()} and {@link FrameRenderRequest#syncAndDraw()}.
|
||||
*
|
||||
* @param content The content to set as the root RenderNode. If null the content root is removed
|
||||
* and the renderer will draw nothing.
|
||||
@@ -294,6 +291,126 @@ public class HardwareRenderer {
|
||||
nSetSurface(mNativeProxy, surface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the parameters that can be used to control a render request for a
|
||||
* {@link HardwareRenderer}. This is not thread-safe and must not be held on to for longer
|
||||
* than a single frame request.
|
||||
*/
|
||||
public final class FrameRenderRequest {
|
||||
private FrameInfo mFrameInfo = new FrameInfo();
|
||||
private boolean mWaitForPresent;
|
||||
|
||||
private FrameRenderRequest() { }
|
||||
|
||||
private void reset() {
|
||||
mWaitForPresent = false;
|
||||
// Default to the animation time which, if choreographer is in play, will default to the
|
||||
// current vsync time. Otherwise it will be 'now'.
|
||||
mRenderRequest.setVsyncTime(
|
||||
AnimationUtils.currentAnimationTimeMillis() * TimeUtils.NANOS_PER_MS);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void setFrameInfo(FrameInfo info) {
|
||||
System.arraycopy(info.frameInfo, 0, mFrameInfo.frameInfo, 0, info.frameInfo.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the vsync time that represents the start point of this frame. Typically this
|
||||
* comes from {@link android.view.Choreographer.FrameCallback}. Other compatible time
|
||||
* sources include {@link System#nanoTime()}, however if the result is being displayed
|
||||
* on-screen then using {@link android.view.Choreographer} is strongly recommended to
|
||||
* ensure smooth animations.
|
||||
*
|
||||
* <p>If the clock source is not from a CLOCK_MONOTONIC source then any animations driven
|
||||
* directly by RenderThread will not be synchronized properly with the current frame.
|
||||
*
|
||||
* @param vsyncTime The vsync timestamp for this frame. The timestamp is in nanoseconds
|
||||
* and should come from a CLOCK_MONOTONIC source.
|
||||
*
|
||||
* @return this instance
|
||||
*/
|
||||
public FrameRenderRequest setVsyncTime(long vsyncTime) {
|
||||
mFrameInfo.setVsync(vsyncTime, vsyncTime);
|
||||
mFrameInfo.addFlags(FrameInfo.FLAG_SURFACE_CANVAS);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a frame commit callback. This callback will be invoked when the current rendering
|
||||
* content has been rendered into a frame and submitted to the swap chain. The frame may
|
||||
* not currently be visible on the display when this is invoked, but it has been submitted.
|
||||
* This callback is useful in combination with {@link PixelCopy} to capture the current
|
||||
* rendered content of the UI reliably.
|
||||
*
|
||||
* @param executor The executor to run the callback on. It is strongly recommended that
|
||||
* this executor post to a different thread, as the calling thread is
|
||||
* highly sensitive to being blocked.
|
||||
* @param frameCommitCallback The callback to invoke when the frame content has been drawn.
|
||||
* Will be invoked on the given {@link Executor}.
|
||||
*
|
||||
* @return this instance
|
||||
*/
|
||||
public FrameRenderRequest setFrameCommitCallback(@NonNull Executor executor,
|
||||
@NonNull Runnable frameCommitCallback) {
|
||||
setFrameCompleteCallback(frameNr -> executor.execute(frameCommitCallback));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not {@link #syncAndDraw()} should block until the frame has been
|
||||
* presented. If this is true and {@link #syncAndDraw()} does not return
|
||||
* {@link #SYNC_FRAME_DROPPED} or an error then when {@link #syncAndDraw()} has returned
|
||||
* the frame has been submitted to the {@link Surface}. The default and typically
|
||||
* recommended value is false, as blocking for present will prevent pipelining from
|
||||
* happening, reducing overall throughput. This is useful for situations such as
|
||||
* {@link SurfaceHolder.Callback2#surfaceRedrawNeeded(SurfaceHolder)} where it is desired
|
||||
* to block until a frame has been presented to ensure first-frame consistency with
|
||||
* other Surfaces.
|
||||
*
|
||||
* @param shouldWait If true the next call to {@link #syncAndDraw()} will block until
|
||||
* completion.
|
||||
* @return this instance
|
||||
*/
|
||||
public FrameRenderRequest setWaitForPresent(boolean shouldWait) {
|
||||
mWaitForPresent = shouldWait;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs the RenderNode tree to the render thread and requests a frame to be drawn. This
|
||||
* {@link FrameRenderRequest} instance should no longer be used after calling this method.
|
||||
* The system internally may reuse instances of {@link FrameRenderRequest} to reduce
|
||||
* allocation churn.
|
||||
*
|
||||
* @return The result of the sync operation. See {@link SyncAndDrawResult}.
|
||||
*/
|
||||
@SyncAndDrawResult
|
||||
public int syncAndDraw() {
|
||||
int syncResult = syncAndDrawFrame(mFrameInfo);
|
||||
if (mWaitForPresent && (syncResult & SYNC_FRAME_DROPPED) == 0) {
|
||||
fence();
|
||||
}
|
||||
return syncResult;
|
||||
}
|
||||
}
|
||||
|
||||
private FrameRenderRequest mRenderRequest = new FrameRenderRequest();
|
||||
|
||||
/**
|
||||
* Returns a {@link FrameRenderRequest} that can be used to render a new frame. This is used
|
||||
* to synchronize the RenderNode content provided by {@link #setContentRoot(RenderNode)} with
|
||||
* the RenderThread and then renders a single frame to the Surface set with
|
||||
* {@link #setSurface(Surface)}.
|
||||
*
|
||||
* @return An instance of {@link FrameRenderRequest}. The instance may be reused for every
|
||||
* frame, so the caller should not hold onto it for longer than a single render request.
|
||||
*/
|
||||
public FrameRenderRequest createRenderRequest() {
|
||||
mRenderRequest.reset();
|
||||
return mRenderRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs the RenderNode tree to the render thread and requests a frame to be drawn.
|
||||
*
|
||||
@@ -304,55 +421,16 @@ public class HardwareRenderer {
|
||||
return nSyncAndDrawFrame(mNativeProxy, frameInfo.frameInfo, frameInfo.frameInfo.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs the RenderNode tree to the render thread and requests a frame to be drawn.
|
||||
*
|
||||
* @param vsyncTime The vsync timestamp for this frame. Typically this comes from
|
||||
* {@link android.view.Choreographer.FrameCallback}. Must be set and be valid
|
||||
* as the renderer uses this time internally to drive animations.
|
||||
* @return The result of the sync operation. See {@link SyncAndDrawResult}.
|
||||
*/
|
||||
@SyncAndDrawResult
|
||||
public int syncAndDrawFrame(long vsyncTime) {
|
||||
if (mScratchInfo == null) {
|
||||
mScratchInfo = new FrameInfo();
|
||||
}
|
||||
mScratchInfo.setVsync(vsyncTime, vsyncTime);
|
||||
mScratchInfo.addFlags(FrameInfo.FLAG_SURFACE_CANVAS);
|
||||
return syncAndDrawFrame(mScratchInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs the RenderNode tree to the render thread and requests a frame to be drawn.
|
||||
* frameCommitCallback callback will be invoked when the current rendering content has been
|
||||
* rendered into a frame and submitted to the swap chain.
|
||||
*
|
||||
* @param vsyncTime The vsync timestamp for this frame. Typically this comes from
|
||||
* {@link android.view.Choreographer.FrameCallback}. Must be set and
|
||||
* be valid as the renderer uses this time internally to drive
|
||||
* animations.
|
||||
* @param frameCommitCallback The callback to invoke when the frame content has been drawn.
|
||||
* Will be invoked on the current {@link android.os.Looper} thread.
|
||||
* @return The result of the sync operation. See {@link SyncAndDrawResult}.
|
||||
*/
|
||||
@SyncAndDrawResult
|
||||
public int syncAndDrawFrame(long vsyncTime,
|
||||
@Nullable Runnable frameCommitCallback) {
|
||||
if (frameCommitCallback != null) {
|
||||
setFrameCompleteCallback(frameNr -> frameCommitCallback.run());
|
||||
}
|
||||
return syncAndDrawFrame(vsyncTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Suspends any current rendering into the surface but do not do any destruction. This
|
||||
* is useful to temporarily suspend using the active Surface in order to do any Surface
|
||||
* mutations necessary.
|
||||
*
|
||||
* Any subsequent draws will override the pause, resuming normal operation.
|
||||
* <p>Any subsequent draws will override the pause, resuming normal operation.
|
||||
*
|
||||
* @return true if there was an outstanding render request, false otherwise. If this is true
|
||||
* the caller should ensure that {@link #syncAndDrawFrame(long)} is called at the soonest
|
||||
* the caller should ensure that {@link #createRenderRequest()}
|
||||
* and {@link FrameRenderRequest#syncAndDraw()} is called at the soonest
|
||||
* possible time to resume normal operation.
|
||||
*
|
||||
* TODO Should this be exposed? ViewRootImpl needs it because it destroys the old
|
||||
@@ -367,14 +445,14 @@ public class HardwareRenderer {
|
||||
|
||||
/**
|
||||
* Hard stops rendering into the surface. If the renderer is stopped it will
|
||||
* block any attempt to render. Calls to {@link #syncAndDrawFrame(long)} will still
|
||||
* sync over the latest rendering content, however they will not render and instead
|
||||
* block any attempt to render. Calls to {@link FrameRenderRequest#syncAndDraw()} will
|
||||
* still sync over the latest rendering content, however they will not render and instead
|
||||
* {@link #SYNC_CONTEXT_IS_STOPPED} will be returned.
|
||||
*
|
||||
* If false is passed then rendering will resume as normal. Any pending rendering requests
|
||||
* <p>If false is passed then rendering will resume as normal. Any pending rendering requests
|
||||
* will produce a new frame at the next vsync signal.
|
||||
*
|
||||
* This is useful in combination with lifecycle events such as {@link Activity#onStop()}
|
||||
* <p>This is useful in combination with lifecycle events such as {@link Activity#onStop()}
|
||||
* and {@link Activity#onStart()}.
|
||||
*
|
||||
* @param stopped true to stop all rendering, false to resume
|
||||
@@ -384,24 +462,26 @@ public class HardwareRenderer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys all hardware rendering resources associated with the current rendering content.
|
||||
* Destroys all the display lists associated with the current rendering content.
|
||||
* This includes releasing a reference to the current content root RenderNode. It will
|
||||
* therefore be necessary to call {@link #setContentRoot(RenderNode)} in order to resume
|
||||
* rendering after calling this.
|
||||
* rendering after calling this, along with re-recording the display lists for the
|
||||
* RenderNode tree.
|
||||
*
|
||||
* It is recommended, but not necessary, to use this in combination with lifecycle events
|
||||
* <p>It is recommended, but not necessary, to use this in combination with lifecycle events
|
||||
* such as {@link Activity#onStop()} and {@link Activity#onStart()} or in response to
|
||||
* {@link android.content.ComponentCallbacks2#onTrimMemory(int)} signals such as
|
||||
* {@link android.content.ComponentCallbacks2#TRIM_MEMORY_UI_HIDDEN}
|
||||
*
|
||||
* See also {@link #setStopped(boolean)}
|
||||
*/
|
||||
public void destroyHardwareResources() {
|
||||
public void clearContent() {
|
||||
nDestroyHardwareResources(mNativeProxy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the force-dark feature should be used for this renderer.
|
||||
* @hide
|
||||
*/
|
||||
public boolean setForceDark(boolean enable) {
|
||||
if (mForceDark != enable) {
|
||||
@@ -415,20 +495,24 @@ public class HardwareRenderer {
|
||||
/**
|
||||
* Allocate buffers ahead of time to avoid allocation delays during rendering.
|
||||
*
|
||||
* Typically a Surface will allocate buffers lazily. This is usually fine and reduces the
|
||||
* <p>Typically a Surface will allocate buffers lazily. This is usually fine and reduces the
|
||||
* memory usage of Surfaces that render rarely or never hit triple buffering. However
|
||||
* for UI it can result in a slight bit of jank on first launch. This hint will
|
||||
* tell the HardwareRenderer that now is a good time to allocate the 3 buffers
|
||||
* necessary for typical rendering.
|
||||
*
|
||||
* Must be called after a {@link Surface} has been set.
|
||||
* <p>Must be called after a {@link Surface} has been set.
|
||||
*
|
||||
* TODO: Figure out if we even need/want this. Should HWUI just be doing this in response
|
||||
* to setSurface anyway? Vulkan swapchain makes this murky, so delay making it public
|
||||
* @hide
|
||||
*/
|
||||
public void allocateBuffers() {
|
||||
nAllocateBuffers(mNativeProxy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the hardware renderer that a call to {@link #syncAndDrawFrame(long)} will
|
||||
* Notifies the hardware renderer that a call to {@link FrameRenderRequest#syncAndDraw()} will
|
||||
* be coming soon. This is used to help schedule when RenderThread-driven animations will
|
||||
* happen as the renderer wants to avoid producing more than one frame per vsync signal.
|
||||
*/
|
||||
@@ -439,7 +523,7 @@ public class HardwareRenderer {
|
||||
/**
|
||||
* Change the HardwareRenderer's opacity. Will take effect on the next frame produced.
|
||||
*
|
||||
* If the renderer is set to opaque it is the app's responsibility to ensure that the
|
||||
* <p>If the renderer is set to opaque it is the app's responsibility to ensure that the
|
||||
* content renders to every pixel of the Surface, otherwise corruption may result. Note that
|
||||
* this includes ensuring that the first draw of any given pixel does not attempt to blend
|
||||
* against the destination. If this is false then the hardware renderer will clear to
|
||||
@@ -527,7 +611,7 @@ public class HardwareRenderer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents any further drawing until {@link #syncAndDrawFrame(long)} is called.
|
||||
* Prevents any further drawing until {@link FrameRenderRequest#syncAndDraw()} is called.
|
||||
* This is a signal that the contents of the RenderNode tree are no longer safe to play back.
|
||||
* In practice this usually means that there are Functor pointers in the
|
||||
* display list that are no longer valid.
|
||||
@@ -718,10 +802,8 @@ public class HardwareRenderer {
|
||||
* Interface for listening to picture captures
|
||||
* @hide
|
||||
*/
|
||||
@TestApi
|
||||
public interface PictureCapturedCallback {
|
||||
/** @hide */
|
||||
@TestApi
|
||||
void onPictureCaptured(Picture picture);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ import android.graphics.RecordingCanvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RenderNode;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.IntArray;
|
||||
@@ -1241,6 +1242,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
|
||||
|
||||
// If the duration of an animation is more than 300 frames, we cap the sample size to 300.
|
||||
private static final int MAX_SAMPLE_POINTS = 300;
|
||||
private Handler mHandler;
|
||||
private AnimatorListener mListener = null;
|
||||
private final LongArray mStartDelays = new LongArray();
|
||||
private PropertyValuesHolder.PropertyValues mTmpValues =
|
||||
@@ -1671,6 +1673,9 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
|
||||
.mRootName);
|
||||
}
|
||||
mStarted = true;
|
||||
if (mHandler == null) {
|
||||
mHandler = new Handler();
|
||||
}
|
||||
nStart(mSetPtr, this, ++mLastListenerId);
|
||||
invalidateOwningView();
|
||||
if (mListener != null) {
|
||||
@@ -1780,7 +1785,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
|
||||
// onFinished: should be called from native
|
||||
@UnsupportedAppUsage
|
||||
private static void callOnFinished(VectorDrawableAnimatorRT set, int id) {
|
||||
set.onAnimationEnd(id);
|
||||
set.mHandler.post(() -> set.onAnimationEnd(id));
|
||||
}
|
||||
|
||||
private void transferPendingActions(VectorDrawableAnimator animatorSet) {
|
||||
|
||||
@@ -1028,8 +1028,28 @@
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="PositionListenerActivity"
|
||||
android:label="RenderNode/PositionListener"
|
||||
android:name="PositionListenerActivity"
|
||||
android:label="RenderNode/PositionListener"
|
||||
android:screenOrientation="fullSensor">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="com.android.test.hwui.TEST" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="CustomRenderer"
|
||||
android:label="HardwareRenderer/HelloTakeSurface"
|
||||
android:screenOrientation="fullSensor">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="com.android.test.hwui.TEST" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="MyLittleTextureView"
|
||||
android:label="HardwareRenderer/MyLittleTextureView"
|
||||
android:screenOrientation="fullSensor">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 com.android.test.hwui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.HardwareRenderer;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RecordingCanvas;
|
||||
import android.graphics.RenderNode;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.SurfaceHolder;
|
||||
|
||||
public class CustomRenderer extends Activity {
|
||||
private RenderNode mContent = new RenderNode("CustomRenderer");
|
||||
private HardwareRenderer mRenderer = new HardwareRenderer();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
getWindow().takeSurface(mSurfaceCallbacks);
|
||||
}
|
||||
|
||||
private SurfaceHolder.Callback2 mSurfaceCallbacks = new SurfaceHolder.Callback2() {
|
||||
|
||||
@Override
|
||||
public void surfaceRedrawNeeded(SurfaceHolder holder) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
mContent.setLeftTopRightBottom(0, 0, width, height);
|
||||
RecordingCanvas canvas = mContent.startRecording();
|
||||
canvas.drawColor(Color.WHITE);
|
||||
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
paint.setColor(Color.BLACK);
|
||||
paint.setTextAlign(Paint.Align.CENTER);
|
||||
paint.setTextSize(Math.min(width, height) * .05f);
|
||||
canvas.drawText("Hello custom renderer!", width / 2, height / 2, paint);
|
||||
mContent.endRecording();
|
||||
|
||||
mRenderer.setContentRoot(mContent);
|
||||
mRenderer.setSurface(holder.getSurface());
|
||||
mRenderer.createRenderRequest()
|
||||
.setVsyncTime(System.nanoTime())
|
||||
.setFrameCommitCallback(Runnable::run, () -> {
|
||||
Log.d("CustomRenderer", "Frame committed!");
|
||||
})
|
||||
.syncAndDraw();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
mRenderer.destroy();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 com.android.test.hwui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorSpace;
|
||||
import android.graphics.HardwareRenderer;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RenderNode;
|
||||
import android.hardware.HardwareBuffer;
|
||||
import android.media.Image;
|
||||
import android.media.ImageReader;
|
||||
import android.os.Bundle;
|
||||
import android.widget.ImageView;
|
||||
|
||||
public class MyLittleTextureView extends Activity {
|
||||
private RenderNode mContent = new RenderNode("CustomRenderer");
|
||||
private HardwareRenderer mRenderer = new HardwareRenderer();
|
||||
private ImageView mImageView;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mImageView = new ImageView(this);
|
||||
mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
|
||||
setContentView(mImageView);
|
||||
|
||||
ImageReader reader = ImageReader.newInstance(100, 100, PixelFormat.RGBA_8888, 3,
|
||||
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT);
|
||||
mRenderer.setSurface(reader.getSurface());
|
||||
mRenderer.setLightSourceAlpha(0.0f, 1.0f);
|
||||
mRenderer.setLightSourceGeometry(100 / 2f, 0f, 800.0f, 20.0f);
|
||||
mContent.setLeftTopRightBottom(0, 0, 100, 100);
|
||||
|
||||
Rect childRect = new Rect(25, 25, 65, 65);
|
||||
RenderNode childNode = new RenderNode("shadowCaster");
|
||||
childNode.setLeftTopRightBottom(childRect.left, childRect.top,
|
||||
childRect.right, childRect.bottom);
|
||||
Outline outline = new Outline();
|
||||
outline.setRect(new Rect(0, 0, childRect.width(), childRect.height()));
|
||||
outline.setAlpha(1f);
|
||||
childNode.setOutline(outline);
|
||||
{
|
||||
Canvas canvas = childNode.startRecording();
|
||||
canvas.drawColor(Color.BLUE);
|
||||
}
|
||||
childNode.endRecording();
|
||||
childNode.setElevation(20f);
|
||||
|
||||
{
|
||||
Canvas canvas = mContent.startRecording();
|
||||
canvas.drawColor(Color.WHITE);
|
||||
canvas.enableZ();
|
||||
canvas.drawRenderNode(childNode);
|
||||
canvas.disableZ();
|
||||
}
|
||||
mContent.endRecording();
|
||||
mRenderer.setContentRoot(mContent);
|
||||
mRenderer.createRenderRequest()
|
||||
.setWaitForPresent(true)
|
||||
.syncAndDraw();
|
||||
Image image = reader.acquireNextImage();
|
||||
Bitmap bitmap = Bitmap.wrapHardwareBuffer(image.getHardwareBuffer(),
|
||||
ColorSpace.get(ColorSpace.Named.SRGB));
|
||||
mImageView.setImageBitmap(bitmap);
|
||||
image.close();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user