| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "session_connection.h" |
| |
| #include "flutter/fml/make_copyable.h" |
| #include "flutter/fml/trace_event.h" |
| |
| #include "vsync_recorder.h" |
| #include "vsync_waiter.h" |
| |
| namespace flutter_runner { |
| |
| SessionConnection::SessionConnection( |
| std::string debug_label, |
| fidl::InterfaceHandle<fuchsia::ui::scenic::Session> session, |
| fml::closure session_error_callback, |
| on_frame_presented_event on_frame_presented_callback, |
| zx_handle_t vsync_event_handle, |
| uint64_t max_frames_in_flight) |
| : session_wrapper_(session.Bind(), nullptr), |
| on_frame_presented_callback_(std::move(on_frame_presented_callback)), |
| vsync_event_handle_(vsync_event_handle), |
| kMaxFramesInFlight(max_frames_in_flight) { |
| session_wrapper_.set_error_handler( |
| [callback = session_error_callback](zx_status_t status) { callback(); }); |
| |
| // Set the |fuchsia::ui::scenic::OnFramePresented()| event handler that will |
| // fire every time a set of one or more frames is presented. |
| session_wrapper_.set_on_frame_presented_handler( |
| [this, handle = vsync_event_handle_]( |
| fuchsia::scenic::scheduling::FramePresentedInfo info) { |
| // Update Scenic's limit for our remaining frames in flight allowed. |
| size_t num_presents_handled = info.presentation_infos.size(); |
| frames_in_flight_allowed_ = info.num_presents_allowed; |
| |
| // A frame was presented: Update our |frames_in_flight| to match the |
| // updated unfinalized present requests. |
| frames_in_flight_ -= num_presents_handled; |
| TRACE_EVENT2("gfx", "OnFramePresented", "frames_in_flight", |
| frames_in_flight_, "max_frames_in_flight", |
| kMaxFramesInFlight); |
| FML_DCHECK(frames_in_flight_ >= 0); |
| |
| VsyncRecorder::GetInstance().UpdateFramePresentedInfo( |
| zx::time(info.actual_presentation_time)); |
| |
| // Call the client-provided callback once we are done using |info|. |
| on_frame_presented_callback_(std::move(info)); |
| |
| if (present_session_pending_) { |
| PresentSession(); |
| } |
| ToggleSignal(handle, true); |
| } // callback |
| ); |
| |
| session_wrapper_.SetDebugName(debug_label); |
| |
| // Get information to finish initialization and only then allow Present()s. |
| session_wrapper_.RequestPresentationTimes( |
| /*requested_prediction_span=*/0, |
| [this](fuchsia::scenic::scheduling::FuturePresentationTimes info) { |
| frames_in_flight_allowed_ = info.remaining_presents_in_flight_allowed; |
| |
| // If Scenic alloted us 0 frames to begin with, we should fail here. |
| FML_CHECK(frames_in_flight_allowed_ > 0); |
| |
| VsyncRecorder::GetInstance().UpdateNextPresentationInfo( |
| std::move(info)); |
| |
| // Signal is initially high indicating availability of the session. |
| ToggleSignal(vsync_event_handle_, true); |
| initialized_ = true; |
| |
| PresentSession(); |
| }); |
| } |
| |
| SessionConnection::~SessionConnection() = default; |
| |
| void SessionConnection::Present() { |
| TRACE_EVENT2("gfx", "SessionConnection::Present", "frames_in_flight", |
| frames_in_flight_, "max_frames_in_flight", kMaxFramesInFlight); |
| |
| TRACE_FLOW_BEGIN("gfx", "SessionConnection::PresentSession", |
| next_present_session_trace_id_); |
| next_present_session_trace_id_++; |
| |
| present_requested_time_ = fml::TimePoint::Now(); |
| |
| // Throttle frame submission to Scenic if we already have the maximum amount |
| // of frames in flight. This allows the paint tasks for this frame to execute |
| // in parallel with the presentation of previous frame but still provides |
| // back-pressure to prevent us from enqueuing even more work. |
| if (initialized_ && frames_in_flight_ < kMaxFramesInFlight) { |
| PresentSession(); |
| } else { |
| // We should never exceed the max frames in flight. |
| FML_CHECK(frames_in_flight_ <= kMaxFramesInFlight); |
| |
| present_session_pending_ = true; |
| ToggleSignal(vsync_event_handle_, false); |
| } |
| } |
| |
| fml::TimePoint SessionConnection::CalculateNextLatchPoint( |
| fml::TimePoint present_requested_time, |
| fml::TimePoint now, |
| fml::TimePoint last_latch_point_targeted, |
| fml::TimeDelta flutter_frame_build_time, |
| fml::TimeDelta vsync_interval, |
| std::deque<std::pair<fml::TimePoint, fml::TimePoint>>& |
| future_presentation_infos) { |
| // The minimum latch point is the largest of: |
| // Now |
| // When we expect the Flutter work for the frame to be completed |
| // The last latch point targeted |
| fml::TimePoint minimum_latch_point_to_target = |
| std::max(std::max(now, present_requested_time + flutter_frame_build_time), |
| last_latch_point_targeted); |
| |
| for (auto& info : future_presentation_infos) { |
| fml::TimePoint latch_point = info.first; |
| |
| if (latch_point >= minimum_latch_point_to_target) { |
| return latch_point; |
| } |
| } |
| |
| // We could not find a suitable latch point in the list given to us from |
| // Scenic, so aim for the smallest safe value. |
| return minimum_latch_point_to_target; |
| } |
| |
| void SessionConnection::PresentSession() { |
| TRACE_EVENT0("gfx", "SessionConnection::PresentSession"); |
| |
| // If we cannot call Present2() because we have no more Scenic frame budget, |
| // then we must wait until the OnFramePresented() event fires so we can |
| // continue our work. |
| if (frames_in_flight_allowed_ == 0) { |
| FML_CHECK(!initialized_ || present_session_pending_); |
| return; |
| } |
| |
| present_session_pending_ = false; |
| |
| while (processed_present_session_trace_id_ < next_present_session_trace_id_) { |
| TRACE_FLOW_END("gfx", "SessionConnection::PresentSession", |
| processed_present_session_trace_id_); |
| processed_present_session_trace_id_++; |
| } |
| TRACE_FLOW_BEGIN("gfx", "Session::Present", next_present_trace_id_); |
| next_present_trace_id_++; |
| |
| ++frames_in_flight_; |
| |
| // Flush all session ops. Paint tasks may not yet have executed but those are |
| // fenced. The compositor can start processing ops while we finalize paint |
| // tasks. |
| |
| fml::TimeDelta presentation_interval = |
| VsyncRecorder::GetInstance().GetCurrentVsyncInfo().presentation_interval; |
| |
| fml::TimePoint next_latch_point = CalculateNextLatchPoint( |
| fml::TimePoint::Now(), present_requested_time_, |
| last_latch_point_targeted_, |
| fml::TimeDelta::FromMicroseconds(0), // flutter_frame_build_time |
| presentation_interval, future_presentation_infos_); |
| |
| last_latch_point_targeted_ = next_latch_point; |
| |
| session_wrapper_.Present2( |
| /*requested_presentation_time=*/next_latch_point.ToEpochDelta() |
| .ToNanoseconds(), |
| /*requested_prediction_span=*/presentation_interval.ToNanoseconds() * 10, |
| [this](fuchsia::scenic::scheduling::FuturePresentationTimes info) { |
| // Clear |future_presentation_infos_| and replace it with the updated |
| // information. |
| std::deque<std::pair<fml::TimePoint, fml::TimePoint>>().swap( |
| future_presentation_infos_); |
| |
| for (fuchsia::scenic::scheduling::PresentationInfo& presentation_info : |
| info.future_presentations) { |
| future_presentation_infos_.push_back( |
| {fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromNanoseconds( |
| presentation_info.latch_point())), |
| fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromNanoseconds( |
| presentation_info.presentation_time()))}); |
| } |
| |
| frames_in_flight_allowed_ = info.remaining_presents_in_flight_allowed; |
| VsyncRecorder::GetInstance().UpdateNextPresentationInfo( |
| std::move(info)); |
| }); |
| } |
| |
| void SessionConnection::ToggleSignal(zx_handle_t handle, bool set) { |
| const auto signal = VsyncWaiter::SessionPresentSignal; |
| auto status = zx_object_signal(handle, // handle |
| set ? 0 : signal, // clear mask |
| set ? signal : 0 // set mask |
| ); |
| if (status != ZX_OK) { |
| FML_LOG(ERROR) << "Could not toggle vsync signal: " << set; |
| } |
| } |
| |
| } // namespace flutter_runner |