Merge "Support projection in OpReorderer"

This commit is contained in:
Chris Craik
2015-11-25 22:14:42 +00:00
committed by Android (Google) Code Review
18 changed files with 326 additions and 139 deletions

View File

@@ -276,7 +276,9 @@ LOCAL_MULTILIB := both
LOCAL_MODULE_STEM_32 := hwuimicro
LOCAL_MODULE_STEM_64 := hwuimicro64
LOCAL_SHARED_LIBRARIES := $(hwui_shared_libraries)
LOCAL_CFLAGS := $(hwui_cflags)
LOCAL_CFLAGS := \
$(hwui_cflags) \
-DHWUI_NULL_GPU
LOCAL_C_INCLUDES += bionic/benchmarks/
LOCAL_WHOLE_STATIC_LIBRARIES := libhwui_static_null_gpu

View File

@@ -132,7 +132,7 @@ public:
DisplayList();
~DisplayList();
// index of DisplayListOp restore, after which projected descendents should be drawn
// index of DisplayListOp restore, after which projected descendants should be drawn
int projectionReceiveIndex;
const LsaVector<Chunk>& getChunks() const { return chunks; }

View File

@@ -55,6 +55,7 @@ class DeferredDisplayList;
class DeferredLayerUpdater;
class DisplayListOp;
class DrawOp;
class DrawRenderNodeOp;
class RenderNode;
class StateOp;

View File

