Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reland 3: Multiview pipeline #49950

Merged
merged 34 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
1950e72
Impl engine
dkwingsmt Feb 12, 2024
f55dfdf
Web
dkwingsmt Feb 12, 2024
7f76c5f
Rename to RequestWarmUpFrame and add web time
dkwingsmt Feb 13, 2024
31a5ea3
Change to scheduleWarmUpFrame and EndWarmUpFrame
dkwingsmt Feb 13, 2024
590287d
Comment
dkwingsmt Feb 13, 2024
40b269b
Doc
dkwingsmt Feb 13, 2024
2d70a0f
Simplify web implementation
dkwingsmt Feb 14, 2024
4af9f06
Fix comment
dkwingsmt Feb 14, 2024
bfe5570
More doc
dkwingsmt Feb 14, 2024
21439c4
Fix doc
dkwingsmt Feb 14, 2024
1bd21db
More doc
dkwingsmt Feb 14, 2024
4f41658
Fix linter
dkwingsmt Feb 14, 2024
eeac064
Fix comment
dkwingsmt Feb 14, 2024
9c5bce4
Add test
dkwingsmt Feb 15, 2024
cdcf71a
Fix test
dkwingsmt Feb 15, 2024
7ab7d8e
Better test
dkwingsmt Feb 15, 2024
afbcfae
Merge remote-tracking branch 'origin/main' into force-sync-frame
dkwingsmt Feb 15, 2024
7381087
Simplify test and add platformdispatcher test
dkwingsmt Feb 15, 2024
05d3d2d
Better structure
dkwingsmt Feb 15, 2024
06957bb
Better name
dkwingsmt Feb 15, 2024
68953c0
Merge branch 'main' into force-sync-frame
dkwingsmt Feb 15, 2024
a9b7511
Merge branch 'main' into force-sync-frame
dkwingsmt Feb 20, 2024
7789d11
Original changes
dkwingsmt Feb 20, 2024
06b4a50
Merge branch 'force-sync-frame' into reland-3-mv-pipeline-base
dkwingsmt Feb 20, 2024
3464c05
Additional changes
dkwingsmt Feb 20, 2024
fe14311
Comments
dkwingsmt Feb 20, 2024
5b0719b
Merge branch 'main' into reland-3-mv-pipeline
dkwingsmt Feb 20, 2024
8731e47
Add web platform dispatcher test
dkwingsmt Feb 20, 2024
cdeffd9
Merge branch 'main' into force-sync-frame
dkwingsmt Feb 20, 2024
4c91a9e
Merge branch 'force-sync-frame' into reland-3-mv-pipeline
dkwingsmt Feb 20, 2024
34998fa
Fix lint
dkwingsmt Feb 20, 2024
658d5d1
Merge branch 'main' into reland-3-mv-pipeline
dkwingsmt Feb 21, 2024
946ffb5
Merge remote-tracking branch 'origin/main' into reland-3-mv-pipeline
dkwingsmt Feb 23, 2024
f9d5ad3
Merge remote-tracking branch 'dkwingsmt/reland-3-mv-pipeline' into re…
dkwingsmt Feb 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion flow/frame_timings.cc
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,30 @@ const char* FrameTimingsRecorder::GetFrameNumberTraceArg() const {
return frame_number_trace_arg_val_.c_str();
}

static const char* StateToString(FrameTimingsRecorder::State state) {
dkwingsmt marked this conversation as resolved.
Show resolved Hide resolved
#ifndef NDEBUG
switch (state) {
case FrameTimingsRecorder::State::kUninitialized:
return "kUninitialized";
case FrameTimingsRecorder::State::kVsync:
return "kVsync";
case FrameTimingsRecorder::State::kBuildStart:
return "kBuildStart";
case FrameTimingsRecorder::State::kBuildEnd:
return "kBuildEnd";
case FrameTimingsRecorder::State::kRasterStart:
return "kRasterStart";
case FrameTimingsRecorder::State::kRasterEnd:
return "kRasterEnd";
};
FML_UNREACHABLE();
#endif
return "";
}

