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
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; }
class DeferredLayerUpdater;
class DisplayListOp;
class DrawOp;
+class DrawRenderNodeOp;
class RenderNode;
class StateOp;
: 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);
}
}
/**
* 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
*
* Note: doesn't include transformation within the RenderNode, or its properties.
*/
- mat4 mTransformFromCompositingAncestor;
- bool mSkipInOrderDraw;
+ mat4 transformFromCompositingAncestor;
+ bool skipInOrderDraw;
};
/**
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();
layerDamage, lightCenter, nullptr, layerNode);
if (layerNode->getDisplayList()) {
- deferDisplayList(*(layerNode->getDisplayList()));
+ deferNodeOps(*layerNode);
}
restoreForLayer();
}
// 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);
}
}
-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) {}
Matrix4::identity(),
saveLayerBounds,
&saveLayerPaint));
- deferDisplayList(*(node.getDisplayList()));
+ deferNodeOps(node);
onEndLayerOp(*new (mAllocator) EndLayerOp());
} else {
- deferDisplayList(*(node.getDisplayList()));
+ deferNodeOps(node);
}
}
}
}
}
+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);
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);
}
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() {}
/**
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);
+ template <typename V>
+ void defer3dChildren(ChildrenSelectMode mode, const V& zTranslatedNodes);
+
void deferShadow(const RenderNodeOp& casterOp);
- void deferDisplayList(const DisplayList& displayList);
+ void deferProjectedChildren(const RenderNode& renderNode);
- template <typename V>
- void defer3dChildren(ChildrenSelectMode mode, const V& zTranslatedNodes);
+ void deferNodeOps(const RenderNode& renderNode);
void deferRenderNodeOp(const RenderNodeOp& op);
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;
};
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;
* which are flagged to not draw in the standard draw loop.
*/
void RenderNode::computeOrdering() {
-#if !HWUI_NEW_OPS
ATRACE_CALL();
mProjectedNodes.clear();
// 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
child->computeOrderingImpl(childOp, projectionChildren, projectionTransform);
}
}
-#endif
}
class DeferOperationHandler {
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;
}
}
// 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++;
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++;
// 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);
}
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 {
*/
class RenderNode : public VirtualLightRefBase {
friend class TestUtils; // allow TestUtils to access syncDisplayList / syncProperties
+friend class OpReorderer;
public:
enum DirtyPropertyMask {
GENERIC = 1 << 1,
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>
*/
// 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)
#include "BakedOpState.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>
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();
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);
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 {
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());
canvas.insertReorderBarrier(true);
card = TestUtils::createNode(50, 50, 250, 250,
- [](TestCanvas& canvas) {
+ [](RenderProperties& props, TestCanvas& canvas) {
canvas.drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode);
SkRegion region;
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);
// 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;
}
}
};
- 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);
}
};
- 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.
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())
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);
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())
}
};
- 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);
}
};
- 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);
});
}
};
- 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());
}
};
- 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());
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);
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
}
};
- 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;
}
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);
};
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);
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;
}
};
- 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());
});
}
};
- 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);
}
};
- 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);
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());
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);
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());
}
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
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(),
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());
}