Initial refactoring to enable the addition of the SkiaOpenGLPipeline.

Test: existing and new HWUI unit tests all pass.
Change-Id: I4f5c1dc839a2ed15d8b0f6245fe030684501b083
This commit is contained in:
Derek Sollenberger
2016-09-27 16:04:42 -04:00
parent 4306608707
commit 0df6209a02
15 changed files with 746 additions and 59 deletions

View File

@@ -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 \

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -308,6 +308,7 @@ public:
static ProfileType getProfileType();
static RenderPipelineType getRenderPipelineType();
static bool isSkiaEnabled();
// Should be used only by test apps
static bool waitForGpuCompletion;

View File

@@ -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

View File

@@ -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 */

View File

@@ -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

152
libs/hwui/SkiaDisplayList.h Normal file
View File

@@ -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

102
libs/hwui/SkiaDrawables.h Normal file
View File

@@ -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

View File

@@ -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);

View File

@@ -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() {}
};

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);
});
}