| // Copyright 2019 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 "cc/metrics/frame_sequence_tracker.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/containers/contains.h" |
| #include "base/containers/cxx20_erase.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/notreached.h" |
| #include "base/trace_event/trace_event.h" |
| #include "base/trace_event/traced_value.h" |
| #include "components/viz/common/frame_sinks/begin_frame_args.h" |
| #include "components/viz/common/quads/compositor_frame_metadata.h" |
| #include "ui/gfx/presentation_feedback.h" |
| |
| // This macro is used with DCHECK to provide addition debug info. |
| #if DCHECK_IS_ON() |
| #define TRACKER_TRACE_STREAM frame_sequence_trace_ |
| #define TRACKER_DCHECK_MSG \ |
| " in " << GetFrameSequenceTrackerTypeName(this->type()) \ |
| << " tracker: " << frame_sequence_trace_.str() << " (" \ |
| << frame_sequence_trace_.str().size() << ")"; |
| #else |
| #define TRACKER_TRACE_STREAM EAT_STREAM_PARAMETERS |
| #define TRACKER_DCHECK_MSG "" |
| #endif |
| |
| namespace cc { |
| |
| namespace { |
| |
| constexpr char kTraceCategory[] = |
| "cc,benchmark," TRACE_DISABLED_BY_DEFAULT("devtools.timeline.frame"); |
| |
| } // namespace |
| |
| using ThreadType = FrameInfo::SmoothEffectDrivingThread; |
| |
| // In the |TRACKER_TRACE_STREAM|, we mod the numbers such as frame sequence |
| // number, or frame token, such that the debug string is not too long. |
| constexpr int kDebugStrMod = 1000; |
| |
| const char* FrameSequenceTracker::GetFrameSequenceTrackerTypeName( |
| FrameSequenceTrackerType type) { |
| switch (type) { |
| case FrameSequenceTrackerType::kCompositorAnimation: |
| return "CompositorAnimation"; |
| case FrameSequenceTrackerType::kMainThreadAnimation: |
| return "MainThreadAnimation"; |
| case FrameSequenceTrackerType::kPinchZoom: |
| return "PinchZoom"; |
| case FrameSequenceTrackerType::kRAF: |
| return "RAF"; |
| case FrameSequenceTrackerType::kTouchScroll: |
| return "TouchScroll"; |
| case FrameSequenceTrackerType::kVideo: |
| return "Video"; |
| case FrameSequenceTrackerType::kWheelScroll: |
| return "WheelScroll"; |
| case FrameSequenceTrackerType::kScrollbarScroll: |
| return "ScrollbarScroll"; |
| case FrameSequenceTrackerType::kCustom: |
| return "Custom"; |
| case FrameSequenceTrackerType::kCanvasAnimation: |
| return "CanvasAnimation"; |
| case FrameSequenceTrackerType::kJSAnimation: |
| return "JSAnimation"; |
| case FrameSequenceTrackerType::kMaxType: |
| return ""; |
| } |
| } |
| |
| FrameSequenceTracker::FrameSequenceTracker( |
| FrameSequenceTrackerType type, |
| ThroughputUkmReporter* throughput_ukm_reporter) |
| : custom_sequence_id_(-1), |
| metrics_( |
| std::make_unique<FrameSequenceMetrics>(type, |
| throughput_ukm_reporter)) { |
| DCHECK_LT(type, FrameSequenceTrackerType::kMaxType); |
| DCHECK(type != FrameSequenceTrackerType::kCustom); |
| // TODO(crbug.com/1158439): remove the trace event once the validation is |
| // completed. |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP1( |
| kTraceCategory, "TrackerValidation", TRACE_ID_LOCAL(this), |
| base::TimeTicks::Now(), "name", GetFrameSequenceTrackerTypeName(type)); |
| } |
| |
| FrameSequenceTracker::FrameSequenceTracker( |
| int custom_sequence_id, |
| FrameSequenceMetrics::CustomReporter custom_reporter) |
| : custom_sequence_id_(custom_sequence_id), |
| metrics_(std::make_unique<FrameSequenceMetrics>( |
| FrameSequenceTrackerType::kCustom, |
| /*ukm_reporter=*/nullptr)) { |
| DCHECK_GT(custom_sequence_id_, 0); |
| metrics_->SetCustomReporter(std::move(custom_reporter)); |
| } |
| |
| FrameSequenceTracker::~FrameSequenceTracker() { |
| TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP2( |
| kTraceCategory, "TrackerValidation", TRACE_ID_LOCAL(this), |
| base::TimeTicks::Now(), "aborted_main", aborted_main_frame_, |
| "no_damage_main", no_damage_draw_main_frames_); |
| CleanUp(); |
| } |
| |
| void FrameSequenceTracker::ScheduleTerminate() { |
| // If the last frame has ended and there is no frame awaiting presentation, |
| // then it is ready to terminate. |
| if (!is_inside_frame_ && last_submitted_frame_ == 0) |
| termination_status_ = TerminationStatus::kReadyForTermination; |
| else |
| termination_status_ = TerminationStatus::kScheduledForTermination; |
| } |
| |
| void FrameSequenceTracker::ReportBeginImplFrame( |
| const viz::BeginFrameArgs& args) { |
| if (termination_status_ != TerminationStatus::kActive) |
| return; |
| |
| if (ShouldIgnoreBeginFrameSource(args.frame_id.source_id)) |
| return; |
| |
| TRACKER_TRACE_STREAM << "b(" << args.frame_id.sequence_number % kDebugStrMod |
| << ")"; |
| |
| DCHECK(!is_inside_frame_) << TRACKER_DCHECK_MSG; |
| is_inside_frame_ = true; |
| #if DCHECK_IS_ON() |
| if (args.type == viz::BeginFrameArgs::NORMAL) |
| impl_frames_.insert(args.frame_id); |
| #endif |
| |
| DCHECK_EQ(last_started_impl_sequence_, 0u) << TRACKER_DCHECK_MSG; |
| last_started_impl_sequence_ = args.frame_id.sequence_number; |
| if (reset_all_state_) { |
| begin_impl_frame_data_ = {}; |
| begin_main_frame_data_ = {}; |
| reset_all_state_ = false; |
| } |
| |
| DCHECK(!frame_had_no_compositor_damage_) << TRACKER_DCHECK_MSG; |
| DCHECK(!compositor_frame_submitted_) << TRACKER_DCHECK_MSG; |
| |
| UpdateTrackedFrameData(&begin_impl_frame_data_, args.frame_id.source_id, |
| args.frame_id.sequence_number, |
| args.frames_throttled_since_last); |
| impl_throughput().frames_expected += |
| begin_impl_frame_data_.previous_sequence_delta; |
| #if DCHECK_IS_ON() |
| ++impl_throughput().frames_received; |
| #endif |
| |
| if (first_frame_timestamp_.is_null()) |
| first_frame_timestamp_ = args.frame_time; |
| } |
| |
| void FrameSequenceTracker::ReportBeginMainFrame( |
| const viz::BeginFrameArgs& args) { |
| if (termination_status_ != TerminationStatus::kActive) |
| return; |
| |
| if (ShouldIgnoreBeginFrameSource(args.frame_id.source_id)) |
| return; |
| |
| TRACKER_TRACE_STREAM << "B(" |
| << begin_main_frame_data_.previous_sequence % |
| kDebugStrMod |
| << "," << args.frame_id.sequence_number % kDebugStrMod |
| << ")"; |
| |
| if (first_received_main_sequence_ && |
| first_received_main_sequence_ > args.frame_id.sequence_number) { |
| return; |
| } |
| |
| if (!first_received_main_sequence_ && |
| ShouldIgnoreSequence(args.frame_id.sequence_number)) { |
| return; |
| } |
| |
| #if DCHECK_IS_ON() |
| if (args.type == viz::BeginFrameArgs::NORMAL) { |
| DCHECK(impl_frames_.contains(args.frame_id)) << TRACKER_DCHECK_MSG; |
| } |
| #endif |
| |
| last_processed_main_sequence_latency_ = 0; |
| pending_main_sequences_.push_back(args.frame_id.sequence_number); |
| |
| UpdateTrackedFrameData(&begin_main_frame_data_, args.frame_id.source_id, |
| args.frame_id.sequence_number, |
| args.frames_throttled_since_last); |
| if (!first_received_main_sequence_ || |
| first_received_main_sequence_ <= last_no_main_damage_sequence_) { |
| first_received_main_sequence_ = args.frame_id.sequence_number; |
| } |
| main_throughput().frames_expected += |
| begin_main_frame_data_.previous_sequence_delta; |
| previous_begin_main_sequence_ = current_begin_main_sequence_; |
| current_begin_main_sequence_ = args.frame_id.sequence_number; |
| } |
| |
| void FrameSequenceTracker::ReportMainFrameProcessed( |
| const viz::BeginFrameArgs& args) { |
| if (termination_status_ != TerminationStatus::kActive) |
| return; |
| |
| if (ShouldIgnoreBeginFrameSource(args.frame_id.source_id)) |
| return; |
| |
| TRACKER_TRACE_STREAM << "E(" << args.frame_id.sequence_number % kDebugStrMod |
| << ")"; |
| |
| const bool previous_main_frame_submitted_or_no_damage = |
| previous_begin_main_sequence_ != 0 && |
| (last_submitted_main_sequence_ == previous_begin_main_sequence_ || |
| last_no_main_damage_sequence_ == previous_begin_main_sequence_); |
| if (last_processed_main_sequence_ != 0 && |
| !had_impl_frame_submitted_between_commits_ && |
| !previous_main_frame_submitted_or_no_damage) { |
| DCHECK_GE(main_throughput().frames_expected, |
| begin_main_frame_data_.previous_sequence_delta) |
| << TRACKER_DCHECK_MSG; |
| main_throughput().frames_expected -= |
| begin_main_frame_data_.previous_sequence_delta; |
| last_no_main_damage_sequence_ = previous_begin_main_sequence_; |
| } |
| had_impl_frame_submitted_between_commits_ = false; |
| |
| if (first_received_main_sequence_ && |
| args.frame_id.sequence_number >= first_received_main_sequence_) { |
| DCHECK_EQ(last_processed_main_sequence_latency_, 0u) << TRACKER_DCHECK_MSG; |
| last_processed_main_sequence_ = args.frame_id.sequence_number; |
| last_processed_main_sequence_latency_ = |
| std::max(last_started_impl_sequence_, last_processed_impl_sequence_) - |
| args.frame_id.sequence_number; |
| } |
| } |
| |
| void FrameSequenceTracker::ReportSubmitFrame( |
| uint32_t frame_token, |
| bool has_missing_content, |
| const viz::BeginFrameAck& ack, |
| const viz::BeginFrameArgs& origin_args) { |
| DCHECK_NE(termination_status_, TerminationStatus::kReadyForTermination); |
| |
| // TODO(crbug.com/1072482): find a proper way to terminate a tracker. |
| // Right now, we define a magical number |frames_to_terminate_tracker| = 3, |
| // which means that if this frame_token is more than 3 frames compared with |
| // the last submitted frame, then we assume that the last submitted frame is |
| // not going to be presented, and thus terminate this tracker. |
| const uint32_t frames_to_terminate_tracker = 3; |
| if (termination_status_ == TerminationStatus::kScheduledForTermination && |
| last_submitted_frame_ != 0 && |
| viz::FrameTokenGT(frame_token, |
| last_submitted_frame_ + frames_to_terminate_tracker)) { |
| termination_status_ = TerminationStatus::kReadyForTermination; |
| return; |
| } |
| |
| if (ShouldIgnoreBeginFrameSource(ack.frame_id.source_id) || |
| ShouldIgnoreSequence(ack.frame_id.sequence_number)) { |
| ignored_frame_tokens_.insert(frame_token); |
| return; |
| } |
| |
| #if DCHECK_IS_ON() |
| DCHECK(is_inside_frame_) << TRACKER_DCHECK_MSG; |
| DCHECK_LT(impl_throughput().frames_processed, |
| impl_throughput().frames_received) |
| << TRACKER_DCHECK_MSG; |
| ++impl_throughput().frames_processed; |
| #endif |
| |
| last_processed_impl_sequence_ = ack.frame_id.sequence_number; |
| if (first_submitted_frame_ == 0) |
| first_submitted_frame_ = frame_token; |
| last_submitted_frame_ = frame_token; |
| compositor_frame_submitted_ = true; |
| |
| TRACKER_TRACE_STREAM << "s(" << frame_token % kDebugStrMod << ")"; |
| had_impl_frame_submitted_between_commits_ = true; |
| metrics()->NotifySubmitForJankReporter( |
| FrameInfo::SmoothEffectDrivingThread::kCompositor, frame_token, |
| ack.frame_id.sequence_number); |
| |
| const bool main_changes_after_sequence_started = |
| first_received_main_sequence_ && |
| origin_args.frame_id.sequence_number >= first_received_main_sequence_; |
| const bool main_changes_include_new_changes = |
| last_submitted_main_sequence_ == 0 || |
| origin_args.frame_id.sequence_number > last_submitted_main_sequence_; |
| const bool main_change_had_no_damage = |
| last_no_main_damage_sequence_ != 0 && |
| origin_args.frame_id.sequence_number == last_no_main_damage_sequence_; |
| const bool origin_args_is_valid = origin_args.frame_id.sequence_number <= |
| begin_main_frame_data_.previous_sequence; |
| |
| if (!ShouldIgnoreBeginFrameSource(origin_args.frame_id.source_id) && |
| origin_args_is_valid) { |
| if (main_changes_after_sequence_started && |
| main_changes_include_new_changes && !main_change_had_no_damage) { |
| submitted_frame_had_new_main_content_ = true; |
| TRACKER_TRACE_STREAM << "S(" |
| << origin_args.frame_id.sequence_number % |
| kDebugStrMod |
| << ")"; |
| metrics()->NotifySubmitForJankReporter( |
| FrameInfo::SmoothEffectDrivingThread::kMain, frame_token, |
| origin_args.frame_id.sequence_number); |
| |
| last_submitted_main_sequence_ = origin_args.frame_id.sequence_number; |
| main_frames_.push_back(frame_token); |
| DCHECK_GE(main_throughput().frames_expected, main_frames_.size()) |
| << TRACKER_DCHECK_MSG; |
| } |
| } |
| |
| if (has_missing_content) { |
| checkerboarding_.frames.push_back(frame_token); |
| } |
| } |
| |
| void FrameSequenceTracker::ReportFrameEnd( |
| const viz::BeginFrameArgs& args, |
| const viz::BeginFrameArgs& main_args) { |
| DCHECK_NE(termination_status_, TerminationStatus::kReadyForTermination); |
| |
| if (ShouldIgnoreBeginFrameSource(args.frame_id.source_id)) |
| return; |
| |
| // We only update the `pending_main_sequences` when the frame has successfully |
| // submitted, or when we determine that it has no damage. See |
| // ReportMainFrameCausedNoDamage. We do not do this in |
| // NotifyMainFrameProcessed, as that can occur during Commit, and we may yet |
| // determine at Draw that there was no damage. |
| while (!pending_main_sequences_.empty() && |
| pending_main_sequences_.front() <= |
| main_args.frame_id.sequence_number) { |
| pending_main_sequences_.pop_front(); |
| } |
| TRACKER_TRACE_STREAM << "e(" << args.frame_id.sequence_number % kDebugStrMod |
| << "," |
| << main_args.frame_id.sequence_number % kDebugStrMod |
| << ")"; |
| |
| bool should_ignore_sequence = |
| ShouldIgnoreSequence(args.frame_id.sequence_number); |
| if (reset_all_state_) { |
| begin_impl_frame_data_ = {}; |
| begin_main_frame_data_ = {}; |
| reset_all_state_ = false; |
| } |
| |
| if (should_ignore_sequence) { |
| is_inside_frame_ = false; |
| return; |
| } |
| |
| if (compositor_frame_submitted_ && submitted_frame_had_new_main_content_ && |
| last_processed_main_sequence_latency_) { |
| // If a compositor frame was submitted with new content from the |
| // main-thread, then make sure the latency gets accounted for. |
| main_throughput().frames_expected += last_processed_main_sequence_latency_; |
| } |
| |
| // It is possible that the compositor claims there was no damage from the |
| // compositor, but before the frame ends, it submits a compositor frame (e.g. |
| // with some damage from main). In such cases, the compositor is still |
| // responsible for processing the update, and therefore the 'no damage' claim |
| // is ignored. |
| if (frame_had_no_compositor_damage_ && !compositor_frame_submitted_) { |
| DCHECK_GT(impl_throughput().frames_expected, 0u) << TRACKER_DCHECK_MSG; |
| DCHECK_GT(impl_throughput().frames_expected, |
| impl_throughput().frames_produced) |
| << TRACKER_DCHECK_MSG; |
| DCHECK_GE(impl_throughput().frames_produced, |
| impl_throughput().frames_ontime) |
| << TRACKER_DCHECK_MSG; |
| --impl_throughput().frames_expected; |
| metrics()->NotifyNoUpdateForJankReporter( |
| FrameInfo::SmoothEffectDrivingThread::kCompositor, |
| args.frame_id.sequence_number, args.interval); |
| #if DCHECK_IS_ON() |
| ++impl_throughput().frames_processed; |
| // If these two are the same, it means that each impl frame is either |
| // no-damage or submitted. That's expected, so we don't need those in the |
| // output of DCHECK. |
| if (impl_throughput().frames_processed == impl_throughput().frames_received) |
| ignored_trace_char_count_ = frame_sequence_trace_.str().size(); |
| else |
| NOTREACHED() << TRACKER_DCHECK_MSG; |
| #endif |
| begin_impl_frame_data_.previous_sequence = 0; |
| } |
| // last_submitted_frame_ == 0 means the last impl frame has been presented. |
| if (termination_status_ == TerminationStatus::kScheduledForTermination && |
| last_submitted_frame_ == 0) |
| termination_status_ = TerminationStatus::kReadyForTermination; |
| |
| frame_had_no_compositor_damage_ = false; |
| compositor_frame_submitted_ = false; |
| submitted_frame_had_new_main_content_ = false; |
| last_processed_main_sequence_latency_ = 0; |
| |
| DCHECK(is_inside_frame_) << TRACKER_DCHECK_MSG; |
| is_inside_frame_ = false; |
| |
| DCHECK_EQ(last_started_impl_sequence_, last_processed_impl_sequence_) |
| << TRACKER_DCHECK_MSG; |
| last_started_impl_sequence_ = 0; |
| } |
| |
| void FrameSequenceTracker::ReportFramePresented( |
| uint32_t frame_token, |
| const gfx::PresentationFeedback& feedback) { |
| // TODO(xidachen): We should early exit if |last_submitted_frame_| = 0, as it |
| // means that we are presenting the same frame_token again. |
| const bool submitted_frame_since_last_presentation = !!last_submitted_frame_; |
| // !viz::FrameTokenGT(a, b) is equivalent to b >= a. |
| const bool frame_token_acks_last_frame = |
| !viz::FrameTokenGT(last_submitted_frame_, frame_token); |
| |
| // Even if the presentation timestamp is null, we set last_submitted_frame_ to |
| // 0 such that the tracker can be terminated. |
| if (last_submitted_frame_ && frame_token_acks_last_frame) |
| last_submitted_frame_ = 0; |
| // Update termination status if this is scheduled for termination, and it is |
| // not waiting for any frames, or it has received the presentation-feedback |
| // for the latest frame it is tracking. |
| // |
| // We should always wait for an impl frame to end, that is, ReportFrameEnd. |
| if (termination_status_ == TerminationStatus::kScheduledForTermination && |
| last_submitted_frame_ == 0 && !is_inside_frame_) { |
| termination_status_ = TerminationStatus::kReadyForTermination; |
| } |
| |
| if (first_submitted_frame_ == 0 || |
| viz::FrameTokenGT(first_submitted_frame_, frame_token)) { |
| // We are getting presentation feedback for frames that were submitted |
| // before this sequence started. So ignore these. |
| return; |
| } |
| |
| TRACKER_TRACE_STREAM << "P(" << frame_token % kDebugStrMod << ")"; |
| |
| base::EraseIf(ignored_frame_tokens_, [frame_token](const uint32_t& token) { |
| return viz::FrameTokenGT(frame_token, token); |
| }); |
| if (ignored_frame_tokens_.contains(frame_token)) |
| return; |
| |
| const auto vsync_interval = |
| (feedback.interval.is_zero() ? viz::BeginFrameArgs::DefaultInterval() |
| : feedback.interval); |
| DCHECK(!vsync_interval.is_zero()) << TRACKER_DCHECK_MSG; |
| base::TimeTicks safe_deadline_for_frame = |
| last_frame_presentation_timestamp_ + vsync_interval * 1.5; |
| |
| const bool was_presented = !feedback.failed(); |
| if (was_presented && submitted_frame_since_last_presentation) { |
| if (!last_frame_presentation_timestamp_.is_null() && |
| (safe_deadline_for_frame < feedback.timestamp)) { |
| DCHECK_LE(impl_throughput().frames_ontime, |
| impl_throughput().frames_produced) |
| << TRACKER_DCHECK_MSG; |
| ++impl_throughput().frames_ontime; |
| } |
| |
| DCHECK_LT(impl_throughput().frames_produced, |
| impl_throughput().frames_expected) |
| << TRACKER_DCHECK_MSG; |
| ++impl_throughput().frames_produced; |
| if (metrics()->GetEffectiveThread() == ThreadType::kCompositor) { |
| metrics()->AdvanceTrace(feedback.timestamp); |
| } |
| |
| metrics()->ComputeJank(FrameInfo::SmoothEffectDrivingThread::kCompositor, |
| frame_token, feedback.timestamp, vsync_interval); |
| } |
| |
| if (was_presented) { |
| // This presentation includes the visual update from all main frame tokens |
| // <= |frame_token|. |
| const unsigned size_before_erase = main_frames_.size(); |
| while (!main_frames_.empty() && |
| !viz::FrameTokenGT(main_frames_.front(), frame_token)) { |
| main_frames_.pop_front(); |
| } |
| if (main_frames_.size() < size_before_erase) { |
| DCHECK_LT(main_throughput().frames_produced, |
| main_throughput().frames_expected) |
| << TRACKER_DCHECK_MSG; |
| ++main_throughput().frames_produced; |
| if (metrics()->GetEffectiveThread() == ThreadType::kMain) { |
| metrics()->AdvanceTrace(feedback.timestamp); |
| } |
| |
| metrics()->ComputeJank(FrameInfo::SmoothEffectDrivingThread::kMain, |
| frame_token, feedback.timestamp, vsync_interval); |
| } |
| if (main_frames_.size() < size_before_erase) { |
| if (!last_frame_presentation_timestamp_.is_null() && |
| (safe_deadline_for_frame < feedback.timestamp)) { |
| DCHECK_LE(main_throughput().frames_ontime, |
| main_throughput().frames_produced) |
| << TRACKER_DCHECK_MSG; |
| ++main_throughput().frames_ontime; |
| } |
| } |
| last_frame_presentation_timestamp_ = feedback.timestamp; |
| |
| if (checkerboarding_.last_frame_had_checkerboarding) { |
| DCHECK(!checkerboarding_.last_frame_timestamp.is_null()) |
| << TRACKER_DCHECK_MSG; |
| DCHECK(!feedback.timestamp.is_null()) << TRACKER_DCHECK_MSG; |
| |
| // |feedback.timestamp| is the timestamp when the latest frame was |
| // presented. |checkerboarding_.last_frame_timestamp| is the timestamp |
| // when the previous frame (which had checkerboarding) was presented. Use |
| // |feedback.interval| to compute the number of vsyncs that have passed |
| // between the two frames (since that is how many times the user saw that |
| // checkerboarded frame). |
| base::TimeDelta difference = |
| feedback.timestamp - checkerboarding_.last_frame_timestamp; |
| const auto& interval = feedback.interval.is_zero() |
| ? viz::BeginFrameArgs::DefaultInterval() |
| : feedback.interval; |
| DCHECK(!interval.is_zero()) << TRACKER_DCHECK_MSG; |
| constexpr base::TimeDelta kEpsilon = base::Milliseconds(1); |
| int64_t frames = (difference + kEpsilon).IntDiv(interval); |
| metrics_->add_checkerboarded_frames(frames); |
| } |
| |
| const bool frame_had_checkerboarding = |
| base::Contains(checkerboarding_.frames, frame_token); |
| checkerboarding_.last_frame_had_checkerboarding = frame_had_checkerboarding; |
| checkerboarding_.last_frame_timestamp = feedback.timestamp; |
| } |
| |
| while (!checkerboarding_.frames.empty() && |
| !viz::FrameTokenGT(checkerboarding_.frames.front(), frame_token)) { |
| checkerboarding_.frames.pop_front(); |
| } |
| } |
| |
| void FrameSequenceTracker::ReportImplFrameCausedNoDamage( |
| const viz::BeginFrameAck& ack) { |
| DCHECK_NE(termination_status_, TerminationStatus::kReadyForTermination); |
| |
| if (ShouldIgnoreBeginFrameSource(ack.frame_id.source_id)) |
| return; |
| |
| TRACKER_TRACE_STREAM << "n(" << ack.frame_id.sequence_number % kDebugStrMod |
| << ")"; |
| |
| // This tracker would be scheduled to terminate, and this frame doesn't belong |
| // to that tracker. |
| if (ShouldIgnoreSequence(ack.frame_id.sequence_number)) |
| return; |
| |
| last_processed_impl_sequence_ = ack.frame_id.sequence_number; |
| // If there is no damage for this frame (and no frame is submitted), then the |
| // impl-sequence needs to be reset. However, this should be done after the |
| // processing the frame is complete (i.e. in ReportFrameEnd()), so that other |
| // notifications (e.g. 'no main damage' etc.) can be handled correctly. |
| DCHECK_EQ(begin_impl_frame_data_.previous_sequence, |
| ack.frame_id.sequence_number); |
| frame_had_no_compositor_damage_ = true; |
| } |
| |
| void FrameSequenceTracker::ReportMainFrameCausedNoDamage( |
| const viz::BeginFrameArgs& args, |
| bool aborted) { |
| if (termination_status_ != TerminationStatus::kActive) |
| return; |
| |
| if (ShouldIgnoreBeginFrameSource(args.frame_id.source_id)) |
| return; |
| |
| TRACKER_TRACE_STREAM << "N(" |
| << begin_main_frame_data_.previous_sequence % |
| kDebugStrMod |
| << "," << args.frame_id.sequence_number % kDebugStrMod |
| << ")"; |
| |
| if (!first_received_main_sequence_ || |
| first_received_main_sequence_ > args.frame_id.sequence_number) { |
| return; |
| } |
| |
| if (last_no_main_damage_sequence_ == args.frame_id.sequence_number) |
| return; |
| |
| auto initial_pending_size = pending_main_sequences_.size(); |
| while (!pending_main_sequences_.empty() && |
| pending_main_sequences_.front() <= args.frame_id.sequence_number) { |
| pending_main_sequences_.pop_front(); |
| } |
| // If we didn't remove any `pending_main_sequences`, then we have previously |
| // submitted a CompositorFrame with damage for `args.frame_id.sequence_number` |
| // and the sequence is being re-used on a subsequent Impl frame. Which just |
| // happens to have no damage. |
| // |
| // This can occur when there is a Compositor Animation that is offscreen, and |
| // when we are awaiting the next BeginMainFrame to be Committed and Activated. |
| // |
| // We do not change the `main_throughput` expectations when the sequence is |
| // re-used. |
| if (pending_main_sequences_.size() == initial_pending_size) |
| return; |
| |
| if (aborted) |
| ++aborted_main_frame_; |
| else |
| ++no_damage_draw_main_frames_; |
| |
| DCHECK_GT(main_throughput().frames_expected, 0u) << TRACKER_DCHECK_MSG; |
| DCHECK_GT(main_throughput().frames_expected, |
| main_throughput().frames_produced) |
| << TRACKER_DCHECK_MSG; |
| DCHECK_GE(main_throughput().frames_produced, main_throughput().frames_ontime) |
| << TRACKER_DCHECK_MSG; |
| last_no_main_damage_sequence_ = args.frame_id.sequence_number; |
| --main_throughput().frames_expected; |
| metrics()->NotifyNoUpdateForJankReporter( |
| FrameInfo::SmoothEffectDrivingThread::kMain, |
| args.frame_id.sequence_number, args.interval); |
| |
| DCHECK_GE(main_throughput().frames_expected, main_frames_.size()) |
| << TRACKER_DCHECK_MSG; |
| |
| // Could be 0 if there were a pause frame production. |
| if (begin_main_frame_data_.previous_sequence != 0) { |
| DCHECK_GE(begin_main_frame_data_.previous_sequence, |
| args.frame_id.sequence_number) |
| << TRACKER_DCHECK_MSG; |
| } |
| begin_main_frame_data_.previous_sequence = 0; |
| } |
| |
| void FrameSequenceTracker::PauseFrameProduction() { |
| // The states need to be reset, so that the tracker ignores the vsyncs until |
| // the next received begin-frame. However, defer doing that until the frame |
| // ends (or a new frame starts), so that in case a frame is in-progress, |
| // subsequent notifications for that frame can be handled correctly. |
| TRACKER_TRACE_STREAM << 'R'; |
| reset_all_state_ = true; |
| } |
| |
| void FrameSequenceTracker::UpdateTrackedFrameData( |
| TrackedFrameData* frame_data, |
| uint64_t source_id, |
| uint64_t sequence_number, |
| uint64_t throttled_frame_count) { |
| if (frame_data->previous_sequence && |
| frame_data->previous_source == source_id) { |
| uint32_t current_latency = |
| sequence_number - frame_data->previous_sequence - throttled_frame_count; |
| DCHECK_GT(current_latency, 0u) << TRACKER_DCHECK_MSG; |
| frame_data->previous_sequence_delta = current_latency; |
| } else { |
| frame_data->previous_sequence_delta = 1; |
| } |
| frame_data->previous_source = source_id; |
| frame_data->previous_sequence = sequence_number; |
| } |
| |
| bool FrameSequenceTracker::ShouldIgnoreBeginFrameSource( |
| uint64_t source_id) const { |
| if (begin_impl_frame_data_.previous_source == 0) |
| return source_id == viz::BeginFrameArgs::kManualSourceId; |
| return source_id != begin_impl_frame_data_.previous_source; |
| } |
| |
| // This check handles two cases: |
| // 1. When there is a call to ReportBeginMainFrame, or ReportSubmitFrame, or |
| // ReportFramePresented, there must be a ReportBeginImplFrame for that sequence. |
| // Otherwise, the begin_impl_frame_data_.previous_sequence would be 0. |
| // 2. A tracker is scheduled to terminate, then any new request to handle a new |
| // impl frame whose sequence_number > begin_impl_frame_data_.previous_sequence |
| // should be ignored. |
| // Note that sequence_number < begin_impl_frame_data_.previous_sequence cannot |
| // happen. |
| bool FrameSequenceTracker::ShouldIgnoreSequence( |
| uint64_t sequence_number) const { |
| return sequence_number != begin_impl_frame_data_.previous_sequence; |
| } |
| |
| bool FrameSequenceTracker::ShouldReportMetricsNow( |
| const viz::BeginFrameArgs& args) const { |
| return metrics_->HasEnoughDataForReporting() && |
| !first_frame_timestamp_.is_null() && |
| args.frame_time - first_frame_timestamp_ >= time_delta_to_report_; |
| } |
| |
| std::unique_ptr<FrameSequenceMetrics> FrameSequenceTracker::TakeMetrics() { |
| #if DCHECK_IS_ON() |
| DCHECK_EQ(impl_throughput().frames_received, |
| impl_throughput().frames_processed) |
| << frame_sequence_trace_.str().substr(ignored_trace_char_count_); |
| #endif |
| return std::move(metrics_); |
| } |
| |
| void FrameSequenceTracker::CleanUp() { |
| if (metrics_) |
| metrics_->ReportLeftoverData(); |
| } |
| |
| void FrameSequenceTracker::AddSortedFrame(const viz::BeginFrameArgs& args, |
| const FrameInfo& frame_info) { |
| if (metrics_) |
| metrics_->AddSortedFrame(args, frame_info); |
| } |
| |
| FrameSequenceTracker::CheckerboardingData::CheckerboardingData() = default; |
| FrameSequenceTracker::CheckerboardingData::~CheckerboardingData() = default; |
| |
| } // namespace cc |