| // Copyright 2016 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "cobalt/renderer/submission_queue.h" |
| |
| #include <cmath> |
| |
| #include "base/debug/trace_event.h" |
| #include "base/logging.h" |
| #include "base/string_number_conversions.h" |
| #include "cobalt/base/polymorphic_downcast.h" |
| #include "cobalt/render_tree/animations/animate_node.h" |
| |
| namespace cobalt { |
| namespace renderer { |
| |
| // The maximum change (in units of ms/s) of to_submission_time_in_ms_. This |
| // value must be less than 1000ms/s, or else it might be possible to adjust |
| // our render time/submission time offset by less than -1 second per second, |
| // meaning that we would move backwards in time. We keep this at a healthy |
| // value of 800ms. This also acts as a crude form of regularization. |
| const double kMaxSlopeMagnitude = 800.0; |
| |
| SubmissionQueue::SubmissionQueue( |
| size_t max_queue_size, base::TimeDelta time_to_converge, |
| bool allow_latency_reduction, |
| const DisposeSubmissionFunction& dispose_function) |
| : max_queue_size_(max_queue_size), |
| dispose_function_(dispose_function), |
| to_submission_time_in_ms_(time_to_converge, kMaxSlopeMagnitude), |
| to_submission_time_cval_( |
| "Renderer.ToSubmissionTime", base::TimeDelta(), |
| "The current difference in milliseconds between the layout's clock " |
| "and the renderer's clock. The absolute value does not mean much, " |
| "but how it changes as the user navigates can show how the " |
| "renderer's clock changes relative to layout's clock."), |
| queue_size_( |
| "Renderer.SubmissionQueueSize", 0, |
| "The current size of the renderer submission queue. Each item in " |
| "queue contains a render tree and associated animations."), |
| allow_latency_reduction_(allow_latency_reduction) {} |
| |
| void SubmissionQueue::PushSubmission(const Submission& submission, |
| const base::TimeTicks& now) { |
| TRACE_EVENT0("cobalt::renderer", "SubmissionQueue::PushSubmission()"); |
| |
| if (!submission_queue_.empty()) { |
| CheckThatNowIsMonotonicallyIncreasing(now); |
| } |
| |
| if (submission_queue_.size() >= max_queue_size_) { |
| // If we are at capacity, then make room for the new submission by erasing |
| // our first element. |
| submission_queue_.pop_front(); |
| |
| // Ensure that the next oldest item in the queue is older than the current |
| // time. If this is not the case, snap time forward so that it is. |
| double to_front_submission_time_in_ms = |
| (submission_queue_.front().time_offset - render_time(now)) |
| .InMillisecondsF(); |
| if (to_submission_time_in_ms_.GetValueAtTime(now) < |
| to_front_submission_time_in_ms) { |
| to_submission_time_in_ms_.SetTarget(to_front_submission_time_in_ms, now); |
| to_submission_time_in_ms_.SnapToTarget(); |
| } |
| } |
| |
| base::TimeDelta latest_to_submission_time = |
| submission.time_offset - render_time(now); |
| |
| double latest_to_submission_time_in_ms = |
| latest_to_submission_time.InMillisecondsF(); |
| |
| // Update our mapping from render time to submission time. |
| if (allow_latency_reduction_ || submission_queue_.empty() || |
| to_submission_time_in_ms_.GetValueAtTime(now) > |
| latest_to_submission_time_in_ms) { |
| to_submission_time_in_ms_.SetTarget(latest_to_submission_time_in_ms, now); |
| } |
| |
| // Snap time to the new submission if no existing animations are playing both |
| // currently and during the time that we are snapping to. |
| if (submission_queue_.size() == 1 && |
| submission_queue_.front().render_tree->GetTypeId() == |
| base::GetTypeId<render_tree::animations::AnimateNode>()) { |
| render_tree::animations::AnimateNode* animate_node = |
| base::polymorphic_downcast<render_tree::animations::AnimateNode*>( |
| submission_queue_.front().render_tree.get()); |
| |
| // Check the expiration of only animations that depend on the time |
| // parameter, since they are the only ones that will be affected by snapping |
| // time. |
| if (animate_node->depends_on_time_expiry() <= submission_time(now) && |
| animate_node->depends_on_time_expiry() <= |
| latest_to_submission_time + render_time(now)) { |
| to_submission_time_in_ms_.SnapToTarget(); |
| } |
| } |
| |
| // Save the new submission. |
| submission_queue_.push_back(submission); |
| |
| // Possibly purge old stale submissions. |
| PurgeStaleSubmissionsFromQueue(now); |
| } |
| |
| Submission SubmissionQueue::GetCurrentSubmission(const base::TimeTicks& now) { |
| TRACE_EVENT0("cobalt::renderer", "SubmissionQueue::GetCurrentSubmission()"); |
| |
| CheckThatNowIsMonotonicallyIncreasing(now); |
| |
| DCHECK(!submission_queue_.empty()); |
| |
| // First get rid of any stale submissions from our queue. |
| PurgeStaleSubmissionsFromQueue(now); |
| |
| // Create a new submission with an updated time offset to account for the |
| // fact that time has passed since it was submitted. |
| Submission updated_time_submission(submission_queue_.front()); |
| |
| // Our current clock should always be setup (via PushSubmission()) such |
| // that it is always larger than the front of the queue. |
| DCHECK_GE(to_submission_time_in_ms_.GetValueAtTime(now), |
| (submission_queue_.front().time_offset - render_time(now)) |
| .InMillisecondsF()); |
| |
| base::TimeDelta updated_time = submission_time(now); |
| |
| // This if statement is very similar to the DCHECK above, but not exactly |
| // the same because of rounding issues. |
| if (updated_time > updated_time_submission.time_offset) { |
| updated_time_submission.time_offset = updated_time; |
| } |
| |
| return updated_time_submission; |
| } |
| |
| base::TimeDelta SubmissionQueue::render_time(const base::TimeTicks& time) { |
| if (!renderer_time_origin_) { |
| renderer_time_origin_ = time; |
| } |
| |
| return time - *renderer_time_origin_; |
| } |
| |
| base::TimeDelta SubmissionQueue::submission_time(const base::TimeTicks& time) { |
| return base::TimeDelta::FromMillisecondsD( |
| to_submission_time_in_ms_.GetValueAtTime(time)) + |
| render_time(time); |
| } |
| |
| void SubmissionQueue::PurgeStaleSubmissionsFromQueue( |
| const base::TimeTicks& time) { |
| TRACE_EVENT0("cobalt::renderer", |
| "SubmissionQueue::PurgeStaleSubmissionsFromQueue()"); |
| double current_to_submission_time_in_ms = |
| to_submission_time_in_ms_.GetValueAtTime(time); |
| |
| SubmissionQueueInternal::iterator submission = submission_queue_.end(); |
| --submission; |
| |
| // If there is more than one element in the queue... |
| if (submission != submission_queue_.begin()) { |
| // Skip past the submissions that are in the future. This means we start |
| // from the back because the queue is sorted in ascending order of time. |
| while (current_to_submission_time_in_ms < |
| (submission->time_offset - render_time(time)).InMillisecondsF()) { |
| if (submission == submission_queue_.begin()) { |
| // It is an invariant of this class that the oldest submission in the |
| // queue is older than the current render time. This should be |
| // managed within PushSubmission(). |
| NOTREACHED(); |
| break; |
| } |
| --submission; |
| } |
| } |
| |
| // Delete all previous, old render trees. |
| while (submission_queue_.begin() != submission) { |
| TRACE_EVENT0("cobalt::renderer", "Delete Render Tree Submission"); |
| if (!dispose_function_.is_null()) { |
| // Package the submission for sending to the disposal callback function. |
| // We erase it from our queue before calling the callback in order to |
| // ensure that the callback disposes of the Submission object after we |
| // do. |
| scoped_ptr<Submission> submission( |
| new Submission(submission_queue_.front())); |
| submission_queue_.pop_front(); |
| dispose_function_.Run(submission.Pass()); |
| } else { |
| // If no callback is passed in to dispose of submissions for us, just |
| // delete it immediately. |
| submission_queue_.pop_front(); |
| } |
| } |
| |
| // Update our CVal tracking the current (smoothed) to_submission_time value |
| // and the one tracking submission queue size. |
| to_submission_time_cval_ = base::TimeDelta::FromMilliseconds( |
| to_submission_time_in_ms_.GetValueAtTime(time)); |
| queue_size_ = submission_queue_.size(); |
| } |
| |
| void SubmissionQueue::CheckThatNowIsMonotonicallyIncreasing( |
| const base::TimeTicks& now) { |
| if (last_now_) { |
| DCHECK(now >= *last_now_); |
| } |
| last_now_ = now; |
| } |
| |
| } // namespace renderer |
| } // namespace cobalt |