Partial unclipped save layer support
Not yet implemented in renderer.
Change-Id: I491ec6e7886bfa313d1db71dd5981690d45b78a9
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index 3f492d5..34c3d60 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -240,8 +240,50 @@
}
}
+void OpReorderer::LayerReorderer::deferLayerClear(const Rect& rect) {
+ mClearRects.push_back(rect);
+}
+
+void OpReorderer::LayerReorderer::flushLayerClears(LinearAllocator& allocator) {
+ if (CC_UNLIKELY(!mClearRects.empty())) {
+ const int vertCount = mClearRects.size() * 4;
+ // put the verts in the frame allocator, since
+ // 1) SimpleRectsOps needs verts, not rects
+ // 2) even if mClearRects stored verts, std::vectors will move their contents
+ Vertex* const verts = (Vertex*) allocator.alloc(vertCount * sizeof(Vertex));
+
+ Vertex* currentVert = verts;
+ Rect bounds = mClearRects[0];
+ for (auto&& rect : mClearRects) {
+ bounds.unionWith(rect);
+ Vertex::set(currentVert++, rect.left, rect.top);
+ Vertex::set(currentVert++, rect.right, rect.top);
+ Vertex::set(currentVert++, rect.left, rect.bottom);
+ Vertex::set(currentVert++, rect.right, rect.bottom);
+ }
+ mClearRects.clear(); // discard rects before drawing so this method isn't reentrant
+
+ // One or more unclipped saveLayers have been enqueued, with deferred clears.
+ // Flush all of these clears with a single draw
+ SkPaint* paint = allocator.create<SkPaint>();
+ paint->setXfermodeMode(SkXfermode::kClear_Mode);
+ SimpleRectsOp* op = new (allocator) SimpleRectsOp(bounds,
+ Matrix4::identity(), nullptr, paint,
+ verts, vertCount);
+ BakedOpState* bakedState = BakedOpState::directConstruct(allocator, bounds, *op);
+
+
+ deferUnmergeableOp(allocator, bakedState, OpBatchType::Vertices);
+ }
+}
+
void OpReorderer::LayerReorderer::deferUnmergeableOp(LinearAllocator& allocator,
BakedOpState* op, batchid_t batchId) {
+ if (batchId != OpBatchType::CopyToLayer) {
+ // if first op after one or more unclipped saveLayers, flush the layer clears
+ flushLayerClears(allocator);
+ }
+
OpBatch* targetBatch = mBatchLookup[batchId];
size_t insertBatchIndex = mBatches.size();
@@ -260,10 +302,12 @@
}
}
-// 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) {
+ if (batchId != OpBatchType::CopyToLayer) {
+ // if first op after one or more unclipped saveLayers, flush the layer clears
+ flushLayerClears(allocator);
+ }
MergingOpBatch* targetBatch = nullptr;
// Try to merge with any existing batch with same mergeId
@@ -726,6 +770,11 @@
deferStrokeableOp(op, tessBatchId(op));
}
+static bool hasMergeableClip(const BakedOpState& state) {
+ return state.computedState.clipState
+ || state.computedState.clipState->mode == ClipMode::Rectangle;
+}
+
void OpReorderer::deferBitmapOp(const BitmapOp& op) {
BakedOpState* bakedState = tryBakeOpState(op);
if (!bakedState) return; // quick rejected
@@ -736,7 +785,8 @@
if (bakedState->computedState.transform.isSimple()
&& bakedState->computedState.transform.positiveScale()
&& PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode
- && op.bitmap->colorType() != kAlpha_8_SkColorType) {
+ && op.bitmap->colorType() != kAlpha_8_SkColorType
+ && hasMergeableClip(*bakedState)) {
mergeid_t mergeId = (mergeid_t) op.bitmap->getGenerationID();
// TODO: AssetAtlas in mergeId
currentLayer().deferMergeableOp(mAllocator, bakedState, OpBatchType::Bitmap, mergeId);
@@ -792,7 +842,8 @@
if (!bakedState) return; // quick rejected
if (bakedState->computedState.transform.isPureTranslate()
- && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode) {
+ && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode
+ && hasMergeableClip(*bakedState)) {
mergeid_t mergeId = (mergeid_t) op.bitmap->getGenerationID();
// TODO: AssetAtlas in mergeId
@@ -849,7 +900,8 @@
batchid_t batchId = textBatchId(*(op.paint));
if (bakedState->computedState.transform.isPureTranslate()
- && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode) {
+ && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode
+ && hasMergeableClip(*bakedState)) {
mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.paint->getColor());
currentLayer().deferMergeableOp(mAllocator, bakedState, batchId, mergeId);
} else {
@@ -894,7 +946,8 @@
mLayerStack.pop_back();
}
-// TODO: test rejection at defer time, where the bounds become empty
+// TODO: defer time rejection (when bounds become empty) + tests
+// Option - just skip layers with no bounds at playback + defer?
void OpReorderer::deferBeginLayerOp(const BeginLayerOp& op) {
uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth();
uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight();
@@ -904,7 +957,7 @@
// Combine all transforms used to present saveLayer content:
// parent content transform * canvas transform * bounds offset
- Matrix4 contentTransform(*previous->transform);
+ Matrix4 contentTransform(*(previous->transform));
contentTransform.multiply(op.localMatrix);
contentTransform.translate(op.unmappedBounds.left, op.unmappedBounds.top);
@@ -961,10 +1014,53 @@
currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap);
} else {
// Layer won't be drawn - delete its drawing batches to prevent it from doing any work
+ // TODO: need to prevent any render work from being done
+ // - create layerop earlier for reject purposes?
mLayerReorderers[finishedLayerIndex].clear();
return;
}
}
+void OpReorderer::deferBeginUnclippedLayerOp(const BeginUnclippedLayerOp& op) {
+ Matrix4 boundsTransform(*(mCanvasState.currentSnapshot()->transform));
+ boundsTransform.multiply(op.localMatrix);
+
+ Rect dstRect(op.unmappedBounds);
+ boundsTransform.mapRect(dstRect);
+ dstRect.doIntersect(mCanvasState.currentSnapshot()->getRenderTargetClip());
+
+ // Allocate a holding position for the layer object (copyTo will produce, copyFrom will consume)
+ OffscreenBuffer** layerHandle = mAllocator.create<OffscreenBuffer*>();
+
+ /**
+ * First, defer an operation to copy out the content from the rendertarget into a layer.
+ */
+ auto copyToOp = new (mAllocator) CopyToLayerOp(op, layerHandle);
+ BakedOpState* bakedState = BakedOpState::directConstruct(mAllocator, dstRect, *copyToOp);
+ currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::CopyToLayer);
+
+ /**
+ * Defer a clear rect, so that clears from multiple unclipped layers can be drawn
+ * both 1) simultaneously, and 2) as long after the copyToLayer executes as possible
+ */
+ currentLayer().deferLayerClear(dstRect);
+
+ /**
+ * And stash an operation to copy that layer back under the rendertarget until
+ * a balanced EndUnclippedLayerOp is seen
+ */
+ auto copyFromOp = new (mAllocator) CopyFromLayerOp(op, layerHandle);
+ bakedState = BakedOpState::directConstruct(mAllocator, dstRect, *copyFromOp);
+ currentLayer().activeUnclippedSaveLayers.push_back(bakedState);
+}
+
+void OpReorderer::deferEndUnclippedLayerOp(const EndUnclippedLayerOp& op) {
+ LOG_ALWAYS_FATAL_IF(currentLayer().activeUnclippedSaveLayers.empty(), "no layer to end!");
+
+ BakedOpState* copyFromLayerOp = currentLayer().activeUnclippedSaveLayers.back();
+ currentLayer().deferUnmergeableOp(mAllocator, copyFromLayerOp, OpBatchType::CopyFromLayer);
+ currentLayer().activeUnclippedSaveLayers.pop_back();
+}
+
} // namespace uirenderer
} // namespace android