diff options
Diffstat (limited to 'libs')
36 files changed, 740 insertions, 198 deletions
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index c9d4af69c0df..366ef716c109 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -113,6 +113,10 @@ hwui_cflags := \ -DATRACE_TAG=ATRACE_TAG_VIEW -DLOG_TAG=\"OpenGLRenderer\" \ -Wall -Wno-unused-parameter -Wunreachable-code -Werror +ifeq ($(TARGET_USES_HWC2),true) + hwui_cflags += -DUSE_HWC2 +endif + # GCC false-positives on this warning, and since we -Werror that's # a problem hwui_cflags += -Wno-free-nonheap-object diff --git a/libs/hwui/AnimationContext.h b/libs/hwui/AnimationContext.h index 909ed36a2127..11d305c3c8d0 100644 --- a/libs/hwui/AnimationContext.h +++ b/libs/hwui/AnimationContext.h @@ -100,6 +100,8 @@ public: ANDROID_API virtual void destroy(); + ANDROID_API virtual void pauseAnimators() {} + private: friend class AnimationHandle; void addAnimationHandle(AnimationHandle* handle); diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp index 4d65782f684b..74aa3033ee12 100644 --- a/libs/hwui/Animator.cpp +++ b/libs/hwui/Animator.cpp @@ -123,22 +123,27 @@ void BaseRenderNodeAnimator::resolveStagingRequest(Request request) { mPlayTime = (mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) ? mPlayTime : 0; mPlayState = PlayState::Running; + mPendingActionUponFinish = Action::None; break; case Request::Reverse: mPlayTime = (mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) ? mPlayTime : mDuration; mPlayState = PlayState::Reversing; + mPendingActionUponFinish = Action::None; break; case Request::Reset: mPlayTime = 0; mPlayState = PlayState::Finished; + mPendingActionUponFinish = Action::Reset; break; case Request::Cancel: mPlayState = PlayState::Finished; + mPendingActionUponFinish = Action::None; break; case Request::End: mPlayTime = mPlayState == PlayState::Reversing ? 0 : mDuration; mPlayState = PlayState::Finished; + mPendingActionUponFinish = Action::End; break; default: LOG_ALWAYS_FATAL("Invalid staging request: %d", static_cast<int>(request)); @@ -176,8 +181,6 @@ void BaseRenderNodeAnimator::pushStaging(AnimationContext& context) { mStagingRequests.clear(); if (mStagingPlayState == PlayState::Finished) { - // Set the staging play time and end the animation - updatePlayTime(mPlayTime); callOnFinishedListener(context); } else if (mStagingPlayState == PlayState::Running || mStagingPlayState == PlayState::Reversing) { @@ -236,6 +239,15 @@ bool BaseRenderNodeAnimator::animate(AnimationContext& context) { return false; } if (mPlayState == PlayState::Finished) { + if (mPendingActionUponFinish == Action::Reset) { + // Skip to start. + updatePlayTime(0); + } else if (mPendingActionUponFinish == Action::End) { + // Skip to end. + updatePlayTime(mDuration); + } + // Reset pending action. + mPendingActionUponFinish = Action ::None; return true; } @@ -274,6 +286,10 @@ bool BaseRenderNodeAnimator::updatePlayTime(nsecs_t playTime) { return playTime >= mDuration; } +nsecs_t BaseRenderNodeAnimator::getRemainingPlayTime() { + return mPlayState == PlayState::Reversing ? mPlayTime : mDuration - mPlayTime; +} + void BaseRenderNodeAnimator::forceEndNow(AnimationContext& context) { if (mPlayState < PlayState::Finished) { mPlayState = PlayState::Finished; diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h index fdae0f32d4e6..72bac6c2a515 100644 --- a/libs/hwui/Animator.h +++ b/libs/hwui/Animator.h @@ -44,6 +44,12 @@ protected: ANDROID_API virtual ~AnimationListener() {} }; +enum class RepeatMode { + // These are the same values as the RESTART and REVERSE in ValueAnimator.java. + Restart = 1, + Reverse = 2 +}; + class BaseRenderNodeAnimator : public VirtualLightRefBase { PREVENT_COPY_AND_ASSIGN(BaseRenderNodeAnimator); public: @@ -62,19 +68,23 @@ public: } bool mayRunAsync() { return mMayRunAsync; } ANDROID_API void start(); - ANDROID_API void reset(); + ANDROID_API virtual void reset(); ANDROID_API void reverse(); // Terminates the animation at its current progress. ANDROID_API void cancel(); // Terminates the animation and skip to the end of the animation. - ANDROID_API void end(); + ANDROID_API virtual void end(); void attach(RenderNode* target); virtual void onAttached() {} void detach() { mTarget = nullptr; } - void pushStaging(AnimationContext& context); - bool animate(AnimationContext& context); + ANDROID_API void pushStaging(AnimationContext& context); + ANDROID_API bool animate(AnimationContext& context); + + // Returns the remaining time in ms for the animation. Note this should only be called during + // an animation on RenderThread. + ANDROID_API nsecs_t getRemainingPlayTime(); bool isRunning() { return mPlayState == PlayState::Running || mPlayState == PlayState::Reversing; } @@ -155,6 +165,17 @@ private: Cancel, End }; + + // Defines different actions upon finish. + enum class Action { + // For animations that got canceled or finished normally. no more action needs to be done. + None, + // For animations that get reset, the reset will happen in the next animation pulse. + Reset, + // For animations being ended, in the next animation pulse the animation will skip to end. + End + }; + inline void checkMutable(); virtual void transitionToRunning(AnimationContext& context); void doSetStartValue(float value); @@ -162,7 +183,7 @@ private: void resolveStagingRequest(Request request); std::vector<Request> mStagingRequests; - + Action mPendingActionUponFinish = Action::None; }; class RenderPropertyAnimator : public BaseRenderNodeAnimator { diff --git a/libs/hwui/ClipArea.cpp b/libs/hwui/ClipArea.cpp index fe6823925083..84451bacbc09 100644 --- a/libs/hwui/ClipArea.cpp +++ b/libs/hwui/ClipArea.cpp @@ -464,10 +464,7 @@ const ClipBase* ClipArea::serializeIntersectedClip(LinearAllocator& allocator, } case ClipMode::Region: other = getRegion(recordedClip); - - // TODO: handle non-translate transforms properly! - other.translate(recordedClipTransform.getTranslateX(), - recordedClipTransform.getTranslateY()); + applyTransformToRegion(recordedClipTransform, &other); } ClipRegion* regionClip = allocator.create<ClipRegion>(); @@ -527,11 +524,29 @@ void ClipArea::applyClip(const ClipBase* clip, const Matrix4& transform) { } } else { SkRegion region(getRegion(clip)); - // TODO: handle non-translate transforms properly! - region.translate(transform.getTranslateX(), transform.getTranslateY()); + applyTransformToRegion(transform, ®ion); clipRegion(region, SkRegion::kIntersect_Op); } } +void ClipArea::applyTransformToRegion(const Matrix4& transform, SkRegion* region) { + if (transform.rectToRect() && !transform.isPureTranslate()) { + // handle matrices with scale manually by mapping each rect + SkRegion other; + SkRegion::Iterator it(*region); + while (!it.done()) { + Rect rect(it.rect()); + transform.mapRect(rect); + rect.snapGeometryToPixelBoundaries(true); + other.op(rect.left, rect.top, rect.right, rect.bottom, SkRegion::kUnion_Op); + it.next(); + } + region->swap(other); + } else { + // TODO: handle non-translate transforms properly! + region->translate(transform.getTranslateX(), transform.getTranslateY()); + } +} + } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/ClipArea.h b/libs/hwui/ClipArea.h index 6eb2eef5a5f9..53d9d03a3629 100644 --- a/libs/hwui/ClipArea.h +++ b/libs/hwui/ClipArea.h @@ -179,6 +179,8 @@ public: const ClipBase* recordedClip, const Matrix4& recordedClipTransform); void applyClip(const ClipBase* recordedClip, const Matrix4& recordedClipTransform); + static void applyTransformToRegion(const Matrix4& transform, SkRegion* region); + private: void enterRectangleMode(); void rectangleModeClipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op); diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp index b572bdaccb86..28be05c52cfc 100644 --- a/libs/hwui/DisplayList.cpp +++ b/libs/hwui/DisplayList.cpp @@ -45,7 +45,7 @@ DisplayList::DisplayList() , regions(stdAllocator) , referenceHolders(stdAllocator) , functors(stdAllocator) - , pushStagingFunctors(stdAllocator) + , vectorDrawables(stdAllocator) , hasDrawOps(false) { } diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h index 5b3227b7db97..ccf71c6d360b 100644 --- a/libs/hwui/DisplayList.h +++ b/libs/hwui/DisplayList.h @@ -69,6 +69,11 @@ typedef DisplayListOp BaseOpType; typedef DrawRenderNodeOp NodeOpType; #endif +namespace VectorDrawable { +class Tree; +}; +typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot; + /** * Holds data used in the playback a tree of DisplayLists. */ @@ -110,16 +115,6 @@ struct ReplayStateStruct : public PlaybackStateStruct { LinearAllocator mReplayAllocator; }; -/** - * Functor that can be used for objects with data in both UI thread and RT to keep the data - * in sync. This functor, when added to DisplayList, will be call during DisplayList sync. - */ -struct PushStagingFunctor { - PushStagingFunctor() {} - virtual ~PushStagingFunctor() {} - virtual void operator ()() {} -}; - struct FunctorContainer { Functor* functor; GlFunctorLifecycleListener* listener; @@ -161,7 +156,7 @@ public: const LsaVector<const SkBitmap*>& getBitmapResources() const { return bitmapResources; } const LsaVector<FunctorContainer>& getFunctors() const { return functors; } - const LsaVector<PushStagingFunctor*>& getPushStagingFunctors() { return pushStagingFunctors; } + const LsaVector<VectorDrawableRoot*>& getVectorDrawables() { return vectorDrawables; } size_t addChild(NodeOpType* childOp); @@ -203,10 +198,10 @@ private: // List of functors LsaVector<FunctorContainer> functors; - // List of functors that need to be notified of pushStaging. Note that this list gets nothing + // List of VectorDrawables that need to be notified of pushStaging. Note that this list gets nothing // but a callback during sync DisplayList, unlike the list of functors defined above, which // gets special treatment exclusive for webview. - LsaVector<PushStagingFunctor*> pushStagingFunctors; + LsaVector<VectorDrawableRoot*> vectorDrawables; bool hasDrawOps; // only used if !HWUI_NEW_OPS diff --git a/libs/hwui/DisplayListCanvas.cpp b/libs/hwui/DisplayListCanvas.cpp index ca968cef91b2..bec662959f91 100644 --- a/libs/hwui/DisplayListCanvas.cpp +++ b/libs/hwui/DisplayListCanvas.cpp @@ -417,7 +417,7 @@ void DisplayListCanvas::drawPoints(const float* points, int count, const SkPaint void DisplayListCanvas::drawVectorDrawable(VectorDrawableRoot* tree) { mDisplayList->ref(tree); - mDisplayList->pushStagingFunctors.push_back(tree->getFunctor()); + mDisplayList->vectorDrawables.push_back(tree); addDrawOp(new (alloc()) DrawVectorDrawableOp(tree, tree->stagingProperties()->getBounds())); } diff --git a/libs/hwui/FrameInfo.cpp b/libs/hwui/FrameInfo.cpp index 41e22332d8ed..826f0bba294c 100644 --- a/libs/hwui/FrameInfo.cpp +++ b/libs/hwui/FrameInfo.cpp @@ -35,8 +35,17 @@ const std::string FrameInfoNames[] = { "IssueDrawCommandsStart", "SwapBuffers", "FrameCompleted", + "DequeueBufferDuration", + "QueueBufferDuration", }; +static_assert((sizeof(FrameInfoNames)/sizeof(FrameInfoNames[0])) + == static_cast<int>(FrameInfoIndex::NumIndexes), + "size mismatch: FrameInfoNames doesn't match the enum!"); + +static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 16, + "Must update value in FrameMetrics.java#FRAME_STATS_COUNT (and here)"); + void FrameInfo::importUiThreadInfo(int64_t* info) { memcpy(mFrameInfo, info, UI_THREAD_FRAME_INFO_SIZE * sizeof(int64_t)); } diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h index 0baca391be79..45b57de0b504 100644 --- a/libs/hwui/FrameInfo.h +++ b/libs/hwui/FrameInfo.h @@ -48,7 +48,11 @@ enum class FrameInfoIndex { SwapBuffers, FrameCompleted, + DequeueBufferDuration, + QueueBufferDuration, + // Must be the last value! + // Also must be kept in sync with FrameMetrics.java#FRAME_STATS_COUNT NumIndexes }; diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp index ebe9c4240221..ed6b211eef1b 100644 --- a/libs/hwui/JankTracker.cpp +++ b/libs/hwui/JankTracker.cpp @@ -16,6 +16,7 @@ #include "JankTracker.h" #include "Properties.h" +#include "utils/TimeUtils.h" #include <algorithm> #include <cutils/ashmem.h> @@ -119,11 +120,27 @@ static uint32_t frameTimeForFrameCountIndex(uint32_t index) { return index; } -JankTracker::JankTracker(nsecs_t frameIntervalNanos) { +JankTracker::JankTracker(const DisplayInfo& displayInfo) { // By default this will use malloc memory. It may be moved later to ashmem // if there is shared space for it and a request comes in to do that. mData = new ProfileData; reset(); + nsecs_t frameIntervalNanos = static_cast<nsecs_t>(1_s / displayInfo.fps); +#if USE_HWC2 + nsecs_t sfOffset = frameIntervalNanos - (displayInfo.presentationDeadline - 1_ms); + nsecs_t offsetDelta = sfOffset - displayInfo.appVsyncOffset; + // There are two different offset cases. If the offsetDelta is positive + // and small, then the intention is to give apps extra time by leveraging + // pipelining between the UI & RT threads. If the offsetDelta is large or + // negative, the intention is to subtract time from the total duration + // in which case we can't afford to wait for dequeueBuffer blockage. + if (offsetDelta <= 4_ms && offsetDelta >= 0) { + // SF will begin composition at VSYNC-app + offsetDelta. If we are triple + // buffered, this is the expected time at which dequeueBuffer will + // return due to the staggering of VSYNC-app & VSYNC-sf. + mDequeueTimeForgiveness = offsetDelta + 4_ms; + } +#endif setFrameInterval(frameIntervalNanos); } @@ -213,6 +230,19 @@ void JankTracker::addFrame(const FrameInfo& frame) { mData->totalFrameCount++; // Fast-path for jank-free frames int64_t totalDuration = frame.duration(sFrameStart, FrameInfoIndex::FrameCompleted); + if (mDequeueTimeForgiveness + && frame[FrameInfoIndex::DequeueBufferDuration] > 500_us) { + nsecs_t expectedDequeueDuration = + mDequeueTimeForgiveness + frame[FrameInfoIndex::Vsync] + - frame[FrameInfoIndex::IssueDrawCommandsStart]; + if (expectedDequeueDuration > 0) { + // Forgive only up to the expected amount, but not more than + // the actual time spent blocked. + nsecs_t forgiveAmount = std::min(expectedDequeueDuration, + frame[FrameInfoIndex::DequeueBufferDuration]); + totalDuration -= forgiveAmount; + } + } uint32_t framebucket = frameCountIndexForFrameTime(totalDuration); // Keep the fast path as fast as possible. if (CC_LIKELY(totalDuration < mFrameInterval)) { diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h index 84b8c3f3f155..126025c00933 100644 --- a/libs/hwui/JankTracker.h +++ b/libs/hwui/JankTracker.h @@ -21,6 +21,7 @@ #include "utils/RingBuffer.h" #include <cutils/compiler.h> +#include <ui/DisplayInfo.h> #include <array> #include <memory> @@ -56,7 +57,7 @@ struct ProfileData { // TODO: Replace DrawProfiler with this class JankTracker { public: - JankTracker(nsecs_t frameIntervalNanos); + JankTracker(const DisplayInfo& displayInfo); ~JankTracker(); void addFrame(const FrameInfo& frame); @@ -79,6 +80,14 @@ private: std::array<int64_t, NUM_BUCKETS> mThresholds; int64_t mFrameInterval; + // The amount of time we will erase from the total duration to account + // for SF vsync offsets with HWC2 blocking dequeueBuffers. + // (Vsync + mDequeueBlockTolerance) is the point at which we expect + // SF to have released the buffer normally, so we will forgive up to that + // point in time by comparing to (IssueDrawCommandsStart + DequeueDuration) + // This is only used if we are in pipelined mode and are using HWC2, + // otherwise it's 0. + nsecs_t mDequeueTimeForgiveness = 0; ProfileData* mData; bool mIsMapped = false; }; diff --git a/libs/hwui/PropertyValuesAnimatorSet.cpp b/libs/hwui/PropertyValuesAnimatorSet.cpp index b29f91ff34aa..38fb70a92e43 100644 --- a/libs/hwui/PropertyValuesAnimatorSet.cpp +++ b/libs/hwui/PropertyValuesAnimatorSet.cpp @@ -23,13 +23,17 @@ namespace android { namespace uirenderer { void PropertyValuesAnimatorSet::addPropertyAnimator(PropertyValuesHolder* propertyValuesHolder, - Interpolator* interpolator, nsecs_t startDelay, - nsecs_t duration, int repeatCount) { + Interpolator* interpolator, nsecs_t startDelay, nsecs_t duration, int repeatCount, + RepeatMode repeatMode) { PropertyAnimator* animator = new PropertyAnimator(propertyValuesHolder, - interpolator, startDelay, duration, repeatCount); + interpolator, startDelay, duration, repeatCount, repeatMode); mAnimators.emplace_back(animator); - setListener(new PropertyAnimatorSetListener(this)); + + // Check whether any child animator is infinite after adding it them to the set. + if (repeatCount == -1) { + mIsInfinite = true; + } } PropertyValuesAnimatorSet::PropertyValuesAnimatorSet() @@ -37,6 +41,7 @@ PropertyValuesAnimatorSet::PropertyValuesAnimatorSet() setStartValue(0); mLastFraction = 0.0f; setInterpolator(new LinearInterpolator()); + setListener(new PropertyAnimatorSetListener(this)); } void PropertyValuesAnimatorSet::onFinished(BaseRenderNodeAnimator* animator) { @@ -61,14 +66,9 @@ void PropertyValuesAnimatorSet::onPlayTimeChanged(nsecs_t playTime) { // Note that this set may containing animators modifying the same property, so when we // reset the animators, we need to make sure the animators that end the first will // have the final say on what the property value should be. - (*it)->setFraction(0); - } - } else if (playTime >= mDuration) { - // Skip all the animators to end - for (auto& anim : mAnimators) { - anim->setFraction(1); + (*it)->setFraction(0, 0); } - } else { + } else { for (auto& anim : mAnimators) { anim->setCurrentPlayTime(playTime); } @@ -78,15 +78,27 @@ void PropertyValuesAnimatorSet::onPlayTimeChanged(nsecs_t playTime) { void PropertyValuesAnimatorSet::start(AnimationListener* listener) { init(); mOneShotListener = listener; + mRequestId++; BaseRenderNodeAnimator::start(); } void PropertyValuesAnimatorSet::reverse(AnimationListener* listener) { init(); mOneShotListener = listener; + mRequestId++; BaseRenderNodeAnimator::reverse(); } +void PropertyValuesAnimatorSet::reset() { + mRequestId++; + BaseRenderNodeAnimator::reset(); +} + +void PropertyValuesAnimatorSet::end() { + mRequestId++; + BaseRenderNodeAnimator::end(); +} + void PropertyValuesAnimatorSet::init() { if (mInitialized) { return; @@ -98,7 +110,7 @@ void PropertyValuesAnimatorSet::init() { std::sort(mAnimators.begin(), mAnimators.end(), [](auto& a, auto&b) { return a->getTotalDuration() < b->getTotalDuration(); }); - mDuration = mAnimators[mAnimators.size() - 1]->getTotalDuration(); + mDuration = mAnimators.empty() ? 0 : mAnimators[mAnimators.size() - 1]->getTotalDuration(); mInitialized = true; } @@ -107,7 +119,8 @@ uint32_t PropertyValuesAnimatorSet::dirtyMask() { } PropertyAnimator::PropertyAnimator(PropertyValuesHolder* holder, Interpolator* interpolator, - nsecs_t startDelay, nsecs_t duration, int repeatCount) + nsecs_t startDelay, nsecs_t duration, int repeatCount, + RepeatMode repeatMode) : mPropertyValuesHolder(holder), mInterpolator(interpolator), mStartDelay(startDelay), mDuration(duration) { if (repeatCount < 0) { @@ -115,24 +128,44 @@ PropertyAnimator::PropertyAnimator(PropertyValuesHolder* holder, Interpolator* i } else { mRepeatCount = repeatCount; } + mRepeatMode = repeatMode; mTotalDuration = ((nsecs_t) mRepeatCount + 1) * mDuration + mStartDelay; } void PropertyAnimator::setCurrentPlayTime(nsecs_t playTime) { - if (playTime >= mStartDelay && playTime < mTotalDuration) { - nsecs_t currentIterationPlayTime = (playTime - mStartDelay) % mDuration; - float fraction = currentIterationPlayTime / (float) mDuration; - setFraction(fraction); - } else if (mLatestFraction < 1.0f && playTime >= mTotalDuration) { - // This makes sure we only set the fraction = 1 once. It is needed because there might - // be another animator modifying the same property after this animator finishes, we need - // to make sure we don't set conflicting values on the same property within one frame. - setFraction(1.0f); + if (playTime < mStartDelay) { + return; + } + + float currentIterationFraction; + long iteration; + if (playTime >= mTotalDuration) { + // Reached the end of the animation. + iteration = mRepeatCount; + currentIterationFraction = 1.0f; + } else { + // play time here is in range [mStartDelay, mTotalDuration) + iteration = (playTime - mStartDelay) / mDuration; + currentIterationFraction = ((playTime - mStartDelay) % mDuration) / (float) mDuration; } + setFraction(currentIterationFraction, iteration); } -void PropertyAnimator::setFraction(float fraction) { - mLatestFraction = fraction; +void PropertyAnimator::setFraction(float fraction, long iteration) { + double totalFraction = fraction + iteration; + // This makes sure we only set the fraction = repeatCount + 1 once. It is needed because there + // might be another animator modifying the same property after this animator finishes, we need + // to make sure we don't set conflicting values on the same property within one frame. + if ((mLatestFraction == mRepeatCount + 1.0) && (totalFraction >= mRepeatCount + 1.0)) { + return; + } + + mLatestFraction = totalFraction; + // Check the play direction (i.e. reverse or restart) every other iteration, and calculate the + // fraction based on the play direction. + if (iteration % 2 && mRepeatMode == RepeatMode::Reverse) { + fraction = 1.0f - fraction; + } float interpolatedFraction = mInterpolator->interpolate(fraction); mPropertyValuesHolder->setFraction(interpolatedFraction); } diff --git a/libs/hwui/PropertyValuesAnimatorSet.h b/libs/hwui/PropertyValuesAnimatorSet.h index c7ae7c0e8ce1..e208b08cc27a 100644 --- a/libs/hwui/PropertyValuesAnimatorSet.h +++ b/libs/hwui/PropertyValuesAnimatorSet.h @@ -26,12 +26,13 @@ namespace uirenderer { class PropertyAnimator { public: PropertyAnimator(PropertyValuesHolder* holder, Interpolator* interpolator, nsecs_t startDelay, - nsecs_t duration, int repeatCount); + nsecs_t duration, int repeatCount, RepeatMode repeatMode); void setCurrentPlayTime(nsecs_t playTime); nsecs_t getTotalDuration() { return mTotalDuration; } - void setFraction(float fraction); + // fraction range: [0, 1], iteration range [0, repeatCount] + void setFraction(float fraction, long iteration); private: std::unique_ptr<PropertyValuesHolder> mPropertyValuesHolder; @@ -40,9 +41,11 @@ private: nsecs_t mDuration; uint32_t mRepeatCount; nsecs_t mTotalDuration; - float mLatestFraction = 0.0f; + RepeatMode mRepeatMode; + double mLatestFraction = 0; }; +// TODO: This class should really be named VectorDrawableAnimator class ANDROID_API PropertyValuesAnimatorSet : public BaseRenderNodeAnimator { public: friend class PropertyAnimatorSetListener; @@ -50,11 +53,19 @@ public: void start(AnimationListener* listener); void reverse(AnimationListener* listener); + virtual void reset() override; + virtual void end() override; void addPropertyAnimator(PropertyValuesHolder* propertyValuesHolder, Interpolator* interpolators, int64_t startDelays, - nsecs_t durations, int repeatCount); + nsecs_t durations, int repeatCount, RepeatMode repeatMode); virtual uint32_t dirtyMask(); + bool isInfinite() { return mIsInfinite; } + void setVectorDrawable(VectorDrawableRoot* vd) { mVectorDrawable = vd; } + VectorDrawableRoot* getVectorDrawable() const { return mVectorDrawable.get(); } + AnimationListener* getOneShotListener() { return mOneShotListener.get(); } + void clearOneShotListener() { mOneShotListener = nullptr; } + uint32_t getRequestId() const { return mRequestId; } protected: virtual float getValue(RenderNode* target) const override; @@ -69,6 +80,11 @@ private: std::vector< std::unique_ptr<PropertyAnimator> > mAnimators; float mLastFraction = 0.0f; bool mInitialized = false; + sp<VectorDrawableRoot> mVectorDrawable; + bool mIsInfinite = false; + // This request id gets incremented (on UI thread only) when a new request to modfiy the + // lifecycle of an animation happens, namely when start/end/reset/reverse is called. + uint32_t mRequestId = 0; }; class PropertyAnimatorSetListener : public AnimationListener { diff --git a/libs/hwui/PropertyValuesHolder.cpp b/libs/hwui/PropertyValuesHolder.cpp index 0932d653fd5e..6ba0ab59a88c 100644 --- a/libs/hwui/PropertyValuesHolder.cpp +++ b/libs/hwui/PropertyValuesHolder.cpp @@ -25,7 +25,27 @@ namespace uirenderer { using namespace VectorDrawable; -float PropertyValuesHolder::getValueFromData(float fraction) { +inline U8CPU lerp(U8CPU fromValue, U8CPU toValue, float fraction) { + return (U8CPU) (fromValue * (1 - fraction) + toValue * fraction); +} + +// TODO: Add a test for this +void ColorEvaluator::evaluate(SkColor* outColor, + const SkColor& fromColor, const SkColor& toColor, float fraction) const { + U8CPU alpha = lerp(SkColorGetA(fromColor), SkColorGetA(toColor), fraction); + U8CPU red = lerp(SkColorGetR(fromColor), SkColorGetR(toColor), fraction); + U8CPU green = lerp(SkColorGetG(fromColor), SkColorGetG(toColor), fraction); + U8CPU blue = lerp(SkColorGetB(fromColor), SkColorGetB(toColor), fraction); + *outColor = SkColorSetARGB(alpha, red, green, blue); +} + +void PathEvaluator::evaluate(PathData* out, + const PathData& from, const PathData& to, float fraction) const { + VectorDrawableUtils::interpolatePaths(out, from, to, fraction); +} + +template<typename T> +const T PropertyValuesHolderImpl<T>::getValueFromData(float fraction) const { if (mDataSource.size() == 0) { LOG_ALWAYS_FATAL("No data source is defined"); return 0; @@ -41,57 +61,44 @@ float PropertyValuesHolder::getValueFromData(float fraction) { int lowIndex = floor(fraction); fraction -= lowIndex; - float value = mDataSource[lowIndex] * (1.0f - fraction) - + mDataSource[lowIndex + 1] * fraction; + T value; + mEvaluator->evaluate(&value, mDataSource[lowIndex], mDataSource[lowIndex + 1], fraction); return value; } -void GroupPropertyValuesHolder::setFraction(float fraction) { - float animatedValue; +template<typename T> +const T PropertyValuesHolderImpl<T>::calculateAnimatedValue(float fraction) const { if (mDataSource.size() > 0) { - animatedValue = getValueFromData(fraction); + return getValueFromData(fraction); } else { - animatedValue = mStartValue * (1 - fraction) + mEndValue * fraction; + T value; + mEvaluator->evaluate(&value, mStartValue, mEndValue, fraction); + return value; } - mGroup->mutateProperties()->setPropertyValue(mPropertyId, animatedValue); } -inline U8CPU lerp(U8CPU fromValue, U8CPU toValue, float fraction) { - return (U8CPU) (fromValue * (1 - fraction) + toValue * fraction); -} - -// TODO: Add a test for this -SkColor FullPathColorPropertyValuesHolder::interpolateColors(SkColor fromColor, SkColor toColor, - float fraction) { - U8CPU alpha = lerp(SkColorGetA(fromColor), SkColorGetA(toColor), fraction); - U8CPU red = lerp(SkColorGetR(fromColor), SkColorGetR(toColor), fraction); - U8CPU green = lerp(SkColorGetG(fromColor), SkColorGetG(toColor), fraction); - U8CPU blue = lerp(SkColorGetB(fromColor), SkColorGetB(toColor), fraction); - return SkColorSetARGB(alpha, red, green, blue); +void GroupPropertyValuesHolder::setFraction(float fraction) { + float animatedValue = calculateAnimatedValue(fraction); + mGroup->mutateProperties()->setPropertyValue(mPropertyId, animatedValue); } void FullPathColorPropertyValuesHolder::setFraction(float fraction) { - SkColor animatedValue = interpolateColors(mStartValue, mEndValue, fraction); + SkColor animatedValue = calculateAnimatedValue(fraction); mFullPath->mutateProperties()->setColorPropertyValue(mPropertyId, animatedValue); } void FullPathPropertyValuesHolder::setFraction(float fraction) { - float animatedValue; - if (mDataSource.size() > 0) { - animatedValue = getValueFromData(fraction); - } else { - animatedValue = mStartValue * (1 - fraction) + mEndValue * fraction; - } + float animatedValue = calculateAnimatedValue(fraction); mFullPath->mutateProperties()->setPropertyValue(mPropertyId, animatedValue); } void PathDataPropertyValuesHolder::setFraction(float fraction) { - VectorDrawableUtils::interpolatePaths(&mPathData, mStartValue, mEndValue, fraction); + mEvaluator->evaluate(&mPathData, mStartValue, mEndValue, fraction); mPath->mutateProperties()->setData(mPathData); } void RootAlphaPropertyValuesHolder::setFraction(float fraction) { - float animatedValue = mStartValue * (1 - fraction) + mEndValue * fraction; + float animatedValue = calculateAnimatedValue(fraction); mTree->mutateProperties()->setRootAlpha(animatedValue); } diff --git a/libs/hwui/PropertyValuesHolder.h b/libs/hwui/PropertyValuesHolder.h index b905faef104c..432f8ba82afb 100644 --- a/libs/hwui/PropertyValuesHolder.h +++ b/libs/hwui/PropertyValuesHolder.h @@ -31,91 +31,130 @@ namespace uirenderer { class ANDROID_API PropertyValuesHolder { public: virtual void setFraction(float fraction) = 0; - void setPropertyDataSource(float* dataSource, int length) { + virtual ~PropertyValuesHolder() {} +}; + +template <typename T> +class Evaluator { +public: + virtual void evaluate(T* out, const T& from, const T& to, float fraction) const {}; + virtual ~Evaluator() {} +}; + +class FloatEvaluator : public Evaluator<float> { +public: + virtual void evaluate(float* out, const float& from, const float& to, float fraction) + const override { + *out = from * (1 - fraction) + to * fraction; + } +}; + +class ANDROID_API ColorEvaluator : public Evaluator<SkColor> { +public: + virtual void evaluate(SkColor* outColor, const SkColor& from, const SkColor& to, + float fraction) const override; +}; + +class ANDROID_API PathEvaluator : public Evaluator<PathData> { + virtual void evaluate(PathData* out, const PathData& from, const PathData& to, float fraction) + const override; +}; + +template <typename T> +class ANDROID_API PropertyValuesHolderImpl : public PropertyValuesHolder { +public: + PropertyValuesHolderImpl(const T& startValue, const T& endValue) + : mStartValue(startValue) + , mEndValue(endValue) {} + void setPropertyDataSource(T* dataSource, int length) { mDataSource.insert(mDataSource.begin(), dataSource, dataSource + length); } - float getValueFromData(float fraction); - virtual ~PropertyValuesHolder() {} + // Calculate the animated value from the data source. + const T getValueFromData(float fraction) const; + // Convenient method to favor getting animated value from data source. If no data source is set + // fall back to linear interpolation. + const T calculateAnimatedValue(float fraction) const; protected: - std::vector<float> mDataSource; + std::unique_ptr<Evaluator<T>> mEvaluator = nullptr; + // This contains uniformly sampled data throughout the animation duration. The first element + // should be the start value and the last should be the end value of the animation. When the + // data source is set, we'll favor data source over the linear interpolation of start/end value + // for calculation of animated value. + std::vector<T> mDataSource; + T mStartValue; + T mEndValue; }; -class ANDROID_API GroupPropertyValuesHolder : public PropertyValuesHolder { +class ANDROID_API GroupPropertyValuesHolder : public PropertyValuesHolderImpl<float> { public: GroupPropertyValuesHolder(VectorDrawable::Group* ptr, int propertyId, float startValue, float endValue) - : mGroup(ptr) - , mPropertyId(propertyId) - , mStartValue(startValue) - , mEndValue(endValue){ + : PropertyValuesHolderImpl(startValue, endValue) + , mGroup(ptr) + , mPropertyId(propertyId) { + mEvaluator.reset(new FloatEvaluator()); } void setFraction(float fraction) override; private: VectorDrawable::Group* mGroup; int mPropertyId; - float mStartValue; - float mEndValue; }; -class ANDROID_API FullPathColorPropertyValuesHolder : public PropertyValuesHolder { +class ANDROID_API FullPathColorPropertyValuesHolder : public PropertyValuesHolderImpl<SkColor> { public: - FullPathColorPropertyValuesHolder(VectorDrawable::FullPath* ptr, int propertyId, int32_t startValue, - int32_t endValue) - : mFullPath(ptr) - , mPropertyId(propertyId) - , mStartValue(startValue) - , mEndValue(endValue) {}; + FullPathColorPropertyValuesHolder(VectorDrawable::FullPath* ptr, int propertyId, + SkColor startValue, SkColor endValue) + : PropertyValuesHolderImpl(startValue, endValue) + , mFullPath(ptr) + , mPropertyId(propertyId) { + mEvaluator.reset(new ColorEvaluator()); + } void setFraction(float fraction) override; static SkColor interpolateColors(SkColor fromColor, SkColor toColor, float fraction); private: VectorDrawable::FullPath* mFullPath; int mPropertyId; - int32_t mStartValue; - int32_t mEndValue; }; -class ANDROID_API FullPathPropertyValuesHolder : public PropertyValuesHolder { +class ANDROID_API FullPathPropertyValuesHolder : public PropertyValuesHolderImpl<float> { public: FullPathPropertyValuesHolder(VectorDrawable::FullPath* ptr, int propertyId, float startValue, float endValue) - : mFullPath(ptr) - , mPropertyId(propertyId) - , mStartValue(startValue) - , mEndValue(endValue) {}; + : PropertyValuesHolderImpl(startValue, endValue) + , mFullPath(ptr) + , mPropertyId(propertyId) { + mEvaluator.reset(new FloatEvaluator()); + }; void setFraction(float fraction) override; private: VectorDrawable::FullPath* mFullPath; int mPropertyId; - float mStartValue; - float mEndValue; }; -class ANDROID_API PathDataPropertyValuesHolder : public PropertyValuesHolder { +class ANDROID_API PathDataPropertyValuesHolder : public PropertyValuesHolderImpl<PathData> { public: PathDataPropertyValuesHolder(VectorDrawable::Path* ptr, PathData* startValue, PathData* endValue) - : mPath(ptr) - , mStartValue(*startValue) - , mEndValue(*endValue) {}; + : PropertyValuesHolderImpl(*startValue, *endValue) + , mPath(ptr) { + mEvaluator.reset(new PathEvaluator()); + }; void setFraction(float fraction) override; private: VectorDrawable::Path* mPath; PathData mPathData; - PathData mStartValue; - PathData mEndValue; }; -class ANDROID_API RootAlphaPropertyValuesHolder : public PropertyValuesHolder { +class ANDROID_API RootAlphaPropertyValuesHolder : public PropertyValuesHolderImpl<float> { public: RootAlphaPropertyValuesHolder(VectorDrawable::Tree* tree, float startValue, float endValue) - : mTree(tree) - , mStartValue(startValue) - , mEndValue(endValue) {} + : PropertyValuesHolderImpl(startValue, endValue) + , mTree(tree) { + mEvaluator.reset(new FloatEvaluator()); + } void setFraction(float fraction) override; private: VectorDrawable::Tree* mTree; - float mStartValue; - float mEndValue; }; } } diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index b49f9b529989..0c552bac1d7f 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -127,7 +127,8 @@ int RecordingCanvas::saveLayer(float left, float top, float right, float bottom, // operations will be able to store and restore the current clip and transform info, and // quick rejection will be correct (for display lists) - const Rect unmappedBounds(left, top, right, bottom); + Rect unmappedBounds(left, top, right, bottom); + unmappedBounds.roundOut(); // determine clipped bounds relative to previous viewport. Rect visibleBounds = unmappedBounds; @@ -148,50 +149,53 @@ int RecordingCanvas::saveLayer(float left, float top, float right, float bottom, // Map visible bounds back to layer space, and intersect with parameter bounds Rect layerBounds = visibleBounds; - Matrix4 inverse; - inverse.loadInverse(*previous.transform); - inverse.mapRect(layerBounds); - layerBounds.doIntersect(unmappedBounds); + if (CC_LIKELY(!layerBounds.isEmpty())) { + // if non-empty, can safely map by the inverse transform + Matrix4 inverse; + inverse.loadInverse(*previous.transform); + inverse.mapRect(layerBounds); + layerBounds.doIntersect(unmappedBounds); + } int saveValue = mState.save((int) flags); Snapshot& snapshot = *mState.writableSnapshot(); // layerBounds is in original bounds space, but clipped by current recording clip - if (layerBounds.isEmpty() || unmappedBounds.isEmpty()) { - // Don't bother recording layer, since it's been rejected + if (!layerBounds.isEmpty() && !unmappedBounds.isEmpty()) { if (CC_LIKELY(clippedLayer)) { - snapshot.resetClip(0, 0, 0, 0); + auto previousClip = getRecordedClip(); // capture before new snapshot clip has changed + if (addOp(alloc().create_trivial<BeginLayerOp>( + unmappedBounds, + *previous.transform, // transform to *draw* with + previousClip, // clip to *draw* with + refPaint(paint))) >= 0) { + snapshot.flags |= Snapshot::kFlagIsLayer | Snapshot::kFlagIsFboLayer; + snapshot.initializeViewport(unmappedBounds.getWidth(), unmappedBounds.getHeight()); + snapshot.transform->loadTranslate(-unmappedBounds.left, -unmappedBounds.top, 0.0f); + + Rect clip = layerBounds; + clip.translate(-unmappedBounds.left, -unmappedBounds.top); + snapshot.resetClip(clip.left, clip.top, clip.right, clip.bottom); + snapshot.roundRectClipState = nullptr; + return saveValue; + } + } else { + if (addOp(alloc().create_trivial<BeginUnclippedLayerOp>( + unmappedBounds, + *mState.currentSnapshot()->transform, + getRecordedClip(), + refPaint(paint))) >= 0) { + snapshot.flags |= Snapshot::kFlagIsLayer; + return saveValue; + } } - return saveValue; } + // Layer not needed, so skip recording it... if (CC_LIKELY(clippedLayer)) { - auto previousClip = getRecordedClip(); // note: done before new snapshot's clip has changed - - snapshot.flags |= Snapshot::kFlagIsLayer | Snapshot::kFlagIsFboLayer; - snapshot.initializeViewport(unmappedBounds.getWidth(), unmappedBounds.getHeight()); - snapshot.transform->loadTranslate(-unmappedBounds.left, -unmappedBounds.top, 0.0f); - - Rect clip = layerBounds; - clip.translate(-unmappedBounds.left, -unmappedBounds.top); - snapshot.resetClip(clip.left, clip.top, clip.right, clip.bottom); - snapshot.roundRectClipState = nullptr; - - addOp(alloc().create_trivial<BeginLayerOp>( - unmappedBounds, - *previous.transform, // transform to *draw* with - previousClip, // clip to *draw* with - refPaint(paint))); - } else { - snapshot.flags |= Snapshot::kFlagIsLayer; - - addOp(alloc().create_trivial<BeginUnclippedLayerOp>( - unmappedBounds, - *mState.currentSnapshot()->transform, - getRecordedClip(), - refPaint(paint))); + // ... and set empty clip to reject inner content, if possible + snapshot.resetClip(0, 0, 0, 0); } - return saveValue; } @@ -267,7 +271,7 @@ static Rect calcBoundsOfPoints(const float* points, int floatCount) { // Geometry void RecordingCanvas::drawPoints(const float* points, int floatCount, const SkPaint& paint) { - if (floatCount < 2) return; + if (CC_UNLIKELY(floatCount < 2 || PaintUtils::paintWillNotDraw(paint))) return; floatCount &= ~0x1; // round down to nearest two addOp(alloc().create_trivial<PointsOp>( @@ -278,7 +282,7 @@ void RecordingCanvas::drawPoints(const float* points, int floatCount, const SkPa } void RecordingCanvas::drawLines(const float* points, int floatCount, const SkPaint& paint) { - if (floatCount < 4) return; + if (CC_UNLIKELY(floatCount < 4 || PaintUtils::paintWillNotDraw(paint))) return; floatCount &= ~0x3; // round down to nearest four addOp(alloc().create_trivial<LinesOp>( @@ -289,6 +293,8 @@ void RecordingCanvas::drawLines(const float* points, int floatCount, const SkPai } void RecordingCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) { + if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return; + addOp(alloc().create_trivial<RectOp>( Rect(left, top, right, bottom), *(mState.currentSnapshot()->transform), @@ -330,6 +336,8 @@ void RecordingCanvas::drawSimpleRects(const float* rects, int vertexCount, const } void RecordingCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) { + if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return; + if (paint.getStyle() == SkPaint::kFill_Style && (!paint.isAntiAlias() || mState.currentTransform()->isSimple())) { int count = 0; @@ -354,8 +362,11 @@ void RecordingCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) { } } } + void RecordingCanvas::drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, const SkPaint& paint) { + if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return; + if (CC_LIKELY(MathUtils::isPositive(rx) || MathUtils::isPositive(ry))) { addOp(alloc().create_trivial<RoundRectOp>( Rect(left, top, right, bottom), @@ -390,7 +401,8 @@ void RecordingCanvas::drawRoundRect( void RecordingCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) { // TODO: move to Canvas.h - if (radius <= 0) return; + if (CC_UNLIKELY(radius <= 0 || PaintUtils::paintWillNotDraw(paint))) return; + drawOval(x - radius, y - radius, x + radius, y + radius, paint); } @@ -410,6 +422,8 @@ void RecordingCanvas::drawCircle( } void RecordingCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) { + if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return; + addOp(alloc().create_trivial<OvalOp>( Rect(left, top, right, bottom), *(mState.currentSnapshot()->transform), @@ -419,6 +433,8 @@ void RecordingCanvas::drawOval(float left, float top, float right, float bottom, void RecordingCanvas::drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) { + if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return; + if (fabs(sweepAngle) >= 360.0f) { drawOval(left, top, right, bottom, paint); } else { @@ -432,6 +448,8 @@ void RecordingCanvas::drawArc(float left, float top, float right, float bottom, } void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) { + if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return; + addOp(alloc().create_trivial<PathOp>( Rect(path.getBounds()), *(mState.currentSnapshot()->transform), @@ -440,8 +458,8 @@ void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) { } void RecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) { - mDisplayList->pushStagingFunctors.push_back(tree->getFunctor()); mDisplayList->ref(tree); + mDisplayList->vectorDrawables.push_back(tree); addOp(alloc().create_trivial<VectorDrawableOp>( tree, Rect(tree->stagingProperties()->getBounds()), @@ -604,7 +622,7 @@ void RecordingCanvas::callDrawGLFunction(Functor* functor, functor)); } -size_t RecordingCanvas::addOp(RecordedOp* op) { +int RecordingCanvas::addOp(RecordedOp* op) { // skip op with empty clip if (op->localClip && op->localClip->rect.isEmpty()) { // NOTE: this rejection happens after op construction/content ref-ing, so content ref'd diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h index 372be241042a..337e97bf450b 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -208,7 +208,7 @@ private: void drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint); - size_t addOp(RecordedOp* op); + int addOp(RecordedOp* op); // ---------------------------------------------------------------------------- // lazy object copy // ---------------------------------------------------------------------------- diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h index de4fa55bb508..5786668addc5 100644 --- a/libs/hwui/Rect.h +++ b/libs/hwui/Rect.h @@ -73,6 +73,13 @@ public: bottom(height) { } + inline Rect(const SkIRect& rect): + left(rect.fLeft), + top(rect.fTop), + right(rect.fRight), + bottom(rect.fBottom) { + } + inline Rect(const SkRect& rect): left(rect.fLeft), top(rect.fTop), diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index a393625ee616..bdcad798f05e 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -422,6 +422,16 @@ void RenderNode::prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer) { pushStagingDisplayListChanges(info); } prepareSubTree(info, childFunctorsNeedLayer, mDisplayList); + + if (mDisplayList) { + for (auto& vectorDrawable : mDisplayList->getVectorDrawables()) { + // If any vector drawable in the display list needs update, damage the node. + if (vectorDrawable->isDirty()) { + damageSelf(info); + } + vectorDrawable->setPropertyChangeWillBeConsumed(true); + } + } pushLayerUpdate(info); info.damageAccumulator->popTransform(); @@ -482,8 +492,8 @@ void RenderNode::syncDisplayList(TreeInfo* info) { for (auto& iter : mDisplayList->getFunctors()) { (*iter.functor)(DrawGlInfo::kModeSync, nullptr); } - for (size_t i = 0; i < mDisplayList->getPushStagingFunctors().size(); i++) { - (*mDisplayList->getPushStagingFunctors()[i])(); + for (auto& vectorDrawable : mDisplayList->getVectorDrawables()) { + vectorDrawable->syncProperties(); } } } diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h index f80be5ec9ae9..47fef6da355c 100644 --- a/libs/hwui/RenderNode.h +++ b/libs/hwui/RenderNode.h @@ -232,7 +232,7 @@ public: // the frameNumber to appropriately batch/synchronize these transactions. // There is no other filtering/batching to ensure that only the "final" // state called once per frame. - class ANDROID_API PositionListener { + class ANDROID_API PositionListener : public VirtualLightRefBase { public: virtual ~PositionListener() {} // Called when the RenderNode's position changes @@ -247,7 +247,7 @@ public: // before the RenderNode is used for drawing. // RenderNode takes ownership of the pointer ANDROID_API void setPositionListener(PositionListener* listener) { - mPositionListener.reset(listener); + mPositionListener = listener; } // This is only modified in MODE_FULL, so it can be safely accessed @@ -366,7 +366,7 @@ private: // mDisplayList, not mStagingDisplayList. uint32_t mParentCount; - std::unique_ptr<PositionListener> mPositionListener; + sp<PositionListener> mPositionListener; }; // class RenderNode } /* namespace uirenderer */ diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp index ade8600ab78b..523924af5ef1 100644 --- a/libs/hwui/TextureCache.cpp +++ b/libs/hwui/TextureCache.cpp @@ -167,6 +167,10 @@ bool TextureCache::prefetchAndMarkInUse(void* ownerToken, const SkBitmap* bitmap return texture; } +bool TextureCache::prefetch(const SkBitmap* bitmap) { + return getCachedTexture(bitmap, AtlasUsageType::Use); +} + Texture* TextureCache::get(const SkBitmap* bitmap, AtlasUsageType atlasUsageType) { Texture* texture = getCachedTexture(bitmap, atlasUsageType); diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h index a4317cee73fd..0a61b6b1a522 100644 --- a/libs/hwui/TextureCache.h +++ b/libs/hwui/TextureCache.h @@ -78,6 +78,13 @@ public: bool prefetchAndMarkInUse(void* ownerToken, const SkBitmap* bitmap); /** + * Attempts to precache the SkBitmap. Returns true if a Texture was successfully + * acquired for the bitmap, false otherwise. Does not mark the Texture + * as in use and won't update currently in-use Textures. + */ + bool prefetch(const SkBitmap* bitmap); + + /** * Returns the texture associated with the specified bitmap from either within the cache, or * the AssetAtlas. If the texture cannot be found in the cache, a new texture is generated. */ diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h index a5d1d4b86673..a0c3d9db8ae1 100644 --- a/libs/hwui/VectorDrawable.h +++ b/libs/hwui/VectorDrawable.h @@ -673,21 +673,17 @@ public: void onPropertyChanged(TreeProperties* prop); TreeProperties* mutateStagingProperties() { return &mStagingProperties; } const TreeProperties* stagingProperties() const { return &mStagingProperties; } - PushStagingFunctor* getFunctor() { return &mFunctor;} // This should only be called from animations on RT TreeProperties* mutateProperties() { return &mProperties; } + // This should always be called from RT. + void markDirty() { mCache.dirty = true; } + bool isDirty() const { return mCache.dirty; } + bool getPropertyChangeWillBeConsumed() const { return mWillBeConsumed; } + void setPropertyChangeWillBeConsumed(bool willBeConsumed) { mWillBeConsumed = willBeConsumed; } + private: - class VectorDrawableFunctor : public PushStagingFunctor { - public: - VectorDrawableFunctor(Tree* tree) : mTree(tree) {} - virtual void operator ()() { - mTree->syncProperties(); - } - private: - Tree* mTree; - }; SkPaint* updatePaint(SkPaint* outPaint, TreeProperties* prop); bool allocateBitmapIfNeeded(SkBitmap* outCache, int width, int height); @@ -704,8 +700,6 @@ private: TreeProperties mProperties = TreeProperties(this); TreeProperties mStagingProperties = TreeProperties(this); - VectorDrawableFunctor mFunctor = VectorDrawableFunctor(this); - SkPaint mPaint; struct Cache { SkBitmap bitmap; @@ -717,6 +711,8 @@ private: PropertyChangedListener mPropertyChangedListener = PropertyChangedListener(&mCache.dirty, &mStagingCache.dirty); + + mutable bool mWillBeConsumed = false; }; } // namespace VectorDrawable diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index c626c5452310..dcaec428449c 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -67,7 +67,7 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, , mEglManager(thread.eglManager()) , mOpaque(!translucent) , mAnimationContext(contextFactory->createAnimationContext(mRenderThread.timeLord())) - , mJankTracker(thread.timeLord().frameIntervalNanos()) + , mJankTracker(thread.mainDisplayInfo()) , mProfiler(mFrames) , mContentDrawBounds(0, 0, 0, 0) { mRenderNodes.emplace_back(rootRenderNode); @@ -198,6 +198,48 @@ static bool wasSkipped(FrameInfo* info) { return info && ((*info)[FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame); } +bool CanvasContext::isSwapChainStuffed() { + static const auto SLOW_THRESHOLD = 6_ms; + + if (mSwapHistory.size() != mSwapHistory.capacity()) { + // We want at least 3 frames of history before attempting to + // guess if the queue is stuffed + return false; + } + nsecs_t frameInterval = mRenderThread.timeLord().frameIntervalNanos(); + auto& swapA = mSwapHistory[0]; + + // Was there a happy queue & dequeue time? If so, don't + // consider it stuffed + if (swapA.dequeueDuration < SLOW_THRESHOLD + && swapA.queueDuration < SLOW_THRESHOLD) { + return false; + } + + for (size_t i = 1; i < mSwapHistory.size(); i++) { + auto& swapB = mSwapHistory[i]; + + // If there's a multi-frameInterval gap we effectively already dropped a frame, + // so consider the queue healthy. + if (swapA.swapCompletedTime - swapB.swapCompletedTime > frameInterval * 3) { + return false; + } + + // Was there a happy queue & dequeue time? If so, don't + // consider it stuffed + if (swapB.dequeueDuration < SLOW_THRESHOLD + && swapB.queueDuration < SLOW_THRESHOLD) { + return false; + } + + swapA = swapB; + } + + // All signs point to a stuffed swap chain + ATRACE_NAME("swap chain stuffed"); + return true; +} + void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued, RenderNode* target) { mRenderThread.removeFrameCallback(this); @@ -243,7 +285,7 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, if (CC_LIKELY(mSwapHistory.size())) { nsecs_t latestVsync = mRenderThread.timeLord().latestVsync(); - const SwapHistory& lastSwap = mSwapHistory.back(); + SwapHistory& lastSwap = mSwapHistory.back(); nsecs_t vsyncDelta = std::abs(lastSwap.vsyncTime - latestVsync); // The slight fudge-factor is to deal with cases where // the vsync was estimated due to being slow handling the signal. @@ -253,15 +295,17 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, // Already drew for this vsync pulse, UI draw request missed // the deadline for RT animations info.out.canDrawThisFrame = false; - } else if (lastSwap.swapTime < latestVsync) { + } else if (vsyncDelta >= mRenderThread.timeLord().frameIntervalNanos() * 3 + || (latestVsync - mLastDropVsync) < 500_ms) { + // It's been several frame intervals, assume the buffer queue is fine + // or the last drop was too recent info.out.canDrawThisFrame = true; } else { - // We're maybe behind? Find out for sure - int runningBehind = 0; - // TODO: Have this method be on Surface, too, not just ANativeWindow... - ANativeWindow* window = mNativeSurface.get(); - window->query(window, NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind); - info.out.canDrawThisFrame = !runningBehind; + info.out.canDrawThisFrame = !isSwapChainStuffed(); + if (!info.out.canDrawThisFrame) { + // dropping frame + mLastDropVsync = mRenderThread.timeLord().latestVsync(); + } } } else { info.out.canDrawThisFrame = true; @@ -282,6 +326,7 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, void CanvasContext::stopDrawing() { mRenderThread.removeFrameCallback(this); + mAnimationContext->pauseAnimators(); } void CanvasContext::notifyFramePending() { @@ -515,10 +560,27 @@ void CanvasContext::draw() { } SwapHistory& swap = mSwapHistory.next(); swap.damage = screenDirty; - swap.swapTime = systemTime(CLOCK_MONOTONIC); + swap.swapCompletedTime = systemTime(CLOCK_MONOTONIC); swap.vsyncTime = mRenderThread.timeLord().latestVsync(); + if (mNativeSurface.get()) { + int durationUs; + mNativeSurface->query(NATIVE_WINDOW_LAST_DEQUEUE_DURATION, &durationUs); + swap.dequeueDuration = us2ns(durationUs); + mNativeSurface->query(NATIVE_WINDOW_LAST_QUEUE_DURATION, &durationUs); + swap.queueDuration = us2ns(durationUs); + } else { + swap.dequeueDuration = 0; + swap.queueDuration = 0; + } + mCurrentFrameInfo->set(FrameInfoIndex::DequeueBufferDuration) + = swap.dequeueDuration; + mCurrentFrameInfo->set(FrameInfoIndex::QueueBufferDuration) + = swap.queueDuration; mHaveNewSurface = false; mFrameNumber = -1; + } else { + mCurrentFrameInfo->set(FrameInfoIndex::DequeueBufferDuration) = 0; + mCurrentFrameInfo->set(FrameInfoIndex::QueueBufferDuration) = 0; } // TODO: Use a fence for real completion? @@ -783,6 +845,7 @@ void CanvasContext::enqueueFrameWork(std::function<void()>&& func) { } sp<FuncTask> task(new FuncTask()); task->func = func; + mFrameFences.push_back(task); mFrameWorkProcessor->add(task); } diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index a6eb7adc3568..3eef29b24473 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -180,6 +180,8 @@ private: void waitOnFences(); + bool isSwapChainStuffed(); + EGLint mLastFrameWidth = 0; EGLint mLastFrameHeight = 0; @@ -198,12 +200,17 @@ private: struct SwapHistory { SkRect damage; nsecs_t vsyncTime; - nsecs_t swapTime; + nsecs_t swapCompletedTime; + nsecs_t dequeueDuration; + nsecs_t queueDuration; }; RingBuffer<SwapHistory, 3> mSwapHistory; int64_t mFrameNumber = -1; + // last vsync for a dropped frame due to stuffed queue + nsecs_t mLastDropVsync = 0; + bool mOpaque; #if HWUI_NEW_OPS BakedOpRenderer::LightInfo mLightInfo; diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 54af2829cf40..a734401a2be6 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -22,9 +22,11 @@ #include "Readback.h" #include "Rect.h" #include "renderthread/CanvasContext.h" +#include "renderthread/EglManager.h" #include "renderthread/RenderTask.h" #include "renderthread/RenderThread.h" #include "utils/Macros.h" +#include "utils/TimeUtils.h" namespace android { namespace uirenderer { @@ -44,6 +46,8 @@ namespace renderthread { typedef struct { \ a1; a2; a3; a4; a5; a6; a7; a8; \ } ARGS(name); \ + static_assert(std::is_trivially_destructible<ARGS(name)>::value, \ + "Error, ARGS must be trivially destructible!"); \ static void* Bridge_ ## name(ARGS(name)* args) #define SETUP_TASK(method) \ @@ -154,7 +158,7 @@ void RenderProxy::updateSurface(const sp<Surface>& surface) { SETUP_TASK(updateSurface); args->context = mContext; args->surface = surface.get(); - postAndWait(task); + post(task); } CREATE_BRIDGE2(pauseSurface, CanvasContext* context, Surface* surface) { @@ -514,6 +518,10 @@ void RenderProxy::setProcessStatsBuffer(int fd) { post(task); } +int RenderProxy::getRenderThreadTid() { + return mRenderThread.getTid(); +} + CREATE_BRIDGE3(addRenderNode, CanvasContext* context, RenderNode* node, bool placeFront) { args->context->addRenderNode(args->node, args->placeFront); return nullptr; @@ -632,6 +640,41 @@ int RenderProxy::copySurfaceInto(sp<Surface>& surface, SkBitmap* bitmap) { reinterpret_cast<intptr_t>( staticPostAndWait(task) )); } +CREATE_BRIDGE2(prepareToDraw, RenderThread* thread, SkBitmap* bitmap) { + if (Caches::hasInstance() && args->thread->eglManager().hasEglContext()) { + ATRACE_NAME("Bitmap#prepareToDraw task"); + Caches::getInstance().textureCache.prefetch(args->bitmap); + } + delete args->bitmap; + args->bitmap = nullptr; + return nullptr; +} + +void RenderProxy::prepareToDraw(const SkBitmap& bitmap) { + // If we haven't spun up a hardware accelerated window yet, there's no + // point in precaching these bitmaps as it can't impact jank. + // We also don't know if we even will spin up a hardware-accelerated + // window or not. + if (!RenderThread::hasInstance()) return; + RenderThread* renderThread = &RenderThread::getInstance(); + SETUP_TASK(prepareToDraw); + args->thread = renderThread; + args->bitmap = new SkBitmap(bitmap); + nsecs_t lastVsync = renderThread->timeLord().latestVsync(); + nsecs_t estimatedNextVsync = lastVsync + renderThread->timeLord().frameIntervalNanos(); + nsecs_t timeToNextVsync = estimatedNextVsync - systemTime(CLOCK_MONOTONIC); + // We expect the UI thread to take 4ms and for RT to be active from VSYNC+4ms to + // VSYNC+12ms or so, so aim for the gap during which RT is expected to + // be idle + // TODO: Make this concept a first-class supported thing? RT could use + // knowledge of pending draws to better schedule this task + if (timeToNextVsync > -6_ms && timeToNextVsync < 1_ms) { + renderThread->queueAt(task, estimatedNextVsync + 8_ms); + } else { + renderThread->queue(task); + } +} + void RenderProxy::post(RenderTask* task) { mRenderThread.queue(task); } diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index 898b31421aad..bb111bd0de25 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -115,6 +115,7 @@ public: ANDROID_API void setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t size); ANDROID_API void setProcessStatsBuffer(int fd); + ANDROID_API int getRenderThreadTid(); ANDROID_API void serializeDisplayListTree(); @@ -128,6 +129,7 @@ public: ANDROID_API long getDroppedFrameReportCount(); ANDROID_API static int copySurfaceInto(sp<Surface>& surface, SkBitmap* bitmap); + ANDROID_API static void prepareToDraw(const SkBitmap& bitmap); private: RenderThread& mRenderThread; diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index 9fb30c928c00..f4b44164b84e 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -190,7 +190,7 @@ void RenderThread::initThreadLocals() { initializeDisplayEventReceiver(); mEglManager = new EglManager(*this); mRenderState = new RenderState(*this); - mJankTracker = new JankTracker(frameIntervalNanos); + mJankTracker = new JankTracker(mDisplayInfo); } int RenderThread::displayEventReceiverCallback(int fd, int events, void* data) { diff --git a/libs/hwui/tests/common/TestContext.cpp b/libs/hwui/tests/common/TestContext.cpp index 146e735839d1..99569755205f 100644 --- a/libs/hwui/tests/common/TestContext.cpp +++ b/libs/hwui/tests/common/TestContext.cpp @@ -33,7 +33,6 @@ static android::DisplayInfo DUMMY_DISPLAY { false, // secure? 0, // appVsyncOffset 0, // presentationDeadline - 0, // colorTransform }; DisplayInfo getBuiltInDisplay() { diff --git a/libs/hwui/tests/unit/ClipAreaTests.cpp b/libs/hwui/tests/unit/ClipAreaTests.cpp index 54ca68d63dbe..d4d7919f9eee 100644 --- a/libs/hwui/tests/unit/ClipAreaTests.cpp +++ b/libs/hwui/tests/unit/ClipAreaTests.cpp @@ -275,5 +275,73 @@ TEST(ClipArea, serializeIntersectedClip_snap) { } } +TEST(ClipArea, serializeIntersectedClip_scale) { + ClipArea area(createClipArea()); + area.setClip(0, 0, 400, 400); + LinearAllocator allocator; + + SkPath circlePath; + circlePath.addCircle(50, 50, 50); + + ClipRegion recordedClip; + recordedClip.region.setPath(circlePath, SkRegion(SkIRect::MakeWH(100, 100))); + recordedClip.rect = Rect(100, 100); + + Matrix4 translateScale; + translateScale.loadTranslate(100, 100, 0); + translateScale.scale(2, 2, 1); + auto resolvedClip = area.serializeIntersectedClip(allocator, &recordedClip, translateScale); + + ASSERT_NE(nullptr, resolvedClip); + EXPECT_EQ(ClipMode::Region, resolvedClip->mode); + EXPECT_EQ(Rect(100, 100, 300, 300), resolvedClip->rect); + auto clipRegion = reinterpret_cast<const ClipRegion*>(resolvedClip); + EXPECT_EQ(SkIRect::MakeLTRB(100, 100, 300, 300), clipRegion->region.getBounds()); +} + +TEST(ClipArea, applyTransformToRegion_identity) { + SkRegion region(SkIRect::MakeLTRB(1, 2, 3, 4)); + ClipArea::applyTransformToRegion(Matrix4::identity(), ®ion); + EXPECT_TRUE(region.isRect()); + EXPECT_EQ(SkIRect::MakeLTRB(1, 2, 3, 4), region.getBounds()); +} + +TEST(ClipArea, applyTransformToRegion_translate) { + SkRegion region(SkIRect::MakeLTRB(1, 2, 3, 4)); + Matrix4 transform; + transform.loadTranslate(10, 20, 0); + ClipArea::applyTransformToRegion(transform, ®ion); + EXPECT_TRUE(region.isRect()); + EXPECT_EQ(SkIRect::MakeLTRB(11, 22, 13, 24), region.getBounds()); +} + +TEST(ClipArea, applyTransformToRegion_scale) { + SkRegion region(SkIRect::MakeLTRB(1, 2, 3, 4)); + Matrix4 transform; + transform.loadScale(2, 3, 1); + ClipArea::applyTransformToRegion(transform, ®ion); + EXPECT_TRUE(region.isRect()); + EXPECT_EQ(SkIRect::MakeLTRB(2, 6, 6, 12), region.getBounds()); +} + +TEST(ClipArea, applyTransformToRegion_translateScale) { + SkRegion region(SkIRect::MakeLTRB(1, 2, 3, 4)); + Matrix4 transform; + transform.translate(10, 20); + transform.scale(2, 3, 1); + ClipArea::applyTransformToRegion(transform, ®ion); + EXPECT_TRUE(region.isRect()); + EXPECT_EQ(SkIRect::MakeLTRB(12, 26, 16, 32), region.getBounds()); +} + +TEST(ClipArea, applyTransformToRegion_rotate90) { + SkRegion region(SkIRect::MakeLTRB(1, 2, 3, 4)); + Matrix4 transform; + transform.loadRotate(90); + ClipArea::applyTransformToRegion(transform, ®ion); + EXPECT_TRUE(region.isRect()); + EXPECT_EQ(SkIRect::MakeLTRB(-4, 1, -2, 3), region.getBounds()); +} + } // namespace uirenderer } // namespace android diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp index af54e079daab..53dbede2f8e1 100644 --- a/libs/hwui/tests/unit/FrameBuilderTests.cpp +++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp @@ -1459,7 +1459,8 @@ RENDERTHREAD_TEST(FrameBuilder, buildLayer) { static void drawOrderedRect(RecordingCanvas* canvas, uint8_t expectedDrawOrder) { SkPaint paint; - paint.setColor(SkColorSetARGB(256, 0, 0, expectedDrawOrder)); // order put in blue channel + // order put in blue channel, transparent so overlapped content doesn't get rejected + paint.setColor(SkColorSetARGB(1, 0, 0, expectedDrawOrder)); canvas->drawRect(0, 0, 100, 100, paint); } static void drawOrderedNode(RecordingCanvas* canvas, uint8_t expectedDrawOrder, float z) { diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp index 18171de250d0..c072d0b80135 100644 --- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp +++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp @@ -81,6 +81,27 @@ TEST(RecordingCanvas, emptyClipRect) { ASSERT_EQ(0u, dl->getOps().size()) << "Must be zero ops. Rect should be rejected."; } +TEST(RecordingCanvas, emptyPaintRejection) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + SkPaint emptyPaint; + emptyPaint.setColor(Color::Transparent); + + float points[] = {0, 0, 200, 200}; + canvas.drawPoints(points, 4, emptyPaint); + canvas.drawLines(points, 4, emptyPaint); + canvas.drawRect(0, 0, 200, 200, emptyPaint); + canvas.drawRegion(SkRegion(SkIRect::MakeWH(200, 200)), emptyPaint); + canvas.drawRoundRect(0, 0, 200, 200, 10, 10, emptyPaint); + canvas.drawCircle(100, 100, 100, emptyPaint); + canvas.drawOval(0, 0, 200, 200, emptyPaint); + canvas.drawArc(0, 0, 200, 200, 0, 360, true, emptyPaint); + SkPath path; + path.addRect(0, 0, 200, 200); + canvas.drawPath(path, emptyPaint); + }); + EXPECT_EQ(0u, dl->getOps().size()) << "Op should be rejected"; +} + TEST(RecordingCanvas, drawArc) { auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { canvas.drawArc(0, 0, 200, 200, 0, 180, true, SkPaint()); @@ -340,6 +361,36 @@ TEST(RecordingCanvas, saveLayer_simple) { EXPECT_EQ(3, count); } +TEST(RecordingCanvas, saveLayer_rounding) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) { + canvas.saveLayerAlpha(10.25f, 10.75f, 89.25f, 89.75f, 128, SaveFlags::ClipToLayer); + canvas.drawRect(20, 20, 80, 80, SkPaint()); + canvas.restore(); + }); + int count = 0; + playbackOps(*dl, [&count](const RecordedOp& op) { + Matrix4 expectedMatrix; + switch(count++) { + case 0: + EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId); + EXPECT_EQ(Rect(10, 10, 90, 90), op.unmappedBounds) << "Expect bounds rounded out"; + break; + case 1: + EXPECT_EQ(RecordedOpId::RectOp, op.opId); + expectedMatrix.loadTranslate(-10, -10, 0); + EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix) << "Expect rounded offset"; + break; + case 2: + EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId); + // Don't bother asserting recording state data - it's not used + break; + default: + ADD_FAILURE(); + } + }); + EXPECT_EQ(3, count); +} + TEST(RecordingCanvas, saveLayer_missingRestore) { auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { canvas.saveLayerAlpha(0, 0, 200, 200, 128, SaveFlags::ClipToLayer); @@ -494,6 +545,21 @@ TEST(RecordingCanvas, saveLayer_rotateClipped) { EXPECT_EQ(3, count); } +TEST(RecordingCanvas, saveLayer_rejectBegin) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + canvas.save(SaveFlags::MatrixClip); + canvas.translate(0, -20); // avoid identity case + // empty clip rect should force layer + contents to be rejected + canvas.clipRect(0, -20, 200, -20, SkRegion::kIntersect_Op); + canvas.saveLayerAlpha(0, 0, 200, 200, 128, SaveFlags::ClipToLayer); + canvas.drawRect(0, 0, 200, 200, SkPaint()); + canvas.restore(); + canvas.restore(); + }); + + ASSERT_EQ(0u, dl->getOps().size()) << "Begin/Rect/End should all be rejected."; +} + TEST(RecordingCanvas, drawRenderNode_rejection) { auto child = TestUtils::createNode(50, 50, 150, 150, [](RenderProperties& props, RecordingCanvas& canvas) { diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp index b2997dfb357f..cf76a8691dcd 100644 --- a/libs/hwui/tests/unit/RenderNodeTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeTests.cpp @@ -16,13 +16,26 @@ #include <gtest/gtest.h> +#include "AnimationContext.h" +#include "DamageAccumulator.h" +#include "IContextFactory.h" #include "RenderNode.h" #include "TreeInfo.h" +#include "renderthread/CanvasContext.h" #include "tests/common/TestUtils.h" #include "utils/Color.h" using namespace android; using namespace android::uirenderer; +using namespace android::uirenderer::renderthread; + +class ContextFactory : public android::uirenderer::IContextFactory { +public: + android::uirenderer::AnimationContext* createAnimationContext + (android::uirenderer::renderthread::TimeLord& clock) override { + return new android::uirenderer::AnimationContext(clock); + } +}; TEST(RenderNode, hasParents) { auto child = TestUtils::createNode(0, 0, 200, 400, @@ -89,3 +102,31 @@ TEST(RenderNode, releasedCallback) { TestUtils::syncHierarchyPropertiesAndDisplayList(node); EXPECT_EQ(0, refcnt); } + +RENDERTHREAD_TEST(RenderNode, prepareTree_nullableDisplayList) { + ContextFactory contextFactory; + CanvasContext canvasContext(renderThread, false, nullptr, &contextFactory); + TreeInfo info(TreeInfo::MODE_RT_ONLY, canvasContext); + DamageAccumulator damageAccumulator; + info.damageAccumulator = &damageAccumulator; + info.observer = nullptr; + + { + auto nonNullDLNode = TestUtils::createNode(0, 0, 200, 400, + [](RenderProperties& props, TestCanvas& canvas) { + canvas.drawColor(Color::Red_500, SkXfermode::kSrcOver_Mode); + }); + TestUtils::syncHierarchyPropertiesAndDisplayList(nonNullDLNode); + EXPECT_TRUE(nonNullDLNode->getDisplayList()); + nonNullDLNode->prepareTree(info); + } + + { + auto nullDLNode = TestUtils::createNode(0, 0, 200, 400, nullptr); + TestUtils::syncHierarchyPropertiesAndDisplayList(nullDLNode); + EXPECT_FALSE(nullDLNode->getDisplayList()); + nullDLNode->prepareTree(info); + } + + canvasContext.destroy(nullptr); +} diff --git a/libs/hwui/utils/TimeUtils.h b/libs/hwui/utils/TimeUtils.h index 8d42d7e55521..ce181b766841 100644 --- a/libs/hwui/utils/TimeUtils.h +++ b/libs/hwui/utils/TimeUtils.h @@ -21,10 +21,18 @@ namespace android { namespace uirenderer { +constexpr nsecs_t operator"" _s (unsigned long long s) { + return seconds_to_nanoseconds(s); +} + constexpr nsecs_t operator"" _ms (unsigned long long ms) { return milliseconds_to_nanoseconds(ms); } +constexpr nsecs_t operator"" _us (unsigned long long us) { + return microseconds_to_nanoseconds(us); +} + } /* namespace uirenderer */ } /* namespace android */ |