diff options
author | 2016-09-27 16:04:42 -0400 | |
---|---|---|
committer | 2016-10-05 15:28:27 -0400 | |
commit | 0df6209a02d0ea99d2dff3a46ed9febd5925df4b (patch) | |
tree | f0ae38776b4dc08456f111dd61076ba0a9394119 | |
parent | 4306608707ad24af2b9a7c5764412e429329eab3 (diff) |
Initial refactoring to enable the addition of the SkiaOpenGLPipeline.
Test: existing and new HWUI unit tests all pass.
Change-Id: I4f5c1dc839a2ed15d8b0f6245fe030684501b083
-rw-r--r-- | libs/hwui/Android.mk | 2 | ||||
-rw-r--r-- | libs/hwui/DisplayList.cpp | 43 | ||||
-rw-r--r-- | libs/hwui/DisplayList.h | 23 | ||||
-rw-r--r-- | libs/hwui/Properties.cpp | 8 | ||||
-rw-r--r-- | libs/hwui/Properties.h | 1 | ||||
-rw-r--r-- | libs/hwui/RenderNode.cpp | 73 | ||||
-rw-r--r-- | libs/hwui/RenderNode.h | 61 | ||||
-rw-r--r-- | libs/hwui/SkiaDisplayList.cpp | 134 | ||||
-rw-r--r-- | libs/hwui/SkiaDisplayList.h | 152 | ||||
-rw-r--r-- | libs/hwui/SkiaDrawables.h | 102 | ||||
-rw-r--r-- | libs/hwui/renderthread/CanvasContext.h | 6 | ||||
-rw-r--r-- | libs/hwui/renderthread/IRenderPipeline.h | 5 | ||||
-rw-r--r-- | libs/hwui/renderthread/OpenGLPipeline.h | 4 | ||||
-rw-r--r-- | libs/hwui/tests/unit/RenderNodeTests.cpp | 2 | ||||
-rw-r--r-- | libs/hwui/tests/unit/SkiaDisplayListTests.cpp | 189 |
15 files changed, 746 insertions, 59 deletions
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 81a183126cce..a13cddeadae6 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -95,6 +95,7 @@ hwui_src_files := \ ShadowTessellator.cpp \ SkiaCanvas.cpp \ SkiaCanvasProxy.cpp \ + SkiaDisplayList.cpp \ SkiaShader.cpp \ Snapshot.cpp \ SpotShadow.cpp \ @@ -280,6 +281,7 @@ LOCAL_SRC_FILES += \ tests/unit/RenderNodeTests.cpp \ tests/unit/RenderPropertiesTests.cpp \ tests/unit/SkiaBehaviorTests.cpp \ + tests/unit/SkiaDisplayListTests.cpp \ tests/unit/SkiaCanvasTests.cpp \ tests/unit/SnapshotTests.cpp \ tests/unit/StringUtilsTests.cpp \ diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp index ca9e2bd4d3ef..6e7d11fa0f02 100644 --- a/libs/hwui/DisplayList.cpp +++ b/libs/hwui/DisplayList.cpp @@ -19,10 +19,12 @@ #include <utils/Trace.h> +#include "DamageAccumulator.h" #include "Debug.h" #include "DisplayList.h" #include "RecordedOp.h" #include "RenderNode.h" +#include "VectorDrawable.h" namespace android { namespace uirenderer { @@ -86,5 +88,46 @@ size_t DisplayList::addChild(NodeOpType* op) { return index; } +void DisplayList::syncContents() { + for (auto& iter : functors) { + (*iter.functor)(DrawGlInfo::kModeSync, nullptr); + } + for (auto& vectorDrawable : vectorDrawables) { + vectorDrawable->syncProperties(); + } +} + +void DisplayList::updateChildren(std::function<void(RenderNode*)> updateFn) { + for (auto&& child : children) { + updateFn(child->renderNode); + } +} + +bool DisplayList::prepareListAndChildren(TreeInfo& info, bool functorsNeedLayer, + std::function<void(RenderNode*, TreeInfo&, bool)> childFn) { + TextureCache& cache = Caches::getInstance().textureCache; + for (auto&& bitmapResource : bitmapResources) { + void* ownerToken = &info.canvasContext; + info.prepareTextures = cache.prefetchAndMarkInUse(ownerToken, bitmapResource); + } + for (auto&& op : children) { + RenderNode* childNode = op->renderNode; + info.damageAccumulator->pushTransform(&op->localMatrix); + bool childFunctorsNeedLayer = functorsNeedLayer; // TODO! || op->mRecordedWithPotentialStencilClip; + childFn(childNode, info, childFunctorsNeedLayer); + info.damageAccumulator->popTransform(); + } + + bool isDirty = false; + for (auto& vectorDrawable : vectorDrawables) { + // If any vector drawable in the display list needs update, damage the node. + if (vectorDrawable->isDirty()) { + isDirty = true; + } + vectorDrawable->setPropertyChangeWillBeConsumed(true); + } + return isDirty; +} + }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h index c5d87676b7ff..06b08919732f 100644 --- a/libs/hwui/DisplayList.h +++ b/libs/hwui/DisplayList.h @@ -17,6 +17,7 @@ #pragma once #include <SkCamera.h> +#include <SkDrawable.h> #include <SkMatrix.h> #include <private/hwui/DrawGlInfo.h> @@ -36,6 +37,7 @@ #include "GlFunctorLifecycleListener.h" #include "Matrix.h" #include "RenderProperties.h" +#include "TreeInfo.h" #include <vector> @@ -89,7 +91,7 @@ public: }; DisplayList(); - ~DisplayList(); + virtual ~DisplayList(); // index of DisplayListOp restore, after which projected descendants should be drawn int projectionReceiveIndex; @@ -100,8 +102,6 @@ public: const LsaVector<NodeOpType*>& getChildren() const { return children; } const LsaVector<const SkBitmap*>& getBitmapResources() const { return bitmapResources; } - const LsaVector<FunctorContainer>& getFunctors() const { return functors; } - const LsaVector<VectorDrawableRoot*>& getVectorDrawables() const { return vectorDrawables; } size_t addChild(NodeOpType* childOp); @@ -113,15 +113,26 @@ public: size_t getUsedSize() { return allocator.usedSize(); } - bool isEmpty() { - return ops.empty(); + + virtual bool isEmpty() const { return ops.empty(); } + virtual bool hasFunctor() const { return !functors.empty(); } + virtual bool hasVectorDrawables() const { return !vectorDrawables.empty(); } + virtual bool isSkiaDL() const { return false; } + virtual bool reuseDisplayList(RenderNode* node, renderthread::CanvasContext* context) { + return false; } -private: + virtual void syncContents(); + virtual void updateChildren(std::function<void(RenderNode*)> updateFn); + virtual bool prepareListAndChildren(TreeInfo& info, bool functorsNeedLayer, + std::function<void(RenderNode*, TreeInfo&, bool)> childFn); + +protected: // allocator into which all ops and LsaVector arrays allocated LinearAllocator allocator; LinearStdAllocator<void*> stdAllocator; +private: LsaVector<Chunk> chunks; LsaVector<BaseOpType*> ops; diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index 93b2e15a6590..848161e44604 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -214,7 +214,7 @@ RenderPipelineType Properties::getRenderPipelineType() { property_get(PROPERTY_DEFAULT_RENDERER, prop, "opengl"); if (!strcmp(prop, "skiagl") ) { sRenderPipelineType = RenderPipelineType::SkiaGL; - } else if (!strcmp(prop, "skiavulkan") ) { + } else if (!strcmp(prop, "skiavk") ) { sRenderPipelineType = RenderPipelineType::SkiaVulkan; } else { //"opengl" sRenderPipelineType = RenderPipelineType::OpenGL; @@ -222,5 +222,11 @@ RenderPipelineType Properties::getRenderPipelineType() { return sRenderPipelineType; } +bool Properties::isSkiaEnabled() { + auto renderType = getRenderPipelineType(); + return RenderPipelineType::SkiaGL == renderType + || RenderPipelineType::SkiaVulkan == renderType; +} + }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index eedc9e7d5333..c4486a480754 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -308,6 +308,7 @@ public: static ProfileType getProfileType(); static RenderPipelineType getRenderPipelineType(); + static bool isSkiaEnabled(); // Should be used only by test apps static bool waitForGpuCompletion; diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index 8c36ab5d94dd..a03ded643521 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -51,7 +51,7 @@ RenderNode::RenderNode() RenderNode::~RenderNode() { deleteDisplayList(nullptr); delete mStagingDisplayList; - LOG_ALWAYS_FATAL_IF(mLayer, "layer missed detachment!"); + LOG_ALWAYS_FATAL_IF(hasLayer(), "layer missed detachment!"); } void RenderNode::setStagingDisplayList(DisplayList* displayList, TreeObserver* observer) { @@ -81,7 +81,7 @@ void RenderNode::output(std::ostream& output, uint32_t level) { << (properties().hasShadow() ? ", casting shadow" : "") << (isRenderable() ? "" : ", empty") << (properties().getProjectBackwards() ? ", projected" : "") - << (mLayer != nullptr ? ", on HW Layer" : "") + << (hasLayer() ? ", on HW Layer" : "") << ")" << std::endl; properties().debugOutputProperties(output, level + 1); @@ -237,7 +237,7 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { || CC_UNLIKELY(!isRenderable()) || CC_UNLIKELY(properties().getWidth() == 0) || CC_UNLIKELY(properties().getHeight() == 0)) { - if (CC_UNLIKELY(mLayer)) { + if (CC_UNLIKELY(hasLayer())) { renderthread::CanvasContext::destroyLayer(this); } return; @@ -247,7 +247,7 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { damageSelf(info); } - if (!mLayer) { + if (!hasLayer()) { Caches::getInstance().dumpMemoryUsage(); if (info.errorHandler) { std::ostringstream err; @@ -295,9 +295,9 @@ void RenderNode::prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer) { bool willHaveFunctor = false; if (info.mode == TreeInfo::MODE_FULL && mStagingDisplayList) { - willHaveFunctor = !mStagingDisplayList->getFunctors().empty(); + willHaveFunctor = mStagingDisplayList->hasFunctor(); } else if (mDisplayList) { - willHaveFunctor = !mDisplayList->getFunctors().empty(); + willHaveFunctor = mDisplayList->hasFunctor(); } bool childFunctorsNeedLayer = mProperties.prepareForFunctorPresence( willHaveFunctor, functorsNeedLayer); @@ -310,15 +310,15 @@ void RenderNode::prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer) { if (info.mode == TreeInfo::MODE_FULL) { 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); + info.out.hasFunctors |= mDisplayList->hasFunctor(); + bool isDirty = mDisplayList->prepareListAndChildren(info, childFunctorsNeedLayer, + [](RenderNode* child, TreeInfo& info, bool functorsNeedLayer) { + child->prepareTreeImpl(info, functorsNeedLayer); + }); + if (isDirty) { + damageSelf(info); } } pushLayerUpdate(info); @@ -356,20 +356,15 @@ void RenderNode::syncDisplayList(TreeInfo* info) { // Make sure we inc first so that we don't fluctuate between 0 and 1, // which would thrash the layer cache if (mStagingDisplayList) { - for (auto&& child : mStagingDisplayList->getChildren()) { - child->renderNode->incParentRefCount(); - } + mStagingDisplayList->updateChildren([](RenderNode* child) { + child->incParentRefCount(); + }); } deleteDisplayList(info ? info->observer : nullptr, info); mDisplayList = mStagingDisplayList; mStagingDisplayList = nullptr; if (mDisplayList) { - for (auto& iter : mDisplayList->getFunctors()) { - (*iter.functor)(DrawGlInfo::kModeSync, nullptr); - } - for (auto& vectorDrawable : mDisplayList->getVectorDrawables()) { - vectorDrawable->syncProperties(); - } + mDisplayList->syncContents(); } } @@ -386,40 +381,24 @@ void RenderNode::pushStagingDisplayListChanges(TreeInfo& info) { void RenderNode::deleteDisplayList(TreeObserver* observer, TreeInfo* info) { if (mDisplayList) { - for (auto&& child : mDisplayList->getChildren()) { - child->renderNode->decParentRefCount(observer, info); + mDisplayList->updateChildren([observer, info](RenderNode* child) { + child->decParentRefCount(observer, info); + }); + if (!mDisplayList->reuseDisplayList(this, info ? &info->canvasContext : nullptr)) { + delete mDisplayList; } } - delete mDisplayList; mDisplayList = nullptr; } -void RenderNode::prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayList* subtree) { - if (subtree) { - TextureCache& cache = Caches::getInstance().textureCache; - info.out.hasFunctors |= subtree->getFunctors().size(); - for (auto&& bitmapResource : subtree->getBitmapResources()) { - void* ownerToken = &info.canvasContext; - info.prepareTextures = cache.prefetchAndMarkInUse(ownerToken, bitmapResource); - } - for (auto&& op : subtree->getChildren()) { - RenderNode* childNode = op->renderNode; - info.damageAccumulator->pushTransform(&op->localMatrix); - bool childFunctorsNeedLayer = functorsNeedLayer; // TODO! || op->mRecordedWithPotentialStencilClip; - childNode->prepareTreeImpl(info, childFunctorsNeedLayer); - info.damageAccumulator->popTransform(); - } - } -} - void RenderNode::destroyHardwareResources(TreeObserver* observer, TreeInfo* info) { - if (mLayer) { + if (hasLayer()) { renderthread::CanvasContext::destroyLayer(this); } if (mDisplayList) { - for (auto&& child : mDisplayList->getChildren()) { - child->renderNode->destroyHardwareResources(observer, info); - } + mDisplayList->updateChildren([observer, info](RenderNode* child) { + child->destroyHardwareResources(observer, info); + }); if (mNeedsDisplayListSync) { // Next prepare tree we are going to push a new display list, so we can // drop our current one now diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h index da93c131bb5a..a05c744c4a56 100644 --- a/libs/hwui/RenderNode.h +++ b/libs/hwui/RenderNode.h @@ -32,6 +32,7 @@ #include "DisplayList.h" #include "Matrix.h" #include "RenderProperties.h" +#include "SkiaDisplayList.h" #include <vector> @@ -39,6 +40,7 @@ class SkBitmap; class SkPaint; class SkPath; class SkRegion; +class SkSurface; namespace android { namespace uirenderer { @@ -48,6 +50,7 @@ class DisplayListOp; class FrameBuilder; class OffscreenBuffer; class Rect; +class SkiaDisplayList; class SkiaShader; struct RenderNodeOp; @@ -240,7 +243,6 @@ private: void prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer); void pushStagingPropertiesChanges(TreeInfo& info); void pushStagingDisplayListChanges(TreeInfo& info); - void prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayList* subtree); void prepareLayer(TreeInfo& info, uint32_t dirtyMask); void pushLayerUpdate(TreeInfo& info); void deleteDisplayList(TreeObserver* observer, TreeInfo* info = nullptr); @@ -285,6 +287,63 @@ private: uint32_t mParentCount; sp<PositionListener> mPositionListener; + +// METHODS & FIELDS ONLY USED BY THE SKIA RENDERER +public: + /** + * Detach and transfer ownership of an already allocated displayList for use + * in recording updated content for this renderNode + */ + std::unique_ptr<SkiaDisplayList> detachAvailableList() { + return std::move(mAvailableDisplayList); + } + + /** + * Attach unused displayList to this node for potential future reuse. + */ + void attachAvailableList(SkiaDisplayList* skiaDisplayList) { + mAvailableDisplayList.reset(skiaDisplayList); + } + + /** + * Returns true if an offscreen layer from any renderPipeline is attached + * to this node. + */ + bool hasLayer() const { return mLayer || mLayerSurface.get(); } + + /** + * Used by the RenderPipeline to attach an offscreen surface to the RenderNode. + * The surface is then will be used to store the contents of a layer. + */ + void setLayerSurface(sk_sp<SkSurface> layer) { mLayerSurface = layer; } + + + /** + * If the RenderNode is of type LayerType::RenderLayer then this method will + * return the an offscreen rendering surface that is used to both render into + * the layer and composite the layer into its parent. If the type is not + * LayerType::RenderLayer then it will return a nullptr. + * + * NOTE: this function is only guaranteed to return accurate results after + * prepareTree has been run for this RenderNode + */ + SkSurface* getLayerSurface() const { return mLayerSurface.get(); } + +private: + /** + * If this RenderNode has been used in a previous frame then the SkiaDisplayList + * from that frame is cached here until one of the following conditions is met: + * 1) The RenderNode is deleted (causing this to be deleted) + * 2) It is replaced with the displayList from the next completed frame + * 3) It is detached and used to to record a new displayList for a later frame + */ + std::unique_ptr<SkiaDisplayList> mAvailableDisplayList; + + /** + * An offscreen rendering target used to contain the contents this RenderNode + * when it has been set to draw as a LayerType::RenderLayer. + */ + sk_sp<SkSurface> mLayerSurface; }; // class RenderNode } /* namespace uirenderer */ diff --git a/libs/hwui/SkiaDisplayList.cpp b/libs/hwui/SkiaDisplayList.cpp new file mode 100644 index 000000000000..d10f306ad6d1 --- /dev/null +++ b/libs/hwui/SkiaDisplayList.cpp @@ -0,0 +1,134 @@ +/* + * 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 "SkiaDisplayList.h" + +#include "renderthread/CanvasContext.h" +#include "VectorDrawable.h" + +#include <SkImagePriv.h> +#include <SkMutex.h> + +namespace android { +namespace uirenderer { + +SkiaDisplayList::SkiaDisplayList(SkRect bounds) : mDrawable(SkLiteDL::New(bounds)) { + SkASSERT(projectionReceiveIndex == -1); +} + +void SkiaDisplayList::syncContents() { + for (auto& functor : mChildFunctors) { + functor.syncFunctor(); + } + for (auto& vectorDrawable : mVectorDrawables) { + vectorDrawable->syncProperties(); + } +} + +bool SkiaDisplayList::reuseDisplayList(RenderNode* node, renderthread::CanvasContext* context) { + reset(context ? context->getGrContext() : nullptr, SkRect::MakeEmpty()); + node->attachAvailableList(this); + return true; +} + +void SkiaDisplayList::updateChildren(std::function<void(RenderNode*)> updateFn) { + for (auto& child : mChildNodes) { + updateFn(child.getRenderNode()); + } +} + +bool SkiaDisplayList::prepareListAndChildren(TreeInfo& info, bool functorsNeedLayer, + std::function<void(RenderNode*, TreeInfo&, bool)> childFn) { + // force all mutable images to be pinned in the GPU cache for the duration + // of this frame + pinImages(info.canvasContext.getGrContext()); + + for (auto& child : mChildNodes) { + RenderNode* childNode = child.getRenderNode(); + Matrix4 mat4(child.getRecordedMatrix()); + info.damageAccumulator->pushTransform(&mat4); + // TODO: a layer is needed if the canvas is rotated or has a non-rect clip + bool childFunctorsNeedLayer = functorsNeedLayer; + childFn(childNode, info, childFunctorsNeedLayer); + info.damageAccumulator->popTransform(); + } + + bool isDirty = false; + for (auto& vectorDrawable : mVectorDrawables) { + // If any vector drawable in the display list needs update, damage the node. + if (vectorDrawable->isDirty()) { + isDirty = true; + } + vectorDrawable->setPropertyChangeWillBeConsumed(true); + } + return isDirty; +} + +static std::vector<sk_sp<SkImage>> gPinnedImages; +static SkBaseMutex gLock; + +void SkiaDisplayList::pinImages(GrContext* context) { + if (mPinnedImages) return; + for (SkImage* image : mMutableImages) { + SkImage_pinAsTexture(image, context); + } + mPinnedImages = true; +} + +void SkiaDisplayList::unpinImages(GrContext* context) { + if (!mPinnedImages) return; + if (context) { + for (SkImage* image : mMutableImages) { + SkImage_unpinAsTexture(image, context); + } + } else { + gLock.acquire(); + for (SkImage* image : mMutableImages) { + gPinnedImages.emplace_back(sk_ref_sp(image)); + } + gLock.release(); + } + mPinnedImages = false; +} + +void SkiaDisplayList::cleanupImages(GrContext* context) { + gLock.acquire(); + for (auto& image : gPinnedImages) { + SkImage_unpinAsTexture(image.get(), context); + } + gPinnedImages.clear(); + gLock.release(); +} + +void SkiaDisplayList::reset(GrContext* context, SkRect bounds) { + unpinImages(context); + SkASSERT(!mPinnedImages); + mIsProjectionReceiver = false; + + mDrawable->reset(bounds); + + mMutableImages.clear(); + mVectorDrawables.clear(); + mChildFunctors.clear(); + mChildNodes.clear(); + + projectionReceiveIndex = -1; + allocator.~LinearAllocator(); + new (&allocator) LinearAllocator(); +} + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/SkiaDisplayList.h b/libs/hwui/SkiaDisplayList.h new file mode 100644 index 000000000000..c8a82bd7f57c --- /dev/null +++ b/libs/hwui/SkiaDisplayList.h @@ -0,0 +1,152 @@ +/* + * 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 "DisplayList.h" +#include "SkiaDrawables.h" + +#include <deque> +#include <SkLiteDL.h> +#include <SkPictureRecorder.h> + +namespace android { +namespace uirenderer { + +/** + * This class is intended to be self contained, but still subclasses from + * DisplayList to make it easier to support switching between the two at + * runtime. The downside of this inheritance is that we pay for the overhead + * of the parent class construction/destruction without any real benefit. + */ +class SkiaDisplayList : public DisplayList { +public: + SkiaDisplayList(SkRect bounds); + virtual ~SkiaDisplayList() { + /* Given that we are using a LinearStdAllocator to store some of the + * SkDrawable contents we must ensure that any other object that is + * holding a reference to those drawables is destroyed prior to their + * deletion. + */ + mDrawable.reset(); + } + + /** + * This resets the DisplayList so that it behaves as if the object were newly + * constructed with the provided bounds. The reuse avoids any overhead + * associated with destroying the SkLiteDL as well as the deques and vectors. + */ + void reset(GrContext* context, SkRect bounds); + + /** + * Use the linear allocator to create any SkDrawables needed by the display + * list. This could be dangerous as these objects are ref-counted, so we + * need to monitor that they don't extend beyond the lifetime of the class + * that creates them. + */ + template<class T, typename... Params> + SkDrawable* allocateDrawable(Params&&... params) { + return allocator.create<T>(std::forward<Params>(params)...); + } + + bool isSkiaDL() const override { return true; } + + /** + * Returns true if the DisplayList does not have any recorded content + */ + bool isEmpty() const override { return mDrawable->empty(); } + + /** + * Returns true if this list directly contains a GLFunctor drawing command. + */ + bool hasFunctor() const override { return !mChildFunctors.empty(); } + + /** + * Returns true if this list directly contains a VectorDrawable drawing command. + */ + bool hasVectorDrawables() const override { return !mVectorDrawables.empty(); } + + /** + * Attempts to reset and reuse this DisplayList. + * + * @return true if the displayList will be reused and therefore should not be deleted + */ + bool reuseDisplayList(RenderNode* node, renderthread::CanvasContext* context) override; + + /** + * ONLY to be called by RenderNode::syncDisplayList so that we can notify any + * contained VectorDrawables or GLFunctors to sync their state. + * + * NOTE: This function can be folded into RenderNode when we no longer need + * to subclass from DisplayList + */ + void syncContents() override; + + /** + * ONLY to be called by RenderNode::prepareTree in order to prepare this + * list while the UI thread is blocked. Here we can upload mutable bitmaps + * and notify our parent if any of our content has been invalidated and in + * need of a redraw. If the renderNode has any children then they are also + * call in order to prepare them. + * + * @return true if any content change requires the node to be invalidated + * + * NOTE: This function can be folded into RenderNode when we no longer need + * to subclass from DisplayList + */ + + bool prepareListAndChildren(TreeInfo& info, bool functorsNeedLayer, + std::function<void(RenderNode*, TreeInfo&, bool)> childFn) override; + + /** + * Calls the provided function once for each child of this DisplayList + */ + void updateChildren(std::function<void(RenderNode*)> updateFn) override; + + /** + * Pin/Unpin any mutable images to the GPU cache. A pinned images is + * guaranteed to be remain in the cache until it has been unpinned which + * we leverage to avoid making a CPU copy of the pixels. + */ + void pinImages(GrContext* context); + void unpinImages(GrContext* context); + + /** + * If a SkiaDisplayList is deleted on the UI thread we cache a list of any + * images that need unpinned from the GPU cache and call this function on + * a subsequent frame to perform that cleanup. + */ + static void cleanupImages(GrContext* context); + + /** + * We use std::deque here because (1) we need to iterate through these + * elements and (2) mDrawable holds pointers to the elements, so they cannot + * relocate. + */ + std::deque<RenderNodeDrawable> mChildNodes; + std::deque<GLFunctorDrawable> mChildFunctors; + std::vector<SkImage*> mMutableImages; + std::vector<VectorDrawableRoot*> mVectorDrawables; + sk_sp<SkLiteDL> mDrawable; + + bool mIsProjectionReceiver = false; + +private: + bool mPinnedImages = false; +}; + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/SkiaDrawables.h b/libs/hwui/SkiaDrawables.h new file mode 100644 index 000000000000..a1ceeaa0afb4 --- /dev/null +++ b/libs/hwui/SkiaDrawables.h @@ -0,0 +1,102 @@ +/* + * 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 "Layer.h" +#include "RenderNode.h" + +#include <SkCanvas.h> +#include <SkDrawable.h> +#include <SkMatrix.h> + +#include <utils/RefBase.h> +#include <utils/FatVector.h> +#include <utils/Functor.h> + +namespace android { + +class Functor; + +namespace uirenderer { + + +class RenderProperties; +class OffscreenBuffer; +class GlFunctorLifecycleListener; +class SkiaDisplayList; + +/** + * This drawable wraps a RenderNode and enables it to be recorded into a list + * of Skia drawing commands. + */ +class RenderNodeDrawable : public SkDrawable { +public: + explicit RenderNodeDrawable(RenderNode* node, SkCanvas* canvas) + : mRenderNode(node) + , mRecordedTransform(canvas->getTotalMatrix()) {} + + /** + * The renderNode (and its properties) that is to be drawn + */ + RenderNode* getRenderNode() const { return mRenderNode.get(); } + + /** + * Returns the transform on the canvas at time of recording and is used for + * computing total transform without rerunning DL contents. + */ + const SkMatrix& getRecordedMatrix() const { return mRecordedTransform; } + +protected: + virtual SkRect onGetBounds() override { + // We don't want to enable a record time quick reject because the properties + // of the RenderNode may be updated on subsequent frames. + return SkRect::MakeLargest(); + } + virtual void onDraw(SkCanvas* canvas) override { /* TODO */ } + +private: + sp<RenderNode> mRenderNode; + const SkMatrix mRecordedTransform; +}; + +/** + * This drawable wraps a OpenGL functor enabling it to be recorded into a list + * of Skia drawing commands. + */ +class GLFunctorDrawable : public SkDrawable { +public: + GLFunctorDrawable(Functor* functor, GlFunctorLifecycleListener* listener, SkCanvas* canvas) + : mFunctor(functor) + , mListener(listener) { + canvas->getClipBounds(&mBounds); + } + virtual ~GLFunctorDrawable() {} + + void syncFunctor() const { (*mFunctor)(DrawGlInfo::kModeSync, nullptr); } + + protected: + virtual SkRect onGetBounds() override { return mBounds; } + virtual void onDraw(SkCanvas* canvas) override { /* TODO */ } + + private: + Functor* mFunctor; + sp<GlFunctorLifecycleListener> mListener; + SkRect mBounds; +}; + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 652cddd968bb..44228f097190 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -85,6 +85,12 @@ public: */ static void destroyLayer(RenderNode* node); + /* + * If Properties::isSkiaEnabled() is true then this will return the Skia + * grContext associated with the current RenderPipeline. + */ + GrContext* getGrContext() const { return mRenderPipeline->getGrContext(); } + // Won't take effect until next EGLSurface creation void setSwapBehavior(SwapBehavior swapBehavior); diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h index 97cdf7fd4e8c..f96c2fdcfc42 100644 --- a/libs/hwui/renderthread/IRenderPipeline.h +++ b/libs/hwui/renderthread/IRenderPipeline.h @@ -22,6 +22,8 @@ #include <SkRect.h> #include <utils/RefBase.h> +class GrContext; + namespace android { class Surface; @@ -43,6 +45,8 @@ enum class MakeCurrentResult { Succeeded }; +class Frame; + class IRenderPipeline { public: virtual MakeCurrentResult makeCurrent() = 0; @@ -69,6 +73,7 @@ public: virtual TaskManager* getTaskManager() = 0; virtual bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator) = 0; + virtual GrContext* getGrContext() = 0; virtual ~IRenderPipeline() {} }; diff --git a/libs/hwui/renderthread/OpenGLPipeline.h b/libs/hwui/renderthread/OpenGLPipeline.h index 34d9bc0c96bd..d024aecec249 100644 --- a/libs/hwui/renderthread/OpenGLPipeline.h +++ b/libs/hwui/renderthread/OpenGLPipeline.h @@ -26,9 +26,6 @@ namespace android { namespace uirenderer { namespace renderthread { -class Frame; - - class OpenGLPipeline : public IRenderPipeline { public: OpenGLPipeline(RenderThread& thread); @@ -59,6 +56,7 @@ public: bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator) override; static void destroyLayer(RenderNode* node); + GrContext* getGrContext() override { return nullptr; } private: EglManager& mEglManager; diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp index 0d90afa06ab4..39c7f32e0da1 100644 --- a/libs/hwui/tests/unit/RenderNodeTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeTests.cpp @@ -160,7 +160,7 @@ RENDERTHREAD_TEST(RenderNode, prepareTree_HwLayer_AVD_enqueueDamage) { // Check that the VD is in the dislay list, and the layer update queue contains the correct // damage rect. - EXPECT_FALSE(rootNode->getDisplayList()->getVectorDrawables().empty()); + EXPECT_TRUE(rootNode->getDisplayList()->hasVectorDrawables()); EXPECT_FALSE(info.layerUpdateQueue->entries().empty()); EXPECT_EQ(rootNode.get(), info.layerUpdateQueue->entries().at(0).renderNode); EXPECT_EQ(uirenderer::Rect(0, 0, 200, 400), info.layerUpdateQueue->entries().at(0).damage); diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp new file mode 100644 index 000000000000..7f622eb96d04 --- /dev/null +++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp @@ -0,0 +1,189 @@ +/* + * 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 <gtest/gtest.h> +#include <VectorDrawable.h> + +#include "AnimationContext.h" +#include "DamageAccumulator.h" +#include "IContextFactory.h" +#include "SkiaDisplayList.h" +#include "renderthread/CanvasContext.h" +#include "tests/common/TestUtils.h" + + +using namespace android; +using namespace android::uirenderer; +using namespace android::uirenderer::renderthread; + +TEST(SkiaDisplayList, create) { + SkRect bounds = SkRect::MakeWH(200, 200); + SkiaDisplayList skiaDL(bounds); + ASSERT_TRUE(skiaDL.isEmpty()); + ASSERT_FALSE(skiaDL.mIsProjectionReceiver); + ASSERT_EQ(skiaDL.mDrawable->getBounds(), bounds); +} + +TEST(SkiaDisplayList, reset) { + SkRect bounds = SkRect::MakeWH(200, 200); + SkiaDisplayList skiaDL(bounds); + + SkCanvas dummyCanvas; + skiaDL.mChildNodes.emplace_back(nullptr, &dummyCanvas); + skiaDL.mChildFunctors.emplace_back(nullptr, nullptr, &dummyCanvas); + skiaDL.mMutableImages.push_back(nullptr); + skiaDL.mVectorDrawables.push_back(nullptr); + skiaDL.mDrawable->drawAnnotation(bounds, "testAnnotation", nullptr); + skiaDL.mIsProjectionReceiver = true; + + ASSERT_EQ(skiaDL.mDrawable->getBounds(), bounds); + ASSERT_FALSE(skiaDL.mChildNodes.empty()); + ASSERT_FALSE(skiaDL.mChildFunctors.empty()); + ASSERT_FALSE(skiaDL.mMutableImages.empty()); + ASSERT_FALSE(skiaDL.mVectorDrawables.empty()); + ASSERT_FALSE(skiaDL.isEmpty()); + ASSERT_TRUE(skiaDL.mIsProjectionReceiver); + + bounds = SkRect::MakeWH(100, 100); + skiaDL.reset(nullptr, bounds); + + ASSERT_EQ(skiaDL.mDrawable->getBounds(), bounds); + ASSERT_TRUE(skiaDL.mChildNodes.empty()); + ASSERT_TRUE(skiaDL.mChildFunctors.empty()); + ASSERT_TRUE(skiaDL.mMutableImages.empty()); + ASSERT_TRUE(skiaDL.mVectorDrawables.empty()); + ASSERT_TRUE(skiaDL.isEmpty()); + ASSERT_FALSE(skiaDL.mIsProjectionReceiver); +} + +TEST(SkiaDisplayList, reuseDisplayList) { + sp<RenderNode> renderNode = new RenderNode(); + std::unique_ptr<SkiaDisplayList> availableList; + + // no list has been attached so it should return a nullptr + availableList = renderNode->detachAvailableList(); + ASSERT_EQ(availableList.get(), nullptr); + + // attach a displayList for reuse + SkiaDisplayList skiaDL(SkRect::MakeWH(200, 200)); + ASSERT_TRUE(skiaDL.reuseDisplayList(renderNode.get(), nullptr)); + + // detach the list that you just attempted to reuse + availableList = renderNode->detachAvailableList(); + ASSERT_EQ(availableList.get(), &skiaDL); + availableList.release(); // prevents an invalid free since our DL is stack allocated + + // after detaching there should return no available list + availableList = renderNode->detachAvailableList(); + ASSERT_EQ(availableList.get(), nullptr); +} + +class TestFunctor : public Functor { +public: + bool didSync = false; + + virtual status_t operator ()(int what, void* data) { + if (what == DrawGlInfo::kModeSync) { didSync = true; } + return DrawGlInfo::kStatusDone; + } +}; + +TEST(SkiaDisplayList, syncContexts) { + SkRect bounds = SkRect::MakeWH(200, 200); + SkiaDisplayList skiaDL(bounds); + + SkCanvas dummyCanvas; + TestFunctor functor; + skiaDL.mChildFunctors.emplace_back(&functor, nullptr, &dummyCanvas); + + VectorDrawableRoot vectorDrawable(new VectorDrawable::Group()); + vectorDrawable.mutateStagingProperties()->setBounds(bounds); + skiaDL.mVectorDrawables.push_back(&vectorDrawable); + + // ensure that the functor and vectorDrawable are properly synced + skiaDL.syncContents(); + + ASSERT_TRUE(functor.didSync); + ASSERT_EQ(vectorDrawable.mutateProperties()->getBounds(), bounds); +} + +class ContextFactory : public IContextFactory { +public: + virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override { + return new AnimationContext(clock); + } +}; + +RENDERTHREAD_TEST(SkiaDisplayList, prepareListAndChildren) { + auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr); + ContextFactory contextFactory; + std::unique_ptr<CanvasContext> canvasContext(CanvasContext::create( + renderThread, false, rootNode.get(), &contextFactory)); + TreeInfo info(TreeInfo::MODE_FULL, *canvasContext.get()); + DamageAccumulator damageAccumulator; + info.damageAccumulator = &damageAccumulator; + info.observer = nullptr; + + SkiaDisplayList skiaDL(SkRect::MakeWH(200, 200)); + + // prepare with a clean VD + VectorDrawableRoot cleanVD(new VectorDrawable::Group()); + skiaDL.mVectorDrawables.push_back(&cleanVD); + cleanVD.getBitmapUpdateIfDirty(); // this clears the dirty bit + + ASSERT_FALSE(cleanVD.isDirty()); + ASSERT_FALSE(cleanVD.getPropertyChangeWillBeConsumed()); + ASSERT_FALSE(skiaDL.prepareListAndChildren(info, false, [](RenderNode*, TreeInfo&, bool) {})); + ASSERT_TRUE(cleanVD.getPropertyChangeWillBeConsumed()); + + // prepare again this time adding a dirty VD + VectorDrawableRoot dirtyVD(new VectorDrawable::Group()); + skiaDL.mVectorDrawables.push_back(&dirtyVD); + + ASSERT_TRUE(dirtyVD.isDirty()); + ASSERT_FALSE(dirtyVD.getPropertyChangeWillBeConsumed()); + ASSERT_TRUE(skiaDL.prepareListAndChildren(info, false, [](RenderNode*, TreeInfo&, bool) {})); + ASSERT_TRUE(dirtyVD.getPropertyChangeWillBeConsumed()); + + // prepare again this time adding a RenderNode and a callback + sp<RenderNode> renderNode = new RenderNode(); + TreeInfo* infoPtr = &info; + SkCanvas dummyCanvas; + skiaDL.mChildNodes.emplace_back(renderNode.get(), &dummyCanvas); + bool hasRun = false; + ASSERT_TRUE(skiaDL.prepareListAndChildren(info, false, + [&hasRun, renderNode, infoPtr](RenderNode* n, TreeInfo& i, bool r) { + hasRun = true; + ASSERT_EQ(renderNode.get(), n); + ASSERT_EQ(infoPtr, &i); + ASSERT_FALSE(r); + })); + ASSERT_TRUE(hasRun); + + canvasContext->destroy(nullptr); +} + +TEST(SkiaDisplayList, updateChildren) { + SkRect bounds = SkRect::MakeWH(200, 200); + SkiaDisplayList skiaDL(bounds); + + sp<RenderNode> renderNode = new RenderNode(); + SkCanvas dummyCanvas; + skiaDL.mChildNodes.emplace_back(renderNode.get(), &dummyCanvas); + skiaDL.updateChildren([renderNode](RenderNode* n) { + ASSERT_EQ(renderNode.get(), n); + }); +} |