Add scheduleWarmUpFrame (flutter/engine#50570)
This PR adds `PlatformDispatcher.scheduleWarmUpFrame`. This PR is needed for the follow up changes: * The framework will switch to using this function to render warmup frames in https://github.com/flutter/flutter/pull/143290. * Then the engine will finally be able to switch to multiview pipeline with no regression on startup timing in https://github.com/flutter/engine/pull/49950. For why the warm up frame must involve the engine to render, see https://github.com/flutter/flutter/issues/142851. ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide] and the [C++, Objective-C, Java style guides]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I added new tests to check the change I am making or feature I am adding, or the PR is [test-exempt]. See [testing the engine] for instructions on writing and running engine tests. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I signed the [CLA]. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/wiki/Tree-hygiene#overview [Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene [test-exempt]: https://github.com/flutter/flutter/wiki/Tree-hygiene#tests [Flutter Style Guide]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style [testing the engine]: https://github.com/flutter/flutter/wiki/Testing-the-engine [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/wiki/Chat
This commit is contained in:
parent
b357d7a45e
commit
c5e0858a01
@ -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) \
|
||||
|
@ -801,11 +801,47 @@ 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.
|
||||
///
|
||||
/// The application can call this method as soon as it starts up 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;
|
||||
|
||||
|
@ -589,6 +589,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(
|
||||
|
@ -65,6 +65,13 @@ 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.
|
||||
@ -557,6 +564,8 @@ class PlatformConfigurationNativeApi {
|
||||
|
||||
static void ScheduleFrame();
|
||||
|
||||
static void EndWarmUpFrame();
|
||||
|
||||
static void Render(int64_t view_id,
|
||||
Scene* scene,
|
||||
double width,
|
||||
|
@ -90,6 +90,8 @@ abstract class PlatformDispatcher {
|
||||
|
||||
void scheduleFrame();
|
||||
|
||||
void scheduleWarmUpFrame({required VoidCallback beginFrame, required VoidCallback drawFrame});
|
||||
|
||||
AccessibilityFeatures get accessibilityFeatures;
|
||||
|
||||
VoidCallback? get onAccessibilityFeaturesChanged;
|
||||
|
@ -187,7 +187,8 @@ Future<void> initializeEngineServices({
|
||||
// TODO(yjbanov): technically Flutter flushes microtasks between
|
||||
// onBeginFrame and onDrawFrame. We don't, which hasn't
|
||||
// been an issue yet, but eventually we'll have to
|
||||
// implement it properly.
|
||||
// implement it properly. (Also see the to-do in
|
||||
// `EnginePlatformDispatcher.scheduleWarmUpFrame`).
|
||||
EnginePlatformDispatcher.instance.invokeOnDrawFrame();
|
||||
}
|
||||
});
|
||||
|
@ -782,6 +782,19 @@ 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.
|
||||
//
|
||||
// TODO(dkwingsmt): This logic was moved from the framework and is different
|
||||
// from how Web renders a regular frame, which doesn't flush microtasks
|
||||
// between the callbacks at all (see `initializeEngineServices`). We might
|
||||
// want to change this. See the to-do in `initializeEngineServices` and
|
||||
// https://github.com/flutter/engine/pull/50570#discussion_r1496671676
|
||||
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
|
||||
|
@ -417,6 +417,23 @@ void testMain() {
|
||||
dispatcher.dispose();
|
||||
expect(dispatcher.accessibilityPlaceholder.isConnected, isFalse);
|
||||
});
|
||||
|
||||
test('scheduleWarmupFrame should call both callbacks', () async {
|
||||
bool beginFrameCalled = false;
|
||||
final Completer<void> drawFrameCalled = Completer<void>();
|
||||
dispatcher.scheduleWarmUpFrame(beginFrame: () {
|
||||
expect(drawFrameCalled.isCompleted, false);
|
||||
expect(beginFrameCalled, false);
|
||||
beginFrameCalled = true;
|
||||
}, drawFrame: () {
|
||||
expect(beginFrameCalled, true);
|
||||
expect(drawFrameCalled.isCompleted, false);
|
||||
drawFrameCalled.complete();
|
||||
});
|
||||
await drawFrameCalled.future;
|
||||
expect(beginFrameCalled, true);
|
||||
expect(drawFrameCalled.isCompleted, true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -340,6 +340,11 @@ void RuntimeController::ScheduleFrame() {
|
||||
client_.ScheduleFrame();
|
||||
}
|
||||
|
||||
// |PlatformConfigurationClient|
|
||||
void RuntimeController::EndWarmUpFrame() {
|
||||
client_.EndWarmUpFrame();
|
||||
}
|
||||
|
||||
// |PlatformConfigurationClient|
|
||||
void RuntimeController::Render(Scene* scene, double width, double height) {
|
||||
// TODO(dkwingsmt): Currently only supports a single window.
|
||||
|
@ -657,6 +657,9 @@ class RuntimeController : public PlatformConfigurationClient {
|
||||
// |PlatformConfigurationClient|
|
||||
void ScheduleFrame() override;
|
||||
|
||||
// |PlatformConfigurationClient|
|
||||
void EndWarmUpFrame() override;
|
||||
|
||||
// |PlatformConfigurationClient|
|
||||
void Render(Scene* scene, double width, double height) override;
|
||||
|
||||
|
@ -25,6 +25,8 @@ class RuntimeDelegate {
|
||||
|
||||
virtual void ScheduleFrame(bool regenerate_layer_trees = true) = 0;
|
||||
|
||||
virtual void EndWarmUpFrame() = 0;
|
||||
|
||||
virtual void Render(std::unique_ptr<flutter::LayerTree> layer_tree,
|
||||
float device_pixel_ratio) = 0;
|
||||
|
||||
|
@ -264,6 +264,12 @@ void Animator::AwaitVSync() {
|
||||
}
|
||||
}
|
||||
|
||||
void Animator::EndWarmUpFrame() {
|
||||
// Do nothing. The warm up frame does not need any additional work to end the
|
||||
// frame for now. This will change once the pipeline supports multi-view.
|
||||
// https://github.com/flutter/flutter/issues/142851
|
||||
}
|
||||
|
||||
void Animator::ScheduleSecondaryVsyncCallback(uintptr_t id,
|
||||
const fml::closure& callback) {
|
||||
waiter_->ScheduleSecondaryCallback(id, callback);
|
||||
|
@ -53,6 +53,22 @@ class Animator final {
|
||||
|
||||
void RequestFrame(bool regenerate_layer_trees = true);
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/// @brief Tells the Animator that a warm up frame has ended.
|
||||
///
|
||||
/// In a warm up frame, `Animator::Render` is called out of vsync
|
||||
/// tasks, and Animator requires an explicit end-of-frame call to
|
||||
/// know when to send the layer trees to the pipeline.
|
||||
///
|
||||
/// This is different from regular frames, where Animator::Render is
|
||||
/// always called within a vsync task, and Animator can send
|
||||
/// the views at the end of the vsync task.
|
||||
///
|
||||
/// For more about warm up frames, see
|
||||
/// `PlatformDispatcher.scheduleWarmUpFrame`.
|
||||
///
|
||||
void EndWarmUpFrame();
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
/// @brief Tells the Animator that this frame needs to render another view.
|
||||
///
|
||||
|
@ -462,6 +462,10 @@ void Engine::ScheduleFrame(bool regenerate_layer_trees) {
|
||||
animator_->RequestFrame(regenerate_layer_trees);
|
||||
}
|
||||
|
||||
void Engine::EndWarmUpFrame() {
|
||||
animator_->EndWarmUpFrame();
|
||||
}
|
||||
|
||||
void Engine::Render(std::unique_ptr<flutter::LayerTree> layer_tree,
|
||||
float device_pixel_ratio) {
|
||||
if (!layer_tree) {
|
||||
|
@ -837,6 +837,9 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
|
||||
/// tree.
|
||||
void ScheduleFrame() { ScheduleFrame(true); }
|
||||
|
||||
// |RuntimeDelegate|
|
||||
void EndWarmUpFrame() override;
|
||||
|
||||
// |RuntimeDelegate|
|
||||
FontCollection& GetFontCollection() override;
|
||||
|
||||
|
@ -6,7 +6,10 @@
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "flutter/common/constants.h"
|
||||
#include "flutter/runtime/dart_vm_lifecycle.h"
|
||||
#include "flutter/shell/common/shell.h"
|
||||
#include "flutter/shell/common/shell_test.h"
|
||||
#include "flutter/shell/common/thread_host.h"
|
||||
#include "flutter/testing/fixture_test.h"
|
||||
#include "flutter/testing/testing.h"
|
||||
@ -19,6 +22,19 @@ namespace flutter {
|
||||
|
||||
namespace {
|
||||
|
||||
using ::testing::Invoke;
|
||||
using ::testing::ReturnRef;
|
||||
|
||||
static void PostSync(const fml::RefPtr<fml::TaskRunner>& task_runner,
|
||||
const fml::closure& task) {
|
||||
fml::AutoResetWaitableEvent latch;
|
||||
fml::TaskRunner::RunNowOrPostTask(task_runner, [&latch, &task] {
|
||||
task();
|
||||
latch.Signal();
|
||||
});
|
||||
latch.Wait();
|
||||
}
|
||||
|
||||
class MockDelegate : public Engine::Delegate {
|
||||
public:
|
||||
MOCK_METHOD(void,
|
||||
@ -63,6 +79,7 @@ class MockRuntimeDelegate : public RuntimeDelegate {
|
||||
public:
|
||||
MOCK_METHOD(std::string, DefaultRouteName, (), (override));
|
||||
MOCK_METHOD(void, ScheduleFrame, (bool), (override));
|
||||
MOCK_METHOD(void, EndWarmUpFrame, (), (override));
|
||||
MOCK_METHOD(void,
|
||||
Render,
|
||||
(std::unique_ptr<flutter::LayerTree>, float),
|
||||
@ -117,6 +134,51 @@ class MockRuntimeController : public RuntimeController {
|
||||
MOCK_METHOD(bool, NotifyIdle, (fml::TimeDelta), (override));
|
||||
};
|
||||
|
||||
class MockAnimatorDelegate : public Animator::Delegate {
|
||||
public:
|
||||
/* Animator::Delegate */
|
||||
MOCK_METHOD(void,
|
||||
OnAnimatorBeginFrame,
|
||||
(fml::TimePoint frame_target_time, uint64_t frame_number),
|
||||
(override));
|
||||
MOCK_METHOD(void,
|
||||
OnAnimatorNotifyIdle,
|
||||
(fml::TimeDelta deadline),
|
||||
(override));
|
||||
MOCK_METHOD(void,
|
||||
OnAnimatorUpdateLatestFrameTargetTime,
|
||||
(fml::TimePoint frame_target_time),
|
||||
(override));
|
||||
MOCK_METHOD(void,
|
||||
OnAnimatorDraw,
|
||||
(std::shared_ptr<FramePipeline> pipeline),
|
||||
(override));
|
||||
MOCK_METHOD(void,
|
||||
OnAnimatorDrawLastLayerTrees,
|
||||
(std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder),
|
||||
(override));
|
||||
};
|
||||
|
||||
class MockPlatformMessageHandler : public PlatformMessageHandler {
|
||||
public:
|
||||
MOCK_METHOD(void,
|
||||
HandlePlatformMessage,
|
||||
(std::unique_ptr<PlatformMessage> message),
|
||||
(override));
|
||||
MOCK_METHOD(bool,
|
||||
DoesHandlePlatformMessageOnPlatformThread,
|
||||
(),
|
||||
(const, override));
|
||||
MOCK_METHOD(void,
|
||||
InvokePlatformMessageResponseCallback,
|
||||
(int response_id, std::unique_ptr<fml::Mapping> mapping),
|
||||
(override));
|
||||
MOCK_METHOD(void,
|
||||
InvokePlatformMessageEmptyResponseCallback,
|
||||
(int response_id),
|
||||
(override));
|
||||
};
|
||||
|
||||
std::unique_ptr<PlatformMessage> MakePlatformMessage(
|
||||
const std::string& channel,
|
||||
const std::map<std::string, std::string>& values,
|
||||
@ -185,6 +247,96 @@ class EngineTest : public testing::FixtureTest {
|
||||
std::shared_ptr<fml::ConcurrentTaskRunner> image_decoder_task_runner_;
|
||||
fml::TaskRunnerAffineWeakPtr<SnapshotDelegate> snapshot_delegate_;
|
||||
};
|
||||
|
||||
// A class that can launch an Engine with the specified Engine::Delegate.
|
||||
//
|
||||
// To use this class, contruct this class with Create, call Run, and use the
|
||||
// engine with EngineTaskSync().
|
||||
class EngineContext {
|
||||
public:
|
||||
using EngineCallback = std::function<void(Engine&)>;
|
||||
|
||||
[[nodiscard]] static std::unique_ptr<EngineContext> Create(
|
||||
Engine::Delegate& delegate, //
|
||||
Settings settings, //
|
||||
const TaskRunners& task_runners, //
|
||||
std::unique_ptr<Animator> animator) {
|
||||
auto [vm, isolate_snapshot] = Shell::InferVmInitDataFromSettings(settings);
|
||||
FML_CHECK(vm) << "Must be able to initialize the VM.";
|
||||
// Construct the class with `new` because `make_unique` has no access to the
|
||||
// private constructor.
|
||||
EngineContext* raw_pointer =
|
||||
new EngineContext(delegate, settings, task_runners, std::move(animator),
|
||||
std::move(vm), isolate_snapshot);
|
||||
return std::unique_ptr<EngineContext>(raw_pointer);
|
||||
}
|
||||
|
||||
void Run(RunConfiguration configuration) {
|
||||
PostSync(task_runners_.GetUITaskRunner(), [this, &configuration] {
|
||||
Engine::RunStatus run_status = engine_->Run(std::move(configuration));
|
||||
FML_CHECK(run_status == Engine::RunStatus::Success)
|
||||
<< "Engine failed to run.";
|
||||
(void)run_status; // Suppress unused-variable warning
|
||||
});
|
||||
}
|
||||
|
||||
// Run a task that operates the Engine on the UI thread, and wait for the
|
||||
// task to end.
|
||||
//
|
||||
// If called on the UI thread, the task is executed synchronously.
|
||||
void EngineTaskSync(EngineCallback task) {
|
||||
ASSERT_TRUE(engine_);
|
||||
ASSERT_TRUE(task);
|
||||
auto runner = task_runners_.GetUITaskRunner();
|
||||
if (runner->RunsTasksOnCurrentThread()) {
|
||||
task(*engine_);
|
||||
} else {
|
||||
PostSync(task_runners_.GetUITaskRunner(), [&]() { task(*engine_); });
|
||||
}
|
||||
}
|
||||
|
||||
~EngineContext() {
|
||||
PostSync(task_runners_.GetUITaskRunner(), [this] { engine_.reset(); });
|
||||
}
|
||||
|
||||
private:
|
||||
EngineContext(Engine::Delegate& delegate, //
|
||||
Settings settings, //
|
||||
const TaskRunners& task_runners, //
|
||||
std::unique_ptr<Animator> animator, //
|
||||
DartVMRef vm, //
|
||||
fml::RefPtr<const DartSnapshot> isolate_snapshot)
|
||||
: task_runners_(task_runners), vm_(std::move(vm)) {
|
||||
PostSync(task_runners.GetUITaskRunner(), [this, &settings, &animator,
|
||||
&delegate, &isolate_snapshot] {
|
||||
auto dispatcher_maker =
|
||||
[](DefaultPointerDataDispatcher::Delegate& delegate) {
|
||||
return std::make_unique<DefaultPointerDataDispatcher>(delegate);
|
||||
};
|
||||
engine_ = std::make_unique<Engine>(
|
||||
/*delegate=*/delegate,
|
||||
/*dispatcher_maker=*/dispatcher_maker,
|
||||
/*vm=*/*&vm_,
|
||||
/*isolate_snapshot=*/std::move(isolate_snapshot),
|
||||
/*task_runners=*/task_runners_,
|
||||
/*platform_data=*/PlatformData(),
|
||||
/*settings=*/settings,
|
||||
/*animator=*/std::move(animator),
|
||||
/*io_manager=*/io_manager_,
|
||||
/*unref_queue=*/nullptr,
|
||||
/*snapshot_delegate=*/snapshot_delegate_,
|
||||
/*volatile_path_tracker=*/nullptr,
|
||||
/*gpu_disabled_switch=*/std::make_shared<fml::SyncSwitch>());
|
||||
});
|
||||
}
|
||||
|
||||
TaskRunners task_runners_;
|
||||
DartVMRef vm_;
|
||||
std::unique_ptr<Engine> engine_;
|
||||
|
||||
fml::WeakPtr<IOManager> io_manager_;
|
||||
fml::TaskRunnerAffineWeakPtr<SnapshotDelegate> snapshot_delegate_;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_F(EngineTest, Create) {
|
||||
@ -418,4 +570,69 @@ TEST_F(EngineTest, PassesLoadDartDeferredLibraryErrorToRuntime) {
|
||||
});
|
||||
}
|
||||
|
||||
// The animator should submit to the pipeline the implicit view rendered in a
|
||||
// warm up frame if there's already a continuation (i.e. Animator::BeginFrame
|
||||
// has been called)
|
||||
TEST_F(EngineTest, AnimatorSubmitWarmUpImplicitView) {
|
||||
MockAnimatorDelegate animator_delegate;
|
||||
std::unique_ptr<EngineContext> engine_context;
|
||||
|
||||
std::shared_ptr<PlatformMessageHandler> platform_message_handler =
|
||||
std::make_shared<MockPlatformMessageHandler>();
|
||||
EXPECT_CALL(delegate_, GetPlatformMessageHandler)
|
||||
.WillOnce(ReturnRef(platform_message_handler));
|
||||
|
||||
fml::AutoResetWaitableEvent continuation_ready_latch;
|
||||
fml::AutoResetWaitableEvent draw_latch;
|
||||
EXPECT_CALL(animator_delegate, OnAnimatorDraw)
|
||||
.WillOnce(Invoke([&draw_latch](
|
||||
const std::shared_ptr<FramePipeline>& pipeline) {
|
||||
auto status = pipeline->Consume([&](std::unique_ptr<FrameItem> item) {
|
||||
EXPECT_EQ(item->layer_tree_tasks.size(), 1u);
|
||||
EXPECT_EQ(item->layer_tree_tasks[0]->view_id, kFlutterImplicitViewId);
|
||||
});
|
||||
EXPECT_EQ(status, PipelineConsumeResult::Done);
|
||||
draw_latch.Signal();
|
||||
}));
|
||||
EXPECT_CALL(animator_delegate, OnAnimatorBeginFrame)
|
||||
.WillRepeatedly(
|
||||
Invoke([&engine_context, &continuation_ready_latch](
|
||||
fml::TimePoint frame_target_time, uint64_t frame_number) {
|
||||
continuation_ready_latch.Signal();
|
||||
engine_context->EngineTaskSync([&](Engine& engine) {
|
||||
engine.BeginFrame(frame_target_time, frame_number);
|
||||
});
|
||||
}));
|
||||
|
||||
std::unique_ptr<Animator> animator;
|
||||
PostSync(task_runners_.GetUITaskRunner(),
|
||||
[&animator, &animator_delegate, &task_runners = task_runners_] {
|
||||
animator = std::make_unique<Animator>(
|
||||
animator_delegate, task_runners,
|
||||
static_cast<std::unique_ptr<VsyncWaiter>>(
|
||||
std::make_unique<testing::ConstantFiringVsyncWaiter>(
|
||||
task_runners)));
|
||||
});
|
||||
|
||||
engine_context = EngineContext::Create(delegate_, settings_, task_runners_,
|
||||
std::move(animator));
|
||||
|
||||
engine_context->EngineTaskSync([](Engine& engine) {
|
||||
// Schedule a frame to trigger Animator::BeginFrame to create a
|
||||
// continuation. The continuation needs to be available before `Engine::Run`
|
||||
// since the Dart program immediately schedules a warm up frame.
|
||||
engine.ScheduleFrame(true);
|
||||
// Add the implicit view so that the engine recognizes it and that its
|
||||
// metrics is not empty.
|
||||
engine.AddView(kFlutterImplicitViewId, ViewportMetrics{1.0, 10, 10, 1, 0});
|
||||
});
|
||||
continuation_ready_latch.Wait();
|
||||
|
||||
auto configuration = RunConfiguration::InferFromSettings(settings_);
|
||||
configuration.SetEntrypoint("renderWarmUpImplicitView");
|
||||
engine_context->Run(std::move(configuration));
|
||||
|
||||
draw_latch.Wait();
|
||||
}
|
||||
|
||||
} // namespace flutter
|
||||
|
@ -2,11 +2,18 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:convert' show utf8, json;
|
||||
import 'dart:async' show scheduleMicrotask;
|
||||
import 'dart:convert' show json, utf8;
|
||||
import 'dart:isolate';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui';
|
||||
|
||||
void expect(Object? a, Object? b) {
|
||||
if (a != b) {
|
||||
throw AssertionError('Expected $a to == $b');
|
||||
}
|
||||
}
|
||||
|
||||
void main() {}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
@ -349,11 +356,6 @@ Future<void> toImageSync() async {
|
||||
|
||||
onBeforeToImageSync();
|
||||
final Image image = picture.toImageSync(20, 25);
|
||||
void expect(Object? a, Object? b) {
|
||||
if (a != b) {
|
||||
throw 'Expected $a to == $b';
|
||||
}
|
||||
}
|
||||
expect(image.width, 20);
|
||||
expect(image.height, 25);
|
||||
|
||||
@ -529,3 +531,31 @@ void testReportViewWidths() {
|
||||
nativeReportViewWidthsCallback(getCurrentViewWidths());
|
||||
};
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void renderWarmUpImplicitView() {
|
||||
bool beginFrameCalled = false;
|
||||
|
||||
PlatformDispatcher.instance.scheduleWarmUpFrame(
|
||||
beginFrame: () {
|
||||
expect(beginFrameCalled, false);
|
||||
beginFrameCalled = true;
|
||||
},
|
||||
drawFrame: () {
|
||||
expect(beginFrameCalled, true);
|
||||
|
||||
final SceneBuilder builder = SceneBuilder();
|
||||
final PictureRecorder recorder = PictureRecorder();
|
||||
final Canvas canvas = Canvas(recorder);
|
||||
canvas.drawPaint(Paint()..color = const Color(0xFFABCDEF));
|
||||
final Picture picture = recorder.endRecording();
|
||||
builder.addPicture(Offset.zero, picture);
|
||||
|
||||
final Scene scene = builder.build();
|
||||
PlatformDispatcher.instance.implicitView!.render(scene);
|
||||
|
||||
scene.dispose();
|
||||
picture.dispose();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:litetest/litetest.dart';
|
||||
@ -46,4 +47,31 @@ void main() {
|
||||
expect(constraints.isSatisfiedBy(const Size(400, 500)), false);
|
||||
expect(constraints / 2, const ViewConstraints(minWidth: 50, maxWidth: 100, minHeight: 150, maxHeight: 200));
|
||||
});
|
||||
|
||||
test('scheduleWarmupFrame should call both callbacks and flush microtasks', () async {
|
||||
bool microtaskFlushed = false;
|
||||
bool beginFrameCalled = false;
|
||||
final Completer<void> drawFrameCalled = Completer<void>();
|
||||
PlatformDispatcher.instance.scheduleWarmUpFrame(beginFrame: () {
|
||||
expect(microtaskFlushed, false);
|
||||
expect(drawFrameCalled.isCompleted, false);
|
||||
expect(beginFrameCalled, false);
|
||||
beginFrameCalled = true;
|
||||
scheduleMicrotask(() {
|
||||
expect(microtaskFlushed, false);
|
||||
expect(drawFrameCalled.isCompleted, false);
|
||||
microtaskFlushed = true;
|
||||
});
|
||||
expect(microtaskFlushed, false);
|
||||
}, drawFrame: () {
|
||||
expect(beginFrameCalled, true);
|
||||
expect(microtaskFlushed, true);
|
||||
expect(drawFrameCalled.isCompleted, false);
|
||||
drawFrameCalled.complete();
|
||||
});
|
||||
await drawFrameCalled.future;
|
||||
expect(beginFrameCalled, true);
|
||||
expect(drawFrameCalled.isCompleted, true);
|
||||
expect(microtaskFlushed, true);
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user