0

Measure the percentage of Canvas is dirty before each canvas repaint

Added UMA metrics Canvas.Repaint.Percentage to measure the percentage
of Canvas is dirty before each repaint. We record this metrics for all
canvas that has an area of more than 65k pixel^2 (256x256).

This metrics will be expired after M81. This is a study we use to
evaluate the importance of implementing partial canvas updates.

Design doc:
https://docs.google.com/document/u/1/d/1elb-s5UwE0k-iCJGTO0ss9TlX48s0-QwcJjfOXGBf7g

TBR=mpearson@chromium.org

Bug: 1014246
Change-Id: I0430f9100d1f163a629dbd1f70cf95e7945f6a4b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1946694
Reviewed-by: Yi Xu <yiyix@chromium.org>
Reviewed-by: Juanmi Huertas <juanmihd@chromium.org>
Reviewed-by: Khushal <khushalsagar@chromium.org>
Reviewed-by: Aaron Krajeski <aaronhk@chromium.org>
Reviewed-by: Fernando Serboncini <fserb@chromium.org>
Commit-Queue: Yi Xu <yiyix@chromium.org>
Cr-Commit-Position: refs/heads/master@{#730774}
This commit is contained in:
Yi Xu
2020-01-13 20:26:00 +00:00
committed by Commit Bot
parent 19059f7ddb
commit a1f4ec2dc5
7 changed files with 378 additions and 74 deletions

@ -24,26 +24,6 @@ namespace cc {
namespace {
const int kMaxRectsSize = 256;
SkRect MapRect(const SkMatrix& matrix, const SkRect& src) {
SkRect dst;
matrix.mapRect(&dst, src);
return dst;
}
bool ComputePaintBounds(const SkRect& rect,
const SkPaint* current_paint,
SkRect* paint_bounds) {
*paint_bounds = rect;
if (current_paint) {
if (!current_paint->canComputeFastBounds())
return false;
*paint_bounds =
current_paint->computeFastBounds(*paint_bounds, paint_bounds);
}
return true;
}
class DiscardableImageGenerator {
public:
DiscardableImageGenerator(int width,
@ -148,7 +128,10 @@ class DiscardableImageGenerator {
if (top_level_op_rect) {
op_rect = *top_level_op_rect;
} else {
local_op_rect = ComputePaintRect(op, canvas);
const SkRect& clip_rect = SkRect::Make(canvas->getDeviceClipBounds());
const SkMatrix& ctm = canvas->getTotalMatrix();
local_op_rect = PaintOp::ComputePaintRect(op, clip_rect, ctm);
if (local_op_rect.value().IsEmpty())
continue;
@ -184,58 +167,6 @@ class DiscardableImageGenerator {
}
}
// Given the |op_rect|, which is the rect for the draw op, returns the
// transformed rect accounting for the current transform, clip and paint
// state on |canvas_|.
gfx::Rect ComputePaintRect(const PaintOp* op, SkNoDrawCanvas* canvas) {
const SkRect& clip_rect = SkRect::Make(canvas->getDeviceClipBounds());
const SkMatrix& ctm = canvas->getTotalMatrix();
gfx::Rect transformed_rect;
SkRect op_rect;
if (!op->IsDrawOp() || !PaintOp::GetBounds(op, &op_rect)) {
// If we can't provide a conservative bounding rect for the op, assume it
// covers the complete current clip.
// TODO(khushalsagar): See if we can do something better for non-draw ops.
transformed_rect = gfx::ToEnclosingRect(gfx::SkRectToRectF(clip_rect));
} else {
const PaintFlags* flags =
op->IsPaintOpWithFlags()
? &static_cast<const PaintOpWithFlags*>(op)->flags
: nullptr;
SkPaint paint;
if (flags)
paint = flags->ToSkPaint();
SkRect paint_rect = MapRect(ctm, op_rect);
bool computed_paint_bounds =
ComputePaintBounds(paint_rect, &paint, &paint_rect);
if (!computed_paint_bounds) {
// TODO(vmpstr): UMA this case.
paint_rect = clip_rect;
}
// Clamp the image rect by the current clip rect.
if (!paint_rect.intersect(clip_rect))
return gfx::Rect();
transformed_rect = gfx::ToEnclosingRect(gfx::SkRectToRectF(paint_rect));
}
// During raster, we use the device clip bounds on the canvas, which outsets
// the actual clip by 1 due to the possibility of antialiasing. Account for
// this here by outsetting the image rect by 1. Note that this only affects
// queries into the rtree, which will now return images that only touch the
// bounds of the query rect.
//
// Note that it's not sufficient for us to inset the device clip bounds at
// raster time, since we might be sending a larger-than-one-item display
// item to skia, which means that skia will internally determine whether to
// raster the picture (using device clip bounds that are outset).
transformed_rect.Inset(-1, -1);
return transformed_rect;
}
void AddImageFromFlags(const gfx::Rect& op_rect,
const PaintFlags& flags,
const SkMatrix& ctm) {

@ -18,6 +18,7 @@
#include "third_party/skia/include/core/SkSerialProcs.h"
#include "third_party/skia/include/docs/SkPDFDocument.h"
#include "third_party/skia/include/gpu/GrContext.h"
#include "ui/gfx/skia_util.h"
namespace cc {
namespace {
@ -46,6 +47,12 @@ SkRect AdjustSrcRectForScale(SkRect original, SkSize scale_adjustment) {
original.width() * x_scale,
original.height() * y_scale);
}
SkRect MapRect(const SkMatrix& matrix, const SkRect& src) {
SkRect dst;
matrix.mapRect(&dst, src);
return dst;
}
} // namespace
#define TYPES(M) \
@ -2046,6 +2053,50 @@ bool PaintOp::GetBounds(const PaintOp* op, SkRect* rect) {
return false;
}
// static
gfx::Rect PaintOp::ComputePaintRect(const PaintOp* op,
const SkRect& clip_rect,
const SkMatrix& ctm) {
gfx::Rect transformed_rect;
SkRect op_rect;
if (!op->IsDrawOp() || !PaintOp::GetBounds(op, &op_rect)) {
// If we can't provide a conservative bounding rect for the op, assume it
// covers the complete current clip.
// TODO(khushalsagar): See if we can do something better for non-draw ops.
transformed_rect = gfx::ToEnclosingRect(gfx::SkRectToRectF(clip_rect));
} else {
const PaintFlags* flags =
op->IsPaintOpWithFlags()
? &static_cast<const PaintOpWithFlags*>(op)->flags
: nullptr;
SkRect paint_rect = MapRect(ctm, op_rect);
if (flags) {
SkPaint paint = flags->ToSkPaint();
paint_rect = paint.canComputeFastBounds()
? paint.computeFastBounds(paint_rect, &paint_rect)
: clip_rect;
}
// Clamp the image rect by the current clip rect.
if (!paint_rect.intersect(clip_rect))
return gfx::Rect();
transformed_rect = gfx::ToEnclosingRect(gfx::SkRectToRectF(paint_rect));
}
// During raster, we use the device clip bounds on the canvas, which outsets
// the actual clip by 1 due to the possibility of antialiasing. Account for
// this here by outsetting the image rect by 1. Note that this only affects
// queries into the rtree, which will now return images that only touch the
// bounds of the query rect.
//
// Note that it's not sufficient for us to inset the device clip bounds at
// raster time, since we might be sending a larger-than-one-item display
// item to skia, which means that skia will internally determine whether to
// raster the picture (using device clip bounds that are outset).
transformed_rect.Inset(-1, -1);
return transformed_rect;
}
// static
bool PaintOp::QuickRejectDraw(const PaintOp* op, const SkCanvas* canvas) {
if (!op->IsDrawOp())

@ -227,6 +227,13 @@ class CC_PAINT_EXPORT PaintOp {
// for the op.
static bool GetBounds(const PaintOp* op, SkRect* rect);
// Returns the minimum conservative bounding rect that |op| draws to on a
// canvas. |clip_rect| and |ctm| are the current clip rect and transform on
// this canvas.
static gfx::Rect ComputePaintRect(const PaintOp* op,
const SkRect& clip_rect,
const SkMatrix& ctm);
// Returns true if the op lies outside the current clip and should be skipped.
// Should only be used with draw ops.
static bool QuickRejectDraw(const PaintOp* op, const SkCanvas* canvas);

@ -29,12 +29,15 @@
#include <utility>
#include "base/location.h"
#include "base/numerics/checked_math.h"
#include "base/rand_util.h"
#include "base/single_thread_task_runner.h"
#include "base/timer/elapsed_timer.h"
#include "cc/base/region.h"
#include "cc/layers/texture_layer.h"
#include "components/viz/common/resources/transferable_resource.h"
#include "gpu/command_buffer/client/raster_interface.h"
#include "ui/gfx/geometry/size.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/platform/platform.h"
@ -56,6 +59,52 @@
namespace blink {
namespace {
// Canvas with an area less than 256 pixel by 256 pixel are considered too small
// for partial repaint instead of full repaint.
constexpr int kMinAreaForComputingDirtyRegion = 256 * 256;
// Returns true if the area defined by |width| and |height| are dirty; false
// otherwise.
bool GatherDirtyRect(const cc::PaintOpBuffer* buffer,
cc::InvalidationRegion* dirty_region,
SkNoDrawCanvas* canvas,
SkScalar width,
SkScalar height) {
// Prevent PaintOpBuffers from having side effects back into the canvas.
SkAutoCanvasRestore save_restore(canvas, true);
cc::PlaybackParams params(nullptr, canvas->getTotalMatrix());
for (auto* op : cc::PaintOpBuffer::Iterator(buffer)) {
// Note that the dirty from SaveLayer will not computed until the next draw
// op. Since there is alawys a draw op between SaveLayer and Restore, this
// approach does the computation correctly.
if (!op->IsDrawOp()) {
op->Raster(canvas, params);
continue;
}
if (static_cast<cc::PaintOpType>(op->type) == cc::PaintOpType::DrawRecord) {
sk_sp<const PaintRecord> paint_record =
static_cast<cc::DrawRecordOp*>(op)->record;
if (GatherDirtyRect(paint_record.get(), dirty_region, canvas, width,
height))
return true;
} else {
const SkRect& clip_rect = SkRect::Make(canvas->getDeviceClipBounds());
const SkMatrix& ctm = canvas->getTotalMatrix();
gfx::Rect rect = cc::PaintOp::ComputePaintRect(op, clip_rect, ctm);
dirty_region->Union(rect);
// If one draw op takes the entire canvas, stop compute for the dirty
// rect from the remaining draw ops.
if (rect.width() >= width && rect.height() >= height)
return true;
}
}
return false;
}
} // namespace
Canvas2DLayerBridge::Canvas2DLayerBridge(const IntSize& size,
AccelerationMode acceleration_mode,
const CanvasColorParams& color_params)
@ -471,6 +520,45 @@ void Canvas2DLayerBridge::SkipQueuedDrawCommands() {
rate_limiter_->Reset();
}
void Canvas2DLayerBridge::CalculateDirtyRegion(int canvas_width,
int canvas_height) {
base::CheckedNumeric<int> area(canvas_width);
area *= canvas_height;
if (!area.IsValid() || area.ValueOrDie() < kMinAreaForComputingDirtyRegion)
return;
SkNoDrawCanvas no_draw_canvas(canvas_width, canvas_height);
dirty_invalidate_region_.Clear();
// If GatherDirtyRect returns true, the entire canvas is dirty. Record the
// percentage of dirty area in the canvas to 100 and skip the calculation.
if (GatherDirtyRect(last_recording_.get(), &dirty_invalidate_region_,
&no_draw_canvas, canvas_width, canvas_height)) {
UMA_HISTOGRAM_PERCENTAGE("Canvas.Repaint.Region.Percentage", 100);
UMA_HISTOGRAM_PERCENTAGE("Canvas.Repaint.Bounds.Percentage", 100);
} else {
int valid_area = area.ValueOrDie();
base::CheckedNumeric<int> used_total = 0;
cc::Region dirty_region;
dirty_invalidate_region_.Swap(&dirty_region);
gfx::Rect bounds;
for (const gfx::Rect& rect : dirty_region) {
bounds.Union(rect);
used_total += rect.size().GetCheckedArea();
}
int ratio_region =
(static_cast<float>(used_total.ValueOrDefault(valid_area)) /
valid_area) *
100;
int ratio_rect =
(static_cast<float>(
bounds.size().GetCheckedArea().ValueOrDefault(valid_area)) /
valid_area) *
100;
UMA_HISTOGRAM_PERCENTAGE("Canvas.Repaint.Region.Percentage", ratio_region);
UMA_HISTOGRAM_PERCENTAGE("Canvas.Repaint.Bounds.Percentage", ratio_rect);
}
}
void Canvas2DLayerBridge::ClearPendingRasterTimers() {
gpu::raster::RasterInterface* raster_interface = nullptr;
if (IsAccelerated() && SharedGpuContext::ContextProviderWrapper() &&
@ -567,10 +655,15 @@ void Canvas2DLayerBridge::FlushRecording() {
}
timer.emplace();
}
{ // Make a new scope so that PaintRecord gets deleted and that gets timed
cc::PaintCanvas* canvas = ResourceProvider()->Canvas();
last_recording_ = recorder_->finishRecordingAsPicture();
SkScalar canvas_width = canvas->getLocalClipBounds().width();
SkScalar canvas_height = canvas->getLocalClipBounds().height();
DCHECK_GE(canvas_width, size_.Width());
DCHECK_GE(canvas_height, size_.Height());
CalculateDirtyRegion(canvas_width, canvas_height);
canvas->drawPicture(last_recording_);
last_record_tainted_by_write_pixels_ = false;
if (!clear_frame_ || !resource_host_ || !resource_host_->IsPrinting()) {

@ -35,6 +35,7 @@
#include "base/memory/weak_ptr.h"
#include "base/numerics/checked_math.h"
#include "build/build_config.h"
#include "cc/base/invalidation_region.h"
#include "cc/layers/texture_layer_client.h"
#include "components/viz/common/resources/transferable_resource.h"
#include "gpu/GLES2/gl2extchromium.h"
@ -202,6 +203,7 @@ class PLATFORM_EXPORT Canvas2DLayerBridge : public cc::TextureLayerClient {
void SkipQueuedDrawCommands();
bool ShouldAccelerate(AccelerationHint) const;
void CalculateDirtyRegion(int canvas_width, int canvas_height);
std::unique_ptr<PaintRecorder> recorder_;
sk_sp<SkImage> hibernation_image_;
@ -254,6 +256,7 @@ class PLATFORM_EXPORT Canvas2DLayerBridge : public cc::TextureLayerClient {
Deque<RasterTimer> pending_raster_timers_;
sk_sp<cc::PaintRecord> last_recording_;
cc::InvalidationRegion dirty_invalidate_region_;
void SetNeedsFlush();
base::RepeatingClosure set_needs_flush_callback_;

@ -31,6 +31,7 @@
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/test/metrics/histogram_tester.h"
#include "build/build_config.h"
#include "cc/layers/texture_layer.h"
#include "cc/test/skia_common.h"
@ -1155,4 +1156,200 @@ TEST_F(Canvas2DLayerBridgeTest, NonDisplayedCanvasIsNotRateLimited) {
EXPECT_FALSE(bridge->HasRateLimiterForTesting());
}
// Test if we skip dirty rect calculation for canvas smaller than 256x256.
TEST_F(Canvas2DLayerBridgeTest, SkipDirtyRectForSmallCanvas) {
PaintFlags flags;
std::unique_ptr<Canvas2DLayerBridge> bridge =
MakeBridge(IntSize(100, 100), Canvas2DLayerBridge::kEnableAcceleration,
CanvasColorParams());
bridge->FinalizeFrame();
base::HistogramTester histogram_tester;
cc::PaintCanvas* canvas = bridge->DrawingCanvas();
canvas->drawRect(SkRect::MakeWH(100, 100), flags);
DrawSomething(bridge.get());
histogram_tester.ExpectTotalCount("Canvas.Repaint.Bounds.Percentage", 0);
histogram_tester.ExpectTotalCount("Canvas.Repaint.Region.Percentage", 0);
}
// Test if we can correctly calculate dirty rect for region with complexity 1.
TEST_F(Canvas2DLayerBridgeTest, SmallDirtyRectCalculation) {
PaintFlags flags;
std::unique_ptr<Canvas2DLayerBridge> bridge =
MakeBridge(IntSize(300, 300), Canvas2DLayerBridge::kEnableAcceleration,
CanvasColorParams());
bridge->FinalizeFrame();
base::HistogramTester histogram_tester;
cc::PaintCanvas* canvas = bridge->DrawingCanvas();
canvas->drawRect(SkRect::MakeWH(100, 100), flags);
DrawSomething(bridge.get());
// Dirty rect: (-1, -1, 102x102) & canvas size: 302x302. Dirty percentage:
// (102x102)/(302x302) = 11. (1 pixel is added around the rect for anti-alias
// effect.)
histogram_tester.ExpectTotalCount("Canvas.Repaint.Bounds.Percentage", 1);
histogram_tester.ExpectUniqueSample("Canvas.Repaint.Bounds.Percentage", 11,
1);
histogram_tester.ExpectTotalCount("Canvas.Repaint.Region.Percentage", 1);
histogram_tester.ExpectUniqueSample("Canvas.Repaint.Region.Percentage", 11,
1);
}
TEST_F(Canvas2DLayerBridgeTest, BigDirtyRectCalculation) {
PaintFlags flags;
std::unique_ptr<Canvas2DLayerBridge> bridge =
MakeBridge(IntSize(300, 300), Canvas2DLayerBridge::kEnableAcceleration,
CanvasColorParams());
bridge->FinalizeFrame();
base::HistogramTester histogram_tester;
cc::PaintCanvas* canvas = bridge->DrawingCanvas();
canvas->drawRect(SkRect::MakeWH(300, 300), flags);
DrawSomething(bridge.get());
// Dirty rect: (-1, -1, 302x302) & canvas size: 302x302. Dirty percentage:
// (302x302)/(302x302) = 100. (1 pixel is added around the rect for anti-alias
// effect.)
histogram_tester.ExpectTotalCount("Canvas.Repaint.Bounds.Percentage", 1);
histogram_tester.ExpectUniqueSample("Canvas.Repaint.Bounds.Percentage", 100,
1);
histogram_tester.ExpectTotalCount("Canvas.Repaint.Region.Percentage", 1);
histogram_tester.ExpectUniqueSample("Canvas.Repaint.Region.Percentage", 100,
1);
}
// Test if we can correctly calculate dirty rect for region with complexity 2;
// where dirty bounds and dirty region have different areas.
TEST_F(Canvas2DLayerBridgeTest, TwoRegionDirtyRectCalculation) {
PaintFlags flags;
std::unique_ptr<Canvas2DLayerBridge> bridge =
MakeBridge(IntSize(300, 300), Canvas2DLayerBridge::kEnableAcceleration,
CanvasColorParams());
bridge->FinalizeFrame();
base::HistogramTester histogram_tester;
cc::PaintCanvas* canvas = bridge->DrawingCanvas();
canvas->drawRect(SkRect::MakeWH(300, 30), flags);
canvas->drawRect(SkRect::MakeWH(30, 300), flags);
DrawSomething(bridge.get());
// Dirty region: (-1, -1, 302x32) Union (-1, 31, 32x270) & canvas size:
// 302x302. Dirty percentage: (302x31)/(31x271) = 20. (1 pixel is added
// around the rect for anti-alias effect.)
histogram_tester.ExpectTotalCount("Canvas.Repaint.Region.Percentage", 1);
histogram_tester.ExpectUniqueSample("Canvas.Repaint.Region.Percentage", 20,
1);
// Dirty region: (-1, -1, 302x32) Union (-1, 31, 32x270) = (-1, -1, 302x302)
// & canvas size: 302x302. Dirty percentage: (302x302)/(302x302) = 100. (1
// pixel is added around the rect for anti-alias effect.)
histogram_tester.ExpectTotalCount("Canvas.Repaint.Bounds.Percentage", 1);
histogram_tester.ExpectUniqueSample("Canvas.Repaint.Bounds.Percentage", 100,
1);
}
// Test dirty rect calculation for canvas with scale transforms.
TEST_F(Canvas2DLayerBridgeTest, TransformedCanvasDirtyRect) {
PaintFlags flags;
std::unique_ptr<Canvas2DLayerBridge> bridge =
MakeBridge(IntSize(500, 500), Canvas2DLayerBridge::kEnableAcceleration,
CanvasColorParams());
bridge->FinalizeFrame();
base::HistogramTester histogram_tester;
cc::PaintCanvas* canvas = bridge->DrawingCanvas();
canvas->scale(0.5f, 0.5f);
canvas->drawRect(SkRect::MakeWH(500, 500), flags);
DrawSomething(bridge.get());
// Dirty region: 252x252 (scale transform reduces the height and width by
// half) & canvas size: 502x502, Dirty percentage: (252x252)/(502x502) = 25.
histogram_tester.ExpectTotalCount("Canvas.Repaint.Region.Percentage", 1);
histogram_tester.ExpectUniqueSample("Canvas.Repaint.Region.Percentage", 25,
1);
histogram_tester.ExpectTotalCount("Canvas.Repaint.Bounds.Percentage", 1);
histogram_tester.ExpectUniqueSample("Canvas.Repaint.Bounds.Percentage", 25,
1);
}
// Test dirty rect calculation for canvas with rotation transforms.
TEST_F(Canvas2DLayerBridgeTest, RotationCanvasDirtyRect) {
PaintFlags flags;
std::unique_ptr<Canvas2DLayerBridge> bridge =
MakeBridge(IntSize(200, 600), Canvas2DLayerBridge::kEnableAcceleration,
CanvasColorParams());
bridge->FinalizeFrame();
base::HistogramTester histogram_tester;
cc::PaintCanvas* canvas = bridge->DrawingCanvas();
canvas->rotate(90);
SkRect dirty_rect;
dirty_rect.setXYWH(50, -100, 60, 60);
canvas->drawRect(dirty_rect, flags);
DrawSomething(bridge.get());
// After rotation, the canvas is at (-600, 0, 600x200) at 90
// degree. Dirty Region: 62x62 & Canvas size: 202x602, dirty percentage:
// (62x62)/(202x602) = 3.
histogram_tester.ExpectTotalCount("Canvas.Repaint.Region.Percentage", 1);
histogram_tester.ExpectUniqueSample("Canvas.Repaint.Region.Percentage", 3, 1);
histogram_tester.ExpectTotalCount("Canvas.Repaint.Bounds.Percentage", 1);
histogram_tester.ExpectUniqueSample("Canvas.Repaint.Bounds.Percentage", 3, 1);
}
// Test dirty rect calculation for canvas with translation transforms.
TEST_F(Canvas2DLayerBridgeTest, TranslationCanvasDirtyRect) {
PaintFlags flags;
std::unique_ptr<Canvas2DLayerBridge> bridge =
MakeBridge(IntSize(500, 500), Canvas2DLayerBridge::kEnableAcceleration,
CanvasColorParams());
bridge->FinalizeFrame();
base::HistogramTester histogram_tester;
cc::PaintCanvas* canvas = bridge->DrawingCanvas();
canvas->translate(20, 50);
SkRect dirty_rect;
dirty_rect.setXYWH(50, 70, 60, 60);
canvas->drawRect(dirty_rect, flags);
DrawSomething(bridge.get());
// After translation, the canvas is at (20, 50, 500x500). Dirty
// Region: 62x62 & Canvas size: 502x502, dirty percentage:
// (62x62)/(502x502)=1.
histogram_tester.ExpectTotalCount("Canvas.Repaint.Region.Percentage", 1);
histogram_tester.ExpectUniqueSample("Canvas.Repaint.Region.Percentage", 1, 1);
histogram_tester.ExpectTotalCount("Canvas.Repaint.Bounds.Percentage", 1);
histogram_tester.ExpectUniqueSample("Canvas.Repaint.Bounds.Percentage", 1, 1);
}
// Test dirty rect calculation for canvas with clip rect.
TEST_F(Canvas2DLayerBridgeTest, ClipRectCanvasDirtyRect) {
PaintFlags flags;
std::unique_ptr<Canvas2DLayerBridge> bridge =
MakeBridge(IntSize(500, 500), Canvas2DLayerBridge::kEnableAcceleration,
CanvasColorParams());
bridge->FinalizeFrame();
base::HistogramTester histogram_tester;
cc::PaintCanvas* canvas = bridge->DrawingCanvas();
canvas->clipRect(SkRect::MakeWH(100, 100));
canvas->drawRect(SkRect::MakeWH(200, 200), flags);
DrawSomething(bridge.get());
// Dirty region: 102x102 (clip rect restrict the dirty to be in 102x202) &
// canvas size: 502x502, dirty percentage:
// (102x102)/(502x502) = 4.
histogram_tester.ExpectTotalCount("Canvas.Repaint.Region.Percentage", 1);
histogram_tester.ExpectUniqueSample("Canvas.Repaint.Region.Percentage", 4, 1);
histogram_tester.ExpectTotalCount("Canvas.Repaint.Bounds.Percentage", 1);
histogram_tester.ExpectUniqueSample("Canvas.Repaint.Bounds.Percentage", 4, 1);
}
// Test if we can correctly calculate dirty rect for canvas with paintrecord;
TEST_F(Canvas2DLayerBridgeTest, PaintRecordDirtyRect) {
PaintFlags flags;
std::unique_ptr<Canvas2DLayerBridge> bridge =
MakeBridge(IntSize(500, 500), Canvas2DLayerBridge::kEnableAcceleration,
CanvasColorParams());
bridge->FinalizeFrame();
base::HistogramTester histogram_tester;
cc::PaintCanvas* canvas = bridge->DrawingCanvas();
PaintRecorder recorder;
recorder.beginRecording(SkRect::MakeWH(50, 50))
->drawRect(SkRect::MakeWH(50, 50), flags);
canvas->drawPicture(recorder.finishRecordingAsPicture());
DrawSomething(bridge.get());
// Dirty region: 52x52 &
// canvas size: 502x502, dirty percentage: (52x52)/(502x502) = 1.
histogram_tester.ExpectTotalCount("Canvas.Repaint.Region.Percentage", 1);
histogram_tester.ExpectUniqueSample("Canvas.Repaint.Region.Percentage", 1, 1);
histogram_tester.ExpectTotalCount("Canvas.Repaint.Bounds.Percentage", 1);
histogram_tester.ExpectUniqueSample("Canvas.Repaint.Bounds.Percentage", 1, 1);
}
} // namespace blink

@ -20939,6 +20939,28 @@ uploading your change for review.
</summary>
</histogram>
<histogram name="Canvas.Repaint.Bounds.Percentage" units="%"
expires_after="M82">
<owner>yiyix@google.com</owner>
<owner>fserb@google.com</owner>
<summary>
measure the percentage of Canvas is dirty before each repaint by using
cc::Rect. Note that we record this metrics for all canvas that has an area
of more than 65k pixel^2 (256x256).
</summary>
</histogram>
<histogram name="Canvas.Repaint.Region.Percentage" units="%"
expires_after="M82">
<owner>yiyix@google.com</owner>
<owner>fserb@google.com</owner>
<summary>
measure the percentage of Canvas is dirty before each repaint by using
cc::Region. Note that we record this metrics for all canvas that has an area
of more than 65k pixel^2 (256x256).
</summary>
</histogram>
<histogram name="Canvas.RequestedImageMimeTypes" enum="RequestedImageMimeType"
expires_after="2018-11-01">
<obsolete>