| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "platform/graphics/paint/RasterInvalidationTracking.h" |
| |
| #include "SkImageFilter.h" |
| #include "platform/geometry/GeometryAsJSON.h" |
| #include "platform/geometry/LayoutRect.h" |
| #include "platform/graphics/Color.h" |
| #include "platform/graphics/paint/PaintCanvas.h" |
| #include "platform/graphics/paint/PaintRecorder.h" |
| |
| namespace blink { |
| |
| static bool g_simulate_raster_under_invalidations = false; |
| |
| void RasterInvalidationTracking::SimulateRasterUnderInvalidations(bool enable) { |
| g_simulate_raster_under_invalidations = enable; |
| } |
| |
| void RasterInvalidationTracking::AddInvalidation( |
| const DisplayItemClient* client, |
| const String& debug_name, |
| const IntRect& rect, |
| PaintInvalidationReason reason) { |
| if (rect.IsEmpty()) |
| return; |
| |
| RasterInvalidationInfo info; |
| info.client = client; |
| info.client_debug_name = debug_name; |
| info.rect = rect; |
| info.reason = reason; |
| invalidations_.push_back(info); |
| |
| // TODO(crbug.com/496260): Some antialiasing effects overflow the paint |
| // invalidation rect. |
| IntRect r = rect; |
| r.Inflate(1); |
| invalidation_region_since_last_paint_.Unite(r); |
| } |
| |
| static bool CompareRasterInvalidationInfo(const RasterInvalidationInfo& a, |
| const RasterInvalidationInfo& b) { |
| // Sort by rect first, bigger rects before smaller ones. |
| if (a.rect.Width() != b.rect.Width()) |
| return a.rect.Width() > b.rect.Width(); |
| if (a.rect.Height() != b.rect.Height()) |
| return a.rect.Height() > b.rect.Height(); |
| if (a.rect.X() != b.rect.X()) |
| return a.rect.X() > b.rect.X(); |
| if (a.rect.Y() != b.rect.Y()) |
| return a.rect.Y() > b.rect.Y(); |
| |
| // Then compare clientDebugName, in alphabetic order. |
| int name_compare_result = |
| CodePointCompare(a.client_debug_name, b.client_debug_name); |
| if (name_compare_result != 0) |
| return name_compare_result < 0; |
| |
| return a.reason < b.reason; |
| } |
| |
| void RasterInvalidationTracking::AsJSON(JSONObject* json) { |
| if (!invalidations_.IsEmpty()) { |
| std::sort(invalidations_.begin(), invalidations_.end(), |
| &CompareRasterInvalidationInfo); |
| std::unique_ptr<JSONArray> paint_invalidations_json = JSONArray::Create(); |
| for (auto& info : invalidations_) { |
| std::unique_ptr<JSONObject> info_json = JSONObject::Create(); |
| info_json->SetString("object", info.client_debug_name); |
| if (!info.rect.IsEmpty()) { |
| if (info.rect == LayoutRect::InfiniteIntRect()) |
| info_json->SetString("rect", "infinite"); |
| else |
| info_json->SetArray("rect", RectAsJSONArray(info.rect)); |
| } |
| info_json->SetString("reason", |
| PaintInvalidationReasonToString(info.reason)); |
| paint_invalidations_json->PushObject(std::move(info_json)); |
| } |
| json->SetArray("paintInvalidations", std::move(paint_invalidations_json)); |
| } |
| |
| if (!under_invalidations_.IsEmpty()) { |
| std::unique_ptr<JSONArray> under_paint_invalidations_json = |
| JSONArray::Create(); |
| for (auto& under_paint_invalidation : under_invalidations_) { |
| std::unique_ptr<JSONObject> under_paint_invalidation_json = |
| JSONObject::Create(); |
| under_paint_invalidation_json->SetDouble("x", under_paint_invalidation.x); |
| under_paint_invalidation_json->SetDouble("y", under_paint_invalidation.y); |
| under_paint_invalidation_json->SetString( |
| "oldPixel", |
| Color(under_paint_invalidation.old_pixel).NameForLayoutTreeAsText()); |
| under_paint_invalidation_json->SetString( |
| "newPixel", |
| Color(under_paint_invalidation.new_pixel).NameForLayoutTreeAsText()); |
| under_paint_invalidations_json->PushObject( |
| std::move(under_paint_invalidation_json)); |
| } |
| json->SetArray("underPaintInvalidations", |
| std::move(under_paint_invalidations_json)); |
| } |
| } |
| |
| static bool PixelComponentsDiffer(int c1, int c2) { |
| // Compare strictly for saturated values. |
| if (c1 == 0 || c1 == 255 || c2 == 0 || c2 == 255) |
| return c1 != c2; |
| // Tolerate invisible differences that may occur in gradients etc. |
| return abs(c1 - c2) > 2; |
| } |
| |
| static bool PixelsDiffer(SkColor p1, SkColor p2) { |
| return PixelComponentsDiffer(SkColorGetA(p1), SkColorGetA(p2)) || |
| PixelComponentsDiffer(SkColorGetR(p1), SkColorGetR(p2)) || |
| PixelComponentsDiffer(SkColorGetG(p1), SkColorGetG(p2)) || |
| PixelComponentsDiffer(SkColorGetB(p1), SkColorGetB(p2)); |
| } |
| |
| void RasterInvalidationTracking::CheckUnderInvalidations( |
| const String& layer_debug_name, |
| sk_sp<PaintRecord> new_record, |
| const IntRect& new_interest_rect) { |
| auto old_interest_rect = last_interest_rect_; |
| Region invalidation_region; |
| if (!g_simulate_raster_under_invalidations) |
| invalidation_region = invalidation_region_since_last_paint_; |
| auto old_record = std::move(last_painted_record_); |
| |
| last_painted_record_ = new_record; |
| last_interest_rect_ = new_interest_rect; |
| invalidation_region_since_last_paint_ = Region(); |
| |
| if (!old_record) |
| return; |
| |
| IntRect rect = Intersection(old_interest_rect, new_interest_rect); |
| // Avoid too big area as the following code is slow. |
| rect.Intersect(IntRect(rect.X(), rect.Y(), 1200, 6000)); |
| if (rect.IsEmpty()) |
| return; |
| |
| SkBitmap old_bitmap; |
| old_bitmap.allocPixels( |
| SkImageInfo::MakeN32Premul(rect.Width(), rect.Height())); |
| { |
| SkiaPaintCanvas canvas(old_bitmap); |
| canvas.clear(SK_ColorTRANSPARENT); |
| canvas.translate(-rect.X(), -rect.Y()); |
| canvas.drawPicture(std::move(old_record)); |
| } |
| |
| SkBitmap new_bitmap; |
| new_bitmap.allocPixels( |
| SkImageInfo::MakeN32Premul(rect.Width(), rect.Height())); |
| { |
| SkiaPaintCanvas canvas(new_bitmap); |
| canvas.clear(SK_ColorTRANSPARENT); |
| canvas.translate(-rect.X(), -rect.Y()); |
| canvas.drawPicture(std::move(new_record)); |
| } |
| |
| int mismatching_pixels = 0; |
| static const int kMaxMismatchesToReport = 50; |
| for (int bitmap_y = 0; bitmap_y < rect.Height(); ++bitmap_y) { |
| int layer_y = bitmap_y + rect.Y(); |
| for (int bitmap_x = 0; bitmap_x < rect.Width(); ++bitmap_x) { |
| int layer_x = bitmap_x + rect.X(); |
| SkColor old_pixel = old_bitmap.getColor(bitmap_x, bitmap_y); |
| SkColor new_pixel = new_bitmap.getColor(bitmap_x, bitmap_y); |
| if (PixelsDiffer(old_pixel, new_pixel) && |
| !invalidation_region.Contains(IntPoint(layer_x, layer_y))) { |
| if (mismatching_pixels < kMaxMismatchesToReport) { |
| RasterUnderInvalidation under_invalidation = {layer_x, layer_y, |
| old_pixel, new_pixel}; |
| under_invalidations_.push_back(under_invalidation); |
| LOG(ERROR) << layer_debug_name |
| << " Uninvalidated old/new pixels mismatch at " << layer_x |
| << "," << layer_y << " old:" << std::hex << old_pixel |
| << " new:" << new_pixel; |
| } else if (mismatching_pixels == kMaxMismatchesToReport) { |
| LOG(ERROR) << "and more..."; |
| } |
| ++mismatching_pixels; |
| *new_bitmap.getAddr32(bitmap_x, bitmap_y) = |
| SkColorSetARGB(0xFF, 0xA0, 0, 0); // Dark red. |
| } else { |
| *new_bitmap.getAddr32(bitmap_x, bitmap_y) = SK_ColorTRANSPARENT; |
| } |
| } |
| } |
| |
| if (!mismatching_pixels) |
| return; |
| |
| PaintRecorder recorder; |
| recorder.beginRecording(rect); |
| auto* canvas = recorder.getRecordingCanvas(); |
| if (under_invalidation_record_) |
| canvas->drawPicture(std::move(under_invalidation_record_)); |
| canvas->drawBitmap(new_bitmap, rect.X(), rect.Y()); |
| under_invalidation_record_ = recorder.finishRecordingAsPicture(); |
| } |
| |
| } // namespace blink |