Add support for platform views in the CanvasKit backend (flutter/engine#14263)

* Add support for platform views in the CanvasKit backend

* Respond to comments

* Update license file
This commit is contained in:
Harry Terkelsen 2019-12-09 17:59:50 -08:00 committed by GitHub
parent 392be60370
commit db352ddd25
16 changed files with 849 additions and 82 deletions

View File

@ -386,6 +386,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/color_filter.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/canvas.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/canvas_kit_canvas.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/color_filter.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/embedded_views.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/engine_delegate.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/fonts.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/image.dart
@ -394,6 +395,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/initialization.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/layer.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/layer_scene_builder.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/layer_tree.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/n_way_canvas.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/path.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/path_metrics.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/picture.dart

View File

@ -27,6 +27,7 @@ part 'engine/color_filter.dart';
part 'engine/compositor/canvas.dart';
part 'engine/compositor/canvas_kit_canvas.dart';
part 'engine/compositor/color_filter.dart';
part 'engine/compositor/embedded_views.dart';
part 'engine/compositor/engine_delegate.dart';
part 'engine/compositor/fonts.dart';
part 'engine/compositor/image.dart';
@ -35,6 +36,7 @@ part 'engine/compositor/initialization.dart';
part 'engine/compositor/layer.dart';
part 'engine/compositor/layer_scene_builder.dart';
part 'engine/compositor/layer_tree.dart';
part 'engine/compositor/n_way_canvas.dart';
part 'engine/compositor/path.dart';
part 'engine/compositor/path_metrics.dart';
part 'engine/compositor/picture.dart';

View File

@ -12,6 +12,10 @@ class SkCanvas {
int get saveCount => skCanvas.callMethod('getSaveCount');
void clear(ui.Color color) {
skCanvas.callMethod('clear', <int>[color.value]);
}
void clipPath(ui.Path path, bool doAntiAlias) {
final SkPath skPath = path;
final js.JsObject intersectClipOp = canvasKit['ClipOp']['Intersect'];

View File

@ -0,0 +1,544 @@
// 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.
part of engine;
/// This composites HTML views into the [ui.Scene].
class HtmlViewEmbedder {
/// A picture recorder associated with a view id.
///
/// When we composite in the platform view, we need to create a new canvas
/// for further paint commands to paint to, since the composited view will
/// be on top of the current canvas, and we want further paint commands to
/// be on top of the platform view.
final Map<int, SkPictureRecorder> _pictureRecorders =
<int, SkPictureRecorder>{};
/// The most recent composition parameters for a given view id.
///
/// If we receive a request to composite a view, but the composition
/// parameters haven't changed, we can avoid having to recompute the
/// element stack that correctly composites the view into the scene.
final Map<int, EmbeddedViewParams> _currentCompositionParams =
<int, EmbeddedViewParams>{};
/// The HTML element associated with the given view id.
final Map<int, html.Element> _views = <int, html.Element>{};
/// The root view in the stack of mutator elements for the view id.
final Map<int, html.Element> _rootViews = <int, html.Element>{};
/// The overlay for the view id.
final Map<int, Overlay> _overlays = <int, Overlay>{};
/// The views that need to be recomposited into the scene on the next frame.
final Set<int> _viewsToRecomposite = <int>{};
/// The views that need to be disposed of on the next frame.
final Set<int> _viewsToDispose = <int>{};
/// The list of view ids that should be composited, in order.
List<int> _compositionOrder = <int>[];
/// The most recent composition order.
List<int> _activeCompositionOrder = <int>[];
/// The number of clipping elements used last time the view was composited.
Map<int, int> _clipCount = <int, int>{};
/// The size of the frame, in physical pixels.
ui.Size _frameSize;
void set frameSize(ui.Size size) {
if (_frameSize == size) {
return;
}
_activeCompositionOrder.clear();
_frameSize = size;
}
void handlePlatformViewCall(
ByteData data,
ui.PlatformMessageResponseCallback callback,
) {
const MethodCodec codec = StandardMethodCodec();
final MethodCall decoded = codec.decodeMethodCall(data);
switch (decoded.method) {
case 'create':
_create(decoded, callback);
return;
case 'dispose':
_dispose(decoded, callback);
return;
}
callback(null);
}
void _create(
MethodCall methodCall, ui.PlatformMessageResponseCallback callback) {
final Map<dynamic, dynamic> args = methodCall.arguments;
final int viewId = args['id'];
final String viewType = args['viewType'];
const MethodCodec codec = StandardMethodCodec();
if (_views[viewId] != null) {
callback(codec.encodeErrorEnvelope(
code: 'recreating_view',
message: 'trying to create an already created view',
details: 'view id: $viewId',
));
return;
}
final PlatformViewFactory factory =
platformViewRegistry.registeredFactories[viewType];
if (factory == null) {
callback(codec.encodeErrorEnvelope(
code: 'unregistered_view_type',
message: 'trying to create a view with an unregistered type',
details: 'unregistered view type: $viewType',
));
return;
}
// TODO(het): Support creation parameters.
html.Element embeddedView = factory(viewId);
_views[viewId] = embeddedView;
_rootViews[viewId] = embeddedView;
callback(codec.encodeSuccessEnvelope(null));
}
void _dispose(
MethodCall methodCall, ui.PlatformMessageResponseCallback callback) {
int viewId = methodCall.arguments;
const MethodCodec codec = StandardMethodCodec();
if (!_views.containsKey(viewId)) {
callback(codec.encodeErrorEnvelope(
code: 'unknown_view',
message: 'trying to dispose an unknown view',
details: 'view id: $viewId',
));
}
_viewsToDispose.add(viewId);
callback(codec.encodeSuccessEnvelope(null));
}
List<SkCanvas> getCurrentCanvases() {
final List<SkCanvas> canvases = <SkCanvas>[];
for (int i = 0; i < _compositionOrder.length; i++) {
final int viewId = _compositionOrder[i];
canvases.add(_pictureRecorders[viewId].recordingCanvas);
}
return canvases;
}
void prerollCompositeEmbeddedView(int viewId, EmbeddedViewParams params) {
final pictureRecorder = SkPictureRecorder();
pictureRecorder.beginRecording(ui.Offset.zero & _frameSize);
pictureRecorder.recordingCanvas.clear(ui.Color(0x00000000));
_pictureRecorders[viewId] = pictureRecorder;
_compositionOrder.add(viewId);
// Do nothing if the params didn't change.
if (_currentCompositionParams[viewId] == params) {
return;
}
_currentCompositionParams[viewId] = params;
_viewsToRecomposite.add(viewId);
}
SkCanvas compositeEmbeddedView(int viewId) {
// Do nothing if this view doesn't need to be composited.
if (!_viewsToRecomposite.contains(viewId)) {
return _pictureRecorders[viewId].recordingCanvas;
}
_compositeWithParams(viewId, _currentCompositionParams[viewId]);
_viewsToRecomposite.remove(viewId);
return _pictureRecorders[viewId].recordingCanvas;
}
void _compositeWithParams(int viewId, EmbeddedViewParams params) {
final html.Element platformView = _views[viewId];
platformView.style.width = '${params.size.width}px';
platformView.style.height = '${params.size.height}px';
platformView.style.position = 'absolute';
final int currentClippingCount = _countClips(params.mutators);
final int previousClippingCount = _clipCount[viewId];
if (currentClippingCount != previousClippingCount) {
_clipCount[viewId] = currentClippingCount;
html.Element oldPlatformViewRoot = _rootViews[viewId];
html.Element newPlatformViewRoot = _reconstructClipViewsChain(
currentClippingCount,
platformView,
oldPlatformViewRoot,
);
_rootViews[viewId] = newPlatformViewRoot;
}
_applyMutators(params.mutators, platformView);
}
int _countClips(MutatorsStack mutators) {
int clipCount = 0;
for (final Mutator mutator in mutators) {
if (mutator.isClipType) {
clipCount++;
}
}
return clipCount;
}
html.Element _reconstructClipViewsChain(
int numClips,
html.Element platformView,
html.Element headClipView,
) {
int indexInFlutterView = -1;
if (headClipView.parent != null) {
indexInFlutterView = skiaSceneHost.children.indexOf(headClipView);
headClipView.remove();
}
html.Element head = platformView;
int clipIndex = 0;
// Re-use as much existing clip views as needed.
while (head != headClipView && clipIndex < numClips) {
head = head.parent;
clipIndex++;
}
// If there weren't enough existing clip views, add more.
while (clipIndex < numClips) {
html.Element clippingView = html.Element.tag('flt-clip');
clippingView.append(head);
head = clippingView;
clipIndex++;
}
head.remove();
// If the chain was previously attached, attach it to the same position.
if (indexInFlutterView > -1) {
skiaSceneHost.children.insert(indexInFlutterView, head);
}
return head;
}
void _applyMutators(MutatorsStack mutators, html.Element embeddedView) {
html.Element head = embeddedView;
Matrix4 headTransform = Matrix4.identity();
double embeddedOpacity = 1.0;
_resetAnchor(head);
for (final Mutator mutator in mutators) {
switch (mutator.type) {
case MutatorType.transform:
headTransform.multiply(mutator.matrix);
head.style.transform =
float64ListToCssTransform(headTransform.storage);
break;
case MutatorType.clipRect:
case MutatorType.clipRRect:
case MutatorType.clipPath:
html.Element clipView = head.parent;
clipView.style.clip = '';
clipView.style.clipPath = '';
headTransform = Matrix4.identity();
clipView.style.transform = '';
if (mutator.rect != null) {
final ui.Rect rect = mutator.rect;
clipView.style.clip = 'rect(${rect.top}px, ${rect.right}px, '
'${rect.bottom}px, ${rect.left}px)';
} else if (mutator.rrect != null) {
final SkPath path = SkPath();
path.addRRect(mutator.rrect);
_ensureSvgPathDefs();
html.Element pathDefs = _svgPathDefs.querySelector('#sk_path_defs');
_clipPathCount += 1;
html.Element newClipPath =
html.Element.html('<clipPath id="svgClip$_clipPathCount">'
'<path d="${path.toSvgString()}">'
'</path></clipPath>');
pathDefs.append(newClipPath);
clipView.style.clipPath = 'url(#svgClip$_clipPathCount)';
} else if (mutator.path != null) {
final SkPath path = mutator.path;
_ensureSvgPathDefs();
html.Element pathDefs = _svgPathDefs.querySelector('#sk_path_defs');
_clipPathCount += 1;
html.Element newClipPath =
html.Element.html('<clipPath id="svgClip$_clipPathCount">'
'<path d="${path.toSvgString()}">'
'</path></clipPath>');
pathDefs.append(newClipPath);
clipView.style.clipPath = 'url(#svgClip$_clipPathCount)';
}
_resetAnchor(clipView);
head = clipView;
break;
case MutatorType.opacity:
embeddedOpacity *= mutator.alphaFloat;
break;
}
}
embeddedView.style.opacity = embeddedOpacity.toString();
// Reverse scale based on screen scale.
//
// HTML elements use logical (CSS) pixels, but we have been using physical
// pixels, so scale down the head element to match the logical resolution.
final double scale = html.window.devicePixelRatio;
final double inverseScale = 1 / scale;
final Matrix4 scaleMatrix =
Matrix4.diagonal3Values(inverseScale, inverseScale, 1);
headTransform.multiply(scaleMatrix);
head.style.transform = float64ListToCssTransform(headTransform.storage);
}
/// Sets the transform origin to the top-left corner of the element.
///
/// By default, the transform origin is the center of the element, but
/// Flutter assumes the transform origin is the top-left point.
void _resetAnchor(html.Element element) {
element.style.transformOrigin = '0 0 0';
element.style.position = 'absolute';
}
int _clipPathCount = 0;
html.Element _svgPathDefs;
/// Ensures we add a container of SVG path defs to the DOM so they can
/// be referred to in clip-path: url(#blah).
void _ensureSvgPathDefs() {
if (_svgPathDefs != null) return;
_svgPathDefs = html.Element.html(
'<svg width="0" height="0"><defs id="sk_path_defs"></defs></svg>',
treeSanitizer: _NullTreeSanitizer(),
);
skiaSceneHost.append(_svgPathDefs);
}
void submitFrame() {
disposeViews();
for (int i = 0; i < _compositionOrder.length; i++) {
int viewId = _compositionOrder[i];
ensureOverlayInitialized(viewId);
final SurfaceFrame frame =
_overlays[viewId].surface.acquireFrame(_frameSize);
final SkCanvas canvas = frame.skiaCanvas;
canvas.drawPicture(_pictureRecorders[viewId].endRecording());
frame.submit();
}
_pictureRecorders.clear();
if (_listEquals(_compositionOrder, _activeCompositionOrder)) {
_compositionOrder.clear();
return;
}
_activeCompositionOrder.clear();
for (int i = 0; i < _compositionOrder.length; i++) {
int viewId = _compositionOrder[i];
html.Element platformViewRoot = _rootViews[viewId];
html.Element overlay = _overlays[viewId].surface.htmlElement;
platformViewRoot.remove();
skiaSceneHost.append(platformViewRoot);
overlay.remove();
skiaSceneHost.append(overlay);
_activeCompositionOrder.add(viewId);
}
_compositionOrder.clear();
}
void disposeViews() {
if (_viewsToDispose.isEmpty) {
return;
}
for (int viewId in _viewsToDispose) {
final html.Element rootView = _rootViews[viewId];
rootView.remove();
_views.remove(viewId);
_rootViews.remove(viewId);
if (_overlays[viewId] != null) {
final Overlay overlay = _overlays[viewId];
overlay.surface.htmlElement?.remove();
}
_overlays.remove(viewId);
_currentCompositionParams.remove(viewId);
_clipCount.remove(viewId);
_viewsToRecomposite.remove(viewId);
}
_viewsToDispose.clear();
}
void ensureOverlayInitialized(int viewId) {
Overlay overlay = _overlays[viewId];
if (overlay != null) {
return;
}
Surface surface = Surface();
SkSurface skSurface = surface.acquireRenderSurface(_frameSize);
_overlays[viewId] = Overlay(surface, skSurface);
}
}
/// The parameters passed to the view embedder.
class EmbeddedViewParams {
EmbeddedViewParams(this.offset, this.size, MutatorsStack mutators)
: mutators = MutatorsStack._copy(mutators);
final ui.Offset offset;
final ui.Size size;
final MutatorsStack mutators;
bool operator ==(dynamic other) {
if (identical(this, other)) return true;
if (other is! EmbeddedViewParams) return false;
EmbeddedViewParams typedOther = other;
return offset == typedOther.offset &&
size == typedOther.size &&
mutators == typedOther.mutators;
}
int get hashCode => ui.hashValues(offset, size, mutators);
}
enum MutatorType {
clipRect,
clipRRect,
clipPath,
transform,
opacity,
}
/// Stores mutation information like clipping or transform.
class Mutator {
const Mutator._(
this.type,
this.rect,
this.rrect,
this.path,
this.matrix,
this.alpha,
);
final MutatorType type;
final ui.Rect rect;
final ui.RRect rrect;
final ui.Path path;
final Matrix4 matrix;
final int alpha;
const Mutator.clipRect(ui.Rect rect)
: this._(MutatorType.clipRect, rect, null, null, null, null);
const Mutator.clipRRect(ui.RRect rrect)
: this._(MutatorType.clipRRect, null, rrect, null, null, null);
const Mutator.clipPath(ui.Path path)
: this._(MutatorType.clipPath, null, null, path, null, null);
const Mutator.transform(Matrix4 matrix)
: this._(MutatorType.transform, null, null, null, matrix, null);
const Mutator.opacity(int alpha)
: this._(MutatorType.opacity, null, null, null, null, alpha);
bool get isClipType =>
type == MutatorType.clipRect ||
type == MutatorType.clipRRect ||
type == MutatorType.clipPath;
double get alphaFloat => alpha / 255.0;
bool operator ==(dynamic other) {
if (identical(this, other)) return true;
if (other is! Mutator) return false;
final Mutator typedOther = other;
if (type != typedOther.type) {
return false;
}
switch (type) {
case MutatorType.clipRect:
return rect == typedOther.rect;
case MutatorType.clipRRect:
return rrect == typedOther.rrect;
case MutatorType.clipPath:
return path == typedOther.path;
case MutatorType.transform:
return matrix == typedOther.matrix;
case MutatorType.opacity:
return alpha == typedOther.alpha;
}
}
int get hashCode => ui.hashValues(type, rect, rrect, path, matrix, alpha);
}
/// A stack of mutators that can be applied to an embedded view.
class MutatorsStack extends Iterable<Mutator> {
MutatorsStack() : _mutators = <Mutator>[];
MutatorsStack._copy(MutatorsStack original)
: _mutators = List<Mutator>.from(original._mutators);
final List<Mutator> _mutators;
void pushClipRect(ui.Rect rect) {
_mutators.add(Mutator.clipRect(rect));
}
void pushClipRRect(ui.RRect rrect) {
_mutators.add(Mutator.clipRRect(rrect));
}
void pushClipPath(ui.Path path) {
_mutators.add(Mutator.clipPath(path));
}
void pushTransform(Matrix4 matrix) {
_mutators.add(Mutator.transform(matrix));
}
void pushOpacity(int alpha) {
_mutators.add(Mutator.opacity(alpha));
}
void pop() {
_mutators.removeLast();
}
bool operator ==(dynamic other) {
if (identical(other, this)) return true;
if (other is! MutatorsStack) return false;
final MutatorsStack typedOther = other;
if (_mutators.length != typedOther._mutators.length) {
return false;
}
for (int i = 0; i < _mutators.length; i++) {
if (_mutators[i] != typedOther._mutators[i]) {
return false;
}
}
return true;
}
int get hashCode => ui.hashList(_mutators);
@override
Iterator<Mutator> get iterator => _mutators.reversed.iterator;
}
/// Represents a surface overlaying a platform view.
class Overlay {
final Surface surface;
final SkSurface skSurface;
Overlay(this.surface, this.skSurface);
}

View File

@ -58,6 +58,7 @@ class Engine extends RuntimeDelegate {
}
layerTree.frameSize = frameSize;
layerTree.devicePixelRatio = _viewportMetrics.devicePixelRatio;
_animator.render(layerTree);
}

View File

@ -32,6 +32,10 @@ Future<void> initializeSkia() {
},
]);
});
/// Add a Skia scene host.
skiaSceneHost = html.Element.tag('flt-scene');
domRenderer.renderScene(skiaSceneHost);
return canvasKitCompleter.future;
}
@ -42,3 +46,6 @@ js.JsObject canvasKit;
/// The Skia font collection.
SkiaFontCollection skiaFontCollection;
/// The scene host, where the root canvas and overlay canvases are added to.
html.Element skiaSceneHost;

