Merge "Work to support saveLayer in new pipeline"
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index 4d9f9b4..94806ca 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -74,53 +74,64 @@
#endif
}
-void BakedOpRenderer::onRenderNodeOp(Info*, const RenderNodeOp&, const BakedOpState&) {
+void BakedOpRenderer::onRenderNodeOp(Info&, const RenderNodeOp&, const BakedOpState&) {
LOG_ALWAYS_FATAL("unsupported operation");
}
-void BakedOpRenderer::onBitmapOp(Info* info, const BitmapOp& op, const BakedOpState& state) {
- info->caches.textureState().activateTexture(0); // TODO: should this be automatic, and/or elsewhere?
- Texture* texture = info->getTexture(op.bitmap);
+void BakedOpRenderer::onBitmapOp(Info& info, const BitmapOp& op, const BakedOpState& state) {
+ info.caches.textureState().activateTexture(0); // TODO: should this be automatic, and/or elsewhere?
+ Texture* texture = info.getTexture(op.bitmap);
if (!texture) return;
const AutoTexture autoCleanup(texture);
const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType)
? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None;
Glop glop;
- GlopBuilder(info->renderState, info->caches, &glop)
+ GlopBuilder(info.renderState, info.caches, &glop)
.setRoundRectClipState(state.roundRectClipState)
.setMeshTexturedUnitQuad(texture->uvMapper)
.setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
.setTransform(state.computedState.transform, TransformFlags::None)
.setModelViewMapUnitToRectSnap(Rect(0, 0, texture->width, texture->height))
.build();
- info->renderGlop(state, glop);
+ info.renderGlop(state, glop);
}
-void BakedOpRenderer::onRectOp(Info* info, const RectOp& op, const BakedOpState& state) {
+void BakedOpRenderer::onRectOp(Info& info, const RectOp& op, const BakedOpState& state) {
Glop glop;
- GlopBuilder(info->renderState, info->caches, &glop)
+ GlopBuilder(info.renderState, info.caches, &glop)
.setRoundRectClipState(state.roundRectClipState)
.setMeshUnitQuad()
.setFillPaint(*op.paint, state.alpha)
.setTransform(state.computedState.transform, TransformFlags::None)
.setModelViewMapUnitToRect(op.unmappedBounds)
.build();
- info->renderGlop(state, glop);
+ info.renderGlop(state, glop);
}
-void BakedOpRenderer::onSimpleRectsOp(Info* info, const SimpleRectsOp& op, const BakedOpState& state) {
+void BakedOpRenderer::onSimpleRectsOp(Info& info, const SimpleRectsOp& op, const BakedOpState& state) {
Glop glop;
- GlopBuilder(info->renderState, info->caches, &glop)
+ GlopBuilder(info.renderState, info.caches, &glop)
.setRoundRectClipState(state.roundRectClipState)
.setMeshIndexedQuads(&op.vertices[0], op.vertexCount / 4)
.setFillPaint(*op.paint, state.alpha)
.setTransform(state.computedState.transform, TransformFlags::None)
.setModelViewOffsetRect(0, 0, op.unmappedBounds)
.build();
- info->renderGlop(state, glop);
+ info.renderGlop(state, glop);
}
+void BakedOpRenderer::onBeginLayerOp(Info& info, const BeginLayerOp& op, const BakedOpState& state) {
+ LOG_ALWAYS_FATAL("unsupported operation");
+}
+
+void BakedOpRenderer::onEndLayerOp(Info& info, const EndLayerOp& op, const BakedOpState& state) {
+ LOG_ALWAYS_FATAL("unsupported operation");
+}
+
+void BakedOpRenderer::onLayerOp(Info& info, const LayerOp& op, const BakedOpState& state) {
+ LOG_ALWAYS_FATAL("unsupported operation");
+}
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
index b8b4426..f45dbe4 100644
--- a/libs/hwui/BakedOpRenderer.h
+++ b/libs/hwui/BakedOpRenderer.h
@@ -65,7 +65,7 @@
* These functions will perform the actual rendering of the individual operations in OpenGL,
* given the transform/clip and other state built into the BakedOpState object passed in.
*/
- #define BAKED_OP_RENDERER_METHOD(Type) static void on##Type(Info* info, const Type& op, const BakedOpState& state);
+ #define BAKED_OP_RENDERER_METHOD(Type) static void on##Type(Info& info, const Type& op, const BakedOpState& state);
MAP_OPS(BAKED_OP_RENDERER_METHOD);
};
diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h
index e2201ca..ddb8c84 100644
--- a/libs/hwui/BakedOpState.h
+++ b/libs/hwui/BakedOpState.h
@@ -68,7 +68,7 @@
// resolvedClipRect = intersect(parentMatrix * localClip, parentClip)
clipRect = recordedOp.localClipRect;
snapshot.transform->mapRect(clipRect);
- clipRect.doIntersect(snapshot.getClipRect());
+ clipRect.doIntersect(snapshot.getRenderTargetClip());
clipRect.snapToPixelBoundaries();
// resolvedClippedBounds = intersect(resolvedMatrix * opBounds, resolvedClipRect)
diff --git a/libs/hwui/CanvasState.cpp b/libs/hwui/CanvasState.cpp
index eca71c6..6a6cc42 100644
--- a/libs/hwui/CanvasState.cpp
+++ b/libs/hwui/CanvasState.cpp
@@ -259,7 +259,7 @@
currentTransform()->mapRect(r);
r.snapGeometryToPixelBoundaries(snapOut);
- Rect clipRect(currentClipRect());
+ Rect clipRect(currentRenderTargetClip());
clipRect.snapToPixelBoundaries();
if (!clipRect.intersects(r)) return true;
@@ -287,7 +287,7 @@
currentTransform()->mapRect(r);
r.roundOut(); // rounded out to be conservative
- Rect clipRect(currentClipRect());
+ Rect clipRect(currentRenderTargetClip());
clipRect.snapToPixelBoundaries();
if (!clipRect.intersects(r)) return true;
diff --git a/libs/hwui/CanvasState.h b/libs/hwui/CanvasState.h
index be57f44..4709ef4 100644
--- a/libs/hwui/CanvasState.h
+++ b/libs/hwui/CanvasState.h
@@ -147,7 +147,7 @@
void setInvisible(bool value) { mSnapshot->invisible = value; }
inline const mat4* currentTransform() const { return currentSnapshot()->transform; }
- inline const Rect& currentClipRect() const { return currentSnapshot()->getClipRect(); }
+ inline const Rect& currentRenderTargetClip() const { return currentSnapshot()->getRenderTargetClip(); }
inline Region* currentRegion() const { return currentSnapshot()->region; }
inline int currentFlags() const { return currentSnapshot()->flags; }
const Vector3& currentLightCenter() const { return currentSnapshot()->getRelativeLightCenter(); }
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index 7c0e257..c1417c4 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -52,7 +52,8 @@
const std::vector<BakedOpState*>& getOps() const { return mOps; }
void dump() const {
- ALOGD(" Batch %p, merging %d, bounds " RECT_STRING, this, mMerging, RECT_ARGS(mBounds));
+ ALOGD(" Batch %p, id %d, merging %d, count %d, bounds " RECT_STRING,
+ this, mBatchId, mMerging, mOps.size(), RECT_ARGS(mBounds));
}
protected:
batchid_t mBatchId;
@@ -201,17 +202,106 @@
Rect mClipRect;
};
-class NullClient: public CanvasStateClient {
- void onViewportInitialized() override {}
- void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
- GLuint getTargetFbo() const override { return 0; }
-};
-static NullClient sNullClient;
+// iterate back toward target to see if anything drawn since should overlap the new op
+// if no target, merging ops still interate to find similar batch to insert after
+void OpReorderer::LayerReorderer::locateInsertIndex(int batchId, const Rect& clippedBounds,
+ BatchBase** targetBatch, size_t* insertBatchIndex) const {
+ for (int i = mBatches.size() - 1; i >= 0; i--) {
+ BatchBase* overBatch = mBatches[i];
+
+ if (overBatch == *targetBatch) break;
+
+ // TODO: also consider shader shared between batch types
+ if (batchId == overBatch->getBatchId()) {
+ *insertBatchIndex = i + 1;
+ if (!*targetBatch) break; // found insert position, quit
+ }
+
+ if (overBatch->intersects(clippedBounds)) {
+ // NOTE: it may be possible to optimize for special cases where two operations
+ // of the same batch/paint could swap order, such as with a non-mergeable
+ // (clipped) and a mergeable text operation
+ *targetBatch = nullptr;
+ break;
+ }
+ }
+}
+
+void OpReorderer::LayerReorderer::deferUnmergeableOp(LinearAllocator& allocator,
+ BakedOpState* op, batchid_t batchId) {
+ OpBatch* targetBatch = mBatchLookup[batchId];
+
+ size_t insertBatchIndex = mBatches.size();
+ if (targetBatch) {
+ locateInsertIndex(batchId, op->computedState.clippedBounds,
+ (BatchBase**)(&targetBatch), &insertBatchIndex);
+ }
+
+ if (targetBatch) {
+ targetBatch->batchOp(op);
+ } else {
+ // new non-merging batch
+ targetBatch = new (allocator) OpBatch(batchId, op);
+ mBatchLookup[batchId] = targetBatch;
+ mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
+ }
+}
+
+// insertion point of a new batch, will hopefully be immediately after similar batch
+// (generally, should be similar shader)
+void OpReorderer::LayerReorderer::deferMergeableOp(LinearAllocator& allocator,
+ BakedOpState* op, batchid_t batchId, mergeid_t mergeId) {
+ MergingOpBatch* targetBatch = nullptr;
+
+ // Try to merge with any existing batch with same mergeId
+ auto getResult = mMergingBatchLookup[batchId].find(mergeId);
+ if (getResult != mMergingBatchLookup[batchId].end()) {
+ targetBatch = getResult->second;
+ if (!targetBatch->canMergeWith(op)) {
+ targetBatch = nullptr;
+ }
+ }
+
+ size_t insertBatchIndex = mBatches.size();
+ locateInsertIndex(batchId, op->computedState.clippedBounds,
+ (BatchBase**)(&targetBatch), &insertBatchIndex);
+
+ if (targetBatch) {
+ targetBatch->mergeOp(op);
+ } else {
+ // new merging batch
+ targetBatch = new (allocator) MergingOpBatch(batchId, op);
+ mMergingBatchLookup[batchId].insert(std::make_pair(mergeId, targetBatch));
+
+ mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
+ }
+}
+
+void OpReorderer::LayerReorderer::replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) const {
+ for (const BatchBase* batch : mBatches) {
+ // TODO: different behavior based on batch->isMerging()
+ for (const BakedOpState* op : batch->getOps()) {
+ receivers[op->op->opId](arg, *op->op, *op);
+ }
+ }
+}
+
+void OpReorderer::LayerReorderer::dump() const {
+ for (const BatchBase* batch : mBatches) {
+ batch->dump();
+ }
+}
OpReorderer::OpReorderer()
- : mCanvasState(sNullClient) {
+ : mCanvasState(*this) {
+ mLayerReorderers.emplace_back();
+ mLayerStack.push_back(0);
}
+void OpReorderer::onViewportInitialized() {}
+
+void OpReorderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {}
+
void OpReorderer::defer(const SkRect& clip, int viewportWidth, int viewportHeight,
const std::vector< sp<RenderNode> >& nodes) {
mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
@@ -244,11 +334,11 @@
* 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_RECIEVER(Type) \
+#define OP_RECEIVER(Type) \
[](OpReorderer& reorderer, const RecordedOp& op) { reorderer.on##Type(static_cast<const Type&>(op)); },
void OpReorderer::deferImpl(const DisplayList& displayList) {
static std::function<void(OpReorderer& reorderer, const RecordedOp&)> receivers[] = {
- MAP_OPS(OP_RECIEVER)
+ MAP_OPS(OP_RECEIVER)
};
for (const DisplayList::Chunk& chunk : displayList.getChunks()) {
for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
@@ -260,23 +350,18 @@
void OpReorderer::replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) {
ATRACE_NAME("flush drawing commands");
- for (const BatchBase* batch : mBatches) {
- // TODO: different behavior based on batch->isMerging()
- for (const BakedOpState* op : batch->getOps()) {
- receivers[op->op->opId](arg, *op->op, *op);
- }
+ // Relay through layers in reverse order, since layers
+ // later in the list will be drawn by earlier ones
+ for (int i = mLayerReorderers.size() - 1; i >= 0; i--) {
+ mLayerReorderers[i].replayBakedOpsImpl(arg, receivers);
}
}
-BakedOpState* OpReorderer::bakeOpState(const RecordedOp& recordedOp) {
- return BakedOpState::tryConstruct(mAllocator, *mCanvasState.currentSnapshot(), recordedOp);
-}
-
void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) {
if (op.renderNode->nothingToDraw()) {
return;
}
- mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
+ int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
// apply state from RecordedOp
mCanvasState.concatMatrix(op.localMatrix);
@@ -285,10 +370,10 @@
// apply RenderProperties state
if (op.renderNode->applyViewProperties(mCanvasState)) {
- // not rejected do ops...
+ // if node not rejected based on properties, do ops...
deferImpl(op.renderNode->getDisplayList());
}
- mCanvasState.restore();
+ mCanvasState.restoreToCount(count);
}
static batchid_t tessellatedBatchId(const SkPaint& paint) {
@@ -298,104 +383,70 @@
}
void OpReorderer::onBitmapOp(const BitmapOp& op) {
- BakedOpState* bakedStateOp = bakeOpState(op);
+ BakedOpState* bakedStateOp = tryBakeOpState(op);
if (!bakedStateOp) return; // quick rejected
mergeid_t mergeId = (mergeid_t) op.bitmap->getGenerationID();
// TODO: AssetAtlas
-
- deferMergeableOp(bakedStateOp, OpBatchType::Bitmap, mergeId);
+ currentLayer().deferMergeableOp(mAllocator, bakedStateOp, OpBatchType::Bitmap, mergeId);
}
void OpReorderer::onRectOp(const RectOp& op) {
- BakedOpState* bakedStateOp = bakeOpState(op);
+ BakedOpState* bakedStateOp = tryBakeOpState(op);
if (!bakedStateOp) return; // quick rejected
- deferUnmergeableOp(bakedStateOp, tessellatedBatchId(*op.paint));
+ currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, tessellatedBatchId(*op.paint));
}
void OpReorderer::onSimpleRectsOp(const SimpleRectsOp& op) {
- BakedOpState* bakedStateOp = bakeOpState(op);
+ BakedOpState* bakedStateOp = tryBakeOpState(op);
if (!bakedStateOp) return; // quick rejected
- deferUnmergeableOp(bakedStateOp, OpBatchType::Vertices);
+ currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices);
}
-// iterate back toward target to see if anything drawn since should overlap the new op
-// if no target, merging ops still interate to find similar batch to insert after
-void OpReorderer::locateInsertIndex(int batchId, const Rect& clippedBounds,
- BatchBase** targetBatch, size_t* insertBatchIndex) const {
- for (int i = mBatches.size() - 1; i >= mEarliestBatchIndex; i--) {
- BatchBase* overBatch = mBatches[i];
+// TODO: test rejection at defer time, where the bounds become empty
+void OpReorderer::onBeginLayerOp(const BeginLayerOp& op) {
+ mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
+ mCanvasState.writableSnapshot()->transform->loadIdentity();
+ mCanvasState.writableSnapshot()->initializeViewport(
+ (int) op.unmappedBounds.getWidth(), (int) op.unmappedBounds.getHeight());
+ mCanvasState.writableSnapshot()->roundRectClipState = nullptr;
- if (overBatch == *targetBatch) break;
+ // create a new layer, and push its index on the stack
+ mLayerStack.push_back(mLayerReorderers.size());
+ mLayerReorderers.emplace_back();
+ mLayerReorderers.back().beginLayerOp = &op;
+}
- // TODO: also consider shader shared between batch types
- if (batchId == overBatch->getBatchId()) {
- *insertBatchIndex = i + 1;
- if (!*targetBatch) break; // found insert position, quit
- }
+void OpReorderer::onEndLayerOp(const EndLayerOp& /* ignored */) {
+ mCanvasState.restore();
- if (overBatch->intersects(clippedBounds)) {
- // NOTE: it may be possible to optimize for special cases where two operations
- // of the same batch/paint could swap order, such as with a non-mergeable
- // (clipped) and a mergeable text operation
- *targetBatch = nullptr;
- break;
- }
+ const BeginLayerOp& beginLayerOp = *currentLayer().beginLayerOp;
+
+ // pop finished layer off of the stack
+ int finishedLayerIndex = mLayerStack.back();
+ mLayerStack.pop_back();
+
+ // record the draw operation into the previous layer's list of draw commands
+ // uses state from the associated beginLayerOp, since it has all the state needed for drawing
+ LayerOp* drawLayerOp = new (mAllocator) LayerOp(
+ beginLayerOp.unmappedBounds,
+ beginLayerOp.localMatrix,
+ beginLayerOp.localClipRect,
+ beginLayerOp.paint);
+ BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp);
+
+ if (bakedOpState) {
+ // Layer will be drawn into parent layer (which is now current, since we popped mLayerStack)
+ currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap);
+ } else {
+ // Layer won't be drawn - delete its drawing batches to prevent it from doing any work
+ mLayerReorderers[finishedLayerIndex].clear();
+ return;
}
}
-void OpReorderer::deferUnmergeableOp(BakedOpState* op, batchid_t batchId) {
- OpBatch* targetBatch = mBatchLookup[batchId];
-
- size_t insertBatchIndex = mBatches.size();
- if (targetBatch) {
- locateInsertIndex(batchId, op->computedState.clippedBounds,
- (BatchBase**)(&targetBatch), &insertBatchIndex);
- }
-
- if (targetBatch) {
- targetBatch->batchOp(op);
- } else {
- // new non-merging batch
- targetBatch = new (mAllocator) OpBatch(batchId, op);
- mBatchLookup[batchId] = targetBatch;
- mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
- }
-}
-
-// insertion point of a new batch, will hopefully be immediately after similar batch
-// (generally, should be similar shader)
-void OpReorderer::deferMergeableOp(BakedOpState* op, batchid_t batchId, mergeid_t mergeId) {
- MergingOpBatch* targetBatch = nullptr;
-
- // Try to merge with any existing batch with same mergeId
- auto getResult = mMergingBatches[batchId].find(mergeId);
- if (getResult != mMergingBatches[batchId].end()) {
- targetBatch = getResult->second;
- if (!targetBatch->canMergeWith(op)) {
- targetBatch = nullptr;
- }
- }
-
- size_t insertBatchIndex = mBatches.size();
- locateInsertIndex(batchId, op->computedState.clippedBounds,
- (BatchBase**)(&targetBatch), &insertBatchIndex);
-
- if (targetBatch) {
- targetBatch->mergeOp(op);
- } else {
- // new merging batch
- targetBatch = new (mAllocator) MergingOpBatch(batchId, op);
- mMergingBatches[batchId].insert(std::make_pair(mergeId, targetBatch));
-
- mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch);
- }
-}
-
-void OpReorderer::dump() {
- for (const BatchBase* batch : mBatches) {
- batch->dump();
- }
+void OpReorderer::onLayerOp(const LayerOp& op) {
+ LOG_ALWAYS_FATAL("unsupported");
}
} // namespace uirenderer
diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h
index 6776a3c..73dc9af 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/OpReorderer.h
@@ -54,19 +54,63 @@
};
}
-class OpReorderer {
+class OpReorderer : public CanvasStateClient {
+ typedef std::function<void(void*, const RecordedOp&, const BakedOpState&)> BakedOpReceiver;
+
+ /**
+ * Stores the deferred render operations and state used to compute ordering
+ * for a single FBO/layer.
+ */
+ class LayerReorderer {
+ public:
+ // iterate back toward target to see if anything drawn since should overlap the new op
+ // if no target, merging ops still iterate to find similar batch to insert after
+ void locateInsertIndex(int batchId, const Rect& clippedBounds,
+ BatchBase** targetBatch, size_t* insertBatchIndex) const;
+
+ void deferUnmergeableOp(LinearAllocator& allocator, BakedOpState* op, batchid_t batchId);
+
+ // insertion point of a new batch, will hopefully be immediately after similar batch
+ // (generally, should be similar shader)
+ void deferMergeableOp(LinearAllocator& allocator,
+ BakedOpState* op, batchid_t batchId, mergeid_t mergeId);
+
+ void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) const;
+
+ void clear() {
+ mBatches.clear();
+ }
+
+ void dump() const;
+
+ const BeginLayerOp* beginLayerOp = nullptr;
+
+ private:
+ std::vector<BatchBase*> mBatches;
+
+ /**
+ * Maps the mergeid_t returned by an op's getMergeId() to the most recently seen
+ * MergingDrawBatch of that id. These ids are unique per draw type and guaranteed to not
+ * collide, which avoids the need to resolve mergeid collisions.
+ */
+ std::unordered_map<mergeid_t, MergingOpBatch*> mMergingBatchLookup[OpBatchType::Count];
+
+ // Maps batch ids to the most recent *non-merging* batch of that id
+ OpBatch* mBatchLookup[OpBatchType::Count] = { nullptr };
+
+ };
public:
OpReorderer();
+ virtual ~OpReorderer() {}
// TODO: not final, just presented this way for simplicity. Layers too?
void defer(const SkRect& clip, int viewportWidth, int viewportHeight,
const std::vector< sp<RenderNode> >& nodes);
void defer(int viewportWidth, int viewportHeight, const DisplayList& displayList);
- typedef std::function<void(void*, const RecordedOp&, const BakedOpState&)> BakedOpReceiver;
/**
- * replayBakedOps() is templated based on what class will recieve ops being replayed.
+ * replayBakedOps() is templated based on what class will receive ops being replayed.
*
* It constructs a lookup array of lambdas, which allows a recorded BakeOpState to use
* state->op->opId to lookup a receiver that will be called when the op is replayed.
@@ -77,19 +121,37 @@
*/
#define BAKED_OP_RECEIVER(Type) \
[](void* internalArg, const RecordedOp& op, const BakedOpState& state) { \
- StaticReceiver::on##Type(static_cast<Arg*>(internalArg), static_cast<const Type&>(op), state); \
+ StaticReceiver::on##Type(*(static_cast<Arg*>(internalArg)), static_cast<const Type&>(op), state); \
},
template <typename StaticReceiver, typename Arg>
- void replayBakedOps(Arg* arg) {
+ void replayBakedOps(Arg& arg) {
static BakedOpReceiver receivers[] = {
MAP_OPS(BAKED_OP_RECEIVER)
};
- StaticReceiver::startFrame(*arg);
- replayBakedOpsImpl((void*)arg, receivers);
- StaticReceiver::endFrame(*arg);
+ StaticReceiver::startFrame(arg);
+ replayBakedOpsImpl((void*)&arg, receivers);
+ StaticReceiver::endFrame(arg);
}
+
+ void dump() const {
+ for (auto&& layer : mLayerReorderers) {
+ layer.dump();
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////
+ /// CanvasStateClient interface
+ ///////////////////////////////////////////////////////////////////
+ virtual void onViewportInitialized() override;
+ virtual void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) override;
+ virtual GLuint getTargetFbo() const override { return 0; }
+
private:
- BakedOpState* bakeOpState(const RecordedOp& recordedOp);
+ LayerReorderer& currentLayer() { return mLayerReorderers[mLayerStack.back()]; }
+
+ BakedOpState* tryBakeOpState(const RecordedOp& recordedOp) {
+ return BakedOpState::tryConstruct(mAllocator, *mCanvasState.currentSnapshot(), recordedOp);
+ }
void deferImpl(const DisplayList& displayList);
@@ -105,36 +167,27 @@
void on##Type(const Type& op);
MAP_OPS(INTERNAL_OP_HANDLER)
- // iterate back toward target to see if anything drawn since should overlap the new op
- // if no target, merging ops still iterate to find similar batch to insert after
- void locateInsertIndex(int batchId, const Rect& clippedBounds,
- BatchBase** targetBatch, size_t* insertBatchIndex) const;
+ // List of every deferred layer's render state. Replayed in reverse order to render a frame.
+ std::vector<LayerReorderer> mLayerReorderers;
- void deferUnmergeableOp(BakedOpState* op, batchid_t batchId);
+ /*
+ * Stack of indices within mLayerReorderers representing currently active layers. If drawing
+ * layerA within a layerB, will contain, in order:
+ * - 0 (representing FBO 0, always present)
+ * - layerB's index
+ * - layerA's index
+ *
+ * Note that this doesn't vector doesn't always map onto all values of mLayerReorderers. When a
+ * layer is finished deferring, it will still be represented in mLayerReorderers, but it's index
+ * won't be in mLayerStack. This is because it can be replayed, but can't have any more drawing
+ * ops added to it.
+ */
+ std::vector<size_t> mLayerStack;
- // insertion point of a new batch, will hopefully be immediately after similar batch
- // (generally, should be similar shader)
- void deferMergeableOp(BakedOpState* op, batchid_t batchId, mergeid_t mergeId);
-
- void dump();
-
- std::vector<BatchBase*> mBatches;
-
- /**
- * Maps the mergeid_t returned by an op's getMergeId() to the most recently seen
- * MergingDrawBatch of that id. These ids are unique per draw type and guaranteed to not
- * collide, which avoids the need to resolve mergeid collisions.
- */
- std::unordered_map<mergeid_t, MergingOpBatch*> mMergingBatches[OpBatchType::Count];
-
- // Maps batch ids to the most recent *non-merging* batch of that id
- OpBatch* mBatchLookup[OpBatchType::Count] = { nullptr };
CanvasState mCanvasState;
// contains ResolvedOps and Batches
LinearAllocator mAllocator;
-
- int mEarliestBatchIndex = 0;
};
}; // namespace uirenderer
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index cd03ac4..d4f65b6 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -223,7 +223,7 @@
void OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) {
if (mState.currentlyIgnored()) return;
- Rect clip(mState.currentClipRect());
+ Rect clip(mState.currentRenderTargetClip());
clip.snapToPixelBoundaries();
// Since we don't know what the functor will draw, let's dirty
@@ -488,7 +488,7 @@
currentTransform()->mapRect(bounds);
// Layers only make sense if they are in the framebuffer's bounds
- bounds.doIntersect(mState.currentClipRect());
+ bounds.doIntersect(mState.currentRenderTargetClip());
if (!bounds.isEmpty()) {
// We cannot work with sub-pixels in this case
bounds.snapToPixelBoundaries();
@@ -1036,7 +1036,7 @@
}
void OpenGLRenderer::dirtyLayerUnchecked(Rect& bounds, Region* region) {
- bounds.doIntersect(mState.currentClipRect());
+ bounds.doIntersect(mState.currentRenderTargetClip());
if (!bounds.isEmpty()) {
bounds.snapToPixelBoundaries();
android::Rect dirty(bounds.left, bounds.top, bounds.right, bounds.bottom);
@@ -1084,7 +1084,7 @@
.setMeshIndexedQuads(&mesh[0], quadCount)
.setFillClear()
.setTransform(*currentSnapshot(), transformFlags)
- .setModelViewOffsetRect(0, 0, Rect(currentSnapshot()->getClipRect()))
+ .setModelViewOffsetRect(0, 0, Rect(currentSnapshot()->getRenderTargetClip()))
.build();
renderGlop(glop, GlopRenderType::LayerClear);
@@ -1099,7 +1099,7 @@
///////////////////////////////////////////////////////////////////////////////
bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDeferFlags) {
- const Rect& currentClip = mState.currentClipRect();
+ const Rect& currentClip = mState.currentRenderTargetClip();
const mat4* currentMatrix = currentTransform();
if (stateDeferFlags & kStateDeferFlag_Draw) {
@@ -1187,7 +1187,7 @@
///////////////////////////////////////////////////////////////////////////////
void OpenGLRenderer::setScissorFromClip() {
- Rect clip(mState.currentClipRect());
+ Rect clip(mState.currentRenderTargetClip());
clip.snapToPixelBoundaries();
if (mRenderState.scissor().set(clip.left, getViewportHeight() - clip.bottom,
@@ -1430,7 +1430,7 @@
return;
}
- DeferredDisplayList deferredList(mState.currentClipRect());
+ DeferredDisplayList deferredList(mState.currentRenderTargetClip());
DeferStateStruct deferStruct(deferredList, *this, replayFlags);
renderNode->defer(deferStruct, 0);
@@ -1765,7 +1765,7 @@
// No need to check against the clip, we fill the clip region
if (mState.currentlyIgnored()) return;
- Rect clip(mState.currentClipRect());
+ Rect clip(mState.currentRenderTargetClip());
clip.snapToPixelBoundaries();
SkPaint paint;
@@ -2030,7 +2030,7 @@
}
fontRenderer.setTextureFiltering(linearFilter);
- const Rect& clip(pureTranslate ? writableSnapshot()->getClipRect() : writableSnapshot()->getLocalClip());
+ const Rect& clip(pureTranslate ? writableSnapshot()->getRenderTargetClip() : writableSnapshot()->getLocalClip());
Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
TextDrawFunctor functor(this, x, y, pureTranslate, alpha, mode, paint);
@@ -2191,7 +2191,7 @@
fontRenderer.setTextureFiltering(linearFilter);
// TODO: Implement better clipping for scaled/rotated text
- const Rect* clip = !pureTranslate ? nullptr : &mState.currentClipRect();
+ const Rect* clip = !pureTranslate ? nullptr : &mState.currentRenderTargetClip();
Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
bool status;
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index a69f030..dd01637 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -41,7 +41,10 @@
OP_FN(BitmapOp) \
OP_FN(RectOp) \
OP_FN(RenderNodeOp) \
- OP_FN(SimpleRectsOp)
+ OP_FN(SimpleRectsOp) \
+ OP_FN(BeginLayerOp) \
+ OP_FN(EndLayerOp) \
+ OP_FN(LayerOp)
// Generate OpId enum
#define IDENTITY_FN(Type) Type,
@@ -112,6 +115,31 @@
const size_t vertexCount;
};
+/**
+ * Stateful operation! denotes the creation of an off-screen layer,
+ * and that commands following will render into it.
+ */
+struct BeginLayerOp : RecordedOp {
+ BeginLayerOp(BASE_PARAMS)
+ : SUPER(BeginLayerOp) {}
+};
+
+/**
+ * Stateful operation! Denotes end of off-screen layer, and that
+ * commands since last BeginLayerOp should be drawn into parent FBO.
+ *
+ * State in this op is empty, it just serves to signal that a layer has been finished.
+ */
+struct EndLayerOp : RecordedOp {
+ EndLayerOp()
+ : RecordedOp(RecordedOpId::EndLayerOp, Rect(0, 0), Matrix4::identity(), Rect(0, 0), nullptr) {}
+};
+
+struct LayerOp : RecordedOp {
+ LayerOp(BASE_PARAMS)
+ : SUPER(LayerOp) {}
+};
+
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 3b413aa..1f113bc 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -73,6 +73,20 @@
}
// ----------------------------------------------------------------------------
+// CanvasStateClient implementation
+// ----------------------------------------------------------------------------
+
+void RecordingCanvas::onViewportInitialized() {
+
+}
+
+void RecordingCanvas::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {
+ if (removed.flags & Snapshot::kFlagIsFboLayer) {
+ addOp(new (alloc()) EndLayerOp());
+ }
+}
+
+// ----------------------------------------------------------------------------
// android/graphics/Canvas state operations
// ----------------------------------------------------------------------------
// Save (layer)
@@ -97,8 +111,66 @@
int RecordingCanvas::saveLayer(float left, float top, float right, float bottom, const SkPaint* paint,
SkCanvas::SaveFlags flags) {
- LOG_ALWAYS_FATAL("TODO");
- return 0;
+ if (!(flags & SkCanvas::kClipToLayer_SaveFlag)) {
+ LOG_ALWAYS_FATAL("unclipped layers not supported");
+ }
+ // force matrix/clip isolation for layer
+ flags |= SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag;
+
+
+ const Snapshot& previous = *mState.currentSnapshot();
+
+ // initialize the snapshot as though it almost represents an FBO layer so deferred draw
+ // operations will be able to store and restore the current clip and transform info, and
+ // quick rejection will be correct (for display lists)
+
+ const Rect untransformedBounds(left, top, right, bottom);
+
+ // determine clipped bounds relative to previous viewport.
+ Rect visibleBounds = untransformedBounds;
+ previous.transform->mapRect(visibleBounds);
+
+
+ visibleBounds.doIntersect(previous.getRenderTargetClip());
+ visibleBounds.snapToPixelBoundaries();
+
+ Rect previousViewport(0, 0, previous.getViewportWidth(), previous.getViewportHeight());
+ visibleBounds.doIntersect(previousViewport);
+
+ // Map visible bounds back to layer space, and intersect with parameter bounds
+ Rect layerBounds = visibleBounds;
+ Matrix4 inverse;
+ inverse.loadInverse(*previous.transform);
+ inverse.mapRect(layerBounds);
+ layerBounds.doIntersect(untransformedBounds);
+
+ int saveValue = mState.save((int) flags);
+ Snapshot& snapshot = *mState.writableSnapshot();
+
+ // layerBounds is now original bounds, but with clipped to clip
+ // and viewport to ensure it's minimal size.
+ if (layerBounds.isEmpty() || untransformedBounds.isEmpty()) {
+ // Don't bother recording layer, since it's been rejected
+ snapshot.resetClip(0, 0, 0, 0);
+ return saveValue;
+ }
+
+ snapshot.flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer;
+ snapshot.initializeViewport(untransformedBounds.getWidth(), untransformedBounds.getHeight());
+ snapshot.resetTransform(-untransformedBounds.left, -untransformedBounds.top, 0.0f);
+
+ Rect clip = layerBounds;
+ clip.translate(-untransformedBounds.left, -untransformedBounds.top);
+ snapshot.resetClip(clip.left, clip.top, clip.right, clip.bottom);
+ snapshot.roundRectClipState = nullptr;
+
+ addOp(new (alloc()) BeginLayerOp(
+ Rect(left, top, right, bottom),
+ *previous.transform, // transform to *draw* with
+ previous.getRenderTargetClip(), // clip to *draw* with
+ refPaint(paint)));
+
+ return saveValue;
}
// Matrix
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 2179e4c..9c32b1a 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -52,8 +52,8 @@
// ----------------------------------------------------------------------------
// CanvasStateClient interface
// ----------------------------------------------------------------------------
- virtual void onViewportInitialized() override {}
- virtual void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) override {}
+ virtual void onViewportInitialized() override;
+ virtual void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) override;
virtual GLuint getTargetFbo() const override { return -1; }
// ----------------------------------------------------------------------------
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 894a2bd..351fbaa 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -929,7 +929,7 @@
const RenderProperties& backgroundProps = backgroundOp->renderNode->properties();
renderer.translate(backgroundProps.getTranslationX(), backgroundProps.getTranslationY());
- // If the projection reciever has an outline, we mask projected content to it
+ // If the projection receiver has an outline, we mask projected content to it
// (which we know, apriori, are all tessellated paths)
renderer.setProjectionPathMask(alloc, projectionReceiverOutline);
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index f824cc0..abef806 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -203,8 +203,8 @@
return RP_SET(mPrimitiveFields.mProjectBackwards, shouldProject);
}
- bool setProjectionReceiver(bool shouldRecieve) {
- return RP_SET(mPrimitiveFields.mProjectionReceiver, shouldRecieve);
+ bool setProjectionReceiver(bool shouldReceive) {
+ return RP_SET(mPrimitiveFields.mProjectionReceiver, shouldReceive);
}
bool isProjectionReceiver() const {
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index aeeda96..4789b33 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -158,13 +158,12 @@
/**
* Returns the current clip in render target coordinates.
*/
- const Rect& getRenderTargetClip() { return mClipArea->getClipRect(); }
+ const Rect& getRenderTargetClip() const { return mClipArea->getClipRect(); }
/*
* Accessor functions so that the clip area can stay private
*/
bool clipIsEmpty() const { return mClipArea->isEmpty(); }
- const Rect& getClipRect() const { return mClipArea->getClipRect(); }
const SkRegion& getClipRegion() const { return mClipArea->getClipRegion(); }
bool clipIsSimple() const { return mClipArea->isSimple(); }
const ClipArea& getClipArea() const { return *mClipArea; }
diff --git a/libs/hwui/microbench/OpReordererBench.cpp b/libs/hwui/microbench/OpReordererBench.cpp
index 4c8dedf..cf96d44 100644
--- a/libs/hwui/microbench/OpReordererBench.cpp
+++ b/libs/hwui/microbench/OpReordererBench.cpp
@@ -65,7 +65,7 @@
MicroBench::DoNotOptimize(&reorderer);
BakedOpRenderer::Info info(caches, renderState, 200, 200, true);
- reorderer.replayBakedOps<BakedOpRenderer>(&info);
+ reorderer.replayBakedOps<BakedOpRenderer>(info);
}
StopBenchmarkTiming();
});
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 238cf06..f571426 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -323,7 +323,7 @@
BakedOpRenderer::Info info(Caches::getInstance(), mRenderThread.renderState(),
frame.width(), frame.height(), mOpaque);
// TODO: profiler().draw(mCanvas);
- reorderer.replayBakedOps<BakedOpRenderer>(&info);
+ reorderer.replayBakedOps<BakedOpRenderer>(info);
bool drew = info.didDraw;
diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp
index e1249fb..d02f89d 100644
--- a/libs/hwui/unit_tests/OpReordererTests.cpp
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -27,26 +27,69 @@
namespace android {
namespace uirenderer {
-#define UNSUPPORTED_OP(Info, Type) \
- static void on##Type(Info*, const Type&, const BakedOpState&) { FAIL(); }
+/**
+ * Class that redirects static operation dispatch to virtual methods on a Client class.
+ *
+ * The client is recreated for every op (so data cannot be persisted between operations), but the
+ * virtual dispatch allows for default behaviors to be specified without enumerating each operation
+ * for every test.
+ *
+ * onXXXOp methods fail by default - tests should override ops they expect
+ * startFrame/endFrame do nothing by default - tests should override to intercept
+ */
+template<class CustomClient, class Arg>
+class TestReceiver {
+public:
+#define CLIENT_METHOD(Type) \
+ virtual void on##Type(Arg&, const Type&, const BakedOpState&) { FAIL(); }
+ class Client {
+ public:
+ virtual ~Client() {};
+ MAP_OPS(CLIENT_METHOD)
+
+ virtual void startFrame(Arg& info) {}
+ virtual void endFrame(Arg& info) {}
+ };
+
+#define DISPATCHER_METHOD(Type) \
+ static void on##Type(Arg& arg, const Type& op, const BakedOpState& state) { \
+ CustomClient client; client.on##Type(arg, op, state); \
+ }
+ MAP_OPS(DISPATCHER_METHOD)
+
+ static void startFrame(Arg& info) {
+ CustomClient client;
+ client.startFrame(info);
+ }
+
+ static void endFrame(Arg& info) {
+ CustomClient client;
+ client.endFrame(info);
+ }
+};
class Info {
public:
int index = 0;
};
-class SimpleReceiver {
+// Receiver class which will fail if it receives any ops
+class FailReceiver : public TestReceiver<FailReceiver, Info>::Client {};
+
+class SimpleReceiver : public TestReceiver<SimpleReceiver, Info>::Client {
public:
- static void onBitmapOp(Info* info, const BitmapOp& op, const BakedOpState& state) {
- EXPECT_EQ(1, info->index++);
+ void startFrame(Info& info) override {
+ EXPECT_EQ(0, info.index++);
}
- static void onRectOp(Info* info, const RectOp& op, const BakedOpState& state) {
- EXPECT_EQ(0, info->index++);
+ void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, info.index++);
}
- UNSUPPORTED_OP(Info, RenderNodeOp)
- UNSUPPORTED_OP(Info, SimpleRectsOp)
- static void startFrame(Info& info) {}
- static void endFrame(Info& info) {}
+ void onBitmapOp(Info& info, const BitmapOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(2, info.index++);
+ }
+ void endFrame(Info& info) override {
+ EXPECT_EQ(3, info.index++);
+ }
};
TEST(OpReorderer, simple) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
@@ -54,28 +97,39 @@
canvas.drawRect(0, 0, 100, 200, SkPaint());
canvas.drawBitmap(bitmap, 10, 10, nullptr);
});
-
OpReorderer reorderer;
reorderer.defer(200, 200, *dl);
Info info;
- reorderer.replayBakedOps<SimpleReceiver>(&info);
+ reorderer.replayBakedOps<TestReceiver<SimpleReceiver, Info>>(info);
+ EXPECT_EQ(4, info.index); // 2 ops + start + end
+}
+
+
+TEST(OpReorderer, simpleRejection) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](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;
+ reorderer.defer(200, 200, *dl);
+
+ Info info;
+ reorderer.replayBakedOps<TestReceiver<FailReceiver, Info>>(info);
}
static int SIMPLE_BATCHING_LOOPS = 5;
-class SimpleBatchingReceiver {
+class SimpleBatchingReceiver : public TestReceiver<SimpleBatchingReceiver, Info>::Client {
public:
- static void onBitmapOp(Info* info, const BitmapOp& op, const BakedOpState& state) {
- EXPECT_TRUE(info->index++ >= SIMPLE_BATCHING_LOOPS);
+ void onBitmapOp(Info& info, const BitmapOp& op, const BakedOpState& state) override {
+ EXPECT_TRUE(info.index++ >= SIMPLE_BATCHING_LOOPS);
}
- static void onRectOp(Info* info, const RectOp& op, const BakedOpState& state) {
- EXPECT_TRUE(info->index++ < SIMPLE_BATCHING_LOOPS);
+ void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
+ EXPECT_TRUE(info.index++ < SIMPLE_BATCHING_LOOPS);
}
- UNSUPPORTED_OP(Info, RenderNodeOp)
- UNSUPPORTED_OP(Info, SimpleRectsOp)
- static void startFrame(Info& info) {}
- static void endFrame(Info& info) {}
};
TEST(OpReorderer, simpleBatching) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
@@ -96,15 +150,14 @@
reorderer.defer(200, 200, *dl);
Info info;
- reorderer.replayBakedOps<SimpleBatchingReceiver>(&info);
+ reorderer.replayBakedOps<TestReceiver<SimpleBatchingReceiver, Info>>(info);
EXPECT_EQ(2 * SIMPLE_BATCHING_LOOPS, info.index); // 2 x loops ops, because no merging (TODO: force no merging)
}
-class RenderNodeReceiver {
+class RenderNodeReceiver : public TestReceiver<RenderNodeReceiver, Info>::Client {
public:
- UNSUPPORTED_OP(Info, BitmapOp)
- static void onRectOp(Info* info, const RectOp& op, const BakedOpState& state) {
- switch(info->index++) {
+ void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
+ switch(info.index++) {
case 0:
EXPECT_EQ(Rect(0, 0, 200, 200), state.computedState.clippedBounds);
EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
@@ -117,10 +170,6 @@
FAIL();
}
}
- UNSUPPORTED_OP(Info, RenderNodeOp)
- UNSUPPORTED_OP(Info, SimpleRectsOp)
- static void startFrame(Info& info) {}
- static void endFrame(Info& info) {}
};
TEST(OpReorderer, renderNode) {
sp<RenderNode> child = TestUtils::createNode<RecordingCanvas>(10, 10, 110, 110, [](RecordingCanvas& canvas) {
@@ -151,22 +200,17 @@
reorderer.defer(SkRect::MakeWH(200, 200), 200, 200, nodes);
Info info;
- reorderer.replayBakedOps<RenderNodeReceiver>(&info);
+ reorderer.replayBakedOps<TestReceiver<RenderNodeReceiver, Info>>(info);
}
-class ClippedReceiver {
+class ClippedReceiver : public TestReceiver<ClippedReceiver, Info>::Client {
public:
- static void onBitmapOp(Info* info, const BitmapOp& op, const BakedOpState& state) {
- EXPECT_EQ(0, info->index++);
+ void onBitmapOp(Info& info, const BitmapOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(0, info.index++);
EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clippedBounds);
EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clipRect);
EXPECT_TRUE(state.computedState.transform.isIdentity());
}
- UNSUPPORTED_OP(Info, RectOp)
- UNSUPPORTED_OP(Info, RenderNodeOp)
- UNSUPPORTED_OP(Info, SimpleRectsOp)
- static void startFrame(Info& info) {}
- static void endFrame(Info& info) {}
};
TEST(OpReorderer, clipped) {
sp<RenderNode> node = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [](RecordingCanvas& canvas) {
@@ -182,8 +226,106 @@
200, 200, nodes);
Info info;
- reorderer.replayBakedOps<ClippedReceiver>(&info);
+ reorderer.replayBakedOps<TestReceiver<ClippedReceiver, Info>>(info);
}
+
+class SaveLayerSimpleReceiver : public TestReceiver<SaveLayerSimpleReceiver, Info>::Client {
+public:
+ void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(0, info.index++);
+ EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds);
+ EXPECT_EQ(Rect(0, 0, 180, 180), state.computedState.clippedBounds);
+ EXPECT_EQ(Rect(0, 0, 180, 180), state.computedState.clipRect);
+
+ Matrix4 expectedTransform;
+ expectedTransform.loadTranslate(-10, -10, 0);
+ EXPECT_MATRIX_APPROX_EQ(expectedTransform, state.computedState.transform);
+ }
+ void onLayerOp(Info& info, const LayerOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, info.index++);
+ EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds);
+ EXPECT_EQ(Rect(0, 0, 200, 200), state.computedState.clipRect);
+ EXPECT_TRUE(state.computedState.transform.isIdentity());
+ }
+};
+TEST(OpReorderer, saveLayerSimple) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ canvas.saveLayerAlpha(10, 10, 190, 190, 128, SkCanvas::kClipToLayer_SaveFlag);
+ canvas.drawRect(10, 10, 190, 190, SkPaint());
+ canvas.restore();
+ });
+
+ OpReorderer reorderer;
+ reorderer.defer(200, 200, *dl);
+
+ Info info;
+ reorderer.replayBakedOps<TestReceiver<SaveLayerSimpleReceiver, Info>>(info);
+ EXPECT_EQ(2, info.index);
}
+
+
+// saveLayer1 {rect1, saveLayer2 { rect2 } } will play back as rect2, rect1, layerOp2, layerOp1
+class SaveLayerNestedReceiver : public TestReceiver<SaveLayerNestedReceiver, Info>::Client {
+public:
+ void onRectOp(Info& info, const RectOp& op, const BakedOpState& state) override {
+ const int index = info.index++;
+ if (index == 0) {
+ EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner rect
+ } else if (index == 1) {
+ EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer rect
+ } else { FAIL(); }
+ }
+ void onLayerOp(Info& info, const LayerOp& op, const BakedOpState& state) override {
+ const int index = info.index++;
+ if (index == 2) {
+ EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds); // inner layer
+ } else if (index == 3) {
+ EXPECT_EQ(Rect(0, 0, 800, 800), op.unmappedBounds); // outer layer
+ } else { FAIL(); }
+ }
+};
+TEST(OpReorderer, saveLayerNested) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(800, 800, [](RecordingCanvas& canvas) {
+ canvas.saveLayerAlpha(0, 0, 800, 800, 128, SkCanvas::kClipToLayer_SaveFlag);
+ {
+ canvas.drawRect(0, 0, 800, 800, SkPaint());
+ canvas.saveLayerAlpha(0, 0, 400, 400, 128, SkCanvas::kClipToLayer_SaveFlag);
+ {
+ canvas.drawRect(0, 0, 400, 400, SkPaint());
+ }
+ canvas.restore();
+ }
+ canvas.restore();
+ });
+
+ OpReorderer reorderer;
+ reorderer.defer(800, 800, *dl);
+
+ Info info;
+ reorderer.replayBakedOps<TestReceiver<SaveLayerNestedReceiver, Info>>(info);
+ EXPECT_EQ(4, info.index);
}
+
+TEST(OpReorderer, saveLayerContentRejection) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](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);
+
+ // draw within save layer may still be recorded, but shouldn't be drawn
+ canvas.drawRect(200, 200, 400, 400, SkPaint());
+
+ canvas.restore();
+ canvas.restore();
+ });
+ OpReorderer reorderer;
+ reorderer.defer(200, 200, *dl);
+ Info info;
+
+ // should see no ops, even within the layer, since the layer should be rejected
+ reorderer.replayBakedOps<TestReceiver<FailReceiver, Info>>(info);
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/unit_tests/RecordingCanvasTests.cpp b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
index ce25fc6..c023123 100644
--- a/libs/hwui/unit_tests/RecordingCanvasTests.cpp
+++ b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
@@ -24,11 +24,11 @@
namespace uirenderer {
static void playbackOps(const DisplayList& displayList,
- std::function<void(const RecordedOp&)> opReciever) {
+ std::function<void(const RecordedOp&)> opReceiver) {
for (const DisplayList::Chunk& chunk : displayList.getChunks()) {
for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) {
RecordedOp* op = displayList.getOps()[opIndex];
- opReciever(*op);
+ opReceiver(*op);
}
}
}
@@ -109,5 +109,123 @@
ASSERT_EQ(2, count); // two draws observed
}
+TEST(RecordingCanvas, saveLayerSimple) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ canvas.saveLayerAlpha(10, 20, 190, 180, 128, SkCanvas::kARGB_ClipLayer_SaveFlag);
+ canvas.drawRect(10, 20, 190, 180, SkPaint());
+ canvas.restore();
+ });
+ int count = 0;
+ playbackOps(*dl, [&count](const RecordedOp& op) {
+ Matrix4 expectedMatrix;
+ switch(count++) {
+ case 0:
+ EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId);
+ // TODO: add asserts
+ break;
+ case 1:
+ EXPECT_EQ(RecordedOpId::RectOp, op.opId);
+ EXPECT_EQ(Rect(0, 0, 180, 160), op.localClipRect);
+ EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
+ expectedMatrix.loadTranslate(-10, -20, 0);
+ EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
+ break;
+ case 2:
+ EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId);
+ // TODO: add asserts
+ break;
+ default:
+ FAIL();
+ }
+ });
+ EXPECT_EQ(3, count);
}
+
+TEST(RecordingCanvas, saveLayerViewportCrop) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ // shouldn't matter, since saveLayer will clip to its bounds
+ canvas.clipRect(-1000, -1000, 1000, 1000, SkRegion::kReplace_Op);
+
+ canvas.saveLayerAlpha(100, 100, 300, 300, 128, SkCanvas::kARGB_ClipLayer_SaveFlag);
+ canvas.drawRect(0, 0, 400, 400, SkPaint());
+ canvas.restore();
+ });
+ int count = 0;
+ playbackOps(*dl, [&count](const RecordedOp& op) {
+ if (count++ == 1) {
+ Matrix4 expectedMatrix;
+ EXPECT_EQ(RecordedOpId::RectOp, op.opId);
+
+ // recorded clip rect should be intersection of
+ // viewport and saveLayer bounds, in layer space
+ EXPECT_EQ(Rect(0, 0, 100, 100), op.localClipRect);
+ EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds);
+ expectedMatrix.loadTranslate(-100, -100, 0);
+ EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
+ }
+ });
+ EXPECT_EQ(3, count);
}
+
+TEST(RecordingCanvas, saveLayerRotateUnclipped) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ canvas.translate(100, 100);
+ canvas.rotate(45);
+ canvas.translate(-50, -50);
+
+ canvas.saveLayerAlpha(0, 0, 100, 100, 128, SkCanvas::kARGB_ClipLayer_SaveFlag);
+ canvas.drawRect(0, 0, 100, 100, SkPaint());
+ canvas.restore();
+
+ canvas.restore();
+ });
+ int count = 0;
+ playbackOps(*dl, [&count](const RecordedOp& op) {
+ if (count++ == 1) {
+ Matrix4 expectedMatrix;
+ EXPECT_EQ(RecordedOpId::RectOp, op.opId);
+
+ // recorded rect doesn't see rotate, since recorded relative to saveLayer bounds
+ EXPECT_EQ(Rect(0, 0, 100, 100), op.localClipRect);
+ EXPECT_EQ(Rect(0, 0, 100, 100), op.unmappedBounds);
+ expectedMatrix.loadIdentity();
+ EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
+ }
+ });
+ EXPECT_EQ(3, count);
+}
+
+TEST(RecordingCanvas, saveLayerRotateClipped) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ canvas.translate(100, 100);
+ canvas.rotate(45);
+ canvas.translate(-200, -200);
+
+ // area of saveLayer will be clipped to parent viewport, so we ask for 400x400...
+ canvas.saveLayerAlpha(0, 0, 400, 400, 128, SkCanvas::kARGB_ClipLayer_SaveFlag);
+ canvas.drawRect(0, 0, 400, 400, SkPaint());
+ canvas.restore();
+
+ canvas.restore();
+ });
+ int count = 0;
+ playbackOps(*dl, [&count](const RecordedOp& op) {
+ if (count++ == 1) {
+ Matrix4 expectedMatrix;
+ EXPECT_EQ(RecordedOpId::RectOp, op.opId);
+
+ // ...and get about 58.6, 58.6, 341.4 341.4, because the bounds are clipped by
+ // the parent 200x200 viewport, but prior to rotation
+ EXPECT_RECT_APPROX_EQ(Rect(58.57864, 58.57864, 341.42136, 341.42136), op.localClipRect);
+ EXPECT_EQ(Rect(0, 0, 400, 400), op.unmappedBounds);
+ expectedMatrix.loadIdentity();
+ EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix);
+ }
+ });
+ EXPECT_EQ(3, count);
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/unit_tests/TestUtils.h b/libs/hwui/unit_tests/TestUtils.h
index 80d83a2..99ecc9b 100644
--- a/libs/hwui/unit_tests/TestUtils.h
+++ b/libs/hwui/unit_tests/TestUtils.h
@@ -31,6 +31,12 @@
#define EXPECT_MATRIX_APPROX_EQ(a, b) \
EXPECT_TRUE(TestUtils::matricesAreApproxEqual(a, b))
+#define EXPECT_RECT_APPROX_EQ(a, b) \
+ EXPECT_TRUE(MathUtils::areEqual(a.left, b.left) \
+ && MathUtils::areEqual(a.top, b.top) \
+ && MathUtils::areEqual(a.right, b.right) \
+ && MathUtils::areEqual(a.bottom, b.bottom));
+
class TestUtils {
public:
static bool matricesAreApproxEqual(const Matrix4& a, const Matrix4& b) {