1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/platform/graphics/paint/raster_invalidation_tracking.h"
6
7 #include "cc/layers/layer.h"
8 #include "third_party/blink/renderer/platform/geometry/geometry_as_json.h"
9 #include "third_party/blink/renderer/platform/geometry/layout_rect.h"
10 #include "third_party/blink/renderer/platform/graphics/color.h"
11 #include "third_party/blink/renderer/platform/graphics/paint/paint_canvas.h"
12 #include "third_party/blink/renderer/platform/graphics/paint/paint_recorder.h"
13 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
14 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
15 #include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
16 #include "third_party/skia/include/core/SkImageFilter.h"
17
18 namespace blink {
19
20 static bool g_simulate_raster_under_invalidations = false;
21
SimulateRasterUnderInvalidations(bool enable)22 void RasterInvalidationTracking::SimulateRasterUnderInvalidations(bool enable) {
23 g_simulate_raster_under_invalidations = enable;
24 }
25
ShouldAlwaysTrack()26 bool RasterInvalidationTracking::ShouldAlwaysTrack() {
27 return RuntimeEnabledFeatures::PaintUnderInvalidationCheckingEnabled() ||
28 IsTracingRasterInvalidations();
29 }
30
IsTracingRasterInvalidations()31 bool RasterInvalidationTracking::IsTracingRasterInvalidations() {
32 bool tracing_enabled;
33 TRACE_EVENT_CATEGORY_GROUP_ENABLED(
34 TRACE_DISABLED_BY_DEFAULT("blink.invalidation"), &tracing_enabled);
35 return tracing_enabled;
36 }
37
AddInvalidation(const DisplayItemClient * client,const String & debug_name,const IntRect & rect,PaintInvalidationReason reason)38 void RasterInvalidationTracking::AddInvalidation(
39 const DisplayItemClient* client,
40 const String& debug_name,
41 const IntRect& rect,
42 PaintInvalidationReason reason) {
43 if (rect.IsEmpty())
44 return;
45
46 RasterInvalidationInfo info;
47 info.client = client;
48 info.client_debug_name = debug_name;
49 info.rect = rect;
50 info.reason = reason;
51 invalidations_.push_back(info);
52
53 // TODO(crbug.com/496260): Some antialiasing effects overflow the paint
54 // invalidation rect.
55 IntRect r = rect;
56 r.Inflate(1);
57 invalidation_region_since_last_paint_.Unite(r);
58 }
59
CompareRasterInvalidationInfo(const RasterInvalidationInfo & a,const RasterInvalidationInfo & b)60 static bool CompareRasterInvalidationInfo(const RasterInvalidationInfo& a,
61 const RasterInvalidationInfo& b) {
62 // Sort by rect first, bigger rects before smaller ones.
63 if (a.rect.Width() != b.rect.Width())
64 return a.rect.Width() > b.rect.Width();
65 if (a.rect.Height() != b.rect.Height())
66 return a.rect.Height() > b.rect.Height();
67 if (a.rect.X() != b.rect.X())
68 return a.rect.X() > b.rect.X();
69 if (a.rect.Y() != b.rect.Y())
70 return a.rect.Y() > b.rect.Y();
71
72 // Then compare clientDebugName, in alphabetic order.
73 int name_compare_result =
74 CodeUnitCompare(a.client_debug_name, b.client_debug_name);
75 if (name_compare_result != 0)
76 return name_compare_result < 0;
77
78 return a.reason < b.reason;
79 }
80
AsJSON(JSONObject * json,bool detailed) const81 void RasterInvalidationTracking::AsJSON(JSONObject* json, bool detailed) const {
82 if (!invalidations_.IsEmpty()) {
83 // Sort to make the output more readable and easier to see the differences
84 // by a human.
85 auto sorted = invalidations_;
86 std::sort(sorted.begin(), sorted.end(), &CompareRasterInvalidationInfo);
87 auto invalidations_json = std::make_unique<JSONArray>();
88 IntRect last_rect;
89 for (auto* it = sorted.begin(); it != sorted.end(); it++) {
90 const auto& info = *it;
91 if (detailed) {
92 auto info_json = std::make_unique<JSONObject>();
93 info_json->SetArray("rect", RectAsJSONArray(info.rect));
94 info_json->SetString("object", info.client_debug_name);
95 info_json->SetString("reason",
96 PaintInvalidationReasonToString(info.reason));
97 invalidations_json->PushObject(std::move(info_json));
98 } else if (std::none_of(sorted.begin(), it, [&info](auto& previous) {
99 return previous.rect.Contains(info.rect);
100 })) {
101 invalidations_json->PushArray(RectAsJSONArray(info.rect));
102 last_rect = info.rect;
103 }
104 }
105 json->SetArray("invalidations", std::move(invalidations_json));
106 }
107
108 if (!under_invalidations_.IsEmpty()) {
109 auto under_invalidations_json = std::make_unique<JSONArray>();
110 for (auto& under_invalidation : under_invalidations_) {
111 auto under_invalidation_json = std::make_unique<JSONObject>();
112 under_invalidation_json->SetDouble("x", under_invalidation.x);
113 under_invalidation_json->SetDouble("y", under_invalidation.y);
114 under_invalidation_json->SetString(
115 "oldPixel",
116 Color(under_invalidation.old_pixel).NameForLayoutTreeAsText());
117 under_invalidation_json->SetString(
118 "newPixel",
119 Color(under_invalidation.new_pixel).NameForLayoutTreeAsText());
120 under_invalidations_json->PushObject(std::move(under_invalidation_json));
121 }
122 json->SetArray("underInvalidations", std::move(under_invalidations_json));
123 }
124 }
125
AddToLayerDebugInfo(cc::LayerDebugInfo & debug_info) const126 void RasterInvalidationTracking::AddToLayerDebugInfo(
127 cc::LayerDebugInfo& debug_info) const {
128 // This is not sorted because the output is for client programs, and the
129 // invalidations may be accumulated in debug_info.
130 for (auto& info : invalidations_) {
131 if (info.rect.IsEmpty())
132 continue;
133 debug_info.invalidations.push_back(
134 {gfx::Rect(info.rect), PaintInvalidationReasonToString(info.reason),
135 info.client_debug_name.Utf8()});
136 }
137 }
138
PixelComponentsDiffer(int c1,int c2)139 static bool PixelComponentsDiffer(int c1, int c2) {
140 // Compare strictly for saturated values.
141 if (c1 == 0 || c1 == 255 || c2 == 0 || c2 == 255)
142 return c1 != c2;
143 // Tolerate invisible differences that may occur in gradients etc.
144 return abs(c1 - c2) > 2;
145 }
146
PixelsDiffer(SkColor p1,SkColor p2)147 static bool PixelsDiffer(SkColor p1, SkColor p2) {
148 return PixelComponentsDiffer(SkColorGetA(p1), SkColorGetA(p2)) ||
149 PixelComponentsDiffer(SkColorGetR(p1), SkColorGetR(p2)) ||
150 PixelComponentsDiffer(SkColorGetG(p1), SkColorGetG(p2)) ||
151 PixelComponentsDiffer(SkColorGetB(p1), SkColorGetB(p2));
152 }
153
CheckUnderInvalidations(const String & layer_debug_name,sk_sp<PaintRecord> new_record,const IntRect & new_interest_rect)154 void RasterInvalidationTracking::CheckUnderInvalidations(
155 const String& layer_debug_name,
156 sk_sp<PaintRecord> new_record,
157 const IntRect& new_interest_rect) {
158 auto old_interest_rect = last_interest_rect_;
159 Region invalidation_region;
160 if (!g_simulate_raster_under_invalidations)
161 invalidation_region = invalidation_region_since_last_paint_;
162 auto old_record = std::move(last_painted_record_);
163
164 last_painted_record_ = new_record;
165 last_interest_rect_ = new_interest_rect;
166 invalidation_region_since_last_paint_ = Region();
167
168 if (!old_record)
169 return;
170
171 IntRect rect = Intersection(old_interest_rect, new_interest_rect);
172 // Avoid too big area as the following code is slow.
173 rect.Intersect(IntRect(rect.X(), rect.Y(), 1200, 6000));
174 if (rect.IsEmpty())
175 return;
176
177 SkBitmap old_bitmap;
178 old_bitmap.allocPixels(
179 SkImageInfo::MakeN32Premul(rect.Width(), rect.Height()));
180 {
181 SkiaPaintCanvas canvas(old_bitmap);
182 canvas.clear(SK_ColorTRANSPARENT);
183 canvas.translate(-rect.X(), -rect.Y());
184 canvas.drawPicture(std::move(old_record));
185 }
186
187 SkBitmap new_bitmap;
188 new_bitmap.allocPixels(
189 SkImageInfo::MakeN32Premul(rect.Width(), rect.Height()));
190 {
191 SkiaPaintCanvas canvas(new_bitmap);
192 canvas.clear(SK_ColorTRANSPARENT);
193 canvas.translate(-rect.X(), -rect.Y());
194 canvas.drawPicture(std::move(new_record));
195 }
196
197 int mismatching_pixels = 0;
198 static const int kMaxMismatchesToReport = 50;
199 for (int bitmap_y = 0; bitmap_y < rect.Height(); ++bitmap_y) {
200 int layer_y = bitmap_y + rect.Y();
201 for (int bitmap_x = 0; bitmap_x < rect.Width(); ++bitmap_x) {
202 int layer_x = bitmap_x + rect.X();
203 SkColor old_pixel = old_bitmap.getColor(bitmap_x, bitmap_y);
204 SkColor new_pixel = new_bitmap.getColor(bitmap_x, bitmap_y);
205 if (PixelsDiffer(old_pixel, new_pixel) &&
206 !invalidation_region.Contains(IntPoint(layer_x, layer_y))) {
207 if (mismatching_pixels < kMaxMismatchesToReport) {
208 RasterUnderInvalidation under_invalidation = {layer_x, layer_y,
209 old_pixel, new_pixel};
210 under_invalidations_.push_back(under_invalidation);
211 LOG(ERROR) << layer_debug_name
212 << " Uninvalidated old/new pixels mismatch at " << layer_x
213 << "," << layer_y << " old:" << std::hex << old_pixel
214 << " new:" << new_pixel;
215 } else if (mismatching_pixels == kMaxMismatchesToReport) {
216 LOG(ERROR) << "and more...";
217 }
218 ++mismatching_pixels;
219 *new_bitmap.getAddr32(bitmap_x, bitmap_y) =
220 SkColorSetARGB(0xFF, 0xA0, 0, 0); // Dark red.
221 } else {
222 *new_bitmap.getAddr32(bitmap_x, bitmap_y) = SK_ColorTRANSPARENT;
223 }
224 }
225 }
226
227 if (!mismatching_pixels)
228 return;
229
230 PaintRecorder recorder;
231 recorder.beginRecording(rect);
232 auto* canvas = recorder.getRecordingCanvas();
233 if (under_invalidation_record_)
234 canvas->drawPicture(std::move(under_invalidation_record_));
235 canvas->drawImage(cc::PaintImage::CreateFromBitmap(std::move(new_bitmap)),
236 rect.X(), rect.Y());
237 under_invalidation_record_ = recorder.finishRecordingAsPicture();
238 }
239
240 } // namespace blink
241