From 8f71d85fd1834798897eab6da3fd0621bd6c7017 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Mon, 28 Aug 2023 17:52:23 -0700 Subject: [PATCH 01/13] Keep flow/stopwatch.cc --- ...opwatch.cc => flow__stopwatch.cc-move-to-flow__stopwatch_sk.cc | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename flow/stopwatch.cc => flow__stopwatch.cc-move-to-flow__stopwatch_sk.cc (100%) diff --git a/flow/stopwatch.cc b/flow__stopwatch.cc-move-to-flow__stopwatch_sk.cc similarity index 100% rename from flow/stopwatch.cc rename to flow__stopwatch.cc-move-to-flow__stopwatch_sk.cc From adad8fa2e2223519adfe263e819edfbc9d94621c Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Mon, 28 Aug 2023 17:52:25 -0700 Subject: [PATCH 02/13] Copy flow/stopwatch.cc into flow/stopwatch_sk.cc --- flow/{stopwatch.cc => stopwatch_sk.cc} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename flow/{stopwatch.cc => stopwatch_sk.cc} (100%) diff --git a/flow/stopwatch.cc b/flow/stopwatch_sk.cc similarity index 100% rename from flow/stopwatch.cc rename to flow/stopwatch_sk.cc From 58e760dc26cfd8687793a7e4cb82a29cda048974 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Mon, 28 Aug 2023 17:52:28 -0700 Subject: [PATCH 03/13] Set back flow/stopwatch.cc file --- .../stopwatch.cc | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename flow__stopwatch.cc-move-to-flow__stopwatch_sk.cc => flow/stopwatch.cc (100%) diff --git a/flow__stopwatch.cc-move-to-flow__stopwatch_sk.cc b/flow/stopwatch.cc similarity index 100% rename from flow__stopwatch.cc-move-to-flow__stopwatch_sk.cc rename to flow/stopwatch.cc From 44b2e674ea6a6ac42d1783a41cb6f2f248a8bc74 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Mon, 28 Aug 2023 17:52:48 -0700 Subject: [PATCH 04/13] Keep flow/stopwatch.h --- .../stopwatch.h => flow__stopwatch.h-move-to-flow__stopwatch_sk.h | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename flow/stopwatch.h => flow__stopwatch.h-move-to-flow__stopwatch_sk.h (100%) diff --git a/flow/stopwatch.h b/flow__stopwatch.h-move-to-flow__stopwatch_sk.h similarity index 100% rename from flow/stopwatch.h rename to flow__stopwatch.h-move-to-flow__stopwatch_sk.h From 46eba671be417bdb074b9944621949fbeca9b1bb Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Mon, 28 Aug 2023 17:52:49 -0700 Subject: [PATCH 05/13] Copy flow/stopwatch.h into flow/stopwatch_sk.h --- flow/{stopwatch.h => stopwatch_sk.h} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename flow/{stopwatch.h => stopwatch_sk.h} (100%) diff --git a/flow/stopwatch.h b/flow/stopwatch_sk.h similarity index 100% rename from flow/stopwatch.h rename to flow/stopwatch_sk.h From 3a5f3923a563ab47648c0b8a06f2f991855c87c7 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Mon, 28 Aug 2023 17:52:52 -0700 Subject: [PATCH 06/13] Set back flow/stopwatch.h file --- .../stopwatch.h | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename flow__stopwatch.h-move-to-flow__stopwatch_sk.h => flow/stopwatch.h (100%) diff --git a/flow__stopwatch.h-move-to-flow__stopwatch_sk.h b/flow/stopwatch.h similarity index 100% rename from flow__stopwatch.h-move-to-flow__stopwatch_sk.h rename to flow/stopwatch.h From d0cc34b464df34469e70f55d9d11b9401a075126 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Mon, 28 Aug 2023 18:34:24 -0700 Subject: [PATCH 07/13] Split Stopwatch into Stopwatch{Visualizer}, and split out SkStopwatchVisualizer. --- ci/licenses_golden/licenses_flutter | 4 + flow/BUILD.gn | 2 + flow/layers/performance_overlay_layer.cc | 7 +- flow/stopwatch.cc | 185 ++--------------------- flow/stopwatch.h | 51 +++++-- flow/stopwatch_sk.cc | 167 ++++++-------------- flow/stopwatch_sk.h | 100 +++--------- 7 files changed, 128 insertions(+), 388 deletions(-) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index ebe4a47eaf704..f017ba19023b2 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -846,6 +846,8 @@ ORIGIN: ../../../flutter/flow/raster_cache_util.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/flow/skia_gpu_object.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/flow/stopwatch.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/flow/stopwatch.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/flow/stopwatch_sk.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/flow/stopwatch_sk.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/flow/surface.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/flow/surface.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/flow/surface_frame.cc + ../../../flutter/LICENSE @@ -3591,6 +3593,8 @@ FILE: ../../../flutter/flow/raster_cache_util.h FILE: ../../../flutter/flow/skia_gpu_object.h FILE: ../../../flutter/flow/stopwatch.cc FILE: ../../../flutter/flow/stopwatch.h +FILE: ../../../flutter/flow/stopwatch_sk.cc +FILE: ../../../flutter/flow/stopwatch_sk.h FILE: ../../../flutter/flow/surface.cc FILE: ../../../flutter/flow/surface.h FILE: ../../../flutter/flow/surface_frame.cc diff --git a/flow/BUILD.gn b/flow/BUILD.gn index f52d3251c45d8..5d69ae2e166fa 100644 --- a/flow/BUILD.gn +++ b/flow/BUILD.gn @@ -77,6 +77,8 @@ source_set("flow") { "skia_gpu_object.h", "stopwatch.cc", "stopwatch.h", + "stopwatch_sk.cc", + "stopwatch_sk.h", "surface.cc", "surface.h", "surface_frame.cc", diff --git a/flow/layers/performance_overlay_layer.cc b/flow/layers/performance_overlay_layer.cc index 2b693b270033c..4275a996fdb35 100644 --- a/flow/layers/performance_overlay_layer.cc +++ b/flow/layers/performance_overlay_layer.cc @@ -8,6 +8,7 @@ #include #include +#include "flow/stopwatch_sk.h" #include "third_party/skia/include/core/SkFont.h" #include "third_party/skia/include/core/SkTextBlob.h" @@ -29,7 +30,11 @@ void VisualizeStopWatch(DlCanvas* canvas, if (show_graph) { SkRect visualization_rect = SkRect::MakeXYWH(x, y, width, height); - stopwatch.Visualize(canvas, visualization_rect); + + // TODO(matanlurey): Select a visualizer based on the current backend. + // https://github.com/flutter/flutter/issues/126009 + SkStopwatchVisualizer visualizer = SkStopwatchVisualizer(stopwatch); + visualizer.Visualize(canvas, visualization_rect); } if (show_labels) { diff --git a/flow/stopwatch.cc b/flow/stopwatch.cc index 4d28f375a4479..fe3166970c64f 100644 --- a/flow/stopwatch.cc +++ b/flow/stopwatch.cc @@ -4,10 +4,6 @@ #include "flutter/flow/stopwatch.h" -#include "include/core/SkCanvas.h" -#include "third_party/skia/include/core/SkPath.h" -#include "third_party/skia/include/core/SkSurface.h" - namespace flutter { static const size_t kMaxSamples = 120; @@ -19,8 +15,6 @@ Stopwatch::Stopwatch(const RefreshRateUpdater& updater) current_sample_(0) { const fml::TimeDelta delta = fml::TimeDelta::Zero(); laps_.resize(kMaxSamples, delta); - cache_dirty_ = true; - prev_drawn_sample_index_ = 0; } Stopwatch::~Stopwatch() = default; @@ -51,12 +45,20 @@ const fml::TimeDelta& Stopwatch::LastLap() const { return laps_[(current_sample_ - 1) % kMaxSamples]; } -double Stopwatch::UnitFrameInterval(double raster_time_ms) const { - return raster_time_ms / GetFrameBudget().count(); +const fml::TimeDelta& Stopwatch::GetLap(size_t index) const { + return laps_[(current_sample_ - index) % kMaxSamples]; +} + +size_t Stopwatch::GetCurrentSample() const { + return current_sample_; +} + +double StopwatchVisualizer::UnitFrameInterval(double raster_time_ms) const { + return raster_time_ms / stopwatch_.GetFrameBudget().count(); } -double Stopwatch::UnitHeight(double raster_time_ms, - double max_unit_interval) const { +double StopwatchVisualizer::UnitHeight(double raster_time_ms, + double max_unit_interval) const { double unit_height = UnitFrameInterval(raster_time_ms) / max_unit_interval; if (unit_height > 1.0) { unit_height = 1.0; @@ -82,169 +84,6 @@ fml::TimeDelta Stopwatch::AverageDelta() const { return sum / kMaxSamples; } -// Initialize the SkSurface for drawing into. Draws the base background and any -// timing data from before the initial Visualize() call. -void Stopwatch::InitVisualizeSurface(SkISize size) const { - // Mark as dirty if the size has changed. - if (visualize_cache_surface_) { - if (size.width() != visualize_cache_surface_->width() || - size.height() != visualize_cache_surface_->height()) { - cache_dirty_ = true; - }; - } - - if (!cache_dirty_) { - return; - } - cache_dirty_ = false; - - // TODO(garyq): Use a GPU surface instead of a CPU surface. - visualize_cache_surface_ = - SkSurfaces::Raster(SkImageInfo::MakeN32Premul(size)); - - SkCanvas* cache_canvas = visualize_cache_surface_->getCanvas(); - - // Establish the graph position. - const SkScalar x = 0; - const SkScalar y = 0; - const SkScalar width = size.width(); - const SkScalar height = size.height(); - - SkPaint paint; - paint.setColor(0x99FFFFFF); - cache_canvas->drawRect(SkRect::MakeXYWH(x, y, width, height), paint); - - // Scale the graph to show frame times up to those that are 3 times the frame - // time. - const double one_frame_ms = GetFrameBudget().count(); - const double max_interval = one_frame_ms * 3.0; - const double max_unit_interval = UnitFrameInterval(max_interval); - - // Draw the old data to initially populate the graph. - // Prepare a path for the data. We start at the height of the last point, so - // it looks like we wrap around - SkPath path; - path.setIsVolatile(true); - path.moveTo(x, height); - path.lineTo(x, y + height * (1.0 - UnitHeight(laps_[0].ToMillisecondsF(), - max_unit_interval))); - double unit_x; - double unit_next_x = 0.0; - for (size_t i = 0; i < kMaxSamples; i += 1) { - unit_x = unit_next_x; - unit_next_x = (static_cast(i + 1) / kMaxSamples); - const double sample_y = - y + height * (1.0 - UnitHeight(laps_[i].ToMillisecondsF(), - max_unit_interval)); - path.lineTo(x + width * unit_x, sample_y); - path.lineTo(x + width * unit_next_x, sample_y); - } - path.lineTo( - width, - y + height * (1.0 - UnitHeight(laps_[kMaxSamples - 1].ToMillisecondsF(), - max_unit_interval))); - path.lineTo(width, height); - path.close(); - - // Draw the graph. - paint.setColor(0xAA0000FF); - cache_canvas->drawPath(path, paint); -} - -void Stopwatch::Visualize(DlCanvas* canvas, const SkRect& rect) const { - // Initialize visualize cache if it has not yet been initialized. - InitVisualizeSurface(SkISize::Make(rect.width(), rect.height())); - - SkCanvas* cache_canvas = visualize_cache_surface_->getCanvas(); - SkPaint paint; - - // Establish the graph position. - const SkScalar x = 0; - const SkScalar y = 0; - const SkScalar width = visualize_cache_surface_->width(); - const SkScalar height = visualize_cache_surface_->height(); - - // Scale the graph to show frame times up to those that are 3 times the frame - // time. - const double one_frame_ms = GetFrameBudget().count(); - const double max_interval = one_frame_ms * 3.0; - const double max_unit_interval = UnitFrameInterval(max_interval); - - const double sample_unit_width = (1.0 / kMaxSamples); - - // Draw vertical replacement bar to erase old/stale pixels. - paint.setColor(0x99FFFFFF); - paint.setStyle(SkPaint::Style::kFill_Style); - paint.setBlendMode(SkBlendMode::kSrc); - double sample_x = - x + width * (static_cast(prev_drawn_sample_index_) / kMaxSamples); - const auto eraser_rect = SkRect::MakeLTRB( - sample_x, y, sample_x + width * sample_unit_width, height); - cache_canvas->drawRect(eraser_rect, paint); - - // Draws blue timing bar for new data. - paint.setColor(0xAA0000FF); - paint.setBlendMode(SkBlendMode::kSrcOver); - const auto bar_rect = SkRect::MakeLTRB( - sample_x, - y + height * (1.0 - - UnitHeight(laps_[current_sample_ == 0 ? kMaxSamples - 1 - : current_sample_ - 1] - .ToMillisecondsF(), - max_unit_interval)), - sample_x + width * sample_unit_width, height); - cache_canvas->drawRect(bar_rect, paint); - - // Draw horizontal frame markers. - paint.setStrokeWidth(0); // hairline - paint.setStyle(SkPaint::Style::kStroke_Style); - paint.setColor(0xCC000000); - - if (max_interval > one_frame_ms) { - // Paint the horizontal markers - size_t frame_marker_count = - static_cast(max_interval / one_frame_ms); - - // Limit the number of markers displayed. After a certain point, the graph - // becomes crowded - if (frame_marker_count > kMaxFrameMarkers) { - frame_marker_count = 1; - } - - for (size_t frame_index = 0; frame_index < frame_marker_count; - frame_index++) { - const double frame_height = - height * (1.0 - (UnitFrameInterval((frame_index + 1) * one_frame_ms) / - max_unit_interval)); - cache_canvas->drawLine(x, y + frame_height, width, y + frame_height, - paint); - } - } - - // Paint the vertical marker for the current frame. - // We paint it over the current frame, not after it, because when we - // paint this we don't yet have all the times for the current frame. - paint.setStyle(SkPaint::Style::kFill_Style); - paint.setBlendMode(SkBlendMode::kSrcOver); - if (UnitFrameInterval(LastLap().ToMillisecondsF()) > 1.0) { - // budget exceeded - paint.setColor(SK_ColorRED); - } else { - // within budget - paint.setColor(SK_ColorGREEN); - } - sample_x = x + width * (static_cast(current_sample_) / kMaxSamples); - const auto marker_rect = SkRect::MakeLTRB( - sample_x, y, sample_x + width * sample_unit_width, height); - cache_canvas->drawRect(marker_rect, paint); - prev_drawn_sample_index_ = current_sample_; - - // Draw the cached surface onto the output canvas. - auto image = DlImage::Make(visualize_cache_surface_->makeImageSnapshot()); - canvas->DrawImage(image, {rect.x(), rect.y()}, - DlImageSampling::kNearestNeighbor); -} - fml::Milliseconds Stopwatch::GetFrameBudget() const { return refresh_rate_updater_.GetFrameBudget(); } diff --git a/flow/stopwatch.h b/flow/stopwatch.h index 005697dd9a164..d6e8f0da7fbcc 100644 --- a/flow/stopwatch.h +++ b/flow/stopwatch.h @@ -12,8 +12,6 @@ #include "flutter/fml/time/time_delta.h" #include "flutter/fml/time/time_point.h" -#include "third_party/skia/include/core/SkSurface.h" - namespace flutter { class Stopwatch { @@ -32,16 +30,16 @@ class Stopwatch { ~Stopwatch(); + const fml::TimeDelta& GetLap(size_t index) const; + + size_t GetCurrentSample() const; + const fml::TimeDelta& LastLap() const; fml::TimeDelta MaxDelta() const; fml::TimeDelta AverageDelta() const; - void InitVisualizeSurface(SkISize size) const; - - void Visualize(DlCanvas* canvas, const SkRect& rect) const; - void Start(); void Stop(); @@ -52,20 +50,11 @@ class Stopwatch { fml::Milliseconds GetFrameBudget() const; private: - inline double UnitFrameInterval(double time_ms) const; - inline double UnitHeight(double time_ms, double max_height) const; - const RefreshRateUpdater& refresh_rate_updater_; fml::TimePoint start_; std::vector laps_; size_t current_sample_; - // Mutable data cache for performance optimization of the graphs. Prevents - // expensive redrawing of old data. - mutable bool cache_dirty_; - mutable sk_sp visualize_cache_surface_; - mutable size_t prev_drawn_sample_index_; - FML_DISALLOW_COPY_AND_ASSIGN(Stopwatch); }; @@ -91,6 +80,38 @@ class FixedRefreshRateStopwatch : public Stopwatch { FixedRefreshRateUpdater fixed_delegate_; }; +//------------------------------------------------------------------------------ +/// @brief Abstract class for visualizing (i.e. drawing) a stopwatch. +/// +/// @note This was originally folded into the |Stopwatch| class, but +/// was separated out to make it easier to change the underlying +/// implementation (which relied directly on Skia, not showing on +/// Impeller: https://github.com/flutter/flutter/issues/126009). +class StopwatchVisualizer { + public: + explicit StopwatchVisualizer(const Stopwatch& stopwatch) + : stopwatch_(stopwatch) {} + + virtual ~StopwatchVisualizer() = default; + + /// @brief Renders the stopwatch as a graph. + /// + /// @param canvas The canvas to draw on. + /// @param[in] rect The rectangle to draw in. + virtual void Visualize(DlCanvas* canvas, const SkRect& rect) const = 0; + + FML_DISALLOW_COPY_AND_ASSIGN(StopwatchVisualizer); + + protected: + /// @brief Converts a raster time to a unit interval. + double UnitFrameInterval(double time_ms) const; + + /// @brief Converts a raster time to a unit height. + double UnitHeight(double time_ms, double max_height) const; + + const Stopwatch& stopwatch_; +}; + } // namespace flutter #endif // FLUTTER_FLOW_INSTRUMENTATION_H_ diff --git a/flow/stopwatch_sk.cc b/flow/stopwatch_sk.cc index 4d28f375a4479..03b070e471a23 100644 --- a/flow/stopwatch_sk.cc +++ b/flow/stopwatch_sk.cc @@ -2,159 +2,90 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "flutter/flow/stopwatch.h" - +#include "flutter/flow/stopwatch_sk.h" #include "include/core/SkCanvas.h" -#include "third_party/skia/include/core/SkPath.h" -#include "third_party/skia/include/core/SkSurface.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkPaint.h" +#include "include/core/SkPath.h" +#include "include/core/SkSize.h" +#include "include/core/SkSurface.h" namespace flutter { static const size_t kMaxSamples = 120; static const size_t kMaxFrameMarkers = 8; -Stopwatch::Stopwatch(const RefreshRateUpdater& updater) - : refresh_rate_updater_(updater), - start_(fml::TimePoint::Now()), - current_sample_(0) { - const fml::TimeDelta delta = fml::TimeDelta::Zero(); - laps_.resize(kMaxSamples, delta); - cache_dirty_ = true; - prev_drawn_sample_index_ = 0; -} - -Stopwatch::~Stopwatch() = default; - -FixedRefreshRateStopwatch::FixedRefreshRateStopwatch( - fml::Milliseconds frame_budget) - : Stopwatch(fixed_delegate_), fixed_delegate_(frame_budget) {} - -FixedRefreshRateUpdater::FixedRefreshRateUpdater( - fml::Milliseconds fixed_frame_budget) - : fixed_frame_budget_(fixed_frame_budget) {} - -void Stopwatch::Start() { - start_ = fml::TimePoint::Now(); - current_sample_ = (current_sample_ + 1) % kMaxSamples; -} - -void Stopwatch::Stop() { - laps_[current_sample_] = fml::TimePoint::Now() - start_; -} - -void Stopwatch::SetLapTime(const fml::TimeDelta& delta) { - current_sample_ = (current_sample_ + 1) % kMaxSamples; - laps_[current_sample_] = delta; -} - -const fml::TimeDelta& Stopwatch::LastLap() const { - return laps_[(current_sample_ - 1) % kMaxSamples]; -} - -double Stopwatch::UnitFrameInterval(double raster_time_ms) const { - return raster_time_ms / GetFrameBudget().count(); -} - -double Stopwatch::UnitHeight(double raster_time_ms, - double max_unit_interval) const { - double unit_height = UnitFrameInterval(raster_time_ms) / max_unit_interval; - if (unit_height > 1.0) { - unit_height = 1.0; - } - return unit_height; -} - -fml::TimeDelta Stopwatch::MaxDelta() const { - fml::TimeDelta max_delta; - for (size_t i = 0; i < kMaxSamples; i++) { - if (laps_[i] > max_delta) { - max_delta = laps_[i]; - } - } - return max_delta; -} - -fml::TimeDelta Stopwatch::AverageDelta() const { - fml::TimeDelta sum; // default to 0 - for (size_t i = 0; i < kMaxSamples; i++) { - sum = sum + laps_[i]; - } - return sum / kMaxSamples; -} - -// Initialize the SkSurface for drawing into. Draws the base background and any -// timing data from before the initial Visualize() call. -void Stopwatch::InitVisualizeSurface(SkISize size) const { +void SkStopwatchVisualizer::InitVisualizeSurface(SkISize size) const { // Mark as dirty if the size has changed. if (visualize_cache_surface_) { - if (size.width() != visualize_cache_surface_->width() || - size.height() != visualize_cache_surface_->height()) { - cache_dirty_ = true; - }; + cache_dirty_ = size.width() != visualize_cache_surface_->width() || + size.height() != visualize_cache_surface_->height(); } - if (!cache_dirty_) { return; } cache_dirty_ = false; - - // TODO(garyq): Use a GPU surface instead of a CPU surface. visualize_cache_surface_ = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(size)); - SkCanvas* cache_canvas = visualize_cache_surface_->getCanvas(); + SkCanvas* canvas = visualize_cache_surface_->getCanvas(); // Establish the graph position. - const SkScalar x = 0; - const SkScalar y = 0; + const SkScalar x = 10.0f; + const SkScalar y = 10.0f; const SkScalar width = size.width(); const SkScalar height = size.height(); SkPaint paint; paint.setColor(0x99FFFFFF); - cache_canvas->drawRect(SkRect::MakeXYWH(x, y, width, height), paint); + canvas->drawRect(SkRect::MakeXYWH(x, y, width, height), paint); - // Scale the graph to show frame times up to those that are 3 times the frame - // time. - const double one_frame_ms = GetFrameBudget().count(); + // Scale the graph to show frame times up to those that are 3x the frame time. + const double one_frame_ms = stopwatch_.GetFrameBudget().count(); const double max_interval = one_frame_ms * 3.0; const double max_unit_interval = UnitFrameInterval(max_interval); // Draw the old data to initially populate the graph. // Prepare a path for the data. We start at the height of the last point, so - // it looks like we wrap around + // it looks like we wrap around. SkPath path; path.setIsVolatile(true); path.moveTo(x, height); - path.lineTo(x, y + height * (1.0 - UnitHeight(laps_[0].ToMillisecondsF(), - max_unit_interval))); + path.lineTo( + x, y + height * (1.0 - UnitHeight(stopwatch_.GetLap(0).ToMillisecondsF(), + max_unit_interval))); + double unit_x; double unit_next_x = 0.0; for (size_t i = 0; i < kMaxSamples; i += 1) { unit_x = unit_next_x; unit_next_x = (static_cast(i + 1) / kMaxSamples); const double sample_y = - y + height * (1.0 - UnitHeight(laps_[i].ToMillisecondsF(), + y + height * (1.0 - UnitHeight(stopwatch_.GetLap(i).ToMillisecondsF(), max_unit_interval)); path.lineTo(x + width * unit_x, sample_y); path.lineTo(x + width * unit_next_x, sample_y); } path.lineTo( width, - y + height * (1.0 - UnitHeight(laps_[kMaxSamples - 1].ToMillisecondsF(), - max_unit_interval))); + y + height * + (1.0 - + UnitHeight(stopwatch_.GetLap(kMaxSamples - 1).ToMillisecondsF(), + max_unit_interval))); path.lineTo(width, height); path.close(); // Draw the graph. paint.setColor(0xAA0000FF); - cache_canvas->drawPath(path, paint); + canvas->drawPath(path, paint); } -void Stopwatch::Visualize(DlCanvas* canvas, const SkRect& rect) const { - // Initialize visualize cache if it has not yet been initialized. - InitVisualizeSurface(SkISize::Make(rect.width(), rect.height())); +void SkStopwatchVisualizer::Visualize(DlCanvas* canvas, + const SkRect& rect) const { + // Initialize the surface if necessary. + InitVisualizeSurface(rect.roundOut().size()); + // Draw the cached surface. SkCanvas* cache_canvas = visualize_cache_surface_->getCanvas(); SkPaint paint; @@ -164,17 +95,15 @@ void Stopwatch::Visualize(DlCanvas* canvas, const SkRect& rect) const { const SkScalar width = visualize_cache_surface_->width(); const SkScalar height = visualize_cache_surface_->height(); - // Scale the graph to show frame times up to those that are 3 times the frame - // time. - const double one_frame_ms = GetFrameBudget().count(); + // Scale the graph to show frame times up to those that are 3x the frame time. + const double one_frame_ms = stopwatch_.GetFrameBudget().count(); const double max_interval = one_frame_ms * 3.0; const double max_unit_interval = UnitFrameInterval(max_interval); - const double sample_unit_width = (1.0 / kMaxSamples); // Draw vertical replacement bar to erase old/stale pixels. paint.setColor(0x99FFFFFF); - paint.setStyle(SkPaint::Style::kFill_Style); + paint.setStyle(SkPaint::kFill_Style); paint.setBlendMode(SkBlendMode::kSrc); double sample_x = x + width * (static_cast(prev_drawn_sample_index_) / kMaxSamples); @@ -187,11 +116,14 @@ void Stopwatch::Visualize(DlCanvas* canvas, const SkRect& rect) const { paint.setBlendMode(SkBlendMode::kSrcOver); const auto bar_rect = SkRect::MakeLTRB( sample_x, - y + height * (1.0 - - UnitHeight(laps_[current_sample_ == 0 ? kMaxSamples - 1 - : current_sample_ - 1] - .ToMillisecondsF(), - max_unit_interval)), + y + height * + (1.0 - + UnitHeight(stopwatch_ + .GetLap(stopwatch_.GetCurrentSample() == 0 + ? kMaxSamples - 1 + : stopwatch_.GetCurrentSample() - 1) + .ToMillisecondsF(), + max_unit_interval)), sample_x + width * sample_unit_width, height); cache_canvas->drawRect(bar_rect, paint); @@ -226,18 +158,19 @@ void Stopwatch::Visualize(DlCanvas* canvas, const SkRect& rect) const { // paint this we don't yet have all the times for the current frame. paint.setStyle(SkPaint::Style::kFill_Style); paint.setBlendMode(SkBlendMode::kSrcOver); - if (UnitFrameInterval(LastLap().ToMillisecondsF()) > 1.0) { + if (UnitFrameInterval(stopwatch_.LastLap().ToMillisecondsF()) > 1.0) { // budget exceeded paint.setColor(SK_ColorRED); } else { // within budget paint.setColor(SK_ColorGREEN); } - sample_x = x + width * (static_cast(current_sample_) / kMaxSamples); + sample_x = x + width * (static_cast(stopwatch_.GetCurrentSample()) / + kMaxSamples); const auto marker_rect = SkRect::MakeLTRB( sample_x, y, sample_x + width * sample_unit_width, height); cache_canvas->drawRect(marker_rect, paint); - prev_drawn_sample_index_ = current_sample_; + prev_drawn_sample_index_ = stopwatch_.GetCurrentSample(); // Draw the cached surface onto the output canvas. auto image = DlImage::Make(visualize_cache_surface_->makeImageSnapshot()); @@ -245,12 +178,4 @@ void Stopwatch::Visualize(DlCanvas* canvas, const SkRect& rect) const { DlImageSampling::kNearestNeighbor); } -fml::Milliseconds Stopwatch::GetFrameBudget() const { - return refresh_rate_updater_.GetFrameBudget(); -} - -fml::Milliseconds FixedRefreshRateUpdater::GetFrameBudget() const { - return fixed_frame_budget_; -} - } // namespace flutter diff --git a/flow/stopwatch_sk.h b/flow/stopwatch_sk.h index 005697dd9a164..c1dfb24307d33 100644 --- a/flow/stopwatch_sk.h +++ b/flow/stopwatch_sk.h @@ -2,95 +2,39 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef FLUTTER_FLOW_INSTRUMENTATION_H_ -#define FLUTTER_FLOW_INSTRUMENTATION_H_ +#ifndef FLUTTER_FLOW_STOPWATCH_SK_H_ +#define FLUTTER_FLOW_STOPWATCH_SK_H_ -#include - -#include "flutter/display_list/dl_canvas.h" -#include "flutter/fml/macros.h" -#include "flutter/fml/time/time_delta.h" -#include "flutter/fml/time/time_point.h" - -#include "third_party/skia/include/core/SkSurface.h" +#include "flow/stopwatch.h" +#include "include/core/SkSurface.h" namespace flutter { -class Stopwatch { +//------------------------------------------------------------------------------ +/// A stopwatch visualizer that uses Skia (|SkCanvas|) to draw the stopwatch. +/// +/// @see DlStopwatchVisualizer for the newer non-backend specific version. +class SkStopwatchVisualizer : public StopwatchVisualizer { public: - /// The refresh rate interface for `Stopwatch`. - class RefreshRateUpdater { - public: - /// Time limit for a smooth frame. - /// See: `DisplayManager::GetMainDisplayRefreshRate`. - virtual fml::Milliseconds GetFrameBudget() const = 0; - }; - - /// The constructor with a updater parameter, it will update frame_budget - /// everytime when `GetFrameBudget()` is called. - explicit Stopwatch(const RefreshRateUpdater& updater); - - ~Stopwatch(); - - const fml::TimeDelta& LastLap() const; - - fml::TimeDelta MaxDelta() const; - - fml::TimeDelta AverageDelta() const; + explicit SkStopwatchVisualizer(const Stopwatch& stopwatch) + : StopwatchVisualizer(stopwatch) {} - void InitVisualizeSurface(SkISize size) const; - - void Visualize(DlCanvas* canvas, const SkRect& rect) const; - - void Start(); - - void Stop(); - - void SetLapTime(const fml::TimeDelta& delta); - - /// All places which want to get frame_budget should call this function. - fml::Milliseconds GetFrameBudget() const; + void Visualize(DlCanvas* canvas, const SkRect& rect) const override; private: - inline double UnitFrameInterval(double time_ms) const; - inline double UnitHeight(double time_ms, double max_height) const; - - const RefreshRateUpdater& refresh_rate_updater_; - fml::TimePoint start_; - std::vector laps_; - size_t current_sample_; + /// Initializes the |SkSurface| used for drawing the stopwatch. + /// + /// Draws the base background and any timing data from before the initial + /// call to |Visualize|. + void InitVisualizeSurface(SkISize size) const; - // Mutable data cache for performance optimization of the graphs. Prevents - // expensive redrawing of old data. - mutable bool cache_dirty_; + // Mutable data cache for performance optimization of the graphs. + // Prevents expensive redrawing of old data. + mutable bool cache_dirty_ = true; mutable sk_sp visualize_cache_surface_; - mutable size_t prev_drawn_sample_index_; - - FML_DISALLOW_COPY_AND_ASSIGN(Stopwatch); -}; - -/// Used for fixed refresh rate query cases. -class FixedRefreshRateUpdater : public Stopwatch::RefreshRateUpdater { - fml::Milliseconds GetFrameBudget() const override; - - public: - explicit FixedRefreshRateUpdater( - fml::Milliseconds fixed_frame_budget = fml::kDefaultFrameBudget); - - private: - fml::Milliseconds fixed_frame_budget_; -}; - -/// Used for fixed refresh rate cases. -class FixedRefreshRateStopwatch : public Stopwatch { - public: - explicit FixedRefreshRateStopwatch( - fml::Milliseconds fixed_frame_budget = fml::kDefaultFrameBudget); - - private: - FixedRefreshRateUpdater fixed_delegate_; + mutable size_t prev_drawn_sample_index_ = 0; }; } // namespace flutter -#endif // FLUTTER_FLOW_INSTRUMENTATION_H_ +#endif // FLUTTER_FLOW_STOPWATCH_SK_H_ From 7fe65e86ba77aba45ccb8f7861dc588d4e80b684 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Mon, 28 Aug 2023 18:45:25 -0700 Subject: [PATCH 08/13] Add a few simple unit tests for new methods. --- flow/stopwatch_unittests.cc | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/flow/stopwatch_unittests.cc b/flow/stopwatch_unittests.cc index 76893c7089333..d83a84d538533 100644 --- a/flow/stopwatch_unittests.cc +++ b/flow/stopwatch_unittests.cc @@ -42,5 +42,20 @@ TEST(Instrumentation, GetFrameBudgetFromUpdaterTest) { EXPECT_EQ(frame_budget_90fps, actual_frame_budget); } +TEST(Instrumentation, GetLapByIndexTest) { + fml::Milliseconds frame_budget_90fps = fml::RefreshRateToFrameBudget(90); + FixedRefreshRateStopwatch stopwatch(frame_budget_90fps); + stopwatch.SetLapTime(fml::TimeDelta::FromMilliseconds(10)); + EXPECT_EQ(stopwatch.GetLap(0), fml::TimeDelta::FromMilliseconds(10)); +} + +TEST(Instrumentation, GetCurrentSampleTest) { + fml::Milliseconds frame_budget_90fps = fml::RefreshRateToFrameBudget(90); + FixedRefreshRateStopwatch stopwatch(frame_budget_90fps); + stopwatch.Start(); + stopwatch.Stop(); + EXPECT_EQ(stopwatch.GetCurrentSample(), size_t(1)); +} + } // namespace testing } // namespace flutter From 85e6ae97aa2c8e27879d6bc380000bdb29c7f3e4 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Mon, 28 Aug 2023 19:17:54 -0700 Subject: [PATCH 09/13] Remove unused const. --- flow/stopwatch.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/flow/stopwatch.cc b/flow/stopwatch.cc index fe3166970c64f..6d250f8358480 100644 --- a/flow/stopwatch.cc +++ b/flow/stopwatch.cc @@ -7,7 +7,6 @@ namespace flutter { static const size_t kMaxSamples = 120; -static const size_t kMaxFrameMarkers = 8; Stopwatch::Stopwatch(const RefreshRateUpdater& updater) : refresh_rate_updater_(updater), From 59db868dea61c72b1940214de94ece56b1b3ae9d Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Tue, 29 Aug 2023 08:43:58 -0700 Subject: [PATCH 10/13] Update stopwatch_sk.cc to re-run goldens. --- flow/stopwatch_sk.cc | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/flow/stopwatch_sk.cc b/flow/stopwatch_sk.cc index 03b070e471a23..16a3411fdf9cc 100644 --- a/flow/stopwatch_sk.cc +++ b/flow/stopwatch_sk.cc @@ -18,43 +18,48 @@ static const size_t kMaxFrameMarkers = 8; void SkStopwatchVisualizer::InitVisualizeSurface(SkISize size) const { // Mark as dirty if the size has changed. if (visualize_cache_surface_) { - cache_dirty_ = size.width() != visualize_cache_surface_->width() || - size.height() != visualize_cache_surface_->height(); + if (size.width() != visualize_cache_surface_->width() || + size.height() != visualize_cache_surface_->height()) { + cache_dirty_ = true; + }; } + if (!cache_dirty_) { return; } cache_dirty_ = false; + + // TODO(garyq): Use a GPU surface instead of a CPU surface. visualize_cache_surface_ = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(size)); - SkCanvas* canvas = visualize_cache_surface_->getCanvas(); + SkCanvas* cache_canvas = visualize_cache_surface_->getCanvas(); // Establish the graph position. - const SkScalar x = 10.0f; - const SkScalar y = 10.0f; + const SkScalar x = 0; + const SkScalar y = 0; const SkScalar width = size.width(); const SkScalar height = size.height(); SkPaint paint; paint.setColor(0x99FFFFFF); - canvas->drawRect(SkRect::MakeXYWH(x, y, width, height), paint); + cache_canvas->drawRect(SkRect::MakeXYWH(x, y, width, height), paint); - // Scale the graph to show frame times up to those that are 3x the frame time. + // Scale the graph to show frame times up to those that are 3 times the frame + // time. const double one_frame_ms = stopwatch_.GetFrameBudget().count(); const double max_interval = one_frame_ms * 3.0; const double max_unit_interval = UnitFrameInterval(max_interval); // Draw the old data to initially populate the graph. // Prepare a path for the data. We start at the height of the last point, so - // it looks like we wrap around. + // it looks like we wrap around SkPath path; path.setIsVolatile(true); path.moveTo(x, height); path.lineTo( x, y + height * (1.0 - UnitHeight(stopwatch_.GetLap(0).ToMillisecondsF(), max_unit_interval))); - double unit_x; double unit_next_x = 0.0; for (size_t i = 0; i < kMaxSamples; i += 1) { @@ -77,15 +82,14 @@ void SkStopwatchVisualizer::InitVisualizeSurface(SkISize size) const { // Draw the graph. paint.setColor(0xAA0000FF); - canvas->drawPath(path, paint); + cache_canvas->drawPath(path, paint); } void SkStopwatchVisualizer::Visualize(DlCanvas* canvas, const SkRect& rect) const { - // Initialize the surface if necessary. - InitVisualizeSurface(rect.roundOut().size()); + // Initialize visualize cache if it has not yet been initialized. + InitVisualizeSurface(SkISize::Make(rect.width(), rect.height())); - // Draw the cached surface. SkCanvas* cache_canvas = visualize_cache_surface_->getCanvas(); SkPaint paint; @@ -95,15 +99,17 @@ void SkStopwatchVisualizer::Visualize(DlCanvas* canvas, const SkScalar width = visualize_cache_surface_->width(); const SkScalar height = visualize_cache_surface_->height(); - // Scale the graph to show frame times up to those that are 3x the frame time. + // Scale the graph to show frame times up to those that are 3 times the frame + // time. const double one_frame_ms = stopwatch_.GetFrameBudget().count(); const double max_interval = one_frame_ms * 3.0; const double max_unit_interval = UnitFrameInterval(max_interval); + const double sample_unit_width = (1.0 / kMaxSamples); // Draw vertical replacement bar to erase old/stale pixels. paint.setColor(0x99FFFFFF); - paint.setStyle(SkPaint::kFill_Style); + paint.setStyle(SkPaint::Style::kFill_Style); paint.setBlendMode(SkBlendMode::kSrc); double sample_x = x + width * (static_cast(prev_drawn_sample_index_) / kMaxSamples); From ae5caaea59f828c766e5b6244d592b0c70f97bef Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Tue, 29 Aug 2023 10:02:24 -0700 Subject: [PATCH 11/13] Fix Stopwatch::GetLap. --- flow/stopwatch.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flow/stopwatch.cc b/flow/stopwatch.cc index 6d250f8358480..b8e8d3007a6cd 100644 --- a/flow/stopwatch.cc +++ b/flow/stopwatch.cc @@ -45,7 +45,7 @@ const fml::TimeDelta& Stopwatch::LastLap() const { } const fml::TimeDelta& Stopwatch::GetLap(size_t index) const { - return laps_[(current_sample_ - index) % kMaxSamples]; + return laps_[index]; } size_t Stopwatch::GetCurrentSample() const { From 688f351593b1cdab3a92cad100f8d112a9d7688a Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Tue, 29 Aug 2023 10:40:58 -0700 Subject: [PATCH 12/13] Fix unit test. --- flow/stopwatch_unittests.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flow/stopwatch_unittests.cc b/flow/stopwatch_unittests.cc index d83a84d538533..3ded5142a9fbb 100644 --- a/flow/stopwatch_unittests.cc +++ b/flow/stopwatch_unittests.cc @@ -46,7 +46,7 @@ TEST(Instrumentation, GetLapByIndexTest) { fml::Milliseconds frame_budget_90fps = fml::RefreshRateToFrameBudget(90); FixedRefreshRateStopwatch stopwatch(frame_budget_90fps); stopwatch.SetLapTime(fml::TimeDelta::FromMilliseconds(10)); - EXPECT_EQ(stopwatch.GetLap(0), fml::TimeDelta::FromMilliseconds(10)); + EXPECT_EQ(stopwatch.GetLap(1), fml::TimeDelta::FromMilliseconds(10)); } TEST(Instrumentation, GetCurrentSampleTest) { From 00347a677e84c21485726745c6768df639775b93 Mon Sep 17 00:00:00 2001 From: Matan Lurey Date: Tue, 29 Aug 2023 15:13:08 -0700 Subject: [PATCH 13/13] Update goldens. --- .../performance_overlay_gold_120fps.png | Bin 16287 -> 16293 bytes .../performance_overlay_gold_60fps.png | Bin 16560 -> 16569 bytes .../performance_overlay_gold_90fps.png | Bin 16293 -> 16298 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/testing/resources/performance_overlay_gold_120fps.png b/testing/resources/performance_overlay_gold_120fps.png index c19d1eb7208e1ee768b0031404592fa26e65ffd5..9677724230ad25a4580c7c0309d6af49b2952ad1 100644 GIT binary patch literal 16293 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�}d#?vw^uxJ9Q7oUVFRllrr zP5ErDOxsr=Kv|u?!Iys>&|lE=UYciD#$^!Hqjx<2Ei?XMB=F$diTw?aZ9&glvzHqs zrE)_n;qCdkZwY3*1|=kvFN$XY7%Ri>FNTlx`P3_hPyk;qcBb~|wUjf6KC|~94?XT$ zli^~awvJD}`&Zm4vvun?TSKBobY|exLH_LdU4hy|&r*U>{6I8e=U!Ygxyl~0MR5EI zrV$+w=S)o*?vvb$?^drWKCQ@LGDU^|NC%8N>Vlf?>h1w=im8ZW#^S>E7G3C(^^E~|lRu{6RjXby$#L*w}&w~43-qcNb6?vmRx)a=v z+o4~r!T;CX{)bN}e+f*Fxbz!R#WN*;xNF^8P;x1mp8)OzWi9#B3Xodu`@a5t+L`rx zsfDShhd3|`_uhUz)pdOWaMR|c0A=NhWF(8d{2!?4+u?@&x*?~hNCmCp`Z?_3F9f;qF_bWXo#+gzbvABe8niN6Qvsqnu1J_jSWiR)+ncb_K`1lN zztV~jP9ubUT}2#wV3PeN{j8{|@poIw29AbjH$^LAW1XISD|L#M1=9Yr(JpkL8UGxG_U#jmI9)Zw0WJs z42-=fy&s2zKfeGL=cduPNvvII%(1#xtL|^W{pJXlS|GmCD$35abI|z-U>7cBr>jEC z&?jI%gAK6lSFmtM3=a@P2N|-oJl;tgAX`%IY1vl+R32{V?%mXdhdY3!eSjap*WZ^W zV2zG$&nKp6&TUbN9UYz6ukv;U^uKp10N6c(PLXkVt7mVNlcesrH1)($4YJTM3`0;Z zLU%K__ZK{H)R4yu=1Q-Feq1G^gPPz8%2DP&K2$z$YQjAOm%IG&jbu0Kgmx{H($F?= zcav%tu_~vL!i*QwszCxcNUD3eD?rzDc%}I4+N0cCH?#jcObGh zokqcN9Af!uE6XZ9Bko?X!Q0I+FxvF!UBxL)VDl4JPz2P*Y+;AN+?k12x z^pDgd2A-dtpATh3HI~~6d4Quzz){SD|)t za$1_t@6ZAoosn(K$368U9S3x*HB=t223xtFs(Hm+5=l2KT1ImIR+ zcjdIXMrL9;lpyRC2hFk0msBNZIx{n)5AbSTZDzA&&>{83Lo*9y^N`5UJ4a&W*bEnV zD5bvTW|gCGr4b+jm)_pXbUNA403bBLC;%4!1ngIoMz@QT=zVgY8FTjArnoU4CxWP! zwfn~u1^A5t8g9OOs}j9orC1waQFt0L$?%E+Wntyg;)$53py4tn=aPdK`nI7P>(3eD zp_7|62^+;Wu*kL)h^_sVzVs+24ss%d<=*o8V=XY~o9jylb-@CY zKZ%eLv#U8jYpOH}zUMLiC%`}gf}6IJ%$X8u7hOn?u(a@l;yDC4U4P$h&l*-?PK0;# z>C-K(eSLkvi_Hdfa|R#H&wvWES`+1&PF3!$Fa50Yue9I2d-oHtemp6_VGWQ`3b=J^ zWOYJWv~CSR;cz{e7?|>zbp{-eRvYP#Ph$76-#iRW4XCx;lKlHi1S3+U~ z-+1^7<4p5U9#spOo_8l$7HT^asdbBuTee(b4VFTEZv~)7FySjOG?<6;DzGHG9uv2H zP$SJ)xzu*>;*Qq1h+|hxlzZS{b78SQec(RNmpgMXX$qi$;jK?#H9Wy&!keG;iwX<1 zY-|UR>+UB6g8T+h29C_(#)H_vU^PN1nJl@KPEGAVfoc~ioOCOyPMlnX?wX zKE@dZT(+X3;l;COngF)no2RYvuJSqO7DiYWK5%%*)G}HIBg?OSRhWO%inGC0@R640 zkK5U)?-w_V2C?Q75yk-}po4_j8RaCXJG1BGZg#{7xIL;zx_270-mcitBj$Uowsv2N z54@eh1IMsJh$<2LXFP4M9|u4R*UHk;`KApozuJ@k0$Nm`b_0=*)`Sy0fjM9a^#xQ&gC3z+(J#fC0c0N@u2`1v&hlu~bIMA`vM?-X#z0TeX~ zsAl&Y1Q_3p0KJL-dnV|{qg<#zf42%^CZDM(vTG@^BNQo9ycz)J3U_icd*cE(Zs2lTJ(R;F++gG0JFc;5@$jcr&j{0k3{w%w{!9KXF^?}|{| zUfe7IJ@n8}7PY^?PZ`y9U`0$$6tt%t+gE4FW(1K?xfUqTDZqMfc7o0g9vu8=p0H6f ztVL_Mwg0GYZbo(lur7uf(z zjVNr9)O!x*OgzEZVUN9S_Tj5ypjqW1n_>{;&T5s@@FVJgjRjJsZXC2;MaHpmV8FB@ zkxI3jZB2w$^2bM)eCxg!jm6V_x_fuGb{|=cyS?86;uzHuy4puP{rdFRF9A6Xi`i6gn=wBCW#86`0=w}41Y|V zFs2BOUBN^&&HovNil?~y9|B!rXJk@R5_*A(h0jWBHZ67Lo{pbVhvK8KU|{vN9`$8S z4u_H!uf{oIT5d4@{>(7yEhS~#R0W>g!|0#ZjD>`;f z{5_GigjRQi?f~l@p%sPn`p+T_rd8Yua&}&y2ELPI8QQU_krZ?e0M3v_OvwT`@u?=T zsj0ccHf&q*`30M9D`Yc8-Rh;lY8CsD+%|6?WbZHl8k1HSU@?6ki(F0B2Y!*;jX(Q# z5qQ!6zQ;#Vfb2FPAi=lixXzVFPl~@l^w;CB2>ca+zasEg1pbP^UlI5#0>6sD%gqpp WWnui`xANj&UpQ-ZrozPqzRz>ccURB4Yu&q=wJ3`O-}iabjm(4}MD@?@Y7a^eo zLYL2y&1xQEN}Xb$%Dn-Y3=#151B%0`23f>-U{`$Fob&SqvSSBCOW zpDw2s+XxAbq-GZscr0HTdboXhz-v{e)KTN?q4!QG?TJih<*ABSmAG92Y~l}5&G++KVgj@u{A4xw8X&`{BTgEkZ(PzZL<`DJ1sQ-~RZ~h^nW06v+v! z;^eM_fBgKcSC!PheftJ|F3LK>OG=s!>wBY1=!`n+3cDh7qehth<8q~asVRjI2X%9^ zvbZ+0e-jcaE&km((j#<(yvEnFDpR$Pfw&$1pYS_q3xTKX9KSC*rsi@M=H<0H`O6^i z2iyU1$+tq+?G{&*{)t^v2j1N;c*BA&0iw#r?4SB(}>jE}_;S zR9b!fa>P-){3kV?r1j9MPSWD7(s#cQS@l+pS5hS(>u3A3`=q`HD(P%=%u{uY}H;+a=xM(P8|K z_hj;LnwZ`PH)q*|=-SOXGlc5HZIRF6XIm}saNmcr}<-jkZ&qVyq8XV+W_IO z6Pd);*2~-xVMy@YbZ1V2R?xiFDc6tBxc*b^9ttN;)E{Oa0GYctQ1TqiBYC*DziQr2 zME2RUG(x(vt6|g9B#{&Z=MUA5jg9f1R6-D|w!2qWYCk$4=n(0>Ar+k!Ve3lG%}xUg zqYvZKjq-O6SYw7u?Qolp%`dxA`@y|iBgGn2h}xK%ii(QxkI#YY+}`IabNq?m;|ZD7A@1UOKXpo=OLZ;F1LxqjdLmgh z8#ix=EY4@>h-MJn#s^ZZ+n~mp>9KgQ!_<#%n6lAT`^x$CQ>v;4k7A`#heBq}8$wtoaG}SH(S#SWM7Cu+Ab@}eyd+h_5agLtI z=={JUGCkd5i#$|Uh4=REmhnUd>!pevy(GiSmk~Z!JFDNX(oNH}0}Hd%BgIx3^>}1& zAs<~@R#q0eu{@Kn1{>C{$jx=_qQI&3H`bRXjT7l3+qpp({P`0t5le4k$=vqV$J0zqvSTjAo{!AL$ZZ89$tctX#Tly$( z*gX?q;^8rqoV8knrm7U?_;n2p zjbo$xg@hED4?51t>^b@&QcO=dxqX{#WaC3os5iN4aTViE?0fH2=H==-+4@jAbeeL? zxjEp0Si?+rezZf#WP*jax3{ytNZH4aALBjyiU-VXZ4taNgJ~J$^2~z>VQ9K;z;vvC znUz|5dM$32&qc9$;cBTJ$K>U;kW?hQUOi-KqFXj{VY%Cs@29Wdc(=F6v^T#yBsoTs zPl?L9Md&bY#&hZTjDWR;r?Dw1jTbIlpxx$deAg|!gB%#jnj#boReIa@kw{Q0JG(I6 zFoKDMpA6ow8&)+-(4xxNIt<(V+R^+*N~!``3rBm*F-kA>l9+oduD`2Ep$}!=^!7;Z z+!>ee%pT^5?tI$%v?tFP&#rFi($Xptc0jH`kc_s+@4o83d-v{n5!wA`e6L@$u&|ix zjbt@6_(ze+xL|{pe%!mv!|{h_T;3eDzt)w=b|pZ>+upmi2V<2Xc=i1Je6Q;uE$AYQ za4%k{xWBs>m?JNrx(yh0dioW82t#`ZoW1aF^bope2vV5q>IM~&C5O<6EtZxUNB7Tn z$IImhz$hD9a-VHFH8qfKVbSuPFsHb#?l6py7GsfAizC^vM(S0I1tBo_zwC z0(bA;0+qK(Y+S-pcf?@a?yFc>+%R`{ucGxr8wWa4(ZP7pmD*50jtpvZvAi{24)vp+ z2!u00l=gtZWF#8yQ`>54YVz_kLT@#WFmD=_6#6s{z^~h_)`2m<^)A&q(u{F$MV)F2~7F?ICW+!#{Fuj^JRf1;B z7S76~Ift*#4;0qb)lHb9HoBRcB>sjYe*wl{g`pNe_!#5KG}cn+xNPJns^ePq4{heh zA@NB`bxu@A#%8`T0`J7tX=!(5Wn^VaGZGV%?GqB@ zz7rm51$m;a&30Q#s(WCQ(}3@gH-2Aye0(f&ZL~3em0n8=u&3ajX2mA8x;UZnybb%n zz`z(5jVQCUEl*7E=m~G)98<}N8c5>r;V(3zR6|x~`MNj8-lywJkH4)8xiN`GN#XdW z{9Qj2GDXp0OvLEVY=nqDzjTLY$dXIz0LI-w$+mL-tca}kN{<;DIF2Xurzg{VQT`}o zlLg0fDA;eI4WGLtxVZFB7#$ zYHT}=2B$rig!}fC+IyOCX?$(kB@x-4zCNE#4xZ0}^w%Tc+z}C0T?i`(x6ojp)0p(B zziMngYNvmVzKE=}>BC$@lt$1@SHjRYGQG^{>Hb>RwnU3o7v(3Zsi`4?<#9)4VjZ>5 zO1H9W7j_V&n<0e)d?3l_HmnbQ-I zP59X6HN07SB4X%TfqA&C?aas|Jj9mMYTQJN_x~Pft=x zrQi6DT`f)n#8@4Vu7|l&QqCKpp&!j;BY!oaxhc`RsnRIxmMndE(DxaQAV#qj7)DakQNaf1%S?RA@{NCMmKjC)Aq zEr>RL%r#U(;3anLdih|(KBh&<2)Z^BA?juUjvCND&wM8vdGFpma}yI2%}6mt`*^3} zx)3dy;E}r&S-a<+5Hj_Vn4VRY?^9RAp(&A?6I>KOmPX_d`IGn_f)7j-)Ojaw z=(#R7+#l#~6wPPO&BMTwT3GqRua2dgp}&2YQ4sJ}{I)UdaIm>9Uk& zKudL2EQ%M_@irC4TL``}y8a&ca2vEZ7+0Ay&fW%I9L#{17lT}_CGf_2a4Y1-vKfkl zMQQoa2Zw$@&;Va2RBI z=D&J%5dLcLL6Q6Q7YF>OjO=0F^mkVc;uNO~OSn~4^++dE&Ggh0$2^E2_?06@wIjv4 zX5R1SjIJ$?PjXi2w!vWWxKn#9A3lv&5sPkmlU3$t-6EG+d`3b~1o&Eog+=<&=chfO ztqFe%HJQRv<0jY+!5?wO#l7=jyS zF-B6obhyfIoEA&z?7RvTivvITeE7-FAeP*B%AN3_bNB9!b=k+F6bdCo3AZ+uGB`Lm z#Y8d@O5h(dHf>?`s_q~m%6|i)_LMJ&`LpI3H8sel-d=-k5=stQdG$y*6GSq8<)gv& z8$*?Hwly`!%_R86X!KfMeW|><`d-JfvGu{#)o<%l)pS-opitLaAD@-v3hJc5nQ*Z9 zOD~UsD1)pzQFvqJ5ul&<=#V;9z{^}aJ*oT7uRSP(+pzv1%ftF z{DGLR#26^yU7pdYEx438U$UabIVj9a-b5uy5SJ*Sq(4E z%b1$7w)dy`ydt#C#X!wZON6hD9+cCs`h! z?IL7jx_dN6Jv_i`AqqsecEYc~@xH;I-_t{}Py9$^wpPz>5gljLGKcZLqYaH(aE8~J z@XfU~_2FRW6$2S$z!)3FXFH0@Mj|<@F>_3&l3k_OfSh9`y=dLmuSC$~-ZD1spy7(; zj~!bqR0q=Q_=9%5_HCD|v}ea`kGm1b=07wrAAnXX1(ekHEQ&1Y&p|vIh~Vv)6(YCA z6%kX}bA_h5*5RdPego^x&8{>#Q{HPAK@>(m()>|JXR^RB!dOsrd#Qc50c-LwN34*t2rh#lk}S0Hw815v7nvG#kkd5 zl%e?bj#e?Xpjq;r>5{kwIudpYF%!K`c79u3U0ook3{L0_b#*DLMZR|TwS_{V9-!gz z7m?+U5tmmJbHDlKyKdllTO;DUCB!ofCni@85z>qHE*3HMs%mhc5wpKN+cS|7pvg95 zxAodll{^oz3B5i3WAD=#0~mEnVkX}v@OVO!hoB456A?6*)z!$wuC7Wli)J7S`Y{ue z*+;ZDZXGEnT3v3pG(ixf_a&r{9zQ<2LFIFvSixY{vt7ghICK?iDXsMV=qfRz>nJG1 zZo<%bMdmpONLTL$D?FK2Wll{Q2u8KLqsR@a7PC&ppEcD%)eM~Nc?Dv!lu`>P?gwf> zCrJNFJ?d$rJWIRisGV|^Eg&m_dVHmcwI=+{H@9#5`@;s4fFyb{P4dT2AkNR+c3G@W zR-qQ$x^=6tw4{XoOOsPFmKu(j$-WT1irkpV?-it!Upw5KX96^bsgBPSm0-u7L-;FX z>gE(frNG=FFK;S-%)Ib`&K^Aw1zi$TJNS8P^8@7hWwu1FdwVc?soT^&(7wiLZKiwYQzY{K%>8YSdyyk=?cJ{x)U^u| zyIH~cAdt%q^hHK1hkS;rft({uDz!%pEzfkv(TD31ZZH`06{vCO+hi3obh-6Q4926o z@2Y!GoanBZn_W#v(}H$WK^8daKUKTC!2DdOFXz&B5?n)LOIc@5+%8a?I?JJRxud1b zit=Qm#Fr0i;J^aP(Gqy=jZaxxnwpx;EB2cMMKesJBXUE1Y6b4(s)!t*1F;8w@fM^o zVEDaOZzh^f35%9$$9FV0v?nty!^;}BeQaw(=!;}->jS51`lUnH?CmAKnOCAS;~);5 zWiGAJ75-Co)}4cz74Bp2?Q_@HDN%WOhaGRFv?M&F38AIS>=GAW zl>tY)laG(j8_=iKqR~qExw*N#yTZ~|kT6fj)1WT!^uT0iXTuvJzs;Nn&U6+0F369^ zGS$@7Vuva`Nn@b7o4x-|{W1_ROM>(*{X|C_oQYeu+@CQ{RXb4tY7d)=TfMgKgtP(& ztPce%Dx<6I+WdGgc#2VhQZ?YE5Vr7+ZF{X?v%M=YP-<61C4dr3`6O&x;3hU=z0 zJy2En-6R`!ys7TG2@=Vbs|}*tY|dkdy45H?PXkK{8nOg>)19MxgT7uOXklUwYP`)e zzR~E*DF#xOOzSD!x41&mB0&qKyGpK3;ngFcDA!?iof{1XIpPYz$YnH&%Zzi_cvSP` z=R+G~5-5#pj(GQ;dt2vj^`ZHToi1+g|+r8G;=RuJ( zX`eZ+s0(1yM&3t^dkcHV0~WdlvXKL^(b{wX?mQ_n?Hfg@C6!ZORkG=|Xi)MQC&bG! z6$Ir~zG<8(V}M1M4AK?=6#cUgQ(sjC+Q2;L$~{vuw+jh{)l*(y7z5J$og%s4qw4BEQ(a-H}#;6GV=&ixT`J2lcj zfme{qck4Bt-X|3I-a>eL!)YCbU%%}?{U5UclQu{x!swjSOJrw%kGW3I>ap$H>7;8C zKyPsU-BITEFPAw>5=AR0r^b)cDHp z{kz@x%?@|a@Aof{PW_6&3b?Ri;FruWB7aIa9W6TdE%*()jQ<}0|Cgp14)_&q8`-d~ zRwhGwRzE+ug`xR&cue+{i15FGByr?RxcKCl#`IJ-NHcFgG(8x~dUey`_95X{fZXqI zr2*lkqN2*>P|F?$3+numcB;$2YHv%GG|A(o&vGD68vLg&#q(sBa{xQ+8Mvpw6AyaG zfQ`(~%ggK3_m&Cz8rhkx({b?FNa*7iaRJYGp~YmKE>_iE(|zYh{|l83%MhFagXI4H zwYDEBL_wZCr=YgHr&{B;Ts9oB!|~d#3=juGxbQE!)0c)995CWPG}hO6{x zFVRWh1tt$t^##CP|BKxH-v`76{z=vrP;XcuI8FY6$D9NnwTm?kk~>=dOZ)b(Gpyh~ z=UaLTY(M514OnIXl}p`gE#4W4Y?`sP->FVm1rrCtaLVqdTept#LpElv^z;T6844Vw zeD23jP&ALT(VahsfB?Kz8I8gPFSh0IH_)j(CWPA!K^}<|}%Lx4`Muoe$lkC!NI z@q?8&G_W*)!0cduwbO15ib~*NCnqPpK^Gm4#^HGLBmW6m*~5t9($@=NjJ^`n%E}63 zM*Y8ltRe0zwrzBlEbUDUcKD6Nz3OV#6a&dOn`GP>*`Cc}1b~fj}?0XDi zRXex_=se1)F_ihIIAwvPK3qpK0E*@f_GcQ}oewLmO5u!OtSQIG87Keflx$Q5qYqwz zFW>ydRT-PD0E(5Lr2X*so<3bAXJ`9I0aSJdpi_P3^hjByXOWsQx|4&6}QewIzd8fPvjA&MOuTdg+9p?@4h1!z zxv{a?VyV4orgnIk54)p~`E!LGncAA2{j1hVv9Ws^pme(EvLzG_hYtg8MCGPhW8COC z_HcaQAMv5)f5CKsr(0C0xD&j9NccqpIyNS!Orh*95a3vG1uG~9$=O+Q;)F^lu<-ma zf`3C#+4|{V`3zgPer+v1y>|;5w~-lZw}}bf)C-o{v*!}P-*>kNyO{$qUeOU(cP#Rx z%ULA_?W7MK^k{g%W4h#~HMoMwk?5|&hrL6R7@0r|LUD#rIx`EShow|iA4c<*l!(07 zFJ8QG2LRbtIoT)-)ZlF!8ynw6M@JK>2xdKVM+WNLId=jmi!VYg)g<0K4SednI)rs6 zM$iT7umsi-iba_lwzY#zsbUc_<4TIPukO(kiPPN(XapTd4c3R79xwPwA4CG7u+aJb zOn`&icryx~{U6M6akfQxVIYV5JMq1>xwa)|Y|*?J zbY^@(8wDOxN?8ch*MAyH3b5r)Ifmj8jvLhn8sXCYg_Sim^@o929}nG0P{IX4)?2ff zfb%45@1@GNe=lo?K*0*@7IZ#zqZTD@G#kj=mkZ~y7M2}1FIc_ywmqYb4E?~HkfU-w zgU2?muFmo&eP--ll|0rMwNro{MWL(_ofLqOl<=ENOF+b$8SqleFD{;627b>To@k9X z6@{mR*qKvUimL)m^UG1t#}j-g8!n!X)K@*5>u8cu>S}ls=(^H|L2K6sM6BW8>E1u) zk|%7+D?y3`;|tW@Vci1v)p_+IK}`1*V~;N_JzY%KlJSwOc_aopu{I$1q1**?02=;N zVf%n!OUx(%YJeecIUm9$G>YAV5K@^yvrlFyM0SD3;LrN=1p_&D-I%?sauwxwHZ0}e=3jEXXuV)13+#Gf- zZ0tRm5C_3=y|go8ORN^7%aV<>>-aoTUG7K()uRK*lY8rMNqySpnH`z@!RdEgi4!(j zHI=L5V_#;3nVA1`M%cDWc>zd-xT2kU4QSLzARZp18#veuW{{|k&vA35{8QmoobfSF zY`{zxUA+bmty_2dV0xqxwaJ2r#%Z(3?i}goTI%i88r9F3FCjeBA$)zZR>=nd(1Y6@ z^QLG3?b#Z{Nqbk#mDUc2j77=yO-)U$3Iqbrr>!n7_>*4?-AR>Q;-9MnB5XQQM?GIL zQVeXkRn2mev#7LPU>@LSa1q&E;z1em#|EyX1CR&g%H$RqRG7WDH@zB2F?YrE&H$2} z6&tJ5_OB?LXcm|;V%kzSG6RX)JNNw!~UVuxyMOLrN_B%5SL`|gOF&f||Tf}S};OZ~LFa&kWq4y0{r0%j@?8z%1mRyHy= zHn!fj(#twfmb@!6J3AH#ot_(mgGUbQi`*cUs(Oz%OC61EdXp)I=z1w?C?GE@%g^$G zjGIpAaKmWuHv-O{n_{&%_W4bJN|%bD0av7Y)8~t&0Z&!|nhsgJ)_<_PBO#ODem~{m zEJnj$?J=Qs?=OdcvXO5m42x)Juo_3kzci56S zI+kwIqQvHu1F|`!$ZK!7zYVSkC~<#Ee5*?~9uM)G`0&JoP4*{BMhtm+daCodR6bW` zez?At=a_j>(8a&J^!0LEhZdouBMO`#vYU;NXYJp~20U08>ZG~c?s1oYoPRAO8Jx9< zwb5lH61jEe(WA{xYfQL>wPjyo5_kk)b8(J0&-jR8m#}E~+%ma(&H&`^87;v`kA82^ ze{uReWeJAsZP zH#kL%uH9CaaAgDKnFsWCd3lakD}8fwlLwXt0E21d6)^yfg4$D7OnSL&{!8BcK+Uiz zB)zj9Mn$?S`uP8Wib+5Fc@in!uadkiP!aGvXGI(?DSXD)uLC-Z0YUv^6fgL~1^;k` z#8{A0gW9t%ctS`f0{nZWVmp-h7C07I!mU;2%!OCbB|YOJ}wy>k2ZHvj>tomNwvTbqdAP16&(@rpB!r3D0X z(?P-9JwskzVKD2W|Aqj7^+;+*a|UvCpjVKfrT6SP3XpyOVRb(f6M{R*xe8GZrkMi( zCWVo!{8pM{Bq8v55IJ2%1wVk6rL@%WEYaomn=-47Z8>kj$d7n*lh5E>&29hq#oh85 zXm42Nr&^D0v&^7Ci#$-U+i3bvX;~yO0Kkhn4}RIzN&m#-#Exc+pdJu>2@o==czy2j z$_nBla&wVR6Nuo~FYW(NE>xb5!(|(Z$WGAMVGO@jGT9~U`t`N9@CvXw88Dv=iaivI zt^&h&qhn2;E#+7y10-!G_JNolDAjKG_`nEd&Me%rA^J1I@*c|Ynwc#$vfspv4mXCY zuAEjU&ZcO=^#dIoV0GEq)j(jB(b(H?Kb)&9-)owQEBHy;v?);`q%5fQsRV=*;^AS- z%ZbyaEHq0(G}X@NNj2QRI;-T5vMZ7tp#Ccec+rLp<}(~h3JNq(i^QLQ{@JJXJ2se3 z-%WDe5ekEO6c*1%Z%a=|tfVC*COY(fpsZZ69!SdsA+ZA_-%0@c52VJp?~Ec6ouyT0 z0~nw!Ixd)KIeK*32>7XG)8t@bfyeQ%wNZSEolWTMHk#_0u0e|ZfTMLt-NKl6QxomS z`uf8KhLG+d+r)i)jv5I7-uJxX*9&$ajmY^i>^fdYXW7SWZZgt_d=OcA;Dc-d4-?g0 z8*b%tm>&4o*KG}ciWHljgD0hFs#eRT6S!SP6^#u2=>sqZXon6%3bU zWGwaM#tk_d>IhOZ!DZ|QZMwm4A?_t0Ox=fiV&7DM0gyw`XdPR_OAR1_8Cea~Moj z_R4Noai#E(WiWCQ5D@Tqu67Lr$WpSANo9%mH6j==F%a~vm>-6~bRY#FX_eQ2@i+r# z4LOn;a~~AP^<#zef?nOwU|A(OSPhJv>?nO6?tv18qaOFfFqmL_VyzSsLpXG7lA=xd zO=^B-xD80KI8fxPC7R@;XWHr?J$f_@_BZSeoH=|!Ku3?6<%JFQ_n+|rP%Jt#bhbbO z02_ZZM|BY)p{uv;FMhJM&1ZieQQF)reGg{vdK3VKFbWShB_y7pdM1NWQq}tro}GW5 z!a&wWI426sZ-g$6B{dgYEt31|>W0$!bV;FF$Sx>N!ZnOV2M49VPv0+U02%JEU|g1H1YLYjKv z(9j%zrN+)J>rRpH|K2hIwgvdSJ`kK)vw?GcNY27&qnTU#^D;1e94oD(IUzswU}+-W z6ldn>$ZsvRuN8-YksDJ66^|l#V=0vuXN19@RQdE@C2BF-)xh692qfr6x_|YBv9-E6 zKPhG%i{?yBFPfp&!dIYE$ZoLrr*FNxM>61VkodgmT=Xl+g7eu*;fqNJ1;@q-tJkG& z+zlBSP%!um4t|?d%{I><0bn1t%;ykOYZ=d8hXw0VyvX8^crs#iCFf5fsKO`JP7VhL+*K0*f~~E7ujo zbm$Jx863tkajP@k>J}y$br=N@<7v7&y4CJPEUbSK=wc6kdclh69n>trf});PQVR0V zfylY?OJMq0=%n&*dfeOXsP~lb=z@2&zo3$V_y6B~@lgoh1A?U=_&^Q4|JCvTH=p9K zRsUsyzbx>V1^%+YUl#bw0{55~e^@CG7vRj~Eclyg;rK;U7z&XNGQ=7N#Jg?r$TC_WN zjT%JPM+M~t!B+4+;d#6M0xsy^c)Xdu9#eYkT2m|$lP~j$b?YXyMDZr)-8Z|OVGbG+ z63dpoLqjJcpH0VXUW4lL*m~##-|f6!0XYy5uFI3g6z!Cd*m!@x}}(t|=|Oy*jcvzMLL9 zuxeQd?$W14Gid{0-?Mk`?f_5Ue)nvXgv6=8N&(N5kooE_fB)8}#DR z{rjh&<$DfUJPqK6?p_Tc^<8n*`nWnW(#;~Zjh0_NALX>VZh%t#@!ta@FJ_0wmEH6c;8O9?YyUi^ z{^%2mRkGj47&rL-9-tuozM*~b7Ct;pxUgCNjMzY%dcS0+Kg8`H&+(sp>j!%9SED8! z83_sWV6or1=6=cVj&06G@1C3kQGr(be~Sw7c_OPUv#RT%6Yol=6VJjT#~L6evZgD! zM-yGPEThOnu~|K&wi{X% zHNK>7zneFojt&giRo)(Wt>@F7!xKV0UjGOVv6ft!BzHn-x8f|wo7d##igeL+b#?GpWYqD=u?JkPn->hvQA*i{8<}%%SesSxtbh(xOtz)Y2Y+uE z#YLogs;$oRcf0U56}AngmX~vmuR8a`a(#+m$Ge zjY!d^QU!%QMMXAxw(9Quj&FTQ!j%=<7pm@VQ6)D!(iyr5GNvKxy-hL` z!^Q|j{nW1aRhX)DbXKr|0+L6=5YoE5J!49-H`>fqZr@Jctzf8IEa)%##-}H*6svN& zuDDnYQ(s>%TxnH8s>I6d-@pHSoJ^%PM6gyM^xX%t#-nW6k38-ct=aBJ^ly}7gJ#dO zt1%Ai8XiLa+$PB#51s1*#z_XYRP}=gOY6^_o83jOHI7;ZXBbnCEr&DCl&abD(GBtO z@e7A-3UWrOZxMf+nVAV>kHM)8FU>P_chq)`jGSpsRzgLM&-K!b(X$SBy*m-{+QuuhUIB+lpb z_4QS4Ds}K6_&8cxdajhYrQ{k#N7~QQUNdgjLWi~VArOKuxC{;D*k3FTF@u0JH0gsN z9Mh1Gp^zMY0pIb4R;HY(RD!fpnKL0;;5OTxTb$T(eYm~7JvGIIJ141f{^3n& z?%g;ccTvp>Wn!!qD^pxjVpI=&e0xnzjoeXOoG?)}FFRoQ(*x~<9b-`@vK{eGRfF~d zjHs3kuNsD0IXMkyN-9MLOm3`9_Qc9G1@#!S6U5F?erLLf8*HFi6)hkb9>3I8U*EQY zAqX5McPP0J+1muwy?Qkl>2Ga)jr-&JS&p<_n(Q zsQjVkS~J5n)FlFe2%*Zu_=O$OopX$1w8cepaG5`Ek}R;dv#W8nKB=j0g!<4$OT^SX zTAD{{WU60KO+7dNXn|rl@4d#V}@Dgna!>Z)nNy0i6zq6P-K8IJ}>k+g@3QCA&}@*dP+9N5wPmG;5f+Q%^z z+VM-qzz>;owy%%jZV_RaH8dfT!&K4EJj-g9Mt;2FE_4!(*2C;Ge$DAxoIh-(ps&Sf zs=mc=@T2erGrnW-l%XNVsJV~NouncBWf!IBjq!<}Zx@*y`fq zVip&$66ECIK-yTU>M2_kJ=E3HBOTnCh*uKKnL(81urI2rv>7lF3nuDXnU1wJjd12H!K4wb z2?1W4lMhn9K7l{2sr0CIGSeq7d}aWIKmXAUEvK~+U-TGBl;_|PwT@3tPVTcSw#U_o zGk0p+jouDT0zW`Xxx|HS)+#SASF^r8?L&!VS|>+x=lixQ#~wop7shL9P`jxJ295!e z2(dUQC>>lvx%7|e2|Vh6~(sTmoK-eeEjhN`u&{JKcU z>w%J4w&tKPf(zDfL<+v%>Rx>Ks0nJQmS(8of-xaJK~dAH!d5y|j*fW;uGF8sH9yZ+ zF`KDLkZGFfqQY)LUHj87PJ4Z}ACxNWpT*3l>|MDPh|CtpVx<>EDZG@4$k$d$do<@^bG zj0ZBNC+|#sh;2!dgCW*>%}BdtWkaKv-%7H#+cM2HaciadW+_&C=dhr4qgQ9MQixox z@k>w(F7iZ#{QMWN#X*K{#;?Dw{YISMu(39-DX6VwMP9yq*+WYCfMMv;NBS66ZXHdf zP~1V%3W<3Ca9zU&St zj{u+Gb4DmaPmkKAxXYvw67X0Z0tSQm)TW9WJ$cDeuru+7mEFKflQ?<6Sx~n-qjkA2 zt9;N3l-|n&@EHq%UF!?#dbi=V1qx0%Rz_dFW7SgMF3`Q?->citUHWeu%3p&s&H!#=3M>H~j@TWAAd?O+* z^15^O5qFtNiMv3kt~_EqPR>5h5MI`RMx(!rmAPKws2iI!nXGD2SM0-GW4DTiQGyM2 zPj2y|aH*;&x2wxKRM6rPD~YZpJa|yRcT`u%h9KAP&*r%Z5;BhR5|DE)%Z14Ohm2Ov zO7%cIjvtQ=7{5)fOYb~5Tti@U2>gtZeWj(>Or?|`?z3?+g1PImhCJsb5fNMZ_Uz$s z%+vYiUQWaFjc32-aG)%~leQ;oZ)2zi+P;G$<9>cB5jzwbc>Gg5LcsF^MlL)$tg(FA z^!npNJID-9YEii{~}#@mYsoNU6rnxNV4d56tw!iq6b zL>AgHJ*ex#a9Jq7ZK}M-$I<%bsunQ755RkZ4fMu_>gxP}4T=>b=33VUei#@vCgV|> zqRzsll|s}}Kl!;jP)lLA>m6sG9299yIM1>>1|#Qdv_S>xsI{@}PdB{e7c)Qv9i2X+ zwhUX#`c$58HXGVM7=2}Q#GqCUczyb>)iWdmj6a0T4&^fzOTw>(aOOt_s6|@{tj(5e~GY~(Iccx!95x~@FgWB zi%ilg69M|W65tP)FJ1a23q&>Dk6b&pPC^PuB5UAe%>E)9c~sn zZVyl;dYwQ)sB7Tf9KZKZ!R4+xDNKZl|I|o*UPM+_0lYJ-rp!PSZRidr&^pi7 zN!2rTLuI7n-|%L2p+mX%t$WN?WnIacN zf@rjXC{XTxay3Z~A1c?bPd(&U1_h0Wy$7K%n7am7V=<7QwpIm_`cKFnb?4uqNhDUm;9tRvt{W$_su^VshdaGSqmPNF|Bpy= z=(TaqS$B5_khrsyA>5;}i@sPcckE!H)g#8m!XQF0*0l%Jhxj`*S8HQctInmI4kdaN^rKw?@ix_WO#><#~XuIIXbhHPC)z7r>C#=-MLBP?%UBPcY&WbJyzM;Z4JZ;J5Nu~5I;XZ7j@Z=Mm7bJ zG~0tBFhDC*OaaMj6y$sJLgEUA@*+4qJm@X>+S5WViJfZ+(w>eEO1ALI*YiS|KBCR9 z_>-XyK7 z$-S!dhi?%n#lY3x##L6PhEKM_)KXe1pA!lrg?v&19G(Hh4)&*Ea1CqyMe=G89yFpg zO*3xnmTeo7Uc8k=Iycly>okPghpRiC7s~4d?10+DJ_buw_K= zuZL}tX%rm784hQcA2d&2+N!KB7G1)9e9n1@h8AXK+H%1pcd;9Vo)-^x*}Alt2ez-j z8zV%%+U$$X$-xgTkDEORT?$*BdIUw5gODa$95V0Q(KT8(`%&C2qRA`%^*Fh- zj0|{!%+albV0Os;>4z%{3UY=^Xeh(l#A&b={6dYk58kG?PpimxX~^v*hm)K5{(YRs z?We6Z+vIgi3TtXY-Zg|Q+Jh0qSZR-}EK5=U&JC!)zkg*zLqk}${yFLem4p8z@Bke1## zq1e82GVsE~gTGg=Q2FT8O zg;zM4ElD0_I38VOctqT<0ud-@iJ=dPjtn&e6MN6SuhFE3ih{)n8PlPf#R_6qmyO{e z%aJSi>n+Uu*5om}h~-bKRT#A#pd2|kgx~JTOR&yJ&ua&b(e%h;73pn;p->7(=BP=s zZ{KfSUSN3qS_#=$sS_(>!kH>#0#z!CIfJ_#G(EHo3N>QIXm^2}dRWN2N-xdi&W^?I z;fD3h^o50mZczInNMQx=4=8x#tcrEI-jNaKDo_MG9w`{Ipr8>s5MCbk?tc6Wp&uC6 zn|-YEa{Bc>-(&6IW9t(iMn`XSi@V4r)F-IeaT*$M%~y&VJKIus!Cpzif#2kZNC(rV zW?1ilCiCObALsZ@vy%~v`q7bAycU9whpe#H55c{0Xjjis$MT4GotZEPQGpNd$gZzl z6V%Aq`taK%p@5A^-d{twF!6~hkkt;*ESPergIdFeF31vVCBK91|&zLhx& zj?9UF?;~bVFbwp`NuYy&;(qhH(vRWGvVZJW7i;mC^Z${zfS3QR$9MMtL;WQqg9JC+ z@|8a-k3xBOs)NZ~y@Dy01y@ioN27Hq+~nL9{RDcsyu3VQWB9bhW0@qu_^G5Q`d1R4 zW|CSq(^XQa1!7Wn_p2{)yZ@5@|K|ZeW2j&1h+i)cR78e{-F!AJuNxbJDNctO_<@

W;DmR7{v+P|zdpceFn@XH&SvMlpME2PQ^>D}oQy|rcB>En+b%%T z^V)OPFvi_xmsQ{;dROEmBm(?!I9&ZE4W;01+=&+B*3HiTOKa;KgNLU@pW*9OzvHhy z!z%jxJf~IJPYF2IJxAy#lRKEYFTmc}XUux#6yIct$tf|Px5jUkM+v za|STgJrou9-{gG$;;#?B-fU=g`PFH|uO!~+rG5$7?Ooj)Jb4Q^@YOlseljU?-tm<& zXTQ%?Z0CW=F0H+7U%Sa>glK*5Lg2Rbz5cBx{_n&0zjlergT=c_|VK5l|REH$(G-0u3nt@Yp!g&d{YD24n&*;2K6N)9NR27v=6!sAQg#iXa8sK-{+ge6W^o;q7FD;K*&D{ z4g8gd`GEc7%f955|NfSN_&hQSpyxiCeKOED{QF*c4-7pe1;D66MzH0m0T~2{(ij5v zlkDzT5u<+A^s-rN?oohI@xA^RPkGd@G8TGriWroN$wXoX9)I<%Z?k9tyt!`u>GliJx>FGj;GS~VFJYMK3GRU+zR2xbg zsh2hiC@BL%3_V?=r#i>P7|**+PHH7qe7)7)K^maM93TOMS$&N*#=GR}yErsOGki_` zck$}ZZplk-QPo#LQ*=JpK<9Z-M>^FYyz~!rRd;ATu-I*cQfT&2YEcQH-bt-KY(d2t z?hXwZOBDCH=5;Mm5`cY1-niV$8C5XMGvaJarVt(jB#CZZZC9BO<}#TqKC=fs3dXM& z+CB?U3`32gSC@txeB){QL6q0~0Ur(%2_>EN{J7CUFxa(-l?jjJpMHq&M;QLF(|BcJ z!SkG1O1B><amw$i0DitxP!j^x%6>FPe}dPVSP6!*mN4LA1I^baJ&KY21Z`r-vM(T|*G628zM zOE0o%Zfk68l=g^RX<_J|IALp@sWshSV3~bLM^7&W%fRyn1@ow*xyOcdeSM|38%9U6 z;>4m8ki?S}taF^}S5?pio_tu!Y7r zan2oU6L_TT)20-G5xB*`;B|zgGa4V=Q=XOee!>l_Xb31$NjKS!T64R;8kc(RoH7Qj z`R?l=pbI_xuz9V!yStGfAQJ?7#6oMT;LRr0#Rm_%XIX2z3#1R!u`Sfo6D@POr@f-1 z;Z_a~PSl?_sogHL%pTnNPta#ei?xrze}o)HhRzGgx zL-q04V^>f8bAsqlhRV)2^^W{)94rQTfcwEdZJWdcby)lT-MMW3e)MKoBu4vsdLM}!?gkfI3F60kiZ36skn{Cg7Jt8B6 zv?CUe6uSe}Iwvz99Cbk`mY5%*<@~6iuP2TBhj{h8^FYM2InB&mt8I zqWOVgTp!5R9sviHQ160?Z?-9jT}nanai57JVPtl`*Gx%p_1*n%X&KSy!Jfiph+~(4 zbQ7Qq9Baha9PE%4+^8DPsjRHjmNwb=^nPWs6}4~42oy(>(-bTaB8EEu!1c7lR}=Pt z1rnqfJ39j=9VT8;09x8V2a+d%pqm9CQ7F{P%y+S_OdJmnKzf@eQcFK{n7@b9BbV0R z*u{SA@3$4_Lj>62=;_YUXI6nHY1~V`WHtyw? zUSu16NK-SsEA&;V@rk96&#^tm+_`+;EnBx5LN-3d@M>XH{Bj;((#wy{6vNekD#S|x z3sSP`VWkCi86c^KRWd+|}NL>ye@ z%+Djx?i{@YHX4l9zq3{(f8|VRwyq#F2#_qN$|;ZYGgE8Z!%%{&>n8w7e+9<6Awn(~ zr9tX?`K;(xgqeE!_9R)sowHU}2DP58o`ozLR%GEehA*ylBRHzd&vdzHH@@_`(+b9H zNd+@^=aDQD+2tu7OoRuFLC&AAcWDe;sdX8nbWp-or&LqNA;Mu)Tq{V6%n<3$tUWW( z;hnNaUF!tSs6vuEf`&K8AORWq#-m^+!#!f&#VkcJH-Ld-<3(#kYjk&47YzVn2Zw{H zZ6AbJ!S)U2%#`S~5s+VVHSs5_j>gIWn>aRZ$f-yxx(3z)`tJiMjchGS^RSacuEn8U zK#R#N%!{~f>E}n|0A&!Uh9^Uph5)!THs@h5L9_XV3@oG2bEebD1}y!&y(Wgw!`rznhMsbTBXHR(Z!2fD3b}^6tt9IFQm1Fy5pB zhN}1Y5g^jz_Nb!d*aq;aw@>TSzH`-Kd)=qi~z)X-2&x zNJzRtGY#Va6;}Yo-2_0mYuM0Ig8@P$h(0Uh)ejyi@;VDwd#~bpHVp)=%{8*Xwvqkd z08P^*BG|uGynO=}YL*grX&acOP&`~6hw{Qi0;*x4Fwzeus?c;{HNEnsfn^%D+Ujb< zBo+?2<7SLYJgZgsdLeB5`58FM5K((n|Kq=S%-eC9qpTAi#kin5-0qivBFg(Qa@Jb( zC76J%2a>Ri_?HZvTvJ2HqxoQi=$am)7~Q&Y^TL@kG*{qo&vaB%+s=Z1c;drjl~e{n zB*gGCNP~lAA}oQ2rIK+7xPU-;1VAzMr^ixr4)SoIVDA#KFlRP+1kFrE)0@)cdbpm? zv}v!HHi26E2mVDY6jfH1ECsuN|F{#Z0XZ(|arD4Wi;~essdkljsRCtC_rL6)OjU&)5m7~mbsO-Kq^el<$WQV1rMgKimCBFj9y!>am~Op3 zKg8!0n{}cH+)CPR0DgDalCQ3>t?#+fomaY8R%(25NxX+8-r~U2Cewg&Uj*+Y+wfO_&a9r6mqyNZw_348dyoABG+jrKSGI3AA2AUG4|8Np_s(%Qx{kYf zAjCizD1}i~WJAW2H+iY4scqg>6>{!j*QV=(W?%ET+${h}hPIpottykl*}Jsg4=gh0 zb$L%NlF%OY(E@GlH(O2v-Mxe=R(8 z*u18+)U!bk8pf(hPHqPhtjonhO9Ww|eI1p zLRyizhy0TM63NEehPajtcEjw%J11&KvbMmN!uU6&cQc!_w^Au)?sypoY5sNjq@$&M>nJcNT)xcdYu~@&8S{)i zawzLyc*bvl+59~|5;^}JJvyy=2CE1(dHp0=oU4svm`U%)@+mQA zwN{S#bY~BU{Tax=4pPcub}|hVWOH+CB*h$+n%Xc^u{4HV?KEWYmp5J_#htf{OKS*@ z*)5#{AWGEpE<)#epo)J8LhrJtdA6*Lgtt9|3yVN3=Xam=oNN+rba}PEU+wOG zfEQK_bakZ~0!L5F0n#)XELrIJjY#eRYrzdj&ZTvh?8}ah?5#7M;c9FW0ndPf7%D2_ zRG&;c@)Ix`kl?u2n-i1{kBIkT^(qB#!NML?8>Cmge;aVkA8uE7Tpn2ly`;adulzvJ zJOW*a)6UFU0%fz|{3C;tOB*Z0DE5#p*bC0lr3vsLNcIJa5yvo8-6EUyR>#MS0ap4v zhVxNv@2cj5t!k0)*VM>}@A;2wesQVX&|C)ilSgn?4e1DvL6LA(Ue1+rAILDU%t z-OkoF7))X_L@=gy_jGkxf{o#oDu8e=e-`+`(jMOE`n&sNZry1xeLczUi=p&sz4R6D zCe6;mf0*e(;=JG98&FwgV(;j}gzn-60N9TQM(>MZZl19;>Zpcs8UgHz)c^vy7ql*o zOl8-|1Ek+BWOuZ;J8v-&PY+VcU}L~-*3jSxccu%GKhs_@J|FDQ%1^;lb1v@r0)r7! z`ovTf^xIT}+x@~P0N)?5vk%omf}LtBR*J8p{_540``>OqCm{1TCKGycjP#3w-amvF z(;F6B9)qpZ957xqlpXIPsQ|K;q%-Rys~KI-qGLsjSHMGZG+gjOV9bvD8psKfl*n2sLz$Y2+1C?Gx)_wIFTx)zgMdR8Wb zMl^-Syvk*aADO7Cof#G?HkNmHB(|ZZM(EMgVRvXt-42IZ_El2rrLhH88348*;##oc zEoOJnV_~F~OpF2-`il@}q`($&{w9;_{@(!x)5%+3Uq{ga{eQbvvvIjoDB$@Vb*{dd z6tDw`cw^6SdF;_tzrF1{bfHp?vEWFH;R>^<)2y)Hl@1HRr4FHmZ1wB16w+sl>-^FI1xNrcxckIOP|9Ihz2BBqa8%_Mm z$uG#V;PwAMAIl<)q$KDFB_!_gOC9z;`~1iMKflVKSN*32{G5YHE=j<@&60(-*(tqiLZwqEPea)2e1GtA#vfH)!8!BtM~p7MUfgL diff --git a/testing/resources/performance_overlay_gold_90fps.png b/testing/resources/performance_overlay_gold_90fps.png index 962e2875d4fde53aa45a76081099ee76bc5a7330..d6fb7e8e0d7a1eeeda8ae757408a376a1e251736 100644 GIT binary patch literal 16298 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 t|Jwrpw!ptF@NWzJ+XDZ#z$Yw#6*CrP literal 16293 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