mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
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:
parent
392be60370
commit
db352ddd25
@ -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
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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'];
|
||||
|
||||
@ -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);
|
||||
}
|
||||
@ -58,6 +58,7 @@ class Engine extends RuntimeDelegate {
|
||||
}
|
||||
|
||||
layerTree.frameSize = frameSize;
|
||||
layerTree.devicePixelRatio = _viewportMetrics.devicePixelRatio;
|
||||
_animator.render(layerTree);
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 =
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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');
|
||||
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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':
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user