Merge "Overdraw avoidance in new pipeline" into nyc-dev
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user