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