Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 4b4a76b

Browse files
authored
Reland: Enforce the rule of calling FlutterView.Render (#45300) (#45555)
This PR relands #45300 which was reverted in #45525 due to hanging on a windows startup test. The culprit test still calls `FlutterView.render` in the illegal way, which is ignored, causing no frame being ever produced. This has been fixed in flutter/flutter#134245. I've also searched through the framework repo for `render(` to ensure there are no other cases. [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
1 parent 902ab57 commit 4b4a76b

File tree

7 files changed

+370
-23
lines changed

7 files changed

+370
-23
lines changed

lib/ui/fixtures/ui_test.dart

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,3 +1076,45 @@ external void _callHook(
10761076
Object? arg20,
10771077
Object? arg21,
10781078
]);
1079+
1080+
Scene _createRedBoxScene(Size size) {
1081+
final SceneBuilder builder = SceneBuilder();
1082+
builder.pushOffset(0.0, 0.0);
1083+
final Paint paint = Paint()
1084+
..color = Color.fromARGB(255, 255, 0, 0)
1085+
..style = PaintingStyle.fill;
1086+
final PictureRecorder baseRecorder = PictureRecorder();
1087+
final Canvas canvas = Canvas(baseRecorder);
1088+
canvas.drawRect(Rect.fromLTRB(0.0, 0.0, size.width, size.height), paint);
1089+
final Picture picture = baseRecorder.endRecording();
1090+
builder.addPicture(Offset(0.0, 0.0), picture);
1091+
builder.pop();
1092+
return builder.build();
1093+
}
1094+
1095+
@pragma('vm:entry-point')
1096+
void incorrectImmediateRender() {
1097+
PlatformDispatcher.instance.views.first.render(_createRedBoxScene(Size(2, 2)));
1098+
_finish();
1099+
// Don't schedule a frame here. This test only checks if the
1100+
// [FlutterView.render] call is propagated to PlatformConfiguration.render
1101+
// and thus doesn't need anything from `Animator` or `Engine`, which,
1102+
// besides, are not even created in the native side at all.
1103+
}
1104+
1105+
@pragma('vm:entry-point')
1106+
void incorrectDoubleRender() {
1107+
PlatformDispatcher.instance.onBeginFrame = (Duration value) {
1108+
PlatformDispatcher.instance.views.first.render(_createRedBoxScene(Size(2, 2)));
1109+
PlatformDispatcher.instance.views.first.render(_createRedBoxScene(Size(3, 3)));
1110+
};
1111+
PlatformDispatcher.instance.onDrawFrame = () {
1112+
PlatformDispatcher.instance.views.first.render(_createRedBoxScene(Size(4, 4)));
1113+
PlatformDispatcher.instance.views.first.render(_createRedBoxScene(Size(5, 5)));
1114+
};
1115+
_finish();
1116+
// Don't schedule a frame here. This test only checks if the
1117+
// [FlutterView.render] call is propagated to PlatformConfiguration.render
1118+
// and thus doesn't need anything from `Animator` or `Engine`, which,
1119+
// besides, are not even created in the native side at all.
1120+
}

lib/ui/platform_dispatcher.dart

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,21 @@ class PlatformDispatcher {
308308
_invoke(onMetricsChanged, _onMetricsChangedZone);
309309
}
310310

311+
// [FlutterView]s for which [FlutterView.render] has already been called
312+
// during the current [onBeginFrame]/[onDrawFrame] callback sequence.
313+
//
314+
// The field is null outside the scope of those callbacks indicating that
315+
// calls to [FlutterView.render] must be ignored. Furthermore, if a given
316+
// [FlutterView] is already present in this set when its [FlutterView.render]
317+
// is called again, that call must be ignored as a duplicate.
318+
//
319+
// Between [onBeginFrame] and [onDrawFrame] the properties value is
320+
// temporarily stored in `_renderedViewsBetweenCallbacks` so that it survives
321+
// the gap between the two callbacks.
322+
Set<FlutterView>? _renderedViews;
323+
// Temporary storage of the `_renderedViews` value between `_beginFrame` and
324+
// `_drawFrame`.
325+
Set<FlutterView>? _renderedViewsBetweenCallbacks;
311326

