Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 80d2ade9 authored by Chris Craik's avatar Chris Craik
Browse files

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
parent ede7d958
Loading
Loading
Loading
Loading
+58 −0
Original line number Diff line number Diff line
@@ -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
+9 −40
Original line number Diff line number Diff line
@@ -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;
+7 −1
Original line number Diff line number Diff line
@@ -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
+1 −1
Original line number Diff line number Diff line
@@ -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);

    /**
+25 −9
Original line number Diff line number Diff line
@@ -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,
Loading