Initial refactoring to enable the addition of the SkiaOpenGLPipeline.
Test: existing and new HWUI unit tests all pass.
Change-Id: I4f5c1dc839a2ed15d8b0f6245fe030684501b083
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 81a1831..a13cdde 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -95,6 +95,7 @@
ShadowTessellator.cpp \
SkiaCanvas.cpp \
SkiaCanvasProxy.cpp \
+ SkiaDisplayList.cpp \
SkiaShader.cpp \
Snapshot.cpp \
SpotShadow.cpp \
@@ -280,6 +281,7 @@
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 ca9e2bd..6e7d11f 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 @@
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 c5d8767..06b0891 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 @@
};
DisplayList();
- ~DisplayList();
+ virtual ~DisplayList();
// index of DisplayListOp restore, after which projected descendants should be drawn
int projectionReceiveIndex;
@@ -100,8 +102,6 @@
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 @@
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 93b2e15..848161e 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -214,7 +214,7 @@
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 @@
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 eedc9e7..c4486a4 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -308,6 +308,7 @@
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 8c36ab5..a03ded6 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -51,7 +51,7 @@
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 @@
<< (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 @@
|| 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 @@
damageSelf(info);
}
- if (!mLayer) {
+ if (!hasLayer()) {
Caches::getInstance().dumpMemoryUsage();
if (info.errorHandler) {
std::ostringstream err;
@@ -295,9 +295,9 @@
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 @@
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 @@
// 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::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 da93c13..a05c744 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 SkPaint;
class SkPath;
class SkRegion;
+class SkSurface;
namespace android {
namespace uirenderer {
@@ -48,6 +50,7 @@
class FrameBuilder;
class OffscreenBuffer;
class Rect;
+class SkiaDisplayList;
class SkiaShader;
struct RenderNodeOp;
@@ -240,7 +243,6 @@
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 @@
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 0000000..d10f306
--- /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 0000000..c8a82bd
--- /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 0000000..a1ceeaa
--- /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 652cddd..44228f0 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -85,6 +85,12 @@
*/
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 97cdf7f..f96c2fd 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 @@
Succeeded
};
+class Frame;
+
class IRenderPipeline {
public:
virtual MakeCurrentResult makeCurrent() = 0;
@@ -69,6 +73,7 @@
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 34d9bc0..d024aec 100644
--- a/libs/hwui/renderthread/OpenGLPipeline.h
+++ b/libs/hwui/renderthread/OpenGLPipeline.h
@@ -26,9 +26,6 @@
namespace uirenderer {
namespace renderthread {
-class Frame;
-
-
class OpenGLPipeline : public IRenderPipeline {
public:
OpenGLPipeline(RenderThread& thread);
@@ -59,6 +56,7 @@
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 0d90afa..39c7f32 100644
--- a/libs/hwui/tests/unit/RenderNodeTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeTests.cpp
@@ -160,7 +160,7 @@
// 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 0000000..7f622eb
--- /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);
+ });
+}