void FrameTimingsRecorder::AssertInState(State state) const {
FML_DCHECK(state_ == state);
FML_DCHECK(state_ == state) << "Expected state " << StateToString(state)
<< ", actual state " << StateToString(state_);
}

} // namespace flutter
3 changes: 3 additions & 0 deletions flow/frame_timings.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class FrameTimingsRecorder {
public:
/// Various states that the recorder can be in. When created the recorder is
/// in an unitialized state and transtions in sequential order of the states.
// After adding an item to this enum, modify StateToString accordingly.
enum class State : uint32_t {
kUninitialized,
kVsync,
Expand Down Expand Up @@ -121,6 +122,8 @@ class FrameTimingsRecorder {
///
/// Instead of adding a `GetState` method and asserting on the result, this
/// method prevents other logic from relying on the state.
///
/// In opt builds, this call is a no-op.
dkwingsmt marked this conversation as resolved.
Show resolved Hide resolved
void AssertInState(State state) const;

private:
Expand Down
1 change: 1 addition & 0 deletions lib/ui/dart_ui.cc
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ typedef CanvasPath Path;
V(NativeStringAttribute::initSpellOutStringAttribute) \
V(PlatformConfigurationNativeApi::DefaultRouteName) \
V(PlatformConfigurationNativeApi::ScheduleFrame) \
V(PlatformConfigurationNativeApi::EndWarmUpFrame) \
V(PlatformConfigurationNativeApi::Render) \
V(PlatformConfigurationNativeApi::UpdateSemantics) \
V(PlatformConfigurationNativeApi::SetNeedsReportTimings) \
Expand Down
10 changes: 9 additions & 1 deletion lib/ui/painting/image_dispose_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#define FML_USED_ON_EMBEDDER

#include "flutter/common/task_runners.h"
#include "flutter/fml/synchronization/count_down_latch.h"
#include "flutter/fml/synchronization/waitable_event.h"
#include "flutter/lib/ui/painting/canvas.h"
#include "flutter/lib/ui/painting/image.h"
Expand Down Expand Up @@ -57,6 +58,10 @@ TEST_F(ImageDisposeTest, ImageReleasedAfterFrameAndDisposePictureAndLayer) {
};

Settings settings = CreateSettingsForFixture();
fml::CountDownLatch frame_latch{2};
settings.frame_rasterized_callback = [&frame_latch](const FrameTiming& t) {
frame_latch.CountDown();
};
auto task_runner = CreateNewThread();
TaskRunners task_runners("test", // label
GetCurrentTaskRunner(), // platform
Expand All @@ -83,12 +88,15 @@ TEST_F(ImageDisposeTest, ImageReleasedAfterFrameAndDisposePictureAndLayer) {
shell->RunEngine(std::move(configuration), [&](auto result) {
ASSERT_EQ(result, Engine::RunStatus::Success);
});

message_latch_.Wait();

ASSERT_TRUE(current_display_list_);
ASSERT_TRUE(current_image_);

// Wait for 2 frames to be rasterized. The 2nd frame releases resources of the
// 1st frame.
frame_latch.Wait();

// Force a drain the SkiaUnrefQueue. The engine does this normally as frames
// pump, but we force it here to make the test more deterministic.
message_latch_.Reset();
Expand Down
35 changes: 35 additions & 0 deletions lib/ui/platform_dispatcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -801,11 +801,46 @@ class PlatformDispatcher {
///
/// * [SchedulerBinding], the Flutter framework class which manages the
/// scheduling of frames.
/// * [scheduleWarmUpFrame], which should only be used to schedule warm up
/// frames.
void scheduleFrame() => _scheduleFrame();

@Native<Void Function()>(symbol: 'PlatformConfigurationNativeApi::ScheduleFrame')
external static void _scheduleFrame();

/// Schedule a frame to run as soon as possible, rather than waiting for the
/// engine to request a frame in response to a system "Vsync" signal.
///
/// This method is used during application startup so that the first frame
/// (which is likely to be quite expensive) can start a few extra milliseconds
/// earlier. Using it in other situations might lead to unintended results,
/// such as screen tearing. Depending on platforms and situations, the warm up
/// frame might or might not be actually rendered onto the screen.
///
/// For more introduction to the warm up frame, see
/// [SchedulerBinding.scheduleWarmUpFrame].
///
/// This method uses the provided callbacks as the begin frame callback and
/// the draw frame callback instead of [onBeginFrame] and [onDrawFrame].
///
/// See also:
///
/// * [SchedulerBinding.scheduleWarmUpFrame], which uses this method, and
/// introduces the warm up frame in more details.
/// * [scheduleFrame], which schedules the frame at the next appropriate
/// opportunity and should be used to render regular frames.
void scheduleWarmUpFrame({required VoidCallback beginFrame, required VoidCallback drawFrame}) {
// We use timers here to ensure that microtasks flush in between.
Timer.run(beginFrame);
Timer.run(() {
drawFrame();
_endWarmUpFrame();
});
}

@Native<Void Function()>(symbol: 'PlatformConfigurationNativeApi::EndWarmUpFrame')
external static void _endWarmUpFrame();

/// Additional accessibility features that may be enabled by the platform.
AccessibilityFeatures get accessibilityFeatures => _configuration.accessibilityFeatures;

Expand Down
10 changes: 6 additions & 4 deletions lib/ui/window/platform_configuration.cc
Original file line number Diff line number Diff line change
Expand Up @@ -453,12 +453,9 @@ void PlatformConfigurationNativeApi::Render(int64_t view_id,
Scene* scene,
double width,
double height) {
// TODO(dkwingsmt): Currently only supports a single window.
// See https://github.com/flutter/flutter/issues/135530, item 2.
FML_DCHECK(view_id == kFlutterImplicitViewId);
UIDartState::ThrowIfUIOperationsProhibited();
UIDartState::Current()->platform_configuration()->client()->Render(
scene, width, height);
view_id, scene, width, height);
}

void PlatformConfigurationNativeApi::SetNeedsReportTimings(bool value) {
Expand Down Expand Up @@ -589,6 +586,11 @@ void PlatformConfigurationNativeApi::ScheduleFrame() {
UIDartState::Current()->platform_configuration()->client()->ScheduleFrame();
}

void PlatformConfigurationNativeApi::EndWarmUpFrame() {
UIDartState::ThrowIfUIOperationsProhibited();
UIDartState::Current()->platform_configuration()->client()->EndWarmUpFrame();
}

void PlatformConfigurationNativeApi::UpdateSemantics(SemanticsUpdate* update) {
UIDartState::ThrowIfUIOperationsProhibited();
UIDartState::Current()->platform_configuration()->client()->UpdateSemantics(
Expand Down
14 changes: 13 additions & 1 deletion lib/ui/window/platform_configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,21 @@ class PlatformConfigurationClient {
///
virtual void ScheduleFrame() = 0;

//--------------------------------------------------------------------------
/// @brief Called when a warm up frame has ended.
///
/// For more introduction, see `Animator::EndWarmUpFrame`.
///
virtual void EndWarmUpFrame() = 0;

//--------------------------------------------------------------------------
/// @brief Updates the client's rendering on the GPU with the newly
/// provided Scene.
///
virtual void Render(Scene* scene, double width, double height) = 0;
virtual void Render(int64_t view_id,
Scene* scene,
double width,
double height) = 0;

//--------------------------------------------------------------------------
/// @brief Receives an updated semantics tree from the Framework.
Expand Down Expand Up @@ -557,6 +567,8 @@ class PlatformConfigurationNativeApi {

static void ScheduleFrame();

static void EndWarmUpFrame();

static void Render(int64_t view_id,
Scene* scene,
double width,
Expand Down
2 changes: 2 additions & 0 deletions lib/web_ui/lib/platform_dispatcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ abstract class PlatformDispatcher {

void scheduleFrame();

void scheduleWarmUpFrame({required VoidCallback beginFrame, required VoidCallback drawFrame});

AccessibilityFeatures get accessibilityFeatures;

VoidCallback? get onAccessibilityFeaturesChanged;
Expand Down
7 changes: 7 additions & 0 deletions lib/web_ui/lib/src/engine/platform_dispatcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,13 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
scheduleFrameCallback!();
}

@override
void scheduleWarmUpFrame({required ui.VoidCallback beginFrame, required ui.VoidCallback drawFrame}) {
Timer.run(beginFrame);
// We use timers here to ensure that microtasks flush in between.
Timer.run(drawFrame);
}

/// Updates the application's rendering on the GPU with the newly provided
/// [Scene]. This function must be called within the scope of the
/// [onBeginFrame] or [onDrawFrame] callbacks being invoked. If this function
Expand Down
13 changes: 9 additions & 4 deletions runtime/runtime_controller.cc
Original file line number Diff line number Diff line change
Expand Up @@ -340,16 +340,21 @@ void RuntimeController::ScheduleFrame() {
client_.ScheduleFrame();
}

void RuntimeController::EndWarmUpFrame() {
client_.EndWarmUpFrame();
}

// |PlatformConfigurationClient|
void RuntimeController::Render(Scene* scene, double width, double height) {
// TODO(dkwingsmt): Currently only supports a single window.
int64_t view_id = kFlutterImplicitViewId;
void RuntimeController::Render(int64_t view_id,
Scene* scene,
double width,
double height) {
const ViewportMetrics* view_metrics =
UIDartState::Current()->platform_configuration()->GetMetrics(view_id);
if (view_metrics == nullptr) {
return;
}
client_.Render(scene->takeLayerTree(width, height),
client_.Render(view_id, scene->takeLayerTree(width, height),
view_metrics->device_pixel_ratio);
}

Expand Down
8 changes: 7 additions & 1 deletion runtime/runtime_controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,13 @@ class RuntimeController : public PlatformConfigurationClient {
void ScheduleFrame() override;

// |PlatformConfigurationClient|
void Render(Scene* scene, double width, double height) override;
void EndWarmUpFrame() override;

// |PlatformConfigurationClient|
void Render(int64_t view_id,
Scene* scene,
double width,
double height) override;

// |PlatformConfigurationClient|
void UpdateSemantics(SemanticsUpdate* update) override;
Expand Down
5 changes: 4 additions & 1 deletion runtime/runtime_delegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ class RuntimeDelegate {

virtual void ScheduleFrame(bool regenerate_layer_trees = true) = 0;

virtual void Render(std::unique_ptr<flutter::LayerTree> layer_tree,
virtual void EndWarmUpFrame() = 0;

virtual void Render(int64_t view_id,
std::unique_ptr<flutter::LayerTree> layer_tree,
float device_pixel_ratio) = 0;

virtual void UpdateSemantics(SemanticsNodeUpdates update,
Expand Down
74 changes: 45 additions & 29 deletions shell/common/animator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ void Animator::BeginFrame(
std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder) {
TRACE_EVENT_ASYNC_END0("flutter", "Frame Request Pending",
frame_request_number_);
// Clear layer trees rendered out of a frame. Only Animator::Render called
// within a frame is used.
layer_trees_tasks_.clear();
dkwingsmt marked this conversation as resolved.
Show resolved Hide resolved

frame_request_number_++;

frame_timings_recorder_ = std::move(frame_timings_recorder);
Expand Down Expand Up @@ -112,6 +116,33 @@ void Animator::BeginFrame(
dart_frame_deadline_ = frame_target_time.ToEpochDelta();
uint64_t frame_number = frame_timings_recorder_->GetFrameNumber();
delegate_.OnAnimatorBeginFrame(frame_target_time, frame_number);
}

void Animator::EndFrame() {
FML_DCHECK(frame_timings_recorder_ != nullptr);
if (!layer_trees_tasks_.empty()) {
// The build is completed in OnAnimatorBeginFrame.
frame_timings_recorder_->RecordBuildEnd(fml::TimePoint::Now());

delegate_.OnAnimatorUpdateLatestFrameTargetTime(
frame_timings_recorder_->GetVsyncTargetTime());

// Commit the pending continuation.
PipelineProduceResult result =
producer_continuation_.Complete(std::make_unique<FrameItem>(
std::move(layer_trees_tasks_), std::move(frame_timings_recorder_)));

if (!result.success) {
FML_DLOG(INFO) << "Failed to commit to the pipeline";
} else if (!result.is_first_item) {
// Do nothing. It has been successfully pushed to the pipeline but not as
// the first item. Eventually the 'Rasterizer' will consume it, so we
// don't need to notify the delegate.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm suspicious that this branch might be cuasing the redness on CI.

} else {
delegate_.OnAnimatorDraw(layer_tree_pipeline_);
}
}
frame_timings_recorder_ = nullptr;

if (!frame_scheduled_ && has_rendered_) {
// Wait a tad more than 3 60hz frames before reporting a big idle period.
Expand Down Expand Up @@ -139,14 +170,18 @@ void Animator::BeginFrame(
},
kNotifyIdleTaskWaitTime);
}
FML_DCHECK(layer_trees_tasks_.empty());
FML_DCHECK(frame_timings_recorder_ == nullptr);
}

void Animator::Render(std::unique_ptr<flutter::LayerTree> layer_tree,
void Animator::Render(int64_t view_id,
std::unique_ptr<flutter::LayerTree> layer_tree,
float device_pixel_ratio) {
has_rendered_ = true;

if (!frame_timings_recorder_) {
// Framework can directly call render with a built scene.
// Framework can directly call render with a built scene. A major reason is
// to render warm up frames.
frame_timings_recorder_ = std::make_unique<FrameTimingsRecorder>();
const fml::TimePoint placeholder_time = fml::TimePoint::Now();
frame_timings_recorder_->RecordVsync(placeholder_time, placeholder_time);
Expand All @@ -156,35 +191,9 @@ void Animator::Render(std::unique_ptr<flutter::LayerTree> layer_tree,
TRACE_EVENT_WITH_FRAME_NUMBER(frame_timings_recorder_, "flutter",
"Animator::Render", /*flow_id_count=*/0,
/*flow_ids=*/nullptr);
frame_timings_recorder_->RecordBuildEnd(fml::TimePoint::Now());

delegate_.OnAnimatorUpdateLatestFrameTargetTime(
frame_timings_recorder_->GetVsyncTargetTime());

// TODO(dkwingsmt): Currently only supports a single window.
// See https://github.com/flutter/flutter/issues/135530, item 2.
int64_t view_id = kFlutterImplicitViewId;
std::vector<std::unique_ptr<LayerTreeTask>> layer_trees_tasks;
layer_trees_tasks.push_back(std::make_unique<LayerTreeTask>(
layer_trees_tasks_.push_back(std::make_unique<LayerTreeTask>(
view_id, std::move(layer_tree), device_pixel_ratio));
// Commit the pending continuation.
PipelineProduceResult result =
producer_continuation_.Complete(std::make_unique<FrameItem>(
std::move(layer_trees_tasks), std::move(frame_timings_recorder_)));

if (!result.success) {
FML_DLOG(INFO) << "No pending continuation to commit";
return;
}

if (!result.is_first_item) {
// It has been successfully pushed to the pipeline but not as the first
// item. Eventually the 'Rasterizer' will consume it, so we don't need to
// notify the delegate.
return;
}

delegate_.OnAnimatorDraw(layer_tree_pipeline_);
}

const std::weak_ptr<VsyncWaiter> Animator::GetVsyncWaiter() const {
Expand Down Expand Up @@ -256,6 +265,7 @@ void Animator::AwaitVSync() {
self->DrawLastLayerTrees(std::move(frame_timings_recorder));
} else {
self->BeginFrame(std::move(frame_timings_recorder));
self->EndFrame();
}
}
});
Expand All @@ -264,6 +274,12 @@ void Animator::AwaitVSync() {
}
}

void Animator::EndWarmUpFrame() {
if (!layer_trees_tasks_.empty()) {
EndFrame();
}
}

void Animator::ScheduleSecondaryVsyncCallback(uintptr_t id,
const fml::closure& callback) {
waiter_->ScheduleSecondaryCallback(id, callback);
Expand Down
Loading