summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Chris Craik <ccraik@google.com> 2016-03-28 12:54:07 -0700
committer Chris Craik <ccraik@google.com> 2016-03-29 13:30:15 -0700
commit80d2ade939153da87b3cd3b0a69a713bf68b64ba (patch)
tree40b554ece6ad2253c4176abc151e3c0d46d8db24
parentede7d958ab54a0701baa8ae8b494e8ea905c61c6 (diff)
Overdraw avoidance in new pipeline
bug:27873093 Adds the simple overdraw avoidance optimization to the new pipeline. This means when LayerBuilder defers draws that are opaque over the full area of the repaint region, it will discard all drawing content beneth. Also moves a lot of complexity out of BakedOpState's header. Change-Id: Iffca6d8e1b170ef31a5d6c83d25592670e02323d
-rw-r--r--libs/hwui/BakedOpState.cpp58
-rw-r--r--libs/hwui/BakedOpState.h49
-rw-r--r--libs/hwui/FrameBuilder.cpp8
-rw-r--r--libs/hwui/FrameBuilder.h2
-rw-r--r--libs/hwui/LayerBuilder.cpp34
-rw-r--r--libs/hwui/LayerBuilder.h5
-rw-r--r--libs/hwui/tests/unit/FrameBuilderTests.cpp74
-rw-r--r--libs/hwui/utils/PaintUtils.h15
8 files changed, 191 insertions, 54 deletions
diff --git a/libs/hwui/BakedOpState.cpp b/libs/hwui/BakedOpState.cpp
index 859036543b4a..b70d5868c718 100644
--- a/libs/hwui/BakedOpState.cpp
+++ b/libs/hwui/BakedOpState.cpp
@@ -108,5 +108,63 @@ ResolvedRenderState::ResolvedRenderState(const ClipRect* clipRect, const Rect& d
clippedBounds.doIntersect(clipRect->rect);
}
+BakedOpState* BakedOpState::tryConstruct(LinearAllocator& allocator,
+ Snapshot& snapshot, const RecordedOp& recordedOp) {
+ if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
+ BakedOpState* bakedState = allocator.create_trivial<BakedOpState>(
+ allocator, snapshot, recordedOp, false);
+ if (bakedState->computedState.clippedBounds.isEmpty()) {
+ // bounds are empty, so op is rejected
+ allocator.rewindIfLastAlloc(bakedState);
+ return nullptr;
+ }
+ return bakedState;
+}
+
+BakedOpState* BakedOpState::tryConstructUnbounded(LinearAllocator& allocator,
+ Snapshot& snapshot, const RecordedOp& recordedOp) {
+ if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
+ return allocator.create_trivial<BakedOpState>(allocator, snapshot, recordedOp);
+}
+
+BakedOpState* BakedOpState::tryStrokeableOpConstruct(LinearAllocator& allocator,
+ Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior) {
+ if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
+ bool expandForStroke = (strokeBehavior == StrokeBehavior::StyleDefined)
+ ? (recordedOp.paint && recordedOp.paint->getStyle() != SkPaint::kFill_Style)
+ : true;
+
+ BakedOpState* bakedState = allocator.create_trivial<BakedOpState>(
+ allocator, snapshot, recordedOp, expandForStroke);
+ if (bakedState->computedState.clippedBounds.isEmpty()) {
+ // bounds are empty, so op is rejected
+ // NOTE: this won't succeed if a clip was allocated
+ allocator.rewindIfLastAlloc(bakedState);
+ return nullptr;
+ }
+ return bakedState;
+}
+
+BakedOpState* BakedOpState::tryShadowOpConstruct(LinearAllocator& allocator,
+ Snapshot& snapshot, const ShadowOp* shadowOpPtr) {
+ if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
+
+ // clip isn't empty, so construct the op
+ return allocator.create_trivial<BakedOpState>(allocator, snapshot, shadowOpPtr);
+}
+
+BakedOpState* BakedOpState::directConstruct(LinearAllocator& allocator,
+ const ClipRect* clip, const Rect& dstRect, const RecordedOp& recordedOp) {
+ return allocator.create_trivial<BakedOpState>(clip, dstRect, recordedOp);
+}
+
+void BakedOpState::setupOpacity(const SkPaint* paint) {
+ computedState.opaqueOverClippedBounds = computedState.transform.isSimple()
+ && computedState.clipState->mode == ClipMode::Rectangle
+ && MathUtils::areEqual(alpha, 1.0f)
+ && !roundRectClipState
+ && PaintUtils::isOpaquePaint(paint);
+}
+
} // namespace uirenderer
} // namespace android
diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h
index 4e3cb8a15e24..e1441fca5ee2 100644
--- a/libs/hwui/BakedOpState.h
+++ b/libs/hwui/BakedOpState.h
@@ -93,6 +93,7 @@ public:
Rect clippedBounds;
int clipSideFlags = 0;
const SkPath* localProjectionPathMask = nullptr;
+ bool opaqueOverClippedBounds = false;
};
/**
@@ -103,23 +104,10 @@ public:
class BakedOpState {
public:
static BakedOpState* tryConstruct(LinearAllocator& allocator,
- Snapshot& snapshot, const RecordedOp& recordedOp) {
- if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
- BakedOpState* bakedState = allocator.create_trivial<BakedOpState>(
- allocator, snapshot, recordedOp, false);
- if (bakedState->computedState.clippedBounds.isEmpty()) {
- // bounds are empty, so op is rejected
- allocator.rewindIfLastAlloc(bakedState);
- return nullptr;
- }
- return bakedState;
- }
+ Snapshot& snapshot, const RecordedOp& recordedOp);
static BakedOpState* tryConstructUnbounded(LinearAllocator& allocator,
- Snapshot& snapshot, const RecordedOp& recordedOp) {
- if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
- return allocator.create_trivial<BakedOpState>(allocator, snapshot, recordedOp);
- }
+ Snapshot& snapshot, const RecordedOp& recordedOp);
enum class StrokeBehavior {
// stroking is forced, regardless of style on paint (such as for lines)
@@ -129,35 +117,16 @@ public:
};
static BakedOpState* tryStrokeableOpConstruct(LinearAllocator& allocator,
- Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior) {
- if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
- bool expandForStroke = (strokeBehavior == StrokeBehavior::StyleDefined)
- ? (recordedOp.paint && recordedOp.paint->getStyle() != SkPaint::kFill_Style)
- : true;
-
- BakedOpState* bakedState = allocator.create_trivial<BakedOpState>(
- allocator, snapshot, recordedOp, expandForStroke);
- if (bakedState->computedState.clippedBounds.isEmpty()) {
- // bounds are empty, so op is rejected
- // NOTE: this won't succeed if a clip was allocated
- allocator.rewindIfLastAlloc(bakedState);
- return nullptr;
- }
- return bakedState;
- }
+ Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior);
static BakedOpState* tryShadowOpConstruct(LinearAllocator& allocator,
- Snapshot& snapshot, const ShadowOp* shadowOpPtr) {
- if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
-
- // clip isn't empty, so construct the op
- return allocator.create_trivial<BakedOpState>(allocator, snapshot, shadowOpPtr);
- }
+ Snapshot& snapshot, const ShadowOp* shadowOpPtr);
static BakedOpState* directConstruct(LinearAllocator& allocator,
- const ClipRect* clip, const Rect& dstRect, const RecordedOp& recordedOp) {
- return allocator.create_trivial<BakedOpState>(clip, dstRect, recordedOp);
- }
+ const ClipRect* clip, const Rect& dstRect, const RecordedOp& recordedOp);
+
+ // Set opaqueOverClippedBounds. If this method isn't called, the op is assumed translucent.
+ void setupOpacity(const SkPaint* paint);
// computed state:
ResolvedRenderState computedState;
diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp
index b1314feebf34..b18836f175ea 100644
--- a/libs/hwui/FrameBuilder.cpp
+++ b/libs/hwui/FrameBuilder.cpp
@@ -481,12 +481,17 @@ void FrameBuilder::deferRenderNodeOp(const RenderNodeOp& op) {
* Defers an unmergeable, strokeable op, accounting correctly
* for paint's style on the bounds being computed.
*/
-const BakedOpState* FrameBuilder::deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
+BakedOpState* FrameBuilder::deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
BakedOpState::StrokeBehavior strokeBehavior) {
// Note: here we account for stroke when baking the op
BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct(
mAllocator, *mCanvasState.writableSnapshot(), op, strokeBehavior);
if (!bakedState) return nullptr; // quick rejected
+
+ if (op.opId == RecordedOpId::RectOp && op.paint->getStyle() != SkPaint::kStroke_Style) {
+ bakedState->setupOpacity(op.paint);
+ }
+
currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId);
return bakedState;
}
@@ -516,6 +521,7 @@ static bool hasMergeableClip(const BakedOpState& state) {
void FrameBuilder::deferBitmapOp(const BitmapOp& op) {
BakedOpState* bakedState = tryBakeOpState(op);
if (!bakedState) return; // quick rejected
+ bakedState->setupOpacity(op.paint);
// Don't merge non-simply transformed or neg scale ops, SET_TEXTURE doesn't handle rotation
// Don't merge A8 bitmaps - the paint's color isn't compared by mergeId, or in
diff --git a/libs/hwui/FrameBuilder.h b/libs/hwui/FrameBuilder.h
index 0b7a6062456a..02c05cb1bbbe 100644
--- a/libs/hwui/FrameBuilder.h
+++ b/libs/hwui/FrameBuilder.h
@@ -201,7 +201,7 @@ private:
return mAllocator.create<SkPath>();
}
- const BakedOpState* deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
+ BakedOpState* deferStrokeableOp(const RecordedOp& op, batchid_t batchId,
BakedOpState::StrokeBehavior strokeBehavior = BakedOpState::StrokeBehavior::StyleDefined);
/**
diff --git a/libs/hwui/LayerBuilder.cpp b/libs/hwui/LayerBuilder.cpp
index e6a95ff177a4..eea11bff7d8a 100644
--- a/libs/hwui/LayerBuilder.cpp
+++ b/libs/hwui/LayerBuilder.cpp
@@ -236,6 +236,21 @@ void LayerBuilder::deferLayerClear(const Rect& rect) {
mClearRects.push_back(rect);
}
+void LayerBuilder::onDeferOp(LinearAllocator& allocator, const BakedOpState* bakedState) {
+ if (bakedState->op->opId != RecordedOpId::CopyToLayerOp) {
+ // First non-CopyToLayer, so stop stashing up layer clears for unclipped save layers,
+ // and issue them together in one draw.
+ flushLayerClears(allocator);
+
+ if (CC_UNLIKELY(activeUnclippedSaveLayers.empty()
+ && bakedState->computedState.opaqueOverClippedBounds
+ && bakedState->computedState.clippedBounds.contains(repaintRect))) {
+ // discard all deferred drawing ops, since new one will occlude them
+ clear();
+ }
+ }
+}
+
void LayerBuilder::flushLayerClears(LinearAllocator& allocator) {
if (CC_UNLIKELY(!mClearRects.empty())) {
const int vertCount = mClearRects.size() * 4;
@@ -270,11 +285,7 @@ void LayerBuilder::flushLayerClears(LinearAllocator& allocator) {
void LayerBuilder::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);
- }
-
+ onDeferOp(allocator, op);
OpBatch* targetBatch = mBatchLookup[batchId];
size_t insertBatchIndex = mBatches.size();
@@ -295,10 +306,7 @@ void LayerBuilder::deferUnmergeableOp(LinearAllocator& allocator,
void LayerBuilder::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);
- }
+ onDeferOp(allocator, op);
MergingOpBatch* targetBatch = nullptr;
// Try to merge with any existing batch with same mergeId
@@ -348,6 +356,14 @@ void LayerBuilder::replayBakedOpsImpl(void* arg,
}
}
+void LayerBuilder::clear() {
+ mBatches.clear();
+ for (int i = 0; i < OpBatchType::Count; i++) {
+ mBatchLookup[i] = nullptr;
+ mMergingBatchLookup[i].clear();
+ }
+}
+
void LayerBuilder::dump() const {
ALOGD("LayerBuilder %p, %ux%u buffer %p, blo %p, rn %p (%s)",
this, width, height, offscreenBuffer, beginLayerOp,
diff --git a/libs/hwui/LayerBuilder.h b/libs/hwui/LayerBuilder.h
index 4a7ca2de9b1b..4de432c5e7be 100644
--- a/libs/hwui/LayerBuilder.h
+++ b/libs/hwui/LayerBuilder.h
@@ -100,9 +100,7 @@ public:
return mBatches.empty();
}
- void clear() {
- mBatches.clear();
- }
+ void clear();
void dump() const;
@@ -117,6 +115,7 @@ public:
// list of deferred CopyFromLayer ops, to be deferred upon encountering EndUnclippedLayerOps
std::vector<BakedOpState*> activeUnclippedSaveLayers;
private:
+ void onDeferOp(LinearAllocator& allocator, const BakedOpState* bakedState);
void flushLayerClears(LinearAllocator& allocator);
std::vector<BatchBase*> mBatches;
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index e97aaa6ac688..ba22f91cde9d 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -216,6 +216,80 @@ RENDERTHREAD_TEST(FrameBuilder, simpleBatching) {
<< "Expect number of ops = 2 * loop count";
}
+RENDERTHREAD_TEST(FrameBuilder, avoidOverdraw_rects) {
+ class AvoidOverdrawRectsTestRenderer : public TestRendererBase {
+ public:
+ void onRectOp(const RectOp& op, const BakedOpState& state) override {
+ EXPECT_EQ(mIndex++, 0) << "Should be one rect";
+ EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds)
+ << "Last rect should occlude others.";
+ }
+ };
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.drawRect(0, 0, 200, 200, SkPaint());
+ canvas.drawRect(0, 0, 200, 200, SkPaint());
+ canvas.drawRect(10, 10, 190, 190, SkPaint());
+ });
+
+ // Damage (and therefore clip) is same as last draw, subset of renderable area.
+ // This means last op occludes other contents, and they'll be rejected to avoid overdraw.
+ SkRect damageRect = SkRect::MakeLTRB(10, 10, 190, 190);
+ FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, damageRect, 200, 200,
+ TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+
+ EXPECT_EQ(3u, node->getDisplayList()->getOps().size())
+ << "Recording must not have rejected ops, in order for this test to be valid";
+
+ AvoidOverdrawRectsTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(1, renderer.getIndex()) << "Expect exactly one op";
+}
+
+RENDERTHREAD_TEST(FrameBuilder, avoidOverdraw_bitmaps) {
+ static SkBitmap opaqueBitmap = TestUtils::createSkBitmap(50, 50,
+ SkColorType::kRGB_565_SkColorType);
+ static SkBitmap transpBitmap = TestUtils::createSkBitmap(50, 50,
+ SkColorType::kAlpha_8_SkColorType);
+ class AvoidOverdrawBitmapsTestRenderer : public TestRendererBase {
+ public:
+ void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
+ EXPECT_LT(mIndex++, 2) << "Should be two bitmaps";
+ switch(mIndex++) {
+ case 0:
+ EXPECT_EQ(opaqueBitmap.pixelRef(), op.bitmap->pixelRef());
+ break;
+ case 1:
+ EXPECT_EQ(transpBitmap.pixelRef(), op.bitmap->pixelRef());
+ break;
+ default:
+ ADD_FAILURE() << "Only two ops expected.";
+ }
+ }
+ };
+
+ auto node = TestUtils::createNode(0, 0, 50, 50,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.drawRect(0, 0, 50, 50, SkPaint());
+ canvas.drawRect(0, 0, 50, 50, SkPaint());
+ canvas.drawBitmap(transpBitmap, 0, 0, nullptr);
+
+ // only the below draws should remain, since they're
+ canvas.drawBitmap(opaqueBitmap, 0, 0, nullptr);
+ canvas.drawBitmap(transpBitmap, 0, 0, nullptr);
+ });
+
+ FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(50, 50), 50, 50,
+ TestUtils::createSyncedNodeList(node), sLightGeometry, Caches::getInstance());
+
+ EXPECT_EQ(5u, node->getDisplayList()->getOps().size())
+ << "Recording must not have rejected ops, in order for this test to be valid";
+
+ AvoidOverdrawBitmapsTestRenderer renderer;
+ frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+ EXPECT_EQ(2, renderer.getIndex()) << "Expect exactly one op";
+}
+
RENDERTHREAD_TEST(FrameBuilder, clippedMerging) {
class ClippedMergingTestRenderer : public TestRendererBase {
public:
diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h
index db537130e12e..4faab9a5f648 100644
--- a/libs/hwui/utils/PaintUtils.h
+++ b/libs/hwui/utils/PaintUtils.h
@@ -67,6 +67,21 @@ public:
&& getXfermode(paint.getXfermode()) == SkXfermode::kSrcOver_Mode;
}
+ static bool isOpaquePaint(const SkPaint* paint) {
+ if (!paint) return true; // default (paintless) behavior is SrcOver, black
+
+ if (paint->getAlpha() != 0xFF
+ || PaintUtils::isBlendedShader(paint->getShader())
+ || PaintUtils::isBlendedColorFilter(paint->getColorFilter())) {
+ return false;
+ }
+
+ // Only let simple srcOver / src blending modes declare opaque, since behavior is clear.
+ SkXfermode::Mode mode = getXfermode(paint->getXfermode());
+ return mode == SkXfermode::Mode::kSrcOver_Mode
+ || mode == SkXfermode::Mode::kSrc_Mode;
+ }
+
static bool isBlendedShader(const SkShader* shader) {
if (shader == nullptr) {
return false;