diff options
-rw-r--r-- | libs/hwui/BakedOpState.cpp | 58 | ||||
-rw-r--r-- | libs/hwui/BakedOpState.h | 49 | ||||
-rw-r--r-- | libs/hwui/FrameBuilder.cpp | 8 | ||||
-rw-r--r-- | libs/hwui/FrameBuilder.h | 2 | ||||
-rw-r--r-- | libs/hwui/LayerBuilder.cpp | 34 | ||||
-rw-r--r-- | libs/hwui/LayerBuilder.h | 5 | ||||
-rw-r--r-- | libs/hwui/tests/unit/FrameBuilderTests.cpp | 74 | ||||
-rw-r--r-- | libs/hwui/utils/PaintUtils.h | 15 |
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; |