Partial unclipped save layer support
Not yet implemented in renderer.
Change-Id: I491ec6e7886bfa313d1db71dd5981690d45b78a9
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp
index 097675a..f58ca31 100644
--- a/libs/hwui/BakedOpDispatcher.cpp
+++ b/libs/hwui/BakedOpDispatcher.cpp
@@ -753,5 +753,13 @@
}
}
+void BakedOpDispatcher::onCopyToLayerOp(BakedOpRenderer& renderer, const CopyToLayerOp& op, const BakedOpState& state) {
+ LOG_ALWAYS_FATAL("TODO!");
+}
+
+void BakedOpDispatcher::onCopyFromLayerOp(BakedOpRenderer& renderer, const CopyFromLayerOp& op, const BakedOpState& state) {
+ LOG_ALWAYS_FATAL("TODO!");
+}
+
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index a0d5fae..4aebe3c 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -236,12 +236,13 @@
}
void BakedOpRenderer::prepareRender(const Rect* dirtyBounds, const ClipBase* clip) {
- // prepare scissor / stencil
+ // Prepare scissor (done before stencil, to simplify filling stencil)
mRenderState.scissor().setEnabled(clip != nullptr);
if (clip) {
mRenderState.scissor().set(mRenderTarget.viewportHeight, clip->rect);
}
+ // If stencil may be used for clipping, enable it, fill it, or disable it as appropriate
if (CC_LIKELY(!Properties::debugOverdraw)) {
// only modify stencil mode and content when it's not used for overdraw visualization
if (CC_UNLIKELY(clip && clip->mode != ClipMode::Rectangle)) {
diff --git a/libs/hwui/BakedOpState.cpp b/libs/hwui/BakedOpState.cpp
index e6b943a..b7c9281 100644
--- a/libs/hwui/BakedOpState.cpp
+++ b/libs/hwui/BakedOpState.cpp
@@ -75,5 +75,11 @@
clipSideFlags = OpClipSideFlags::Full;
}
+ResolvedRenderState::ResolvedRenderState(const Rect& dstRect)
+ : transform(Matrix4::identity())
+ , clipState(nullptr)
+ , clippedBounds(dstRect)
+ , clipSideFlags(OpClipSideFlags::None) {}
+
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h
index 9df4e3a..70b0484 100644
--- a/libs/hwui/BakedOpState.h
+++ b/libs/hwui/BakedOpState.h
@@ -58,6 +58,10 @@
// Constructor for unbounded ops without transform/clip (namely shadows)
ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot);
+ // Constructor for primitive ops without clip or transform
+ // NOTE: these ops can't be queried for RT clip / local clip
+ ResolvedRenderState(const Rect& dstRect);
+
Rect computeLocalSpaceClip() const {
Matrix4 inverse;
inverse.loadInverse(transform);
@@ -67,10 +71,12 @@
return outClip;
}
- Matrix4 transform;
+ // NOTE: Can only be used on clipped/snapshot based ops
const Rect& clipRect() const {
return clipState->rect;
}
+
+ // NOTE: Can only be used on clipped/snapshot based ops
bool requiresClip() const {
return clipSideFlags != OpClipSideFlags::None
|| CC_UNLIKELY(clipState->mode != ClipMode::Rectangle);
@@ -80,9 +86,11 @@
const ClipBase* getClipIfNeeded() const {
return requiresClip() ? clipState : nullptr;
}
+
+ Matrix4 transform;
const ClipBase* clipState = nullptr;
- int clipSideFlags = 0;
Rect clippedBounds;
+ int clipSideFlags = 0;
};
/**
@@ -135,12 +143,17 @@
return new (allocator) BakedOpState(allocator, snapshot, shadowOpPtr);
}
+ static BakedOpState* directConstruct(LinearAllocator& allocator,
+ const Rect& dstRect, const RecordedOp& recordedOp) {
+ return new (allocator) BakedOpState(dstRect, recordedOp);
+ }
+
static void* operator new(size_t size, LinearAllocator& allocator) {
return allocator.alloc(size);
}
// computed state:
- const ResolvedRenderState computedState;
+ ResolvedRenderState computedState;
// simple state (straight pointer/value storage):
const float alpha;
@@ -163,6 +176,13 @@
, roundRectClipState(snapshot.roundRectClipState)
, projectionPathMask(snapshot.projectionPathMask)
, op(shadowOpPtr) {}
+
+ BakedOpState(const Rect& dstRect, const RecordedOp& recordedOp)
+ : computedState(dstRect)
+ , alpha(1.0f)
+ , roundRectClipState(nullptr)
+ , projectionPathMask(nullptr)
+ , op(&recordedOp) {}
};
}; // namespace uirenderer
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
diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h
index 429913f..b824d02 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/OpReorderer.h
@@ -53,6 +53,8 @@
Shadow,
TextureLayer,
Functor,
+ CopyToLayer,
+ CopyFromLayer,
Count // must be last
};
@@ -91,6 +93,8 @@
void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers, MergedOpReceiver*) const;
+ void deferLayerClear(const Rect& dstRect);
+
bool empty() const {
return mBatches.empty();
}
@@ -107,7 +111,12 @@
OffscreenBuffer* offscreenBuffer;
const BeginLayerOp* beginLayerOp;
const RenderNode* renderNode;
+
+ // list of deferred CopyFromLayer ops, to be deferred upon encountering EndUnclippedLayerOps
+ std::vector<BakedOpState*> activeUnclippedSaveLayers;
private:
+ void flushLayerClears(LinearAllocator& allocator);
+
std::vector<BatchBase*> mBatches;
/**
@@ -119,6 +128,8 @@
// Maps batch ids to the most recent *non-merging* batch of that id
OpBatch* mBatchLookup[OpBatchType::Count] = { nullptr };
+
+ std::vector<Rect> mClearRects;
};
public:
@@ -147,7 +158,8 @@
*/
#define X(Type) \
[](void* renderer, const BakedOpState& state) { \
- StaticDispatcher::on##Type(*(static_cast<Renderer*>(renderer)), static_cast<const Type&>(*(state.op)), state); \
+ StaticDispatcher::on##Type(*(static_cast<Renderer*>(renderer)), \
+ static_cast<const Type&>(*(state.op)), state); \
},
static BakedOpReceiver unmergedReceivers[] = BUILD_RENDERABLE_OP_LUT(X);
#undef X
@@ -233,8 +245,7 @@
void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers);
SkPath* createFrameAllocatedPath() {
- mFrameAllocatedPaths.emplace_back(new SkPath);
- return mFrameAllocatedPaths.back().get();
+ return mAllocator.create<SkPath>();
}
void deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
@@ -250,8 +261,6 @@
MAP_DEFERRABLE_OPS(X)
#undef X
- std::vector<std::unique_ptr<SkPath> > mFrameAllocatedPaths;
-
// List of every deferred layer's render state. Replayed in reverse order to render a frame.
std::vector<LayerReorderer> mLayerReorderers;
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index b243f99..30d5c29 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -43,12 +43,28 @@
* the functions to which they dispatch. Parameter macros are executed for each op,
* in order, based on the op's type.
*
- * There are 4 types of op:
+ * There are 4 types of op, which defines dispatch/LUT capability:
*
- * Pre render - not directly consumed by renderer, reorder stage resolves this into renderable type
- * Render only - generated renderable ops - never passed to a reorderer
- * Unmergeable - reorderable, renderable (but not mergeable)
- * Mergeable - reorderable, renderable (and mergeable)
+ * | DisplayList | Render | Merge |
+ * -------------|-------------|-------------|-------------|
+ * PRE RENDER | Yes | | |
+ * RENDER ONLY | | Yes | |
+ * UNMERGEABLE | Yes | Yes | |
+ * MERGEABLE | Yes | Yes | Yes |
+ *
+ * PRE RENDER - These ops are recorded into DisplayLists, but can't be directly rendered. This
+ * may be because they need to be transformed into other op types (e.g. CirclePropsOp),
+ * be traversed to access multiple renderable ops within (e.g. RenderNodeOp), or because they
+ * modify renderbuffer lifecycle, instead of directly rendering content (the various LayerOps).
+ *
+ * RENDER ONLY - These ops cannot be recorded into DisplayLists, and are instead implicitly
+ * constructed from other commands/RenderNode properties. They cannot be merged.
+ *
+ * UNMERGEABLE - These ops can be recorded into DisplayLists and rendered directly, but do not
+ * support merged rendering.
+ *
+ * MERGEABLE - These ops can be recorded into DisplayLists and rendered individually, or merged
+ * under certain circumstances.
*/
#define MAP_OPS_BASED_ON_TYPE(PRE_RENDER_OP_FN, RENDER_ONLY_OP_FN, UNMERGEABLE_OP_FN, MERGEABLE_OP_FN) \
PRE_RENDER_OP_FN(RenderNodeOp) \
@@ -56,9 +72,13 @@
PRE_RENDER_OP_FN(RoundRectPropsOp) \
PRE_RENDER_OP_FN(BeginLayerOp) \
PRE_RENDER_OP_FN(EndLayerOp) \
+ PRE_RENDER_OP_FN(BeginUnclippedLayerOp) \
+ PRE_RENDER_OP_FN(EndUnclippedLayerOp) \
\
RENDER_ONLY_OP_FN(ShadowOp) \
RENDER_ONLY_OP_FN(LayerOp) \
+ RENDER_ONLY_OP_FN(CopyToLayerOp) \
+ RENDER_ONLY_OP_FN(CopyFromLayerOp) \
\
UNMERGEABLE_OP_FN(ArcOp) \
UNMERGEABLE_OP_FN(BitmapMeshOp) \
@@ -99,15 +119,15 @@
*/
#define NULL_OP_FN(Type)
+#define MAP_DEFERRABLE_OPS(OP_FN) \
+ MAP_OPS_BASED_ON_TYPE(OP_FN, NULL_OP_FN, OP_FN, OP_FN)
+
#define MAP_MERGEABLE_OPS(OP_FN) \
MAP_OPS_BASED_ON_TYPE(NULL_OP_FN, NULL_OP_FN, NULL_OP_FN, OP_FN)
#define MAP_RENDERABLE_OPS(OP_FN) \
MAP_OPS_BASED_ON_TYPE(NULL_OP_FN, OP_FN, OP_FN, OP_FN)
-#define MAP_DEFERRABLE_OPS(OP_FN) \
- MAP_OPS_BASED_ON_TYPE(OP_FN, NULL_OP_FN, OP_FN, OP_FN)
-
// Generate OpId enum
#define IDENTITY_FN(Type) Type,
namespace RecordedOpId {
@@ -407,6 +427,46 @@
: RecordedOp(RecordedOpId::EndLayerOp, Rect(), Matrix4::identity(), nullptr, nullptr) {}
};
+struct BeginUnclippedLayerOp : RecordedOp {
+ BeginUnclippedLayerOp(BASE_PARAMS)
+ : SUPER(BeginUnclippedLayerOp) {}
+};
+
+struct EndUnclippedLayerOp : RecordedOp {
+ EndUnclippedLayerOp()
+ : RecordedOp(RecordedOpId::EndUnclippedLayerOp, Rect(), Matrix4::identity(), nullptr, nullptr) {}
+};
+
+struct CopyToLayerOp : RecordedOp {
+ CopyToLayerOp(const RecordedOp& op, OffscreenBuffer** layerHandle)
+ : RecordedOp(RecordedOpId::CopyToLayerOp,
+ op.unmappedBounds,
+ op.localMatrix,
+ nullptr, // clip intentionally ignored
+ op.paint)
+ , layerHandle(layerHandle) {}
+
+ // Records a handle to the Layer object, since the Layer itself won't be
+ // constructed until after this operation is constructed.
+ OffscreenBuffer** layerHandle;
+};
+
+
+// draw the parameter layer underneath
+struct CopyFromLayerOp : RecordedOp {
+ CopyFromLayerOp(const RecordedOp& op, OffscreenBuffer** layerHandle)
+ : RecordedOp(RecordedOpId::CopyFromLayerOp,
+ op.unmappedBounds,
+ op.localMatrix,
+ nullptr, // clip intentionally ignored
+ op.paint)
+ , layerHandle(layerHandle) {}
+
+ // Records a handle to the Layer object, since the Layer itself won't be
+ // constructed until after this operation is constructed.
+ OffscreenBuffer** layerHandle;
+};
+
/**
* Draws an OffscreenBuffer.
*
@@ -424,12 +484,12 @@
, destroy(true) {}
LayerOp(RenderNode& node)
- : RecordedOp(RecordedOpId::LayerOp, Rect(node.getWidth(), node.getHeight()), Matrix4::identity(), nullptr, nullptr)
- , layerHandle(node.getLayerHandle())
- , alpha(node.properties().layerProperties().alpha() / 255.0f)
- , mode(node.properties().layerProperties().xferMode())
- , colorFilter(node.properties().layerProperties().colorFilter())
- , destroy(false) {}
+ : RecordedOp(RecordedOpId::LayerOp, Rect(node.getWidth(), node.getHeight()), Matrix4::identity(), nullptr, nullptr)
+ , layerHandle(node.getLayerHandle())
+ , alpha(node.properties().layerProperties().alpha() / 255.0f)
+ , mode(node.properties().layerProperties().xferMode())
+ , colorFilter(node.properties().layerProperties().colorFilter())
+ , destroy(false) {}
// Records a handle to the Layer object, since the Layer itself won't be
// constructed until after this operation is constructed.
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index f7f6caf..78855e5 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -43,10 +43,10 @@
mDeferredBarrierType = DeferredBarrierType::InOrder;
mState.setDirtyClip(false);
- mRestoreSaveCount = -1;
}
DisplayList* RecordingCanvas::finishRecording() {
+ restoreToCount(1);
mPaintMap.clear();
mRegionMap.clear();
mPathMap.clear();
@@ -83,6 +83,8 @@
void RecordingCanvas::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {
if (removed.flags & Snapshot::kFlagIsFboLayer) {
addOp(new (alloc()) EndLayerOp());
+ } else if (removed.flags & Snapshot::kFlagIsLayer) {
+ addOp(new (alloc()) EndUnclippedLayerOp());
}
}
@@ -95,28 +97,18 @@
}
void RecordingCanvas::RecordingCanvas::restore() {
- if (mRestoreSaveCount < 0) {
- restoreToCount(getSaveCount() - 1);
- return;
- }
-
- mRestoreSaveCount--;
mState.restore();
}
void RecordingCanvas::restoreToCount(int saveCount) {
- mRestoreSaveCount = saveCount;
mState.restoreToCount(saveCount);
}
-int RecordingCanvas::saveLayer(float left, float top, float right, float bottom, const SkPaint* paint,
- SkCanvas::SaveFlags flags) {
- if (!(flags & SkCanvas::kClipToLayer_SaveFlag)) {
- LOG_ALWAYS_FATAL("unclipped layers not supported");
- }
+int RecordingCanvas::saveLayer(float left, float top, float right, float bottom,
+ const SkPaint* paint, SkCanvas::SaveFlags flags) {
// force matrix/clip isolation for layer
flags |= SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag;
-
+ bool clippedLayer = flags & SkCanvas::kClipToLayer_SaveFlag;
const Snapshot& previous = *mState.currentSnapshot();
@@ -124,53 +116,70 @@
// 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);
+ const Rect unmappedBounds(left, top, right, bottom);
// determine clipped bounds relative to previous viewport.
- Rect visibleBounds = untransformedBounds;
+ Rect visibleBounds = unmappedBounds;
previous.transform->mapRect(visibleBounds);
+ if (CC_UNLIKELY(!clippedLayer
+ && previous.transform->rectToRect()
+ && visibleBounds.contains(previous.getRenderTargetClip()))) {
+ // unlikely case where an unclipped savelayer is recorded with a clip it can use,
+ // as none of its unaffected/unclipped area is visible
+ clippedLayer = true;
+ flags |= SkCanvas::kClipToLayer_SaveFlag;
+ }
visibleBounds.doIntersect(previous.getRenderTargetClip());
visibleBounds.snapToPixelBoundaries();
-
- Rect previousViewport(0, 0, previous.getViewportWidth(), previous.getViewportHeight());
- visibleBounds.doIntersect(previousViewport);
+ visibleBounds.doIntersect(Rect(previous.getViewportWidth(), previous.getViewportHeight()));
// 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);
+ layerBounds.doIntersect(unmappedBounds);
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()) {
+ // layerBounds is in original bounds space, but clipped by current recording clip
+ if (layerBounds.isEmpty() || unmappedBounds.isEmpty()) {
// Don't bother recording layer, since it's been rejected
- snapshot.resetClip(0, 0, 0, 0);
+ if (CC_LIKELY(clippedLayer)) {
+ snapshot.resetClip(0, 0, 0, 0);
+ }
return saveValue;
}
- auto previousClip = getRecordedClip(); // note: done while snapshot == previous
+ if (CC_LIKELY(clippedLayer)) {
+ auto previousClip = getRecordedClip(); // note: done before new snapshot's clip has changed
- snapshot.flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer;
- snapshot.initializeViewport(untransformedBounds.getWidth(), untransformedBounds.getHeight());
- snapshot.transform->loadTranslate(-untransformedBounds.left, -untransformedBounds.top, 0.0f);
+ snapshot.flags |= Snapshot::kFlagIsLayer | Snapshot::kFlagIsFboLayer;
+ snapshot.initializeViewport(unmappedBounds.getWidth(), unmappedBounds.getHeight());
+ snapshot.transform->loadTranslate(-unmappedBounds.left, -unmappedBounds.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;
+ Rect clip = layerBounds;
+ clip.translate(-unmappedBounds.left, -unmappedBounds.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
- previousClip, // clip to *draw* with
- refPaint(paint)));
+ addOp(new (alloc()) BeginLayerOp(
+ unmappedBounds,
+ *previous.transform, // transform to *draw* with
+ previousClip, // clip to *draw* with
+ refPaint(paint)));
+ } else {
+ snapshot.flags |= Snapshot::kFlagIsLayer;
+
+ addOp(new (alloc()) BeginUnclippedLayerOp(
+ unmappedBounds,
+ *mState.currentSnapshot()->transform,
+ getRecordedClip(),
+ refPaint(paint)));
+ }
return saveValue;
}
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 1a2ac97f..8aa7506 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -314,7 +314,6 @@
DisplayList* mDisplayList = nullptr;
bool mHighContrastText = false;
SkAutoTUnref<SkDrawFilter> mDrawFilter;
- int mRestoreSaveCount = -1;
}; // class RecordingCanvas
}; // namespace uirenderer
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index 5fac3a1..dbaa905 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -116,7 +116,7 @@
* Indicates that this snapshot or an ancestor snapshot is
* an FBO layer.
*/
- kFlagFboTarget = 0x8,
+ kFlagFboTarget = 0x8, // TODO: remove for HWUI_NEW_OPS
};
/**
diff --git a/libs/hwui/tests/unit/OpReordererTests.cpp b/libs/hwui/tests/unit/OpReordererTests.cpp
index 66dccb4..0ed70a0 100644
--- a/libs/hwui/tests/unit/OpReordererTests.cpp
+++ b/libs/hwui/tests/unit/OpReordererTests.cpp
@@ -423,7 +423,7 @@
reorderer.replayBakedOps<TestDispatcher>(renderer);
}
-TEST(OpReorderer, saveLayerSimple) {
+TEST(OpReorderer, saveLayer_simple) {
class SaveLayerSimpleTestRenderer : public TestRendererBase {
public:
OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override {
@@ -466,7 +466,7 @@
EXPECT_EQ(4, renderer.getIndex());
}
-TEST(OpReorderer, saveLayerNested) {
+TEST(OpReorderer, saveLayer_nested) {
/* saveLayer1 { rect1, saveLayer2 { rect2 } } will play back as:
* - startTemporaryLayer2, rect2 endLayer2
* - startTemporaryLayer1, rect1, drawLayer2, endLayer1
@@ -538,7 +538,7 @@
EXPECT_EQ(10, renderer.getIndex());
}
-TEST(OpReorderer, saveLayerContentRejection) {
+TEST(OpReorderer, saveLayer_contentRejection) {
auto node = TestUtils::createNode(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
@@ -559,7 +559,165 @@
reorderer.replayBakedOps<TestDispatcher>(renderer);
}
-RENDERTHREAD_TEST(OpReorderer, hwLayerSimple) {
+TEST(OpReorderer, saveLayerUnclipped_simple) {
+ class SaveLayerUnclippedSimpleTestRenderer : public TestRendererBase {
+ public:
+ void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(0, mIndex++);
+ EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds);
+ EXPECT_EQ(nullptr, state.computedState.clipState);
+ EXPECT_TRUE(state.computedState.transform.isIdentity());
+ }
+ void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(1, mIndex++);
+ ASSERT_NE(nullptr, op.paint);
+ ASSERT_EQ(SkXfermode::kClear_Mode, PaintUtils::getXfermodeDirect(op.paint));
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(2, mIndex++);
+ EXPECT_EQ(Rect(200, 200), op.unmappedBounds);
+ EXPECT_EQ(Rect(200, 200), state.computedState.clippedBounds);
+ EXPECT_EQ(Rect(200, 200), state.computedState.clipRect());
+ EXPECT_TRUE(state.computedState.transform.isIdentity());
+ }
+ void onCopyFromLayerOp(const CopyFromLayerOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(3, mIndex++);
+ EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds);
+ EXPECT_EQ(nullptr, state.computedState.clipState);
+ EXPECT_TRUE(state.computedState.transform.isIdentity());
+ }
+ };
+
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.saveLayerAlpha(10, 10, 190, 190, 128, SkCanvas::kMatrixClip_SaveFlag);
+ canvas.drawRect(0, 0, 200, 200, SkPaint());
+ canvas.restore();
+ });
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ createSyncedNodeList(node), sLightCenter);
+ SaveLayerUnclippedSimpleTestRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(4, renderer.getIndex());
+}
+
+TEST(OpReorderer, saveLayerUnclipped_mergedClears) {
+ class SaveLayerUnclippedMergedClearsTestRenderer : public TestRendererBase {
+ public:
+ void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override {
+ int index = mIndex++;
+ EXPECT_GT(4, index);
+ EXPECT_EQ(5, op.unmappedBounds.getWidth());
+ EXPECT_EQ(5, op.unmappedBounds.getHeight());
+ if (index == 0) {
+ EXPECT_EQ(Rect(10, 10), state.computedState.clippedBounds);
+ } else if (index == 1) {
+ EXPECT_EQ(Rect(190, 0, 200, 10), state.computedState.clippedBounds);
+ } else if (index == 2) {
+ EXPECT_EQ(Rect(0, 190, 10, 200), state.computedState.clippedBounds);
+ } else if (index == 3) {
+ EXPECT_EQ(Rect(190, 190, 200, 200), state.computedState.clippedBounds);
+ }
+ }
+ void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(4, mIndex++);
+ ASSERT_EQ(op.vertexCount, 16u);
+ for (size_t i = 0; i < op.vertexCount; i++) {
+ auto v = op.vertices[i];
+ EXPECT_TRUE(v.x == 0 || v.x == 10 || v.x == 190 || v.x == 200);
+ EXPECT_TRUE(v.y == 0 || v.y == 10 || v.y == 190 || v.y == 200);
+ }
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(5, mIndex++);
+ }
+ void onCopyFromLayerOp(const CopyFromLayerOp& op, const BakedOpState& state) override {
+ EXPECT_LT(5, mIndex++);
+ }
+ };
+
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+
+ int restoreTo = canvas.save(SkCanvas::kMatrixClip_SaveFlag);
+ canvas.scale(2, 2);
+ canvas.saveLayerAlpha(0, 0, 5, 5, 128, SkCanvas::kMatrixClip_SaveFlag);
+ canvas.saveLayerAlpha(95, 0, 100, 5, 128, SkCanvas::kMatrixClip_SaveFlag);
+ canvas.saveLayerAlpha(0, 95, 5, 100, 128, SkCanvas::kMatrixClip_SaveFlag);
+ canvas.saveLayerAlpha(95, 95, 100, 100, 128, SkCanvas::kMatrixClip_SaveFlag);
+ canvas.drawRect(0, 0, 100, 100, SkPaint());
+ canvas.restoreToCount(restoreTo);
+ });
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ createSyncedNodeList(node), sLightCenter);
+ SaveLayerUnclippedMergedClearsTestRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(10, renderer.getIndex())
+ << "Expect 4 copyTos, 4 copyFroms, 1 clear SimpleRects, and 1 rect.";
+}
+
+/* saveLayerUnclipped { saveLayer { saveLayerUnclipped { rect } } } will play back as:
+ * - startTemporaryLayer, onCopyToLayer, onSimpleRects, onRect, onCopyFromLayer, endLayer
+ * - startFrame, onCopyToLayer, onSimpleRects, drawLayer, onCopyFromLayer, endframe
+ */
+TEST(OpReorderer, saveLayerUnclipped_complex) {
+ class SaveLayerUnclippedComplexTestRenderer : public TestRendererBase {
+ public:
+ OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) {
+ EXPECT_EQ(0, mIndex++); // savelayer first
+ return (OffscreenBuffer*)0xabcd;
+ }
+ void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override {
+ int index = mIndex++;
+ EXPECT_TRUE(index == 1 || index == 7);
+ }
+ void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override {
+ int index = mIndex++;
+ EXPECT_TRUE(index == 2 || index == 8);
+ }
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(3, mIndex++);
+ Matrix4 expected;
+ expected.loadTranslate(-100, -100, 0);
+ EXPECT_EQ(Rect(100, 100, 200, 200), state.computedState.clippedBounds);
+ EXPECT_MATRIX_APPROX_EQ(expected, state.computedState.transform);
+ }
+ void onCopyFromLayerOp(const CopyFromLayerOp& op, const BakedOpState& state) override {
+ int index = mIndex++;
+ EXPECT_TRUE(index == 4 || index == 10);
+ }
+ void endLayer() override {
+ EXPECT_EQ(5, mIndex++);
+ }
+ void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override {
+ EXPECT_EQ(6, mIndex++);
+ }
+ void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(9, mIndex++);
+ }
+ void endFrame(const Rect& repaintRect) override {
+ EXPECT_EQ(11, mIndex++);
+ }
+ };
+
+ auto node = TestUtils::createNode(0, 0, 600, 600, // 500x500 triggers clipping
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.saveLayerAlpha(0, 0, 500, 500, 128, (SkCanvas::SaveFlags)0); // unclipped
+ canvas.saveLayerAlpha(100, 100, 400, 400, 128, SkCanvas::kClipToLayer_SaveFlag); // clipped
+ canvas.saveLayerAlpha(200, 200, 300, 300, 128, (SkCanvas::SaveFlags)0); // unclipped
+ canvas.drawRect(200, 200, 300, 300, SkPaint());
+ canvas.restore();
+ canvas.restore();
+ canvas.restore();
+ });
+ OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(600, 600), 600, 600,
+ createSyncedNodeList(node), sLightCenter);
+ SaveLayerUnclippedComplexTestRenderer renderer;
+ reorderer.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(12, renderer.getIndex());
+}
+
+RENDERTHREAD_TEST(OpReorderer, hwLayer_simple) {
class HwLayerSimpleTestRenderer : public TestRendererBase {
public:
void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
@@ -620,7 +778,7 @@
*layerHandle = nullptr;
}
-RENDERTHREAD_TEST(OpReorderer, hwLayerComplex) {
+RENDERTHREAD_TEST(OpReorderer, hwLayer_complex) {
/* parentLayer { greyRect, saveLayer { childLayer { whiteRect } } } will play back as:
* - startRepaintLayer(child), rect(grey), endLayer
* - startTemporaryLayer, drawLayer(child), endLayer
diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
index a63cb18..795ac30 100644
--- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp
+++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
@@ -232,7 +232,7 @@
TEST(RecordingCanvas, saveLayer_simple) {
auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
- canvas.saveLayerAlpha(10, 20, 190, 180, 128, SkCanvas::kARGB_ClipLayer_SaveFlag);
+ canvas.saveLayerAlpha(10, 20, 190, 180, 128, SkCanvas::kClipToLayer_SaveFlag);
canvas.drawRect(10, 20, 190, 180, SkPaint());
canvas.restore();
});
@@ -264,12 +264,78 @@
EXPECT_EQ(3, count);
}
+TEST(RecordingCanvas, saveLayer_missingRestore) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ canvas.saveLayerAlpha(0, 0, 200, 200, 128, SkCanvas::kClipToLayer_SaveFlag);
+ canvas.drawRect(0, 0, 200, 200, SkPaint());
+ // Note: restore omitted, shouldn't result in unmatched save
+ });
+ int count = 0;
+ playbackOps(*dl, [&count](const RecordedOp& op) {
+ if (count++ == 2) {
+ EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId);
+ }
+ });
+ EXPECT_EQ(3, count) << "Missing a restore shouldn't result in an unmatched saveLayer";
+}
+
+TEST(RecordingCanvas, saveLayer_simpleUnclipped) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SkCanvas::SaveFlags)0); // unclipped
+ canvas.drawRect(10, 20, 190, 180, SkPaint());
+ canvas.restore();
+ });
+ int count = 0;
+ playbackOps(*dl, [&count](const RecordedOp& op) {
+ switch(count++) {
+ case 0:
+ EXPECT_EQ(RecordedOpId::BeginUnclippedLayerOp, op.opId);
+ EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
+ EXPECT_EQ(nullptr, op.localClip);
+ EXPECT_TRUE(op.localMatrix.isIdentity());
+ break;
+ case 1:
+ EXPECT_EQ(RecordedOpId::RectOp, op.opId);
+ EXPECT_EQ(nullptr, op.localClip);
+ EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds);
+ EXPECT_TRUE(op.localMatrix.isIdentity());
+ break;
+ case 2:
+ EXPECT_EQ(RecordedOpId::EndUnclippedLayerOp, op.opId);
+ // Don't bother asserting recording state data - it's not used
+ break;
+ default:
+ ADD_FAILURE();
+ }
+ });
+ EXPECT_EQ(3, count);
+}
+
+TEST(RecordingCanvas, saveLayer_addClipFlag) {
+ auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+ canvas.save(SkCanvas::kMatrixClip_SaveFlag);
+ canvas.clipRect(10, 20, 190, 180, SkRegion::kIntersect_Op);
+ canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SkCanvas::SaveFlags)0); // unclipped
+ canvas.drawRect(10, 20, 190, 180, SkPaint());
+ canvas.restore();
+ canvas.restore();
+ });
+ int count = 0;
+ playbackOps(*dl, [&count](const RecordedOp& op) {
+ if (count++ == 0) {
+ EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId)
+ << "Clip + unclipped saveLayer should result in a clipped layer";
+ }
+ });
+ EXPECT_EQ(3, count);
+}
+
TEST(RecordingCanvas, saveLayer_viewportCrop) {
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.saveLayerAlpha(100, 100, 300, 300, 128, SkCanvas::kClipToLayer_SaveFlag);
canvas.drawRect(0, 0, 400, 400, SkPaint());
canvas.restore();
});
@@ -295,7 +361,7 @@
canvas.rotate(45);
canvas.translate(-50, -50);
- canvas.saveLayerAlpha(0, 0, 100, 100, 128, SkCanvas::kARGB_ClipLayer_SaveFlag);
+ canvas.saveLayerAlpha(0, 0, 100, 100, 128, SkCanvas::kClipToLayer_SaveFlag);
canvas.drawRect(0, 0, 100, 100, SkPaint());
canvas.restore();
@@ -322,7 +388,7 @@
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.saveLayerAlpha(0, 0, 400, 400, 128, SkCanvas::kClipToLayer_SaveFlag);
canvas.drawRect(0, 0, 400, 400, SkPaint());
canvas.restore();