From 115a44dca9a88f2ac6e64d38da477eeac60237b7 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Fri, 4 Oct 2019 16:29:27 -0700 Subject: [PATCH 01/15] [web] Implement drawVertices for BlendMode.src and hairline rendering --- lib/web_ui/lib/src/engine/bitmap_canvas.dart | 311 ++++++++++++++++++ .../lib/src/engine/compositor/vertices.dart | 67 ++-- lib/web_ui/lib/src/engine/dom_canvas.dart | 6 + lib/web_ui/lib/src/engine/engine_canvas.dart | 3 + lib/web_ui/lib/src/engine/houdini_canvas.dart | 6 + .../lib/src/engine/recording_canvas.dart | 51 ++- lib/web_ui/lib/src/ui/canvas.dart | 82 ++++- lib/web_ui/test/mock_engine_canvas.dart | 10 + 8 files changed, 499 insertions(+), 37 deletions(-) diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index 381398b7396a6..88aec6a615ba0 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -850,6 +850,203 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { picture.recordingCanvas.apply(this); } + static const _vertexShaderTriangle = ''' + #version 300 es + layout (location=0) in vec4 position; + layout (location=1) in vec4 color; + out vec4 vColor; + void main() { + gl_Position = position; + vColor = color; + }'''; + static const _fragmentShaderTriangle = ''' + #version 300 es + precision highp float; + in vec4 vColor; + out vec4 fragColor; + void main() { + fragColor = vec4(vColor[2], vColor[1], vColor[0], vColor[3]); + }'''; + + /// Draws vertices on a gl context. + /// + /// If both colors and textures is specified in paint data, + /// for [BlendMode.source] we skip colors and use textures, + /// for [BlendMode.dst] we only use colors and ignore textures. + /// We also skip paint shader when no texture is specified. + /// + /// If no colors or textures are specified, stroke hairlines with + /// [Paint.color]. + /// + /// If colors is specified, convert colors to premultiplied (alpha) colors + /// and use a SkTriColorShader to render. + @override + void drawVertices(ui.Vertices vertices, ui.BlendMode blendMode, + ui.PaintData paint) { + + final Int32List colors = vertices.colors; + if (colors == null) { + // Draw hairline for vertices if no vertex colors are specified. + _drawHairline(vertices, paint.color ?? ui.Color(0xFF000000)); + throw UnimplementedError(); + //return; + } + + final html.CanvasElement glCanvas = html.CanvasElement( + width: _widthInBitmapPixels, + height: _heightInBitmapPixels, + ); + glCanvas.style + ..position = 'absolute' + ..width = _canvas.style.width + ..height = _canvas.style.height; + glCanvas.className = 'gl-canvas'; + + _children.add(glCanvas); + rootElement.append(glCanvas); + + _GlContext gl = _GlContext(glCanvas); + + //gl.viewport(0, 0, _widthInBitmapPixels.toDouble(), _heightInBitmapPixels.toDouble()); + // Create and compile shaders. + Object vertexShader = gl.compileShader('VERTEX_SHADER', _vertexShaderTriangle); + Object fragmentShader = gl.compileShader('FRAGMENT_SHADER', _fragmentShaderTriangle); + // Create a gl program and link shaders. + Object program = gl.createProgram(); + gl.attachShader(program, vertexShader); + gl.attachShader(program, fragmentShader); + gl.linkProgram(program); + gl.useProgram(program); + + // Setup geometry. + Object positionsBuffer = gl.createBuffer(); + assert(positionsBuffer != null); + gl.bindArrayBuffer(positionsBuffer); + + int positionCount = vertices.positions.length; + Float32List scaledList = Float32List(3 * positionCount ~/ 2); + for (int i = 0, destIndex = 0; i < positionCount; i += 2, destIndex += 3) { + // Scale. + scaledList[destIndex] = + ((vertices.positions[i]) / (_widthInBitmapPixels / 2)) - 1; + // Scale + invert axis. + scaledList[destIndex + 1] = + -(vertices.positions[i + 1] / (_heightInBitmapPixels / 2)) + 1;// + (_heightInBitmapPixels / 2) / _heightInBitmapPixels; + // Set depth to 0. + scaledList[destIndex + 2] = 0.0; + } + gl.bufferData(scaledList, gl.STATIC_DRAW); + js_util.callMethod(gl.glContext, 'vertexAttribPointer', [0, 3, gl.FLOAT, false, 0 , 0]); + gl.enableVertexAttribArray(0); + + // Setup color buffer. + Object colorsBuffer = gl.createBuffer(); + gl.bindArrayBuffer(colorsBuffer); + // Buffer kBGRA_8888. + gl.bufferData(colors, gl.STATIC_DRAW); + + js_util.callMethod(gl.glContext, 'vertexAttribPointer', [1, 4, gl.UNSIGNED_BYTE, true, 0 , 0]); + gl.enableVertexAttribArray(1); + gl.clear(); + gl.drawTriangles(positionCount ~/ 2); + } + + void _drawHairline(ui.Vertices vertices, ui.Color color) { + html.CanvasRenderingContext2D _ctx = ctx; + save(); + final Float32List positions = vertices.positions; + final int pointCount = positions.length ~/ 2; + _setFillAndStrokeStyle('', color.toCssString()); + _ctx.lineWidth = 1.0; + int triIndex = 0; + _ctx.beginPath(); + for (int i = 0, len = pointCount * 2; i < len; i += 2) { + final double dx = positions[i]; + final double dy = positions[i + 1]; + if (triIndex & 3 == 0) { + _ctx.moveTo(dx, dy); + } else { + _ctx.lineTo(dx, dy); + } + if (triIndex & 3 == 2) { + _ctx.closePath(); + _ctx.stroke(); + triIndex = 0; + } else { + triIndex++; + } + } + restore(); + } + + ui.Path _verticesToPath(ui.Vertices vertices) { + final Float32List positions = vertices.positions; + final ui.Path path = ui.Path(); + final int vertexCount = positions.length ~/ 2; + switch (vertices.mode) { + case ui.VertexMode.triangles: + for (int i = 0, len = vertexCount * 2; i < len;) { + for (int triangleIndex = 0; triangleIndex < 3; ++triangleIndex, + i += 2) { + final double dx = positions[i]; + final double dy = positions[i + 1]; + switch (triangleIndex) { + case 0: + path.moveTo(dx, dy); + break; + case 1: + path.lineTo(dx, dy); + break; + case 2: + path.lineTo(dx, dy); + path.close(); + break; + } + } + } + break; + case ui.VertexMode.triangleFan: + // Set of triangles connected to a central point (first vertex). + // Each point defines new triangle connected to prior vertex. + final double centerX = positions[0]; + final double centerY = positions[1]; + for (int i = 2, len = vertexCount * 2; i < len; i += 2) { + final double dx1 = positions[i]; + final double dy1 = positions[i + 1]; + final double dx2 = positions[i + 2]; + final double dy2 = positions[i + 3]; + path.moveTo(centerX, centerY); + path.lineTo(dx1, dy1); + path.lineTo(dx2, dy2); + path.close(); + } + break; + case ui.VertexMode.triangleStrip: + // Set of connected triangles. Each triangle shares 2 last vertices. + int triangleCount = vertexCount - 2; + double x0 = positions[0]; + double y0 = positions[1]; + double x1 = positions[2]; + double y1 = positions[3]; + double x2 = positions[4]; + double y2 = positions[5]; + for (int i = 0, positionIndex = 6; i < triangleCount; i++, positionIndex += 2) { + path.moveTo(x0, y0); + path.lineTo(x1, y1); + path.lineTo(x2, y2); + path.close(); + x0 = x1; + y0 = y1; + x1 = x2; + y1 = y2; + x2 = positions[positionIndex]; + y2 = positions[positionIndex + 1]; + } + break; + } + return path; + } + /// 'Runs' the given [path] by applying all of its commands to the canvas. void _runPath(ui.Path path) { ctx.beginPath(); @@ -1087,3 +1284,117 @@ String _cssTransformAtOffset( return matrix4ToCssTransform( transformWithOffset(transform, ui.Offset(offsetX, offsetY))); } + +/// JS Interop wrapper around webgl apis. +class _GlContext { + final Object glContext; + dynamic _compileStatus; + dynamic _array_buffer; + dynamic _static_draw; + dynamic _float; + dynamic _color_buffer_bit; + dynamic _triangles; + dynamic _link_status; + dynamic _unsigned_byte; + + _GlContext(html.CanvasElement canvas) + : glContext = canvas.getContext('webgl2'); + + Object compileShader(String shaderType, String source) { + Object shader = js_util.callMethod(glContext, 'createShader', + [js_util.getProperty(glContext, shaderType)]); + js_util.callMethod(glContext, 'shaderSource', [shader, source]); + js_util.callMethod(glContext,'compileShader', [shader]); + bool shaderStatus = js_util.callMethod(glContext,'getShaderParameter', + [shader, compileStatus]); + if (!shaderStatus) { + throw Exception('Shader compilation failed'); + } + return shader; + } + + Object createProgram() => + js_util.callMethod(glContext, 'createProgram', const []); + + void attachShader(Object program, Object shader) { + js_util.callMethod(glContext, 'attachShader', [program, shader]); + } + + void linkProgram(Object program) { + js_util.callMethod(glContext, 'linkProgram', [program]); + if (!js_util.callMethod(glContext,'getProgramParameter', + [program, LINK_STATUS])) { + throw Exception(_getProgramInfoLog(program)); + } + } + + void useProgram(Object program) { + js_util.callMethod(glContext, 'useProgram', [program]); + } + + Object createBuffer() => + js_util.callMethod(glContext, 'createBuffer', const []); + + void bindArrayBuffer(Object buffer) { + js_util.callMethod(glContext, 'bindBuffer', [ARRAY_BUFFER , buffer]); + } + + void bufferData(TypedData data, dynamic type) { + js_util.callMethod(glContext, 'bufferData', + [ARRAY_BUFFER, data, type]); + } + + void enableVertexAttribArray(int index) { + js_util.callMethod(glContext, 'enableVertexAttribArray', [index]); + } + + // Fill background. + void clear() { + js_util.callMethod(glContext, 'clear', [COLOR_BUFFER_BIT]); + } + + void drawTriangles(int triangleCount) { + js_util.callMethod(glContext, 'drawArrays', [TRIANGLES, 0, triangleCount]); + } + + void viewport(double x, double y, double width, double height) { + js_util.callMethod(glContext, 'viewport', [x, y, width, height]); + } + + void setupBlendMode(ui.BlendMode blendMode) { + assert(blendMode != ui.BlendMode.src, + 'No need to setup default blend mode'); + assert(blendMode != ui.BlendMode.dst); + + } + + dynamic get error => js_util.callMethod(glContext, 'getError', const []); + + dynamic get ARRAY_BUFFER => + _array_buffer ??= js_util.getProperty(glContext, 'ARRAY_BUFFER'); + + dynamic get LINK_STATUS => + _link_status ??= js_util.getProperty(glContext, 'LINK_STATUS'); + + dynamic get FLOAT => + _float ??= js_util.getProperty(glContext, 'FLOAT'); + + dynamic get UNSIGNED_BYTE => + _unsigned_byte ??= js_util.getProperty(glContext,'UNSIGNED_BYTE'); + + dynamic get STATIC_DRAW => + _static_draw ??= js_util.getProperty(glContext, 'STATIC_DRAW'); + + dynamic get TRIANGLES => + _triangles ??= js_util.getProperty(glContext, 'TRIANGLES'); + + dynamic get COLOR_BUFFER_BIT => + _color_buffer_bit ??= js_util.getProperty(glContext, 'COLOR_BUFFER_BIT'); + + dynamic get compileStatus => + _compileStatus ??= js_util.getProperty(glContext, 'COMPILE_STATUS'); + + String _getProgramInfoLog(Object glProgram) { + return js_util.callMethod(glContext, 'getProgramInfoLog', [glProgram]); + } +} diff --git a/lib/web_ui/lib/src/engine/compositor/vertices.dart b/lib/web_ui/lib/src/engine/compositor/vertices.dart index 9644bfbef25eb..0a1e84f0f572b 100644 --- a/lib/web_ui/lib/src/engine/compositor/vertices.dart +++ b/lib/web_ui/lib/src/engine/compositor/vertices.dart @@ -28,15 +28,19 @@ Float32List _encodePointList(List points) { class SkVertices implements ui.Vertices { js.JsObject skVertices; + final Int32List _colors; + final Float32List _positions; - SkVertices( - ui.VertexMode mode, - List positions, { - List textureCoordinates, - List colors, - List indices, - }) : assert(mode != null), - assert(positions != null) { + SkVertices(ui.VertexMode mode, + List positions, { + List textureCoordinates, + List colors, + List indices, + }) + : assert(mode != null), + assert(positions != null), + _colors = Int32List.fromList(colors.map((ui.Color c) => c.value)), + _positions = _offsetListToInt32List(positions) { if (textureCoordinates != null && textureCoordinates.length != positions.length) throw ArgumentError( @@ -53,23 +57,25 @@ class SkVertices implements ui.Vertices { ? _encodePointList(textureCoordinates) : null; final Int32List encodedColors = - colors != null ? _encodeColorList(colors) : null; + colors != null ? _encodeColorList(colors) : null; final Uint16List encodedIndices = - indices != null ? Uint16List.fromList(indices) : null; + 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) { + SkVertices.raw(ui.VertexMode mode, + Float32List positions, { + Float32List textureCoordinates, + Int32List colors, + Uint16List indices, + }) + : assert(mode != null), + assert(positions != null), + _colors = colors, + _positions = positions { if (textureCoordinates != null && textureCoordinates.length != positions.length) throw ArgumentError( @@ -101,7 +107,7 @@ class SkVertices implements ui.Vertices { } final js.JsObject vertices = - canvasKit.callMethod('MakeSkVertices', [ + canvasKit.callMethod('MakeSkVertices', [ skVertexMode, _encodePoints(positions), _encodePoints(textureCoordinates), @@ -123,11 +129,30 @@ class SkVertices implements ui.Vertices { if (points == null) return null; js.JsArray> encodedPoints = - js.JsArray>(); + 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; } -} + + static Float32List _offsetListToInt32List(List offsetList) { + if (offsetList == null) { + return null; + } + final int length = offsetList.length; + final floatList = Float32List(length * 2); + for (int i = 0, destIndex = 0; i < length; i++, destIndex += 2) { + floatList[destIndex] = offsetList[i].dx; + floatList[destIndex + 1] = offsetList[i].dx; + } + return floatList; + } + + @override + Int32List get colors => _colors; + + @override + Float32List get positions => _positions; +} \ No newline at end of file diff --git a/lib/web_ui/lib/src/engine/dom_canvas.dart b/lib/web_ui/lib/src/engine/dom_canvas.dart index 93df70a24b5f9..5d207f5a28b39 100644 --- a/lib/web_ui/lib/src/engine/dom_canvas.dart +++ b/lib/web_ui/lib/src/engine/dom_canvas.dart @@ -173,4 +173,10 @@ class DomCanvas extends EngineCanvas with SaveElementStackTracking { _drawParagraphElement(paragraph, offset, transform: currentTransform); currentElement.append(paragraphElement); } + + @override + void drawVertices(ui.Vertices vertices, ui.BlendMode blendMode, + ui.PaintData paint) { + throw UnimplementedError(); + } } diff --git a/lib/web_ui/lib/src/engine/engine_canvas.dart b/lib/web_ui/lib/src/engine/engine_canvas.dart index 66854a3d597cf..e7c37ca0a79a6 100644 --- a/lib/web_ui/lib/src/engine/engine_canvas.dart +++ b/lib/web_ui/lib/src/engine/engine_canvas.dart @@ -65,6 +65,9 @@ abstract class EngineCanvas { ui.Image image, ui.Rect src, ui.Rect dst, ui.PaintData paint); void drawParagraph(EngineParagraph paragraph, ui.Offset offset); + + void drawVertices(ui.Vertices vertices, ui.BlendMode blendMode, + ui.PaintData paint); } /// Adds an [offset] transformation to a [transform] matrix and returns the diff --git a/lib/web_ui/lib/src/engine/houdini_canvas.dart b/lib/web_ui/lib/src/engine/houdini_canvas.dart index 53e3fd9275f88..2eb83b9c76269 100644 --- a/lib/web_ui/lib/src/engine/houdini_canvas.dart +++ b/lib/web_ui/lib/src/engine/houdini_canvas.dart @@ -227,6 +227,12 @@ class HoudiniCanvas extends EngineCanvas with SaveElementStackTracking { _drawParagraphElement(paragraph, offset, transform: currentTransform); currentElement.append(paragraphElement); } + + @override + void drawVertices(ui.Vertices vertices, ui.BlendMode blendMode, + ui.PaintData paint) { + // TODO(flutter_web): implement. + } } class _SaveElementStackEntry { diff --git a/lib/web_ui/lib/src/engine/recording_canvas.dart b/lib/web_ui/lib/src/engine/recording_canvas.dart index 2608a6e30de62..836efbffc69bb 100644 --- a/lib/web_ui/lib/src/engine/recording_canvas.dart +++ b/lib/web_ui/lib/src/engine/recording_canvas.dart @@ -356,8 +356,29 @@ class RecordingCanvas { _commands.add(PaintDrawShadow(path, color, elevation, transparentOccluder)); } - void drawVertices(ui.Vertices vertices, ui.BlendMode blendMode, ui.Paint paint) { - throw new UnimplementedError(); + void drawVertices(ui.Vertices vertices, ui.BlendMode blendMode, + ui.Paint paint) { + _hasArbitraryPaint = true; + _didDraw = true; + final Float32List positions = vertices.positions; + double minValueX, maxValueX, minValueY, maxValueY; + minValueX = maxValueX = positions[0]; + minValueY = maxValueY = positions[1]; + for (int i = 2, len = positions.length; i < len; i += 2) { + final double x = positions[i]; + final double y = positions[i + 1]; + if (x.isNaN || y.isNaN) { + // Follows skia implementation that sets bounds to empty + // and aborts. + return; + } + minValueX = math.min(minValueX, x); + maxValueX = math.max(maxValueX, x); + minValueY = math.min(minValueY, y); + maxValueY = math.max(maxValueY, y); + } + _paintBounds.growLTRB(minValueX, minValueY, maxValueX, maxValueY); + _commands.add(PaintVertices(vertices, blendMode, paint.webOnlyPaintData)); } int saveCount = 1; @@ -715,6 +736,32 @@ class PaintDrawPaint extends PaintCommand { } } +class PaintVertices extends PaintCommand { + final ui.Vertices vertices; + final ui.BlendMode blendMode; + final ui.PaintData paint; + PaintVertices(this.vertices, this.blendMode, this.paint); + + @override + void apply(EngineCanvas canvas) { + canvas.drawVertices(vertices, blendMode, paint); + } + + @override + String toString() { + if (assertionsEnabled) { + return 'drawVertices($vertices, $blendMode, $paint)'; + } else { + return super.toString(); + } + } + + @override + void serializeToCssPaint(List> serializedCommands) { + throw UnimplementedError(); + } +} + class PaintDrawRect extends PaintCommand { final ui.Rect rect; final ui.PaintData paint; diff --git a/lib/web_ui/lib/src/ui/canvas.dart b/lib/web_ui/lib/src/ui/canvas.dart index e4d1693f2f3a3..4788f85950487 100644 --- a/lib/web_ui/lib/src/ui/canvas.dart +++ b/lib/web_ui/lib/src/ui/canvas.dart @@ -59,37 +59,91 @@ enum VertexMode { /// A set of vertex data used by [Canvas.drawVertices]. class Vertices { + final VertexMode _mode; + final Float32List _positions; + final Float32List _textureCoordinates; + final Int32List _colors; + final Uint16List _indices; + + Vertices._( + VertexMode mode, + List positions, { + List textureCoordinates, + List colors, + List indices, + }) : assert(mode != null), + assert(positions != null), + _mode = mode, + _colors = Int32List.fromList(colors.map((Color c) => c.value)), + _indices = Uint16List.fromList(indices), + _positions = _offsetListToInt32List(positions), + _textureCoordinates = _offsetListToInt32List(textureCoordinates); + factory Vertices( - VertexMode mode, - List positions, { - List textureCoordinates, - List colors, - List indices, - }) { + VertexMode mode, + List positions, { + List textureCoordinates, + List colors, + List indices, + }) { if (engine.experimentalUseSkia) { return engine.SkVertices(mode, positions, textureCoordinates: textureCoordinates, colors: colors, indices: indices); } - return null; + return Vertices._(mode, positions, + textureCoordinates: textureCoordinates, + colors: colors , indices: indices); + } + + Vertices._raw( + VertexMode mode, + Float32List positions, { + Float32List textureCoordinates, + Int32List colors, + Uint16List indices, + }) : assert(mode != null), + assert(positions != null), + _mode = mode, + _positions = positions, + _textureCoordinates = textureCoordinates, + _colors = colors, + _indices = indices; + + static Float32List _offsetListToInt32List(List offsetList) { + if (offsetList == null) { + return null; + } + final int length = offsetList.length; + final floatList = Float32List(length * 2); + for (int i = 0, destIndex = 0; i < length; i++, destIndex += 2) { + floatList[destIndex] = offsetList[i].dx; + floatList[destIndex + 1] = offsetList[i].dx; + } + return floatList; } factory Vertices.raw( - VertexMode mode, - Float32List positions, { - Float32List textureCoordinates, - Int32List colors, - Uint16List indices, - }) { + VertexMode mode, + Float32List positions, { + Float32List textureCoordinates, + Int32List colors, + Uint16List indices, + }) { if (engine.experimentalUseSkia) { return engine.SkVertices.raw(mode, positions, textureCoordinates: textureCoordinates, colors: colors, indices: indices); } - return null; + return Vertices._raw(mode, positions, + textureCoordinates: textureCoordinates, colors: colors , indices: indices); } + + VertexMode get mode => _mode; + Int32List get colors => _colors; + Float32List get positions => _positions; } /// Records a [Picture] containing a sequence of graphical operations. diff --git a/lib/web_ui/test/mock_engine_canvas.dart b/lib/web_ui/test/mock_engine_canvas.dart index 2cf562f4fd2e9..d5e8fade6d275 100644 --- a/lib/web_ui/test/mock_engine_canvas.dart +++ b/lib/web_ui/test/mock_engine_canvas.dart @@ -218,4 +218,14 @@ class MockEngineCanvas implements EngineCanvas { 'offset': offset, }); } + + @override + void drawVertices(Vertices vertices, BlendMode blendMode, + PaintData paint) { + _called('drawVertices', arguments: { + 'vertices': vertices, + 'blendMode': blendMode, + 'paint': paint, + }); + } } From c754ee636430ba8506c5c5140bb00717133b0ad2 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Fri, 4 Oct 2019 16:34:03 -0700 Subject: [PATCH 02/15] add vertex mode --- lib/web_ui/lib/src/engine/compositor/vertices.dart | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/web_ui/lib/src/engine/compositor/vertices.dart b/lib/web_ui/lib/src/engine/compositor/vertices.dart index 0a1e84f0f572b..0bc0e404600e0 100644 --- a/lib/web_ui/lib/src/engine/compositor/vertices.dart +++ b/lib/web_ui/lib/src/engine/compositor/vertices.dart @@ -30,6 +30,7 @@ class SkVertices implements ui.Vertices { js.JsObject skVertices; final Int32List _colors; final Float32List _positions; + final ui.VertexMode _mode; SkVertices(ui.VertexMode mode, List positions, { @@ -40,7 +41,8 @@ class SkVertices implements ui.Vertices { : assert(mode != null), assert(positions != null), _colors = Int32List.fromList(colors.map((ui.Color c) => c.value)), - _positions = _offsetListToInt32List(positions) { + _positions = _offsetListToInt32List(positions), + _mode = mode { if (textureCoordinates != null && textureCoordinates.length != positions.length) throw ArgumentError( @@ -75,7 +77,8 @@ class SkVertices implements ui.Vertices { : assert(mode != null), assert(positions != null), _colors = colors, - _positions = positions { + _positions = positions, + _mode = mode { if (textureCoordinates != null && textureCoordinates.length != positions.length) throw ArgumentError( @@ -155,4 +158,7 @@ class SkVertices implements ui.Vertices { @override Float32List get positions => _positions; -} \ No newline at end of file + + @override + ui.VertexMode get mode => _mode; +} From 28a645fdf5e15746c720193ecbca143d2f535511 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Mon, 7 Oct 2019 15:56:27 -0700 Subject: [PATCH 03/15] add test --- lib/web_ui/lib/src/engine/bitmap_canvas.dart | 75 ++++++++++-- .../engine/draw_vertices_golden_test.dart | 111 ++++++++++++++++++ 2 files changed, 175 insertions(+), 11 deletions(-) create mode 100644 lib/web_ui/test/golden_tests/engine/draw_vertices_golden_test.dart diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index 88aec6a615ba0..6146ac9b380bb 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -885,11 +885,14 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { ui.PaintData paint) { final Int32List colors = vertices.colors; + final ui.VertexMode mode = vertices.mode; + final Float32List positions = mode == ui.VertexMode.triangles + ? vertices.positions + : _convertVertexPositions(mode, vertices.positions); if (colors == null) { // Draw hairline for vertices if no vertex colors are specified. - _drawHairline(vertices, paint.color ?? ui.Color(0xFF000000)); - throw UnimplementedError(); - //return; + _drawHairline(positions, paint.color ?? ui.Color(0xFF000000)); + return; } final html.CanvasElement glCanvas = html.CanvasElement( @@ -907,7 +910,6 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { _GlContext gl = _GlContext(glCanvas); - //gl.viewport(0, 0, _widthInBitmapPixels.toDouble(), _heightInBitmapPixels.toDouble()); // Create and compile shaders. Object vertexShader = gl.compileShader('VERTEX_SHADER', _vertexShaderTriangle); Object fragmentShader = gl.compileShader('FRAGMENT_SHADER', _fragmentShaderTriangle); @@ -923,15 +925,15 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { assert(positionsBuffer != null); gl.bindArrayBuffer(positionsBuffer); - int positionCount = vertices.positions.length; + int positionCount = positions.length; Float32List scaledList = Float32List(3 * positionCount ~/ 2); for (int i = 0, destIndex = 0; i < positionCount; i += 2, destIndex += 3) { // Scale. scaledList[destIndex] = - ((vertices.positions[i]) / (_widthInBitmapPixels / 2)) - 1; + ((positions[i]) / (_widthInBitmapPixels / 2)) - 1; // Scale + invert axis. scaledList[destIndex + 1] = - -(vertices.positions[i + 1] / (_heightInBitmapPixels / 2)) + 1;// + (_heightInBitmapPixels / 2) / _heightInBitmapPixels; + -(positions[i + 1] / (_heightInBitmapPixels / 2)) + 1;// + (_heightInBitmapPixels / 2) / _heightInBitmapPixels; // Set depth to 0. scaledList[destIndex + 2] = 0.0; } @@ -951,10 +953,10 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { gl.drawTriangles(positionCount ~/ 2); } - void _drawHairline(ui.Vertices vertices, ui.Color color) { + void _drawHairline(Float32List positions, ui.Color color) { + assert(positions != null); html.CanvasRenderingContext2D _ctx = ctx; save(); - final Float32List positions = vertices.positions; final int pointCount = positions.length ~/ 2; _setFillAndStrokeStyle('', color.toCssString()); _ctx.lineWidth = 1.0; @@ -979,6 +981,58 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { restore(); } + // Converts from [VertexMode] triangleFan and triangleStrip to triangles. + Float32List _convertVertexPositions(ui.VertexMode mode, Float32List + positions) { + assert(mode != ui.VertexMode.triangles); + if (mode == ui.VertexMode.triangleFan) { + final int coordinateCount = positions.length ~/ 2; + final int triangleCount = (coordinateCount - 2) ~/ 2; + final Float32List triangleList = Float32List(triangleCount * 3 * 2); + double centerX = positions[0]; + double centerY = positions[1]; + int destIndex = 0; + int positionIndex = 2; + for (int triangleIndex = 0; triangleIndex < triangleCount; + triangleIndex++, positionIndex += 2) { + triangleList[destIndex++] = centerX; + triangleList[destIndex++] = centerY; + triangleList[destIndex++] = positions[positionIndex]; + triangleList[destIndex++] = positions[positionIndex + 1]; + triangleList[destIndex++] = positions[positionIndex + 2]; + triangleList[destIndex++] = positions[positionIndex + 3]; + } + return triangleList; + } else { + // Set of connected triangles. Each triangle shares 2 last vertices. + final int vertexCount = positions.length ~/ 2; + int triangleCount = vertexCount - 2; + double x0 = positions[0]; + double y0 = positions[1]; + double x1 = positions[2]; + double y1 = positions[3]; + double x2 = positions[4]; + double y2 = positions[5]; + final Float32List triangleList = Float32List(triangleCount * 3 * 2); + int destIndex = 0; + for (int i = 0, positionIndex = 6; i < triangleCount; i++, positionIndex += 2) { + triangleList[destIndex++] = x0; + triangleList[destIndex++] = y0; + triangleList[destIndex++] = x1; + triangleList[destIndex++] = y1; + triangleList[destIndex++] = x2; + triangleList[destIndex++] = y2; + x0 = x1; + y0 = y1; + x1 = x2; + y1 = y2; + x2 = positions[positionIndex]; + y2 = positions[positionIndex + 1]; + } + return triangleList; + } + } + ui.Path _verticesToPath(ui.Vertices vertices) { final Float32List positions = vertices.positions; final ui.Path path = ui.Path(); @@ -1022,7 +1076,7 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { } break; case ui.VertexMode.triangleStrip: - // Set of connected triangles. Each triangle shares 2 last vertices. + // Set of connected triangles. Each triangle shares 2 last vertices. int triangleCount = vertexCount - 2; double x0 = positions[0]; double y0 = positions[1]; @@ -1365,7 +1419,6 @@ class _GlContext { assert(blendMode != ui.BlendMode.src, 'No need to setup default blend mode'); assert(blendMode != ui.BlendMode.dst); - } dynamic get error => js_util.callMethod(glContext, 'getError', const []); diff --git a/lib/web_ui/test/golden_tests/engine/draw_vertices_golden_test.dart b/lib/web_ui/test/golden_tests/engine/draw_vertices_golden_test.dart new file mode 100644 index 0000000000000..7f0a3586a5f9c --- /dev/null +++ b/lib/web_ui/test/golden_tests/engine/draw_vertices_golden_test.dart @@ -0,0 +1,111 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:html' as html; +import 'dart:typed_data'; + +import 'package:ui/ui.dart' hide TextStyle; +import 'package:ui/src/engine.dart'; +import 'package:test/test.dart'; + +import 'package:web_engine_tester/golden_tester.dart'; + +void main() async { + const double screenWidth = 600.0; + const double screenHeight = 800.0; + const Rect screenRect = Rect.fromLTWH(0, 0, screenWidth, screenHeight); + + // Commit a recording canvas to a bitmap, and compare with the expected + Future _checkScreenshot(RecordingCanvas rc, String fileName, + {Rect region = const Rect.fromLTWH(0, 0, 500, 500), + bool write = false}) async { + final EngineCanvas engineCanvas = BitmapCanvas(screenRect); + rc.apply(engineCanvas); + + // Wrap in so that our CSS selectors kick in. + final html.Element sceneElement = html.Element.tag('flt-scene'); + try { + sceneElement.append(engineCanvas.rootElement); + html.document.body.append(sceneElement); + await matchGoldenFile('$fileName.png', region: region, write: write); + } finally { + // The page is reused across tests, so remove the element after taking the + // golden screenshot. + sceneElement.remove(); + } + } + + setUp(() async { + debugEmulateFlutterTesterEnvironment = true; + await webOnlyInitializePlatform(); + webOnlyFontCollection.debugRegisterTestFonts(); + await webOnlyFontCollection.ensureFontsLoaded(); + }); + + Future _testVertices(String fileName, Vertices vertices, + BlendMode blendMode, + Paint paint) async { + final RecordingCanvas rc = + RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500)); + rc.drawVertices(vertices, blendMode, paint); + await _checkScreenshot(rc, fileName, write: true); + } + + test('Should draw green hairline triangles when colors array is null.', + () async { + final Vertices vertices = Vertices.raw(VertexMode.triangles, + Float32List.fromList([ + 20.0, 20.0, 220.0, 10.0, 110.0, 220.0, + 220.0, 320.0, 20.0, 310.0, 200.0, 420.0 + ])); + await _testVertices( + 'draw_vertices_hairline_triangle', + vertices, + BlendMode.src, + Paint()..color = Color.fromARGB(255, 0, 128, 0)); + }); + + test('Should draw black hairline triangles when colors array is null' + ' and Paint() has no color.', + () async { + final Vertices vertices = Vertices.raw(VertexMode.triangles, + Float32List.fromList([ + 20.0, 20.0, 220.0, 10.0, 110.0, 220.0, + 220.0, 320.0, 20.0, 310.0, 200.0, 420.0 + ])); + await _testVertices( + 'draw_vertices_hairline_triangle_black', + vertices, + BlendMode.src, + Paint()); + }); + + test('Should draw hairline triangleFan.', + () async { + final Vertices vertices = Vertices.raw(VertexMode.triangleFan, + Float32List.fromList([ + 150.0, 150.0, 20.0, 10.0, 80.0, 20.0, + 220.0, 15.0, 280.0, 30.0, 300.0, 420.0 + ])); + await _testVertices( + 'draw_vertices_hairline_triangle_fan', + vertices, + BlendMode.src, + Paint()..color = Color.fromARGB(255, 0, 128, 0)); + }); + + test('Should draw hairline triangleStrip.', + () async { + final Vertices vertices = Vertices.raw(VertexMode.triangleStrip, + Float32List.fromList([ + 20.0, 20.0, 220.0, 10.0, 110.0, 220.0, + 220.0, 320.0, 20.0, 310.0, 200.0, 420.0 + ])); + await _testVertices( + 'draw_vertices_hairline_triangle_strip', + vertices, + BlendMode.src, + Paint()..color = Color.fromARGB(255, 0, 128, 0)); + }); +} From fad22cb55eb016e33c75b65447bf3f8fc59da4f2 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Wed, 9 Oct 2019 14:23:45 -0700 Subject: [PATCH 04/15] add vertex mode to GLContext --- lib/web_ui/lib/src/engine/bitmap_canvas.dart | 46 +++++++++----- .../engine/draw_vertices_golden_test.dart | 61 ++++++++++++++++++- 2 files changed, 92 insertions(+), 15 deletions(-) diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index 2da31c301402c..87e05efefa658 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -697,13 +697,14 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { @override void drawVertices(ui.Vertices vertices, ui.BlendMode blendMode, ui.PaintData paint) { - + assert(paint.shader == null, + 'Linear/Radial/SweepGradient and ImageShader not supported yet'); final Int32List colors = vertices.colors; final ui.VertexMode mode = vertices.mode; - final Float32List positions = mode == ui.VertexMode.triangles - ? vertices.positions - : _convertVertexPositions(mode, vertices.positions); if (colors == null) { + final Float32List positions = mode == ui.VertexMode.triangles + ? vertices.positions + : _convertVertexPositions(mode, vertices.positions); // Draw hairline for vertices if no vertex colors are specified. _drawHairline(positions, paint.color ?? ui.Color(0xFF000000)); return; @@ -738,7 +739,7 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { Object positionsBuffer = gl.createBuffer(); assert(positionsBuffer != null); gl.bindArrayBuffer(positionsBuffer); - + final Float32List positions = vertices.positions; int positionCount = positions.length; Float32List scaledList = Float32List(3 * positionCount ~/ 2); for (int i = 0, destIndex = 0; i < positionCount; i += 2, destIndex += 3) { @@ -764,7 +765,7 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { js_util.callMethod(gl.glContext, 'vertexAttribPointer', [1, 4, gl.UNSIGNED_BYTE, true, 0 , 0]); gl.enableVertexAttribArray(1); gl.clear(); - gl.drawTriangles(positionCount ~/ 2); + gl.drawTriangles(positionCount ~/ 2, mode); } void _drawHairline(Float32List positions, ui.Color color) { @@ -801,7 +802,7 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { assert(mode != ui.VertexMode.triangles); if (mode == ui.VertexMode.triangleFan) { final int coordinateCount = positions.length ~/ 2; - final int triangleCount = (coordinateCount - 2) ~/ 2; + final int triangleCount = coordinateCount - 2; final Float32List triangleList = Float32List(triangleCount * 3 * 2); double centerX = positions[0]; double centerY = positions[1]; @@ -825,11 +826,11 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { double y0 = positions[1]; double x1 = positions[2]; double y1 = positions[3]; - double x2 = positions[4]; - double y2 = positions[5]; final Float32List triangleList = Float32List(triangleCount * 3 * 2); int destIndex = 0; - for (int i = 0, positionIndex = 6; i < triangleCount; i++, positionIndex += 2) { + for (int i = 0, positionIndex = 4; i < triangleCount; i++) { + final double x2 = positions[positionIndex++]; + final double y2 = positions[positionIndex++]; triangleList[destIndex++] = x0; triangleList[destIndex++] = y0; triangleList[destIndex++] = x1; @@ -840,8 +841,6 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { y0 = y1; x1 = x2; y1 = y2; - x2 = positions[positionIndex]; - y2 = positions[positionIndex + 1]; } return triangleList; } @@ -1222,8 +1221,23 @@ class _GlContext { js_util.callMethod(glContext, 'clear', [COLOR_BUFFER_BIT]); } - void drawTriangles(int triangleCount) { - js_util.callMethod(glContext, 'drawArrays', [TRIANGLES, 0, triangleCount]); + void drawTriangles(int triangleCount, ui.VertexMode vertexMode) { + dynamic mode = _triangleTypeFromMode(vertexMode); + js_util.callMethod(glContext, 'drawArrays', [mode, 0, triangleCount]); + } + + dynamic _triangleTypeFromMode(ui.VertexMode mode) { + switch (mode) { + case ui.VertexMode.triangles: + return TRIANGLES; + break; + case ui.VertexMode.triangleFan: + return TRIANGLE_FAN; + break; + case ui.VertexMode.triangleStrip: + return TRIANGLE_STRIP; + break; + } } void viewport(double x, double y, double width, double height) { @@ -1255,6 +1269,10 @@ class _GlContext { dynamic get TRIANGLES => _triangles ??= js_util.getProperty(glContext, 'TRIANGLES'); + dynamic get TRIANGLE_FAN => + _triangles ??= js_util.getProperty(glContext, 'TRIANGLE_FAN'); + dynamic get TRIANGLE_STRIP => + _triangles ??= js_util.getProperty(glContext, 'TRIANGLE_STRIP'); dynamic get COLOR_BUFFER_BIT => _color_buffer_bit ??= js_util.getProperty(glContext, 'COLOR_BUFFER_BIT'); diff --git a/lib/web_ui/test/golden_tests/engine/draw_vertices_golden_test.dart b/lib/web_ui/test/golden_tests/engine/draw_vertices_golden_test.dart index 7f0a3586a5f9c..2136bedc0fa1e 100644 --- a/lib/web_ui/test/golden_tests/engine/draw_vertices_golden_test.dart +++ b/lib/web_ui/test/golden_tests/engine/draw_vertices_golden_test.dart @@ -49,7 +49,7 @@ void main() async { final RecordingCanvas rc = RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500)); rc.drawVertices(vertices, blendMode, paint); - await _checkScreenshot(rc, fileName, write: true); + await _checkScreenshot(rc, fileName); } test('Should draw green hairline triangles when colors array is null.', @@ -69,6 +69,11 @@ void main() async { test('Should draw black hairline triangles when colors array is null' ' and Paint() has no color.', () async { + final Int32List colors = Int32List.fromList([ + 0xFFFF0000, 0xFF00FF00, 0xFF0000FF, + 0xFFFF0000, 0xFF00FF00, 0xFF0000FF, + 0xFFFF0000, 0xFF00FF00, 0xFF0000FF, + 0xFFFF0000, 0xFF00FF00, 0xFF0000FF]); final Vertices vertices = Vertices.raw(VertexMode.triangles, Float32List.fromList([ 20.0, 20.0, 220.0, 10.0, 110.0, 220.0, @@ -88,6 +93,7 @@ void main() async { 150.0, 150.0, 20.0, 10.0, 80.0, 20.0, 220.0, 15.0, 280.0, 30.0, 300.0, 420.0 ])); + await _testVertices( 'draw_vertices_hairline_triangle_fan', vertices, @@ -108,4 +114,57 @@ void main() async { BlendMode.src, Paint()..color = Color.fromARGB(255, 0, 128, 0)); }); + + test('Should draw triangles with colors.', + () async { + final Int32List colors = Int32List.fromList([ + 0xFFFF0000, 0xFF00FF00, 0xFF0000FF, + 0xFFFF0000, 0xFF00FF00, 0xFF0000FF]); + final Vertices vertices = Vertices.raw(VertexMode.triangles, + Float32List.fromList([ + 150.0, 150.0, 20.0, 10.0, 80.0, 20.0, + 220.0, 15.0, 280.0, 30.0, 300.0, 420.0 + ]), colors: colors); + + await _testVertices( + 'draw_vertices_triangles', + vertices, + BlendMode.src, + Paint()..color = Color.fromARGB(255, 0, 128, 0)); + }); + + test('Should draw triangleFan with colors.', + () async { + final Int32List colors = Int32List.fromList([ + 0xFFFF0000, 0xFF00FF00, 0xFF0000FF, + 0xFFFF0000, 0xFF00FF00, 0xFF0000FF]); + final Vertices vertices = Vertices.raw(VertexMode.triangleFan, + Float32List.fromList([ + 150.0, 150.0, 20.0, 10.0, 80.0, 20.0, + 220.0, 15.0, 280.0, 30.0, 300.0, 420.0 + ]), colors: colors); + + await _testVertices( + 'draw_vertices_triangle_fan', + vertices, + BlendMode.src, + Paint()..color = Color.fromARGB(255, 0, 128, 0)); + }); + + test('Should draw triangleStrip with colors.', + () async { + final Int32List colors = Int32List.fromList([ + 0xFFFF0000, 0xFF00FF00, 0xFF0000FF, + 0xFFFF0000, 0xFF00FF00, 0xFF0000FF]); + final Vertices vertices = Vertices.raw(VertexMode.triangleStrip, + Float32List.fromList([ + 20.0, 20.0, 220.0, 10.0, 110.0, 220.0, + 220.0, 320.0, 20.0, 310.0, 200.0, 420.0 + ]), colors: colors); + await _testVertices( + 'draw_vertices_triangle_strip', + vertices, + BlendMode.src, + Paint()..color = Color.fromARGB(255, 0, 128, 0)); + }); } From ede8610c464801b28bc9cfefd64a378c07fbafc5 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Thu, 10 Oct 2019 12:43:39 -0700 Subject: [PATCH 05/15] Move viewport transformation into shader --- lib/web_ui/lib/src/engine/bitmap_canvas.dart | 272 ++++++++----------- 1 file changed, 120 insertions(+), 152 deletions(-) diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index 87e05efefa658..88bc1976f82e4 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -668,9 +668,12 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { #version 300 es layout (location=0) in vec4 position; layout (location=1) in vec4 color; + uniform vec4 u_scale; + uniform vec4 u_shift; + out vec4 vColor; void main() { - gl_Position = position; + gl_Position = (position * u_scale) + u_shift; vColor = color; }'''; static const _fragmentShaderTriangle = ''' @@ -695,10 +698,13 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { /// If colors is specified, convert colors to premultiplied (alpha) colors /// and use a SkTriColorShader to render. @override - void drawVertices(ui.Vertices vertices, ui.BlendMode blendMode, - ui.PaintData paint) { + void drawVertices( + ui.Vertices vertices, ui.BlendMode blendMode, ui.PaintData paint) { + // TODO(flutter_web): Implement shaders for [Paint.shader] and + // blendMode. https://github.com/flutter/flutter/issues/40096 assert(paint.shader == null, 'Linear/Radial/SweepGradient and ImageShader not supported yet'); + assert(blendMode == ui.BlendMode.srcOver); final Int32List colors = vertices.colors; final ui.VertexMode mode = vertices.mode; if (colors == null) { @@ -714,6 +720,7 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { width: _widthInBitmapPixels, height: _heightInBitmapPixels, ); + glCanvas.style ..position = 'absolute' ..width = _canvas.style.width @@ -726,8 +733,10 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { _GlContext gl = _GlContext(glCanvas); // Create and compile shaders. - Object vertexShader = gl.compileShader('VERTEX_SHADER', _vertexShaderTriangle); - Object fragmentShader = gl.compileShader('FRAGMENT_SHADER', _fragmentShaderTriangle); + Object vertexShader = + gl.compileShader('VERTEX_SHADER', _vertexShaderTriangle); + Object fragmentShader = + gl.compileShader('FRAGMENT_SHADER', _fragmentShaderTriangle); // Create a gl program and link shaders. Object program = gl.createProgram(); gl.attachShader(program, vertexShader); @@ -735,37 +744,36 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { gl.linkProgram(program); gl.useProgram(program); + // Set uniform to scale 0..width/height pixels coordinates to -1..1 + // clipspace range and flip the Y axis. + Object resolution = gl.getUniformLocation(program, 'u_scale'); + gl.setUniform4f(resolution, 2.0 / _widthInBitmapPixels.toDouble(), + -2.0 / _heightInBitmapPixels.toDouble(), 1, 1); + Object shift = gl.getUniformLocation(program, 'u_shift'); + gl.setUniform4f(shift, -1, 1, 0, 0); + // Setup geometry. Object positionsBuffer = gl.createBuffer(); assert(positionsBuffer != null); gl.bindArrayBuffer(positionsBuffer); final Float32List positions = vertices.positions; - int positionCount = positions.length; - Float32List scaledList = Float32List(3 * positionCount ~/ 2); - for (int i = 0, destIndex = 0; i < positionCount; i += 2, destIndex += 3) { - // Scale. - scaledList[destIndex] = - ((positions[i]) / (_widthInBitmapPixels / 2)) - 1; - // Scale + invert axis. - scaledList[destIndex + 1] = - -(positions[i + 1] / (_heightInBitmapPixels / 2)) + 1;// + (_heightInBitmapPixels / 2) / _heightInBitmapPixels; - // Set depth to 0. - scaledList[destIndex + 2] = 0.0; - } - gl.bufferData(scaledList, gl.STATIC_DRAW); - js_util.callMethod(gl.glContext, 'vertexAttribPointer', [0, 3, gl.FLOAT, false, 0 , 0]); + gl.bufferData(positions, gl.kStaticDraw); + js_util.callMethod( + gl.glContext, 'vertexAttribPointer', [0, 2, gl.kFloat, false, 0, 0]); gl.enableVertexAttribArray(0); // Setup color buffer. Object colorsBuffer = gl.createBuffer(); gl.bindArrayBuffer(colorsBuffer); // Buffer kBGRA_8888. - gl.bufferData(colors, gl.STATIC_DRAW); + gl.bufferData(colors, gl.kStaticDraw); - js_util.callMethod(gl.glContext, 'vertexAttribPointer', [1, 4, gl.UNSIGNED_BYTE, true, 0 , 0]); + js_util.callMethod(gl.glContext, 'vertexAttribPointer', + [1, 4, gl.kUnsignedByte, true, 0, 0]); gl.enableVertexAttribArray(1); gl.clear(); - gl.drawTriangles(positionCount ~/ 2, mode); + final int vertexCount = positions.length ~/ 2; + gl.drawTriangles(vertexCount, mode); } void _drawHairline(Float32List positions, ui.Color color) { @@ -797,8 +805,8 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { } // Converts from [VertexMode] triangleFan and triangleStrip to triangles. - Float32List _convertVertexPositions(ui.VertexMode mode, Float32List - positions) { + Float32List _convertVertexPositions( + ui.VertexMode mode, Float32List positions) { assert(mode != ui.VertexMode.triangles); if (mode == ui.VertexMode.triangleFan) { final int coordinateCount = positions.length ~/ 2; @@ -808,7 +816,8 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { double centerY = positions[1]; int destIndex = 0; int positionIndex = 2; - for (int triangleIndex = 0; triangleIndex < triangleCount; + for (int triangleIndex = 0; + triangleIndex < triangleCount; triangleIndex++, positionIndex += 2) { triangleList[destIndex++] = centerX; triangleList[destIndex++] = centerY; @@ -821,7 +830,7 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { } else { // Set of connected triangles. Each triangle shares 2 last vertices. final int vertexCount = positions.length ~/ 2; - int triangleCount = vertexCount - 2; + int triangleCount = vertexCount - 2; double x0 = positions[0]; double y0 = positions[1]; double x1 = positions[2]; @@ -846,74 +855,6 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { } } - ui.Path _verticesToPath(ui.Vertices vertices) { - final Float32List positions = vertices.positions; - final ui.Path path = ui.Path(); - final int vertexCount = positions.length ~/ 2; - switch (vertices.mode) { - case ui.VertexMode.triangles: - for (int i = 0, len = vertexCount * 2; i < len;) { - for (int triangleIndex = 0; triangleIndex < 3; ++triangleIndex, - i += 2) { - final double dx = positions[i]; - final double dy = positions[i + 1]; - switch (triangleIndex) { - case 0: - path.moveTo(dx, dy); - break; - case 1: - path.lineTo(dx, dy); - break; - case 2: - path.lineTo(dx, dy); - path.close(); - break; - } - } - } - break; - case ui.VertexMode.triangleFan: - // Set of triangles connected to a central point (first vertex). - // Each point defines new triangle connected to prior vertex. - final double centerX = positions[0]; - final double centerY = positions[1]; - for (int i = 2, len = vertexCount * 2; i < len; i += 2) { - final double dx1 = positions[i]; - final double dy1 = positions[i + 1]; - final double dx2 = positions[i + 2]; - final double dy2 = positions[i + 3]; - path.moveTo(centerX, centerY); - path.lineTo(dx1, dy1); - path.lineTo(dx2, dy2); - path.close(); - } - break; - case ui.VertexMode.triangleStrip: - // Set of connected triangles. Each triangle shares 2 last vertices. - int triangleCount = vertexCount - 2; - double x0 = positions[0]; - double y0 = positions[1]; - double x1 = positions[2]; - double y1 = positions[3]; - double x2 = positions[4]; - double y2 = positions[5]; - for (int i = 0, positionIndex = 6; i < triangleCount; i++, positionIndex += 2) { - path.moveTo(x0, y0); - path.lineTo(x1, y1); - path.lineTo(x2, y2); - path.close(); - x0 = x1; - y0 = y1; - x1 = x2; - y1 = y2; - x2 = positions[positionIndex]; - y2 = positions[positionIndex + 1]; - } - break; - } - return path; - } - /// 'Runs' the given [path] by applying all of its commands to the canvas. void _runPath(ui.Path path) { ctx.beginPath(); @@ -950,8 +891,8 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { break; case PathCommandTypes.rRect: final RRectCommand rrectCommand = command; - _RRectToCanvasRenderer(ctx).render(rrectCommand.rrect, - startNewPath: false); + _RRectToCanvasRenderer(ctx) + .render(rrectCommand.rrect, startNewPath: false); break; case PathCommandTypes.rect: final RectCommand rectCommand = command; @@ -1153,30 +1094,29 @@ String _cssTransformAtOffset( transformWithOffset(transform, ui.Offset(offsetX, offsetY))); } -/// JS Interop wrapper around webgl apis. +/// JS Interop helper for webgl apis. class _GlContext { final Object glContext; - dynamic _compileStatus; - dynamic _array_buffer; - dynamic _static_draw; - dynamic _float; - dynamic _color_buffer_bit; - dynamic _triangles; - dynamic _link_status; - dynamic _unsigned_byte; + dynamic _kCompileStatus; + dynamic _kArrayBuffer; + dynamic _kStaticDraw; + dynamic _kFloat; + dynamic _kColorBufferBit; + dynamic _kTriangles; + dynamic _kLinkStatus; + dynamic _kUnsignedByte; _GlContext(html.CanvasElement canvas) : glContext = canvas.getContext('webgl2'); Object compileShader(String shaderType, String source) { - Object shader = js_util.callMethod(glContext, 'createShader', - [js_util.getProperty(glContext, shaderType)]); + Object shader = _createShader(shaderType); js_util.callMethod(glContext, 'shaderSource', [shader, source]); - js_util.callMethod(glContext,'compileShader', [shader]); - bool shaderStatus = js_util.callMethod(glContext,'getShaderParameter', - [shader, compileStatus]); + js_util.callMethod(glContext, 'compileShader', [shader]); + bool shaderStatus = js_util + .callMethod(glContext, 'getShaderParameter', [shader, compileStatus]); if (!shaderStatus) { - throw Exception('Shader compilation failed'); + throw Exception('Shader compilation failed: ${getShaderInfoLog(shader)}'); } return shader; } @@ -1190,9 +1130,9 @@ class _GlContext { void linkProgram(Object program) { js_util.callMethod(glContext, 'linkProgram', [program]); - if (!js_util.callMethod(glContext,'getProgramParameter', - [program, LINK_STATUS])) { - throw Exception(_getProgramInfoLog(program)); + if (!js_util + .callMethod(glContext, 'getProgramParameter', [program, kLinkStatus])) { + throw Exception(getProgramInfoLog(program)); } } @@ -1204,21 +1144,20 @@ class _GlContext { js_util.callMethod(glContext, 'createBuffer', const []); void bindArrayBuffer(Object buffer) { - js_util.callMethod(glContext, 'bindBuffer', [ARRAY_BUFFER , buffer]); + js_util.callMethod(glContext, 'bindBuffer', [kArrayBuffer, buffer]); } void bufferData(TypedData data, dynamic type) { - js_util.callMethod(glContext, 'bufferData', - [ARRAY_BUFFER, data, type]); + js_util.callMethod(glContext, 'bufferData', [kArrayBuffer, data, type]); } void enableVertexAttribArray(int index) { js_util.callMethod(glContext, 'enableVertexAttribArray', [index]); } - // Fill background. + /// Clear background. void clear() { - js_util.callMethod(glContext, 'clear', [COLOR_BUFFER_BIT]); + js_util.callMethod(glContext, 'clear', [kColorBufferBit]); } void drawTriangles(int triangleCount, ui.VertexMode vertexMode) { @@ -1226,61 +1165,90 @@ class _GlContext { js_util.callMethod(glContext, 'drawArrays', [mode, 0, triangleCount]); } + /// Sets affine transformation from normalized device coordinates + /// to window coordinates + void viewport(double x, double y, double width, double height) { + js_util.callMethod(glContext, 'viewport', [x, y, width, height]); + } + dynamic _triangleTypeFromMode(ui.VertexMode mode) { switch (mode) { case ui.VertexMode.triangles: - return TRIANGLES; + return kTriangles; break; case ui.VertexMode.triangleFan: - return TRIANGLE_FAN; + return kTriangleFan; break; case ui.VertexMode.triangleStrip: - return TRIANGLE_STRIP; + return kTriangleStrip; break; } } - void viewport(double x, double y, double width, double height) { - js_util.callMethod(glContext, 'viewport', [x, y, width, height]); - } - - void setupBlendMode(ui.BlendMode blendMode) { - assert(blendMode != ui.BlendMode.src, - 'No need to setup default blend mode'); - assert(blendMode != ui.BlendMode.dst); - } + Object _createShader(String shaderType) => js_util.callMethod( + glContext, 'createShader', [js_util.getProperty(glContext, shaderType)]); + /// Error state of gl context. dynamic get error => js_util.callMethod(glContext, 'getError', const []); - dynamic get ARRAY_BUFFER => - _array_buffer ??= js_util.getProperty(glContext, 'ARRAY_BUFFER'); + /// Shader compiler error, if this returns [kFalse], to get details use + /// [getShaderInfoLog]. + dynamic get compileStatus => + _kCompileStatus ??= js_util.getProperty(glContext, 'COMPILE_STATUS'); - dynamic get LINK_STATUS => - _link_status ??= js_util.getProperty(glContext, 'LINK_STATUS'); + dynamic get kArrayBuffer => + _kArrayBuffer ??= js_util.getProperty(glContext, 'ARRAY_BUFFER'); - dynamic get FLOAT => - _float ??= js_util.getProperty(glContext, 'FLOAT'); + dynamic get kLinkStatus => + _kLinkStatus ??= js_util.getProperty(glContext, 'LINK_STATUS'); - dynamic get UNSIGNED_BYTE => - _unsigned_byte ??= js_util.getProperty(glContext,'UNSIGNED_BYTE'); + dynamic get kFloat => _kFloat ??= js_util.getProperty(glContext, 'FLOAT'); - dynamic get STATIC_DRAW => - _static_draw ??= js_util.getProperty(glContext, 'STATIC_DRAW'); + dynamic get kUnsignedByte => + _kUnsignedByte ??= js_util.getProperty(glContext, 'UNSIGNED_BYTE'); - dynamic get TRIANGLES => - _triangles ??= js_util.getProperty(glContext, 'TRIANGLES'); - dynamic get TRIANGLE_FAN => - _triangles ??= js_util.getProperty(glContext, 'TRIANGLE_FAN'); - dynamic get TRIANGLE_STRIP => - _triangles ??= js_util.getProperty(glContext, 'TRIANGLE_STRIP'); + dynamic get kStaticDraw => + _kStaticDraw ??= js_util.getProperty(glContext, 'STATIC_DRAW'); - dynamic get COLOR_BUFFER_BIT => - _color_buffer_bit ??= js_util.getProperty(glContext, 'COLOR_BUFFER_BIT'); + dynamic get kTriangles => + _kTriangles ??= js_util.getProperty(glContext, 'TRIANGLES'); - dynamic get compileStatus => - _compileStatus ??= js_util.getProperty(glContext, 'COMPILE_STATUS'); + dynamic get kTriangleFan => + _kTriangles ??= js_util.getProperty(glContext, 'TRIANGLE_FAN'); + + dynamic get kTriangleStrip => + _kTriangles ??= js_util.getProperty(glContext, 'TRIANGLE_STRIP'); + + dynamic get kColorBufferBit => + _kColorBufferBit ??= js_util.getProperty(glContext, 'COLOR_BUFFER_BIT'); + + /// Returns reference to uniform in program. + Object getUniformLocation(Object program, String uniformName) { + return js_util + .callMethod(glContext, 'getUniformLocation', [program, uniformName]); + } + + /// Sets vec2 uniform values. + void setUniform2f(Object uniform, double value1, double value2) { + return js_util + .callMethod(glContext, 'uniform2f', [uniform, value1, value2]); + } + + /// Sets vec4 uniform values. + void setUniform4f(Object uniform, double value1, double value2, double value3, + double value4) { + return js_util.callMethod( + glContext, 'uniform4f', [uniform, value1, value2, value3, value4]); + } + + /// Shader compile error log. + dynamic getShaderInfoLog(Object glShader) { + return js_util.callMethod(glContext, 'getShaderInfoLog', [glShader]); + } - String _getProgramInfoLog(Object glProgram) { + /// Errors that occurred during failed linking or validation of program + /// objects. Typically called after [linkProgram]. + String getProgramInfoLog(Object glProgram) { return js_util.callMethod(glContext, 'getProgramInfoLog', [glProgram]); } } From b8b06ee249649e2f991bcbda5c1a00c5cd54b755 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Thu, 10 Oct 2019 12:46:45 -0700 Subject: [PATCH 06/15] Update golden revision --- lib/web_ui/dev/goldens_lock.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/web_ui/dev/goldens_lock.yaml b/lib/web_ui/dev/goldens_lock.yaml index eb438eef2a3bb..fa2c3c0f72424 100644 --- a/lib/web_ui/dev/goldens_lock.yaml +++ b/lib/web_ui/dev/goldens_lock.yaml @@ -1,2 +1,2 @@ repository: https://github.com/flutter/goldens.git -revision: 7efcec3e8b0bbb6748a992b23a0a89300aa323c7 \ No newline at end of file +revision: 686dd320f6cce6da9a7a43e3ec9c0147f39eb19d \ No newline at end of file From 1f86ec94f0dfe1ebaca5cd79e975cf9afd4bbd77 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Thu, 10 Oct 2019 12:59:39 -0700 Subject: [PATCH 07/15] Add comments on shaders --- lib/web_ui/lib/src/engine/bitmap_canvas.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index 88bc1976f82e4..325417319d8de 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -664,6 +664,8 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { picture.recordingCanvas.apply(this); } + // Vertex shader transforms pixel space [Vertices.positions] to + // final clipSpace -1..1 coordinates with inverted Y Axis. static const _vertexShaderTriangle = ''' #version 300 es layout (location=0) in vec4 position; @@ -676,6 +678,8 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { gl_Position = (position * u_scale) + u_shift; vColor = color; }'''; + // This fragment shader enables Int32List of colors to be passed directly + // to gl context buffer for rendering by decoding RGBA8888. static const _fragmentShaderTriangle = ''' #version 300 es precision highp float; From 832679ef008c54f76d67d7a8c792d143643635d7 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Fri, 11 Oct 2019 11:03:45 -0700 Subject: [PATCH 08/15] add webgl1 support for webkit --- lib/web_ui/lib/src/engine/bitmap_canvas.dart | 72 ++++++++++++------- .../lib/src/engine/compositor/vertices.dart | 36 +++++----- 2 files changed, 65 insertions(+), 43 deletions(-) diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index 325417319d8de..1f915952bd45e 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -676,7 +676,7 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { out vec4 vColor; void main() { gl_Position = (position * u_scale) + u_shift; - vColor = color; + vColor = color.zyxw; }'''; // This fragment shader enables Int32List of colors to be passed directly // to gl context buffer for rendering by decoding RGBA8888. @@ -686,7 +686,26 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { in vec4 vColor; out vec4 fragColor; void main() { - fragColor = vec4(vColor[2], vColor[1], vColor[0], vColor[3]); + fragColor = vColor; + }'''; + + // WebGL 1 version of shaders above for compatibility with Safari. + static const _vertexShaderTriangleEs1 = ''' + attribute vec4 position; + attribute vec4 color; + uniform vec4 u_scale; + uniform vec4 u_shift; + varying vec4 vColor; + void main() { + gl_Position = (position * u_scale) + u_shift; + vColor = color.zyxw; + }'''; + // WebGL 1 version of shaders above for compatibility with Safari. + static const _fragmentShaderTriangleEs1 = ''' + precision highp float; + varying vec4 vColor; + void main() { + gl_FragColor = vColor; }'''; /// Draws vertices on a gl context. @@ -734,13 +753,13 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { _children.add(glCanvas); rootElement.append(glCanvas); - _GlContext gl = _GlContext(glCanvas); - + final bool isWebKit = (browserEngine == BrowserEngine.webkit); + _GlContext gl = _GlContext(glCanvas, isWebKit); // Create and compile shaders. - Object vertexShader = - gl.compileShader('VERTEX_SHADER', _vertexShaderTriangle); - Object fragmentShader = - gl.compileShader('FRAGMENT_SHADER', _fragmentShaderTriangle); + Object vertexShader = gl.compileShader('VERTEX_SHADER', + isWebKit ? _vertexShaderTriangleEs1 : _vertexShaderTriangle); + Object fragmentShader = gl.compileShader('FRAGMENT_SHADER', + isWebKit ? _fragmentShaderTriangleEs1 : _fragmentShaderTriangle); // Create a gl program and link shaders. Object program = gl.createProgram(); gl.attachShader(program, vertexShader); @@ -787,22 +806,24 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { final int pointCount = positions.length ~/ 2; _setFillAndStrokeStyle('', color.toCssString()); _ctx.lineWidth = 1.0; - int triIndex = 0; _ctx.beginPath(); - for (int i = 0, len = pointCount * 2; i < len; i += 2) { - final double dx = positions[i]; - final double dy = positions[i + 1]; - if (triIndex & 3 == 0) { - _ctx.moveTo(dx, dy); - } else { - _ctx.lineTo(dx, dy); - } - if (triIndex & 3 == 2) { - _ctx.closePath(); - _ctx.stroke(); - triIndex = 0; - } else { - triIndex++; + for (int i = 0, len = pointCount * 2; i < len;) { + for (int triangleVertexIndex = 0; + triangleVertexIndex < 3; + triangleVertexIndex++, i += 2) { + final double dx = positions[i]; + final double dy = positions[i + 1]; + switch (triangleVertexIndex) { + case 0: + _ctx.moveTo(dx, dy); + break; + case 1: + _ctx.lineTo(dx, dy); + break; + case 2: + _ctx.closePath(); + _ctx.stroke(); + } } } restore(); @@ -832,6 +853,7 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { } return triangleList; } else { + assert(mode == ui.VertexMode.triangleStrip); // Set of connected triangles. Each triangle shares 2 last vertices. final int vertexCount = positions.length ~/ 2; int triangleCount = vertexCount - 2; @@ -1110,8 +1132,8 @@ class _GlContext { dynamic _kLinkStatus; dynamic _kUnsignedByte; - _GlContext(html.CanvasElement canvas) - : glContext = canvas.getContext('webgl2'); + _GlContext(html.CanvasElement canvas, bool useWebGl1) + : glContext = canvas.getContext(useWebGl1 ? 'webgl' : 'webgl2'); Object compileShader(String shaderType, String source) { Object shader = _createShader(shaderType); diff --git a/lib/web_ui/lib/src/engine/compositor/vertices.dart b/lib/web_ui/lib/src/engine/compositor/vertices.dart index 0bc0e404600e0..6b8490844e11b 100644 --- a/lib/web_ui/lib/src/engine/compositor/vertices.dart +++ b/lib/web_ui/lib/src/engine/compositor/vertices.dart @@ -32,13 +32,13 @@ class SkVertices implements ui.Vertices { final Float32List _positions; final ui.VertexMode _mode; - SkVertices(ui.VertexMode mode, - List positions, { - List textureCoordinates, - List colors, - List indices, - }) - : assert(mode != null), + SkVertices( + ui.VertexMode mode, + List positions, { + List textureCoordinates, + List colors, + List indices, + }) : assert(mode != null), assert(positions != null), _colors = Int32List.fromList(colors.map((ui.Color c) => c.value)), _positions = _offsetListToInt32List(positions), @@ -59,22 +59,22 @@ class SkVertices implements ui.Vertices { ? _encodePointList(textureCoordinates) : null; final Int32List encodedColors = - colors != null ? _encodeColorList(colors) : null; + colors != null ? _encodeColorList(colors) : null; final Uint16List encodedIndices = - indices != null ? Uint16List.fromList(indices) : null; + 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), + SkVertices.raw( + ui.VertexMode mode, + Float32List positions, { + Float32List textureCoordinates, + Int32List colors, + Uint16List indices, + }) : assert(mode != null), assert(positions != null), _colors = colors, _positions = positions, @@ -110,7 +110,7 @@ class SkVertices implements ui.Vertices { } final js.JsObject vertices = - canvasKit.callMethod('MakeSkVertices', [ + canvasKit.callMethod('MakeSkVertices', [ skVertexMode, _encodePoints(positions), _encodePoints(textureCoordinates), @@ -132,7 +132,7 @@ class SkVertices implements ui.Vertices { if (points == null) return null; js.JsArray> encodedPoints = - js.JsArray>(); + 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])); From d00f608b3cbf29ce3c97d3423b258a5fa1b28b50 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Fri, 11 Oct 2019 12:33:41 -0700 Subject: [PATCH 09/15] dartfmt --- lib/web_ui/lib/src/engine/bitmap_canvas.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index 1f915952bd45e..ae774e9b94d01 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -672,7 +672,6 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { layout (location=1) in vec4 color; uniform vec4 u_scale; uniform vec4 u_shift; - out vec4 vColor; void main() { gl_Position = (position * u_scale) + u_shift; From 3a2f80f7d06ac083659db3eb633c30393b7445f9 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Fri, 11 Oct 2019 13:23:25 -0700 Subject: [PATCH 10/15] Update tests and fix hairline rendering --- lib/web_ui/lib/src/engine/bitmap_canvas.dart | 1 + .../engine/draw_vertices_golden_test.dart | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index ae774e9b94d01..f52c6909350a2 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -820,6 +820,7 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { _ctx.lineTo(dx, dy); break; case 2: + _ctx.lineTo(dx, dy); _ctx.closePath(); _ctx.stroke(); } diff --git a/lib/web_ui/test/golden_tests/engine/draw_vertices_golden_test.dart b/lib/web_ui/test/golden_tests/engine/draw_vertices_golden_test.dart index 2136bedc0fa1e..7103209902f0a 100644 --- a/lib/web_ui/test/golden_tests/engine/draw_vertices_golden_test.dart +++ b/lib/web_ui/test/golden_tests/engine/draw_vertices_golden_test.dart @@ -62,7 +62,7 @@ void main() async { await _testVertices( 'draw_vertices_hairline_triangle', vertices, - BlendMode.src, + BlendMode.srcOver, Paint()..color = Color.fromARGB(255, 0, 128, 0)); }); @@ -82,7 +82,7 @@ void main() async { await _testVertices( 'draw_vertices_hairline_triangle_black', vertices, - BlendMode.src, + BlendMode.srcOver, Paint()); }); @@ -97,7 +97,7 @@ void main() async { await _testVertices( 'draw_vertices_hairline_triangle_fan', vertices, - BlendMode.src, + BlendMode.srcOver, Paint()..color = Color.fromARGB(255, 0, 128, 0)); }); @@ -111,7 +111,7 @@ void main() async { await _testVertices( 'draw_vertices_hairline_triangle_strip', vertices, - BlendMode.src, + BlendMode.srcOver, Paint()..color = Color.fromARGB(255, 0, 128, 0)); }); @@ -129,7 +129,7 @@ void main() async { await _testVertices( 'draw_vertices_triangles', vertices, - BlendMode.src, + BlendMode.srcOver, Paint()..color = Color.fromARGB(255, 0, 128, 0)); }); @@ -147,7 +147,7 @@ void main() async { await _testVertices( 'draw_vertices_triangle_fan', vertices, - BlendMode.src, + BlendMode.srcOver, Paint()..color = Color.fromARGB(255, 0, 128, 0)); }); @@ -164,7 +164,7 @@ void main() async { await _testVertices( 'draw_vertices_triangle_strip', vertices, - BlendMode.src, + BlendMode.srcOver, Paint()..color = Color.fromARGB(255, 0, 128, 0)); }); } From 9349a0e5c4fa6b8ee03634fa54570f028072d69b Mon Sep 17 00:00:00 2001 From: ferhatb Date: Fri, 11 Oct 2019 13:35:10 -0700 Subject: [PATCH 11/15] Added TODO for transform/drawImage --- lib/web_ui/lib/src/engine/bitmap_canvas.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index f52c6909350a2..50258b6ae7967 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -724,6 +724,8 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { ui.Vertices vertices, ui.BlendMode blendMode, ui.PaintData paint) { // TODO(flutter_web): Implement shaders for [Paint.shader] and // blendMode. https://github.com/flutter/flutter/issues/40096 + // Move rendering to OffscreenCanvas so that transform is preserved + // as well. assert(paint.shader == null, 'Linear/Radial/SweepGradient and ImageShader not supported yet'); assert(blendMode == ui.BlendMode.srcOver); From d2ec77c201a5a65966007ea8a74baaf1898146ac Mon Sep 17 00:00:00 2001 From: ferhatb Date: Mon, 14 Oct 2019 10:35:25 -0700 Subject: [PATCH 12/15] Implement maxDiffRate parameter for screenshot tests --- lib/web_ui/dev/test_platform.dart | 9 +++++---- .../golden_tests/engine/draw_vertices_golden_test.dart | 4 +++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/web_ui/dev/test_platform.dart b/lib/web_ui/dev/test_platform.dart index 8426676be88c3..1412b43d6e1d9 100644 --- a/lib/web_ui/dev/test_platform.dart +++ b/lib/web_ui/dev/test_platform.dart @@ -146,12 +146,13 @@ class BrowserPlatform extends PlatformPlugin { final Map requestData = json.decode(payload); final String filename = requestData['filename']; final bool write = requestData['write']; + final double maxRateDiff = requestData['maxratediff']; final Map region = requestData['region']; - final String result = await _diffScreenshot(filename, write, region); + final String result = await _diffScreenshot(filename, write, maxRateDiff ?? _kMaxDiffRateFailure, region); return shelf.Response.ok(json.encode(result)); } - Future _diffScreenshot(String filename, bool write, [ Map region ]) async { + Future _diffScreenshot(String filename, bool write, double maxRateDiffFailure, [ Map region ]) async { if (doUpdateScreenshotGoldens) { write = true; } @@ -282,7 +283,7 @@ Golden file $filename did not match the image generated by the test. final StringBuffer message = StringBuffer(); message.writeln('Golden file $filename did not match the image generated by the test.'); - message.writeln(getPrintableDiffFilesInfo(diff.rate, _kMaxDiffRateFailure)); + message.writeln(getPrintableDiffFilesInfo(diff.rate, maxRateDiffFailure)); message.writeln('You can view the test report in your browser by opening:'); // Cirrus cannot serve HTML pages generated by build jobs, so we @@ -314,7 +315,7 @@ Golden file $filename did not match the image generated by the test. message.writeln('Golden file: ${expectedFile.path}'); message.writeln('Actual file: ${actualFile.path}'); - if (diff.rate < _kMaxDiffRateFailure) { + if (diff.rate < maxRateDiffFailure) { // Issue a warning but do not fail the test. print('WARNING:'); print(message); diff --git a/lib/web_ui/test/golden_tests/engine/draw_vertices_golden_test.dart b/lib/web_ui/test/golden_tests/engine/draw_vertices_golden_test.dart index 7103209902f0a..64edabfee6f24 100644 --- a/lib/web_ui/test/golden_tests/engine/draw_vertices_golden_test.dart +++ b/lib/web_ui/test/golden_tests/engine/draw_vertices_golden_test.dart @@ -28,7 +28,9 @@ void main() async { try { sceneElement.append(engineCanvas.rootElement); html.document.body.append(sceneElement); - await matchGoldenFile('$fileName.png', region: region, write: write); + // Set rate to 0.66% for webGL difference across platforms. + await matchGoldenFile('$fileName.png', region: region, write: write, + maxDiffRate: 0.66 / 100.0); } finally { // The page is reused across tests, so remove the element after taking the // golden screenshot. From 98a3eeae351a48de864677945a750ee8006d17cb Mon Sep 17 00:00:00 2001 From: ferhatb Date: Mon, 14 Oct 2019 10:40:30 -0700 Subject: [PATCH 13/15] Update golden_tester with maxDiffRate parameter --- .../web_engine_tester/lib/golden_tester.dart | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/web_sdk/web_engine_tester/lib/golden_tester.dart b/web_sdk/web_engine_tester/lib/golden_tester.dart index 369a6270b83dd..1280fa4c64ae3 100644 --- a/web_sdk/web_engine_tester/lib/golden_tester.dart +++ b/web_sdk/web_engine_tester/lib/golden_tester.dart @@ -21,12 +21,24 @@ Future _callScreenshotServer(dynamic requestData) async { } /// Attempts to match the current browser state with the screenshot [filename]. -Future matchGoldenFile(String filename, { bool write = false, Rect region = null }) async { - final String response = await _callScreenshotServer({ +Future matchGoldenFile(String filename, + {bool write = false, Rect region = null, double maxDiffRate = null}) async { + Map serverParams = { 'filename': filename, 'write': write, - 'region': region == null ? null : {'x': region.left, 'y': region.top, 'width': region.width, 'height': region.height}, - }); + 'region': region == null + ? null + : { + 'x': region.left, + 'y': region.top, + 'width': region.width, + 'height': region.height + } + }; + if (maxDiffRate != null) { + serverParams['maxdiffrate'] = maxDiffRate; + } + final String response = await _callScreenshotServer(serverParams); if (response == 'OK') { // Pass return; From f4d0ab1c0380a628091a8103c104c9b3c650c0c8 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Mon, 14 Oct 2019 10:54:36 -0700 Subject: [PATCH 14/15] Rename maxRate --- lib/web_ui/dev/test_platform.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/web_ui/dev/test_platform.dart b/lib/web_ui/dev/test_platform.dart index 1412b43d6e1d9..3ad153e4b8101 100644 --- a/lib/web_ui/dev/test_platform.dart +++ b/lib/web_ui/dev/test_platform.dart @@ -146,13 +146,13 @@ class BrowserPlatform extends PlatformPlugin { final Map requestData = json.decode(payload); final String filename = requestData['filename']; final bool write = requestData['write']; - final double maxRateDiff = requestData['maxratediff']; + final double maxDiffRate = requestData['maxdiffrate']; final Map region = requestData['region']; - final String result = await _diffScreenshot(filename, write, maxRateDiff ?? _kMaxDiffRateFailure, region); + final String result = await _diffScreenshot(filename, write, maxDiffRate ?? _kMaxDiffRateFailure, region); return shelf.Response.ok(json.encode(result)); } - Future _diffScreenshot(String filename, bool write, double maxRateDiffFailure, [ Map region ]) async { + Future _diffScreenshot(String filename, bool write, double maxDiffRateFailure, [ Map region ]) async { if (doUpdateScreenshotGoldens) { write = true; } @@ -283,7 +283,7 @@ Golden file $filename did not match the image generated by the test. final StringBuffer message = StringBuffer(); message.writeln('Golden file $filename did not match the image generated by the test.'); - message.writeln(getPrintableDiffFilesInfo(diff.rate, maxRateDiffFailure)); + message.writeln(getPrintableDiffFilesInfo(diff.rate, maxDiffRateFailure)); message.writeln('You can view the test report in your browser by opening:'); // Cirrus cannot serve HTML pages generated by build jobs, so we @@ -315,7 +315,7 @@ Golden file $filename did not match the image generated by the test. message.writeln('Golden file: ${expectedFile.path}'); message.writeln('Actual file: ${actualFile.path}'); - if (diff.rate < maxRateDiffFailure) { + if (diff.rate < maxDiffRateFailure) { // Issue a warning but do not fail the test. print('WARNING:'); print(message); From 41525cb2e175e77a0e57da473915042927d081fa Mon Sep 17 00:00:00 2001 From: ferhatb Date: Mon, 14 Oct 2019 12:42:21 -0700 Subject: [PATCH 15/15] Add assert for positions.length --- lib/web_ui/lib/src/engine/recording_canvas.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/web_ui/lib/src/engine/recording_canvas.dart b/lib/web_ui/lib/src/engine/recording_canvas.dart index d9d5b34fd89b1..fe6816d1e2f86 100644 --- a/lib/web_ui/lib/src/engine/recording_canvas.dart +++ b/lib/web_ui/lib/src/engine/recording_canvas.dart @@ -361,6 +361,7 @@ class RecordingCanvas { _hasArbitraryPaint = true; _didDraw = true; final Float32List positions = vertices.positions; + assert(positions.length >= 2); double minValueX, maxValueX, minValueY, maxValueY; minValueX = maxValueX = positions[0]; minValueY = maxValueY = positions[1];