View File

@ -34,18 +34,36 @@ class PrerollContext {
/// A raster cache. Used to register candidates for caching.
final RasterCache rasterCache;
PrerollContext(this.rasterCache);
/// A compositor for embedded HTML views.
final HtmlViewEmbedder viewEmbedder;
final MutatorsStack mutatorsStack = MutatorsStack();
PrerollContext(this.rasterCache, this.viewEmbedder);
}
/// A context shared by all layers during the paint pass.
class PaintContext {
/// The canvas to paint to.
final SkCanvas canvas;
/// A multi-canvas that applies clips, transforms, and opacity
/// operations to all canvases (root canvas and overlay canvases for the
/// platform views).
SkNWayCanvas internalNodesCanvas;
/// The canvas for leaf nodes to paint to.
SkCanvas leafNodesCanvas;
/// A raster cache potentially containing pre-rendered pictures.
final RasterCache rasterCache;
PaintContext(this.canvas, this.rasterCache);
/// A compositor for embedded HTML views.
final HtmlViewEmbedder viewEmbedder;
PaintContext(
this.internalNodesCanvas,
this.leafNodesCanvas,
this.rasterCache,
this.viewEmbedder,
);
}
/// A layer that contains child layers.
@ -100,9 +118,9 @@ class BackdropFilterLayer extends ContainerLayer {
@override
void paint(PaintContext context) {
context.canvas.saveLayerWithFilter(paintBounds, _filter);
context.internalNodesCanvas.saveLayerWithFilter(paintBounds, _filter);
paintChildren(context);
context.canvas.restore();
context.internalNodesCanvas.restore();
}
}
@ -116,29 +134,31 @@ class ClipPathLayer extends ContainerLayer {
: assert(_clipBehavior != ui.Clip.none);
@override
void preroll(PrerollContext prerollContext, Matrix4 matrix) {
final ui.Rect childPaintBounds = prerollChildren(prerollContext, matrix);
void preroll(PrerollContext context, Matrix4 matrix) {
context.mutatorsStack.pushClipPath(_clipPath);
final ui.Rect childPaintBounds = prerollChildren(context, matrix);
final ui.Rect clipBounds = _clipPath.getBounds();
if (childPaintBounds.overlaps(clipBounds)) {
paintBounds = childPaintBounds.intersect(clipBounds);
}
context.mutatorsStack.pop();
}
@override
void paint(PaintContext paintContext) {
assert(needsPainting);
paintContext.canvas.save();
paintContext.canvas.clipPath(_clipPath, _clipBehavior != ui.Clip.hardEdge);
paintContext.internalNodesCanvas.save();
paintContext.internalNodesCanvas.clipPath(_clipPath, _clipBehavior != ui.Clip.hardEdge);
if (_clipBehavior == ui.Clip.antiAliasWithSaveLayer) {
paintContext.canvas.saveLayer(paintBounds, null);
paintContext.internalNodesCanvas.saveLayer(paintBounds, null);
}
paintChildren(paintContext);
if (_clipBehavior == ui.Clip.antiAliasWithSaveLayer) {
paintContext.canvas.restore();
paintContext.internalNodesCanvas.restore();
}
paintContext.canvas.restore();
paintContext.internalNodesCanvas.restore();
}
}
@ -152,31 +172,33 @@ class ClipRectLayer extends ContainerLayer {
: assert(_clipBehavior != ui.Clip.none);
@override
void preroll(PrerollContext prerollContext, Matrix4 matrix) {
final ui.Rect childPaintBounds = prerollChildren(prerollContext, matrix);
void preroll(PrerollContext context, Matrix4 matrix) {
context.mutatorsStack.pushClipRect(_clipRect);
final ui.Rect childPaintBounds = prerollChildren(context, matrix);
if (childPaintBounds.overlaps(_clipRect)) {
paintBounds = childPaintBounds.intersect(_clipRect);
}
context.mutatorsStack.pop();
}
@override
void paint(PaintContext paintContext) {
assert(needsPainting);
paintContext.canvas.save();
paintContext.canvas.clipRect(
paintContext.internalNodesCanvas.save();
paintContext.internalNodesCanvas.clipRect(
_clipRect,
ui.ClipOp.intersect,
_clipBehavior != ui.Clip.hardEdge,
);
if (_clipBehavior == ui.Clip.antiAliasWithSaveLayer) {
paintContext.canvas.saveLayer(_clipRect, null);
paintContext.internalNodesCanvas.saveLayer(_clipRect, null);
}
paintChildren(paintContext);
if (_clipBehavior == ui.Clip.antiAliasWithSaveLayer) {
paintContext.canvas.restore();
paintContext.internalNodesCanvas.restore();
}
paintContext.canvas.restore();
paintContext.internalNodesCanvas.restore();
}
}
@ -190,28 +212,30 @@ class ClipRRectLayer extends ContainerLayer {
: assert(_clipBehavior != ui.Clip.none);
@override
void preroll(PrerollContext prerollContext, Matrix4 matrix) {
final ui.Rect childPaintBounds = prerollChildren(prerollContext, matrix);
void preroll(PrerollContext context, Matrix4 matrix) {
context.mutatorsStack.pushClipRRect(_clipRRect);
final ui.Rect childPaintBounds = prerollChildren(context, matrix);
if (childPaintBounds.overlaps(_clipRRect.outerRect)) {
paintBounds = childPaintBounds.intersect(_clipRRect.outerRect);
}
context.mutatorsStack.pop();
}
@override
void paint(PaintContext paintContext) {
assert(needsPainting);
paintContext.canvas.save();
paintContext.canvas
paintContext.internalNodesCanvas.save();
paintContext.internalNodesCanvas
.clipRRect(_clipRRect, _clipBehavior != ui.Clip.hardEdge);
if (_clipBehavior == ui.Clip.antiAliasWithSaveLayer) {
paintContext.canvas.saveLayer(paintBounds, null);
paintContext.internalNodesCanvas.saveLayer(paintBounds, null);
}
paintChildren(paintContext);
if (_clipBehavior == ui.Clip.antiAliasWithSaveLayer) {
paintContext.canvas.restore();
paintContext.internalNodesCanvas.restore();
}
paintContext.canvas.restore();
paintContext.internalNodesCanvas.restore();
}
}
@ -223,12 +247,16 @@ class OpacityLayer extends ContainerLayer implements ui.OpacityEngineLayer {
OpacityLayer(this._alpha, this._offset);
@override
void preroll(PrerollContext prerollContext, Matrix4 matrix) {
void preroll(PrerollContext context, Matrix4 matrix) {
final Matrix4 childMatrix = Matrix4.copy(matrix);
childMatrix.translate(_offset.dx, _offset.dy);
final ui.Rect childPaintBounds =
prerollChildren(prerollContext, childMatrix);
paintBounds = childPaintBounds.translate(_offset.dx, _offset.dy);
context.mutatorsStack
.pushTransform(Matrix4.translationValues(_offset.dx, _offset.dy, 0.0));
context.mutatorsStack.pushOpacity(_alpha);
super.preroll(context, childMatrix);
context.mutatorsStack.pop();
context.mutatorsStack.pop();
paintBounds = paintBounds.translate(_offset.dx, _offset.dy);
}
@override
@ -238,16 +266,16 @@ class OpacityLayer extends ContainerLayer implements ui.OpacityEngineLayer {
final ui.Paint paint = ui.Paint();
paint.color = ui.Color.fromARGB(_alpha, 0, 0, 0);
paintContext.canvas.save();
paintContext.canvas.translate(_offset.dx, _offset.dy);
paintContext.internalNodesCanvas.save();
paintContext.internalNodesCanvas.translate(_offset.dx, _offset.dy);
final ui.Rect saveLayerBounds = paintBounds.shift(-_offset);
paintContext.canvas.saveLayer(saveLayerBounds, paint);
paintContext.internalNodesCanvas.saveLayer(saveLayerBounds, paint);
paintChildren(paintContext);
// Restore twice: once for the translate and once for the saveLayer.
paintContext.canvas.restore();
paintContext.canvas.restore();
paintContext.internalNodesCanvas.restore();
paintContext.internalNodesCanvas.restore();
}
}
@ -260,11 +288,12 @@ class TransformLayer extends ContainerLayer
TransformLayer(this._transform);
@override
void preroll(PrerollContext prerollContext, Matrix4 matrix) {
void preroll(PrerollContext context, Matrix4 matrix) {
final Matrix4 childMatrix = matrix * _transform;
final ui.Rect childPaintBounds =
prerollChildren(prerollContext, childMatrix);
context.mutatorsStack.pushTransform(_transform);
final ui.Rect childPaintBounds = prerollChildren(context, childMatrix);
paintBounds = _transformRect(_transform, childPaintBounds);
context.mutatorsStack.pop();
}
/// Applies the given matrix as a perspective transform to the given point.
@ -307,10 +336,10 @@ class TransformLayer extends ContainerLayer
void paint(PaintContext paintContext) {
assert(needsPainting);
paintContext.canvas.save();
paintContext.canvas.transform(_transform.storage);
paintContext.internalNodesCanvas.save();
paintContext.internalNodesCanvas.transform(_transform.storage);
paintChildren(paintContext);
paintContext.canvas.restore();
paintContext.internalNodesCanvas.restore();
}
}
@ -340,11 +369,11 @@ class PictureLayer extends Layer {
assert(picture != null);
assert(needsPainting);
paintContext.canvas.save();
paintContext.canvas.translate(offset.dx, offset.dy);
paintContext.leafNodesCanvas.save();
paintContext.leafNodesCanvas.translate(offset.dx, offset.dy);
paintContext.canvas.drawPicture(picture);
paintContext.canvas.restore();
paintContext.leafNodesCanvas.drawPicture(picture);
paintContext.leafNodesCanvas.restore();
}
}
@ -432,26 +461,26 @@ class PhysicalShapeLayer extends ContainerLayer
assert(needsPainting);
if (_elevation != 0) {
drawShadow(paintContext.canvas, _path, _shadowColor, _elevation,
drawShadow(paintContext.leafNodesCanvas, _path, _shadowColor, _elevation,
_color.alpha != 0xff);
}
final ui.Paint paint = ui.Paint()..color = _color;
if (_clipBehavior != ui.Clip.antiAliasWithSaveLayer) {
paintContext.canvas.drawPath(_path, paint);
paintContext.leafNodesCanvas.drawPath(_path, paint);
}
final int saveCount = paintContext.canvas.save();
final int saveCount = paintContext.internalNodesCanvas.save();
switch (_clipBehavior) {
case ui.Clip.hardEdge:
paintContext.canvas.clipPath(_path, false);
paintContext.internalNodesCanvas.clipPath(_path, false);
break;
case ui.Clip.antiAlias:
paintContext.canvas.clipPath(_path, true);
paintContext.internalNodesCanvas.clipPath(_path, true);
break;
case ui.Clip.antiAliasWithSaveLayer:
paintContext.canvas.clipPath(_path, true);
paintContext.canvas.saveLayer(paintBounds, null);
paintContext.internalNodesCanvas.clipPath(_path, true);
paintContext.internalNodesCanvas.saveLayer(paintBounds, null);
break;
case ui.Clip.none:
break;
@ -462,12 +491,12 @@ class PhysicalShapeLayer extends ContainerLayer
// (https://github.com/flutter/flutter/issues/18057#issue-328003931)
// using saveLayer, we have to call drawPaint instead of drawPath as
// anti-aliased drawPath will always have such artifacts.
paintContext.canvas.drawPaint(paint);
paintContext.leafNodesCanvas.drawPaint(paint);
}
paintChildren(paintContext);
paintContext.canvas.restoreToCount(saveCount);
paintContext.internalNodesCanvas.restoreToCount(saveCount);
}
/// Draws a shadow on the given [canvas] for the given [path].
@ -479,3 +508,32 @@ class PhysicalShapeLayer extends ContainerLayer
canvas.drawShadow(path, color, elevation, transparentOccluder);
}
}
/// A layer which renders a platform view (an HTML element in this case).
class PlatformViewLayer extends Layer {
PlatformViewLayer(this.viewId, this.offset, this.width, this.height);
final int viewId;
final ui.Offset offset;
final double width;
final double height;
@override
void preroll(PrerollContext context, Matrix4 matrix) {
paintBounds = ui.Rect.fromLTWH(offset.dx, offset.dy, width, height);
context.viewEmbedder.prerollCompositeEmbeddedView(
viewId,
EmbeddedViewParams(
offset,
ui.Size(width, height),
context.mutatorsStack,
),
);
}
@override
void paint(PaintContext context) {
SkCanvas canvas = context.viewEmbedder.compositeEmbeddedView(viewId);
context.leafNodesCanvas = canvas;
}
}

View File

@ -73,7 +73,7 @@ class LayerSceneBuilder implements ui.SceneBuilder {
double height = 0.0,
Object webOnlyPaintedBy,
}) {
// TODO(b/128317425): implement addPlatformView.
currentLayer.add(PlatformViewLayer(viewId, offset, width, height));
}
@override

View File

@ -12,6 +12,9 @@ class LayerTree {
/// The size (in physical pixels) of the frame to paint this layer tree into.
ui.Size frameSize;
/// The devicePixelRatio of the frame to paint this layer tree into.
double devicePixelRatio;
/// Performs a preroll phase before painting the layer tree.
///
/// In this phase, the paint boundary for each layer is computed and
@ -19,8 +22,10 @@ class LayerTree {
/// to raster. If [ignoreRasterCache] is `true`, then there will be no
/// attempt to register pictures to cache.
void preroll(Frame frame, {bool ignoreRasterCache = false}) {
final PrerollContext context =
PrerollContext(ignoreRasterCache ? null : frame.rasterCache);
final PrerollContext context = PrerollContext(
ignoreRasterCache ? null : frame.rasterCache,
frame.viewEmbedder,
);
rootLayer.preroll(context, Matrix4.identity());
}
@ -29,8 +34,19 @@ class LayerTree {
/// If [ignoreRasterCache] is `true`, then the raster cache will
/// not be used.
void paint(Frame frame, {bool ignoreRasterCache = false}) {
final SkNWayCanvas internalNodesCanvas = SkNWayCanvas();
internalNodesCanvas.addCanvas(frame.canvas);
final List<SkCanvas> overlayCanvases =
frame.viewEmbedder.getCurrentCanvases();
for (int i = 0; i < overlayCanvases.length; i++) {
internalNodesCanvas.addCanvas(overlayCanvases[i]);
}
final PaintContext context = PaintContext(
frame.canvas, ignoreRasterCache ? null : frame.rasterCache);
internalNodesCanvas,
frame.canvas,
ignoreRasterCache ? null : frame.rasterCache,
frame.viewEmbedder,
);
if (rootLayer.needsPainting) {
rootLayer.paint(context);
}
@ -45,7 +61,10 @@ class Frame {
/// A cache of pre-rastered pictures.
final RasterCache rasterCache;
Frame(this.canvas, this.rasterCache);
/// The platform view embedder.
final HtmlViewEmbedder viewEmbedder;
Frame(this.canvas, this.rasterCache, this.viewEmbedder);
/// Rasterize the given layer tree into this frame.
bool raster(LayerTree layerTree, {bool ignoreRasterCache = false}) {
@ -61,7 +80,7 @@ class CompositorContext {
RasterCache rasterCache;
/// Acquire a frame using this compositor's settings.
Frame acquireFrame(SkCanvas canvas) {
return Frame(canvas, rasterCache);
Frame acquireFrame(SkCanvas canvas, HtmlViewEmbedder viewEmbedder) {
return Frame(canvas, rasterCache, viewEmbedder);
}
}

View File

@ -0,0 +1,86 @@
// 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.
part of engine;
/// A virtual canvas that applies operations to multiple canvases at once.
class SkNWayCanvas {
final List<SkCanvas> _canvases = <SkCanvas>[];
void addCanvas(SkCanvas canvas) {
_canvases.add(canvas);
}
/// Calls [save] on all canvases.
int save() {
int saveCount;
for (int i = 0; i < _canvases.length; i++) {
saveCount = _canvases[i].save();
}
return saveCount;
}
/// Calls [saveLayer] on all canvases.
void saveLayer(ui.Rect bounds, ui.Paint paint) {
for (int i = 0; i < _canvases.length; i++) {
_canvases[i].saveLayer(bounds, paint);
}
}
/// Calls [saveLayerWithFilter] on all canvases.
void saveLayerWithFilter(ui.Rect bounds, ui.ImageFilter filter) {
for (int i = 0; i < _canvases.length; i++) {
_canvases[i].saveLayerWithFilter(bounds, filter);
}
}
/// Calls [restore] on all canvases.
void restore() {
for (int i = 0; i < _canvases.length; i++) {
_canvases[i].restore();
}
}
/// Calls [restoreToCount] on all canvases.
void restoreToCount(int count) {
for (int i = 0; i < _canvases.length; i++) {
_canvases[i].restoreToCount(count);
}
}
/// Calls [translate] on all canvases.
void translate(double dx, double dy) {
for (int i = 0; i < _canvases.length; i++) {
_canvases[i].translate(dx, dy);
}
}
/// Calls [transform] on all canvases.
void transform(Float64List matrix) {
for (int i = 0; i < _canvases.length; i++) {
_canvases[i].transform(matrix);
}
}
/// Calls [clipPath] on all canvases.
void clipPath(ui.Path path, bool doAntiAlias) {
for (int i = 0; i < _canvases.length; i++) {
_canvases[i].clipPath(path, doAntiAlias);
}
}
/// Calls [clipRect] on all canvases.
void clipRect(ui.Rect rect, ui.ClipOp clipOp, bool doAntiAlias) {
for (int i = 0; i < _canvases.length; i++) {
_canvases[i].clipRect(rect, clipOp, doAntiAlias);
}
}
/// Calls [clipRRect] on all canvases.
void clipRRect(ui.RRect rrect, bool doAntiAlias) {
for (int i = 0; i < _canvases.length; i++) {
_canvases[i].clipRRect(rrect, doAntiAlias);
}
}
}

View File

@ -343,4 +343,8 @@ class SkPath implements ui.Path {
throw new UnimplementedError(
'webOnlySerializeToCssPaint is not used in the CanvasKit backend.');
}
String toSvgString() {
return _skPath.callMethod('toSVGString');
}
}

View File

@ -7,6 +7,7 @@ part of engine;
class SkPictureRecorder implements ui.PictureRecorder {
ui.Rect _cullRect;
js.JsObject _recorder;
SkCanvas _recordingCanvas;
SkCanvas beginRecording(ui.Rect bounds) {
_cullRect = bounds;
@ -15,9 +16,12 @@ class SkPictureRecorder implements ui.PictureRecorder {
<double>[bounds.left, bounds.top, bounds.right, bounds.bottom]);
final js.JsObject skCanvas =
_recorder.callMethod('beginRecording', <js.JsObject>[skRect]);
return SkCanvas(skCanvas);
_recordingCanvas = SkCanvas(skCanvas);
return _recordingCanvas;
}
SkCanvas get recordingCanvas => _recordingCanvas;
@override
ui.Picture endRecording() {
final js.JsObject skPicture =

View File

@ -8,8 +8,11 @@ part of engine;
class Rasterizer {
final Surface surface;
final CompositorContext context = CompositorContext();
final HtmlViewEmbedder viewEmbedder = HtmlViewEmbedder();
Rasterizer(this.surface);
Rasterizer(this.surface) {
surface.viewEmbedder = viewEmbedder;
}
/// Creates a new frame from this rasterizer's surface, draws the given
/// [LayerTree] into it, and then submits the frame.
@ -30,10 +33,13 @@ class Rasterizer {
layerTree.frameSize = frameSize;
final SurfaceFrame frame = surface.acquireFrame(layerTree.frameSize);
surface.viewEmbedder.frameSize = layerTree.frameSize;
final SkCanvas canvas = frame.skiaCanvas;
final Frame compositorFrame = context.acquireFrame(canvas);
final Frame compositorFrame = context.acquireFrame(canvas, surface.viewEmbedder);
compositorFrame.raster(layerTree, ignoreRasterCache: true);
surface.addToScene();
frame.submit();
surface.viewEmbedder.submitFrame();
}
}

View File

@ -34,29 +34,44 @@ class SurfaceFrame {
/// created.
class Surface {
SkSurface _surface;
html.Element htmlElement;
bool _addedToScene = false;
/// The default view embedder. Coordinates embedding platform views and
/// overlaying subsequent draw operations on top.
HtmlViewEmbedder viewEmbedder;
/// Acquire a frame of the given [size] containing a drawable canvas.
///
/// The given [size] is in physical pixels.
SurfaceFrame acquireFrame(ui.Size size) {
final SkSurface surface = _acquireRenderSurface(size);
final SkSurface surface = acquireRenderSurface(size);
if (surface == null) return null;
SubmitCallback submitCallback = (SurfaceFrame surfaceFrame, SkCanvas canvas) {
SubmitCallback submitCallback =
(SurfaceFrame surfaceFrame, SkCanvas canvas) {
_presentSurface(canvas);
};
return SurfaceFrame(surface, submitCallback);
}
SkSurface _acquireRenderSurface(ui.Size size) {
SkSurface acquireRenderSurface(ui.Size size) {
if (!_createOrUpdateSurfaces(size)) {
return null;
}
return _surface;
}
void addToScene() {
if (!_addedToScene) {
skiaSceneHost.children.insert(0, htmlElement);
}
_addedToScene = true;
}
bool _createOrUpdateSurfaces(ui.Size size) {
if (_surface != null &&
size ==
@ -69,6 +84,9 @@ class Surface {
_surface?.dispose();
_surface = null;
htmlElement?.remove();
htmlElement = null;
_addedToScene = false;
if (size.isEmpty) {
html.window.console.error('Cannot create surfaces of empty size.');
@ -86,25 +104,29 @@ class Surface {
SkSurface _wrapHtmlCanvas(ui.Size size) {
final ui.Size logicalSize = size / ui.window.devicePixelRatio;
final html.CanvasElement htmlCanvas =
html.CanvasElement(width: size.width.ceil(), height: size.height.ceil())
..id = 'flt-sk-canvas';
final html.CanvasElement htmlCanvas = html.CanvasElement(
width: size.width.ceil(), height: size.height.ceil());
htmlCanvas.style
..position = 'absolute'
..width = '${logicalSize.width.ceil()}px'
..height = '${logicalSize.height.ceil()}px';
final js.JsObject glContext = canvasKit
.callMethod('GetWebGLContext', <html.CanvasElement>[htmlCanvas]);
final js.JsObject grContext =
canvasKit.callMethod('MakeGrContext', <js.JsObject>[glContext]);
final js.JsObject skSurface =
canvasKit.callMethod('MakeWebGLCanvasSurface', <dynamic>[
htmlCanvas,
canvasKit.callMethod('MakeOnScreenGLSurface', <dynamic>[
grContext,
size.width,
size.height,
]);
htmlElement = htmlCanvas;
if (skSurface == null) {
return null;
} else {
domRenderer.renderScene(htmlCanvas);
return SkSurface(skSurface);
return SkSurface(skSurface, glContext);
}
}
@ -113,6 +135,7 @@ class Surface {
return false;
}
canvasKit.callMethod('setCurrentContext', <js.JsObject>[_surface.context]);
_surface.getCanvas().flush();
return true;
}
@ -121,14 +144,17 @@ class Surface {
/// A Dart wrapper around Skia's SkSurface.
class SkSurface {
final js.JsObject _surface;
final js.JsObject _glContext;
SkSurface(this._surface);
SkSurface(this._surface, this._glContext);
SkCanvas getCanvas() {
final js.JsObject skCanvas = _surface.callMethod('getCanvas');
return SkCanvas(skCanvas);
}
js.JsObject get context => _glContext;
int width() => _surface.callMethod('width');
int height() => _surface.callMethod('height');

View File

@ -6,7 +6,7 @@ part of engine;
/// A registry for factories that create platform views.
class PlatformViewRegistry {
final Map<String, PlatformViewFactory> _registeredFactories =
final Map<String, PlatformViewFactory> registeredFactories =
<String, PlatformViewFactory>{};
final Map<int, html.Element> _createdViews = <int, html.Element>{};
@ -16,10 +16,10 @@ class PlatformViewRegistry {
/// Register [viewTypeId] as being creating by the given [factory].
bool registerViewFactory(String viewTypeId, PlatformViewFactory factory) {
if (_registeredFactories.containsKey(viewTypeId)) {
if (registeredFactories.containsKey(viewTypeId)) {
return false;
}
_registeredFactories[viewTypeId] = factory;
registeredFactories[viewTypeId] = factory;
return true;
}
@ -65,7 +65,7 @@ void _createPlatformView(
const MethodCodec codec = StandardMethodCodec();
// TODO(het): Use 'direction', 'width', and 'height'.
if (!platformViewRegistry._registeredFactories.containsKey(viewType)) {
if (!platformViewRegistry.registeredFactories.containsKey(viewType)) {
callback(codec.encodeErrorEnvelope(
code: 'Unregistered factory',
message: "No factory registered for viewtype '$viewType'",
@ -74,7 +74,7 @@ void _createPlatformView(
}
// TODO(het): Use creation parameters.
final html.Element element =
platformViewRegistry._registeredFactories[viewType](id);
platformViewRegistry.registeredFactories[viewType](id);
platformViewRegistry._createdViews[id] = element;
callback(codec.encodeSuccessEnvelope(null));

View File

@ -159,7 +159,11 @@ class EngineWindow extends ui.Window {
return;
case 'flutter/platform_views':
handlePlatformViewCall(data, callback);
if (experimentalUseSkia) {
_rasterizer.viewEmbedder.handlePlatformViewCall(data, callback);
} else {
handlePlatformViewCall(data, callback);
}
return;
case 'flutter/accessibility':