diff --git a/shell/platform/fuchsia/flutter/engine.cc b/shell/platform/fuchsia/flutter/engine.cc index b0612ac6a5e13..3543c84223c6f 100644 --- a/shell/platform/fuchsia/flutter/engine.cc +++ b/shell/platform/fuchsia/flutter/engine.cc @@ -354,8 +354,6 @@ void Engine::Initialize( view_protocols = std::move(view_protocols), request = parent_viewport_watcher.NewRequest(), view_ref_pair = std::move(view_ref_pair), - max_frames_in_flight = product_config.get_max_frames_in_flight(), - vsync_offset = product_config.get_vsync_offset(), software_rendering = product_config.software_rendering()]() mutable { if (software_rendering) { surface_producer_ = std::make_shared(); @@ -365,8 +363,7 @@ void Engine::Initialize( flatland_connection_ = std::make_shared( thread_label_, std::move(flatland), - std::move(session_error_callback), [](auto) {}, - max_frames_in_flight, vsync_offset); + std::move(session_error_callback), [](auto) {}); fuchsia::ui::views::ViewIdentityOnCreation view_identity = { .view_ref = std::move(view_ref_pair.second), diff --git a/shell/platform/fuchsia/flutter/flatland_connection.cc b/shell/platform/fuchsia/flutter/flatland_connection.cc index 5b3a029fab893..cfc7ea0923455 100644 --- a/shell/platform/fuchsia/flutter/flatland_connection.cc +++ b/shell/platform/fuchsia/flutter/flatland_connection.cc @@ -13,11 +13,10 @@ namespace flutter_runner { namespace { -fml::TimePoint GetNextPresentationTime(fml::TimePoint now, - fml::TimePoint next_presentation_time) { - return now > next_presentation_time - ? now + flutter_runner::kDefaultFlatlandPresentationInterval - : next_presentation_time; +// Helper function for traces. +double DeltaFromNowInNanoseconds(const fml::TimePoint& now, + const fml::TimePoint& time) { + return (time - now).ToNanoseconds(); } } // namespace @@ -26,9 +25,7 @@ FlatlandConnection::FlatlandConnection( std::string debug_label, fuchsia::ui::composition::FlatlandHandle flatland, fml::closure error_callback, - on_frame_presented_event on_frame_presented_callback, - uint64_t max_frames_in_flight, - fml::TimeDelta vsync_offset) + on_frame_presented_event on_frame_presented_callback) : flatland_(flatland.Bind()), error_callback_(error_callback), on_frame_presented_callback_(std::move(on_frame_presented_callback)) { @@ -49,11 +46,9 @@ FlatlandConnection::~FlatlandConnection() = default; // This method is called from the raster thread. void FlatlandConnection::Present() { - if (!threadsafe_state_.first_present_called_) { - std::scoped_lock lock(threadsafe_state_.mutex_); - threadsafe_state_.first_present_called_ = true; - } - if (present_credits_ > 0) { + TRACE_DURATION("flutter", "FlatlandConnection::Present"); + std::scoped_lock lock(threadsafe_state_.mutex_); + if (threadsafe_state_.present_credits_ > 0) { DoPresent(); } else { present_waiting_for_credit_ = true; @@ -66,14 +61,15 @@ void FlatlandConnection::DoPresent() { TRACE_FLOW_BEGIN("gfx", "Flatland::Present", next_present_trace_id_); ++next_present_trace_id_; - FML_CHECK(present_credits_ > 0); - --present_credits_; + FML_CHECK(threadsafe_state_.present_credits_ > 0); + --threadsafe_state_.present_credits_; fuchsia::ui::composition::PresentArgs present_args; present_args.set_requested_presentation_time(0); present_args.set_acquire_fences(std::move(acquire_fences_)); present_args.set_release_fences(std::move(previous_present_release_fences_)); - present_args.set_unsquashable(false); + // Frame rate over latency. + present_args.set_unsquashable(true); flatland_->Present(std::move(present_args)); // In Flatland, release fences apply to the content of the previous present. @@ -86,38 +82,44 @@ void FlatlandConnection::DoPresent() { // This method is called from the UI thread. void FlatlandConnection::AwaitVsync(FireCallbackCallback callback) { + TRACE_DURATION("flutter", "FlatlandConnection::AwaitVsync"); + std::scoped_lock lock(threadsafe_state_.mutex_); - const fml::TimePoint now = fml::TimePoint::Now(); + threadsafe_state_.pending_fire_callback_ = nullptr; + const auto now = fml::TimePoint::Now(); - // Immediately fire callbacks until the first Present. We might receive - // multiple requests for AwaitVsync() until the first Present, which relies on - // receiving size on PlatformView::OnGetLayout() at an uncertain time. - if (!threadsafe_state_.first_present_called_) { - callback(now, now + kDefaultFlatlandPresentationInterval); + // Initial case. + if (MaybeRunInitialVsyncCallback(now, callback)) return; - } - - threadsafe_state_.fire_callback_ = callback; - // Immediately fire callback if OnNextFrameBegin() is already called. - if (threadsafe_state_.on_next_frame_pending_) { - threadsafe_state_.fire_callback_( - now, GetNextPresentationTime( - now, threadsafe_state_.next_presentation_time_)); - threadsafe_state_.fire_callback_ = nullptr; - threadsafe_state_.on_next_frame_pending_ = false; + // Throttle case. + if (threadsafe_state_.present_credits_ == 0) { + threadsafe_state_.pending_fire_callback_ = callback; + return; } + + // Regular case. + RunVsyncCallback(now, callback); } // This method is called from the UI thread. void FlatlandConnection::AwaitVsyncForSecondaryCallback( FireCallbackCallback callback) { - const fml::TimePoint now = fml::TimePoint::Now(); + TRACE_DURATION("flutter", + "FlatlandConnection::AwaitVsyncForSecondaryCallback"); + std::scoped_lock lock(threadsafe_state_.mutex_); - callback(now, GetNextPresentationTime( - now, threadsafe_state_.next_presentation_time_)); + const auto now = fml::TimePoint::Now(); + + // Initial case. + if (MaybeRunInitialVsyncCallback(now, callback)) + return; + + // Regular case. + RunVsyncCallback(now, callback); } +// This method is called from the raster thread. void FlatlandConnection::OnError( fuchsia::ui::composition::FlatlandError error) { FML_LOG(ERROR) << "Flatland error: " << static_cast(error); @@ -127,30 +129,62 @@ void FlatlandConnection::OnError( // This method is called from the raster thread. void FlatlandConnection::OnNextFrameBegin( fuchsia::ui::composition::OnNextFrameBeginValues values) { - present_credits_ += values.additional_present_credits(); + // Collect now before locking because this is an important timing information + // from Scenic. + const auto now = fml::TimePoint::Now(); + + std::scoped_lock lock(threadsafe_state_.mutex_); + threadsafe_state_.first_feedback_received_ = true; + threadsafe_state_.present_credits_ += values.additional_present_credits(); + TRACE_DURATION("flutter", "FlatlandConnection::OnNextFrameBegin", + "present_credits", threadsafe_state_.present_credits_); - if (present_waiting_for_credit_ && present_credits_ > 0) { + if (present_waiting_for_credit_ && threadsafe_state_.present_credits_ > 0) { DoPresent(); present_waiting_for_credit_ = false; } - if (present_credits_ > 0) { - FML_CHECK(values.has_future_presentation_infos() && - !values.future_presentation_infos().empty()); - const auto next_presentation_time = - fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromNanoseconds( - values.future_presentation_infos().front().presentation_time())); - - std::scoped_lock lock(threadsafe_state_.mutex_); - if (threadsafe_state_.fire_callback_) { - threadsafe_state_.fire_callback_( - /*frame_start=*/fml::TimePoint::Now(), - /*frame_target=*/next_presentation_time); - threadsafe_state_.fire_callback_ = nullptr; - } else { - threadsafe_state_.on_next_frame_pending_ = true; - } - threadsafe_state_.next_presentation_time_ = next_presentation_time; + // Update vsync_interval_ by calculating the difference between the first two + // presentation times. Flatland always returns >1 presentation_infos, so this + // check is to guard against any changes to this assumption. + if (values.has_future_presentation_infos() && + values.future_presentation_infos().size() > 1) { + threadsafe_state_.vsync_interval_ = fml::TimeDelta::FromNanoseconds( + values.future_presentation_infos().at(1).presentation_time() - + values.future_presentation_infos().at(0).presentation_time()); + } else { + FML_LOG(WARNING) + << "Flatland didn't send enough future_presentation_infos to update " + "vsync interval."; + } + + // Update next_presentation_times_. + std::queue new_times; + for (const auto& info : values.future_presentation_infos()) { + new_times.emplace(fml::TimePoint::FromEpochDelta( + fml::TimeDelta::FromNanoseconds(info.presentation_time()))); + } + threadsafe_state_.next_presentation_times_.swap(new_times); + + // Update vsync_offset_. + // We use modulo here because Flatland may point to the following vsync if + // OnNextFrameBegin() is called after the current frame's latch point. + auto vsync_offset = + (fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromNanoseconds( + values.future_presentation_infos().front().presentation_time())) - + now) % + threadsafe_state_.vsync_interval_; + // Thread contention may result in OnNextFrameBegin() being called after the + // presentation time. Ignore these outliers. + if (vsync_offset > fml::TimeDelta::Zero()) { + threadsafe_state_.vsync_offset_ = vsync_offset; + } + + // Throttle case. + if (threadsafe_state_.pending_fire_callback_ && + threadsafe_state_.present_credits_ > 0) { + RunVsyncCallback(now, threadsafe_state_.pending_fire_callback_); + threadsafe_state_.pending_fire_callback_ = nullptr; } } @@ -160,6 +194,68 @@ void FlatlandConnection::OnFramePresented( on_frame_presented_callback_(std::move(info)); } +// Parses and updates next_presentation_times_. +fml::TimePoint FlatlandConnection::GetNextPresentationTime( + const fml::TimePoint& now) { + const fml::TimePoint& cutoff = + now > threadsafe_state_.last_presentation_time_ + ? now + : threadsafe_state_.last_presentation_time_; + + // Remove presentation times that may have been passed. This may happen after + // a long draw call. + while (!threadsafe_state_.next_presentation_times_.empty() && + threadsafe_state_.next_presentation_times_.front() <= cutoff) { + threadsafe_state_.next_presentation_times_.pop(); + } + + // Calculate a presentation time based on + // |threadsafe_state_.last_presentation_time_| that is later than cutoff using + // |vsync_interval| increments if we don't have any future presentation times + // left. + if (threadsafe_state_.next_presentation_times_.empty()) { + auto result = threadsafe_state_.last_presentation_time_; + while (result <= cutoff) { + result = result + threadsafe_state_.vsync_interval_; + } + return result; + } + + // Return the next presentation time in the queue for the regular case. + const auto result = threadsafe_state_.next_presentation_times_.front(); + threadsafe_state_.next_presentation_times_.pop(); + return result; +} + +// This method is called from the UI thread. +bool FlatlandConnection::MaybeRunInitialVsyncCallback( + const fml::TimePoint& now, + FireCallbackCallback& callback) { + if (!threadsafe_state_.first_feedback_received_) { + TRACE_DURATION("flutter", + "FlatlandConnection::MaybeRunInitialVsyncCallback"); + const auto frame_end = now + kInitialFlatlandVsyncOffset; + threadsafe_state_.last_presentation_time_ = frame_end; + callback(now, frame_end); + return true; + } + return false; +} + +// This method may be called from the raster or UI thread, but it is safe +// because VsyncWaiter posts the vsync callback on UI thread. +void FlatlandConnection::RunVsyncCallback(const fml::TimePoint& now, + FireCallbackCallback& callback) { + const auto& frame_end = GetNextPresentationTime(now); + const auto& frame_start = frame_end - threadsafe_state_.vsync_offset_; + threadsafe_state_.last_presentation_time_ = frame_end; + TRACE_DURATION("flutter", "FlatlandConnection::RunVsyncCallback", + "frame_start_delta", + DeltaFromNowInNanoseconds(now, frame_start), "frame_end_delta", + DeltaFromNowInNanoseconds(now, frame_end)); + callback(frame_start, frame_end); +} + // This method is called from the raster thread. void FlatlandConnection::EnqueueAcquireFence(zx::event fence) { acquire_fences_.push_back(std::move(fence)); diff --git a/shell/platform/fuchsia/flutter/flatland_connection.h b/shell/platform/fuchsia/flutter/flatland_connection.h index 2d894de6cd09d..6b03f0685c9c7 100644 --- a/shell/platform/fuchsia/flutter/flatland_connection.h +++ b/shell/platform/fuchsia/flutter/flatland_connection.h @@ -16,6 +16,7 @@ #include #include +#include #include namespace flutter_runner { @@ -23,10 +24,10 @@ namespace flutter_runner { using on_frame_presented_event = std::function; -// 2ms interval to target vsync is only used until Scenic sends presentation -// feedback, or when we run out of present credits. -static constexpr fml::TimeDelta kDefaultFlatlandPresentationInterval = - fml::TimeDelta::FromMilliseconds(2); +// 10ms interval to target vsync is only used until Scenic sends presentation +// feedback. +static constexpr fml::TimeDelta kInitialFlatlandVsyncOffset = + fml::TimeDelta::FromMilliseconds(10); // The component residing on the raster thread that is responsible for // maintaining the Flatland instance connection and presenting updates. @@ -35,9 +36,7 @@ class FlatlandConnection final { FlatlandConnection(std::string debug_label, fuchsia::ui::composition::FlatlandHandle flatland, fml::closure error_callback, - on_frame_presented_event on_frame_presented_callback, - uint64_t max_frames_in_flight, - fml::TimeDelta vsync_offset); + on_frame_presented_event on_frame_presented_callback); ~FlatlandConnection(); @@ -70,6 +69,12 @@ class FlatlandConnection final { void OnFramePresented(fuchsia::scenic::scheduling::FramePresentedInfo info); void DoPresent(); + fml::TimePoint GetNextPresentationTime(const fml::TimePoint& now); + bool MaybeRunInitialVsyncCallback(const fml::TimePoint& now, + FireCallbackCallback& callback); + void RunVsyncCallback(const fml::TimePoint& now, + FireCallbackCallback& callback); + fuchsia::ui::composition::FlatlandPtr flatland_; fml::closure error_callback_; @@ -78,7 +83,6 @@ class FlatlandConnection final { uint64_t next_content_id_ = 0; on_frame_presented_event on_frame_presented_callback_; - uint32_t present_credits_ = 1; bool present_waiting_for_credit_ = false; // A flow event trace id for following |Flatland::Present| calls into Scenic. @@ -89,10 +93,13 @@ class FlatlandConnection final { // You should always lock mutex_ before touching anything in this struct struct { std::mutex mutex_; - FireCallbackCallback fire_callback_; - bool first_present_called_ = false; - bool on_next_frame_pending_ = false; - fml::TimePoint next_presentation_time_; + std::queue next_presentation_times_; + fml::TimeDelta vsync_interval_ = kInitialFlatlandVsyncOffset; + fml::TimeDelta vsync_offset_ = kInitialFlatlandVsyncOffset; + fml::TimePoint last_presentation_time_; + FireCallbackCallback pending_fire_callback_; + uint32_t present_credits_ = 1; + bool first_feedback_received_ = false; } threadsafe_state_; std::vector acquire_fences_; diff --git a/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.cc b/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.cc index f0137c355dbb0..6a536d585b0fe 100644 --- a/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.cc +++ b/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.cc @@ -23,18 +23,6 @@ FlutterRunnerProductConfiguration::FlutterRunnerProductConfiguration( } // Parse out all values we're expecting. - if (document.HasMember("vsync_offset_in_us")) { - auto& val = document["vsync_offset_in_us"]; - if (val.IsInt()) { - vsync_offset_ = fml::TimeDelta::FromMicroseconds(val.GetInt()); - } - } - if (document.HasMember("max_frames_in_flight")) { - auto& val = document["max_frames_in_flight"]; - if (val.IsInt()) { - max_frames_in_flight_ = val.GetInt(); - } - } if (document.HasMember("intercept_all_input")) { auto& val = document["intercept_all_input"]; if (val.IsBool()) { diff --git a/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.h b/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.h index 3b6fceb7e4e8a..7fe73bc755844 100644 --- a/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.h +++ b/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.h @@ -7,8 +7,6 @@ #include -#include "flutter/fml/time/time_delta.h" - namespace flutter_runner { class FlutterRunnerProductConfiguration { @@ -16,8 +14,6 @@ class FlutterRunnerProductConfiguration { FlutterRunnerProductConfiguration() {} explicit FlutterRunnerProductConfiguration(std::string json_string); - fml::TimeDelta get_vsync_offset() { return vsync_offset_; } - uint64_t get_max_frames_in_flight() { return max_frames_in_flight_; } bool get_intercept_all_input() { return intercept_all_input_; } bool software_rendering() { return software_rendering_; } bool enable_shader_warmup() { return enable_shader_warmup_; } @@ -26,8 +22,6 @@ class FlutterRunnerProductConfiguration { } private: - fml::TimeDelta vsync_offset_ = fml::TimeDelta::Zero(); - uint64_t max_frames_in_flight_ = 3; bool intercept_all_input_ = false; bool software_rendering_ = false; bool enable_shader_warmup_ = false; diff --git a/shell/platform/fuchsia/flutter/tests/external_view_embedder_unittests.cc b/shell/platform/fuchsia/flutter/tests/external_view_embedder_unittests.cc index 69f010687362d..97581fa2a0464 100644 --- a/shell/platform/fuchsia/flutter/tests/external_view_embedder_unittests.cc +++ b/shell/platform/fuchsia/flutter/tests/external_view_embedder_unittests.cc @@ -414,12 +414,9 @@ class ExternalViewEmbedderTest : public ::testing::Test { const auto test_name = ::testing::UnitTest::GetInstance()->current_test_info()->name(); - const auto max_frames_in_flight = 1; - const auto vsync_offset = fml::TimeDelta::Zero(); return std::make_shared( std::move(test_name), std::move(flatland), - /*error_callback=*/[] { FAIL(); }, /*ofpe_callback=*/[](auto...) {}, - max_frames_in_flight, vsync_offset); + /*error_callback=*/[] { FAIL(); }, /*ofpe_callback=*/[](auto...) {}); } // Primary loop and subloop for the FakeFlatland instance to process its diff --git a/shell/platform/fuchsia/flutter/tests/flatland_connection_unittests.cc b/shell/platform/fuchsia/flutter/tests/flatland_connection_unittests.cc index 7aab5477182f7..f70afc92d2511 100644 --- a/shell/platform/fuchsia/flutter/tests/flatland_connection_unittests.cc +++ b/shell/platform/fuchsia/flutter/tests/flatland_connection_unittests.cc @@ -52,11 +52,17 @@ void AwaitVsyncChecked(FlatlandConnection& flatland_connection, } std::vector -CreateFuturePresentationInfos(int presentation_time) { +CreateFuturePresentationInfos(const fml::TimePoint& presentation_time_1, + const fml::TimePoint& presentation_time_2) { fuchsia::scenic::scheduling::PresentationInfo info_1; - info_1.set_presentation_time(presentation_time); + info_1.set_presentation_time( + presentation_time_1.ToEpochDelta().ToNanoseconds()); std::vector infos; infos.push_back(std::move(info_1)); + fuchsia::scenic::scheduling::PresentationInfo info_2; + info_2.set_presentation_time( + presentation_time_2.ToEpochDelta().ToNanoseconds()); + infos.push_back(std::move(info_2)); return infos; } @@ -81,15 +87,25 @@ class FlatlandConnectionTest : public ::testing::Test { } // Syntactic sugar for OnNextFrameBegin - void OnNextFrameBegin(int num_present_credits, int presentation_time = 345) { + void OnNextFrameBegin(int num_present_credits, + const fml::TimePoint& presentation_time_1, + const fml::TimePoint& presentation_time_2) { fuchsia::ui::composition::OnNextFrameBeginValues on_next_frame_begin_values; on_next_frame_begin_values.set_additional_present_credits( num_present_credits); on_next_frame_begin_values.set_future_presentation_infos( - CreateFuturePresentationInfos(presentation_time)); + CreateFuturePresentationInfos(presentation_time_1, + presentation_time_2)); fake_flatland().FireOnNextFrameBeginEvent( std::move(on_next_frame_begin_values)); } + void OnNextFrameBegin(int num_present_credits) { + const auto now = fml::TimePoint::Now(); + const auto kPresentationTime1 = now + fml::TimeDelta::FromSeconds(100); + const auto kPresentationTime2 = now + fml::TimeDelta::FromSeconds(200); + OnNextFrameBegin(num_present_credits, kPresentationTime1, + kPresentationTime2); + } private: async::TestLoop loop_; @@ -106,13 +122,13 @@ TEST_F(FlatlandConnectionTest, Initialization) { const std::string debug_name = GetCurrentTestName(); flutter_runner::FlatlandConnection flatland_connection( debug_name, TakeFlatlandHandle(), []() { FAIL(); }, - [](auto...) { FAIL(); }, 1, fml::TimeDelta::Zero()); + [](auto...) { FAIL(); }); EXPECT_EQ(fake_flatland().debug_name(), ""); - // Simulate an AwaitVsync that comes immediately. + // Simulate an AwaitVsync that returns immediately. bool await_vsync_fired = false; AwaitVsyncChecked(flatland_connection, await_vsync_fired, - kDefaultFlatlandPresentationInterval); + kInitialFlatlandVsyncOffset); EXPECT_TRUE(await_vsync_fired); // Ensure the debug name is set. @@ -129,7 +145,7 @@ TEST_F(FlatlandConnectionTest, FlatlandDisconnect) { // completed yet. flutter_runner::FlatlandConnection flatland_connection( GetCurrentTestName(), TakeFlatlandHandle(), std::move(on_session_error), - [](auto...) { FAIL(); }, 1, fml::TimeDelta::Zero()); + [](auto...) { FAIL(); }); EXPECT_FALSE(error_fired); // Simulate a flatland disconnection, then Pump the loop. The error callback @@ -163,7 +179,7 @@ TEST_F(FlatlandConnectionTest, BasicPresent) { // completed yet. flutter_runner::FlatlandConnection flatland_connection( GetCurrentTestName(), TakeFlatlandHandle(), []() { FAIL(); }, - std::move(on_frame_presented), 1, fml::TimeDelta::Zero()); + std::move(on_frame_presented)); EXPECT_EQ(presents_called, 0u); EXPECT_EQ(vsyncs_handled, 0u); @@ -175,7 +191,7 @@ TEST_F(FlatlandConnectionTest, BasicPresent) { // Simulate an AwaitVsync that comes after the first call. bool await_vsync_fired = false; AwaitVsyncChecked(flatland_connection, await_vsync_fired, - kDefaultFlatlandPresentationInterval); + kInitialFlatlandVsyncOffset); EXPECT_TRUE(await_vsync_fired); // Call Present and Pump the loop; `Present` and its callback is called. No @@ -193,17 +209,17 @@ TEST_F(FlatlandConnectionTest, BasicPresent) { EXPECT_FALSE(await_vsync_fired); // Fire the `OnNextFrameBegin` event. AwaitVsync should be fired. - const int kPresentationTime = 123; - AwaitVsyncChecked(flatland_connection, await_vsync_fired, - fml::TimePoint::FromEpochDelta( - fml::TimeDelta::FromNanoseconds(kPresentationTime))); + const auto now = fml::TimePoint::Now(); + const auto kPresentationTime1 = now + fml::TimeDelta::FromSeconds(100); + const auto kPresentationTime2 = now + fml::TimeDelta::FromSeconds(200); fuchsia::ui::composition::OnNextFrameBeginValues on_next_frame_begin_values; on_next_frame_begin_values.set_additional_present_credits(3); on_next_frame_begin_values.set_future_presentation_infos( - CreateFuturePresentationInfos(kPresentationTime)); + CreateFuturePresentationInfos(kPresentationTime1, kPresentationTime2)); fake_flatland().FireOnNextFrameBeginEvent( std::move(on_next_frame_begin_values)); loop().RunUntilIdle(); + AwaitVsyncChecked(flatland_connection, await_vsync_fired, kPresentationTime1); EXPECT_TRUE(await_vsync_fired); // Fire the `OnFramePresented` event associated with the first `Present`, @@ -219,9 +235,14 @@ TEST_F(FlatlandConnectionTest, BasicPresent) { loop().RunUntilIdle(); EXPECT_EQ(presents_called, 2u); EXPECT_EQ(release_fence_handle, first_release_fence_handle); + + // AwaitVsync should be fired with the second present. + await_vsync_fired = false; + AwaitVsyncChecked(flatland_connection, await_vsync_fired, kPresentationTime2); + EXPECT_TRUE(await_vsync_fired); } -TEST_F(FlatlandConnectionTest, AwaitVsyncBeforePresent) { +TEST_F(FlatlandConnectionTest, AwaitVsyncsBeforeOnNextFrameBegin) { // Set up callbacks which allow sensing of how many presents were handled. size_t presents_called = 0u; fake_flatland().SetPresentHandler( @@ -231,7 +252,7 @@ TEST_F(FlatlandConnectionTest, AwaitVsyncBeforePresent) { // completed yet. flutter_runner::FlatlandConnection flatland_connection( GetCurrentTestName(), TakeFlatlandHandle(), []() { FAIL(); }, - [](auto...) {}, 1, fml::TimeDelta::Zero()); + [](auto...) {}); EXPECT_EQ(presents_called, 0u); // Pump the loop. Nothing is called. @@ -241,28 +262,20 @@ TEST_F(FlatlandConnectionTest, AwaitVsyncBeforePresent) { // Simulate an AwaitVsync that comes before the first Present. bool await_vsync_callback_fired = false; AwaitVsyncChecked(flatland_connection, await_vsync_callback_fired, - kDefaultFlatlandPresentationInterval); - EXPECT_TRUE(await_vsync_callback_fired); - - // Another AwaitVsync that comes before the first Present. - await_vsync_callback_fired = false; - AwaitVsyncChecked(flatland_connection, await_vsync_callback_fired, - kDefaultFlatlandPresentationInterval); + kInitialFlatlandVsyncOffset); EXPECT_TRUE(await_vsync_callback_fired); - // Queue Present. - flatland_connection.Present(); - loop().RunUntilIdle(); - EXPECT_EQ(presents_called, 1u); - - // Set the callback with AwaitVsync, callback should not be fired - await_vsync_callback_fired = false; - AwaitVsyncChecked(flatland_connection, await_vsync_callback_fired, - kDefaultFlatlandPresentationInterval); - EXPECT_FALSE(await_vsync_callback_fired); + // AwaitVsync that comes before the first Present. + bool await_vsync_secondary_callback_fired = false; + flatland_connection.AwaitVsyncForSecondaryCallback( + [&await_vsync_secondary_callback_fired](fml::TimePoint frame_start, + fml::TimePoint frame_end) { + await_vsync_secondary_callback_fired = true; + }); + EXPECT_TRUE(await_vsync_secondary_callback_fired); } -TEST_F(FlatlandConnectionTest, OutOfOrderAwait) { +TEST_F(FlatlandConnectionTest, RunsOutOfFuturePresentationInfos) { // Set up callbacks which allow sensing of how many presents were handled. size_t presents_called = 0u; fake_flatland().SetPresentHandler( @@ -279,7 +292,7 @@ TEST_F(FlatlandConnectionTest, OutOfOrderAwait) { // completed yet. flutter_runner::FlatlandConnection flatland_connection( GetCurrentTestName(), TakeFlatlandHandle(), []() { FAIL(); }, - std::move(on_frame_presented), 1, fml::TimeDelta::Zero()); + std::move(on_frame_presented)); EXPECT_EQ(presents_called, 0u); EXPECT_EQ(vsyncs_handled, 0u); @@ -291,7 +304,7 @@ TEST_F(FlatlandConnectionTest, OutOfOrderAwait) { // Simulate an AwaitVsync that comes before the first Present. bool await_vsync_callback_fired = false; AwaitVsyncChecked(flatland_connection, await_vsync_callback_fired, - kDefaultFlatlandPresentationInterval); + kInitialFlatlandVsyncOffset); EXPECT_TRUE(await_vsync_callback_fired); // Queue Present. @@ -299,49 +312,30 @@ TEST_F(FlatlandConnectionTest, OutOfOrderAwait) { loop().RunUntilIdle(); EXPECT_EQ(presents_called, 1u); - // Set the callback with AwaitVsync, callback should not be fired - await_vsync_callback_fired = false; - const int kPresentationTime1 = 567; - AwaitVsyncChecked(flatland_connection, await_vsync_callback_fired, - fml::TimePoint::FromEpochDelta( - fml::TimeDelta::FromNanoseconds(kPresentationTime1))); - EXPECT_FALSE(await_vsync_callback_fired); - // Fire the `OnNextFrameBegin` event. AwaitVsync callback should be fired with - // the given presentation time. + // the first presentation time. await_vsync_callback_fired = false; - OnNextFrameBegin(1, kPresentationTime1); + const auto kPresentationTime1 = + fml::TimePoint::Now() + fml::TimeDelta::FromSeconds(123); + const auto kVsyncInterval = fml::TimeDelta::FromSeconds(234); + const auto kPresentationTime2 = kPresentationTime1 + kVsyncInterval; + OnNextFrameBegin(1, kPresentationTime1, kPresentationTime2); loop().RunUntilIdle(); + AwaitVsyncChecked(flatland_connection, await_vsync_callback_fired, + kPresentationTime1); EXPECT_TRUE(await_vsync_callback_fired); - // Second consecutive ONFB should not call the fire callback and should - // instead set it to be pending to fire on next AwaitVsync - await_vsync_callback_fired = false; - const int kPresentationTime2 = 678; - OnNextFrameBegin(1, kPresentationTime2); - loop().RunUntilIdle(); - EXPECT_FALSE(await_vsync_callback_fired); - - // Now an AwaitVsync should immediately fire the pending callback with the - // default presentation interval. + // Second consecutive AwaitVsync callback should be fired with + // the second presentation time. await_vsync_callback_fired = false; AwaitVsyncChecked(flatland_connection, await_vsync_callback_fired, - kDefaultFlatlandPresentationInterval); + kPresentationTime2); EXPECT_TRUE(await_vsync_callback_fired); - // With the pending callback fired, The new callback should be set for the - // next OnNextFrameBegin to call + // Another AwaitVsync callback should be fired with vsync_interval. await_vsync_callback_fired = false; - const int kPresentationTime3 = 789; AwaitVsyncChecked(flatland_connection, await_vsync_callback_fired, - fml::TimePoint::FromEpochDelta( - fml::TimeDelta::FromNanoseconds(kPresentationTime3))); - EXPECT_FALSE(await_vsync_callback_fired); - - // Now OnNextFrameBegin should fire the callback - await_vsync_callback_fired = false; - OnNextFrameBegin(1, kPresentationTime3); - loop().RunUntilIdle(); + kPresentationTime2 + kVsyncInterval); EXPECT_TRUE(await_vsync_callback_fired); } @@ -371,7 +365,7 @@ TEST_F(FlatlandConnectionTest, PresentCreditExhaustion) { on_frame_presented_event on_frame_presented = [](auto...) {}; flutter_runner::FlatlandConnection flatland_connection( GetCurrentTestName(), TakeFlatlandHandle(), []() { FAIL(); }, - std::move(on_frame_presented), 1, fml::TimeDelta::Zero()); + std::move(on_frame_presented)); EXPECT_EQ(num_presents_called, 0u); // Pump the loop. Nothing is called. diff --git a/shell/platform/fuchsia/flutter/tests/flutter_runner_product_configuration_unittests.cc b/shell/platform/fuchsia/flutter/tests/flutter_runner_product_configuration_unittests.cc index 2e287dbbdbf65..51fa2a1a3816a 100644 --- a/shell/platform/fuchsia/flutter/tests/flutter_runner_product_configuration_unittests.cc +++ b/shell/platform/fuchsia/flutter/tests/flutter_runner_product_configuration_unittests.cc @@ -4,7 +4,6 @@ #include -#include "flutter/fml/time/time_delta.h" #include "flutter/shell/platform/fuchsia/flutter/flutter_runner_product_configuration.h" using namespace flutter_runner; @@ -13,79 +12,24 @@ namespace flutter_runner_test { class FlutterRunnerProductConfigurationTest : public testing::Test {}; -TEST_F(FlutterRunnerProductConfigurationTest, ValidVsyncOffset) { - const std::string json_string = "{ \"vsync_offset_in_us\" : 9000 } "; - const fml::TimeDelta expected_offset = fml::TimeDelta::FromMicroseconds(9000); - - FlutterRunnerProductConfiguration product_config = - FlutterRunnerProductConfiguration(json_string); - EXPECT_EQ(product_config.get_vsync_offset(), expected_offset); -} - TEST_F(FlutterRunnerProductConfigurationTest, InvalidJsonString) { const std::string json_string = "{ \"invalid json string\" }}} "; - const fml::TimeDelta expected_offset = fml::TimeDelta::FromMicroseconds(0); + const uint64_t expected_intercept_all_input = false; FlutterRunnerProductConfiguration product_config = FlutterRunnerProductConfiguration(json_string); - EXPECT_EQ(product_config.get_vsync_offset(), expected_offset); + EXPECT_EQ(expected_intercept_all_input, + product_config.get_intercept_all_input()); } TEST_F(FlutterRunnerProductConfigurationTest, EmptyJsonString) { const std::string json_string = ""; - const fml::TimeDelta expected_offset = fml::TimeDelta::FromMicroseconds(0); - - FlutterRunnerProductConfiguration product_config = - FlutterRunnerProductConfiguration(json_string); - EXPECT_EQ(product_config.get_vsync_offset(), expected_offset); -} - -TEST_F(FlutterRunnerProductConfigurationTest, EmptyVsyncOffset) { - const std::string json_string = "{ \"vsync_offset_in_us\" : } "; - const fml::TimeDelta expected_offset = fml::TimeDelta::FromMicroseconds(0); - - FlutterRunnerProductConfiguration product_config = - FlutterRunnerProductConfiguration(json_string); - EXPECT_EQ(product_config.get_vsync_offset(), expected_offset); -} - -TEST_F(FlutterRunnerProductConfigurationTest, NegativeVsyncOffset) { - const std::string json_string = "{ \"vsync_offset_in_us\" : -15410 } "; - const fml::TimeDelta expected_offset = - fml::TimeDelta::FromMicroseconds(-15410); - - FlutterRunnerProductConfiguration product_config = - FlutterRunnerProductConfiguration(json_string); - EXPECT_EQ(product_config.get_vsync_offset(), expected_offset); -} - -TEST_F(FlutterRunnerProductConfigurationTest, NonIntegerVsyncOffset) { - const std::string json_string = "{ \"vsync_offset_in_us\" : 3.14159 } "; - const fml::TimeDelta expected_offset = fml::TimeDelta::FromMicroseconds(0); - - FlutterRunnerProductConfiguration product_config = - FlutterRunnerProductConfiguration(json_string); - EXPECT_EQ(product_config.get_vsync_offset(), expected_offset); -} - -TEST_F(FlutterRunnerProductConfigurationTest, ValidMaxFramesInFlight) { - const std::string json_string = "{ \"max_frames_in_flight\" : 5 } "; - const uint64_t expected_max_frames_in_flight = 5; - - FlutterRunnerProductConfiguration product_config = - FlutterRunnerProductConfiguration(json_string); - EXPECT_EQ(product_config.get_max_frames_in_flight(), - expected_max_frames_in_flight); -} - -TEST_F(FlutterRunnerProductConfigurationTest, MissingMaxFramesInFlight) { - const std::string json_string = "{ \"max_frames_in_flight\" : } "; - const uint64_t minimum_reasonable_max_frames_in_flight = 1; + const uint64_t expected_intercept_all_input = false; FlutterRunnerProductConfiguration product_config = FlutterRunnerProductConfiguration(json_string); - EXPECT_GE(product_config.get_max_frames_in_flight(), - minimum_reasonable_max_frames_in_flight); + EXPECT_EQ(expected_intercept_all_input, + product_config.get_intercept_all_input()); } TEST_F(FlutterRunnerProductConfigurationTest, ValidInterceptAllInput) {