Commit a5f7079f authored by sunxd's avatar sunxd Committed by Commit Bot

cc: Support ClipRRect in SolidColorAnalyzer

We used to unconditionally return not_solid_color when analyzing the op
ClipRRect. This lead to huge memory usage of composited border-radius
scrolling because we need to store the texture for the whole mask layer.

This CL makes SolidColorAnalyzer capable of determining whether a tile
is fully covered by the clip rrect, so we can save memory by not storing
textures of solid color quads.

Bug: 567293, 567296, 740720
Cq-Include-Trybots: master.tryserver.blink:linux_trusty_blink_rel
Change-Id: I24b10a2d68c0c608aec8e5a8bafaabb9800cd466
Reviewed-on: https://chromium-review.googlesource.com/591913Reviewed-by: default avatarRobert Flack <flackr@chromium.org>
Reviewed-by: default avatarWalter Korman <wkorman@chromium.org>
Reviewed-by: default avatarVladimir Levin <vmpstr@chromium.org>
Commit-Queue: Xianda Sun <sunxd@chromium.org>
Cr-Commit-Position: refs/heads/master@{#491732}
parent 18873a37
...@@ -45,9 +45,13 @@ bool IsSolidColorPaint(const PaintFlags& flags) { ...@@ -45,9 +45,13 @@ bool IsSolidColorPaint(const PaintFlags& flags) {
flags.getStyle() == PaintFlags::kFill_Style; flags.getStyle() == PaintFlags::kFill_Style;
} }
// Returns true if the specified drawn_rect will cover the entire canvas, and // Returns true if the specified |drawn_shape| will cover the entire canvas
// that the canvas is not clipped (i.e. it covers ALL of the canvas). // and that the canvas is not clipped (i.e. it covers ALL of the canvas).
bool IsFullQuad(const SkCanvas& canvas, const SkRect& drawn_rect) { template <typename T>
bool IsFullQuad(const SkCanvas& canvas, const T& drawn_shape) {
if (!canvas.isClipRect())
return false;
SkIRect clip_irect; SkIRect clip_irect;
if (!canvas.getDeviceClipBounds(&clip_irect)) if (!canvas.getDeviceClipBounds(&clip_irect))
return false; return false;
...@@ -62,11 +66,13 @@ bool IsFullQuad(const SkCanvas& canvas, const SkRect& drawn_rect) { ...@@ -62,11 +66,13 @@ bool IsFullQuad(const SkCanvas& canvas, const SkRect& drawn_rect) {
if (!matrix.rectStaysRect()) if (!matrix.rectStaysRect())
return false; return false;
SkRect device_rect; SkMatrix inverse;
matrix.mapRect(&device_rect, drawn_rect); if (!matrix.invert(&inverse))
SkRect clip_rect; return false;
clip_rect.set(clip_irect);
return device_rect.contains(clip_rect); SkRect clip_rect = SkRect::Make(clip_irect);
inverse.mapRect(&clip_rect, clip_rect);
return drawn_shape.contains(clip_rect);
} }
void CheckIfSolidColor(const SkCanvas& canvas, void CheckIfSolidColor(const SkCanvas& canvas,
...@@ -97,18 +103,19 @@ void CheckIfSolidColor(const SkCanvas& canvas, ...@@ -97,18 +103,19 @@ void CheckIfSolidColor(const SkCanvas& canvas,
} }
} }
void CheckIfSolidRect(const SkCanvas& canvas, template <typename T>
const SkRect& rect, void CheckIfSolidShape(const SkCanvas& canvas,
const PaintFlags& flags, const T& shape,
bool* is_solid_color, const PaintFlags& flags,
bool* is_transparent, bool* is_solid_color,
SkColor* color) { bool* is_transparent,
SkColor* color) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"), TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
"SolidColorAnalyzer::HandleDrawRect"); "SolidColorAnalyzer::CheckIfSolidShape");
if (flags.nothingToDraw()) if (flags.nothingToDraw())
return; return;
bool does_cover_canvas = IsFullQuad(canvas, rect); bool does_cover_canvas = IsFullQuad(canvas, shape);
SkBlendMode blendmode = flags.getBlendMode(); SkBlendMode blendmode = flags.getBlendMode();
if (does_cover_canvas && ActsLikeClear(blendmode, flags.getAlpha())) if (does_cover_canvas && ActsLikeClear(blendmode, flags.getAlpha()))
*is_transparent = true; *is_transparent = true;
...@@ -123,6 +130,13 @@ void CheckIfSolidRect(const SkCanvas& canvas, ...@@ -123,6 +130,13 @@ void CheckIfSolidRect(const SkCanvas& canvas,
} }
} }
bool CheckIfRRectClipCoversCanvas(const SkCanvas& canvas,
const SkRRect& rrect) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
"SolidColorAnalyzer::CheckIfRRectClipCoversCanvas");
return IsFullQuad(canvas, rrect);
}
} // namespace } // namespace
base::Optional<SkColor> SolidColorAnalyzer::DetermineIfSolidColor( base::Optional<SkColor> SolidColorAnalyzer::DetermineIfSolidColor(
...@@ -160,7 +174,7 @@ base::Optional<SkColor> SolidColorAnalyzer::DetermineIfSolidColor( ...@@ -160,7 +174,7 @@ base::Optional<SkColor> SolidColorAnalyzer::DetermineIfSolidColor(
stack.emplace_back(PaintOpBuffer::CompositeIterator(buffer, offsets), stack.emplace_back(PaintOpBuffer::CompositeIterator(buffer, offsets),
canvas.getTotalMatrix(), canvas.getSaveCount()); canvas.getTotalMatrix(), canvas.getSaveCount());
int num_ops = 0; int num_draw_ops = 0;
while (!stack.empty()) { while (!stack.empty()) {
auto& frame = stack.back(); auto& frame = stack.back();
if (!frame.iter) { if (!frame.iter) {
...@@ -193,7 +207,16 @@ base::Optional<SkColor> SolidColorAnalyzer::DetermineIfSolidColor( ...@@ -193,7 +207,16 @@ base::Optional<SkColor> SolidColorAnalyzer::DetermineIfSolidColor(
case PaintOpType::DrawOval: case PaintOpType::DrawOval:
case PaintOpType::DrawPath: case PaintOpType::DrawPath:
case PaintOpType::DrawPosText: case PaintOpType::DrawPosText:
case PaintOpType::DrawRRect: return base::nullopt;
// TODO(vmpstr): Add more tests on exceeding max_ops_to_analyze.
case PaintOpType::DrawRRect: {
if (++num_draw_ops > max_ops_to_analyze)
return base::nullopt;
const DrawRRectOp* rrect_op = static_cast<const DrawRRectOp*>(op);
CheckIfSolidShape(canvas, rrect_op->rrect, rrect_op->flags, &is_solid,
&is_transparent, &color);
break;
}
case PaintOpType::DrawText: case PaintOpType::DrawText:
case PaintOpType::DrawTextBlob: case PaintOpType::DrawTextBlob:
// Anything that has to do a save layer is probably not solid. As it will // Anything that has to do a save layer is probably not solid. As it will
...@@ -205,19 +228,27 @@ base::Optional<SkColor> SolidColorAnalyzer::DetermineIfSolidColor( ...@@ -205,19 +228,27 @@ base::Optional<SkColor> SolidColorAnalyzer::DetermineIfSolidColor(
// cover the canvas. // cover the canvas.
// TODO(vmpstr): We could investigate handling these. // TODO(vmpstr): We could investigate handling these.
case PaintOpType::ClipPath: case PaintOpType::ClipPath:
case PaintOpType::ClipRRect:
return base::nullopt; return base::nullopt;
case PaintOpType::ClipRRect: {
const ClipRRectOp* rrect_op = static_cast<const ClipRRectOp*>(op);
bool does_cover_canvas =
CheckIfRRectClipCoversCanvas(canvas, rrect_op->rrect);
// If the clip covers the full canvas, we can treat it as if there's no
// clip at all and continue, otherwise this is no longer a solid color.
if (!does_cover_canvas)
return base::nullopt;
break;
}
case PaintOpType::DrawRect: { case PaintOpType::DrawRect: {
if (++num_ops > max_ops_to_analyze) if (++num_draw_ops > max_ops_to_analyze)
return base::nullopt; return base::nullopt;
const DrawRectOp* rect_op = static_cast<const DrawRectOp*>(op); const DrawRectOp* rect_op = static_cast<const DrawRectOp*>(op);
CheckIfSolidRect(canvas, rect_op->rect, rect_op->flags, &is_solid, CheckIfSolidShape(canvas, rect_op->rect, rect_op->flags, &is_solid,
&is_transparent, &color); &is_transparent, &color);
break; break;
} }
case PaintOpType::DrawColor: { case PaintOpType::DrawColor: {
if (++num_ops > max_ops_to_analyze) if (++num_draw_ops > max_ops_to_analyze)
return base::nullopt; return base::nullopt;
const DrawColorOp* color_op = static_cast<const DrawColorOp*>(op); const DrawColorOp* color_op = static_cast<const DrawColorOp*>(op);
CheckIfSolidColor(canvas, color_op->color, color_op->mode, &is_solid, CheckIfSolidColor(canvas, color_op->color, color_op->mode, &is_solid,
......
...@@ -29,6 +29,11 @@ class SolidColorAnalyzerTest : public testing::Test { ...@@ -29,6 +29,11 @@ class SolidColorAnalyzerTest : public testing::Test {
buffer_ = nullptr; buffer_ = nullptr;
} }
void Reset() {
TearDown();
SetUp();
}
void Initialize(const gfx::Rect& rect = gfx::Rect(0, 0, 100, 100)) { void Initialize(const gfx::Rect& rect = gfx::Rect(0, 0, 100, 100)) {
canvas_.emplace(display_item_list_.get(), gfx::RectToSkRect(rect)); canvas_.emplace(display_item_list_.get(), gfx::RectToSkRect(rect));
rect_ = rect; rect_ = rect;
...@@ -127,6 +132,21 @@ TEST_F(SolidColorAnalyzerTest, DrawRect) { ...@@ -127,6 +132,21 @@ TEST_F(SolidColorAnalyzerTest, DrawRect) {
EXPECT_EQ(color, GetColor()); EXPECT_EQ(color, GetColor());
} }
// TODO(vmpstr): Generalize the DrawRect test cases so that we can test both
// Rect and RRect.
TEST_F(SolidColorAnalyzerTest, DrawRRect) {
SkRect rect = SkRect::MakeWH(200, 200);
SkRRect rrect;
rrect.setRectXY(rect, 5, 5);
gfx::Rect canvas_rect(5, 5, 190, 190);
Initialize(canvas_rect);
PaintFlags flags;
SkColor color = SkColorSetARGB(255, 11, 22, 33);
flags.setColor(color);
canvas()->drawRRect(rrect, flags);
EXPECT_EQ(color, GetColor());
}
TEST_F(SolidColorAnalyzerTest, DrawRectClipped) { TEST_F(SolidColorAnalyzerTest, DrawRectClipped) {
Initialize(); Initialize();
PaintFlags flags; PaintFlags flags;
...@@ -320,5 +340,85 @@ TEST_F(SolidColorAnalyzerTest, SaveLayer) { ...@@ -320,5 +340,85 @@ TEST_F(SolidColorAnalyzerTest, SaveLayer) {
EXPECT_FALSE(IsSolidColor()); EXPECT_FALSE(IsSolidColor());
} }
TEST_F(SolidColorAnalyzerTest, ClipRRectCoversCanvas) {
SkVector radii[4] = {
SkVector::Make(10.0, 15.0), SkVector::Make(20.0, 25.0),
SkVector::Make(30.0, 35.0), SkVector::Make(40.0, 45.0),
};
SkVector radii_scale[4] = {
SkVector::Make(100.0, 150.0), SkVector::Make(200.0, 250.0),
SkVector::Make(300.0, 350.0), SkVector::Make(400.0, 450.0),
};
int rr_size = 600;
int canvas_size = 255;
gfx::Rect canvas_rect(canvas_size, canvas_size);
PaintFlags flags;
flags.setColor(SK_ColorWHITE);
struct {
SkVector offset;
SkVector offset_scale;
bool expected;
} cases[] = {
// Not within bounding box of |rr|.
{SkVector::Make(100, 100), SkVector::Make(100, 100), false},
// Intersects UL corner.
{SkVector::Make(0, 0), SkVector::Make(0, 0), false},
// Between UL and UR.
{SkVector::Make(-50, 0), SkVector::Make(-50, -15), true},
// Intersects UR corner.
{SkVector::Make(canvas_size - rr_size, 0),
SkVector::Make(canvas_size - rr_size, 0), false},
// Between UR and LR.
{SkVector::Make(canvas_size - rr_size, -50), SkVector::Make(-305, -80),
true},
// Intersects LR corner.
{SkVector::Make(canvas_size - rr_size, canvas_size - rr_size),
SkVector::Make(canvas_size - rr_size, canvas_size - rr_size), false},
// Between LL and LR
{SkVector::Make(-50, canvas_size - rr_size), SkVector::Make(-205, -310),
true},
// Intersects LL corner
{SkVector::Make(0, canvas_size - rr_size),
SkVector::Make(0, canvas_size - rr_size), false},
// Between UL and LL
{SkVector::Make(0, -50), SkVector::Make(-15, -60), true},
// In center
{SkVector::Make(-100, -100), SkVector::Make(-100, -100), true},
};
for (int case_scale = 0; case_scale < 2; ++case_scale) {
bool scaled = case_scale > 0;
for (size_t i = 0; i < arraysize(cases); ++i) {
Reset();
Initialize(canvas_rect);
SkRect bounding_rect = SkRect::MakeXYWH(
scaled ? cases[i].offset_scale.x() : cases[i].offset.x(),
scaled ? cases[i].offset_scale.y() : cases[i].offset.y(), rr_size,
rr_size);
SkRRect rr;
rr.setRectRadii(bounding_rect, scaled ? radii_scale : radii);
canvas()->clipRRect(rr, SkClipOp::kIntersect, false);
canvas()->drawRect(RectToSkRect(canvas_rect), flags);
EXPECT_EQ(cases[i].expected, IsSolidColor())
<< "Case " << i << ", " << scaled << " failed.";
}
}
}
} // namespace } // namespace
} // namespace cc } // namespace cc
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment