From 977a30e52a01cb24a72fb6a10fdecb8cb5c80e67 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Mon, 16 Sep 2019 15:12:35 -0700 Subject: [PATCH 1/5] Improve the CanvasKit backend for Flutter Web - Improve font handling by trying to load a "normal" font face instead of using the first face matching the family. - Implement Vertices and drawVertices --- lib/web_ui/lib/src/engine.dart | 1 + .../lib/src/engine/compositor/fonts.dart | 32 +++- .../src/engine/compositor/initialization.dart | 2 +- .../engine/compositor/recording_canvas.dart | 11 ++ .../lib/src/engine/compositor/util.dart | 170 ++++++------------ .../lib/src/engine/compositor/vertices.dart | 134 ++++++++++++++ .../lib/src/engine/recording_canvas.dart | 4 + lib/web_ui/lib/src/ui/canvas.dart | 28 ++- 8 files changed, 258 insertions(+), 124 deletions(-) create mode 100644 lib/web_ui/lib/src/engine/compositor/vertices.dart diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index 896f3fe4ec6b2..a6f9acf329ac2 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -41,6 +41,7 @@ part 'engine/compositor/recording_canvas.dart'; part 'engine/compositor/runtime_delegate.dart'; part 'engine/compositor/surface.dart'; part 'engine/compositor/util.dart'; +part 'engine/compositor/vertices.dart'; part 'engine/compositor/viewport_metrics.dart'; part 'engine/conic.dart'; part 'engine/dom_canvas.dart'; diff --git a/lib/web_ui/lib/src/engine/compositor/fonts.dart b/lib/web_ui/lib/src/engine/compositor/fonts.dart index 0b63b4e5baf38..e30aa415fce11 100644 --- a/lib/web_ui/lib/src/engine/compositor/fonts.dart +++ b/lib/web_ui/lib/src/engine/compositor/fonts.dart @@ -73,13 +73,35 @@ class SkiaFontCollection { js.JsObject getFont(String family, double size) { if (_registeredTypefaces[family] == null) { - if (family == 'sans-serif') { - // If it's the default font, return a default SkFont - return js.JsObject(canvasKit['SkFont'], [null, size]); + if (assertionsEnabled) { + html.window.console.warn('Using unregistered font: $family'); } - throw Exception('Unregistered font: $family'); + return js.JsObject(canvasKit['SkFont'], [null, size]); } - final js.JsObject skTypeface = _registeredTypefaces[family].values.first; + //final js.JsObject skTypeface = _registeredTypefaces[family].values.first; + // We don't attempt to find a Typeface matching the text style. Instead, we + // try to find the "default" typeface. The default typeface either has no + // descriptors, or only has a descriptor of font-weight 400 (the default). + final Map, js.JsObject> typefaces = + _registeredTypefaces[family]; + js.JsObject skTypeface; + + for (MapEntry, js.JsObject> entry + in typefaces.entries) { + final Map descriptors = entry.key; + if (descriptors.isEmpty || + (descriptors.length == 1 && descriptors['weight'] == '400')) { + skTypeface = entry.value; + break; + } + } + + // If we couldn't find a suitable default, just use any typeface in the + // family. + if (skTypeface == null) { + skTypeface = typefaces.values.first; + } + return js.JsObject(canvasKit['SkFont'], [skTypeface, size]); } diff --git a/lib/web_ui/lib/src/engine/compositor/initialization.dart b/lib/web_ui/lib/src/engine/compositor/initialization.dart index 792db2ebcfcde..18efc0b5a2c7f 100644 --- a/lib/web_ui/lib/src/engine/compositor/initialization.dart +++ b/lib/web_ui/lib/src/engine/compositor/initialization.dart @@ -6,7 +6,7 @@ part of engine; /// EXPERIMENTAL: Enable the Skia-based rendering backend. const bool experimentalUseSkia = - bool.fromEnvironment('FLUTTER_WEB_USE_SKIA', defaultValue: false); + bool.fromEnvironment('FLUTTER_WEB_USE_SKIA', defaultValue: true); /// The URL to use when downloading the CanvasKit script and associated wasm. const String canvasKitBaseUrl = 'https://unpkg.com/canvaskit-wasm@0.6.0/bin/'; diff --git a/lib/web_ui/lib/src/engine/compositor/recording_canvas.dart b/lib/web_ui/lib/src/engine/compositor/recording_canvas.dart index a084c75ec3455..a09518629791f 100644 --- a/lib/web_ui/lib/src/engine/compositor/recording_canvas.dart +++ b/lib/web_ui/lib/src/engine/compositor/recording_canvas.dart @@ -220,6 +220,17 @@ class SkRecordingCanvas implements RecordingCanvas { drawSkShadow(skCanvas, path, color, elevation, transparentOccluder); } + @override + void drawVertices( + ui.Vertices vertices, ui.BlendMode blendMode, ui.Paint paint) { + SkVertices skVertices = vertices; + skCanvas.callMethod('drawVertices', [ + skVertices.skVertices, + makeSkBlendMode(blendMode), + makeSkPaint(paint) + ]); + } + @override bool get hasArbitraryPaint => true; diff --git a/lib/web_ui/lib/src/engine/compositor/util.dart b/lib/web_ui/lib/src/engine/compositor/util.dart index 3f6c2b365d224..c7457556e9e73 100644 --- a/lib/web_ui/lib/src/engine/compositor/util.dart +++ b/lib/web_ui/lib/src/engine/compositor/util.dart @@ -17,148 +17,96 @@ js.JsArray makeSkPoint(ui.Offset point) { return skPoint; } -js.JsObject makeSkPaint(ui.Paint paint) { - final dynamic skPaint = js.JsObject(canvasKit['SkPaint']); - - if (paint.shader != null) { - final EngineGradient engineShader = paint.shader; - skPaint.callMethod( - 'setShader', [engineShader.createSkiaShader()]); - } - - if (paint.color != null) { - skPaint.callMethod('setColor', [paint.color.value]); - } - - js.JsObject skPaintStyle; - switch (paint.style) { - case ui.PaintingStyle.stroke: - skPaintStyle = canvasKit['PaintStyle']['Stroke']; - break; - case ui.PaintingStyle.fill: - skPaintStyle = canvasKit['PaintStyle']['Fill']; - break; - } - skPaint.callMethod('setStyle', [skPaintStyle]); - - js.JsObject skBlendMode; - switch (paint.blendMode) { +js.JsObject makeSkBlendMode(ui.BlendMode blendMode) { + switch (blendMode) { case ui.BlendMode.clear: - skBlendMode = canvasKit['BlendMode']['Clear']; - break; - + return canvasKit['BlendMode']['Clear']; case ui.BlendMode.src: - skBlendMode = canvasKit['BlendMode']['Src']; - break; - + return canvasKit['BlendMode']['Src']; case ui.BlendMode.dst: - skBlendMode = canvasKit['BlendMode']['Dst']; - break; - + return canvasKit['BlendMode']['Dst']; case ui.BlendMode.srcOver: - skBlendMode = canvasKit['BlendMode']['SrcOver']; - break; - + return canvasKit['BlendMode']['SrcOver']; case ui.BlendMode.dstOver: - skBlendMode = canvasKit['BlendMode']['DstOver']; - break; - + return canvasKit['BlendMode']['DstOver']; case ui.BlendMode.srcIn: - skBlendMode = canvasKit['BlendMode']['SrcIn']; - break; - + return canvasKit['BlendMode']['SrcIn']; case ui.BlendMode.dstIn: - skBlendMode = canvasKit['BlendMode']['DstIn']; - break; - + return canvasKit['BlendMode']['DstIn']; case ui.BlendMode.srcOut: - skBlendMode = canvasKit['BlendMode']['SrcOut']; - break; - + return canvasKit['BlendMode']['SrcOut']; case ui.BlendMode.dstOut: - skBlendMode = canvasKit['BlendMode']['DstOut']; - break; - + return canvasKit['BlendMode']['DstOut']; case ui.BlendMode.srcATop: - skBlendMode = canvasKit['BlendMode']['SrcATop']; - break; - + return canvasKit['BlendMode']['SrcATop']; case ui.BlendMode.dstATop: - skBlendMode = canvasKit['BlendMode']['DstATop']; - break; - + return canvasKit['BlendMode']['DstATop']; case ui.BlendMode.xor: - skBlendMode = canvasKit['BlendMode']['Xor']; - break; - + return canvasKit['BlendMode']['Xor']; case ui.BlendMode.plus: - skBlendMode = canvasKit['BlendMode']['Plus']; - break; - + return canvasKit['BlendMode']['Plus']; case ui.BlendMode.modulate: - skBlendMode = canvasKit['BlendMode']['Modulate']; - break; - + return canvasKit['BlendMode']['Modulate']; case ui.BlendMode.screen: - skBlendMode = canvasKit['BlendMode']['Screen']; - break; - + return canvasKit['BlendMode']['Screen']; case ui.BlendMode.overlay: - skBlendMode = canvasKit['BlendMode']['Overlay']; - break; - + return canvasKit['BlendMode']['Overlay']; case ui.BlendMode.darken: - skBlendMode = canvasKit['BlendMode']['Darken']; - break; - + return canvasKit['BlendMode']['Darken']; case ui.BlendMode.lighten: - skBlendMode = canvasKit['BlendMode']['Lighten']; - break; - + return canvasKit['BlendMode']['Lighten']; case ui.BlendMode.colorDodge: - skBlendMode = canvasKit['BlendMode']['ColorDodge']; - break; - + return canvasKit['BlendMode']['ColorDodge']; case ui.BlendMode.colorBurn: - skBlendMode = canvasKit['BlendMode']['ColorBurn']; - break; - + return canvasKit['BlendMode']['ColorBurn']; case ui.BlendMode.hardLight: - skBlendMode = canvasKit['BlendMode']['HardLight']; - break; - + return canvasKit['BlendMode']['HardLight']; case ui.BlendMode.softLight: - skBlendMode = canvasKit['BlendMode']['SoftLight']; - break; - + return canvasKit['BlendMode']['SoftLight']; case ui.BlendMode.difference: - skBlendMode = canvasKit['BlendMode']['Difference']; - break; - + return canvasKit['BlendMode']['Difference']; case ui.BlendMode.exclusion: - skBlendMode = canvasKit['BlendMode']['Exclusion']; - break; - + return canvasKit['BlendMode']['Exclusion']; case ui.BlendMode.multiply: - skBlendMode = canvasKit['BlendMode']['Multiply']; - break; - + return canvasKit['BlendMode']['Multiply']; case ui.BlendMode.hue: - skBlendMode = canvasKit['BlendMode']['Hue']; - break; - + return canvasKit['BlendMode']['Hue']; case ui.BlendMode.saturation: - skBlendMode = canvasKit['BlendMode']['Saturation']; - break; - + return canvasKit['BlendMode']['Saturation']; case ui.BlendMode.color: - skBlendMode = canvasKit['BlendMode']['Color']; - break; - + return canvasKit['BlendMode']['Color']; case ui.BlendMode.luminosity: - skBlendMode = canvasKit['BlendMode']['Luminosity']; + return canvasKit['BlendMode']['Luminosity']; + default: + return null; + } +} + +js.JsObject makeSkPaint(ui.Paint paint) { + final dynamic skPaint = js.JsObject(canvasKit['SkPaint']); + + if (paint.shader != null) { + final EngineGradient engineShader = paint.shader; + skPaint.callMethod( + 'setShader', [engineShader.createSkiaShader()]); + } + + if (paint.color != null) { + skPaint.callMethod('setColor', [paint.color.value]); + } + + js.JsObject skPaintStyle; + switch (paint.style) { + case ui.PaintingStyle.stroke: + skPaintStyle = canvasKit['PaintStyle']['Stroke']; + break; + case ui.PaintingStyle.fill: + skPaintStyle = canvasKit['PaintStyle']['Fill']; break; } + skPaint.callMethod('setStyle', [skPaintStyle]); + + js.JsObject skBlendMode = makeSkBlendMode(paint.blendMode); if (skBlendMode != null) { skPaint.callMethod('setBlendMode', [skBlendMode]); } diff --git a/lib/web_ui/lib/src/engine/compositor/vertices.dart b/lib/web_ui/lib/src/engine/compositor/vertices.dart new file mode 100644 index 0000000000000..af2b7f47fde32 --- /dev/null +++ b/lib/web_ui/lib/src/engine/compositor/vertices.dart @@ -0,0 +1,134 @@ +part of engine; + +Int32List _encodeColorList(List colors) { + final int colorCount = colors.length; + final Int32List result = Int32List(colorCount); + for (int i = 0; i < colorCount; ++i) result[i] = colors[i].value; + return result; +} + +Float32List _encodePointList(List points) { + assert(points != null); + final int pointCount = points.length; + final Float32List result = Float32List(pointCount * 2); + for (int i = 0; i < pointCount; ++i) { + final int xIndex = i * 2; + final int yIndex = xIndex + 1; + final ui.Offset point = points[i]; + assert(_offsetIsValid(point)); + result[xIndex] = point.dx; + result[yIndex] = point.dy; + } + return result; +} + +class SkVertices implements ui.Vertices { + js.JsObject skVertices; + + SkVertices( + ui.VertexMode mode, + List positions, { + List textureCoordinates, + List colors, + List indices, + }) : assert(mode != null), + assert(positions != null) { + if (textureCoordinates != null && + textureCoordinates.length != positions.length) + throw ArgumentError( + '"positions" and "textureCoordinates" lengths must match.'); + if (colors != null && colors.length != positions.length) + throw ArgumentError('"positions" and "colors" lengths must match.'); + if (indices != null && + indices.any((int i) => i < 0 || i >= positions.length)) + throw ArgumentError( + '"indices" values must be valid indices in the positions list.'); + + final Float32List encodedPositions = _encodePointList(positions); + final Float32List encodedTextureCoordinates = (textureCoordinates != null) + ? _encodePointList(textureCoordinates) + : null; + final Int32List encodedColors = + colors != null ? _encodeColorList(colors) : null; + final Uint16List encodedIndices = + indices != null ? Uint16List.fromList(indices) : null; + + if (!_init(mode, encodedPositions, encodedTextureCoordinates, encodedColors, + encodedIndices)) + throw ArgumentError('Invalid configuration for vertices.'); + } + + SkVertices.raw( + ui.VertexMode mode, + Float32List positions, { + Float32List textureCoordinates, + Int32List colors, + Uint16List indices, + }) : assert(mode != null), + assert(positions != null) { + if (textureCoordinates != null && + textureCoordinates.length != positions.length) + throw ArgumentError( + '"positions" and "textureCoordinates" lengths must match.'); + if (colors != null && colors.length * 2 != positions.length) + throw ArgumentError('"positions" and "colors" lengths must match.'); + if (indices != null && + indices.any((int i) => i < 0 || i >= positions.length)) + throw ArgumentError( + '"indices" values must be valid indices in the positions list.'); + + if (!_init(mode, positions, textureCoordinates, colors, indices)) + throw ArgumentError('Invalid configuration for vertices.'); + } + + bool _init(ui.VertexMode mode, Float32List positions, + Float32List textureCoordinates, Int32List colors, Uint16List indices) { + js.JsObject skVertexMode; + switch (mode) { + case ui.VertexMode.triangles: + skVertexMode = canvasKit['VertexMode']['Triangles']; + break; + case ui.VertexMode.triangleStrip: + skVertexMode = canvasKit['VertexMode']['TrianglesStrip']; + break; + case ui.VertexMode.triangleFan: + skVertexMode = canvasKit['VertexMode']['TriangleFan']; + break; + } + + js.JsArray> encodedPositions = + js.JsArray>(); + encodedPositions.length = positions.length ~/ 2; + for (int i = 0; i < positions.length; i += 2) {} + + final js.JsObject vertices = + canvasKit.callMethod('MakeSkVertices', [ + skVertexMode, + _encodePoints(positions), + _encodePoints(textureCoordinates), + colors, + null, + null, + indices, + ]); + + if (vertices != null) { + skVertices = vertices; + return true; + } else { + return false; + } + } + + static _encodePoints(List points) { + if (points == null) return null; + + js.JsArray> encodedPoints = + js.JsArray>(); + encodedPoints.length = points.length ~/ 2; + for (int i = 0; i < points.length; i += 2) { + encodedPoints[i ~/ 2] = makeSkPoint(ui.Offset(points[i], points[i + 1])); + } + return encodedPoints; + } +} diff --git a/lib/web_ui/lib/src/engine/recording_canvas.dart b/lib/web_ui/lib/src/engine/recording_canvas.dart index 021ed6262e2bf..d58523d3523b2 100644 --- a/lib/web_ui/lib/src/engine/recording_canvas.dart +++ b/lib/web_ui/lib/src/engine/recording_canvas.dart @@ -325,6 +325,10 @@ class RecordingCanvas { _commands.add(PaintDrawShadow(path, color, elevation, transparentOccluder)); } + void drawVertices(ui.Vertices vertices, ui.BlendMode blendMode, ui.Paint paint) { + throw new UnimplementedError(); + } + int saveCount = 1; /// Prints the commands recorded by this canvas to the console. diff --git a/lib/web_ui/lib/src/ui/canvas.dart b/lib/web_ui/lib/src/ui/canvas.dart index d14bbc0bb5ce0..aec846a4cfc4f 100644 --- a/lib/web_ui/lib/src/ui/canvas.dart +++ b/lib/web_ui/lib/src/ui/canvas.dart @@ -59,23 +59,37 @@ enum VertexMode { /// A set of vertex data used by [Canvas.drawVertices]. class Vertices { - Vertices( + factory Vertices( VertexMode mode, List positions, { List textureCoordinates, List colors, List indices, - }) : assert(mode != null), - assert(positions != null); + }) { + if (engine.experimentalUseSkia) { + return engine.SkVertices(mode, positions, + textureCoordinates: textureCoordinates, + colors: colors, + indices: indices); + } + return null; + } - Vertices.raw( + factory Vertices.raw( VertexMode mode, Float32List positions, { Float32List textureCoordinates, Int32List colors, Uint16List indices, - }) : assert(mode != null), - assert(positions != null); + }) { + if (engine.experimentalUseSkia) { + return engine.SkVertices.raw(mode, positions, + textureCoordinates: textureCoordinates, + colors: colors, + indices: indices); + } + return null; + } } /// Records a [Picture] containing a sequence of graphical operations. @@ -877,7 +891,7 @@ class Canvas { assert(vertices != null); // vertices is checked on the engine side assert(paint != null); assert(blendMode != null); - throw UnimplementedError(); + _canvas.drawVertices(vertices, blendMode, paint); } // From fa62ed0a744e42f77f36f015538ffe3d3a85ad71 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Tue, 17 Sep 2019 10:23:43 -0700 Subject: [PATCH 2/5] Add license header to vertices.dart --- lib/web_ui/lib/src/engine/compositor/vertices.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/web_ui/lib/src/engine/compositor/vertices.dart b/lib/web_ui/lib/src/engine/compositor/vertices.dart index af2b7f47fde32..772c2f9f7aa13 100644 --- a/lib/web_ui/lib/src/engine/compositor/vertices.dart +++ b/lib/web_ui/lib/src/engine/compositor/vertices.dart @@ -1,3 +1,7 @@ +// 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; Int32List _encodeColorList(List colors) { From 992b679a4f35a1e124cae709d69ba255366b6888 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Tue, 17 Sep 2019 10:28:55 -0700 Subject: [PATCH 3/5] Remove unused 'encodedPositions' --- lib/web_ui/lib/src/engine/compositor/vertices.dart | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/web_ui/lib/src/engine/compositor/vertices.dart b/lib/web_ui/lib/src/engine/compositor/vertices.dart index 772c2f9f7aa13..9644bfbef25eb 100644 --- a/lib/web_ui/lib/src/engine/compositor/vertices.dart +++ b/lib/web_ui/lib/src/engine/compositor/vertices.dart @@ -100,11 +100,6 @@ class SkVertices implements ui.Vertices { break; } - js.JsArray> encodedPositions = - js.JsArray>(); - encodedPositions.length = positions.length ~/ 2; - for (int i = 0; i < positions.length; i += 2) {} - final js.JsObject vertices = canvasKit.callMethod('MakeSkVertices', [ skVertexMode, From 510e38201033b9e339c4b0fdda00be5385807c4e Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Tue, 17 Sep 2019 13:16:33 -0700 Subject: [PATCH 4/5] Delete commented old code. Don't use Skia by default --- lib/web_ui/lib/src/engine/compositor/fonts.dart | 2 +- lib/web_ui/lib/src/engine/compositor/initialization.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/web_ui/lib/src/engine/compositor/fonts.dart b/lib/web_ui/lib/src/engine/compositor/fonts.dart index e30aa415fce11..45707f61de2ed 100644 --- a/lib/web_ui/lib/src/engine/compositor/fonts.dart +++ b/lib/web_ui/lib/src/engine/compositor/fonts.dart @@ -78,7 +78,7 @@ class SkiaFontCollection { } return js.JsObject(canvasKit['SkFont'], [null, size]); } - //final js.JsObject skTypeface = _registeredTypefaces[family].values.first; + // We don't attempt to find a Typeface matching the text style. Instead, we // try to find the "default" typeface. The default typeface either has no // descriptors, or only has a descriptor of font-weight 400 (the default). diff --git a/lib/web_ui/lib/src/engine/compositor/initialization.dart b/lib/web_ui/lib/src/engine/compositor/initialization.dart index 18efc0b5a2c7f..792db2ebcfcde 100644 --- a/lib/web_ui/lib/src/engine/compositor/initialization.dart +++ b/lib/web_ui/lib/src/engine/compositor/initialization.dart @@ -6,7 +6,7 @@ part of engine; /// EXPERIMENTAL: Enable the Skia-based rendering backend. const bool experimentalUseSkia = - bool.fromEnvironment('FLUTTER_WEB_USE_SKIA', defaultValue: true); + bool.fromEnvironment('FLUTTER_WEB_USE_SKIA', defaultValue: false); /// The URL to use when downloading the CanvasKit script and associated wasm. const String canvasKitBaseUrl = 'https://unpkg.com/canvaskit-wasm@0.6.0/bin/'; From ea5cf413edf380b4ff4f251bd73ca87218a4dc81 Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Tue, 17 Sep 2019 13:42:31 -0700 Subject: [PATCH 5/5] Add `vertices.dart` to licenses file --- ci/licenses_golden/licenses_flutter | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 5d04beaa2c0fe..fb2d5652d0f17 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -371,6 +371,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/recording_canvas.dar FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/runtime_delegate.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/surface.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/util.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/vertices.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/viewport_metrics.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/conic.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/dom_canvas.dart