friend class RenderNode; // grant RenderNode access to info of child
friend class DisplayList; // grant DisplayList access to info of child
friend class DisplayListCanvas;
+ friend class TestUtils;
public:
DrawRenderNodeOp(RenderNode* renderNode, const mat4& transformFromParent, bool clipIsSimple)
: DrawBoundedOp(0, 0, renderNode->getWidth(), renderNode->getHeight(), nullptr)
#include "OpReorderer.h"
-#include "utils/PaintUtils.h"
-#include "RenderNode.h"
#include "LayerUpdateQueue.h"
+#include "RenderNode.h"
+#include "utils/FatVector.h"
+#include "utils/PaintUtils.h"
-#include "SkCanvas.h"
-#include "utils/Trace.h"
+#include <SkCanvas.h>
+#include <utils/Trace.h>
+#include <utils/TypeHelpers.h>
namespace android {
namespace uirenderer {
}
}
+typedef key_value_pair_t<float, const RenderNodeOp*> ZRenderNodeOpPair;
+
+template <typename V>
+static void buildZSortedChildList(V* zTranslatedNodes,
+ const DisplayList& displayList, const DisplayList::Chunk& chunk) {
+ if (chunk.beginChildIndex == chunk.endChildIndex) return;
+
+ for (size_t i = chunk.beginChildIndex; i < chunk.endChildIndex; i++) {
+ RenderNodeOp* childOp = displayList.getChildren()[i];
+ RenderNode* child = childOp->renderNode;
+ float childZ = child->properties().getZ();
+
+ if (!MathUtils::isZero(childZ) && chunk.reorderChildren) {
+ zTranslatedNodes->push_back(ZRenderNodeOpPair(childZ, childOp));
+ childOp->skipInOrderDraw = true;
+ } else if (!child->properties().getProjectBackwards()) {
+ // regular, in order drawing DisplayList
+ childOp->skipInOrderDraw = false;
+ }
+ }
+
+ // Z sort any 3d children (stable-ness makes z compare fall back to standard drawing order)
+ std::stable_sort(zTranslatedNodes->begin(), zTranslatedNodes->end());
+}
+
+template <typename V>
+static size_t findNonNegativeIndex(const V& zTranslatedNodes) {
+ for (size_t i = 0; i < zTranslatedNodes.size(); i++) {
+ if (zTranslatedNodes[i].key >= 0.0f) return i;
+ }
+ return zTranslatedNodes.size();
+}
+
+template <typename V>
+void OpReorderer::defer3dChildren(ChildrenSelectMode mode, const V& zTranslatedNodes) {
+ const int size = zTranslatedNodes.size();
+ if (size == 0
+ || (mode == ChildrenSelectMode::Negative&& zTranslatedNodes[0].key > 0.0f)
+ || (mode == ChildrenSelectMode::Positive && zTranslatedNodes[size - 1].key < 0.0f)) {
+ // no 3d children to draw
+ return;
+ }
+
+ /**
+ * Draw shadows and (potential) casters mostly in order, but allow the shadows of casters
+ * with very similar Z heights to draw together.
+ *
+ * This way, if Views A & B have the same Z height and are both casting shadows, the shadows are
+ * underneath both, and neither's shadow is drawn on top of the other.
+ */
+ const size_t nonNegativeIndex = findNonNegativeIndex(zTranslatedNodes);
+ size_t drawIndex, shadowIndex, endIndex;
+ if (mode == ChildrenSelectMode::Negative) {
+ drawIndex = 0;
+ endIndex = nonNegativeIndex;
+ shadowIndex = endIndex; // draw no shadows
+ } else {
+ drawIndex = nonNegativeIndex;
+ endIndex = size;
+ shadowIndex = drawIndex; // potentially draw shadow for each pos Z child
+ }
+
+ float lastCasterZ = 0.0f;
+ while (shadowIndex < endIndex || drawIndex < endIndex) {
+ if (shadowIndex < endIndex) {
+ const RenderNodeOp* casterNodeOp = zTranslatedNodes[shadowIndex].value;
+ const float casterZ = zTranslatedNodes[shadowIndex].key;
+ // 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 < 0.1f) {
+ deferShadow(*casterNodeOp);
+
+ lastCasterZ = casterZ; // must do this even if current caster not casting a shadow
+ shadowIndex++;
+ continue;
+ }
+ }
+
+ const RenderNodeOp* childOp = zTranslatedNodes[drawIndex].value;
+ deferRenderNodeOp(*childOp);
+ drawIndex++;
+ }
+}
+
+void OpReorderer::deferShadow(const RenderNodeOp& casterNodeOp) {
+ // TODO
+}
/**
* Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods.
*
MAP_OPS(OP_RECEIVER)
};
for (const DisplayList::Chunk& chunk : displayList.getChunks()) {
+ FatVector<ZRenderNodeOpPair, 16> zTranslatedNodes;
+ buildZSortedChildList(&zTranslatedNodes, displayList, chunk);
+
+ defer3dChildren(ChildrenSelectMode::Negative, zTranslatedNodes);
for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
const RecordedOp* op = displayList.getOps()[opIndex];
receivers[op->opId](*this, *op);
}
+ defer3dChildren(ChildrenSelectMode::Positive, zTranslatedNodes);
}
}
-void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) {
- if (op.renderNode->nothingToDraw()) {
- return;
- }
+void OpReorderer::deferRenderNodeOp(const RenderNodeOp& op) {
+ if (op.renderNode->nothingToDraw()) return;
int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
// apply state from RecordedOp
mCanvasState.restoreToCount(count);
}
+void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) {
+ if (!op.skipInOrderDraw) {
+ deferRenderNodeOp(op);
+ }
+}
+
static batchid_t tessellatedBatchId(const SkPaint& paint) {
return paint.getPathEffect()
? OpBatchType::AlphaMaskTexture
virtual GLuint getTargetFbo() const override { return 0; }
private:
+ enum class ChildrenSelectMode {
+ Negative,
+ Positive
+ };
void saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
const BeginLayerOp* beginLayerOp, RenderNode* renderNode);
void restoreForLayer();
// should always be surrounded by a save/restore pair
void deferNodePropsAndOps(RenderNode& node);
+ void deferShadow(const RenderNodeOp& casterOp);
+
void deferImpl(const DisplayList& displayList);
+ template <typename V>
+ void defer3dChildren(ChildrenSelectMode mode, const V& zTranslatedNodes);
+
+ void deferRenderNodeOp(const RenderNodeOp& op);
+
void replayBakedOpsImpl(void* arg, BakedOpDispatcher* receivers);
/**
: SUPER_PAINTLESS(RenderNodeOp)
, renderNode(renderNode) {}
RenderNode * renderNode; // not const, since drawing modifies it (somehow...)
+ bool skipInOrderDraw = false;
};
struct BitmapOp : RecordedOp {
mState.initializeSaveStack(width, height, 0, 0, width, height, Vector3());
- mDeferredBarrierType = kBarrier_InOrder;
+ mDeferredBarrierType = DeferredBarrierType::InOrder;
mState.setDirtyClip(false);
mRestoreSaveCount = -1;
}
// TODO: validate if "addDrawOp" quickrejection logic is useful before adding
int insertIndex = mDisplayList->ops.size();
mDisplayList->ops.push_back(op);
- if (mDeferredBarrierType != kBarrier_None) {
+ if (mDeferredBarrierType != DeferredBarrierType::None) {
// op is first in new chunk
mDisplayList->chunks.emplace_back();
DisplayList::Chunk& newChunk = mDisplayList->chunks.back();
newChunk.beginOpIndex = insertIndex;
newChunk.endOpIndex = insertIndex + 1;
- newChunk.reorderChildren = (mDeferredBarrierType == kBarrier_OutOfOrder);
+ newChunk.reorderChildren = (mDeferredBarrierType == DeferredBarrierType::OutOfOrder);
int nextChildIndex = mDisplayList->children.size();
newChunk.beginChildIndex = newChunk.endChildIndex = nextChildIndex;
- mDeferredBarrierType = kBarrier_None;
+ mDeferredBarrierType = DeferredBarrierType::None;
} else {
// standard case - append to existing chunk
mDisplayList->chunks.back().endOpIndex = insertIndex + 1;
struct RecordedOp;
class RecordingCanvas: public Canvas, public CanvasStateClient {
+ enum class DeferredBarrierType {
+ None,
+ InOrder,
+ OutOfOrder,
+ };
public:
RecordingCanvas(size_t width, size_t height);
virtual ~RecordingCanvas();
// ----------------------------------------------------------------------------
// MISC HWUI OPERATIONS - TODO: CATEGORIZE
// ----------------------------------------------------------------------------
- void insertReorderBarrier(bool enableReorder) {}
+ void insertReorderBarrier(bool enableReorder) {
+ mDeferredBarrierType = enableReorder
+ ? DeferredBarrierType::OutOfOrder : DeferredBarrierType::InOrder;
+ }
void drawRenderNode(RenderNode* renderNode);
// ----------------------------------------------------------------------------
virtual bool drawTextAbsolutePos() const override { return false; }
private:
- enum DeferredBarrierType {
- kBarrier_None,
- kBarrier_InOrder,
- kBarrier_OutOfOrder,
- };
void drawBitmap(const SkBitmap* bitmap, const SkPaint* paint);
void drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint);
CanvasState mState;
std::unique_ptr<SkiaCanvasProxy> mSkiaCanvasProxy;
ResourceCache& mResourceCache;
- DeferredBarrierType mDeferredBarrierType = kBarrier_None;
+ DeferredBarrierType mDeferredBarrierType = DeferredBarrierType::None;
DisplayList* mDisplayList = nullptr;
bool mHighContrastText = false;
SkAutoTUnref<SkDrawFilter> mDrawFilter;
|| properties().getScaleY() == 0;
}
- // Only call if RenderNode has DisplayList...
const DisplayList* getDisplayList() const {
return mDisplayList;
}
// 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);
- TestUtils::syncNodePropertiesAndDisplayList(node);
+ TestUtils::syncHierarchyPropertiesAndDisplayList(node);
return node;
}
canvas.restore();
});
- TestUtils::syncNodePropertiesAndDisplayList(child);
- TestUtils::syncNodePropertiesAndDisplayList(parent);
+ TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
std::vector< sp<RenderNode> > nodes;
nodes.push_back(parent.get());
SkBitmap bitmap = TestUtils::createSkBitmap(200, 200);
canvas.drawBitmap(bitmap, 0, 0, nullptr);
});
- TestUtils::syncNodePropertiesAndDisplayList(node);
+ TestUtils::syncHierarchyPropertiesAndDisplayList(node);
std::vector< sp<RenderNode> > nodes;
nodes.push_back(node.get());
OffscreenBuffer** bufferHandle = node->getLayerHandle();
*bufferHandle = (OffscreenBuffer*) 0x0124;
- TestUtils::syncNodePropertiesAndDisplayList(node);
+ TestUtils::syncHierarchyPropertiesAndDisplayList(node);
std::vector< sp<RenderNode> > nodes;
nodes.push_back(node.get());
}
};
TEST(OpReorderer, hwLayerComplex) {
- sp<RenderNode> child = TestUtils::createNode<RecordingCanvas>(50, 50, 150, 150, [](RecordingCanvas& canvas) {
+ auto child = TestUtils::createNode<RecordingCanvas>(50, 50, 150, 150,
+ [](RecordingCanvas& canvas) {
SkPaint paint;
paint.setColor(SK_ColorWHITE);
canvas.drawRect(0, 0, 100, 100, paint);
*(child->getLayerHandle()) = (OffscreenBuffer*) 0x4567;
RenderNode* childPtr = child.get();
- sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [childPtr](RecordingCanvas& canvas) {
+ auto parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
+ [childPtr](RecordingCanvas& canvas) {
SkPaint paint;
paint.setColor(SK_ColorDKGRAY);
canvas.drawRect(0, 0, 200, 200, paint);
parent->setPropertyFieldsDirty(RenderNode::GENERIC);
*(parent->getLayerHandle()) = (OffscreenBuffer*) 0x0123;
- TestUtils::syncNodePropertiesAndDisplayList(child);
- TestUtils::syncNodePropertiesAndDisplayList(parent);
+ TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
std::vector< sp<RenderNode> > nodes;
nodes.push_back(parent.get());
}
+class ZReorderTestRenderer : public TestRendererBase {
+public:
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ int expectedOrder = SkColorGetB(op.paint->getColor()); // extract order from blue channel
+ EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
+ }
+};
+static void drawOrderedRect(RecordingCanvas* canvas, uint8_t expectedDrawOrder) {
+ SkPaint paint;
+ paint.setColor(SkColorSetARGB(256, 0, 0, expectedDrawOrder)); // order put in blue channel
+ canvas->drawRect(0, 0, 100, 100, paint);
+}
+static void drawOrderedNode(RecordingCanvas* canvas, uint8_t expectedDrawOrder, float z) {
+ auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+ [expectedDrawOrder](RecordingCanvas& canvas) {
+ drawOrderedRect(&canvas, expectedDrawOrder);
+ });
+ node->mutateStagingProperties().setTranslationZ(z);
+ node->setPropertyFieldsDirty(RenderNode::TRANSLATION_Z);
+ canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
+}
+TEST(OpReorderer, zReorder) {
+ auto parent = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+ [](RecordingCanvas& canvas) {
+ drawOrderedNode(&canvas, 0, 10.0f); // in reorder=false at this point, so played inorder
+ drawOrderedRect(&canvas, 1);
+ canvas.insertReorderBarrier(true);
+ drawOrderedNode(&canvas, 6, 2.0f);
+ drawOrderedRect(&canvas, 3);
+ drawOrderedNode(&canvas, 4, 0.0f);
+ drawOrderedRect(&canvas, 5);
+ drawOrderedNode(&canvas, 2, -2.0f);
+ drawOrderedNode(&canvas, 7, 2.0f);
+ canvas.insertReorderBarrier(false);
+ drawOrderedRect(&canvas, 8);
+ drawOrderedNode(&canvas, 9, -10.0f); // in reorder=false at this point, so played inorder
+ });
+ TestUtils::syncHierarchyPropertiesAndDisplayList(parent);
+
+ std::vector< sp<RenderNode> > nodes;
+ nodes.push_back(parent.get());
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100, nodes);
+
+ ZReorderTestRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(10, renderer.getIndex());
+};
+
+
class PropertyTestRenderer : public TestRendererBase {
public:
PropertyTestRenderer(std::function<void(const RectOp&, const BakedOpState&)> callback)
canvas.drawRect(0, 0, 100, 100, paint);
});
node->setPropertyFieldsDirty(propSetupCallback(node->mutateStagingProperties()));
- TestUtils::syncNodePropertiesAndDisplayList(node);
+ TestUtils::syncHierarchyPropertiesAndDisplayList(node);
std::vector< sp<RenderNode> > nodes;
nodes.push_back(node.get());
<< "Op draw matrix must match expected combination of transformation properties";
});
}
+
} // namespace uirenderer
} // namespace android
static void playbackOps(const DisplayList& displayList,
std::function<void(const RecordedOp&)> opReceiver) {
- for (const DisplayList::Chunk& chunk : displayList.getChunks()) {
+ for (auto& chunk : displayList.getChunks()) {
for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
RecordedOp* op = displayList.getOps()[opIndex];
opReceiver(*op);
EXPECT_EQ(3, count);
}
+TEST(RecordingCanvas, testReorderBarrier) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ canvas.drawRect(0, 0, 400, 400, SkPaint());
+ canvas.insertReorderBarrier(true);
+ canvas.insertReorderBarrier(false);
+ canvas.insertReorderBarrier(false);
+ canvas.insertReorderBarrier(true);
+ canvas.drawRect(0, 0, 400, 400, SkPaint());
+ canvas.insertReorderBarrier(false);
+ });
+
+ auto chunks = dl->getChunks();
+ EXPECT_EQ(0u, chunks[0].beginOpIndex);
+ EXPECT_EQ(1u, chunks[0].endOpIndex);
+ EXPECT_FALSE(chunks[0].reorderChildren);
+
+ EXPECT_EQ(1u, chunks[1].beginOpIndex);
+ EXPECT_EQ(2u, chunks[1].endOpIndex);
+ EXPECT_TRUE(chunks[1].reorderChildren);
+}
+
} // namespace uirenderer
} // namespace android
#define TEST_UTILS_H
#include <DeviceInfo.h>
+#include <DisplayList.h>
#include <Matrix.h>
#include <Rect.h>
#include <RenderNode.h>
#include <renderthread/RenderThread.h>
#include <Snapshot.h>
+#if HWUI_NEW_OPS
+#include <RecordedOp.h>
+#else
+#include <DisplayListOp.h>
+#endif
+
#include <memory>
namespace android {
return node;
}
- static void syncNodePropertiesAndDisplayList(sp<RenderNode>& node) {
- node->syncProperties();
- node->syncDisplayList();
+ static void syncHierarchyPropertiesAndDisplayList(sp<RenderNode>& node) {
+ syncHierarchyPropertiesAndDisplayListImpl(node.get());
}
typedef std::function<void(renderthread::RenderThread& thread)> RtCallback;
TestTask task(rtCallback);
renderthread::RenderThread::getInstance().queueAndWait(&task);
}
+private:
+ static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) {
+ node->syncProperties();
+ node->syncDisplayList();
+ auto displayList = node->getDisplayList();
+ if (displayList) {
+ for (auto&& childOp : displayList->getChildren()) {
+ syncHierarchyPropertiesAndDisplayListImpl(childOp->renderNode);
+ }
+ }
+ }
+
}; // class TestUtils
} /* namespace uirenderer */