312327
/// A callback invoked when any view begins a frame.
313328
///
@@ -329,11 +344,20 @@ class PlatformDispatcher {
329344

330345
// Called from the engine, via hooks.dart
331346
void _beginFrame(int microseconds) {
347+
assert(_renderedViews == null);
348+
assert(_renderedViewsBetweenCallbacks == null);
349+
350+
_renderedViews = <FlutterView>{};
332351
_invoke1<Duration>(
333352
onBeginFrame,
334353
_onBeginFrameZone,
335354
Duration(microseconds: microseconds),
336355
);
356+
_renderedViewsBetweenCallbacks = _renderedViews;
357+
_renderedViews = null;
358+
359+
assert(_renderedViews == null);
360+
assert(_renderedViewsBetweenCallbacks != null);
337361
}
338362

339363
/// A callback that is invoked for each frame after [onBeginFrame] has
@@ -351,7 +375,16 @@ class PlatformDispatcher {
351375

352376
// Called from the engine, via hooks.dart
353377
void _drawFrame() {
378+
assert(_renderedViews == null);
379+
assert(_renderedViewsBetweenCallbacks != null);
380+
381+
_renderedViews = _renderedViewsBetweenCallbacks;
382+
_renderedViewsBetweenCallbacks = null;
354383
_invoke(onDrawFrame, _onDrawFrameZone);
384+
_renderedViews = null;
385+
386+
assert(_renderedViews == null);
387+
assert(_renderedViewsBetweenCallbacks == null);
355388
}
356389

357390
/// A callback that is invoked when pointer data is available.

lib/ui/window.dart

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -327,14 +327,21 @@ class FlutterView {
327327

328328
/// Updates the view's rendering on the GPU with the newly provided [Scene].
329329
///
330-
/// This function must be called within the scope of the
330+
/// ## Requirement for calling this method
331+
///
332+
/// This method must be called within the synchronous scope of the
331333
/// [PlatformDispatcher.onBeginFrame] or [PlatformDispatcher.onDrawFrame]
332-
/// callbacks being invoked.
334+
/// callbacks. Calls out of this scope will be ignored. To use this method,
335+
/// create a callback that calls this method instead, and assign it to either
336+
/// of the fields above; then schedule a frame, which is done typically with
337+
/// [PlatformDispatcher.scheduleFrame]. Also, make sure the callback does not
338+
/// have `await` before the `FlutterWindow.render` call.
339+
///
340+
/// Additionally, this method can only be called once for each view during a
341+
/// single [PlatformDispatcher.onBeginFrame]/[PlatformDispatcher.onDrawFrame]
342+
/// callback sequence. Duplicate calls will be ignored in production.
333343
///
334-
/// If this function is called a second time during a single
335-
/// [PlatformDispatcher.onBeginFrame]/[PlatformDispatcher.onDrawFrame]
336-
/// callback sequence or called outside the scope of those callbacks, the call
337-
/// will be ignored.
344+
/// ## How to record a scene
338345
///
339346
/// To record graphical operations, first create a [PictureRecorder], then
340347
/// construct a [Canvas], passing that [PictureRecorder] to its constructor.
@@ -353,7 +360,14 @@ class FlutterView {
353360
/// scheduling of frames.
354361
/// * [RendererBinding], the Flutter framework class which manages layout and
355362
/// painting.
356-
void render(Scene scene) => _render(scene as _NativeScene);
363+
void render(Scene scene) {
364+
if (platformDispatcher._renderedViews?.add(this) != true) {
365+
// Duplicated calls or calls outside of onBeginFrame/onDrawFrame
366+
// (indicated by _renderedViews being null) are ignored, as documented.
367+
return;
368+
}
369+
_render(scene as _NativeScene);
370+
}
357371

358372
@Native<Void Function(Pointer<Void>)>(symbol: 'PlatformConfigurationNativeApi::Render')
359373
external static void _render(_NativeScene scene);

0 commit comments

Comments
 (0)