| // Copyright 2013 The Flutter 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 "flutter/flow/layers/performance_overlay_layer.h" | |
| #include <iomanip> | |
| #include <iostream> | |
| #include <memory> | |
| #include <string> | |
| #include "flow/stopwatch.h" | |
| #include "flow/stopwatch_dl.h" | |
| #include "flow/stopwatch_sk.h" | |
| #include "third_party/skia/include/core/SkFont.h" | |
| #include "third_party/skia/include/core/SkFontMgr.h" | |
| #include "third_party/skia/include/core/SkTextBlob.h" | |
| #include "third_party/skia/include/core/SkTypeface.h" | |
| #include "txt/platform.h" | |
| #ifdef IMPELLER_SUPPORTS_RENDERING | |
| #include "impeller/typographer/backends/skia/text_frame_skia.h" // nogncheck | |
| #endif // IMPELLER_SUPPORTS_RENDERING | |
| namespace flutter { | |
| namespace { | |
| void VisualizeStopWatch(DlCanvas* canvas, | |
| const bool impeller_enabled, | |
| const Stopwatch& stopwatch, | |
| SkScalar x, | |
| SkScalar y, | |
| SkScalar width, | |
| SkScalar height, | |
| bool show_graph, | |
| bool show_labels, | |
| const std::string& label_prefix, | |
| const std::string& font_path) { | |
| const int label_x = 8; // distance from x | |
| const int label_y = -10; // distance from y+height | |
| if (show_graph) { | |
| SkRect visualization_rect = SkRect::MakeXYWH(x, y, width, height); | |
| std::unique_ptr<StopwatchVisualizer> visualizer; | |
| if (impeller_enabled) { | |
| visualizer = std::make_unique<DlStopwatchVisualizer>(stopwatch); | |
| } else { | |
| visualizer = std::make_unique<SkStopwatchVisualizer>(stopwatch); | |
| } | |
| visualizer->Visualize(canvas, visualization_rect); | |
| } | |
| if (show_labels) { | |
| auto text = PerformanceOverlayLayer::MakeStatisticsText( | |
| stopwatch, label_prefix, font_path); | |
| // Historically SK_ColorGRAY (== 0xFF888888) was used here | |
| DlPaint paint(DlColor(0xFF888888)); | |
| #ifdef IMPELLER_SUPPORTS_RENDERING | |
| if (impeller_enabled) { | |
| canvas->DrawTextFrame(impeller::MakeTextFrameFromTextBlobSkia(text), | |
| x + label_x, y + height + label_y, paint); | |
| return; | |
| } | |
| #endif // IMPELLER_SUPPORTS_RENDERING | |
| canvas->DrawTextBlob(text, x + label_x, y + height + label_y, paint); | |
| } | |
| } | |
| } // namespace | |
| sk_sp<SkTextBlob> PerformanceOverlayLayer::MakeStatisticsText( | |
| const Stopwatch& stopwatch, | |
| const std::string& label_prefix, | |
| const std::string& font_path) { | |
| SkFont font; | |
| sk_sp<SkFontMgr> font_mgr = txt::GetDefaultFontManager(); | |
| if (font_path == "") { | |
| if (sk_sp<SkTypeface> face = font_mgr->matchFamilyStyle(nullptr, {})) { | |
| font = SkFont(face, 15); | |
| } else { | |
| // In Skia's Android fontmgr, matchFamilyStyle can return null instead | |
| // of falling back to a default typeface. If that's the case, we can use | |
| // legacyMakeTypeface, which *does* use that default typeface. | |
| font = SkFont(font_mgr->legacyMakeTypeface(nullptr, {}), 15); | |
| } | |
| } else { | |
| font = SkFont(font_mgr->makeFromFile(font_path.c_str()), 15); | |
| } | |
| // Make sure there's not an empty typeface returned, or we won't see any text. | |
| FML_DCHECK(font.getTypeface()->countGlyphs() > 0); | |
| double max_ms_per_frame = stopwatch.MaxDelta().ToMillisecondsF(); | |
| double average_ms_per_frame = stopwatch.AverageDelta().ToMillisecondsF(); | |
| std::stringstream stream; | |
| stream.setf(std::ios::fixed | std::ios::showpoint); | |
| stream << std::setprecision(1); | |
| stream << label_prefix << " " << "max " << max_ms_per_frame << " ms/frame, " | |
| << "avg " << average_ms_per_frame << " ms/frame"; | |
| auto text = stream.str(); | |
| return SkTextBlob::MakeFromText(text.c_str(), text.size(), font, | |
| SkTextEncoding::kUTF8); | |
| } | |
| PerformanceOverlayLayer::PerformanceOverlayLayer(uint64_t options, | |
| const char* font_path) | |
| : options_(options) { | |
| if (font_path != nullptr) { | |
| font_path_ = font_path; | |
| } | |
| } | |
| void PerformanceOverlayLayer::Diff(DiffContext* context, | |
| const Layer* old_layer) { | |
| DiffContext::AutoSubtreeRestore subtree(context); | |
| if (!context->IsSubtreeDirty()) { | |
| FML_DCHECK(old_layer); | |
| auto prev = old_layer->as_performance_overlay_layer(); | |
| context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(prev)); | |
| } | |
| context->AddLayerBounds(paint_bounds()); | |
| context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); | |
| } | |
| void PerformanceOverlayLayer::Paint(PaintContext& context) const { | |
| const int padding = 8; | |
| if (!options_) { | |
| return; | |
| } | |
| SkScalar x = paint_bounds().x() + padding; | |
| SkScalar y = paint_bounds().y() + padding; | |
| SkScalar width = paint_bounds().width() - (padding * 2); | |
| SkScalar height = paint_bounds().height() / 2; | |
| auto mutator = context.state_stack.save(); | |
| VisualizeStopWatch( | |
| context.canvas, context.impeller_enabled, context.raster_time, x, y, | |
| width, height - padding, options_ & kVisualizeRasterizerStatistics, | |
| options_ & kDisplayRasterizerStatistics, "Raster", font_path_); | |
| VisualizeStopWatch(context.canvas, context.impeller_enabled, context.ui_time, | |
| x, y + height, width, height - padding, | |
| options_ & kVisualizeEngineStatistics, | |
| options_ & kDisplayEngineStatistics, "UI", font_path_); | |
| } | |
| } // namespace flutter |