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:
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -308,6 +308,7 @@ public:
|
||||
|
||||
static ProfileType getProfileType();
|
||||
static RenderPipelineType getRenderPipelineType();
|
||||
static bool isSkiaEnabled();
|
||||
|
||||
// Should be used only by test apps
|
||||
static bool waitForGpuCompletion;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
134
libs/hwui/SkiaDisplayList.cpp
Normal file
134
libs/hwui/SkiaDisplayList.cpp
Normal 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
152
libs/hwui/SkiaDisplayList.h
Normal 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
102
libs/hwui/SkiaDrawables.h
Normal 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
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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() {}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
189
libs/hwui/tests/unit/SkiaDisplayListTests.cpp
Normal file
189
libs/hwui/tests/unit/SkiaDisplayListTests.cpp
Normal 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);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user