Merge "Overdraw avoidance in new pipeline" into nyc-dev

This commit is contained in:
Chris Craik
2016-03-29 21:41:50 +00:00
committed by Android (Google) Code Review
8 changed files with 191 additions and 54 deletions

View File

@@ -108,5 +108,63 @@ ResolvedRenderState::ResolvedRenderState(const ClipRect* clipRect, const Rect& d
clippedBounds.doIntersect(clipRect->rect); 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 uirenderer
} // namespace android } // namespace android

View File

@@ -93,6 +93,7 @@ public:
Rect clippedBounds; Rect clippedBounds;
int clipSideFlags = 0; int clipSideFlags = 0;
const SkPath* localProjectionPathMask = nullptr; const SkPath* localProjectionPathMask = nullptr;
bool opaqueOverClippedBounds = false;
}; };
/** /**
@@ -103,23 +104,10 @@ public:
class BakedOpState { class BakedOpState {
public: public:
static BakedOpState* tryConstruct(LinearAllocator& allocator, static BakedOpState* tryConstruct(LinearAllocator& allocator,
Snapshot& snapshot, const RecordedOp& recordedOp) { 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;
}
static BakedOpState* tryConstructUnbounded(LinearAllocator& allocator, static BakedOpState* tryConstructUnbounded(LinearAllocator& allocator,
Snapshot& snapshot, const RecordedOp& recordedOp) { Snapshot& snapshot, const RecordedOp& recordedOp);
if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr;
return allocator.create_trivial<BakedOpState>(allocator, snapshot, recordedOp);
}
enum class StrokeBehavior { enum class StrokeBehavior {
// stroking is forced, regardless of style on paint (such as for lines) // stroking is forced, regardless of style on paint (such as for lines)
@@ -129,35 +117,16 @@ public:
}; };
static BakedOpState* tryStrokeableOpConstruct(LinearAllocator& allocator, static BakedOpState* tryStrokeableOpConstruct(LinearAllocator& allocator,
Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior) { 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;
}
static BakedOpState* tryShadowOpConstruct(LinearAllocator& allocator, static BakedOpState* tryShadowOpConstruct(LinearAllocator& allocator,
Snapshot& snapshot, const ShadowOp* shadowOpPtr) { 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);
}
static BakedOpState* directConstruct(LinearAllocator& allocator, static BakedOpState* directConstruct(LinearAllocator& allocator,
const ClipRect* clip, const Rect& dstRect, const RecordedOp& recordedOp) { const ClipRect* clip, const Rect& dstRect, const RecordedOp& recordedOp);
return allocator.create_trivial<BakedOpState>(clip, dstRect, recordedOp);
} // Set opaqueOverClippedBounds. If this method isn't called, the op is assumed translucent.
void setupOpacity(const SkPaint* paint);
// computed state: // computed state:
ResolvedRenderState computedState; ResolvedRenderState computedState;

View File

@@ -481,12 +481,17 @@ void FrameBuilder::deferRenderNodeOp(const RenderNodeOp& op) {
* Defers an unmergeable, strokeable op, accounting correctly * Defers an unmergeable, strokeable op, accounting correctly
* for paint's style on the bounds being computed. * 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) { BakedOpState::StrokeBehavior strokeBehavior) {
// Note: here we account for stroke when baking the op // Note: here we account for stroke when baking the op
BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct( BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct(
mAllocator, *mCanvasState.writableSnapshot(), op, strokeBehavior); mAllocator, *mCanvasState.writableSnapshot(), op, strokeBehavior);
if (!bakedState) return nullptr; // quick rejected 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); currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId);
return bakedState; return bakedState;
} }
@@ -516,6 +521,7 @@ static bool hasMergeableClip(const BakedOpState& state) {
void FrameBuilder::deferBitmapOp(const BitmapOp& op) { void FrameBuilder::deferBitmapOp(const BitmapOp& op) {
BakedOpState* bakedState = tryBakeOpState(op); BakedOpState* bakedState = tryBakeOpState(op);
if (!bakedState) return; // quick rejected 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 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 // Don't merge A8 bitmaps - the paint's color isn't compared by mergeId, or in

View File

@@ -201,7 +201,7 @@ private:
return mAllocator.create<SkPath>(); 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); BakedOpState::StrokeBehavior strokeBehavior = BakedOpState::StrokeBehavior::StyleDefined);
/** /**

View File

@@ -236,6 +236,21 @@ void LayerBuilder::deferLayerClear(const Rect& rect) {
mClearRects.push_back(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) { void LayerBuilder::flushLayerClears(LinearAllocator& allocator) {
if (CC_UNLIKELY(!mClearRects.empty())) { if (CC_UNLIKELY(!mClearRects.empty())) {
const int vertCount = mClearRects.size() * 4; const int vertCount = mClearRects.size() * 4;
@@ -270,11 +285,7 @@ void LayerBuilder::flushLayerClears(LinearAllocator& allocator) {
void LayerBuilder::deferUnmergeableOp(LinearAllocator& allocator, void LayerBuilder::deferUnmergeableOp(LinearAllocator& allocator,
BakedOpState* op, batchid_t batchId) { BakedOpState* op, batchid_t batchId) {
if (batchId != OpBatchType::CopyToLayer) { onDeferOp(allocator, op);
// if first op after one or more unclipped saveLayers, flush the layer clears
flushLayerClears(allocator);
}
OpBatch* targetBatch = mBatchLookup[batchId]; OpBatch* targetBatch = mBatchLookup[batchId];
size_t insertBatchIndex = mBatches.size(); size_t insertBatchIndex = mBatches.size();
@@ -295,10 +306,7 @@ void LayerBuilder::deferUnmergeableOp(LinearAllocator& allocator,
void LayerBuilder::deferMergeableOp(LinearAllocator& allocator, void LayerBuilder::deferMergeableOp(LinearAllocator& allocator,
BakedOpState* op, batchid_t batchId, mergeid_t mergeId) { BakedOpState* op, batchid_t batchId, mergeid_t mergeId) {
if (batchId != OpBatchType::CopyToLayer) { onDeferOp(allocator, op);
// if first op after one or more unclipped saveLayers, flush the layer clears
flushLayerClears(allocator);
}
MergingOpBatch* targetBatch = nullptr; MergingOpBatch* targetBatch = nullptr;
// Try to merge with any existing batch with same mergeId // 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 { void LayerBuilder::dump() const {
ALOGD("LayerBuilder %p, %ux%u buffer %p, blo %p, rn %p (%s)", ALOGD("LayerBuilder %p, %ux%u buffer %p, blo %p, rn %p (%s)",
this, width, height, offscreenBuffer, beginLayerOp, this, width, height, offscreenBuffer, beginLayerOp,

View File

@@ -100,9 +100,7 @@ public:
return mBatches.empty(); return mBatches.empty();
} }
void clear() { void clear();
mBatches.clear();
}
void dump() const; void dump() const;
@@ -117,6 +115,7 @@ public:
// list of deferred CopyFromLayer ops, to be deferred upon encountering EndUnclippedLayerOps // list of deferred CopyFromLayer ops, to be deferred upon encountering EndUnclippedLayerOps
std::vector<BakedOpState*> activeUnclippedSaveLayers; std::vector<BakedOpState*> activeUnclippedSaveLayers;
private: private:
void onDeferOp(LinearAllocator& allocator, const BakedOpState* bakedState);
void flushLayerClears(LinearAllocator& allocator); void flushLayerClears(LinearAllocator& allocator);
std::vector<BatchBase*> mBatches; std::vector<BatchBase*> mBatches;

View File

@@ -216,6 +216,80 @@ RENDERTHREAD_TEST(FrameBuilder, simpleBatching) {
<< "Expect number of ops = 2 * loop count"; << "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) { RENDERTHREAD_TEST(FrameBuilder, clippedMerging) {
class ClippedMergingTestRenderer : public TestRendererBase { class ClippedMergingTestRenderer : public TestRendererBase {
public: public:

View File

@@ -67,6 +67,21 @@ public:
&& getXfermode(paint.getXfermode()) == SkXfermode::kSrcOver_Mode; && 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) { static bool isBlendedShader(const SkShader* shader) {
if (shader == nullptr) { if (shader == nullptr) {
return false; return false;