@@ -1386,19 +1386,19 @@ public:
: DrawBoundedOp(0, 0, renderNode->getWidth(), renderNode->getHeight(), nullptr)
, renderNode(renderNode)
, mRecordedWithPotentialStencilClip(!clipIsSimple || !transformFromParent.isSimple())
, mTransformFromParent(transformFromParent)
, mSkipInOrderDraw(false) {}
, localMatrix(transformFromParent)
, skipInOrderDraw(false) {}
virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level,
bool useQuickReject) override {
if (renderNode->isRenderable() && !mSkipInOrderDraw) {
if (renderNode->isRenderable() && !skipInOrderDraw) {
renderNode->defer(deferStruct, level + 1);
}
}
virtual void replay(ReplayStateStruct& replayStruct, int saveCount, int level,
bool useQuickReject) override {
if (renderNode->isRenderable() && !mSkipInOrderDraw) {
if (renderNode->isRenderable() && !skipInOrderDraw) {
renderNode->replay(replayStruct, level + 1);
}
}
@@ -1439,7 +1439,7 @@ private:
/**
* Records transform vs parent, used for computing total transform without rerunning DL contents
*/
const mat4 mTransformFromParent;
const mat4 localMatrix;
/**
* Holds the transformation between the projection surface ViewGroup and this RenderNode
@@ -1449,8 +1449,8 @@ private:
*
* Note: doesn't include transformation within the RenderNode, or its properties.
*/
mat4 mTransformFromCompositingAncestor;
bool mSkipInOrderDraw;
mat4 transformFromCompositingAncestor;
bool skipInOrderDraw;
};
/**

View File

@@ -330,6 +330,7 @@ OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
for (int i = layers.entries().size() - 1; i >= 0; i--) {
RenderNode* layerNode = layers.entries()[i].renderNode;
const Rect& layerDamage = layers.entries()[i].damage;
layerNode->computeOrdering();
// map current light center into RenderNode's coordinate space
Vector3 lightCenter = mCanvasState.currentSnapshot()->getRelativeLightCenter();
@@ -339,7 +340,7 @@ OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
layerDamage, lightCenter, nullptr, layerNode);
if (layerNode->getDisplayList()) {
deferDisplayList(*(layerNode->getDisplayList()));
deferNodeOps(*layerNode);
}
restoreForLayer();
}
@@ -347,6 +348,7 @@ OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
// Defer Fbo0
for (const sp<RenderNode>& node : nodes) {
if (node->nothingToDraw()) continue;
node->computeOrdering();
int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
deferNodePropsAndOps(*node);
@@ -354,20 +356,6 @@ OpReorderer::OpReorderer(const LayerUpdateQueue& layers, const SkRect& clip,
}
}
OpReorderer::OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList,
const Vector3& lightCenter)
: mCanvasState(*this) {
ATRACE_NAME("prepare drawing commands");
// Prepare to defer Fbo0
mLayerReorderers.emplace_back(viewportWidth, viewportHeight,
Rect(viewportWidth, viewportHeight));
mLayerStack.push_back(0);
mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
0, 0, viewportWidth, viewportHeight, lightCenter);
deferDisplayList(displayList);
}
void OpReorderer::onViewportInitialized() {}
void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
@@ -462,10 +450,10 @@ void OpReorderer::deferNodePropsAndOps(RenderNode& node) {
Matrix4::identity(),
saveLayerBounds,
&saveLayerPaint));
deferDisplayList(*(node.getDisplayList()));
deferNodeOps(node);
onEndLayerOp(*new (mAllocator) EndLayerOp());
} else {
deferDisplayList(*(node.getDisplayList()));
deferNodeOps(node);
}
}
}
@@ -610,18 +598,53 @@ void OpReorderer::deferShadow(const RenderNodeOp& casterNodeOp) {
}
}
void OpReorderer::deferProjectedChildren(const RenderNode& renderNode) {
const SkPath* projectionReceiverOutline = renderNode.properties().getOutline().getPath();
int count = mCanvasState.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
// can't be null, since DL=null node rejection happens before deferNodePropsAndOps
const DisplayList& displayList = *(renderNode.getDisplayList());
const RecordedOp* op = (displayList.getOps()[displayList.projectionReceiveIndex]);
const RenderNodeOp* backgroundOp = static_cast<const RenderNodeOp*>(op);
const RenderProperties& backgroundProps = backgroundOp->renderNode->properties();
// Transform renderer to match background we're projecting onto
// (by offsetting canvas by translationX/Y of background rendernode, since only those are set)
mCanvasState.translate(backgroundProps.getTranslationX(), backgroundProps.getTranslationY());
// If the projection receiver has an outline, we mask projected content to it
// (which we know, apriori, are all tessellated paths)
mCanvasState.setProjectionPathMask(mAllocator, projectionReceiverOutline);
// draw projected nodes
for (size_t i = 0; i < renderNode.mProjectedNodes.size(); i++) {
RenderNodeOp* childOp = renderNode.mProjectedNodes[i];
int restoreTo = mCanvasState.save(SkCanvas::kMatrix_SaveFlag);
mCanvasState.concatMatrix(childOp->transformFromCompositingAncestor);
deferRenderNodeOp(*childOp);
mCanvasState.restoreToCount(restoreTo);
}
mCanvasState.restoreToCount(count);
}
/**
* Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods.
*
* This allows opIds embedded in the RecordedOps to be used for dispatching to these lambdas. E.g. a
* BitmapOp op then would be dispatched to OpReorderer::onBitmapOp(const BitmapOp&)
* This allows opIds embedded in the RecordedOps to be used for dispatching to these lambdas.
* E.g. a BitmapOp op then would be dispatched to OpReorderer::onBitmapOp(const BitmapOp&)
*/
#define OP_RECEIVER(Type) \
[](OpReorderer& reorderer, const RecordedOp& op) { reorderer.on##Type(static_cast<const Type&>(op)); },
void OpReorderer::deferDisplayList(const DisplayList& displayList) {
void OpReorderer::deferNodeOps(const RenderNode& renderNode) {
static std::function<void(OpReorderer& reorderer, const RecordedOp&)> receivers[] = {
MAP_OPS(OP_RECEIVER)
};
// can't be null, since DL=null node rejection happens before deferNodePropsAndOps
const DisplayList& displayList = *(renderNode.getDisplayList());
for (const DisplayList::Chunk& chunk : displayList.getChunks()) {
FatVector<ZRenderNodeOpPair, 16> zTranslatedNodes;
buildZSortedChildList(&zTranslatedNodes, displayList, chunk);
@@ -630,6 +653,12 @@ void OpReorderer::deferDisplayList(const DisplayList& displayList) {
for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
const RecordedOp* op = displayList.getOps()[opIndex];
receivers[op->opId](*this, *op);
if (CC_UNLIKELY(!renderNode.mProjectedNodes.empty()
&& displayList.projectionReceiveIndex >= 0
&& static_cast<int>(opIndex) == displayList.projectionReceiveIndex)) {
deferProjectedChildren(renderNode);
}
}
defer3dChildren(ChildrenSelectMode::Positive, zTranslatedNodes);
}

View File

@@ -124,9 +124,6 @@ public:
uint32_t viewportWidth, uint32_t viewportHeight,
const std::vector< sp<RenderNode> >& nodes, const Vector3& lightCenter);
OpReorderer(int viewportWidth, int viewportHeight, const DisplayList& displayList,
const Vector3& lightCenter);
virtual ~OpReorderer() {}
/**
@@ -202,16 +199,18 @@ private:
return BakedOpState::tryConstruct(mAllocator, *mCanvasState.currentSnapshot(), recordedOp);
}
// should always be surrounded by a save/restore pair
// should always be surrounded by a save/restore pair, and not called if DisplayList is null
void deferNodePropsAndOps(RenderNode& node);
void deferShadow(const RenderNodeOp& casterOp);
void deferDisplayList(const DisplayList& displayList);
template <typename V>
void defer3dChildren(ChildrenSelectMode mode, const V& zTranslatedNodes);
void deferShadow(const RenderNodeOp& casterOp);
void deferProjectedChildren(const RenderNode& renderNode);
void deferNodeOps(const RenderNode& renderNode);
void deferRenderNodeOp(const RenderNodeOp& op);
void replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers);

View File

@@ -97,7 +97,17 @@ struct RenderNodeOp : RecordedOp {
RenderNodeOp(BASE_PARAMS_PAINTLESS, RenderNode* renderNode)
: SUPER_PAINTLESS(RenderNodeOp)
, renderNode(renderNode) {}
RenderNode * renderNode; // not const, since drawing modifies it (somehow...)
RenderNode * renderNode; // not const, since drawing modifies it
/**
* Holds the transformation between the projection surface ViewGroup and this RenderNode
* drawing instance. Represents any translations / transformations done within the drawing of
* the compositing ancestor ViewGroup's draw, before the draw of the View represented by this
* DisplayList draw instance.
*
* Note: doesn't include transformation within the RenderNode, or its properties.
*/
Matrix4 transformFromCompositingAncestor;
bool skipInOrderDraw = false;
};

View File

@@ -487,7 +487,7 @@ void RenderNode::prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayL
info.damageAccumulator->pushTransform(&op->localMatrix);
bool childFunctorsNeedLayer = functorsNeedLayer; // TODO! || op->mRecordedWithPotentialStencilClip;
#else
info.damageAccumulator->pushTransform(&op->mTransformFromParent);
info.damageAccumulator->pushTransform(&op->localMatrix);
bool childFunctorsNeedLayer = functorsNeedLayer
// Recorded with non-rect clip, or canvas-rotated by parent
|| op->mRecordedWithPotentialStencilClip;
@@ -658,7 +658,6 @@ void RenderNode::applyViewPropertyTransforms(mat4& matrix, bool true3dTransform)
* which are flagged to not draw in the standard draw loop.
*/
void RenderNode::computeOrdering() {
#if !HWUI_NEW_OPS
ATRACE_CALL();
mProjectedNodes.clear();
@@ -666,43 +665,41 @@ void RenderNode::computeOrdering() {
// transform properties are applied correctly to top level children
if (mDisplayList == nullptr) return;
for (unsigned int i = 0; i < mDisplayList->getChildren().size(); i++) {
DrawRenderNodeOp* childOp = mDisplayList->getChildren()[i];
renderNodeOp_t* childOp = mDisplayList->getChildren()[i];
childOp->renderNode->computeOrderingImpl(childOp, &mProjectedNodes, &mat4::identity());
}
#endif
}
void RenderNode::computeOrderingImpl(
DrawRenderNodeOp* opState,
std::vector<DrawRenderNodeOp*>* compositedChildrenOfProjectionSurface,
renderNodeOp_t* opState,
std::vector<renderNodeOp_t*>* compositedChildrenOfProjectionSurface,
const mat4* transformFromProjectionSurface) {
#if !HWUI_NEW_OPS
mProjectedNodes.clear();
if (mDisplayList == nullptr || mDisplayList->isEmpty()) return;
// TODO: should avoid this calculation in most cases
// TODO: just calculate single matrix, down to all leaf composited elements
Matrix4 localTransformFromProjectionSurface(*transformFromProjectionSurface);
localTransformFromProjectionSurface.multiply(opState->mTransformFromParent);
localTransformFromProjectionSurface.multiply(opState->localMatrix);
if (properties().getProjectBackwards()) {
// composited projectee, flag for out of order draw, save matrix, and store in proj surface
opState->mSkipInOrderDraw = true;
opState->mTransformFromCompositingAncestor = localTransformFromProjectionSurface;
opState->skipInOrderDraw = true;
opState->transformFromCompositingAncestor = localTransformFromProjectionSurface;
compositedChildrenOfProjectionSurface->push_back(opState);
} else {
// standard in order draw
opState->mSkipInOrderDraw = false;
opState->skipInOrderDraw = false;
}
if (mDisplayList->getChildren().size() > 0) {
const bool isProjectionReceiver = mDisplayList->projectionReceiveIndex >= 0;
bool haveAppliedPropertiesToProjection = false;
for (unsigned int i = 0; i < mDisplayList->getChildren().size(); i++) {
DrawRenderNodeOp* childOp = mDisplayList->getChildren()[i];
renderNodeOp_t* childOp = mDisplayList->getChildren()[i];
RenderNode* child = childOp->renderNode;
std::vector<DrawRenderNodeOp*>* projectionChildren = nullptr;
std::vector<renderNodeOp_t*>* projectionChildren = nullptr;
const mat4* projectionTransform = nullptr;
if (isProjectionReceiver && !child->properties().getProjectBackwards()) {
// if receiving projections, collect projecting descendant
@@ -723,7 +720,6 @@ void RenderNode::computeOrderingImpl(
child->computeOrderingImpl(childOp, projectionChildren, projectionTransform);
}
}
#endif
}
class DeferOperationHandler {
@@ -793,10 +789,10 @@ void RenderNode::buildZSortedChildList(const DisplayList::Chunk& chunk,
if (!MathUtils::isZero(childZ) && chunk.reorderChildren) {
zTranslatedNodes.push_back(ZDrawRenderNodeOpPair(childZ, childOp));
childOp->mSkipInOrderDraw = true;
childOp->skipInOrderDraw = true;
} else if (!child->properties().getProjectBackwards()) {
// regular, in order drawing DisplayList
childOp->mSkipInOrderDraw = false;
childOp->skipInOrderDraw = false;
}
}
@@ -913,7 +909,7 @@ void RenderNode::issueOperationsOf3dChildren(ChildrenSelectMode mode,
// attempt to render the shadow if the caster about to be drawn is its caster,
// OR if its caster's Z value is similar to the previous potential caster
if (shadowIndex == drawIndex || casterZ - lastCasterZ < SHADOW_DELTA) {
caster->issueDrawShadowOperation(casterOp->mTransformFromParent, handler);
caster->issueDrawShadowOperation(casterOp->localMatrix, handler);
lastCasterZ = casterZ; // must do this even if current caster not casting a shadow
shadowIndex++;
@@ -927,10 +923,10 @@ void RenderNode::issueOperationsOf3dChildren(ChildrenSelectMode mode,
DrawRenderNodeOp* childOp = zTranslatedNodes[drawIndex].value;
renderer.concatMatrix(childOp->mTransformFromParent);
childOp->mSkipInOrderDraw = false; // this is horrible, I'm so sorry everyone
renderer.concatMatrix(childOp->localMatrix);
childOp->skipInOrderDraw = false; // this is horrible, I'm so sorry everyone
handler(childOp, renderer.getSaveCount() - 1, properties().getClipToBounds());
childOp->mSkipInOrderDraw = true;
childOp->skipInOrderDraw = true;
renderer.restoreToCount(restoreTo);
drawIndex++;
@@ -967,14 +963,14 @@ void RenderNode::issueOperationsOfProjectedChildren(OpenGLRenderer& renderer, T&
// draw projected nodes
for (size_t i = 0; i < mProjectedNodes.size(); i++) {
DrawRenderNodeOp* childOp = mProjectedNodes[i];
renderNodeOp_t* childOp = mProjectedNodes[i];
// matrix save, concat, and restore can be done safely without allocating operations
int restoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag);
renderer.concatMatrix(childOp->mTransformFromCompositingAncestor);
childOp->mSkipInOrderDraw = false; // this is horrible, I'm so sorry everyone
renderer.concatMatrix(childOp->transformFromCompositingAncestor);
childOp->skipInOrderDraw = false; // this is horrible, I'm so sorry everyone
handler(childOp, renderer.getSaveCount() - 1, properties().getClipToBounds());
childOp->mSkipInOrderDraw = true;
childOp->skipInOrderDraw = true;
renderer.restoreToCount(restoreTo);
}

View File

@@ -51,20 +51,22 @@ class OpReorderer;
class Rect;
class SkiaShader;
#if HWUI_NEW_OPS
class OffscreenBuffer;
struct RenderNodeOp;
typedef OffscreenBuffer layer_t;
typedef RenderNodeOp renderNodeOp_t;
#else
class Layer;
typedef Layer layer_t;
typedef DrawRenderNodeOp renderNodeOp_t;
#endif
class ClipRectOp;
class DrawRenderNodeOp;
class SaveLayerOp;
class SaveOp;
class RestoreToCountOp;
class DrawRenderNodeOp;
class TreeInfo;
namespace proto {
@@ -85,6 +87,7 @@ class RenderNode;
*/
class RenderNode : public VirtualLightRefBase {
friend class TestUtils; // allow TestUtils to access syncDisplayList / syncProperties
friend class OpReorderer;
public:
enum DirtyPropertyMask {
GENERIC = 1 << 1,
@@ -221,8 +224,8 @@ private:
PositiveZChildren
};
void computeOrderingImpl(DrawRenderNodeOp* opState,
std::vector<DrawRenderNodeOp*>* compositedChildrenOfProjectionSurface,
void computeOrderingImpl(renderNodeOp_t* opState,
std::vector<renderNodeOp_t*>* compositedChildrenOfProjectionSurface,
const mat4* transformFromProjectionSurface);
template <class T>
@@ -305,7 +308,7 @@ private:
*/
// for projection surfaces, contains a list of all children items
std::vector<DrawRenderNodeOp*> mProjectedNodes;
std::vector<renderNodeOp_t*> mProjectedNodes;
// How many references our parent(s) have to us. Typically this should alternate
// between 2 and 1 (when a staging push happens we inc first then dec)

View File

@@ -19,10 +19,12 @@
#include "BakedOpState.h"
#include "BakedOpDispatcher.h"
#include "BakedOpRenderer.h"
#include "LayerUpdateQueue.h"
#include "OpReorderer.h"
#include "RecordedOp.h"
#include "RecordingCanvas.h"
#include "utils/TestUtils.h"
#include "Vector.h"
#include "microbench/MicroBench.h"
#include <vector>
@@ -30,26 +32,38 @@
using namespace android;
using namespace android::uirenderer;
auto sReorderingDisplayList = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
SkBitmap bitmap = TestUtils::createSkBitmap(10, 10);
SkPaint paint;
const LayerUpdateQueue sEmptyLayerUpdateQueue;
const Vector3 sLightCenter = {100, 100, 100};
// Alternate between drawing rects and bitmaps, with bitmaps overlapping rects.
// Rects don't overlap bitmaps, so bitmaps should be brought to front as a group.
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
for (int i = 0; i < 30; i++) {
canvas.translate(0, 10);
canvas.drawRect(0, 0, 10, 10, paint);
canvas.drawBitmap(bitmap, 5, 0, nullptr);
}
canvas.restore();
});
static std::vector<sp<RenderNode>> createTestNodeList() {
auto node = TestUtils::createNode(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
SkBitmap bitmap = TestUtils::createSkBitmap(10, 10);
SkPaint paint;
// Alternate between drawing rects and bitmaps, with bitmaps overlapping rects.
// Rects don't overlap bitmaps, so bitmaps should be brought to front as a group.
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
for (int i = 0; i < 30; i++) {
canvas.translate(0, 10);
canvas.drawRect(0, 0, 10, 10, paint);
canvas.drawBitmap(bitmap, 5, 0, nullptr);
}
canvas.restore();
});
TestUtils::syncHierarchyPropertiesAndDisplayList(node);
std::vector<sp<RenderNode>> vec;
vec.emplace_back(node);
return vec;
}
BENCHMARK_NO_ARG(BM_OpReorderer_defer);
void BM_OpReorderer_defer::Run(int iters) {
auto nodes = createTestNodeList();
StartBenchmarkTiming();
for (int i = 0; i < iters; i++) {
OpReorderer reorderer(200, 200, *sReorderingDisplayList, (Vector3) { 100, 100, 100 });
OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
nodes, sLightCenter);
MicroBench::DoNotOptimize(&reorderer);
}
StopBenchmarkTiming();
@@ -58,13 +72,16 @@ void BM_OpReorderer_defer::Run(int iters) {
BENCHMARK_NO_ARG(BM_OpReorderer_deferAndRender);
void BM_OpReorderer_deferAndRender::Run(int iters) {
TestUtils::runOnRenderThread([this, iters](renderthread::RenderThread& thread) {
auto nodes = createTestNodeList();
BakedOpRenderer::LightInfo lightInfo = {50.0f, 128, 128 };
RenderState& renderState = thread.renderState();
Caches& caches = Caches::getInstance();
BakedOpRenderer::LightInfo lightInfo = { 50.0f, 128, 128 };
StartBenchmarkTiming();
for (int i = 0; i < iters; i++) {
OpReorderer reorderer(200, 200, *sReorderingDisplayList, (Vector3) { 100, 100, 100 });
OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
nodes, sLightCenter);
BakedOpRenderer renderer(caches, renderState, true, lightInfo);
reorderer.replayBakedOps<BakedOpDispatcher>(renderer);

View File

@@ -29,17 +29,14 @@ public:
sp<RenderNode> card;
void createContent(int width, int height, TestCanvas& canvas) override {
canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
canvas.insertReorderBarrier(true);
card = TestUtils::createNode(0, 0, 200, 200, [](TestCanvas& canvas) {
card = TestUtils::createNode(0, 0, 200, 200,
[](RenderProperties& props, TestCanvas& canvas) {
SkPaint paint;
paint.setAntiAlias(true);
paint.setColor(0xFF000000);
canvas.drawOval(0, 0, 200, 200, paint);
});
canvas.drawRenderNode(card.get());
canvas.insertReorderBarrier(false);
}
void doFrame(int frameNr) override {

View File

@@ -44,7 +44,7 @@ public:
SkColor color = COLORS[static_cast<int>((y / dp(116))) % 4];
sp<RenderNode> card = TestUtils::createNode(x, y,
x + dp(100), y + dp(100),
[color](TestCanvas& canvas) {
[color](RenderProperties& props, TestCanvas& canvas) {
canvas.drawColor(color, SkXfermode::kSrcOver_Mode);
});
canvas.drawRenderNode(card.get());

View File

@@ -34,7 +34,7 @@ public:
canvas.insertReorderBarrier(true);
card = TestUtils::createNode(50, 50, 250, 250,
[](TestCanvas& canvas) {
[](RenderProperties& props, TestCanvas& canvas) {
canvas.drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode);
SkRegion region;

View File

@@ -32,7 +32,7 @@ public:
canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
card = TestUtils::createNode(0, 0, 200, 200,
[](TestCanvas& canvas) {
[](RenderProperties& props, TestCanvas& canvas) {
canvas.saveLayerAlpha(0, 0, 200, 200, 128, SkCanvas::kClipToLayer_SaveFlag);
canvas.drawColor(0xFF00FF00, SkXfermode::kSrcOver_Mode); // outer, unclipped
canvas.saveLayerAlpha(50, 50, 150, 150, 128, SkCanvas::kClipToLayer_SaveFlag);

View File

@@ -31,7 +31,7 @@ TEST(LayerUpdateQueue, construct) {
// sync node properties, so properties() reflects correct width and height
static sp<RenderNode> createSyncedNode(uint32_t width, uint32_t height) {
sp<RenderNode> node = TestUtils::createNode(0, 0, width, height);
sp<RenderNode> node = TestUtils::createNode(0, 0, width, height, nullptr);
TestUtils::syncHierarchyPropertiesAndDisplayList(node);
return node;
}

View File

@@ -111,25 +111,29 @@ TEST(OpReorderer, simple) {
}
};
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
auto node = TestUtils::createNode(0, 0, 100, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
SkBitmap bitmap = TestUtils::createSkBitmap(25, 25);
canvas.drawRect(0, 0, 100, 200, SkPaint());
canvas.drawBitmap(bitmap, 10, 10, nullptr);
});
OpReorderer reorderer(100, 200, *dl, sLightCenter);
OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
createSyncedNodeList(node), sLightCenter);
SimpleTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(4, renderer.getIndex()); // 2 ops + start + end
}
TEST(OpReorderer, simpleRejection) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
auto node = TestUtils::createNode(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
canvas.clipRect(200, 200, 400, 400, SkRegion::kIntersect_Op); // intersection should be empty
canvas.drawRect(0, 0, 400, 400, SkPaint());
canvas.restore();
});
OpReorderer reorderer(200, 200, *dl, sLightCenter);
OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
createSyncedNodeList(node), sLightCenter);
FailRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
@@ -147,7 +151,8 @@ TEST(OpReorderer, simpleBatching) {
}
};
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
auto node = TestUtils::createNode(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
SkBitmap bitmap = TestUtils::createSkBitmap(10, 10);
// Alternate between drawing rects and bitmaps, with bitmaps overlapping rects.
@@ -161,7 +166,8 @@ TEST(OpReorderer, simpleBatching) {
canvas.restore();
});
OpReorderer reorderer(200, 200, *dl, sLightCenter);
OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
createSyncedNodeList(node), sLightCenter);
SimpleBatchingTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(2 * LOOPS, renderer.getIndex())
@@ -179,7 +185,8 @@ TEST(OpReorderer, textStrikethroughBatching) {
EXPECT_TRUE(mIndex++ < LOOPS) << "Text should be beneath all strikethrough rects";
}
};
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 2000, [](RecordingCanvas& canvas) {
auto node = TestUtils::createNode(0, 0, 200, 2000,
[](RenderProperties& props, RecordingCanvas& canvas) {
SkPaint textPaint;
textPaint.setAntiAlias(true);
textPaint.setTextSize(20);
@@ -189,7 +196,8 @@ TEST(OpReorderer, textStrikethroughBatching) {
TestUtils::drawTextToCanvas(&canvas, "test text", textPaint, 10, 100 * (i + 1));
}
});
OpReorderer reorderer(200, 2000, *dl, sLightCenter);
OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 2000), 200, 2000,
createSyncedNodeList(node), sLightCenter);
TextStrikethroughTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(2 * LOOPS, renderer.getIndex())
@@ -215,14 +223,16 @@ TEST(OpReorderer, renderNode) {
}
};
sp<RenderNode> child = TestUtils::createNode(10, 10, 110, 110, [](RecordingCanvas& canvas) {
auto child = TestUtils::createNode(10, 10, 110, 110,
[](RenderProperties& props, RecordingCanvas& canvas) {
SkPaint paint;
paint.setColor(SK_ColorWHITE);
canvas.drawRect(0, 0, 100, 100, paint);
});
RenderNode* childPtr = child.get();
sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200, [childPtr](RecordingCanvas& canvas) {
auto parent = TestUtils::createNode(0, 0, 200, 200,
[childPtr](RenderProperties& props, RecordingCanvas& canvas) {
SkPaint paint;
paint.setColor(SK_ColorDKGRAY);
canvas.drawRect(0, 0, 200, 200, paint);
@@ -250,7 +260,8 @@ TEST(OpReorderer, clipped) {
}
};
sp<RenderNode> node = TestUtils::createNode(0, 0, 200, 200, [](RecordingCanvas& canvas) {
auto node = TestUtils::createNode(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
SkBitmap bitmap = TestUtils::createSkBitmap(200, 200);
canvas.drawBitmap(bitmap, 0, 0, nullptr);
});
@@ -292,13 +303,14 @@ TEST(OpReorderer, saveLayerSimple) {
}
};
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
auto node = TestUtils::createNode(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
canvas.saveLayerAlpha(10, 10, 190, 190, 128, SkCanvas::kClipToLayer_SaveFlag);
canvas.drawRect(10, 10, 190, 190, SkPaint());
canvas.restore();
});
OpReorderer reorderer(200, 200, *dl, sLightCenter);
OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
createSyncedNodeList(node), sLightCenter);
SaveLayerSimpleTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(4, renderer.getIndex());
@@ -355,7 +367,8 @@ TEST(OpReorderer, saveLayerNested) {
}
};
auto dl = TestUtils::createDisplayList<RecordingCanvas>(800, 800, [](RecordingCanvas& canvas) {
auto node = TestUtils::createNode(0, 0, 800, 800,
[](RenderProperties& props, RecordingCanvas& canvas) {
canvas.saveLayerAlpha(0, 0, 800, 800, 128, SkCanvas::kClipToLayer_SaveFlag);
{
canvas.drawRect(0, 0, 800, 800, SkPaint());
@@ -368,14 +381,16 @@ TEST(OpReorderer, saveLayerNested) {
canvas.restore();
});
OpReorderer reorderer(800, 800, *dl, sLightCenter);
OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(800, 800), 800, 800,
createSyncedNodeList(node), sLightCenter);
SaveLayerNestedTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(10, renderer.getIndex());
}
TEST(OpReorderer, saveLayerContentRejection) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
auto node = TestUtils::createNode(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
canvas.clipRect(200, 200, 400, 400, SkRegion::kIntersect_Op);
canvas.saveLayerAlpha(200, 200, 400, 400, 128, SkCanvas::kClipToLayer_SaveFlag);
@@ -386,7 +401,8 @@ TEST(OpReorderer, saveLayerContentRejection) {
canvas.restore();
canvas.restore();
});
OpReorderer reorderer(200, 200, *dl, sLightCenter);
OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
createSyncedNodeList(node), sLightCenter);
FailRenderer renderer;
// should see no ops, even within the layer, since the layer should be rejected
@@ -425,7 +441,7 @@ RENDERTHREAD_TEST(OpReorderer, hwLayerSimple) {
}
};
sp<RenderNode> node = TestUtils::createNode(10, 10, 110, 110,
auto node = TestUtils::createNode(10, 10, 110, 110,
[](RenderProperties& props, RecordingCanvas& canvas) {
props.mutateLayerProperties().setType(LayerType::RenderLayer);
SkPaint paint;
@@ -563,7 +579,7 @@ static void drawOrderedRect(RecordingCanvas* canvas, uint8_t expectedDrawOrder)
}
static void drawOrderedNode(RecordingCanvas* canvas, uint8_t expectedDrawOrder, float z) {
auto node = TestUtils::createNode(0, 0, 100, 100,
[expectedDrawOrder](RecordingCanvas& canvas) {
[expectedDrawOrder](RenderProperties& props, RecordingCanvas& canvas) {
drawOrderedRect(&canvas, expectedDrawOrder);
});
node->mutateStagingProperties().setTranslationZ(z);
@@ -580,7 +596,7 @@ TEST(OpReorderer, zReorder) {
};
auto parent = TestUtils::createNode(0, 0, 100, 100,
[](RecordingCanvas& canvas) {
[](RenderProperties& props, RecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, 10.0f); // in reorder=false at this point, so played inorder
drawOrderedRect(&canvas, 1);
canvas.insertReorderBarrier(true);
@@ -601,10 +617,93 @@ TEST(OpReorderer, zReorder) {
EXPECT_EQ(10, renderer.getIndex());
};
TEST(OpReorderer, projectionReorder) {
static const int scrollX = 5;
static const int scrollY = 10;
class ProjectionReorderTestRenderer : public TestRendererBase {
public:
void onRectOp(const RectOp& op, const BakedOpState& state) override {
const int index = mIndex++;
Matrix4 expectedMatrix;
switch (index) {
case 0:
EXPECT_EQ(Rect(100, 100), op.unmappedBounds);
EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
expectedMatrix.loadIdentity();
break;
case 1:
EXPECT_EQ(Rect(-10, -10, 60, 60), op.unmappedBounds);
EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
expectedMatrix.loadTranslate(50, 50, 0); // TODO: should scroll be respected here?
break;
case 2:
EXPECT_EQ(Rect(100, 50), op.unmappedBounds);
EXPECT_EQ(SK_ColorBLUE, op.paint->getColor());
expectedMatrix.loadTranslate(-scrollX, 50 - scrollY, 0);
break;
default:
ADD_FAILURE();
}
EXPECT_MATRIX_APPROX_EQ(expectedMatrix, state.computedState.transform);
}
};
/**
* Construct a tree of nodes, where the root (A) has a receiver background (B), and a child (C)
* with a projecting child (P) of its own. P would normally draw between B and C's "background"
* draw, but because it is projected backwards, it's drawn in between B and C.
*
* The parent is scrolled by scrollX/scrollY, but this does not affect the background
* (which isn't affected by scroll).
*/
auto receiverBackground = TestUtils::createNode(0, 0, 100, 100,
[](RenderProperties& properties, RecordingCanvas& canvas) {
properties.setProjectionReceiver(true);
// scroll doesn't apply to background, so undone via translationX/Y
// NOTE: translationX/Y only! no other transform properties may be set for a proj receiver!
properties.setTranslationX(scrollX);
properties.setTranslationY(scrollY);
SkPaint paint;
paint.setColor(SK_ColorWHITE);
canvas.drawRect(0, 0, 100, 100, paint);
});
auto projectingRipple = TestUtils::createNode(50, 0, 100, 50,
[](RenderProperties& properties, RecordingCanvas& canvas) {
properties.setProjectBackwards(true);
properties.setClipToBounds(false);
SkPaint paint;
paint.setColor(SK_ColorDKGRAY);
canvas.drawRect(-10, -10, 60, 60, paint);
});
auto child = TestUtils::createNode(0, 50, 100, 100,
[&projectingRipple](RenderProperties& properties, RecordingCanvas& canvas) {
SkPaint paint;
paint.setColor(SK_ColorBLUE);
canvas.drawRect(0, 0, 100, 50, paint);
canvas.drawRenderNode(projectingRipple.get());
});
auto parent = TestUtils::createNode(0, 0, 100, 100,
[&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) {
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
canvas.translate(-scrollX, -scrollY); // Apply scroll (note: bg undoes this internally)
canvas.drawRenderNode(receiverBackground.get());
canvas.drawRenderNode(child.get());
canvas.restore();
});
OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
createSyncedNodeList(parent), sLightCenter);
ProjectionReorderTestRenderer renderer;
reorderer.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(3, renderer.getIndex());
}
// creates a 100x100 shadow casting node with provided translationZ
static sp<RenderNode> createWhiteRectShadowCaster(float translationZ) {
return TestUtils::createNode(0, 0, 100, 100,
[translationZ] (RenderProperties& properties, RecordingCanvas& canvas) {
[translationZ](RenderProperties& properties, RecordingCanvas& canvas) {
properties.setTranslationZ(translationZ);
properties.mutableOutline().setRoundRect(0, 0, 100, 100, 0.0f, 1.0f);
SkPaint paint;
@@ -631,8 +730,8 @@ TEST(OpReorderer, shadow) {
}
};
sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200,
[] (RecordingCanvas& canvas) {
auto parent = TestUtils::createNode(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
canvas.insertReorderBarrier(true);
canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
});
@@ -667,8 +766,8 @@ TEST(OpReorderer, shadowSaveLayer) {
}
};
sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200,
[] (RecordingCanvas& canvas) {
auto parent = TestUtils::createNode(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
// save/restore outside of reorderBarrier, so they don't get moved out of place
canvas.translate(20, 10);
int count = canvas.saveLayerAlpha(30, 50, 130, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
@@ -707,7 +806,7 @@ RENDERTHREAD_TEST(OpReorderer, shadowHwLayer) {
}
};
sp<RenderNode> parent = TestUtils::createNode(50, 60, 150, 160,
auto parent = TestUtils::createNode(50, 60, 150, 160,
[](RenderProperties& props, RecordingCanvas& canvas) {
props.mutateLayerProperties().setType(LayerType::RenderLayer);
canvas.insertReorderBarrier(true);
@@ -750,8 +849,8 @@ TEST(OpReorderer, shadowLayering) {
EXPECT_TRUE(index == 2 || index == 3);
}
};
sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200,
[] (RecordingCanvas& canvas) {
auto parent = TestUtils::createNode(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
canvas.insertReorderBarrier(true);
canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
canvas.drawRenderNode(createWhiteRectShadowCaster(5.0001f).get());
@@ -955,7 +1054,7 @@ TEST(OpReorderer, renderPropSaveLayerAlphaRotate) {
SaveLayerAlphaData observedData;
testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) {
// Translate and rotate the view so that the only visible part is the top left corner of
// the view. It will form an isoceles right triangle with a long side length of 200 at the
// the view. It will form an isosceles right triangle with a long side length of 200 at the
// bottom of the viewport.
properties.setTranslationX(100);
properties.setTranslationY(100);

View File

@@ -321,6 +321,49 @@ TEST(RecordingCanvas, saveLayer_rotateClipped) {
EXPECT_EQ(3, count);
}
TEST(RecordingCanvas, drawRenderNode_projection) {
sp<RenderNode> background = TestUtils::createNode(50, 50, 150, 150,
[](RenderProperties& props, RecordingCanvas& canvas) {
SkPaint paint;
paint.setColor(SK_ColorWHITE);
canvas.drawRect(0, 0, 100, 100, paint);
});
{
background->mutateStagingProperties().setProjectionReceiver(false);
// NO RECEIVER PRESENT
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200,
[&background](RecordingCanvas& canvas) {
canvas.drawRect(0, 0, 100, 100, SkPaint());
canvas.drawRenderNode(background.get());
canvas.drawRect(0, 0, 100, 100, SkPaint());
});
EXPECT_EQ(-1, dl->projectionReceiveIndex)
<< "no projection receiver should have been observed";
}
{
background->mutateStagingProperties().setProjectionReceiver(true);
// RECEIVER PRESENT
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200,
[&background](RecordingCanvas& canvas) {
canvas.drawRect(0, 0, 100, 100, SkPaint());
canvas.drawRenderNode(background.get());
canvas.drawRect(0, 0, 100, 100, SkPaint());
});
ASSERT_EQ(3u, dl->getOps().size()) << "Must be three ops";
auto op = dl->getOps()[1];
EXPECT_EQ(RecordedOpId::RenderNodeOp, op->opId);
EXPECT_EQ(1, dl->projectionReceiveIndex)
<< "correct projection receiver not identified";
// verify the behavior works even though projection receiver hasn't been sync'd yet
EXPECT_TRUE(background->stagingProperties().isProjectionReceiver());
EXPECT_FALSE(background->properties().isProjectionReceiver());
}
}
TEST(RecordingCanvas, insertReorderBarrier) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
canvas.drawRect(0, 0, 400, 400, SkPaint());

View File

@@ -121,7 +121,7 @@ public:
}
static sp<RenderNode> createNode(int left, int top, int right, int bottom,
std::function<void(RenderProperties& props, TestCanvas& canvas)> setup = nullptr) {
std::function<void(RenderProperties& props, TestCanvas& canvas)> setup) {
#if HWUI_NULL_GPU
// if RenderNodes are being sync'd/used, device info will be needed, since
// DeviceInfo::maxTextureSize() affects layer property
@@ -140,22 +140,6 @@ public:
return node;
}
static sp<RenderNode> createNode(int left, int top, int right, int bottom,
std::function<void(RenderProperties& props)> setup) {
return createNode(left, top, right, bottom,
[&setup](RenderProperties& props, TestCanvas& canvas) {
setup(props);
});
}
static sp<RenderNode> createNode(int left, int top, int right, int bottom,
std::function<void(TestCanvas& canvas)> setup) {
return createNode(left, top, right, bottom,
[&setup](RenderProperties& props, TestCanvas& canvas) {
setup(canvas);
});
}
static void recordNode(RenderNode& node,
std::function<void(TestCanvas&)> contentCallback) {
TestCanvas canvas(node.stagingProperties().getWidth(),
@@ -164,6 +148,13 @@ public:
node.setStagingDisplayList(canvas.finishRecording());
}
/**
* Forces a sync of a tree of RenderNode, such that every descendant will have its staging
* properties and DisplayList moved to the render copies.
*
* Note: does not check dirtiness bits, so any non-staging DisplayLists will be discarded.
* For this reason, this should generally only be called once on a tree.
*/
static void syncHierarchyPropertiesAndDisplayList(sp<RenderNode>& node) {
syncHierarchyPropertiesAndDisplayListImpl(node.get());
}