// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/viz/service/display/overdraw_tracker.h"

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <vector>

#include "base/check.h"
#include "base/check_op.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/safe_conversions.h"
#include "base/time/time.h"
#include "components/viz/common/quads/aggregated_render_pass.h"
#include "components/viz/common/quads/shared_quad_state.h"
#include "components/viz/service/display/aggregated_frame.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/transform.h"

namespace viz {
namespace {

// Max number of average overdraw records. It is 30min when interval size
// is 1s and no tests should run longer than 30min.
constexpr size_t kMaxRecords = 1800u;

void LogAverageOverdrawCountUMA(float overdraw) {
  constexpr char kAverageOverdrawHistogramName[] =
      "Compositing.Display.Draw.AverageOverdraw2";
  // For optimal histogram bucketing, convert floating-point values into
  // integers while preserving the desired level of decimal precision.
  constexpr int kConversionFactor = 100'000;

  // The expected overdraw ranges is [1, 12].
  UMA_HISTOGRAM_CUSTOM_COUNTS(
      kAverageOverdrawHistogramName,
      base::ClampRound<int>(overdraw * kConversionFactor),
      /*minimum=*/1 * kConversionFactor,
      /*maximum=*/(12 * kConversionFactor) + 1, /*bucket_count=*/50);
}

}  // namespace

// static
void OverdrawTracker::EstimateAndRecordOverdrawAsUMAMetric(
    const AggregatedFrame* frame) {
  const float overdraw = EstimateOverdraw(frame);
  LogAverageOverdrawCountUMA(overdraw);
}

// static
float OverdrawTracker::EstimateOverdraw(const AggregatedFrame* frame) {
  if (frame->render_pass_list.empty()) {
    return 0;
  }

  auto* root_render_pass = frame->render_pass_list.back().get();
  DCHECK(root_render_pass);

  const gfx::Rect& display_rect = root_render_pass->output_rect;

  base::CheckedNumeric<uint64_t> overdraw = 0;
  for (const auto& pass : frame->render_pass_list) {
    for (auto quad = pass->quad_list.begin(); quad != pass->quad_list.end();
         ++quad) {
      const SharedQuadState* sqs = quad->shared_quad_state;
      auto quad_to_root_transform = sqs->quad_to_target_transform;

      if (!quad_to_root_transform.NonDegeneratePreserves2dAxisAlignment()) {
        continue;
      }

      auto quad_rect = gfx::ToEnclosingRect(
          quad_to_root_transform.MapRect(gfx::RectF(quad->visible_rect)));
      if (sqs->clip_rect) {
        quad_rect.Intersect(sqs->clip_rect.value());
      }

      overdraw += quad_rect.size().GetCheckedArea();
    }
  }

  return static_cast<float>(overdraw.ValueOrDefault(0)) /
         display_rect.size().Area64();
}

OverdrawTracker::OverdrawTracker(const Settings& settings)
    : settings_(settings), start_time_(base::TimeTicks::Now()) {
  interval_overdraw_averages_.reserve(kMaxRecords);
}

OverdrawTracker::~OverdrawTracker() = default;

void OverdrawTracker::EstimateAndRecordOverdraw(const AggregatedFrame* frame,
                                                base::TimeTicks timestamp) {
  const float overdraw = EstimateOverdraw(frame);
  Record(overdraw, timestamp);
}

OverdrawTracker::OverdrawTimeSeries OverdrawTracker::TakeDataAsTimeSeries()
    const {
  OverdrawTimeSeries averages;

  // If the recorder has yet to track overdraw of a frame, return empty vector.
  if (interval_overdraw_averages_.empty()) {
    return averages;
  }

  averages.resize(interval_overdraw_averages_.size());

  std::transform(
      interval_overdraw_averages_.begin(), interval_overdraw_averages_.end(),
      averages.begin(),
      [](const DecomposedAverage& average) { return average.GetAverage(); });

  return averages;
}

void OverdrawTracker::Reset() {
  interval_overdraw_averages_.clear();
}

void OverdrawTracker::Record(float overdraw, base::TimeTicks timestamp) {
  DCHECK_LE(start_time_, timestamp);

  const size_t interval_index = GetIntervalIndex(timestamp);
  DCHECK_LT(interval_index, kMaxRecords);

  if (interval_index >= interval_overdraw_averages_.size()) {
    interval_overdraw_averages_.resize(interval_index + 1);
  }

  // Update the average overdraw of the current time interval.
  DecomposedAverage& interval_average =
      interval_overdraw_averages_.at(interval_index);
  interval_average.AddValue(overdraw);
}

size_t OverdrawTracker::GetIntervalIndex(base::TimeTicks timestamp) const {
  return (timestamp - start_time_).InSeconds() /
         settings_.interval_length_in_seconds;
}

}  // namespace viz
