[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
|
// TODO(harryterkelsen): Merge this logic with the async logic in
|
||||||
// [EngineScene], https://github.com/flutter/flutter/issues/142072.
|
// [EngineScene], https://github.com/flutter/flutter/issues/142072.
|
||||||
@override
|
@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),
|
assert(_rasterizers.containsKey(view.viewId),
|
||||||
"Unable to render to a view which hasn't been registered");
|
"Unable to render to a view which hasn't been registered");
|
||||||
final ViewRasterizer rasterizer = _rasterizers[view.viewId]!;
|
final ViewRasterizer rasterizer = _rasterizers[view.viewId]!;
|
||||||
|
@ -302,7 +302,7 @@ class HtmlRenderer implements Renderer {
|
|||||||
CanvasParagraphBuilder(style as EngineParagraphStyle);
|
CanvasParagraphBuilder(style as EngineParagraphStyle);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> renderScene(ui.Scene scene, ui.FlutterView view) async {
|
Future<void> renderScene(ui.Scene scene, EngineFlutterView view) async {
|
||||||
final EngineFlutterView implicitView =
|
final EngineFlutterView implicitView =
|
||||||
EnginePlatformDispatcher.instance.implicitView!;
|
EnginePlatformDispatcher.instance.implicitView!;
|
||||||
scene as SurfaceScene;
|
scene as SurfaceScene;
|
||||||
|
@ -225,5 +225,5 @@ abstract class Renderer {
|
|||||||
|
|
||||||
ui.ParagraphBuilder createParagraphBuilder(ui.ParagraphStyle style);
|
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`.
|
// This class builds a DOM tree that composites an `EngineScene`.
|
||||||
class EngineSceneView {
|
class EngineSceneView {
|
||||||
factory EngineSceneView(PictureRenderer pictureRenderer, ui.FlutterView flutterView) {
|
factory EngineSceneView(PictureRenderer pictureRenderer, EngineFlutterView flutterView) {
|
||||||
final DomElement sceneElement = createDomElement('flt-scene');
|
final DomElement sceneElement = createDomElement('flt-scene');
|
||||||
return EngineSceneView._(pictureRenderer, flutterView, sceneElement);
|
return EngineSceneView._(pictureRenderer, flutterView, sceneElement);
|
||||||
}
|
}
|
||||||
@ -53,7 +53,7 @@ class EngineSceneView {
|
|||||||
|
|
||||||
final PictureRenderer pictureRenderer;
|
final PictureRenderer pictureRenderer;
|
||||||
final DomElement sceneElement;
|
final DomElement sceneElement;
|
||||||
final ui.FlutterView flutterView;
|
final EngineFlutterView flutterView;
|
||||||
|
|
||||||
List<SliceContainer> containers = <SliceContainer>[];
|
List<SliceContainer> containers = <SliceContainer>[];
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
|
|||||||
|
|
||||||
class SkwasmRenderer implements Renderer {
|
class SkwasmRenderer implements Renderer {
|
||||||
late SkwasmSurface surface;
|
late SkwasmSurface surface;
|
||||||
EngineSceneView? _sceneView;
|
final Map<EngineFlutterView, EngineSceneView> _sceneViews = <EngineFlutterView, EngineSceneView>{};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final SkwasmFontCollection fontCollection = SkwasmFontCollection();
|
final SkwasmFontCollection fontCollection = SkwasmFontCollection();
|
||||||
@ -397,27 +397,21 @@ class SkwasmRenderer implements Renderer {
|
|||||||
return decoder;
|
return decoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(harryterkelsen): Add multiview support,
|
|
||||||
// https://github.com/flutter/flutter/issues/137073.
|
|
||||||
@override
|
@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;
|
final FrameTimingRecorder? recorder = FrameTimingRecorder.frameTimingsEnabled ? FrameTimingRecorder() : null;
|
||||||
recorder?.recordBuildFinish();
|
recorder?.recordBuildFinish();
|
||||||
|
|
||||||
view as EngineFlutterView;
|
|
||||||
assert(view is EngineFlutterWindow, 'Skwasm does not support multi-view mode yet');
|
|
||||||
final EngineSceneView sceneView = _getSceneViewForView(view);
|
final EngineSceneView sceneView = _getSceneViewForView(view);
|
||||||
return sceneView.renderScene(scene as EngineScene, recorder);
|
return sceneView.renderScene(scene as EngineScene, recorder);
|
||||||
}
|
}
|
||||||
|
|
||||||
EngineSceneView _getSceneViewForView(EngineFlutterView view) {
|
EngineSceneView _getSceneViewForView(EngineFlutterView view) {
|
||||||
// TODO(mdebbar): Support multi-view mode.
|
return _sceneViews.putIfAbsent(view, () {
|
||||||
if (_sceneView == null) {
|
final EngineSceneView sceneView = EngineSceneView(SkwasmPictureRenderer(surface), view);
|
||||||
_sceneView = EngineSceneView(SkwasmPictureRenderer(surface), view);
|
view.dom.setScene(sceneView.sceneElement);
|
||||||
final EngineFlutterView implicitView = EnginePlatformDispatcher.instance.implicitView!;
|
return sceneView;
|
||||||
implicitView.dom.setScene(_sceneView!.sceneElement);
|
});
|
||||||
}
|
|
||||||
return _sceneView!;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -44,7 +44,7 @@ int _nextViewId = kImplicitViewId + 1;
|
|||||||
///
|
///
|
||||||
/// In addition to everything defined in [ui.FlutterView], this class adds
|
/// In addition to everything defined in [ui.FlutterView], this class adds
|
||||||
/// a few web-specific properties.
|
/// 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.
|
/// Creates a [ui.FlutterView] that can be used in multi-view mode.
|
||||||
///
|
///
|
||||||
/// The [hostElement] parameter specifies the container in the DOM into which
|
/// 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.
|
/// Convenience getter for the implicit view.
|
||||||
ui.FlutterView get implicitView =>
|
EngineFlutterWindow get implicitView =>
|
||||||
EnginePlatformDispatcher.instance.implicitView!;
|
EnginePlatformDispatcher.instance.implicitView!;
|
||||||
|
|
||||||
/// Utility function for CanvasKit tests to draw pictures without
|
/// 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>{};
|
Map<ScenePicture, ui.Rect> clipRequests = <ScenePicture, ui.Rect>{};
|
||||||
}
|
}
|
||||||
|
|
||||||
class StubFlutterView implements ui.FlutterView {
|
class StubFlutterView implements EngineFlutterView {
|
||||||
@override
|
@override
|
||||||
double get devicePixelRatio => throw UnimplementedError();
|
double get devicePixelRatio => throw UnimplementedError();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ui.Display get display => throw UnimplementedError();
|
EngineFlutterDisplay get display => throw UnimplementedError();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<ui.DisplayFeature> get displayFeatures => throw UnimplementedError();
|
List<ui.DisplayFeature> get displayFeatures => throw UnimplementedError();
|
||||||
@ -67,23 +67,23 @@ class StubFlutterView implements ui.FlutterView {
|
|||||||
ui.GestureSettings get gestureSettings => throw UnimplementedError();
|
ui.GestureSettings get gestureSettings => throw UnimplementedError();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ui.ViewPadding get padding => throw UnimplementedError();
|
ViewPadding get padding => throw UnimplementedError();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ui.ViewConstraints get physicalConstraints => throw UnimplementedError();
|
ViewConstraints get physicalConstraints => throw UnimplementedError();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ui.Size get physicalSize => const ui.Size(1000, 1000);
|
ui.Size get physicalSize => const ui.Size(1000, 1000);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ui.PlatformDispatcher get platformDispatcher => throw UnimplementedError();
|
EnginePlatformDispatcher get platformDispatcher => throw UnimplementedError();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void render(ui.Scene scene, {ui.Size? size}) {
|
void render(ui.Scene scene, {ui.Size? size}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ui.ViewPadding get systemGestureInsets => throw UnimplementedError();
|
ViewPadding get systemGestureInsets => throw UnimplementedError();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void updateSemantics(ui.SemanticsUpdate update) {
|
void updateSemantics(ui.SemanticsUpdate update) {
|
||||||
@ -93,10 +93,60 @@ class StubFlutterView implements ui.FlutterView {
|
|||||||
int get viewId => throw UnimplementedError();
|
int get viewId => throw UnimplementedError();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ui.ViewPadding get viewInsets => throw UnimplementedError();
|
ViewPadding get viewInsets => throw UnimplementedError();
|
||||||
|
|
||||||
@override
|
@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() {
|
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