-
Notifications
You must be signed in to change notification settings - Fork 6k
Fix VsyncWaiter #25551
Fix VsyncWaiter #25551
Conversation
|
Can you describe the patch and what its trying to do? |
|
Ping. Is this WIP or a draft? If progress on this is not likely, lets close it for now. |
Hi Chinmay. I've been OOO recovering from a medical operation, but this PR and the associated documentation is something I'm actively working on now. |
Oh, take you time. No rush. Hope you feel better soon. |
| std::shared_ptr<flutter::ExternalViewEmbedder> external_view_embedder, | ||
| fml::TimeDelta vsync_offset, | ||
| zx_handle_t vsync_event_handle) | ||
| SessionConnection* session_connection) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets be good about tracking lifetimes and make this a shared_ptr
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I should've marked this as TODO.
I left it as a raw pointer until I have a FakeSessionConnection class available to use for platform_view_unittests.
| std::set<std::string /* channel */> unregistered_channels_; | ||
|
|
||
| fml::TimeDelta vsync_offset_; | ||
| zx_handle_t vsync_event_handle_ = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hallelujah!
|
|
||
| VsyncWaiter(std::string debug_label, | ||
| zx_handle_t session_present_handle, | ||
| VsyncWaiter(SessionConnection* session_connection, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: please move constructor below static methods (google style guide dictates this)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| } | ||
|
|
||
| VsyncWaiter::~VsyncWaiter() { | ||
| FML_LOG(WARNING) << "~VsyncWaiter()"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: remove
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
|
||
| FrameTargetTimes GetTargetTimesHelper(bool secondary_callback); | ||
|
|
||
| SessionConnection* session_connection_; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use shared_ptr please (not a nit)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| max_frames_in_flight = product_config.get_max_frames_in_flight(), | ||
| vsync_handle = vsync_event_.get(), &view_embedder_latch]() mutable { | ||
| &view_embedder_latch]() mutable { | ||
| session_connection_.emplace( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this point, this should be a shared_ptr too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| // Initialize the callback that will run every time the SessionConnection | ||
| // receives one or more frame presented events on Vsync. | ||
| session_connection_->InitializeVsyncWaiterCallback([this]() { | ||
| task_runners_.GetUITaskRunner()->PostTask([&weak_factory_ui = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not neccesary to rethread this callback to the UI thread.
This callback bottoms out in VsyncWaiter::FireCallback. If you read the implementation of it, it protects variables w/ a mutex, and also performs the necesary re-threading internally.
So, 3 things:
- Can you update the common VsyncWaiter documentation to indicate that FireCallback may be called from any thread safely?
- Session callbacks happen on the raster thread. So change "weak_factory_ui" to "weak_factory_raster" + initialize and destroy it on the raster thread instead of UI. I plan to move the scenic::Session to the platform thread soon (the thread that weak_factory_ is on), and then the 2nd weak factory will vanish entirely.
- Since all of this code now runs on the raster thread, and the presentation callbacks + Present also run on the raster thread, VsyncRecorder has no more reason to exist. Everything happens on one thread. See my other comment on vsync_recorder.h
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we discussed, this is not currently possible due to decreased performance. I filed flutter/flutter#81984 to track this issue.
| // information will be saved (in order to handle edge cases involving | ||
| // multiple Scenic sessions in the same process). This function is safe to | ||
| // multiple Scenic sessions in the same process). This function is safe to | ||
| // call from any thread. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you follow my other suggestion below, then this class no longer has a reason to exist. Everything happens on one thread now, and mostly inside of the SessionConnection.
At that point, you can just collapse this class into SessionConnection without violating any sort of encapsulation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This also is not possible now unfortunately, so I filed flutter/flutter#81984
mikaelpessa
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM around the scheduling logic.
| // (frame_start_time, frame_target_time) | ||
| using FrameTargetTimes = std::pair<fml::TimePoint, fml::TimePoint>; | ||
| // (latch point time, vsync time) | ||
| using LatchPointVsyncPair = std::pair<fml::TimePoint, fml::TimePoint>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: making these structs would be more readable and make the comments unnecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
In preparation for flutter#25551, rename SessionConnection to DefaultSessionConnection. The plan is to have SessionConnection be an interface we can create a fake of for testing.
In preparation for flutter#25551, rename SessionConnection to DefaultSessionConnection. The plan is to have SessionConnection be an interface we can create a fake of for testing.
In preparation for flutter#25551, rename SessionConnection to DefaultSessionConnection. The plan is to have SessionConnection be an interface we can create a fake of for testing.
In preparation for flutter#25551, rename SessionConnection to DefaultSessionConnection. The plan is to have SessionConnection be an interface we can create a fake of for testing.
In preparation for flutter#25551, rename SessionConnection to DefaultSessionConnection. The plan is to have SessionConnection be an interface we can create a fake of for testing.
In preparation for flutter#25551, rename SessionConnection to DefaultSessionConnection. The plan is to have SessionConnection be an interface we can create a fake of for testing.
In preparation for #25551, rename SessionConnection to DefaultSessionConnection. The plan is to have SessionConnection be an interface we can create a fake of for testing purposes.
nathanrogersgoogle
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
| // FireCallback(). This value should be strictly increasing in order to | ||
| // guarantee that animation code that relies on target vsyncs works correctly, | ||
| // and that Flutter is not producing multiple frames in a small interval. | ||
| fml::TimePoint last_targetted_vsync_; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit, but FWIW I think "targeted" is a more common spelling vs "targetted".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
unittests are deactivated
remove session_connection.h remove vsync_waitr_unittests
|
|
||
| // VsyncRecorder logic. | ||
|
|
||
| VsyncInfo GetCurrentVsyncInfo() const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Move this above all of the fields, with PresentSession and friends
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| VsyncInfo GetCurrentVsyncInfo() const; | ||
|
|
||
| static void ToggleSignal(zx_handle_t handle, bool raise); | ||
| fuchsia::scenic::scheduling::PresentationInfo next_presentation_info_; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these also guarded by the mutex? Its not clear because the GetCurrentVsyncInfo breaks things up
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
This PR rewrites the VsyncWaiter for Fuchsia class from scratch. This change is ready for review, modulo some additional unittests I'm working on involving creating a FakeSessionConnection.
Implementation Details
A VsyncWaiter's only purposes are to wait for vsyncs, and call
FireCallback()when appropriate to let the Animator know when they can produce a new frame, or do some trace bookkeeping. This PR changes how this is done on Fuchsia in some key ways, listed below.SessionConnection integration
VsyncWaiter now has a reference to SessionConnection. SessionConnection is responsible for talking to Scenic, and SessionConnection therefore knows when vsyncs are reported back. This means that VsyncWaiter no longer needs to post tasks and guess when vsyncs happen, but rather hear back from SessionConnection directly. There are some caveats with this approach however, as there can be occasional delays in getting this signal due to the amount of thread hops that need to happen. For this reason, VsyncWaiter still schedules based on
SnapToNextPhase, but receiving the vsync tick means it doesn't post tasks for future work, which significantly reduces code complexity.The integration of SessionConnection in the VsyncWaiter also allows for backpressure from Scenic to translate up into Flutter framework.
Use of AwaitVSyncForSecondaryCallback() (#25787)
There are two flavors of waiting for vsync - one is intended for frame scheduling, and the other for miscellaneous tasks. The problem I noticed while testing is that every input event would call AwaitVsync() for trace related purposes, but VsyncWaiter() would interpret this as a frame request, which wasn't often the case. This would lead to an implementation that either lacked robustness by not implementing backpressure, or one that was too conservative in its scheduling.
By separating the two intentions, it is possible to easily perform better for both cases. AwaitVSyncForSecondaryCallback() requests do not update the
last_targetted_time_, meaning that when real frame requests come in they are not delayed by the occurrence of prior input events.Use of
GetTargetTimes()Similar to
SnapToNextPhase()in VsyncWaiter, andCalculateNextLatchPoint()inSessionConnection, it is useful to extract all scheduling logic into an easy-to-test static function. Whereas the previous implementation had its scheduling logic scattered around the class, and via PostTask timing, now the decision of what arguments to pass intoFireCallback()is easier to read and maintain. This makes it easy to solve and unittest for the performance problems I noticed, more on that below.Motivations
Performance
This implementation solves two main problems, as well as enabling input resampling on Fuchsia.
The first problem I noticed was that we would previously often schedule N+1 frames in an N vsync interval window. This guarantees wasted effort, as one of those frames will be produced only to never be shown. This also meant that the frames that would be displayed would be produced later than necessary, due to the wasted effort.
The second issue is that even when the frames would be scheduled in a reasonable time frame, sometimes we'd have a "galloping" behavior where 2 frames would be scheduled in one vsync interval, then 0 in the following interval, then 2 in the one after, etc. This did mean N frames produced in N vsync intervals given optimal scheduling, yes, but the cadence of the frames was uneven. This hampered our ability to resample input, which led to it being disabled on Fuchsia.
I ran perfcompare tests on common workflows on Fuchsia, and saw a statistically significant increase in FPS, GPU utilization, and other key benchmarks.
Smooth input
This change will enable input resampling on Fuchsia again, as it had to be disabled to do inconsistencies in scheduling.
Testability
This implementation is much easier to test, and it has performance unittests as a result. This PR extracts all the logic of when to schedule next (aka call FireCallback()) into a single static function, GetTargetTimes(), which takes in as input all the relevant variables for when to schedule the next available frame.
This change also adds a
FakeSessionConnection, which allows us to better test the VsyncWaiter without needing a real Scenic or display. Specifically, we can test backpressure limits as well as high CPU load by delaying OnVsync()s.Robustness
This implementation takes into account Scenic backpressure, which means that it is no longer possible for Flutter to produce more GPU work than Scenic and the system can handle. This is accomplished by having the VsyncWaiter take a pointer to the SessionConnection, which knows about how many frames in flight Flutter is allowed to have.