[web] Multi-view support for Skwasm (flutter/engine#48893)
Support multi-view mode in the Skwasm renderer. Fixes https://github.com/flutter/flutter/issues/138925
This commit is contained in:
parent
f2d6dc0a45
commit
ff452d94a3
@ -401,7 +401,7 @@ class CanvasKitRenderer implements Renderer {
|
||||
// TODO(harryterkelsen): Merge this logic with the async logic in
|
||||
// [EngineScene], https://github.com/flutter/flutter/issues/142072.
|
||||
@override
|
||||
Future<void> renderScene(ui.Scene scene, ui.FlutterView view) async {
|
||||
Future<void> renderScene(ui.Scene scene, EngineFlutterView view) async {
|
||||
assert(_rasterizers.containsKey(view.viewId),
|
||||
"Unable to render to a view which hasn't been registered");
|
||||
final ViewRasterizer rasterizer = _rasterizers[view.viewId]!;
|
||||
|
@ -302,7 +302,7 @@ class HtmlRenderer implements Renderer {
|
||||
CanvasParagraphBuilder(style as EngineParagraphStyle);
|
||||
|
||||
@override
|
||||
Future<void> renderScene(ui.Scene scene, ui.FlutterView view) async {
|
||||
Future<void> renderScene(ui.Scene scene, EngineFlutterView view) async {
|
||||
final EngineFlutterView implicitView =
|
||||
EnginePlatformDispatcher.instance.implicitView!;
|
||||
scene as SurfaceScene;
|
||||
|
@ -225,5 +225,5 @@ abstract class Renderer {
|
||||
|
||||
ui.ParagraphBuilder createParagraphBuilder(ui.ParagraphStyle style);
|
||||
|
||||
Future<void> renderScene(ui.Scene scene, ui.FlutterView view);
|
||||
Future<void> renderScene(ui.Scene scene, EngineFlutterView view);
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ class _SceneRender {
|
||||
|
||||
// This class builds a DOM tree that composites an `EngineScene`.
|
||||
class EngineSceneView {
|
||||
factory EngineSceneView(PictureRenderer pictureRenderer, ui.FlutterView flutterView) {
|
||||
factory EngineSceneView(PictureRenderer pictureRenderer, EngineFlutterView flutterView) {
|
||||
final DomElement sceneElement = createDomElement('flt-scene');
|
||||
return EngineSceneView._(pictureRenderer, flutterView, sceneElement);
|
||||
}
|
||||
@ -53,7 +53,7 @@ class EngineSceneView {
|
||||
|
||||
final PictureRenderer pictureRenderer;
|
||||
final DomElement sceneElement;
|
||||
final ui.FlutterView flutterView;
|
||||
final EngineFlutterView flutterView;
|
||||
|
||||
List<SliceContainer> containers = <SliceContainer>[];
|
||||
|
||||
|
@ -14,7 +14,7 @@ import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
|
||||
|
||||
class SkwasmRenderer implements Renderer {
|
||||
late SkwasmSurface surface;
|
||||
EngineSceneView? _sceneView;
|
||||
final Map<EngineFlutterView, EngineSceneView> _sceneViews = <EngineFlutterView, EngineSceneView>{};
|
||||
|
||||
@override
|
||||
final SkwasmFontCollection fontCollection = SkwasmFontCollection();
|
||||
@ -397,27 +397,21 @@ class SkwasmRenderer implements Renderer {
|
||||
return decoder;
|
||||
}
|
||||
|
||||
// TODO(harryterkelsen): Add multiview support,
|
||||
// https://github.com/flutter/flutter/issues/137073.
|
||||
@override
|
||||
Future<void> renderScene(ui.Scene scene, ui.FlutterView view) {
|
||||
Future<void> renderScene(ui.Scene scene, EngineFlutterView 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);
|
||||
}
|
||||
|
||||
EngineSceneView _getSceneViewForView(EngineFlutterView view) {
|
||||
// TODO(mdebbar): Support multi-view mode.
|
||||
if (_sceneView == null) {
|
||||
_sceneView = EngineSceneView(SkwasmPictureRenderer(surface), view);
|
||||
final EngineFlutterView implicitView = EnginePlatformDispatcher.instance.implicitView!;
|
||||
implicitView.dom.setScene(_sceneView!.sceneElement);
|
||||
}
|
||||
return _sceneView!;
|
||||
return _sceneViews.putIfAbsent(view, () {
|
||||
final EngineSceneView sceneView = EngineSceneView(SkwasmPictureRenderer(surface), view);
|
||||
view.dom.setScene(sceneView.sceneElement);
|
||||
return sceneView;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -44,7 +44,7 @@ int _nextViewId = kImplicitViewId + 1;
|
||||
///
|
||||
/// In addition to everything defined in [ui.FlutterView], this class adds
|
||||
/// a few web-specific properties.
|
||||
base class EngineFlutterView implements ui.FlutterView {
|
||||
class EngineFlutterView implements ui.FlutterView {
|
||||
/// Creates a [ui.FlutterView] that can be used in multi-view mode.
|
||||
///
|
||||
/// The [hostElement] parameter specifies the container in the DOM into which
|
||||
|
@ -33,7 +33,7 @@ void setUpCanvasKitTest({bool withImplicitView = false}) {
|
||||
}
|
||||
|
||||
/// Convenience getter for the implicit view.
|
||||
ui.FlutterView get implicitView =>
|
||||
EngineFlutterWindow get implicitView =>
|
||||
EnginePlatformDispatcher.instance.implicitView!;
|
||||
|
||||
/// Utility function for CanvasKit tests to draw pictures without
|
||||
|
@ -53,12 +53,12 @@ class StubPictureRenderer implements PictureRenderer {
|
||||
Map<ScenePicture, ui.Rect> clipRequests = <ScenePicture, ui.Rect>{};
|
||||
}
|
||||
|
||||
class StubFlutterView implements ui.FlutterView {
|
||||
class StubFlutterView implements EngineFlutterView {
|
||||
@override
|
||||
double get devicePixelRatio => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
ui.Display get display => throw UnimplementedError();
|
||||
EngineFlutterDisplay get display => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
List<ui.DisplayFeature> get displayFeatures => throw UnimplementedError();
|
||||
@ -67,23 +67,23 @@ class StubFlutterView implements ui.FlutterView {
|
||||
ui.GestureSettings get gestureSettings => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
ui.ViewPadding get padding => throw UnimplementedError();
|
||||
ViewPadding get padding => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
ui.ViewConstraints get physicalConstraints => throw UnimplementedError();
|
||||
ViewConstraints get physicalConstraints => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
ui.Size get physicalSize => const ui.Size(1000, 1000);
|
||||
|
||||
@override
|
||||
ui.PlatformDispatcher get platformDispatcher => throw UnimplementedError();
|
||||
EnginePlatformDispatcher get platformDispatcher => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
void render(ui.Scene scene, {ui.Size? size}) {
|
||||
}
|
||||
|
||||
@override
|
||||
ui.ViewPadding get systemGestureInsets => throw UnimplementedError();
|
||||
ViewPadding get systemGestureInsets => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
void updateSemantics(ui.SemanticsUpdate update) {
|
||||
@ -93,10 +93,60 @@ class StubFlutterView implements ui.FlutterView {
|
||||
int get viewId => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
ui.ViewPadding get viewInsets => throw UnimplementedError();
|
||||
ViewPadding get viewInsets => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
ui.ViewPadding get viewPadding => throw UnimplementedError();
|
||||
ViewPadding get viewPadding => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
ui.Size? debugPhysicalSizeOverride;
|
||||
|
||||
@override
|
||||
bool isDisposed = false;
|
||||
|
||||
@override
|
||||
PointerBinding get pointerBinding => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
set pointerBinding(_) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
ContextMenu get contextMenu => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
void debugForceResize() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
DimensionsProvider get dimensionsProvider => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
DomManager get dom => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
EmbeddingStrategy get embeddingStrategy => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
MouseCursor get mouseCursor => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Stream<ui.Size?> get onResize => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
void resize(ui.Size newPhysicalSize) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
EngineSemanticsOwner get semantics => throw UnimplementedError();
|
||||
}
|
||||
|
||||
void testMain() {
|
||||
|
102
engine/src/flutter/lib/web_ui/test/ui/renderer_test.dart
Normal file
102
engine/src/flutter/lib/web_ui/test/ui/renderer_test.dart
Normal file
@ -0,0 +1,102 @@
|
||||
// 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 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
import '../common/test_initialization.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
const ui.Color black = ui.Color(0xFF000000);
|
||||
const ui.Color red = ui.Color(0xFFFF0000);
|
||||
const ui.Color green = ui.Color(0xFF00FF00);
|
||||
const ui.Color blue = ui.Color(0xFF0000FF);
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
setUpUnitTests();
|
||||
|
||||
setUp(() {
|
||||
EngineFlutterDisplay.instance.debugOverrideDevicePixelRatio(1.0);
|
||||
});
|
||||
|
||||
test('can render into multiple views', () async {
|
||||
const ui.Rect rect = ui.Rect.fromLTRB(0, 0, 180, 120);
|
||||
|
||||
final host1 = createHostElement(rect);
|
||||
final view1 = EngineFlutterView(EnginePlatformDispatcher.instance, host1);
|
||||
EnginePlatformDispatcher.instance.viewManager.registerView(view1);
|
||||
|
||||
final host2 = createHostElement(rect);
|
||||
final view2 = EngineFlutterView(EnginePlatformDispatcher.instance, host2);
|
||||
EnginePlatformDispatcher.instance.viewManager.registerView(view2);
|
||||
|
||||
final host3 = createHostElement(rect);
|
||||
final view3 = EngineFlutterView(EnginePlatformDispatcher.instance, host3);
|
||||
EnginePlatformDispatcher.instance.viewManager.registerView(view3);
|
||||
|
||||
|
||||
await Future.wait([
|
||||
renderer.renderScene(paintRect(rect, red), view1),
|
||||
renderer.renderScene(paintRect(rect, green), view2),
|
||||
renderer.renderScene(paintRect(rect, blue), view3),
|
||||
]);
|
||||
|
||||
await matchGoldenFile(
|
||||
'ui_multiview_rects.png',
|
||||
region: ui.Rect.fromLTRB(0, 0, rect.width, rect.height * 3),
|
||||
);
|
||||
|
||||
EnginePlatformDispatcher.instance.viewManager.dispose();
|
||||
host1.remove();
|
||||
host2.remove();
|
||||
host3.remove();
|
||||
}, skip: isHtml); // HTML renderer doesn't support multi-view.
|
||||
}
|
||||
|
||||
DomElement createHostElement(ui.Rect rect) {
|
||||
final host = createDomElement('div');
|
||||
host.style
|
||||
..width = '${rect.width}px'
|
||||
..height = '${rect.height}px';
|
||||
domDocument.body!.append(host);
|
||||
return host;
|
||||
}
|
||||
|
||||
ui.Scene paintRect(ui.Rect rect, ui.Color color) {
|
||||
final recorder = ui.PictureRecorder();
|
||||
final canvas = ui.Canvas(recorder, rect);
|
||||
|
||||
// Leave some padding.
|
||||
rect = rect.deflate(5.0);
|
||||
|
||||
// Draw a black border of 5px thickness.
|
||||
canvas.drawRect(
|
||||
rect,
|
||||
ui.Paint()
|
||||
..color = black
|
||||
..style = ui.PaintingStyle.fill,
|
||||
);
|
||||
rect = rect.deflate(5.0);
|
||||
|
||||
// Fill the inner rect with the given color.
|
||||
canvas.drawRect(
|
||||
rect,
|
||||
ui.Paint()
|
||||
..color = color
|
||||
..style = ui.PaintingStyle.fill,
|
||||
);
|
||||
|
||||
final picture = recorder.endRecording();
|
||||
final sb = ui.SceneBuilder();
|
||||
sb.pushOffset(0, 0);
|
||||
sb.addPicture(ui.Offset.zero, picture);
|
||||
return sb.build();
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user