diff options
author | 2019-02-14 20:40:13 +0000 | |
---|---|---|
committer | 2019-02-14 20:40:13 +0000 | |
commit | 989bb3714f7bf6c31c1f13d480b537e485509fb4 (patch) | |
tree | bd5ac3498d56b1a91a02f8cfbce216bb9014b27d | |
parent | b45f78f1ab59c78729b3e66ac29458566091c84a (diff) | |
parent | fe5dfcacfc670f48967472fa87a774d8284ac943 (diff) |
Merge "Make HardwareRenderer public API"
-rw-r--r-- | api/current.txt | 28 | ||||
-rw-r--r-- | core/java/android/view/RenderNodeAnimator.java | 7 | ||||
-rw-r--r-- | core/java/android/view/ThreadedRenderer.java | 6 | ||||
-rw-r--r-- | core/java/android/view/ViewRootImpl.java | 32 | ||||
-rw-r--r-- | core/jni/android_view_ThreadedRenderer.cpp | 80 | ||||
-rw-r--r-- | graphics/java/android/graphics/HardwareRenderer.java | 234 | ||||
-rw-r--r-- | graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java | 7 | ||||
-rw-r--r-- | tests/HwAccelerationTest/AndroidManifest.xml | 24 | ||||
-rw-r--r-- | tests/HwAccelerationTest/src/com/android/test/hwui/CustomRenderer.java | 76 | ||||
-rw-r--r-- | tests/HwAccelerationTest/src/com/android/test/hwui/MyLittleTextureView.java | 87 |
10 files changed, 418 insertions, 163 deletions
diff --git a/api/current.txt b/api/current.txt index 747a3f5d7c53..7db19e907be4 100644 --- a/api/current.txt +++ b/api/current.txt @@ -14096,6 +14096,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); diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java index 1dbc46b3e883..6cfc9f22a692 100644 --- a/core/java/android/view/RenderNodeAnimator.java +++ b/core/java/android/view/RenderNodeAnimator.java @@ -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 diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 47b206ca0dca..20978127f510 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -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(); } } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 1a782ee5d3dd..b1fee2d17079 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -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(); + } + })); } } diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp index 8c7363021d3b..2aa5cb41d5ab 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -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) { - if (mObject) { - mFrameNr = frameNr; - mLooper->sendMessage(this, 0); - } - } - - virtual void handleMessage(const Message&) { + void onFrameComplete(int64_t frameNr) { 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); }); } } diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java index 7ec76d79dfb1..99d8c1b42a4a 100644 --- a/graphics/java/android/graphics/HardwareRenderer.java +++ b/graphics/java/android/graphics/HardwareRenderer.java @@ -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. @@ -295,53 +292,133 @@ public class HardwareRenderer { } /** - * Syncs the RenderNode tree to the render thread and requests a frame to be drawn. - * - * @hide + * 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. */ - @SyncAndDrawResult - public int syncAndDrawFrame(@NonNull FrameInfo frameInfo) { - return nSyncAndDrawFrame(mNativeProxy, frameInfo.frameInfo, frameInfo.frameInfo.length); + 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(); + /** - * Syncs the RenderNode tree to the render thread and requests a frame to be drawn. + * 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)}. * - * @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}. + * @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. */ - @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); + public FrameRenderRequest createRenderRequest() { + mRenderRequest.reset(); + return mRenderRequest; } /** * 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}. + * @hide */ @SyncAndDrawResult - public int syncAndDrawFrame(long vsyncTime, - @Nullable Runnable frameCommitCallback) { - if (frameCommitCallback != null) { - setFrameCompleteCallback(frameNr -> frameCommitCallback.run()); - } - return syncAndDrawFrame(vsyncTime); + public int syncAndDrawFrame(@NonNull FrameInfo frameInfo) { + return nSyncAndDrawFrame(mNativeProxy, frameInfo.frameInfo, frameInfo.frameInfo.length); } /** @@ -349,10 +426,11 @@ public class HardwareRenderer { * 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); } diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java index 789e38c4e650..d7aee7767524 100644 --- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java @@ -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) { diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index 1a4ec94d77b4..7b8c154dea1e 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -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" /> diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/CustomRenderer.java b/tests/HwAccelerationTest/src/com/android/test/hwui/CustomRenderer.java new file mode 100644 index 000000000000..60bd60f0bfd1 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/CustomRenderer.java @@ -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(); + } + }; +} diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MyLittleTextureView.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MyLittleTextureView.java new file mode 100644 index 000000000000..8bd7d797aea3 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MyLittleTextureView.java @@ -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(); + } +} |