diff options
Diffstat (limited to 'libs')
53 files changed, 1154 insertions, 224 deletions
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp index 07044d0e9d61..4c1c1b935980 100644 --- a/libs/androidfw/AssetManager.cpp +++ b/libs/androidfw/AssetManager.cpp @@ -79,6 +79,7 @@ static volatile int32_t gCount = 0; const char* AssetManager::RESOURCES_FILENAME = "resources.arsc"; const char* AssetManager::IDMAP_BIN = "/system/bin/idmap"; const char* AssetManager::OVERLAY_DIR = "/vendor/overlay"; +const char* AssetManager::OVERLAY_SKU_DIR_PROPERTY = "ro.boot.vendor.overlay.sku"; const char* AssetManager::TARGET_PACKAGE_NAME = "android"; const char* AssetManager::TARGET_APK_PATH = "/system/framework/framework-res.apk"; const char* AssetManager::IDMAP_DIR = "/data/resource-cache"; diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 5dcfa4e102fd..a7cbf5e562d1 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -3,6 +3,7 @@ include $(CLEAR_VARS) LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk HWUI_NEW_OPS := true +BUGREPORT_FONT_CACHE_USAGE := false # Enables fine-grained GLES error checking # If set to true, every GLES call is wrapped & error checked @@ -113,6 +114,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 @@ -131,6 +136,13 @@ ifeq (true, $(HWUI_NEW_OPS)) endif +ifeq (true, $(BUGREPORT_FONT_CACHE_USAGE)) + hwui_src_files += \ + font/FontCacheHistoryTracker.cpp + hwui_cflags += -DBUGREPORT_FONT_CACHE_USAGE +endif + + ifndef HWUI_COMPILE_SYMBOLS hwui_cflags += -fvisibility=hidden endif diff --git a/libs/hwui/AnimationContext.h b/libs/hwui/AnimationContext.h index 395dc73cfdf0..71ee8602a29f 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 1954b88107a3..aac355c11ac9 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/Caches.cpp b/libs/hwui/Caches.cpp index 949ad450d5f7..a8ced9b2597b 100644 --- a/libs/hwui/Caches.cpp +++ b/libs/hwui/Caches.cpp @@ -21,6 +21,9 @@ #include "Properties.h" #include "renderstate/RenderState.h" #include "ShadowTessellator.h" +#ifdef BUGREPORT_FONT_CACHE_USAGE +#include "font/FontCacheHistoryTracker.h" +#endif #include "utils/GLUtils.h" #include <cutils/properties.h> @@ -195,12 +198,7 @@ void Caches::dumpMemoryUsage(String8 &log) { log.appendFormat(" PatchCache %8d / %8d\n", patchCache.getSize(), patchCache.getMaxSize()); - const uint32_t sizeA8 = fontRenderer.getFontRendererSize(GL_ALPHA); - const uint32_t sizeRGBA = fontRenderer.getFontRendererSize(GL_RGBA); - log.appendFormat(" FontRenderer A8 %8d / %8d\n", sizeA8, sizeA8); - log.appendFormat(" FontRenderer RGBA %8d / %8d\n", sizeRGBA, sizeRGBA); - log.appendFormat(" FontRenderer total %8d / %8d\n", sizeA8 + sizeRGBA, - sizeA8 + sizeRGBA); + fontRenderer.dumpMemoryUsage(log); log.appendFormat("Other:\n"); log.appendFormat(" FboCache %8d / %8d\n", @@ -213,11 +211,14 @@ void Caches::dumpMemoryUsage(String8 &log) { total += tessellationCache.getSize(); total += dropShadowCache.getSize(); total += patchCache.getSize(); - total += fontRenderer.getFontRendererSize(GL_ALPHA); - total += fontRenderer.getFontRendererSize(GL_RGBA); + total += fontRenderer.getSize(); log.appendFormat("Total memory usage:\n"); log.appendFormat(" %d bytes, %.2f MB\n", total, total / 1024.0f / 1024.0f); + +#ifdef BUGREPORT_FONT_CACHE_USAGE + fontRenderer.getFontRenderer().historyTracker().dump(log); +#endif } /////////////////////////////////////////////////////////////////////////////// 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 32ab501478f7..2e561601d452 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/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index 276c18d0d3f9..681cf55066b4 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -168,10 +168,17 @@ void FontRenderer::flushAllAndInvalidate() { for (uint32_t i = 0; i < mACacheTextures.size(); i++) { mACacheTextures[i]->init(); + +#ifdef BUGREPORT_FONT_CACHE_USAGE + mHistoryTracker.glyphsCleared(mACacheTextures[i]); +#endif } for (uint32_t i = 0; i < mRGBACacheTextures.size(); i++) { mRGBACacheTextures[i]->init(); +#ifdef BUGREPORT_FONT_CACHE_USAGE + mHistoryTracker.glyphsCleared(mRGBACacheTextures[i]); +#endif } mDrawn = false; @@ -183,6 +190,9 @@ void FontRenderer::flushLargeCaches(std::vector<CacheTexture*>& cacheTextures) { CacheTexture* cacheTexture = cacheTextures[i]; if (cacheTexture->getPixelBuffer()) { cacheTexture->init(); +#ifdef BUGREPORT_FONT_CACHE_USAGE + mHistoryTracker.glyphsCleared(cacheTexture); +#endif LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts); while (it.next()) { it.value()->invalidateTextureCache(cacheTexture); @@ -385,6 +395,10 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp } cachedGlyph->mIsValid = true; + +#ifdef BUGREPORT_FONT_CACHE_USAGE + mHistoryTracker.glyphUploaded(cacheTexture, startX, startY, glyph.fWidth, glyph.fHeight); +#endif } CacheTexture* FontRenderer::createCacheTexture(int width, int height, GLenum format, @@ -747,19 +761,68 @@ static uint32_t calculateCacheSize(const std::vector<CacheTexture*>& cacheTextur return size; } -uint32_t FontRenderer::getCacheSize(GLenum format) const { +static uint32_t calculateFreeCacheSize(const std::vector<CacheTexture*>& cacheTextures) { + uint32_t size = 0; + for (uint32_t i = 0; i < cacheTextures.size(); i++) { + CacheTexture* cacheTexture = cacheTextures[i]; + if (cacheTexture && cacheTexture->getPixelBuffer()) { + size += cacheTexture->calculateFreeMemory(); + } + } + return size; +} + +const std::vector<CacheTexture*>& FontRenderer::cacheTexturesForFormat(GLenum format) const { switch (format) { case GL_ALPHA: { - return calculateCacheSize(mACacheTextures); + return mACacheTextures; } case GL_RGBA: { - return calculateCacheSize(mRGBACacheTextures); + return mRGBACacheTextures; } default: { - return 0; + LOG_ALWAYS_FATAL("Unsupported format: %d", format); + // Impossible to hit this, but the compiler doesn't know that + return *(new std::vector<CacheTexture*>()); } } } +static void dumpTextures(String8& log, const char* tag, + const std::vector<CacheTexture*>& cacheTextures) { + for (uint32_t i = 0; i < cacheTextures.size(); i++) { + CacheTexture* cacheTexture = cacheTextures[i]; + if (cacheTexture && cacheTexture->getPixelBuffer()) { + uint32_t free = cacheTexture->calculateFreeMemory(); + uint32_t total = cacheTexture->getPixelBuffer()->getSize(); + log.appendFormat(" %-4s texture %d %8d / %8d\n", tag, i, total - free, total); + } + } +} + +void FontRenderer::dumpMemoryUsage(String8& log) const { + const uint32_t sizeA8 = getCacheSize(GL_ALPHA); + const uint32_t usedA8 = sizeA8 - getFreeCacheSize(GL_ALPHA); + const uint32_t sizeRGBA = getCacheSize(GL_RGBA); + const uint32_t usedRGBA = sizeRGBA - getFreeCacheSize(GL_RGBA); + log.appendFormat(" FontRenderer A8 %8d / %8d\n", usedA8, sizeA8); + dumpTextures(log, "A8", cacheTexturesForFormat(GL_ALPHA)); + log.appendFormat(" FontRenderer RGBA %8d / %8d\n", usedRGBA, sizeRGBA); + dumpTextures(log, "RGBA", cacheTexturesForFormat(GL_RGBA)); + log.appendFormat(" FontRenderer total %8d / %8d\n", usedA8 + usedRGBA, sizeA8 + sizeRGBA); +} + +uint32_t FontRenderer::getCacheSize(GLenum format) const { + return calculateCacheSize(cacheTexturesForFormat(format)); +} + +uint32_t FontRenderer::getFreeCacheSize(GLenum format) const { + return calculateFreeCacheSize(cacheTexturesForFormat(format)); +} + +uint32_t FontRenderer::getSize() const { + return getCacheSize(GL_ALPHA) + getCacheSize(GL_RGBA); +} + }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h index d656864c5133..1e59a966750e 100644 --- a/libs/hwui/FontRenderer.h +++ b/libs/hwui/FontRenderer.h @@ -21,8 +21,12 @@ #include "font/CacheTexture.h" #include "font/CachedGlyphInfo.h" #include "font/Font.h" +#ifdef BUGREPORT_FONT_CACHE_USAGE +#include "font/FontCacheHistoryTracker.h" +#endif #include <utils/LruCache.h> +#include <utils/String8.h> #include <utils/StrongPointer.h> #include <SkPaint.h> @@ -132,7 +136,12 @@ public: mLinearFiltering = linearFiltering; } - uint32_t getCacheSize(GLenum format) const; + uint32_t getSize() const; + void dumpMemoryUsage(String8& log) const; + +#ifdef BUGREPORT_FONT_CACHE_USAGE + FontCacheHistoryTracker& historyTracker() { return mHistoryTracker; } +#endif private: friend class Font; @@ -175,6 +184,10 @@ private: mUploadTexture = true; } + const std::vector<CacheTexture*>& cacheTexturesForFormat(GLenum format) const; + uint32_t getCacheSize(GLenum format) const; + uint32_t getFreeCacheSize(GLenum format) const; + uint32_t mSmallCacheWidth; uint32_t mSmallCacheHeight; uint32_t mLargeCacheWidth; @@ -199,6 +212,10 @@ private: bool mLinearFiltering; +#ifdef BUGREPORT_FONT_CACHE_USAGE + FontCacheHistoryTracker mHistoryTracker; +#endif + #ifdef ANDROID_ENABLE_RENDERSCRIPT // RS constructs RSC::sp<RSC::RS> mRs; 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 afab84c3542c..bac9d12d273c 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/GammaFontRenderer.h b/libs/hwui/GammaFontRenderer.h index 5813e7f717ee..bd27a1a72060 100644 --- a/libs/hwui/GammaFontRenderer.h +++ b/libs/hwui/GammaFontRenderer.h @@ -22,6 +22,8 @@ #include <SkPaint.h> +#include <utils/String8.h> + namespace android { namespace uirenderer { @@ -46,8 +48,16 @@ public: return *mRenderer; } - uint32_t getFontRendererSize(GLenum format) const { - return mRenderer ? mRenderer->getCacheSize(format) : 0; + void dumpMemoryUsage(String8& log) const { + if (mRenderer) { + mRenderer->dumpMemoryUsage(log); + } else { + log.appendFormat("FontRenderer doesn't exist.\n"); + } + } + + uint32_t getSize() const { + return mRenderer ? mRenderer->getSize() : 0; } void endPrecaching(); 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 a23dd7807169..8b482d5a804d 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: - explicit JankTracker(nsecs_t frameIntervalNanos); + explicit 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..e3258e3c1a48 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,12 +41,22 @@ PropertyValuesAnimatorSet::PropertyValuesAnimatorSet() setStartValue(0); mLastFraction = 0.0f; setInterpolator(new LinearInterpolator()); + setListener(new PropertyAnimatorSetListener(this)); } void PropertyValuesAnimatorSet::onFinished(BaseRenderNodeAnimator* animator) { if (mOneShotListener.get()) { - mOneShotListener->onAnimationFinished(animator); + sp<AnimationListener> listener = std::move(mOneShotListener); + // Set the listener to nullptr before the onAnimationFinished callback, rather than after, + // for two reasons: + // 1) We need to prevent changes to mOneShotListener during the onAnimationFinished + // callback (specifically in AnimationListenerBridge::onAnimationFinished(...) from + // triggering dtor of the bridge and potentially unsafely re-entering + // AnimationListenerBridge::onAnimationFinished(...). + // 2) It's possible that there are changes to the listener during the callback, therefore + // we need to reset the listener before the callback rather than afterwards. mOneShotListener = nullptr; + listener->onAnimationFinished(animator); } } @@ -61,14 +75,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 +87,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 +119,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 +128,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 +137,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 602fd91b0412..a5d9e869196f 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/Readback.cpp b/libs/hwui/Readback.cpp index 55f823dfe226..0ab247dc8052 100644 --- a/libs/hwui/Readback.cpp +++ b/libs/hwui/Readback.cpp @@ -136,7 +136,7 @@ CopyResult Readback::copySurfaceInto(renderthread::RenderThread& renderThread, EGL_NATIVE_BUFFER_ANDROID, clientBuffer, attrs); if (sourceImage == EGL_NO_IMAGE_KHR) { - ALOGW("Error creating image (%#x)", eglGetError()); + ALOGW("eglCreateImageKHR failed (%#x)", eglGetError()); return CopyResult::UnknownError; } GLuint sourceTexId; @@ -147,7 +147,8 @@ CopyResult Readback::copySurfaceInto(renderthread::RenderThread& renderThread, GLenum status = GL_NO_ERROR; while ((status = glGetError()) != GL_NO_ERROR) { - ALOGW("Error creating image (%#x)", status); + ALOGW("glEGLImageTargetTexture2DOES failed (%#x)", status); + eglDestroyImageKHR(display, sourceImage); return CopyResult::UnknownError; } @@ -183,6 +184,13 @@ CopyResult Readback::copySurfaceInto(renderthread::RenderThread& renderThread, caches.textureState().deleteTexture(texture); renderState.deleteFramebuffer(fbo); + sourceTexture.deleteTexture(); + // All we're flushing & finishing is the deletion of the texture since + // copyTextureInto already did a major flush & finish as an implicit + // part of glReadPixels, so this shouldn't pose any major stalls. + glFinish(); + eglDestroyImageKHR(display, sourceImage); + GL_CHECKPOINT(MODERATE); return CopyResult::Success; 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 dbd188fa15c5..eb05e9171335 100644 --- a/libs/hwui/Rect.h +++ b/libs/hwui/Rect.h @@ -73,6 +73,13 @@ public: bottom(height) { } + inline Rect(const SkIRect& rect): // NOLINT, implicit + left(rect.fLeft), + top(rect.fTop), + right(rect.fRight), + bottom(rect.fBottom) { + } + inline Rect(const SkRect& rect): // NOLINT, implicit 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/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index 0693804d5770..3b6fae08773d 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -329,9 +329,10 @@ void SkiaCanvas::restoreToCount(int restoreCount) { static inline SkCanvas::SaveLayerFlags layerFlags(SaveFlags::Flags flags) { SkCanvas::SaveLayerFlags layerFlags = 0; - if (!(flags & SaveFlags::HasAlphaLayer)) { - layerFlags |= SkCanvas::kIsOpaque_SaveLayerFlag; - } + // We intentionally ignore the SaveFlags::HasAlphaLayer and + // SkCanvas::kIsOpaque_SaveLayerFlag flags because HWUI ignores it + // and our Android client may use it incorrectly. + // In Skia, this flag is purely for performance optimization. if (!(flags & SaveFlags::ClipToLayer)) { layerFlags |= SkCanvas::kDontClipToLayer_Legacy_SaveLayerFlag; diff --git a/libs/hwui/SpotShadow.cpp b/libs/hwui/SpotShadow.cpp index 760d814f27a8..cc96a137c306 100644 --- a/libs/hwui/SpotShadow.cpp +++ b/libs/hwui/SpotShadow.cpp @@ -942,9 +942,13 @@ void SpotShadow::generateTriangleStrip(bool isCasterOpaque, float shadowStrength AlphaVertex::set(&shadowVertices[vertexBufferIndex++], newPenumbra[i].x, newPenumbra[i].y, PENUMBRA_ALPHA); } + // Since the umbra can be a faked one when the occluder is too high, the umbra should be lighter + // in this case. + float scaledUmbraAlpha = UMBRA_ALPHA * shadowStrengthScale; + for (int i = 0; i < umbraLength; i++) { AlphaVertex::set(&shadowVertices[vertexBufferIndex++], umbra[i].x, umbra[i].y, - UMBRA_ALPHA); + scaledUmbraAlpha); } for (int i = 0; i < verticesPairIndex; i++) { @@ -984,14 +988,14 @@ void SpotShadow::generateTriangleStrip(bool isCasterOpaque, float shadowStrength indexBuffer[indexBufferIndex++] = newPenumbraLength + i; indexBuffer[indexBufferIndex++] = vertexBufferIndex; AlphaVertex::set(&shadowVertices[vertexBufferIndex++], - closerVertex.x, closerVertex.y, UMBRA_ALPHA); + closerVertex.x, closerVertex.y, scaledUmbraAlpha); } } else { // If there is no occluded umbra at all, then draw the triangle fan // starting from the centroid to all umbra vertices. int lastCentroidIndex = vertexBufferIndex; AlphaVertex::set(&shadowVertices[vertexBufferIndex++], centroid.x, - centroid.y, UMBRA_ALPHA); + centroid.y, scaledUmbraAlpha); for (int i = 0; i < umbraLength; i++) { indexBuffer[indexBufferIndex++] = newPenumbraLength + i; indexBuffer[indexBufferIndex++] = lastCentroidIndex; 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.cpp b/libs/hwui/VectorDrawable.cpp index 2b7994139641..aeee66106fb3 100644 --- a/libs/hwui/VectorDrawable.cpp +++ b/libs/hwui/VectorDrawable.cpp @@ -202,7 +202,9 @@ void FullPath::drawPath(SkCanvas* outCanvas, SkPath& renderPath, float strokeSca if (properties.getFillGradient() != nullptr) { paint.setColor(applyAlpha(SK_ColorBLACK, properties.getFillAlpha())); SkShader* newShader = properties.getFillGradient()->newWithLocalMatrix(matrix); - paint.setShader(newShader); + // newWithLocalMatrix(...) creates a new SkShader and returns a bare pointer. We need to + // remove the extra ref so that the ref count is correctly managed. + paint.setShader(newShader)->unref(); needsFill = true; } else if (properties.getFillColor() != SK_ColorTRANSPARENT) { paint.setColor(applyAlpha(properties.getFillColor(), properties.getFillAlpha())); @@ -222,7 +224,9 @@ void FullPath::drawPath(SkCanvas* outCanvas, SkPath& renderPath, float strokeSca if (properties.getStrokeGradient() != nullptr) { paint.setColor(applyAlpha(SK_ColorBLACK, properties.getStrokeAlpha())); SkShader* newShader = properties.getStrokeGradient()->newWithLocalMatrix(matrix); - paint.setShader(newShader); + // newWithLocalMatrix(...) creates a new SkShader and returns a bare pointer. We need to + // remove the extra ref so that the ref count is correctly managed. + paint.setShader(newShader)->unref(); needsStroke = true; } else if (properties.getStrokeColor() != SK_ColorTRANSPARENT) { paint.setColor(applyAlpha(properties.getStrokeColor(), properties.getStrokeAlpha())); diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h index 3586d8a1d967..6c1815fe1301 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/font/CacheTexture.cpp b/libs/hwui/font/CacheTexture.cpp index fcdde45c49f2..49e9f65582ae 100644 --- a/libs/hwui/font/CacheTexture.cpp +++ b/libs/hwui/font/CacheTexture.cpp @@ -330,5 +330,17 @@ bool CacheTexture::fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_ return false; } +uint32_t CacheTexture::calculateFreeMemory() const { + CacheBlock* cacheBlock = mCacheBlocks; + uint32_t free = 0; + // currently only two formats are supported: GL_ALPHA or GL_RGBA; + uint32_t bpp = mFormat == GL_RGBA ? 4 : 1; + while (cacheBlock) { + free += bpp * cacheBlock->mWidth * cacheBlock->mHeight; + cacheBlock = cacheBlock->mNext; + } + return free; +} + }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/font/CacheTexture.h b/libs/hwui/font/CacheTexture.h index 4dfb41dafcc7..6750a8ae11cf 100644 --- a/libs/hwui/font/CacheTexture.h +++ b/libs/hwui/font/CacheTexture.h @@ -178,6 +178,8 @@ public: return mCurrentQuad == mMaxQuadCount; } + uint32_t calculateFreeMemory() const; + private: void setDirty(bool dirty); diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp index 8e04c8715f62..a95454a4c010 100644 --- a/libs/hwui/font/Font.cpp +++ b/libs/hwui/font/Font.cpp @@ -408,9 +408,15 @@ void Font::render(const SkPaint* paint, const glyph_t* glyphs, if (cachedGlyph->mIsValid && cachedGlyph->mCacheTexture) { int penX = x + (int) roundf(positions[(glyphsCount << 1)]); int penY = y + (int) roundf(positions[(glyphsCount << 1) + 1]); - +#ifdef BUGREPORT_FONT_CACHE_USAGE + mState->historyTracker().glyphRendered(cachedGlyph, penX, penY); +#endif (*this.*render)(cachedGlyph, penX, penY, bitmap, bitmapW, bitmapH, bounds, positions); + } else { +#ifdef BUGREPORT_FONT_CACHE_USAGE + mState->historyTracker().glyphRendered(cachedGlyph, -1, -1); +#endif } glyphsCount++; diff --git a/libs/hwui/font/FontCacheHistoryTracker.cpp b/libs/hwui/font/FontCacheHistoryTracker.cpp new file mode 100644 index 000000000000..a2bfb27535e5 --- /dev/null +++ b/libs/hwui/font/FontCacheHistoryTracker.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2016 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. + */ + +#include "FontCacheHistoryTracker.h" + +#include "CachedGlyphInfo.h" +#include "CacheTexture.h" + +namespace android { +namespace uirenderer { + +void FontCacheHistoryTracker::dumpCachedGlyph(String8& log, const CachedGlyph& glyph) { + log.appendFormat("glyph (texture %p, position: (%d, %d), size: %dx%d, gen: %d)", glyph.texture, + glyph.startX, glyph.startY, glyph.bitmapW, glyph.bitmapH, glyph.generation); +} + +void FontCacheHistoryTracker::dumpRenderEntry(String8& log, const RenderEntry& entry) { + if (entry.penX == -1 && entry.penY == -1) { + log.appendFormat(" glyph skipped in gen: %d\n", entry.glyph.generation); + } else { + log.appendFormat(" rendered "); + dumpCachedGlyph(log, entry.glyph); + log.appendFormat(" at (%d, %d)\n", entry.penX, entry.penY); + } +} + +void FontCacheHistoryTracker::dumpUploadEntry(String8& log, const CachedGlyph& glyph) { + if (glyph.bitmapW == 0 && glyph.bitmapH == 0) { + log.appendFormat(" cleared cachetexture %p in gen %d\n", glyph.texture, + glyph.generation); + } else { + log.appendFormat(" uploaded "); + dumpCachedGlyph(log, glyph); + log.appendFormat("\n"); + } +} + +void FontCacheHistoryTracker::dump(String8& log) const { + log.appendFormat("FontCacheHistory: \n"); + log.appendFormat(" Upload history: \n"); + for (size_t i = 0; i < mUploadHistory.size(); i++) { + dumpUploadEntry(log, mUploadHistory[i]); + } + log.appendFormat(" Render history: \n"); + for (size_t i = 0; i < mRenderHistory.size(); i++) { + dumpRenderEntry(log, mRenderHistory[i]); + } +} + +void FontCacheHistoryTracker::glyphRendered(CachedGlyphInfo* glyphInfo, int penX, int penY) { + RenderEntry& entry = mRenderHistory.next(); + entry.glyph.generation = generation; + entry.glyph.texture = glyphInfo->mCacheTexture; + entry.glyph.startX = glyphInfo->mStartX; + entry.glyph.startY = glyphInfo->mStartY; + entry.glyph.bitmapW = glyphInfo->mBitmapWidth; + entry.glyph.bitmapH = glyphInfo->mBitmapHeight; + entry.penX = penX; + entry.penY = penY; +} + +void FontCacheHistoryTracker::glyphUploaded(CacheTexture* texture, uint32_t x, uint32_t y, + uint16_t glyphW, uint16_t glyphH) { + CachedGlyph& glyph = mUploadHistory.next(); + glyph.generation = generation; + glyph.texture = texture; + glyph.startX = x; + glyph.startY = y; + glyph.bitmapW = glyphW; + glyph.bitmapH = glyphH; +} + +void FontCacheHistoryTracker::glyphsCleared(CacheTexture* texture) { + CachedGlyph& glyph = mUploadHistory.next(); + glyph.generation = generation; + glyph.texture = texture; + glyph.startX = 0; + glyph.startY = 0; + glyph.bitmapW = 0; + glyph.bitmapH = 0; +} + +void FontCacheHistoryTracker::frameCompleted() { + generation++; +} +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/font/FontCacheHistoryTracker.h b/libs/hwui/font/FontCacheHistoryTracker.h new file mode 100644 index 000000000000..f1d9b9f10dc0 --- /dev/null +++ b/libs/hwui/font/FontCacheHistoryTracker.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016 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. + */ + +#pragma once +#include "../utils/RingBuffer.h" + +#include <utils/String8.h> + +namespace android { +namespace uirenderer { + +class CacheTexture; +struct CachedGlyphInfo; + +// Tracks glyph uploads and recent rendered/skipped glyphs, so it can give an idea +// what a missing character is: skipped glyph, wrong coordinates in cache texture etc. +class FontCacheHistoryTracker { +public: + void glyphRendered(CachedGlyphInfo*, int penX, int penY); + void glyphUploaded(CacheTexture*, uint32_t x, uint32_t y, uint16_t glyphW, uint16_t glyphH); + void glyphsCleared(CacheTexture*); + void frameCompleted(); + + void dump(String8& log) const; +private: + struct CachedGlyph { + void* texture; + uint16_t generation; + uint16_t startX; + uint16_t startY; + uint16_t bitmapW; + uint16_t bitmapH; + }; + + struct RenderEntry { + CachedGlyph glyph; + int penX; + int penY; + }; + + static void dumpCachedGlyph(String8& log, const CachedGlyph& glyph); + static void dumpRenderEntry(String8& log, const RenderEntry& entry); + static void dumpUploadEntry(String8& log, const CachedGlyph& glyph); + + RingBuffer<RenderEntry, 300> mRenderHistory; + RingBuffer<CachedGlyph, 120> mUploadHistory; + uint16_t generation = 0; +}; + +}; // namespace uirenderer +}; // namespace android
\ No newline at end of file diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 75e7fdf13091..975ac8368e3d 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? @@ -546,6 +608,10 @@ void CanvasContext::draw() { } GpuMemoryTracker::onFrameCompleted(); +#ifdef BUGREPORT_FONT_CACHE_USAGE + caches.fontRenderer.getFontRenderer().historyTracker().frameCompleted(); +#endif + } // Called by choreographer to do an RT-driven animation @@ -571,6 +637,9 @@ void CanvasContext::prepareAndDraw(RenderNode* node) { prepareTree(info, frameInfo, systemTime(CLOCK_MONOTONIC), node); if (info.out.canDrawThisFrame) { draw(); + } else { + // wait on fences so tasks don't overlap next frame + waitOnFences(); } } diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index a6eb7adc3568..e1821751b57f 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -168,6 +168,8 @@ public: ANDROID_API int64_t getFrameNumber(); + void waitOnFences(); + private: friend class RegisterFrameCallbackTask; // TODO: Replace with something better for layer & other GL object @@ -178,7 +180,7 @@ private: void freePrefetchedLayers(TreeObserver* observer); - 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/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp index c9c07b3df292..e3b6dc6fd9fe 100644 --- a/libs/hwui/renderthread/DrawFrameTask.cpp +++ b/libs/hwui/renderthread/DrawFrameTask.cpp @@ -104,6 +104,9 @@ void DrawFrameTask::run() { if (CC_LIKELY(canDrawThisFrame)) { context->draw(); + } else { + // wait on fences so tasks don't overlap next frame + context->waitOnFences(); } if (!canUnblockUiThread) { 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 3c1c0bceba58..968834056ae1 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/tests/unit/VectorDrawableTests.cpp b/libs/hwui/tests/unit/VectorDrawableTests.cpp index 83b485fa705e..8e0d3ee572a6 100644 --- a/libs/hwui/tests/unit/VectorDrawableTests.cpp +++ b/libs/hwui/tests/unit/VectorDrawableTests.cpp @@ -426,5 +426,49 @@ TEST(VectorDrawable, groupProperties) { EXPECT_EQ(1.0f, properties->getPivotY()); } + +static SkShader* createShader(bool* isDestroyed) { + class TestShader : public SkShader { + public: + TestShader(bool* isDestroyed) : SkShader(), mDestroyed(isDestroyed) { + } + ~TestShader() { + *mDestroyed = true; + } + + Factory getFactory() const override { return nullptr; } + private: + bool* mDestroyed; + }; + return new TestShader(isDestroyed); +} + +TEST(VectorDrawable, drawPathWithoutIncrementingShaderRefCount) { + VectorDrawable::FullPath path("m1 1", 4); + SkBitmap bitmap; + SkImageInfo info = SkImageInfo::Make(5, 5, kN32_SkColorType, kPremul_SkAlphaType); + bitmap.setInfo(info); + bitmap.allocPixels(info); + SkCanvas canvas(bitmap); + + bool shaderIsDestroyed = false; + + // Initial ref count is 1 + SkShader* shader = createShader(&shaderIsDestroyed); + + // Setting the fill gradient increments the ref count of the shader by 1 + path.mutateStagingProperties()->setFillGradient(shader); + path.draw(&canvas, SkMatrix::I(), 1.0f, 1.0f, true); + // Resetting the fill gradient decrements the ref count of the shader by 1 + path.mutateStagingProperties()->setFillGradient(nullptr); + + // Expect ref count to be 1 again, i.e. nothing else to have a ref to the shader now. Unref() + // again should bring the ref count to zero and consequently trigger detor. + shader->unref(); + + // Verify that detor is called. + EXPECT_TRUE(shaderIsDestroyed); +} + }; // namespace uirenderer }; // namespace android 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 */ diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index 27193b743379..abef66f9ecd9 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -36,6 +36,29 @@ namespace android { +// --- WeakLooperCallback --- + +class WeakLooperCallback: public LooperCallback { +protected: + virtual ~WeakLooperCallback() { } + +public: + WeakLooperCallback(const wp<LooperCallback>& callback) : + mCallback(callback) { + } + + virtual int handleEvent(int fd, int events, void* data) { + sp<LooperCallback> callback = mCallback.promote(); + if (callback != NULL) { + return callback->handleEvent(fd, events, data); + } + return 0; // the client is gone, remove the callback + } + +private: + wp<LooperCallback> mCallback; +}; + // --- PointerController --- // Time to wait before starting the fade when the pointer is inactive. @@ -57,10 +80,11 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>& const sp<Looper>& looper, const sp<SpriteController>& spriteController) : mPolicy(policy), mLooper(looper), mSpriteController(spriteController) { mHandler = new WeakMessageHandler(this); + mCallback = new WeakLooperCallback(this); if (mDisplayEventReceiver.initCheck() == NO_ERROR) { mLooper->addFd(mDisplayEventReceiver.getFd(), Looper::POLL_CALLBACK, - Looper::EVENT_INPUT, this, nullptr); + Looper::EVENT_INPUT, mCallback, nullptr); } else { ALOGE("Failed to initialize DisplayEventReceiver."); } diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index 99292d7ca8a6..4794f3da824c 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -144,6 +144,7 @@ private: sp<Looper> mLooper; sp<SpriteController> mSpriteController; sp<WeakMessageHandler> mHandler; + sp<LooperCallback> mCallback; DisplayEventReceiver mDisplayEventReceiver; |