Add onReportTimings and FrameRasterizedCallback API (flutter/engine#8983)
Using it, a Flutter app can monitor missing frames in the release mode, and a custom Flutter runner (e.g., Fuchsia) can add a custom FrameRasterizedCallback. Related issues: https://github.com/flutter/flutter/issues/26154 https://github.com/flutter/flutter/issues/31444 https://github.com/flutter/flutter/issues/32447 Need review as soon as possible so we can merge this before the end of May to catch the milestone. Tests added: * NoNeedToReportTimingsByDefault * NeedsReportTimingsIsSetWithCallback * ReportTimingsIsCalled * FrameRasterizedCallbackIsCalled * FrameTimingSetsAndGetsProperly * onReportTimings preserves callback zone * FrameTiming.toString has the correct format This will need a manual engine roll as the TestWindow defined in the framework needs to implement onReportTimings.
This commit is contained in:
@@ -8,6 +8,8 @@
|
||||
|
||||
namespace flutter {
|
||||
|
||||
constexpr FrameTiming::Phase FrameTiming::kPhases[FrameTiming::kCount];
|
||||
|
||||
Settings::Settings() = default;
|
||||
|
||||
Settings::Settings(const Settings& other) = default;
|
||||
@@ -51,6 +53,8 @@ std::string Settings::ToString() const {
|
||||
stream << "icu_data_path: " << icu_data_path << std::endl;
|
||||
stream << "assets_dir: " << assets_dir << std::endl;
|
||||
stream << "assets_path: " << assets_path << std::endl;
|
||||
stream << "frame_rasterized_callback set: " << !!frame_rasterized_callback
|
||||
<< std::endl;
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
|
||||
@@ -14,10 +14,27 @@
|
||||
|
||||
#include "flutter/fml/closure.h"
|
||||
#include "flutter/fml/mapping.h"
|
||||
#include "flutter/fml/time/time_point.h"
|
||||
#include "flutter/fml/unique_fd.h"
|
||||
|
||||
namespace flutter {
|
||||
|
||||
class FrameTiming {
|
||||
public:
|
||||
enum Phase { kBuildStart, kBuildFinish, kRasterStart, kRasterFinish, kCount };
|
||||
|
||||
static constexpr Phase kPhases[kCount] = {kBuildStart, kBuildFinish,
|
||||
kRasterStart, kRasterFinish};
|
||||
|
||||
fml::TimePoint Get(Phase phase) const { return data_[phase]; }
|
||||
fml::TimePoint Set(Phase phase, fml::TimePoint value) {
|
||||
return data_[phase] = value;
|
||||
}
|
||||
|
||||
private:
|
||||
fml::TimePoint data_[kCount];
|
||||
};
|
||||
|
||||
using TaskObserverAdd =
|
||||
std::function<void(intptr_t /* key */, fml::closure /* callback */)>;
|
||||
using TaskObserverRemove = std::function<void(intptr_t /* key */)>;
|
||||
@@ -32,6 +49,8 @@ using MappingCallback = std::function<std::unique_ptr<fml::Mapping>(void)>;
|
||||
using MappingsCallback =
|
||||
std::function<std::vector<std::unique_ptr<const fml::Mapping>>(void)>;
|
||||
|
||||
using FrameRasterizedCallback = std::function<void(const FrameTiming&)>;
|
||||
|
||||
struct Settings {
|
||||
Settings();
|
||||
|
||||
@@ -149,6 +168,10 @@ struct Settings {
|
||||
fml::UniqueFD::traits_type::InvalidValue();
|
||||
std::string assets_path;
|
||||
|
||||
// Callback to handle the timings of a rasterized frame. This is called as
|
||||
// soon as a frame is rasterized.
|
||||
FrameRasterizedCallback frame_rasterized_callback;
|
||||
|
||||
std::string ToString() const;
|
||||
};
|
||||
|
||||
|
||||
@@ -19,6 +19,11 @@ LayerTree::LayerTree()
|
||||
|
||||
LayerTree::~LayerTree() = default;
|
||||
|
||||
void LayerTree::RecordBuildTime(fml::TimePoint start) {
|
||||
build_start_ = start;
|
||||
build_finish_ = fml::TimePoint::Now();
|
||||
}
|
||||
|
||||
void LayerTree::Preroll(CompositorContext::ScopedFrame& frame,
|
||||
bool ignore_raster_cache) {
|
||||
FML_TRACE_EVENT0("flutter", "LayerTree::Preroll");
|
||||
|
||||
@@ -47,11 +47,10 @@ class LayerTree {
|
||||
|
||||
void set_frame_size(const SkISize& frame_size) { frame_size_ = frame_size; }
|
||||
|
||||
void set_construction_time(const fml::TimeDelta& delta) {
|
||||
construction_time_ = delta;
|
||||
}
|
||||
|
||||
const fml::TimeDelta& construction_time() const { return construction_time_; }
|
||||
void RecordBuildTime(fml::TimePoint begin_start);
|
||||
fml::TimePoint build_start() const { return build_start_; }
|
||||
fml::TimePoint build_finish() const { return build_finish_; }
|
||||
fml::TimeDelta build_time() const { return build_finish_ - build_start_; }
|
||||
|
||||
// The number of frame intervals missed after which the compositor must
|
||||
// trace the rasterized picture to a trace file. Specify 0 to disable all
|
||||
@@ -75,7 +74,8 @@ class LayerTree {
|
||||
private:
|
||||
SkISize frame_size_; // Physical pixels.
|
||||
std::shared_ptr<Layer> root_layer_;
|
||||
fml::TimeDelta construction_time_;
|
||||
fml::TimePoint build_start_;
|
||||
fml::TimePoint build_finish_;
|
||||
uint32_t rasterizer_tracing_threshold_;
|
||||
bool checkerboard_raster_cache_images_;
|
||||
bool checkerboard_offscreen_layers_;
|
||||
|
||||
@@ -177,6 +177,17 @@ void _beginFrame(int microseconds) {
|
||||
_invoke1<Duration>(window.onBeginFrame, window._onBeginFrameZone, Duration(microseconds: microseconds));
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
// ignore: unused_element
|
||||
void _reportTimings(List<int> timings) {
|
||||
assert(timings.length % FramePhase.values.length == 0);
|
||||
final List<FrameTiming> frameTimings = <FrameTiming>[];
|
||||
for (int i = 0; i < timings.length; i += FramePhase.values.length) {
|
||||
frameTimings.add(FrameTiming(timings.sublist(i, i + FramePhase.values.length)));
|
||||
}
|
||||
_invoke1(window.onReportTimings, window._onReportTimingsZone, frameTimings);
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
// ignore: unused_element
|
||||
void _drawFrame() {
|
||||
|
||||
@@ -10,6 +10,18 @@ typedef VoidCallback = void Function();
|
||||
/// Signature for [Window.onBeginFrame].
|
||||
typedef FrameCallback = void Function(Duration duration);
|
||||
|
||||
/// Signature for [Window.onReportTimings].
|
||||
///
|
||||
/// {@template dart.ui.TimingsCallback.list}
|
||||
/// The callback takes a list of [FrameTiming] because it may not be immediately
|
||||
/// triggered after each frame. Instead, Flutter tries to batch frames together
|
||||
/// and send all their timings at once to decrease the overhead (as this is
|
||||
/// available in the release mode). The list is sorted in ascending order of
|
||||
/// time (earliest frame first). The timing of any frame will be sent within
|
||||
/// about 1 second even if there are no later frames to batch.
|
||||
/// {@endtemplate}
|
||||
typedef TimingsCallback = void Function(List<FrameTiming> timings);
|
||||
|
||||
/// Signature for [Window.onPointerDataPacket].
|
||||
typedef PointerDataPacketCallback = void Function(PointerDataPacket packet);
|
||||
|
||||
@@ -25,6 +37,98 @@ typedef PlatformMessageResponseCallback = void Function(ByteData data);
|
||||
/// Signature for [Window.onPlatformMessage].
|
||||
typedef PlatformMessageCallback = void Function(String name, ByteData data, PlatformMessageResponseCallback callback);
|
||||
|
||||
/// Various important time points in the lifetime of a frame.
|
||||
///
|
||||
/// [FrameTiming] records a timestamp of each phase for performance analysis.
|
||||
enum FramePhase {
|
||||
/// When the UI thread starts building a frame.
|
||||
///
|
||||
/// See also [FrameTiming.buildDuration].
|
||||
buildStart,
|
||||
|
||||
/// When the UI thread finishes building a frame.
|
||||
///
|
||||
/// See also [FrameTiming.buildDuration].
|
||||
buildFinish,
|
||||
|
||||
/// When the GPU thread starts rasterizing a frame.
|
||||
///
|
||||
/// See also [FrameTiming.rasterDuration].
|
||||
rasterStart,
|
||||
|
||||
/// When the GPU thread finishes rasterizing a frame.
|
||||
///
|
||||
/// See also [FrameTiming.rasterDuration].
|
||||
rasterFinish,
|
||||
}
|
||||
|
||||
/// Time-related performance metrics of a frame.
|
||||
///
|
||||
/// See [Window.onReportTimings] for how to get this.
|
||||
///
|
||||
/// The metrics in debug mode (`flutter run` without any flags) may be very
|
||||
/// different from those in profile and release modes due to the debug overhead.
|
||||
/// Therefore it's recommended to only monitor and analyze performance metrics
|
||||
/// in profile and release modes.
|
||||
class FrameTiming {
|
||||
/// Construct [FrameTiming] with raw timestamps in microseconds.
|
||||
///
|
||||
/// List [timestamps] must have the same number of elements as
|
||||
/// [FramePhase.values].
|
||||
///
|
||||
/// This constructor is usually only called by the Flutter engine, or a test.
|
||||
/// To get the [FrameTiming] of your app, see [Window.onReportTimings].
|
||||
FrameTiming(List<int> timestamps)
|
||||
: assert(timestamps.length == FramePhase.values.length), _timestamps = timestamps;
|
||||
|
||||
/// This is a raw timestamp in microseconds from some epoch. The epoch in all
|
||||
/// [FrameTiming] is the same, but it may not match [DateTime]'s epoch.
|
||||
int timestampInMicroseconds(FramePhase phase) => _timestamps[phase.index];
|
||||
|
||||
Duration _rawDuration(FramePhase phase) => Duration(microseconds: _timestamps[phase.index]);
|
||||
|
||||
/// The duration to build the frame on the UI thread.
|
||||
///
|
||||
/// The build starts approximately when [Window.onBeginFrame] is called. The
|
||||
/// [Duration] in the [Window.onBeginFrame] callback is exactly the
|
||||
/// `Duration(microseconds: timestampInMicroseconds(FramePhase.buildStart))`.
|
||||
///
|
||||
/// The build finishes when [Window.render] is called.
|
||||
///
|
||||
/// {@template dart.ui.FrameTiming.fps_smoothness_milliseconds}
|
||||
/// To ensure smooth animations of X fps, this should not exceed 1000/X
|
||||
/// milliseconds.
|
||||
/// {@endtemplate}
|
||||
/// {@template dart.ui.FrameTiming.fps_milliseconds}
|
||||
/// That's about 16ms for 60fps, and 8ms for 120fps.
|
||||
/// {@endtemplate}
|
||||
Duration get buildDuration => _rawDuration(FramePhase.buildFinish) - _rawDuration(FramePhase.buildStart);
|
||||
|
||||
/// The duration to rasterize the frame on the GPU thread.
|
||||
///
|
||||
/// {@macro dart.ui.FrameTiming.fps_smoothness_milliseconds}
|
||||
/// {@macro dart.ui.FrameTiming.fps_milliseconds}
|
||||
Duration get rasterDuration => _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.rasterStart);
|
||||
|
||||
/// The timespan between build start and raster finish.
|
||||
///
|
||||
/// To achieve the lowest latency on an X fps display, this should not exceed
|
||||
/// 1000/X milliseconds.
|
||||
/// {@macro dart.ui.FrameTiming.fps_milliseconds}
|
||||
///
|
||||
/// See also [buildDuration] and [rasterDuration].
|
||||
Duration get totalSpan => _rawDuration(FramePhase.rasterFinish) - _rawDuration(FramePhase.buildStart);
|
||||
|
||||
final List<int> _timestamps; // in microseconds
|
||||
|
||||
String _formatMS(Duration duration) => '${duration.inMicroseconds * 0.001}ms';
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '$runtimeType(buildDuration: ${_formatMS(buildDuration)}, rasterDuration: ${_formatMS(rasterDuration)}, totalSpan: ${_formatMS(totalSpan)})';
|
||||
}
|
||||
}
|
||||
|
||||
/// States that an application can be in.
|
||||
///
|
||||
/// The values below describe notifications from the operating system.
|
||||
@@ -781,6 +885,36 @@ class Window {
|
||||
_onDrawFrameZone = Zone.current;
|
||||
}
|
||||
|
||||
/// A callback that is invoked to report the [FrameTiming] of recently
|
||||
/// rasterized frames.
|
||||
///
|
||||
/// This can be used to see if the application has missed frames (through
|
||||
/// [FrameTiming.buildDuration] and [FrameTiming.rasterDuration]), or high
|
||||
/// latencies (through [FrameTiming.totalSpan]).
|
||||
///
|
||||
/// Unlike [Timeline], the timing information here is available in the release
|
||||
/// mode (additional to the profile and the debug mode). Hence this can be
|
||||
/// used to monitor the application's performance in the wild.
|
||||
///
|
||||
/// {@macro dart.ui.TimingsCallback.list}
|
||||
///
|
||||
/// If this is null, no additional work will be done. If this is not null,
|
||||
/// Flutter spends less than 0.1ms every 1 second to report the timings
|
||||
/// (measured on iPhone6S). The 0.1ms is about 0.6% of 16ms (frame budget for
|
||||
/// 60fps), or 0.01% CPU usage per second.
|
||||
TimingsCallback get onReportTimings => _onReportTimings;
|
||||
TimingsCallback _onReportTimings;
|
||||
Zone _onReportTimingsZone;
|
||||
set onReportTimings(TimingsCallback callback) {
|
||||
if ((callback == null) != (_onReportTimings == null)) {
|
||||
_setNeedsReportTimings(callback != null);
|
||||
}
|
||||
_onReportTimings = callback;
|
||||
_onReportTimingsZone = Zone.current;
|
||||
}
|
||||
|
||||
void _setNeedsReportTimings(bool value) native 'Window_setNeedsReportTimings';
|
||||
|
||||
/// A callback that is invoked when pointer data is available.
|
||||
///
|
||||
/// The framework invokes this callback in the same zone in which the
|
||||
|
||||
@@ -60,6 +60,12 @@ void SetIsolateDebugName(Dart_NativeArguments args) {
|
||||
UIDartState::Current()->SetDebugName(name);
|
||||
}
|
||||
|
||||
void SetNeedsReportTimings(Dart_NativeArguments args) {
|
||||
Dart_Handle exception = nullptr;
|
||||
bool value = tonic::DartConverter<bool>::FromArguments(args, 1, exception);
|
||||
UIDartState::Current()->window()->client()->SetNeedsReportTimings(value);
|
||||
}
|
||||
|
||||
void ReportUnhandledException(Dart_NativeArguments args) {
|
||||
Dart_Handle exception = nullptr;
|
||||
|
||||
@@ -320,6 +326,31 @@ void Window::BeginFrame(fml::TimePoint frameTime) {
|
||||
tonic::LogIfError(tonic::DartInvokeField(library_.value(), "_drawFrame", {}));
|
||||
}
|
||||
|
||||
void Window::ReportTimings(std::vector<int64_t> timings) {
|
||||
std::shared_ptr<tonic::DartState> dart_state = library_.dart_state().lock();
|
||||
if (!dart_state)
|
||||
return;
|
||||
tonic::DartState::Scope scope(dart_state);
|
||||
|
||||
Dart_Handle data_handle =
|
||||
Dart_NewTypedData(Dart_TypedData_kInt64, timings.size());
|
||||
|
||||
Dart_TypedData_Type type;
|
||||
void* data = nullptr;
|
||||
intptr_t num_acquired = 0;
|
||||
FML_CHECK(!Dart_IsError(
|
||||
Dart_TypedDataAcquireData(data_handle, &type, &data, &num_acquired)));
|
||||
FML_DCHECK(num_acquired == static_cast<int>(timings.size()));
|
||||
|
||||
memcpy(data, timings.data(), sizeof(int64_t) * timings.size());
|
||||
FML_CHECK(Dart_TypedDataReleaseData(data_handle));
|
||||
|
||||
tonic::LogIfError(tonic::DartInvokeField(library_.value(), "_reportTimings",
|
||||
{
|
||||
data_handle,
|
||||
}));
|
||||
}
|
||||
|
||||
void Window::CompletePlatformMessageEmptyResponse(int response_id) {
|
||||
if (!response_id)
|
||||
return;
|
||||
@@ -353,6 +384,7 @@ void Window::RegisterNatives(tonic::DartLibraryNatives* natives) {
|
||||
{"Window_updateSemantics", UpdateSemantics, 2, true},
|
||||
{"Window_setIsolateDebugName", SetIsolateDebugName, 2, true},
|
||||
{"Window_reportUnhandledException", ReportUnhandledException, 2, true},
|
||||
{"Window_setNeedsReportTimings", SetNeedsReportTimings, 2, true},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,16 @@
|
||||
|
||||
namespace tonic {
|
||||
class DartLibraryNatives;
|
||||
|
||||
// So tonice::ToDart<std::vector<int64_t>> returns List<int> instead of
|
||||
// List<dynamic>.
|
||||
template <>
|
||||
struct DartListFactory<int64_t> {
|
||||
static Dart_Handle NewList(intptr_t length) {
|
||||
return Dart_NewListOf(Dart_CoreType_Int, length);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace tonic
|
||||
|
||||
namespace flutter {
|
||||
@@ -46,6 +56,7 @@ class WindowClient {
|
||||
virtual FontCollection& GetFontCollection() = 0;
|
||||
virtual void UpdateIsolateDescription(const std::string isolate_name,
|
||||
int64_t isolate_port) = 0;
|
||||
virtual void SetNeedsReportTimings(bool value) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~WindowClient();
|
||||
@@ -74,6 +85,7 @@ class Window final {
|
||||
SemanticsAction action,
|
||||
std::vector<uint8_t> args);
|
||||
void BeginFrame(fml::TimePoint frameTime);
|
||||
void ReportTimings(std::vector<int64_t> timings);
|
||||
|
||||
void CompletePlatformMessageResponse(int response_id,
|
||||
std::vector<uint8_t> data);
|
||||
|
||||
@@ -231,6 +231,14 @@ bool RuntimeController::BeginFrame(fml::TimePoint frame_time) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RuntimeController::ReportTimings(std::vector<int64_t> timings) {
|
||||
if (auto* window = GetWindowIfAvailable()) {
|
||||
window->ReportTimings(std::move(timings));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RuntimeController::NotifyIdle(int64_t deadline) {
|
||||
std::shared_ptr<DartIsolate> root_isolate = root_isolate_.lock();
|
||||
if (!root_isolate) {
|
||||
@@ -320,6 +328,10 @@ void RuntimeController::UpdateIsolateDescription(const std::string isolate_name,
|
||||
client_.UpdateIsolateDescription(isolate_name, isolate_port);
|
||||
}
|
||||
|
||||
void RuntimeController::SetNeedsReportTimings(bool value) {
|
||||
client_.SetNeedsReportTimings(value);
|
||||
}
|
||||
|
||||
Dart_Port RuntimeController::GetMainPort() {
|
||||
std::shared_ptr<DartIsolate> root_isolate = root_isolate_.lock();
|
||||
return root_isolate ? root_isolate->main_port() : ILLEGAL_PORT;
|
||||
|
||||
@@ -59,6 +59,8 @@ class RuntimeController final : public WindowClient {
|
||||
|
||||
bool BeginFrame(fml::TimePoint frame_time);
|
||||
|
||||
bool ReportTimings(std::vector<int64_t> timings);
|
||||
|
||||
bool NotifyIdle(int64_t deadline);
|
||||
|
||||
bool IsRootIsolateRunning() const;
|
||||
@@ -177,6 +179,9 @@ class RuntimeController final : public WindowClient {
|
||||
void UpdateIsolateDescription(const std::string isolate_name,
|
||||
int64_t isolate_port) override;
|
||||
|
||||
// |WindowClient|
|
||||
void SetNeedsReportTimings(bool value) override;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(RuntimeController);
|
||||
};
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ class RuntimeDelegate {
|
||||
virtual void UpdateIsolateDescription(const std::string isolate_name,
|
||||
int64_t isolate_port) = 0;
|
||||
|
||||
virtual void SetNeedsReportTimings(bool value) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~RuntimeDelegate();
|
||||
};
|
||||
|
||||
@@ -161,6 +161,7 @@ shell_host_executable("shell_unittests") {
|
||||
":shell_unittests_fixtures",
|
||||
":shell_unittests_gpu_configuration",
|
||||
"$flutter_root/common",
|
||||
"$flutter_root/flow",
|
||||
"$flutter_root/shell",
|
||||
"$flutter_root/testing:dart",
|
||||
]
|
||||
|
||||
@@ -174,8 +174,7 @@ void Animator::Render(std::unique_ptr<flutter::LayerTree> layer_tree) {
|
||||
|
||||
if (layer_tree) {
|
||||
// Note the frame time for instrumentation.
|
||||
layer_tree->set_construction_time(fml::TimePoint::Now() -
|
||||
last_begin_frame_time_);
|
||||
layer_tree->RecordBuildTime(last_begin_frame_time_);
|
||||
}
|
||||
|
||||
// Commit the pending continuation.
|
||||
|
||||
@@ -18,6 +18,10 @@
|
||||
|
||||
namespace flutter {
|
||||
|
||||
namespace testing {
|
||||
class ShellTest;
|
||||
}
|
||||
|
||||
class Animator final {
|
||||
public:
|
||||
class Delegate {
|
||||
@@ -87,6 +91,8 @@ class Animator final {
|
||||
|
||||
fml::WeakPtrFactory<Animator> weak_factory_;
|
||||
|
||||
friend class testing::ShellTest;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(Animator);
|
||||
};
|
||||
|
||||
|
||||
@@ -201,6 +201,11 @@ void Engine::BeginFrame(fml::TimePoint frame_time) {
|
||||
runtime_controller_->BeginFrame(frame_time);
|
||||
}
|
||||
|
||||
void Engine::ReportTimings(std::vector<int64_t> timings) {
|
||||
TRACE_EVENT0("flutter", "Engine::ReportTimings");
|
||||
runtime_controller_->ReportTimings(std::move(timings));
|
||||
}
|
||||
|
||||
void Engine::NotifyIdle(int64_t deadline) {
|
||||
FML_TRACE_EVENT1("flutter", "Engine::NotifyIdle", "deadline_now_delta",
|
||||
std::to_string(deadline - Dart_TimelineGetMicros()).c_str());
|
||||
@@ -432,6 +437,10 @@ void Engine::UpdateIsolateDescription(const std::string isolate_name,
|
||||
delegate_.UpdateIsolateDescription(isolate_name, isolate_port);
|
||||
}
|
||||
|
||||
void Engine::SetNeedsReportTimings(bool value) {
|
||||
delegate_.SetNeedsReportTimings(value);
|
||||
}
|
||||
|
||||
FontCollection& Engine::GetFontCollection() {
|
||||
return font_collection_;
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ class Engine final : public RuntimeDelegate {
|
||||
|
||||
virtual void UpdateIsolateDescription(const std::string isolate_name,
|
||||
int64_t isolate_port) = 0;
|
||||
|
||||
virtual void SetNeedsReportTimings(bool value) = 0;
|
||||
};
|
||||
|
||||
Engine(Delegate& delegate,
|
||||
@@ -84,6 +86,8 @@ class Engine final : public RuntimeDelegate {
|
||||
|
||||
void BeginFrame(fml::TimePoint frame_time);
|
||||
|
||||
void ReportTimings(std::vector<int64_t> timings);
|
||||
|
||||
void NotifyIdle(int64_t deadline);
|
||||
|
||||
Dart_Port GetUIIsolateMainPort();
|
||||
@@ -150,6 +154,8 @@ class Engine final : public RuntimeDelegate {
|
||||
void UpdateIsolateDescription(const std::string isolate_name,
|
||||
int64_t isolate_port) override;
|
||||
|
||||
void SetNeedsReportTimings(bool value) override;
|
||||
|
||||
void StopAnimator();
|
||||
|
||||
void StartAnimatorIfPossible();
|
||||
@@ -168,6 +174,8 @@ class Engine final : public RuntimeDelegate {
|
||||
|
||||
RunStatus PrepareAndLaunchIsolate(RunConfiguration configuration);
|
||||
|
||||
friend class testing::ShellTest;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(Engine);
|
||||
};
|
||||
|
||||
|
||||
@@ -3,9 +3,41 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:isolate';
|
||||
import 'dart:ui';
|
||||
|
||||
void main() {}
|
||||
|
||||
void nativeReportTimingsCallback(List<int> timings) native 'NativeReportTimingsCallback';
|
||||
void nativeOnBeginFrame(int microseconds) native 'NativeOnBeginFrame';
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void reportTimingsMain() {
|
||||
window.onReportTimings = (List<FrameTiming> timings) {
|
||||
List<int> timestamps = [];
|
||||
for (FrameTiming t in timings) {
|
||||
for (FramePhase phase in FramePhase.values) {
|
||||
timestamps.add(t.timestampInMicroseconds(phase));
|
||||
}
|
||||
}
|
||||
nativeReportTimingsCallback(timestamps);
|
||||
};
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void onBeginFrameMain() {
|
||||
window.onBeginFrame = (Duration beginTime) {
|
||||
nativeOnBeginFrame(beginTime.inMicroseconds);
|
||||
};
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void emptyMain() {}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void dummyReportTimingsMain() {
|
||||
window.onReportTimings = (List<FrameTiming> timings) {};
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void fixturesAreFunctionalMain() {
|
||||
sayHiFromFixturesAreFunctionalMain();
|
||||
|
||||
@@ -22,14 +22,17 @@ namespace flutter {
|
||||
// used within this interval.
|
||||
static constexpr std::chrono::milliseconds kSkiaCleanupExpiration(15000);
|
||||
|
||||
Rasterizer::Rasterizer(TaskRunners task_runners)
|
||||
: Rasterizer(std::move(task_runners),
|
||||
Rasterizer::Rasterizer(Delegate& delegate, TaskRunners task_runners)
|
||||
: Rasterizer(delegate,
|
||||
std::move(task_runners),
|
||||
std::make_unique<flutter::CompositorContext>()) {}
|
||||
|
||||
Rasterizer::Rasterizer(
|
||||
Delegate& delegate,
|
||||
TaskRunners task_runners,
|
||||
std::unique_ptr<flutter::CompositorContext> compositor_context)
|
||||
: task_runners_(std::move(task_runners)),
|
||||
: delegate_(delegate),
|
||||
task_runners_(std::move(task_runners)),
|
||||
compositor_context_(std::move(compositor_context)),
|
||||
weak_factory_(this) {
|
||||
FML_DCHECK(compositor_context_);
|
||||
@@ -151,6 +154,11 @@ void Rasterizer::DoDraw(std::unique_ptr<flutter::LayerTree> layer_tree) {
|
||||
return;
|
||||
}
|
||||
|
||||
FrameTiming timing;
|
||||
timing.Set(FrameTiming::kBuildStart, layer_tree->build_start());
|
||||
timing.Set(FrameTiming::kBuildFinish, layer_tree->build_finish());
|
||||
timing.Set(FrameTiming::kRasterStart, fml::TimePoint::Now());
|
||||
|
||||
PersistentCache* persistent_cache = PersistentCache::GetCacheForProcess();
|
||||
persistent_cache->ResetStoredNewShaders();
|
||||
|
||||
@@ -164,6 +172,12 @@ void Rasterizer::DoDraw(std::unique_ptr<flutter::LayerTree> layer_tree) {
|
||||
ScreenshotLastLayerTree(ScreenshotType::SkiaPicture, false);
|
||||
persistent_cache->DumpSkp(*screenshot.data);
|
||||
}
|
||||
|
||||
// TODO(liyuqian): in Fuchsia, the rasterization doesn't finish when
|
||||
// Rasterizer::DoDraw finishes. Future work is needed to adapt the timestamp
|
||||
// for Fuchsia to capture SceneUpdateContext::ExecutePaintTasks.
|
||||
timing.Set(FrameTiming::kRasterFinish, fml::TimePoint::Now());
|
||||
delegate_.OnFrameRasterized(timing);
|
||||
}
|
||||
|
||||
bool Rasterizer::DrawToSurface(flutter::LayerTree& layer_tree) {
|
||||
@@ -178,7 +192,7 @@ bool Rasterizer::DrawToSurface(flutter::LayerTree& layer_tree) {
|
||||
// There is no way for the compositor to know how long the layer tree
|
||||
// construction took. Fortunately, the layer tree does. Grab that time
|
||||
// for instrumentation.
|
||||
compositor_context_->ui_time().SetLapTime(layer_tree.construction_time());
|
||||
compositor_context_->ui_time().SetLapTime(layer_tree.build_time());
|
||||
|
||||
auto* canvas = frame->SkiaCanvas();
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "flutter/common/settings.h"
|
||||
#include "flutter/common/task_runners.h"
|
||||
#include "flutter/flow/compositor_context.h"
|
||||
#include "flutter/flow/layers/layer_tree.h"
|
||||
@@ -21,9 +22,14 @@ namespace flutter {
|
||||
|
||||
class Rasterizer final : public SnapshotDelegate {
|
||||
public:
|
||||
Rasterizer(TaskRunners task_runners);
|
||||
class Delegate {
|
||||
public:
|
||||
virtual void OnFrameRasterized(const FrameTiming&) = 0;
|
||||
};
|
||||
Rasterizer(Delegate& delegate, TaskRunners task_runners);
|
||||
|
||||
Rasterizer(TaskRunners task_runners,
|
||||
Rasterizer(Delegate& delegate,
|
||||
TaskRunners task_runners,
|
||||
std::unique_ptr<flutter::CompositorContext> compositor_context);
|
||||
|
||||
~Rasterizer();
|
||||
@@ -76,6 +82,7 @@ class Rasterizer final : public SnapshotDelegate {
|
||||
void SetResourceCacheMaxBytes(int max_bytes);
|
||||
|
||||
private:
|
||||
Delegate& delegate_;
|
||||
TaskRunners task_runners_;
|
||||
std::unique_ptr<Surface> surface_;
|
||||
std::unique_ptr<flutter::CompositorContext> compositor_context_;
|
||||
|
||||
@@ -277,7 +277,8 @@ std::unique_ptr<Shell> Shell::Create(
|
||||
Shell::Shell(DartVMRef vm, TaskRunners task_runners, Settings settings)
|
||||
: task_runners_(std::move(task_runners)),
|
||||
settings_(std::move(settings)),
|
||||
vm_(std::move(vm)) {
|
||||
vm_(std::move(vm)),
|
||||
weak_factory_(this) {
|
||||
FML_CHECK(vm_) << "Must have access to VM to create a shell.";
|
||||
FML_DCHECK(task_runners_.IsValid());
|
||||
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
|
||||
@@ -389,6 +390,13 @@ bool Shell::Setup(std::unique_ptr<PlatformView> platform_view,
|
||||
rasterizer_ = std::move(rasterizer);
|
||||
io_manager_ = std::move(io_manager);
|
||||
|
||||
// The weak ptr must be generated in the platform thread which owns the unique
|
||||
// ptr.
|
||||
//
|
||||
// TODO(liyuqian): make weak_rasterizer_ and weak_platform_view_ and use
|
||||
// them in the getters.
|
||||
weak_engine_ = engine_->GetWeakPtr();
|
||||
|
||||
is_setup_ = true;
|
||||
|
||||
vm_->GetServiceProtocol()->AddHandler(this, GetServiceProtocolDescription());
|
||||
@@ -417,7 +425,7 @@ fml::WeakPtr<Rasterizer> Shell::GetRasterizer() {
|
||||
|
||||
fml::WeakPtr<Engine> Shell::GetEngine() {
|
||||
FML_DCHECK(is_setup_);
|
||||
return engine_->GetWeakPtr();
|
||||
return weak_engine_;
|
||||
}
|
||||
|
||||
fml::WeakPtr<PlatformView> Shell::GetPlatformView() {
|
||||
@@ -876,6 +884,77 @@ void Shell::UpdateIsolateDescription(const std::string isolate_name,
|
||||
vm_->GetServiceProtocol()->SetHandlerDescription(this, description);
|
||||
}
|
||||
|
||||
void Shell::SetNeedsReportTimings(bool value) {
|
||||
needs_report_timings_ = value;
|
||||
}
|
||||
|
||||
void Shell::ReportTimings() {
|
||||
FML_DCHECK(is_setup_);
|
||||
FML_DCHECK(task_runners_.GetGPUTaskRunner()->RunsTasksOnCurrentThread());
|
||||
|
||||
auto timings = std::move(unreported_timings_);
|
||||
unreported_timings_ = {};
|
||||
task_runners_.GetUITaskRunner()->PostTask([timings, engine = GetEngine()] {
|
||||
if (engine) {
|
||||
engine->ReportTimings(std::move(timings));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
size_t Shell::UnreportedFramesCount() const {
|
||||
// Check that this is running on the GPU thread to avoid race conditions.
|
||||
FML_DCHECK(task_runners_.GetGPUTaskRunner()->RunsTasksOnCurrentThread());
|
||||
FML_DCHECK(unreported_timings_.size() % FrameTiming::kCount == 0);
|
||||
return unreported_timings_.size() / FrameTiming::kCount;
|
||||
}
|
||||
|
||||
void Shell::OnFrameRasterized(const FrameTiming& timing) {
|
||||
FML_DCHECK(is_setup_);
|
||||
FML_DCHECK(task_runners_.GetGPUTaskRunner()->RunsTasksOnCurrentThread());
|
||||
|
||||
// The C++ callback defined in settings.h and set by Flutter runner. This is
|
||||
// independent of the timings report to the Dart side.
|
||||
if (settings_.frame_rasterized_callback) {
|
||||
settings_.frame_rasterized_callback(timing);
|
||||
}
|
||||
|
||||
if (!needs_report_timings_) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto phase : FrameTiming::kPhases) {
|
||||
unreported_timings_.push_back(
|
||||
timing.Get(phase).ToEpochDelta().ToMicroseconds());
|
||||
}
|
||||
|
||||
// In tests using iPhone 6S with profile mode, sending a batch of 1 frame or a
|
||||
// batch of 100 frames have roughly the same cost of less than 0.1ms. Sending
|
||||
// a batch of 500 frames costs about 0.2ms. The 1 second threshold usually
|
||||
// kicks in before we reaching the following 100 frames threshold. The 100
|
||||
// threshold here is mainly for unit tests (so we don't have to write a
|
||||
// 1-second unit test), and make sure that our vector won't grow too big with
|
||||
// future 120fps, 240fps, or 1000fps displays.
|
||||
if (UnreportedFramesCount() >= 100) {
|
||||
ReportTimings();
|
||||
} else if (!frame_timings_report_scheduled_) {
|
||||
// Also make sure that frame times get reported with a max latency of 1
|
||||
// second. Otherwise, the timings of last few frames of an animation may
|
||||
// never be reported until the next animation starts.
|
||||
frame_timings_report_scheduled_ = true;
|
||||
task_runners_.GetGPUTaskRunner()->PostDelayedTask(
|
||||
[self = weak_factory_.GetWeakPtr()]() {
|
||||
if (!self.get()) {
|
||||
return;
|
||||
}
|
||||
self->frame_timings_report_scheduled_ = false;
|
||||
if (self->UnreportedFramesCount() > 0) {
|
||||
self->ReportTimings();
|
||||
}
|
||||
},
|
||||
fml::TimeDelta::FromSeconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
// |ServiceProtocol::Handler|
|
||||
fml::RefPtr<fml::TaskRunner> Shell::GetServiceProtocolHandlerTaskRunner(
|
||||
fml::StringView method) const {
|
||||
|
||||
@@ -37,6 +37,7 @@ namespace flutter {
|
||||
class Shell final : public PlatformView::Delegate,
|
||||
public Animator::Delegate,
|
||||
public Engine::Delegate,
|
||||
public Rasterizer::Delegate,
|
||||
public ServiceProtocol::Handler {
|
||||
public:
|
||||
template <class T>
|
||||
@@ -93,6 +94,8 @@ class Shell final : public PlatformView::Delegate,
|
||||
std::unique_ptr<Rasterizer> rasterizer_; // on GPU task runner
|
||||
std::unique_ptr<ShellIOManager> io_manager_; // on IO task runner
|
||||
|
||||
fml::WeakPtr<Engine> weak_engine_; // to be shared across threads
|
||||
|
||||
std::unordered_map<std::string, // method
|
||||
std::pair<fml::RefPtr<fml::TaskRunner>,
|
||||
ServiceProtocolHandler> // task-runner/function
|
||||
@@ -102,6 +105,22 @@ class Shell final : public PlatformView::Delegate,
|
||||
bool is_setup_ = false;
|
||||
uint64_t next_pointer_flow_id_ = 0;
|
||||
|
||||
// Written in the UI thread and read from the GPU thread. Hence make it
|
||||
// atomic.
|
||||
std::atomic<bool> needs_report_timings_{false};
|
||||
|
||||
// Whether there's a task scheduled to report the timings to Dart through
|
||||
// ui.Window.onReportTimings.
|
||||
bool frame_timings_report_scheduled_ = false;
|
||||
|
||||
// Vector of FrameTiming::kCount * n timestamps for n frames whose timings
|
||||
// have not been reported yet. Vector of ints instead of FrameTiming is stored
|
||||
// here for easier conversions to Dart objects.
|
||||
std::vector<int64_t> unreported_timings_;
|
||||
|
||||
// How many frames have been timed since last report.
|
||||
size_t UnreportedFramesCount() const;
|
||||
|
||||
Shell(TaskRunners task_runners, Settings settings);
|
||||
Shell(DartVMRef vm, TaskRunners task_runners, Settings settings);
|
||||
|
||||
@@ -119,6 +138,8 @@ class Shell final : public PlatformView::Delegate,
|
||||
std::unique_ptr<Rasterizer> rasterizer,
|
||||
std::unique_ptr<ShellIOManager> io_manager);
|
||||
|
||||
void ReportTimings();
|
||||
|
||||
// |PlatformView::Delegate|
|
||||
void OnPlatformViewCreated(std::unique_ptr<Surface> surface) override;
|
||||
|
||||
@@ -193,6 +214,12 @@ class Shell final : public PlatformView::Delegate,
|
||||
void UpdateIsolateDescription(const std::string isolate_name,
|
||||
int64_t isolate_port) override;
|
||||
|
||||
// |Engine::Delegate|
|
||||
void SetNeedsReportTimings(bool value) override;
|
||||
|
||||
// |Rasterizer::Delegate|
|
||||
void OnFrameRasterized(const FrameTiming&) override;
|
||||
|
||||
// |ServiceProtocol::Handler|
|
||||
fml::RefPtr<fml::TaskRunner> GetServiceProtocolHandlerTaskRunner(
|
||||
fml::StringView method) const override;
|
||||
@@ -237,6 +264,10 @@ class Shell final : public PlatformView::Delegate,
|
||||
const ServiceProtocol::Handler::ServiceProtocolMap& params,
|
||||
rapidjson::Document& response);
|
||||
|
||||
fml::WeakPtrFactory<Shell> weak_factory_;
|
||||
|
||||
friend class testing::ShellTest;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(Shell);
|
||||
};
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ static void StartupAndShutdownShell(benchmark::State& state,
|
||||
return std::make_unique<PlatformView>(shell, shell.GetTaskRunners());
|
||||
},
|
||||
[](Shell& shell) {
|
||||
return std::make_unique<Rasterizer>(shell.GetTaskRunners());
|
||||
return std::make_unique<Rasterizer>(shell, shell.GetTaskRunners());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
#include "flutter/shell/common/shell_test.h"
|
||||
|
||||
#include "flutter/flow/layers/layer_tree.h"
|
||||
#include "flutter/flow/layers/transform_layer.h"
|
||||
#include "flutter/fml/make_copyable.h"
|
||||
#include "flutter/fml/mapping.h"
|
||||
#include "flutter/runtime/dart_vm.h"
|
||||
#include "flutter/testing/testing.h"
|
||||
@@ -58,6 +61,69 @@ void ShellTest::SetSnapshotsAndAssets(Settings& settings) {
|
||||
}
|
||||
}
|
||||
|
||||
void ShellTest::PlatformViewNotifyCreated(Shell* shell) {
|
||||
fml::AutoResetWaitableEvent latch;
|
||||
fml::TaskRunner::RunNowOrPostTask(
|
||||
shell->GetTaskRunners().GetPlatformTaskRunner(), [shell, &latch]() {
|
||||
shell->GetPlatformView()->NotifyCreated();
|
||||
latch.Signal();
|
||||
});
|
||||
latch.Wait();
|
||||
}
|
||||
|
||||
void ShellTest::RunEngine(Shell* shell, RunConfiguration configuration) {
|
||||
fml::AutoResetWaitableEvent latch;
|
||||
fml::TaskRunner::RunNowOrPostTask(
|
||||
shell->GetTaskRunners().GetUITaskRunner(),
|
||||
fml::MakeCopyable([&latch, config = std::move(configuration),
|
||||
engine = shell->GetEngine()]() mutable {
|
||||
ASSERT_TRUE(engine);
|
||||
ASSERT_EQ(engine->Run(std::move(config)), Engine::RunStatus::Success);
|
||||
latch.Signal();
|
||||
}));
|
||||
latch.Wait();
|
||||
}
|
||||
|
||||
void ShellTest::PumpOneFrame(Shell* shell) {
|
||||
// Set viewport to nonempty, and call Animator::BeginFrame to make the layer
|
||||
// tree pipeline nonempty. Without either of this, the layer tree below
|
||||
// won't be rasterized.
|
||||
fml::AutoResetWaitableEvent latch;
|
||||
shell->GetTaskRunners().GetUITaskRunner()->PostTask(
|
||||
[&latch, engine = shell->GetEngine()]() {
|
||||
engine->SetViewportMetrics({1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0});
|
||||
engine->animator_->BeginFrame(fml::TimePoint::Now(),
|
||||
fml::TimePoint::Now());
|
||||
latch.Signal();
|
||||
});
|
||||
latch.Wait();
|
||||
|
||||
latch.Reset();
|
||||
// Call |Render| to rasterize a layer tree and trigger |OnFrameRasterized|
|
||||
fml::WeakPtr<RuntimeDelegate> runtime_delegate = shell->GetEngine();
|
||||
shell->GetTaskRunners().GetUITaskRunner()->PostTask(
|
||||
[&latch, runtime_delegate]() {
|
||||
auto layer_tree = std::make_unique<LayerTree>();
|
||||
auto root_layer = std::make_shared<TransformLayer>();
|
||||
layer_tree->set_root_layer(root_layer);
|
||||
runtime_delegate->Render(std::move(layer_tree));
|
||||
latch.Signal();
|
||||
});
|
||||
latch.Wait();
|
||||
}
|
||||
|
||||
int ShellTest::UnreportedTimingsCount(Shell* shell) {
|
||||
return shell->unreported_timings_.size();
|
||||
}
|
||||
|
||||
void ShellTest::SetNeedsReportTimings(Shell* shell, bool value) {
|
||||
shell->SetNeedsReportTimings(value);
|
||||
}
|
||||
|
||||
bool ShellTest::GetNeedsReportTimings(Shell* shell) {
|
||||
return shell->needs_report_timings_;
|
||||
}
|
||||
|
||||
Settings ShellTest::CreateSettingsForFixture() {
|
||||
Settings settings;
|
||||
settings.leak_vm = false;
|
||||
@@ -84,6 +150,23 @@ TaskRunners ShellTest::GetTaskRunnersForFixture() {
|
||||
};
|
||||
}
|
||||
|
||||
std::unique_ptr<Shell> ShellTest::CreateShell(Settings settings) {
|
||||
return CreateShell(std::move(settings), GetTaskRunnersForFixture());
|
||||
}
|
||||
|
||||
std::unique_ptr<Shell> ShellTest::CreateShell(Settings settings,
|
||||
TaskRunners task_runners) {
|
||||
return Shell::Create(
|
||||
task_runners, settings,
|
||||
[](Shell& shell) {
|
||||
return std::make_unique<ShellTestPlatformView>(shell,
|
||||
shell.GetTaskRunners());
|
||||
},
|
||||
[](Shell& shell) {
|
||||
return std::make_unique<Rasterizer>(shell, shell.GetTaskRunners());
|
||||
});
|
||||
}
|
||||
|
||||
// |testing::ThreadTest|
|
||||
void ShellTest::SetUp() {
|
||||
ThreadTest::SetUp();
|
||||
|
||||
@@ -26,11 +26,31 @@ class ShellTest : public ThreadTest {
|
||||
~ShellTest();
|
||||
|
||||
Settings CreateSettingsForFixture();
|
||||
|
||||
std::unique_ptr<Shell> CreateShell(Settings settings);
|
||||
std::unique_ptr<Shell> CreateShell(Settings settings,
|
||||
TaskRunners task_runners);
|
||||
TaskRunners GetTaskRunnersForFixture();
|
||||
|
||||
void AddNativeCallback(std::string name, Dart_NativeFunction callback);
|
||||
|
||||
static void PlatformViewNotifyCreated(
|
||||
Shell* shell); // This creates the surface
|
||||
static void RunEngine(Shell* shell, RunConfiguration configuration);
|
||||
|
||||
static void PumpOneFrame(Shell* shell);
|
||||
|
||||
// Declare |UnreportedTimingsCount|, |GetNeedsReportTimings| and
|
||||
// |SetNeedsReportTimings| inside |ShellTest| mainly for easier friend class
|
||||
// declarations as shell unit tests and Shell are in different name spaces.
|
||||
|
||||
static bool GetNeedsReportTimings(Shell* shell);
|
||||
static void SetNeedsReportTimings(Shell* shell, bool value);
|
||||
|
||||
// Do not assert |UnreportedTimingsCount| to be positive in any tests.
|
||||
// Otherwise those tests will be flaky as the clearing of unreported timings
|
||||
// is unpredictive.
|
||||
static int UnreportedTimingsCount(Shell* shell);
|
||||
|
||||
protected:
|
||||
// |testing::ThreadTest|
|
||||
void SetUp() override;
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <future>
|
||||
#include <memory>
|
||||
|
||||
#include "flutter/flow/layers/layer_tree.h"
|
||||
#include "flutter/flow/layers/transform_layer.h"
|
||||
#include "flutter/fml/command_line.h"
|
||||
#include "flutter/fml/make_copyable.h"
|
||||
#include "flutter/fml/message_loop.h"
|
||||
@@ -32,15 +34,7 @@ static bool ValidateShell(Shell* shell) {
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
fml::AutoResetWaitableEvent latch;
|
||||
fml::TaskRunner::RunNowOrPostTask(
|
||||
shell->GetTaskRunners().GetPlatformTaskRunner(), [shell, &latch]() {
|
||||
shell->GetPlatformView()->NotifyCreated();
|
||||
latch.Signal();
|
||||
});
|
||||
latch.Wait();
|
||||
}
|
||||
ShellTest::PlatformViewNotifyCreated(shell);
|
||||
|
||||
{
|
||||
fml::AutoResetWaitableEvent latch;
|
||||
@@ -59,15 +53,7 @@ TEST_F(ShellTest, InitializeWithInvalidThreads) {
|
||||
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
|
||||
Settings settings = CreateSettingsForFixture();
|
||||
TaskRunners task_runners("test", nullptr, nullptr, nullptr, nullptr);
|
||||
auto shell = Shell::Create(
|
||||
std::move(task_runners), settings,
|
||||
[](Shell& shell) {
|
||||
return std::make_unique<ShellTestPlatformView>(shell,
|
||||
shell.GetTaskRunners());
|
||||
},
|
||||
[](Shell& shell) {
|
||||
return std::make_unique<Rasterizer>(shell.GetTaskRunners());
|
||||
});
|
||||
auto shell = CreateShell(std::move(settings), std::move(task_runners));
|
||||
ASSERT_FALSE(shell);
|
||||
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
|
||||
}
|
||||
@@ -82,15 +68,7 @@ TEST_F(ShellTest, InitializeWithDifferentThreads) {
|
||||
thread_host.gpu_thread->GetTaskRunner(),
|
||||
thread_host.ui_thread->GetTaskRunner(),
|
||||
thread_host.io_thread->GetTaskRunner());
|
||||
auto shell = Shell::Create(
|
||||
std::move(task_runners), settings,
|
||||
[](Shell& shell) {
|
||||
return std::make_unique<ShellTestPlatformView>(shell,
|
||||
shell.GetTaskRunners());
|
||||
},
|
||||
[](Shell& shell) {
|
||||
return std::make_unique<Rasterizer>(shell.GetTaskRunners());
|
||||
});
|
||||
auto shell = CreateShell(std::move(settings), std::move(task_runners));
|
||||
ASSERT_TRUE(ValidateShell(shell.get()));
|
||||
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
|
||||
shell.reset();
|
||||
@@ -105,15 +83,7 @@ TEST_F(ShellTest, InitializeWithSingleThread) {
|
||||
auto task_runner = thread_host.platform_thread->GetTaskRunner();
|
||||
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
|
||||
task_runner);
|
||||
auto shell = Shell::Create(
|
||||
std::move(task_runners), settings,
|
||||
[](Shell& shell) {
|
||||
return std::make_unique<ShellTestPlatformView>(shell,
|
||||
shell.GetTaskRunners());
|
||||
},
|
||||
[](Shell& shell) {
|
||||
return std::make_unique<Rasterizer>(shell.GetTaskRunners());
|
||||
});
|
||||
auto shell = CreateShell(std::move(settings), std::move(task_runners));
|
||||
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
|
||||
ASSERT_TRUE(ValidateShell(shell.get()));
|
||||
shell.reset();
|
||||
@@ -127,15 +97,7 @@ TEST_F(ShellTest, InitializeWithSingleThreadWhichIsTheCallingThread) {
|
||||
auto task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner();
|
||||
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
|
||||
task_runner);
|
||||
auto shell = Shell::Create(
|
||||
std::move(task_runners), settings,
|
||||
[](Shell& shell) {
|
||||
return std::make_unique<ShellTestPlatformView>(shell,
|
||||
shell.GetTaskRunners());
|
||||
},
|
||||
[](Shell& shell) {
|
||||
return std::make_unique<Rasterizer>(shell.GetTaskRunners());
|
||||
});
|
||||
auto shell = CreateShell(std::move(settings), std::move(task_runners));
|
||||
ASSERT_TRUE(ValidateShell(shell.get()));
|
||||
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
|
||||
shell.reset();
|
||||
@@ -162,7 +124,7 @@ TEST_F(ShellTest,
|
||||
shell.GetTaskRunners());
|
||||
},
|
||||
[](Shell& shell) {
|
||||
return std::make_unique<Rasterizer>(shell.GetTaskRunners());
|
||||
return std::make_unique<Rasterizer>(shell, shell.GetTaskRunners());
|
||||
});
|
||||
ASSERT_TRUE(ValidateShell(shell.get()));
|
||||
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
|
||||
@@ -183,15 +145,7 @@ TEST_F(ShellTest, InitializeWithGPUAndPlatformThreadsTheSame) {
|
||||
thread_host.ui_thread->GetTaskRunner(), // ui
|
||||
thread_host.io_thread->GetTaskRunner() // io
|
||||
);
|
||||
auto shell = Shell::Create(
|
||||
std::move(task_runners), settings,
|
||||
[](Shell& shell) {
|
||||
return std::make_unique<ShellTestPlatformView>(shell,
|
||||
shell.GetTaskRunners());
|
||||
},
|
||||
[](Shell& shell) {
|
||||
return std::make_unique<Rasterizer>(shell.GetTaskRunners());
|
||||
});
|
||||
auto shell = CreateShell(std::move(settings), std::move(task_runners));
|
||||
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
|
||||
ASSERT_TRUE(ValidateShell(shell.get()));
|
||||
shell.reset();
|
||||
@@ -200,16 +154,8 @@ TEST_F(ShellTest, InitializeWithGPUAndPlatformThreadsTheSame) {
|
||||
|
||||
TEST_F(ShellTest, FixturesAreFunctional) {
|
||||
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
|
||||
const auto settings = CreateSettingsForFixture();
|
||||
auto shell = Shell::Create(
|
||||
GetTaskRunnersForFixture(), settings,
|
||||
[](Shell& shell) {
|
||||
return std::make_unique<ShellTestPlatformView>(shell,
|
||||
shell.GetTaskRunners());
|
||||
},
|
||||
[](Shell& shell) {
|
||||
return std::make_unique<Rasterizer>(shell.GetTaskRunners());
|
||||
});
|
||||
auto settings = CreateSettingsForFixture();
|
||||
auto shell = CreateShell(std::move(settings));
|
||||
ASSERT_TRUE(ValidateShell(shell.get()));
|
||||
|
||||
auto configuration = RunConfiguration::InferFromSettings(settings);
|
||||
@@ -221,17 +167,7 @@ TEST_F(ShellTest, FixturesAreFunctional) {
|
||||
"SayHiFromFixturesAreFunctionalMain",
|
||||
CREATE_NATIVE_ENTRY([&main_latch](auto args) { main_latch.Signal(); }));
|
||||
|
||||
fml::AutoResetWaitableEvent latch;
|
||||
fml::TaskRunner::RunNowOrPostTask(
|
||||
shell->GetTaskRunners().GetUITaskRunner(),
|
||||
fml::MakeCopyable([&latch, config = std::move(configuration),
|
||||
engine = shell->GetEngine()]() mutable {
|
||||
ASSERT_TRUE(engine);
|
||||
ASSERT_EQ(engine->Run(std::move(config)), Engine::RunStatus::Success);
|
||||
latch.Signal();
|
||||
}));
|
||||
|
||||
latch.Wait();
|
||||
RunEngine(shell.get(), std::move(configuration));
|
||||
main_latch.Wait();
|
||||
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
|
||||
shell.reset();
|
||||
@@ -240,16 +176,8 @@ TEST_F(ShellTest, FixturesAreFunctional) {
|
||||
|
||||
TEST_F(ShellTest, SecondaryIsolateBindingsAreSetupViaShellSettings) {
|
||||
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
|
||||
const auto settings = CreateSettingsForFixture();
|
||||
auto shell = Shell::Create(
|
||||
GetTaskRunnersForFixture(), settings,
|
||||
[](Shell& shell) {
|
||||
return std::make_unique<ShellTestPlatformView>(shell,
|
||||
shell.GetTaskRunners());
|
||||
},
|
||||
[](Shell& shell) {
|
||||
return std::make_unique<Rasterizer>(shell.GetTaskRunners());
|
||||
});
|
||||
auto settings = CreateSettingsForFixture();
|
||||
auto shell = CreateShell(std::move(settings));
|
||||
ASSERT_TRUE(ValidateShell(shell.get()));
|
||||
|
||||
auto configuration = RunConfiguration::InferFromSettings(settings);
|
||||
@@ -261,13 +189,7 @@ TEST_F(ShellTest, SecondaryIsolateBindingsAreSetupViaShellSettings) {
|
||||
latch.CountDown();
|
||||
}));
|
||||
|
||||
fml::TaskRunner::RunNowOrPostTask(
|
||||
shell->GetTaskRunners().GetUITaskRunner(),
|
||||
fml::MakeCopyable([config = std::move(configuration),
|
||||
engine = shell->GetEngine()]() mutable {
|
||||
ASSERT_TRUE(engine);
|
||||
ASSERT_EQ(engine->Run(std::move(config)), Engine::RunStatus::Success);
|
||||
}));
|
||||
RunEngine(shell.get(), std::move(configuration));
|
||||
|
||||
latch.Wait();
|
||||
|
||||
@@ -313,5 +235,188 @@ TEST_F(ShellTest, WhitelistedDartVMFlag) {
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_F(ShellTest, NoNeedToReportTimingsByDefault) {
|
||||
auto settings = CreateSettingsForFixture();
|
||||
std::unique_ptr<Shell> shell = CreateShell(std::move(settings));
|
||||
|
||||
// Create the surface needed by rasterizer
|
||||
PlatformViewNotifyCreated(shell.get());
|
||||
|
||||
auto configuration = RunConfiguration::InferFromSettings(settings);
|
||||
configuration.SetEntrypoint("emptyMain");
|
||||
|
||||
RunEngine(shell.get(), std::move(configuration));
|
||||
PumpOneFrame(shell.get());
|
||||
ASSERT_FALSE(GetNeedsReportTimings(shell.get()));
|
||||
|
||||
// This assertion may or may not be the direct result of needs_report_timings_
|
||||
// being false. The count could be 0 simply because we just cleared unreported
|
||||
// timings by reporting them. Hence this can't replace the
|
||||
// ASSERT_FALSE(GetNeedsReportTimings(shell.get())) check. We added this
|
||||
// assertion for an additional confidence that we're not pushing back to
|
||||
// unreported timings unnecessarily.
|
||||
//
|
||||
// Conversely, do not assert UnreportedTimingsCount(shell.get()) to be
|
||||
// positive in any tests. Otherwise those tests will be flaky as the clearing
|
||||
// of unreported timings is unpredictive.
|
||||
ASSERT_EQ(UnreportedTimingsCount(shell.get()), 0);
|
||||
}
|
||||
|
||||
TEST_F(ShellTest, NeedsReportTimingsIsSetWithCallback) {
|
||||
auto settings = CreateSettingsForFixture();
|
||||
std::unique_ptr<Shell> shell = CreateShell(std::move(settings));
|
||||
|
||||
// Create the surface needed by rasterizer
|
||||
PlatformViewNotifyCreated(shell.get());
|
||||
|
||||
auto configuration = RunConfiguration::InferFromSettings(settings);
|
||||
configuration.SetEntrypoint("dummyReportTimingsMain");
|
||||
|
||||
RunEngine(shell.get(), std::move(configuration));
|
||||
PumpOneFrame(shell.get());
|
||||
ASSERT_TRUE(GetNeedsReportTimings(shell.get()));
|
||||
}
|
||||
|
||||
static void CheckFrameTimings(const std::vector<FrameTiming>& timings,
|
||||
fml::TimePoint start,
|
||||
fml::TimePoint finish) {
|
||||
fml::TimePoint last_frame_start;
|
||||
for (size_t i = 0; i < timings.size(); i += 1) {
|
||||
// Ensure that timings are sorted.
|
||||
ASSERT_TRUE(timings[i].Get(FrameTiming::kPhases[0]) >= last_frame_start);
|
||||
last_frame_start = timings[i].Get(FrameTiming::kPhases[0]);
|
||||
|
||||
fml::TimePoint last_phase_time;
|
||||
for (auto phase : FrameTiming::kPhases) {
|
||||
ASSERT_TRUE(timings[i].Get(phase) >= start);
|
||||
ASSERT_TRUE(timings[i].Get(phase) <= finish);
|
||||
|
||||
// phases should have weakly increasing time points
|
||||
ASSERT_TRUE(last_phase_time <= timings[i].Get(phase));
|
||||
last_phase_time = timings[i].Get(phase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ShellTest, ReportTimingsIsCalled) {
|
||||
fml::TimePoint start = fml::TimePoint::Now();
|
||||
auto settings = CreateSettingsForFixture();
|
||||
std::unique_ptr<Shell> shell = CreateShell(std::move(settings));
|
||||
|
||||
// Create the surface needed by rasterizer
|
||||
PlatformViewNotifyCreated(shell.get());
|
||||
|
||||
auto configuration = RunConfiguration::InferFromSettings(settings);
|
||||
ASSERT_TRUE(configuration.IsValid());
|
||||
configuration.SetEntrypoint("reportTimingsMain");
|
||||
fml::AutoResetWaitableEvent reportLatch;
|
||||
std::vector<int64_t> timestamps;
|
||||
auto nativeTimingCallback = [&reportLatch,
|
||||
×tamps](Dart_NativeArguments args) {
|
||||
Dart_Handle exception = nullptr;
|
||||
timestamps = tonic::DartConverter<std::vector<int64_t>>::FromArguments(
|
||||
args, 0, exception);
|
||||
reportLatch.Signal();
|
||||
};
|
||||
AddNativeCallback("NativeReportTimingsCallback",
|
||||
CREATE_NATIVE_ENTRY(nativeTimingCallback));
|
||||
RunEngine(shell.get(), std::move(configuration));
|
||||
|
||||
// Pump many frames so we can trigger the report quickly instead of waiting
|
||||
// for the 1 second threshold.
|
||||
for (int i = 0; i < 200; i += 1) {
|
||||
PumpOneFrame(shell.get());
|
||||
}
|
||||
|
||||
reportLatch.Wait();
|
||||
shell.reset();
|
||||
|
||||
fml::TimePoint finish = fml::TimePoint::Now();
|
||||
ASSERT_TRUE(timestamps.size() > 0);
|
||||
ASSERT_TRUE(timestamps.size() % FrameTiming::kCount == 0);
|
||||
std::vector<FrameTiming> timings(timestamps.size() / FrameTiming::kCount);
|
||||
|
||||
for (size_t i = 0; i * FrameTiming::kCount < timestamps.size(); i += 1) {
|
||||
for (auto phase : FrameTiming::kPhases) {
|
||||
timings[i].Set(
|
||||
phase,
|
||||
fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromMicroseconds(
|
||||
timestamps[i * FrameTiming::kCount + phase])));
|
||||
}
|
||||
}
|
||||
CheckFrameTimings(timings, start, finish);
|
||||
}
|
||||
|
||||
TEST_F(ShellTest, FrameRasterizedCallbackIsCalled) {
|
||||
fml::TimePoint start = fml::TimePoint::Now();
|
||||
|
||||
auto settings = CreateSettingsForFixture();
|
||||
fml::AutoResetWaitableEvent timingLatch;
|
||||
FrameTiming timing;
|
||||
|
||||
for (auto phase : FrameTiming::kPhases) {
|
||||
timing.Set(phase, fml::TimePoint());
|
||||
// Check that the time points are initially smaller than start, so
|
||||
// CheckFrameTimings will fail if they're not properly set later.
|
||||
ASSERT_TRUE(timing.Get(phase) < start);
|
||||
}
|
||||
|
||||
settings.frame_rasterized_callback = [&timing,
|
||||
&timingLatch](const FrameTiming& t) {
|
||||
timing = t;
|
||||
timingLatch.Signal();
|
||||
};
|
||||
|
||||
std::unique_ptr<Shell> shell = CreateShell(std::move(settings));
|
||||
|
||||
// Create the surface needed by rasterizer
|
||||
PlatformViewNotifyCreated(shell.get());
|
||||
|
||||
auto configuration = RunConfiguration::InferFromSettings(settings);
|
||||
configuration.SetEntrypoint("onBeginFrameMain");
|
||||
|
||||
int64_t begin_frame;
|
||||
auto nativeOnBeginFrame = [&begin_frame](Dart_NativeArguments args) {
|
||||
Dart_Handle exception = nullptr;
|
||||
begin_frame =
|
||||
tonic::DartConverter<int64_t>::FromArguments(args, 0, exception);
|
||||
};
|
||||
AddNativeCallback("NativeOnBeginFrame",
|
||||
CREATE_NATIVE_ENTRY(nativeOnBeginFrame));
|
||||
|
||||
RunEngine(shell.get(), std::move(configuration));
|
||||
|
||||
PumpOneFrame(shell.get());
|
||||
|
||||
// Check that timing is properly set. This implies that
|
||||
// settings.frame_rasterized_callback is called.
|
||||
timingLatch.Wait();
|
||||
fml::TimePoint finish = fml::TimePoint::Now();
|
||||
std::vector<FrameTiming> timings = {timing};
|
||||
CheckFrameTimings(timings, start, finish);
|
||||
|
||||
// Check that onBeginFrame has the same timestamp as FrameTiming's build start
|
||||
int64_t build_start =
|
||||
timing.Get(FrameTiming::kBuildStart).ToEpochDelta().ToMicroseconds();
|
||||
ASSERT_EQ(build_start, begin_frame);
|
||||
}
|
||||
|
||||
TEST(SettingsTest, FrameTimingSetsAndGetsProperly) {
|
||||
// Ensure that all phases are in kPhases.
|
||||
ASSERT_EQ(sizeof(FrameTiming::kPhases),
|
||||
FrameTiming::kCount * sizeof(FrameTiming::Phase));
|
||||
|
||||
int lastPhaseIndex = -1;
|
||||
FrameTiming timing;
|
||||
for (auto phase : FrameTiming::kPhases) {
|
||||
ASSERT_TRUE(phase > lastPhaseIndex); // Ensure that kPhases are in order.
|
||||
lastPhaseIndex = phase;
|
||||
auto fake_time =
|
||||
fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromMicroseconds(phase));
|
||||
timing.Set(phase, fake_time);
|
||||
ASSERT_TRUE(timing.Get(phase) == fake_time);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace flutter
|
||||
|
||||
@@ -73,7 +73,7 @@ AndroidShellHolder::AndroidShellHolder(
|
||||
};
|
||||
|
||||
Shell::CreateCallback<Rasterizer> on_create_rasterizer = [](Shell& shell) {
|
||||
return std::make_unique<Rasterizer>(shell.GetTaskRunners());
|
||||
return std::make_unique<Rasterizer>(shell, shell.GetTaskRunners());
|
||||
};
|
||||
|
||||
// The current thread will be used as the platform thread. Ensure that the
|
||||
|
||||
@@ -353,7 +353,7 @@
|
||||
|
||||
flutter::Shell::CreateCallback<flutter::Rasterizer> on_create_rasterizer =
|
||||
[](flutter::Shell& shell) {
|
||||
return std::make_unique<flutter::Rasterizer>(shell.GetTaskRunners());
|
||||
return std::make_unique<flutter::Rasterizer>(shell, shell.GetTaskRunners());
|
||||
};
|
||||
|
||||
if (flutter::IsIosEmbeddedViewsPreviewEnabled()) {
|
||||
|
||||
@@ -527,7 +527,8 @@ FlutterEngineResult FlutterEngineRun(size_t version,
|
||||
|
||||
flutter::Shell::CreateCallback<flutter::Rasterizer> on_create_rasterizer =
|
||||
[](flutter::Shell& shell) {
|
||||
return std::make_unique<flutter::Rasterizer>(shell.GetTaskRunners());
|
||||
return std::make_unique<flutter::Rasterizer>(shell,
|
||||
shell.GetTaskRunners());
|
||||
};
|
||||
|
||||
// TODO(chinmaygarde): This is the wrong spot for this. It belongs in the
|
||||
|
||||
@@ -106,7 +106,7 @@ int RunTester(const flutter::Settings& settings, bool run_forever) {
|
||||
};
|
||||
|
||||
Shell::CreateCallback<Rasterizer> on_create_rasterizer = [](Shell& shell) {
|
||||
return std::make_unique<Rasterizer>(shell.GetTaskRunners());
|
||||
return std::make_unique<Rasterizer>(shell, shell.GetTaskRunners());
|
||||
};
|
||||
|
||||
auto shell = Shell::Create(task_runners, //
|
||||
|
||||
@@ -36,6 +36,7 @@ void main() {
|
||||
VoidCallback originalOnLocaleChanged;
|
||||
FrameCallback originalOnBeginFrame;
|
||||
VoidCallback originalOnDrawFrame;
|
||||
TimingsCallback originalOnReportTimings;
|
||||
PointerDataPacketCallback originalOnPointerDataPacket;
|
||||
VoidCallback originalOnSemanticsEnabledChanged;
|
||||
SemanticsActionCallback originalOnSemanticsAction;
|
||||
@@ -47,6 +48,7 @@ void main() {
|
||||
originalOnLocaleChanged = window.onLocaleChanged;
|
||||
originalOnBeginFrame = window.onBeginFrame;
|
||||
originalOnDrawFrame = window.onDrawFrame;
|
||||
originalOnReportTimings = window.onReportTimings;
|
||||
originalOnPointerDataPacket = window.onPointerDataPacket;
|
||||
originalOnSemanticsEnabledChanged = window.onSemanticsEnabledChanged;
|
||||
originalOnSemanticsAction = window.onSemanticsAction;
|
||||
@@ -59,6 +61,7 @@ void main() {
|
||||
window.onLocaleChanged = originalOnLocaleChanged;
|
||||
window.onBeginFrame = originalOnBeginFrame;
|
||||
window.onDrawFrame = originalOnDrawFrame;
|
||||
window.onReportTimings = originalOnReportTimings;
|
||||
window.onPointerDataPacket = originalOnPointerDataPacket;
|
||||
window.onSemanticsEnabledChanged = originalOnSemanticsEnabledChanged;
|
||||
window.onSemanticsAction = originalOnSemanticsAction;
|
||||
@@ -146,6 +149,22 @@ void main() {
|
||||
expect(runZone, same(innerZone));
|
||||
});
|
||||
|
||||
test('onReportTimings preserves callback zone', () {
|
||||
Zone innerZone;
|
||||
Zone runZone;
|
||||
|
||||
runZoned(() {
|
||||
innerZone = Zone.current;
|
||||
window.onReportTimings = (List<FrameTiming> timings) {
|
||||
runZone = Zone.current;
|
||||
};
|
||||
});
|
||||
|
||||
_reportTimings(<int>[]);
|
||||
expect(runZone, isNotNull);
|
||||
expect(runZone, same(innerZone));
|
||||
});
|
||||
|
||||
test('onPointerDataPacket preserves callback zone', () {
|
||||
Zone innerZone;
|
||||
Zone runZone;
|
||||
|
||||
@@ -19,4 +19,9 @@ void main() {
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
test('FrameTiming.toString has the correct format', () {
|
||||
FrameTiming timing = FrameTiming(<int>[1000, 8000, 9000, 19500]);
|
||||
expect(timing.toString(), 'FrameTiming(buildDuration: 7.0ms, rasterDuration: 10.5ms, totalSpan: 18.5ms)');
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user