Reverts "Implement frame timing callbacks in Skwasm. (#50737)" (#50895)
Reverts flutter/engine#50737
Initiated by: goderbauer
Reason for reverting: Fails in device lab, see https://logs.chromium.org/logs/flutter/buildbucket/cr-buildbucket/8755350727803344657/+/u/run_web_benchmarks_skwasm/stdout
Original PR Author: eyebrowsoffire
Reviewed By: {mdebbar, yjbanov}
This change reverts the following previous change:
Original Description:
Fixes https://github.com/flutter/flutter/issues/140429
Some notes here:
* Refactored the frame timing systems so that we can deal with asynchronous rendering.
* Consolidated rendering of multiple pictures in skwasm into a single call, so that the rasterization can be properly measured.
* Pulled the frame timings tests into the `ui` test suite so that they run on all renderers (including skwasm).
diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index 2d40298..d287ed8 100644
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -10324,7 +10324,6 @@
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/font_fallbacks.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/fonts.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/frame_reference.dart + ../../../flutter/LICENSE
-ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/frame_timing_recorder.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/html/backdrop_filter.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/html/bitmap_canvas.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/html/canvas.dart + ../../../flutter/LICENSE
@@ -13162,7 +13161,6 @@
FILE: ../../../flutter/lib/web_ui/lib/src/engine/font_fallbacks.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/fonts.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/frame_reference.dart
-FILE: ../../../flutter/lib/web_ui/lib/src/engine/frame_timing_recorder.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/backdrop_filter.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/bitmap_canvas.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/canvas.dart
diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart
index 0fbda33..9697966 100644
--- a/lib/web_ui/lib/src/engine.dart
+++ b/lib/web_ui/lib/src/engine.dart
@@ -65,7 +65,6 @@
export 'engine/font_fallbacks.dart';
export 'engine/fonts.dart';
export 'engine/frame_reference.dart';
-export 'engine/frame_timing_recorder.dart';
export 'engine/html/backdrop_filter.dart';
export 'engine/html/bitmap_canvas.dart';
export 'engine/html/canvas.dart';
diff --git a/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart b/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart
index bdf6d74..58a23e8 100644
--- a/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart
+++ b/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart
@@ -131,7 +131,6 @@
typedef RenderRequest = ({
ui.Scene scene,
Completer<void> completer,
- FrameTimingRecorder? recorder,
});
/// A per-view queue of render requests. Only contains the current render
diff --git a/lib/web_ui/lib/src/engine/canvaskit/renderer.dart b/lib/web_ui/lib/src/engine/canvaskit/renderer.dart
index 7d673a6..f4fdaef 100644
--- a/lib/web_ui/lib/src/engine/canvaskit/renderer.dart
+++ b/lib/web_ui/lib/src/engine/canvaskit/renderer.dart
@@ -417,17 +417,16 @@
"Unable to render to a view which hasn't been registered");
final ViewRasterizer rasterizer = _rasterizers[view.viewId]!;
final RenderQueue renderQueue = rasterizer.queue;
- final FrameTimingRecorder? recorder = FrameTimingRecorder.frameTimingsEnabled ? FrameTimingRecorder() : null;
if (renderQueue.current != null) {
// If a scene is already queued up, drop it and queue this one up instead
// so that the scene view always displays the most recently requested scene.
renderQueue.next?.completer.complete();
final Completer<void> completer = Completer<void>();
- renderQueue.next = (scene: scene, completer: completer, recorder: recorder);
+ renderQueue.next = (scene: scene, completer: completer);
return completer.future;
}
final Completer<void> completer = Completer<void>();
- renderQueue.current = (scene: scene, completer: completer, recorder: recorder);
+ renderQueue.current = (scene: scene, completer: completer);
unawaited(_kickRenderLoop(rasterizer));
return completer.future;
}
@@ -436,7 +435,7 @@
final RenderQueue renderQueue = rasterizer.queue;
final RenderRequest current = renderQueue.current!;
try {
- await _renderScene(current.scene, rasterizer, current.recorder);
+ await _renderScene(current.scene, rasterizer);
current.completer.complete();
} catch (error, stackTrace) {
current.completer.completeError(error, stackTrace);
@@ -450,7 +449,7 @@
}
}
- Future<void> _renderScene(ui.Scene scene, ViewRasterizer rasterizer, FrameTimingRecorder? recorder) async {
+ Future<void> _renderScene(ui.Scene scene, ViewRasterizer rasterizer) async {
// "Build finish" and "raster start" happen back-to-back because we
// render on the same thread, so there's no overhead from hopping to
// another thread.
@@ -458,12 +457,11 @@
// CanvasKit works differently from the HTML renderer in that in HTML
// we update the DOM in SceneBuilder.build, which is these function calls
// here are CanvasKit-only.
- recorder?.recordBuildFinish();
- recorder?.recordRasterStart();
+ frameTimingsOnBuildFinish();
+ frameTimingsOnRasterStart();
await rasterizer.draw((scene as LayerScene).layerTree);
- recorder?.recordRasterFinish();
- recorder?.submitTimings();
+ frameTimingsOnRasterFinish();
}
// Map from view id to the associated Rasterizer for that view.
diff --git a/lib/web_ui/lib/src/engine/dom.dart b/lib/web_ui/lib/src/engine/dom.dart
index 386666d..7505877 100644
--- a/lib/web_ui/lib/src/engine/dom.dart
+++ b/lib/web_ui/lib/src/engine/dom.dart
@@ -1487,7 +1487,7 @@
extension DomCanvasRenderingContextBitmapRendererExtension
on DomCanvasRenderingContextBitmapRenderer {
- external void transferFromImageBitmap(DomImageBitmap? bitmap);
+ external void transferFromImageBitmap(DomImageBitmap bitmap);
}
@JS('ImageData')
diff --git a/lib/web_ui/lib/src/engine/frame_timing_recorder.dart b/lib/web_ui/lib/src/engine/frame_timing_recorder.dart
deleted file mode 100644
index ec2944b..0000000
--- a/lib/web_ui/lib/src/engine/frame_timing_recorder.dart
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'package:ui/src/engine.dart';
-import 'package:ui/ui.dart' as ui;
-
-class FrameTimingRecorder {
- final int _vsyncStartMicros = _currentFrameVsyncStart;
- final int _buildStartMicros = _currentFrameBuildStart;
-
- int? _buildFinishMicros;
- int? _rasterStartMicros;
- int? _rasterFinishMicros;
-
- /// Collects frame timings from frames.
- ///
- /// This list is periodically reported to the framework (see [_kFrameTimingsSubmitInterval]).
- static List<ui.FrameTiming> _frameTimings = <ui.FrameTiming>[];
-
- /// These two metrics are collected early in the process, before the respective
- /// scene builders are created. These are instead treated as global state, which
- /// are used to initialize any recorders that are created by the scene builders.
- static int _currentFrameVsyncStart = 0;
- static int _currentFrameBuildStart = 0;
-
- static void recordCurrentFrameVsync() {
- if (frameTimingsEnabled) {
- _currentFrameVsyncStart = _nowMicros();
- }
- }
-
- static void recordCurrentFrameBuildStart() {
- if (frameTimingsEnabled) {
- _currentFrameBuildStart = _nowMicros();
- }
- }
-
- /// The last time (in microseconds) we submitted frame timings.
- static int _frameTimingsLastSubmitTime = _nowMicros();
- /// The amount of time in microseconds we wait between submitting
- /// frame timings.
- static const int _kFrameTimingsSubmitInterval = 100000; // 100 milliseconds
-
- /// Whether we are collecting [ui.FrameTiming]s.
- static bool get frameTimingsEnabled {
- return EnginePlatformDispatcher.instance.onReportTimings != null;
- }
-
- /// Current timestamp in microseconds taken from the high-precision
- /// monotonically increasing timer.
- ///
- /// See also:
- ///
- /// * https://developer.mozilla.org/en-US/docs/Web/API/Performance/now,
- /// particularly notes about Firefox rounding to 1ms for security reasons,
- /// which can be bypassed in tests by setting certain browser options.
- static int _nowMicros() {
- return (domWindow.performance.now() * 1000).toInt();
- }
-
- void recordBuildFinish([int? buildFinish]) {
- assert(_buildFinishMicros == null, "can't record build finish more than once");
- _buildFinishMicros = buildFinish ?? _nowMicros();
- }
-
- void recordRasterStart([int? rasterStart]) {
- assert(_rasterStartMicros == null, "can't record raster start more than once");
- _rasterStartMicros = rasterStart ?? _nowMicros();
- }
-
- void recordRasterFinish([int? rasterFinish]) {
- assert(_rasterFinishMicros == null, "can't record raster finish more than once");
- _rasterFinishMicros = rasterFinish ?? _nowMicros();
- }
-
- void submitTimings() {
- assert(
- _buildFinishMicros != null &&
- _rasterStartMicros != null &&
- _rasterFinishMicros != null,
- 'Attempted to submit an incomplete timings.'
- );
- final ui.FrameTiming timing = ui.FrameTiming(
- vsyncStart: _vsyncStartMicros,
- buildStart: _buildStartMicros,
- buildFinish: _buildFinishMicros!,
- rasterStart: _rasterStartMicros!,
- rasterFinish: _rasterFinishMicros!,
- rasterFinishWallTime: _rasterFinishMicros!,
- );
- _frameTimings.add(timing);
- final int now = _nowMicros();
- if (now - _frameTimingsLastSubmitTime > _kFrameTimingsSubmitInterval) {
- _frameTimingsLastSubmitTime = now;
- EnginePlatformDispatcher.instance.invokeOnReportTimings(_frameTimings);
- _frameTimings = <ui.FrameTiming>[];
- }
- }
-}
diff --git a/lib/web_ui/lib/src/engine/html/renderer.dart b/lib/web_ui/lib/src/engine/html/renderer.dart
index b41fac3..c9febef 100644
--- a/lib/web_ui/lib/src/engine/html/renderer.dart
+++ b/lib/web_ui/lib/src/engine/html/renderer.dart
@@ -323,11 +323,8 @@
@override
Future<void> renderScene(ui.Scene scene, ui.FlutterView view) async {
final EngineFlutterView implicitView = EnginePlatformDispatcher.instance.implicitView!;
- scene as SurfaceScene;
- implicitView.dom.setScene(scene.webOnlyRootElement!);
- final FrameTimingRecorder? recorder = scene.timingRecorder;
- recorder?.recordRasterFinish();
- recorder?.submitTimings();
+ implicitView.dom.setScene((scene as SurfaceScene).webOnlyRootElement!);
+ frameTimingsOnRasterFinish();
}
@override
diff --git a/lib/web_ui/lib/src/engine/html/scene.dart b/lib/web_ui/lib/src/engine/html/scene.dart
index b4deb9a..f15d043 100644
--- a/lib/web_ui/lib/src/engine/html/scene.dart
+++ b/lib/web_ui/lib/src/engine/html/scene.dart
@@ -2,20 +2,22 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'package:ui/src/engine.dart';
+import 'package:ui/src/engine/display.dart';
import 'package:ui/ui.dart' as ui;
+import '../dom.dart';
+import '../vector_math.dart';
+import '../window.dart';
+import 'surface.dart';
+
class SurfaceScene implements ui.Scene {
/// This class is created by the engine, and should not be instantiated
/// or extended directly.
///
/// To create a Scene object, use a [SceneBuilder].
- SurfaceScene(this.webOnlyRootElement, {
- required this.timingRecorder,
- });
+ SurfaceScene(this.webOnlyRootElement);
final DomElement? webOnlyRootElement;
- final FrameTimingRecorder? timingRecorder;
/// Creates a raster image representation of the current state of the scene.
/// This is a slow operation that is performed on a background thread.
diff --git a/lib/web_ui/lib/src/engine/html/scene_builder.dart b/lib/web_ui/lib/src/engine/html/scene_builder.dart
index 701bb11..e721aa5 100644
--- a/lib/web_ui/lib/src/engine/html/scene_builder.dart
+++ b/lib/web_ui/lib/src/engine/html/scene_builder.dart
@@ -7,7 +7,7 @@
import 'package:ui/ui.dart' as ui;
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
-import '../../engine.dart' show FrameTimingRecorder, kProfileApplyFrame, kProfilePrerollFrame;
+import '../../engine.dart' show kProfileApplyFrame, kProfilePrerollFrame;
import '../display.dart';
import '../dom.dart';
import '../picture.dart';
@@ -511,9 +511,8 @@
// In the HTML renderer we time the beginning of the rasterization phase
// (counter-intuitively) in SceneBuilder.build because DOM updates happen
// here. This is different from CanvasKit.
- final FrameTimingRecorder? recorder = FrameTimingRecorder.frameTimingsEnabled ? FrameTimingRecorder() : null;
- recorder?.recordBuildFinish();
- recorder?.recordRasterStart();
+ frameTimingsOnBuildFinish();
+ frameTimingsOnRasterStart();
timeAction<void>(kProfilePrerollFrame, () {
while (_surfaceStack.length > 1) {
// Auto-pop layers that were pushed without a corresponding pop.
@@ -529,7 +528,7 @@
}
commitScene(_persistedScene);
_lastFrameScene = _persistedScene;
- return SurfaceScene(_persistedScene.rootElement, timingRecorder: recorder);
+ return SurfaceScene(_persistedScene.rootElement);
});
}
diff --git a/lib/web_ui/lib/src/engine/initialization.dart b/lib/web_ui/lib/src/engine/initialization.dart
index 745f8b2..0dab016 100644
--- a/lib/web_ui/lib/src/engine/initialization.dart
+++ b/lib/web_ui/lib/src/engine/initialization.dart
@@ -158,15 +158,7 @@
if (!waitingForAnimation) {
waitingForAnimation = true;
domWindow.requestAnimationFrame((JSNumber highResTime) {
- FrameTimingRecorder.recordCurrentFrameVsync();
-
- // In Flutter terminology "building a frame" consists of "beginning
- // frame" and "drawing frame".
- //
- // We do not call `recordBuildFinish` from here because
- // part of the rasterization process, particularly in the HTML
- // renderer, takes place in the `SceneBuilder.build()`.
- FrameTimingRecorder.recordCurrentFrameBuildStart();
+ frameTimingsOnVsync();
// Reset immediately, because `frameHandler` can schedule more frames.
waitingForAnimation = false;
@@ -179,6 +171,13 @@
final int highResTimeMicroseconds =
(1000 * highResTime.toDartDouble).toInt();
+ // In Flutter terminology "building a frame" consists of "beginning
+ // frame" and "drawing frame".
+ //
+ // We do not call `frameTimingsOnBuildFinish` from here because
+ // part of the rasterization process, particularly in the HTML
+ // renderer, takes place in the `SceneBuilder.build()`.
+ frameTimingsOnBuildStart();
if (EnginePlatformDispatcher.instance.onBeginFrame != null) {
EnginePlatformDispatcher.instance.invokeOnBeginFrame(
Duration(microseconds: highResTimeMicroseconds));
diff --git a/lib/web_ui/lib/src/engine/profiler.dart b/lib/web_ui/lib/src/engine/profiler.dart
index d5ef8b3..ffabd12 100644
--- a/lib/web_ui/lib/src/engine/profiler.dart
+++ b/lib/web_ui/lib/src/engine/profiler.dart
@@ -5,8 +5,11 @@
import 'dart:async';
import 'dart:js_interop';
+import 'package:ui/ui.dart' as ui;
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
+import 'dom.dart';
+import 'platform_dispatcher.dart';
import 'util.dart';
// TODO(mdebbar): Deprecate this and remove it.
@@ -124,6 +127,118 @@
}
}
+/// Whether we are collecting [ui.FrameTiming]s.
+bool get _frameTimingsEnabled {
+ return EnginePlatformDispatcher.instance.onReportTimings != null;
+}
+
+/// Collects frame timings from frames.
+///
+/// This list is periodically reported to the framework (see
+/// [_kFrameTimingsSubmitInterval]).
+List<ui.FrameTiming> _frameTimings = <ui.FrameTiming>[];
+
+/// The amount of time in microseconds we wait between submitting
+/// frame timings.
+const int _kFrameTimingsSubmitInterval = 100000; // 100 milliseconds
+
+/// The last time (in microseconds) we submitted frame timings.
+int _frameTimingsLastSubmitTime = _nowMicros();
+
+// These variables store individual [ui.FrameTiming] properties.
+int _vsyncStartMicros = -1;
+int _buildStartMicros = -1;
+int _buildFinishMicros = -1;
+int _rasterStartMicros = -1;
+int _rasterFinishMicros = -1;
+
+/// Records the vsync timestamp for this frame.
+void frameTimingsOnVsync() {
+ if (!_frameTimingsEnabled) {
+ return;
+ }
+ _vsyncStartMicros = _nowMicros();
+}
+
+/// Records the time when the framework started building the frame.
+void frameTimingsOnBuildStart() {
+ if (!_frameTimingsEnabled) {
+ return;
+ }
+ _buildStartMicros = _nowMicros();
+}
+
+/// Records the time when the framework finished building the frame.
+void frameTimingsOnBuildFinish() {
+ if (!_frameTimingsEnabled) {
+ return;
+ }
+ _buildFinishMicros = _nowMicros();
+}
+
+/// Records the time when the framework started rasterizing the frame.
+///
+/// On the web, this value is almost always the same as [_buildFinishMicros]
+/// because it's single-threaded so there's no delay between building
+/// and rasterization.
+///
+/// This also means different things between HTML and CanvasKit renderers.
+///
+/// In HTML "rasterization" only captures DOM updates, but not the work that
+/// the browser performs after the DOM updates are committed. The browser
+/// does not report that information.
+///
+/// CanvasKit captures everything because we control the rasterization
+/// process, so we know exactly when rasterization starts and ends.
+void frameTimingsOnRasterStart() {
+ if (!_frameTimingsEnabled) {
+ return;
+ }
+ _rasterStartMicros = _nowMicros();
+}
+
+/// Records the time when the framework started rasterizing the frame.
+///
+/// See [_frameTimingsOnRasterStart] for more details on what rasterization
+/// timings mean on the web.
+void frameTimingsOnRasterFinish() {
+ if (!_frameTimingsEnabled) {
+ return;
+ }
+ final int now = _nowMicros();
+ _rasterFinishMicros = now;
+ _frameTimings.add(ui.FrameTiming(
+ vsyncStart: _vsyncStartMicros,
+ buildStart: _buildStartMicros,
+ buildFinish: _buildFinishMicros,
+ rasterStart: _rasterStartMicros,
+ rasterFinish: _rasterFinishMicros,
+ rasterFinishWallTime: _rasterFinishMicros,
+ ));
+ _vsyncStartMicros = -1;
+ _buildStartMicros = -1;
+ _buildFinishMicros = -1;
+ _rasterStartMicros = -1;
+ _rasterFinishMicros = -1;
+ if (now - _frameTimingsLastSubmitTime > _kFrameTimingsSubmitInterval) {
+ _frameTimingsLastSubmitTime = now;
+ EnginePlatformDispatcher.instance.invokeOnReportTimings(_frameTimings);
+ _frameTimings = <ui.FrameTiming>[];
+ }
+}
+
+/// Current timestamp in microseconds taken from the high-precision
+/// monotonically increasing timer.
+///
+/// See also:
+///
+/// * https://developer.mozilla.org/en-US/docs/Web/API/Performance/now,
+/// particularly notes about Firefox rounding to 1ms for security reasons,
+/// which can be bypassed in tests by setting certain browser options.
+int _nowMicros() {
+ return (domWindow.performance.now() * 1000).toInt();
+}
+
/// Counts various events that take place while the app is running.
///
/// This class will slow down the app, and therefore should be disabled while
diff --git a/lib/web_ui/lib/src/engine/scene_view.dart b/lib/web_ui/lib/src/engine/scene_view.dart
index b137b70..23010c6 100644
--- a/lib/web_ui/lib/src/engine/scene_view.dart
+++ b/lib/web_ui/lib/src/engine/scene_view.dart
@@ -9,31 +9,20 @@
const String kCanvasContainerTag = 'flt-canvas-container';
-typedef RenderResult = ({
- List<DomImageBitmap> imageBitmaps,
- int rasterStartMicros,
- int rasterEndMicros,
-});
-
// This is an interface that renders a `ScenePicture` as a `DomImageBitmap`.
// It is optionally asynchronous. It is required for the `EngineSceneView` to
// composite pictures into the canvases in the DOM tree it builds.
abstract class PictureRenderer {
- FutureOr<RenderResult> renderPictures(List<ScenePicture> picture);
+ FutureOr<DomImageBitmap> renderPicture(ScenePicture picture);
}
class _SceneRender {
- _SceneRender(
- this.scene,
- this._completer, {
- this.recorder,
- }) {
+ _SceneRender(this.scene, this._completer) {
scene.beginRender();
}
final EngineScene scene;
final Completer<void> _completer;
- final FrameTimingRecorder? recorder;
void done() {
scene.endRender();
@@ -58,24 +47,24 @@
_SceneRender? _currentRender;
_SceneRender? _nextRender;
- Future<void> renderScene(EngineScene scene, FrameTimingRecorder? recorder) {
+ Future<void> renderScene(EngineScene scene) {
if (_currentRender != null) {
// If a scene is already queued up, drop it and queue this one up instead
// so that the scene view always displays the most recently requested scene.
_nextRender?.done();
final Completer<void> completer = Completer<void>();
- _nextRender = _SceneRender(scene, completer, recorder: recorder);
+ _nextRender = _SceneRender(scene, completer);
return completer.future;
}
final Completer<void> completer = Completer<void>();
- _currentRender = _SceneRender(scene, completer, recorder: recorder);
+ _currentRender = _SceneRender(scene, completer);
_kickRenderLoop();
return completer.future;
}
Future<void> _kickRenderLoop() async {
final _SceneRender current = _currentRender!;
- await _renderScene(current.scene, current.recorder);
+ await _renderScene(current.scene);
current.done();
_currentRender = _nextRender;
_nextRender = null;
@@ -86,33 +75,19 @@
}
}
- Future<void> _renderScene(EngineScene scene, FrameTimingRecorder? recorder) async {
+ Future<void> _renderScene(EngineScene scene) async {
final List<LayerSlice> slices = scene.rootLayer.slices;
- final List<ScenePicture> picturesToRender = <ScenePicture>[];
- for (final LayerSlice slice in slices) {
- if (slice is PictureSlice) {
- picturesToRender.add(slice.picture);
- }
- }
- final Map<ScenePicture, DomImageBitmap> renderMap;
- if (picturesToRender.isNotEmpty) {
- final RenderResult renderResult = await pictureRenderer.renderPictures(picturesToRender);
- renderMap = <ScenePicture, DomImageBitmap>{
- for (int i = 0; i < picturesToRender.length; i++)
- picturesToRender[i]: renderResult.imageBitmaps[i],
- };
- recorder?.recordRasterStart(renderResult.rasterStartMicros);
- recorder?.recordRasterFinish(renderResult.rasterEndMicros);
- } else {
- renderMap = <ScenePicture, DomImageBitmap>{};
- recorder?.recordRasterStart();
- recorder?.recordRasterFinish();
- }
- recorder?.submitTimings();
-
+ final Iterable<Future<DomImageBitmap?>> renderFutures = slices.map(
+ (LayerSlice slice) async => switch (slice) {
+ PlatformViewSlice() => null,
+ PictureSlice() => pictureRenderer.renderPicture(slice.picture),
+ }
+ );
+ final List<DomImageBitmap?> renderedBitmaps = await Future.wait(renderFutures);
final List<SliceContainer?> reusableContainers = List<SliceContainer?>.from(containers);
final List<SliceContainer> newContainers = <SliceContainer>[];
- for (final LayerSlice slice in slices) {
+ for (int i = 0; i < slices.length; i++) {
+ final LayerSlice slice = slices[i];
switch (slice) {
case PictureSlice():
PictureSliceContainer? container;
@@ -131,7 +106,7 @@
container = PictureSliceContainer(slice.picture.cullRect);
}
container.updateContents();
- container.renderBitmap(renderMap[slice.picture]!);
+ container.renderBitmap(renderedBitmaps[i]!);
newContainers.add(container);
case PlatformViewSlice():
diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart
index 2b800ba..ee32ffd 100644
--- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart
+++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart
@@ -62,9 +62,9 @@
final ui.Canvas canvas = ui.Canvas(recorder);
canvas.drawImage(this, ui.Offset.zero, ui.Paint());
final DomImageBitmap bitmap =
- (await (renderer as SkwasmRenderer).surface.renderPictures(
- <SkwasmPicture>[recorder.endRecording() as SkwasmPicture],
- )).imageBitmaps.single;
+ await (renderer as SkwasmRenderer).surface.renderPicture(
+ recorder.endRecording() as SkwasmPicture,
+ );
final DomOffscreenCanvas offscreenCanvas =
createDomOffscreenCanvas(bitmap.width.toDartInt, bitmap.height.toDartInt);
final DomCanvasRenderingContextBitmapRenderer context =
@@ -75,7 +75,8 @@
// Zero out the contents of the canvas so that resources can be reclaimed
// by the browser.
- context.transferFromImageBitmap(null);
+ offscreenCanvas.width = 0;
+ offscreenCanvas.height = 0;
return ByteData.view(arrayBuffer.toDart);
} else {
return (renderer as SkwasmRenderer).surface.rasterizeImage(this, format);
diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_surface.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_surface.dart
index 22b7462..3831188 100644
--- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_surface.dart
+++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_surface.dart
@@ -35,10 +35,10 @@
isLeaf: true)
external void surfaceDestroy(SurfaceHandle surface);
-@Native<Int32 Function(SurfaceHandle, Pointer<PictureHandle>, Int)>(
- symbol: 'surface_renderPictures',
+@Native<Int32 Function(SurfaceHandle, PictureHandle)>(
+ symbol: 'surface_renderPicture',
isLeaf: true)
-external CallbackId surfaceRenderPictures(SurfaceHandle surface, Pointer<PictureHandle> picture, int count);
+external CallbackId surfaceRenderPicture(SurfaceHandle surface, PictureHandle picture);
@Native<Int32 Function(
SurfaceHandle,
diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart
index a0bfd47..6e5c4ab 100644
--- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart
+++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart
@@ -401,13 +401,10 @@
// https://github.com/flutter/flutter/issues/137073.
@override
Future<void> renderScene(ui.Scene scene, ui.FlutterView view) {
- final FrameTimingRecorder? recorder = FrameTimingRecorder.frameTimingsEnabled ? FrameTimingRecorder() : null;
- recorder?.recordBuildFinish();
-
view as EngineFlutterView;
assert(view is EngineFlutterWindow, 'Skwasm does not support multi-view mode yet');
final EngineSceneView sceneView = _getSceneViewForView(view);
- return sceneView.renderScene(scene as EngineScene, recorder);
+ return sceneView.renderScene(scene as EngineScene);
}
EngineSceneView _getSceneViewForView(EngineFlutterView view) {
@@ -480,6 +477,6 @@
SkwasmSurface surface;
@override
- FutureOr<RenderResult> renderPictures(List<ScenePicture> pictures) =>
- surface.renderPictures(pictures.cast<SkwasmPicture>());
+ FutureOr<DomImageBitmap> renderPicture(ScenePicture picture) =>
+ surface.renderPicture(picture as SkwasmPicture);
}
diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/surface.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/surface.dart
index eddcc19..34af06d 100644
--- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/surface.dart
+++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/surface.dart
@@ -12,17 +12,6 @@
import 'package:ui/src/engine/skwasm/skwasm_impl.dart';
import 'package:ui/ui.dart' as ui;
-@JS()
-@staticInterop
-@anonymous
-class RasterResult {}
-
-extension RasterResultExtension on RasterResult {
- external JSNumber get rasterStartMilliseconds;
- external JSNumber get rasterEndMilliseconds;
- external JSArray<JSAny> get imageBitmaps;
-}
-
@pragma('wasm:export')
WasmVoid callbackHandler(WasmI32 callbackId, WasmI32 context, WasmExternRef? jsContext) {
// Actually hide this call behind whether skwasm is enabled. Otherwise, the SkwasmCallbackHandler
@@ -89,22 +78,11 @@
surfaceSetCallbackHandler(handle, SkwasmCallbackHandler.instance.callbackPointer);
}
- Future<RenderResult> renderPictures(List<SkwasmPicture> pictures) =>
- withStackScope((StackScope scope) async {
- final Pointer<PictureHandle> pictureHandles =
- scope.allocPointerArray(pictures.length).cast<PictureHandle>();
- for (int i = 0; i < pictures.length; i++) {
- pictureHandles[i] = pictures[i].handle;
- }
- final int callbackId = surfaceRenderPictures(handle, pictureHandles, pictures.length);
- final RasterResult rasterResult = (await SkwasmCallbackHandler.instance.registerCallback(callbackId)) as RasterResult;
- final RenderResult result = (
- imageBitmaps: rasterResult.imageBitmaps.toDart.cast<DomImageBitmap>(),
- rasterStartMicros: (rasterResult.rasterStartMilliseconds.toDartDouble * 1000).toInt(),
- rasterEndMicros: (rasterResult.rasterEndMilliseconds.toDartDouble * 1000).toInt(),
- );
- return result;
- });
+ Future<DomImageBitmap> renderPicture(SkwasmPicture picture) async {
+ final int callbackId = surfaceRenderPicture(handle, picture.handle);
+ final DomImageBitmap bitmap = (await SkwasmCallbackHandler.instance.registerCallback(callbackId)) as DomImageBitmap;
+ return bitmap;
+ }
Future<ByteData> rasterizeImage(SkwasmImage image, ui.ImageByteFormat format) async {
final int callbackId = surfaceRasterizeImage(
diff --git a/lib/web_ui/skwasm/library_skwasm_support.js b/lib/web_ui/skwasm/library_skwasm_support.js
index 76cbac2..5e62614 100644
--- a/lib/web_ui/skwasm/library_skwasm_support.js
+++ b/lib/web_ui/skwasm/library_skwasm_support.js
@@ -27,18 +27,11 @@
return;
}
switch (skwasmMessage) {
- case 'renderPictures':
- _surface_renderPicturesOnWorker(data.surface, data.pictures, data.pictureCount, data.callbackId, performance.now());
+ case 'renderPicture':
+ _surface_renderPictureOnWorker(data.surface, data.picture, data.callbackId);
return;
case 'onRenderComplete':
- _surface_onRenderComplete(
- data.surface,
- data.callbackId, {
- "imageBitmaps": data.imageBitmaps,
- "rasterStartMilliseconds": data.rasterStart,
- "rasterEndMilliseconds": data.rasterEnd,
- },
- );
+ _surface_onRenderComplete(data.surface, data.callbackId, data.imageBitmap);
return;
case 'setAssociatedObject':
associatedObjectsMap.set(data.pointer, data.object);
@@ -61,12 +54,11 @@
PThread.pthreads[threadId].addEventListener("message", eventListener);
}
};
- _skwasm_dispatchRenderPictures = function(threadId, surfaceHandle, pictures, pictureCount, callbackId) {
+ _skwasm_dispatchRenderPicture = function(threadId, surfaceHandle, pictureHandle, callbackId) {
PThread.pthreads[threadId].postMessage({
- skwasmMessage: 'renderPictures',
+ skwasmMessage: 'renderPicture',
surface: surfaceHandle,
- pictures,
- pictureCount,
+ picture: pictureHandle,
callbackId,
});
};
@@ -93,23 +85,15 @@
canvas.width = width;
canvas.height = height;
};
- _skwasm_captureImageBitmap = function(contextHandle, width, height, imagePromises) {
- if (!imagePromises) imagePromises = Array();
+ _skwasm_captureImageBitmap = async function(surfaceHandle, contextHandle, callbackId, width, height) {
const canvas = handleToCanvasMap.get(contextHandle);
- imagePromises.push(createImageBitmap(canvas, 0, 0, width, height));
- return imagePromises;
- };
- _skwasm_resolveAndPostImages = async function(surfaceHandle, imagePromises, rasterStart, callbackId) {
- const imageBitmaps = imagePromises ? await Promise.all(imagePromises) : [];
- const rasterEnd = performance.now();
+ const imageBitmap = await createImageBitmap(canvas, 0, 0, width, height);
postMessage({
skwasmMessage: 'onRenderComplete',
surface: surfaceHandle,
callbackId,
- imageBitmaps,
- rasterStart,
- rasterEnd,
- }, [...imageBitmaps]);
+ imageBitmap,
+ }, [imageBitmap]);
};
_skwasm_createGlTextureFromTextureSource = function(textureSource, width, height) {
const glCtx = GL.currentContext.GLctx;
@@ -141,16 +125,14 @@
skwasm_disposeAssociatedObjectOnThread__deps: ['$skwasm_support_setup'],
skwasm_registerMessageListener: function() {},
skwasm_registerMessageListener__deps: ['$skwasm_support_setup'],
- skwasm_dispatchRenderPictures: function() {},
- skwasm_dispatchRenderPictures__deps: ['$skwasm_support_setup'],
+ skwasm_dispatchRenderPicture: function() {},
+ skwasm_dispatchRenderPicture__deps: ['$skwasm_support_setup'],
skwasm_createOffscreenCanvas: function () {},
skwasm_createOffscreenCanvas__deps: ['$skwasm_support_setup'],
skwasm_resizeCanvas: function () {},
skwasm_resizeCanvas__deps: ['$skwasm_support_setup'],
skwasm_captureImageBitmap: function () {},
skwasm_captureImageBitmap__deps: ['$skwasm_support_setup'],
- skwasm_resolveAndPostImages: function () {},
- skwasm_resolveAndPostImages__deps: ['$skwasm_support_setup'],
skwasm_createGlTextureFromTextureSource: function () {},
skwasm_createGlTextureFromTextureSource__deps: ['$skwasm_support_setup'],
});
diff --git a/lib/web_ui/skwasm/skwasm_support.h b/lib/web_ui/skwasm/skwasm_support.h
index c9132b8..ce36a19 100644
--- a/lib/web_ui/skwasm/skwasm_support.h
+++ b/lib/web_ui/skwasm/skwasm_support.h
@@ -23,21 +23,17 @@
extern void skwasm_disposeAssociatedObjectOnThread(unsigned long threadId,
void* pointer);
extern void skwasm_registerMessageListener(pthread_t threadId);
-extern void skwasm_dispatchRenderPictures(unsigned long threadId,
- Skwasm::Surface* surface,
- sk_sp<SkPicture>* pictures,
- int count,
- uint32_t callbackId);
+extern void skwasm_dispatchRenderPicture(unsigned long threadId,
+ Skwasm::Surface* surface,
+ SkPicture* picture,
+ uint32_t callbackId);
extern uint32_t skwasm_createOffscreenCanvas(int width, int height);
extern void skwasm_resizeCanvas(uint32_t contextHandle, int width, int height);
-extern SkwasmObject skwasm_captureImageBitmap(uint32_t contextHandle,
- int width,
- int height,
- SkwasmObject imagePromises);
-extern void skwasm_resolveAndPostImages(Skwasm::Surface* surface,
- SkwasmObject imagePromises,
- double rasterStart,
- uint32_t callbackId);
+extern void skwasm_captureImageBitmap(Skwasm::Surface* surfaceHandle,
+ uint32_t contextHandle,
+ uint32_t bitmapId,
+ int width,
+ int height);
extern unsigned int skwasm_createGlTextureFromTextureSource(
SkwasmObject textureSource,
int width,
diff --git a/lib/web_ui/skwasm/surface.cpp b/lib/web_ui/skwasm/surface.cpp
index a1c24b4..28b64bd 100644
--- a/lib/web_ui/skwasm/surface.cpp
+++ b/lib/web_ui/skwasm/surface.cpp
@@ -39,19 +39,11 @@
}
// Main thread only
-uint32_t Surface::renderPictures(SkPicture** pictures, int count) {
+uint32_t Surface::renderPicture(SkPicture* picture) {
assert(emscripten_is_main_browser_thread());
uint32_t callbackId = ++_currentCallbackId;
- std::unique_ptr<sk_sp<SkPicture>[]> picturePointers =
- std::make_unique<sk_sp<SkPicture>[]>(count);
- for (int i = 0; i < count; i++) {
- picturePointers[i] = sk_ref_sp(pictures[i]);
- }
-
- // Releasing picturePointers here and will recreate the unique_ptr on the
- // other thread See surface_renderPicturesOnWorker
- skwasm_dispatchRenderPictures(_thread, this, picturePointers.release(), count,
- callbackId);
+ picture->ref();
+ skwasm_dispatchRenderPicture(_thread, this, picture, callbackId);
return callbackId;
}
@@ -144,31 +136,20 @@
}
// Worker thread only
-void Surface::renderPicturesOnWorker(sk_sp<SkPicture>* pictures,
- int pictureCount,
- uint32_t callbackId,
- double rasterStart) {
- // This is populated by the `captureImageBitmap` call the first time it is
- // passed in.
- SkwasmObject imagePromiseArray = __builtin_wasm_ref_null_extern();
- for (int i = 0; i < pictureCount; i++) {
- sk_sp<SkPicture> picture = pictures[i];
- SkRect pictureRect = picture->cullRect();
- SkIRect roundedOutRect;
- pictureRect.roundOut(&roundedOutRect);
- _resizeCanvasToFit(roundedOutRect.width(), roundedOutRect.height());
- SkMatrix matrix =
- SkMatrix::Translate(-roundedOutRect.fLeft, -roundedOutRect.fTop);
- makeCurrent(_glContext);
- auto canvas = _surface->getCanvas();
- canvas->drawColor(SK_ColorTRANSPARENT, SkBlendMode::kSrc);
- canvas->drawPicture(picture, &matrix, nullptr);
- _grContext->flush(_surface.get());
- imagePromiseArray =
- skwasm_captureImageBitmap(_glContext, roundedOutRect.width(),
- roundedOutRect.height(), imagePromiseArray);
- }
- skwasm_resolveAndPostImages(this, imagePromiseArray, rasterStart, callbackId);
+void Surface::renderPictureOnWorker(SkPicture* picture, uint32_t callbackId) {
+ SkRect pictureRect = picture->cullRect();
+ SkIRect roundedOutRect;
+ pictureRect.roundOut(&roundedOutRect);
+ _resizeCanvasToFit(roundedOutRect.width(), roundedOutRect.height());
+ SkMatrix matrix =
+ SkMatrix::Translate(-roundedOutRect.fLeft, -roundedOutRect.fTop);
+ makeCurrent(_glContext);
+ auto canvas = _surface->getCanvas();
+ canvas->drawColor(SK_ColorTRANSPARENT, SkBlendMode::kSrc);
+ canvas->drawPicture(sk_ref_sp<SkPicture>(picture), &matrix, nullptr);
+ _grContext->flush(_surface.get());
+ skwasm_captureImageBitmap(this, _glContext, callbackId,
+ roundedOutRect.width(), roundedOutRect.height());
}
void Surface::_rasterizeImage(SkImage* image,
@@ -244,22 +225,16 @@
surface->dispose();
}
-SKWASM_EXPORT uint32_t surface_renderPictures(Surface* surface,
- SkPicture** pictures,
- int count) {
- return surface->renderPictures(pictures, count);
+SKWASM_EXPORT uint32_t surface_renderPicture(Surface* surface,
+ SkPicture* picture) {
+ return surface->renderPicture(picture);
}
-SKWASM_EXPORT void surface_renderPicturesOnWorker(Surface* surface,
- sk_sp<SkPicture>* pictures,
- int pictureCount,
- uint32_t callbackId,
- double rasterStart) {
- // This will release the pictures when they leave scope.
- std::unique_ptr<sk_sp<SkPicture>> picturesPointer =
- std::unique_ptr<sk_sp<SkPicture>>(pictures);
- surface->renderPicturesOnWorker(pictures, pictureCount, callbackId,
- rasterStart);
+SKWASM_EXPORT void surface_renderPictureOnWorker(Surface* surface,
+ SkPicture* picture,
+ uint32_t callbackId) {
+ surface->renderPictureOnWorker(picture, callbackId);
+ picture->unref();
}
SKWASM_EXPORT uint32_t surface_rasterizeImage(Surface* surface,
diff --git a/lib/web_ui/skwasm/surface.h b/lib/web_ui/skwasm/surface.h
index 7e1d5fb..3577a94 100644
--- a/lib/web_ui/skwasm/surface.h
+++ b/lib/web_ui/skwasm/surface.h
@@ -62,7 +62,7 @@
// Main thread only
void dispose();
- uint32_t renderPictures(SkPicture** picture, int count);
+ uint32_t renderPicture(SkPicture* picture);
uint32_t rasterizeImage(SkImage* image, ImageByteFormat format);
void setCallbackHandler(CallbackHandler* callbackHandler);
void onRenderComplete(uint32_t callbackId, SkwasmObject imageBitmap);
@@ -72,10 +72,7 @@
SkwasmObject textureSource);
// Worker thread
- void renderPicturesOnWorker(sk_sp<SkPicture>* picture,
- int pictureCount,
- uint32_t callbackId,
- double rasterStart);
+ void renderPictureOnWorker(SkPicture* picture, uint32_t callbackId);
private:
void _runWorker();
diff --git a/lib/web_ui/test/canvaskit/frame_timings_test.dart b/lib/web_ui/test/canvaskit/frame_timings_test.dart
new file mode 100644
index 0000000..0cacd42
--- /dev/null
+++ b/lib/web_ui/test/canvaskit/frame_timings_test.dart
@@ -0,0 +1,23 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:test/bootstrap/browser.dart';
+import 'package:test/test.dart';
+
+import '../common/frame_timings_common.dart';
+import 'common.dart';
+
+void main() {
+ internalBootstrapBrowserTest(() => testMain);
+}
+
+void testMain() {
+ group('frame timings', () {
+ setUpCanvasKitTest(withImplicitView: true);
+
+ test('collects frame timings', () async {
+ await runFrameTimingsTest();
+ });
+ });
+}
diff --git a/lib/web_ui/test/common/frame_timings_common.dart b/lib/web_ui/test/common/frame_timings_common.dart
new file mode 100644
index 0000000..314e1a8
--- /dev/null
+++ b/lib/web_ui/test/common/frame_timings_common.dart
@@ -0,0 +1,53 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:test/test.dart';
+import 'package:ui/src/engine.dart' show EnginePlatformDispatcher;
+import 'package:ui/ui.dart' as ui;
+
+/// Tests frame timings in a renderer-agnostic way.
+///
+/// See CanvasKit-specific and HTML-specific test files `frame_timings_test.dart`.
+Future<void> runFrameTimingsTest() async {
+ final EnginePlatformDispatcher dispatcher = ui.PlatformDispatcher.instance as EnginePlatformDispatcher;
+
+ List<ui.FrameTiming>? timings;
+ dispatcher.onReportTimings = (List<ui.FrameTiming> data) {
+ timings = data;
+ };
+ Completer<void> frameDone = Completer<void>();
+ dispatcher.onDrawFrame = () {
+ final ui.SceneBuilder sceneBuilder = ui.SceneBuilder();
+ sceneBuilder
+ ..pushOffset(0, 0)
+ ..pop();
+ dispatcher.render(sceneBuilder.build()).then((_) {
+ frameDone.complete();
+ });
+ };
+
+ // Frame 1.
+ dispatcher.scheduleFrame();
+ await frameDone.future;
+ expect(timings, isNull, reason: "100 ms hasn't passed yet");
+ await Future<void>.delayed(const Duration(milliseconds: 150));
+
+ // Frame 2.
+ frameDone = Completer<void>();
+ dispatcher.scheduleFrame();
+ await frameDone.future;
+ expect(timings, hasLength(2), reason: '100 ms passed. 2 frames pumped.');
+ for (final ui.FrameTiming timing in timings!) {
+ expect(timing.vsyncOverhead, greaterThanOrEqualTo(Duration.zero));
+ expect(timing.buildDuration, greaterThanOrEqualTo(Duration.zero));
+ expect(timing.rasterDuration, greaterThanOrEqualTo(Duration.zero));
+ expect(timing.totalSpan, greaterThanOrEqualTo(Duration.zero));
+ expect(timing.layerCacheCount, equals(0));
+ expect(timing.layerCacheBytes, equals(0));
+ expect(timing.pictureCacheCount, equals(0));
+ expect(timing.pictureCacheBytes, equals(0));
+ }
+}
diff --git a/lib/web_ui/test/engine/scene_view_test.dart b/lib/web_ui/test/engine/scene_view_test.dart
index 93d54b0..48e84b7 100644
--- a/lib/web_ui/test/engine/scene_view_test.dart
+++ b/lib/web_ui/test/engine/scene_view_test.dart
@@ -24,23 +24,17 @@
createDomCanvasElement(width: 500, height: 500);
@override
- Future<RenderResult> renderPictures(List<ScenePicture> pictures) async {
- renderedPictures.addAll(pictures);
- final List<DomImageBitmap> bitmaps = await Future.wait(pictures.map((ScenePicture picture) {
- final ui.Rect cullRect = picture.cullRect;
- final Future<DomImageBitmap> bitmap = createImageBitmap(scratchCanvasElement as JSObject, (
- x: 0,
- y: 0,
- width: cullRect.width.toInt(),
- height: cullRect.height.toInt(),
- ));
- return bitmap;
- }));
- return (
- imageBitmaps: bitmaps,
- rasterStartMicros: 0,
- rasterEndMicros: 0,
- );
+ Future<DomImageBitmap> renderPicture(ScenePicture picture) async {
+ renderedPictures.add(picture);
+ final ui.Rect cullRect = picture.cullRect;
+ final DomImageBitmap bitmap =
+ await createImageBitmap(scratchCanvasElement as JSObject, (
+ x: 0,
+ y: 0,
+ width: cullRect.width.toInt(),
+ height: cullRect.height.toInt(),
+ ));
+ return bitmap;
}
List<ScenePicture> renderedPictures = <ScenePicture>[];
@@ -71,7 +65,7 @@
final EngineRootLayer rootLayer = EngineRootLayer();
rootLayer.slices.add(PictureSlice(picture));
final EngineScene scene = EngineScene(rootLayer);
- await sceneView.renderScene(scene, null);
+ await sceneView.renderScene(scene);
final DomElement sceneElement = sceneView.sceneElement;
final List<DomElement> children = sceneElement.children.toList();
@@ -106,7 +100,7 @@
final EngineRootLayer rootLayer = EngineRootLayer();
rootLayer.slices.add(PlatformViewSlice(<PlatformView>[platformView], null));
final EngineScene scene = EngineScene(rootLayer);
- await sceneView.renderScene(scene, null);
+ await sceneView.renderScene(scene);
final DomElement sceneElement = sceneView.sceneElement;
final List<DomElement> children = sceneElement.children.toList();
@@ -140,7 +134,7 @@
final EngineRootLayer rootLayer = EngineRootLayer();
rootLayer.slices.add(PictureSlice(picture));
final EngineScene scene = EngineScene(rootLayer);
- renderFutures.add(sceneView.renderScene(scene, null));
+ renderFutures.add(sceneView.renderScene(scene));
}
await Future.wait(renderFutures);
diff --git a/lib/web_ui/test/engine/surface/frame_timings_test.dart b/lib/web_ui/test/engine/surface/frame_timings_test.dart
new file mode 100644
index 0000000..14ec8f2
--- /dev/null
+++ b/lib/web_ui/test/engine/surface/frame_timings_test.dart
@@ -0,0 +1,23 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:test/bootstrap/browser.dart';
+import 'package:test/test.dart';
+
+import '../../common/frame_timings_common.dart';
+import '../../common/test_initialization.dart';
+
+void main() {
+ internalBootstrapBrowserTest(() => testMain);
+}
+
+void testMain() {
+ setUp(() async {
+ await bootstrapAndRunApp(withImplicitView: true);
+ });
+
+ test('collects frame timings', () async {
+ await runFrameTimingsTest();
+ });
+}
diff --git a/lib/web_ui/test/ui/frame_timings_test.dart b/lib/web_ui/test/ui/frame_timings_test.dart
deleted file mode 100644
index 62f83b7..0000000
--- a/lib/web_ui/test/ui/frame_timings_test.dart
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'dart:async';
-
-import 'package:test/bootstrap/browser.dart';
-import 'package:test/test.dart';
-import 'package:ui/src/engine.dart';
-import 'package:ui/ui.dart' as ui;
-
-import '../common/test_initialization.dart';
-
-void main() {
- internalBootstrapBrowserTest(() => testMain);
-}
-
-void testMain() {
- setUp(() async {
- await bootstrapAndRunApp(withImplicitView: true);
- });
-
- test('collects frame timings', () async {
- final EnginePlatformDispatcher dispatcher = ui.PlatformDispatcher.instance as EnginePlatformDispatcher;
- List<ui.FrameTiming>? timings;
- dispatcher.onReportTimings = (List<ui.FrameTiming> data) {
- timings = data;
- };
- Completer<void> frameDone = Completer<void>();
- dispatcher.onDrawFrame = () {
- final ui.SceneBuilder sceneBuilder = ui.SceneBuilder();
- sceneBuilder
- ..pushOffset(0, 0)
- ..pop();
- dispatcher.render(sceneBuilder.build()).then((_) {
- frameDone.complete();
- });
- };
-
- // Frame 1.
- dispatcher.scheduleFrame();
- await frameDone.future;
- expect(timings, isNull, reason: "100 ms hasn't passed yet");
- await Future<void>.delayed(const Duration(milliseconds: 150));
-
- // Frame 2.
- frameDone = Completer<void>();
- dispatcher.scheduleFrame();
- await frameDone.future;
- expect(timings, hasLength(2), reason: '100 ms passed. 2 frames pumped.');
- for (final ui.FrameTiming timing in timings!) {
- expect(timing.vsyncOverhead, greaterThanOrEqualTo(Duration.zero));
- expect(timing.buildDuration, greaterThanOrEqualTo(Duration.zero));
- expect(timing.rasterDuration, greaterThanOrEqualTo(Duration.zero));
- expect(timing.totalSpan, greaterThanOrEqualTo(Duration.zero));
- expect(timing.layerCacheCount, equals(0));
- expect(timing.layerCacheBytes, equals(0));
- expect(timing.pictureCacheCount, equals(0));
- expect(timing.pictureCacheBytes, equals(0));
- }
- });
-}