From 33a96c8a4f58e3bcbde6c87b2185d620599f4f06 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Mon, 12 Oct 2020 16:22:39 -0700 Subject: [PATCH 01/14] Update paintStyle to pass shaderBounds to gradient --- lib/web_ui/lib/src/engine/bitmap_canvas.dart | 58 +++++++++++++++---- lib/web_ui/lib/src/engine/canvas_pool.dart | 4 +- .../lib/src/engine/html/shaders/shader.dart | 48 ++++++++++++--- lib/web_ui/lib/src/ui/painting.dart | 2 +- 4 files changed, 88 insertions(+), 24 deletions(-) diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index 96f0c304950f8..698f9b07d78ad 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -208,8 +208,8 @@ class BitmapCanvas extends EngineCanvas { } /// Sets the global paint styles to correspond to [paint]. - void _setUpPaint(SurfacePaintData paint) { - _canvasPool.contextHandle.setUpPaint(paint); + void _setUpPaint(SurfacePaintData paint, ui.Rect? shaderBounds) { + _canvasPool.contextHandle.setUpPaint(paint, shaderBounds); } void _tearDownPaint() { @@ -292,56 +292,61 @@ class BitmapCanvas extends EngineCanvas { @override void drawLine(ui.Offset p1, ui.Offset p2, SurfacePaintData paint) { - _setUpPaint(paint); + ui.Rect? shaderBounds = (paint.shader != null) ? + ui.Rect.fromPoints(p1, p2) : null; + _setUpPaint(paint, shaderBounds); _canvasPool.strokeLine(p1, p2); _tearDownPaint(); } @override void drawPaint(SurfacePaintData paint) { - _setUpPaint(paint); + ui.Rect? shaderBounds = (paint.shader != null) ? + _computeScreenBounds(_canvasPool._currentTransform) : null; + _setUpPaint(paint, shaderBounds); _canvasPool.fill(); _tearDownPaint(); } @override void drawRect(ui.Rect rect, SurfacePaintData paint) { - _setUpPaint(paint); + _setUpPaint(paint, rect); _canvasPool.drawRect(rect, paint.style); _tearDownPaint(); } @override void drawRRect(ui.RRect rrect, SurfacePaintData paint) { - _setUpPaint(paint); + _setUpPaint(paint, rrect.outerRect); _canvasPool.drawRRect(rrect, paint.style); _tearDownPaint(); } @override void drawDRRect(ui.RRect outer, ui.RRect inner, SurfacePaintData paint) { - _setUpPaint(paint); + _setUpPaint(paint, outer.outerRect); _canvasPool.drawDRRect(outer, inner, paint.style); _tearDownPaint(); } @override void drawOval(ui.Rect rect, SurfacePaintData paint) { - _setUpPaint(paint); + _setUpPaint(paint, rect); _canvasPool.drawOval(rect, paint.style); _tearDownPaint(); } @override void drawCircle(ui.Offset c, double radius, SurfacePaintData paint) { - _setUpPaint(paint); + _setUpPaint(paint, paint.shader != null + ? ui.Rect.fromCircle(center: c, radius: radius) : null); _canvasPool.drawCircle(c, radius, paint.style); _tearDownPaint(); } @override void drawPath(ui.Path path, SurfacePaintData paint) { - _setUpPaint(paint); + _setUpPaint(paint, paint.shader != null ? path.getBounds() : null); _canvasPool.drawPath(path, paint.style); _tearDownPaint(); } @@ -655,7 +660,7 @@ class BitmapCanvas extends EngineCanvas { ctx.font = style.cssFontString; _cachedLastStyle = style; } - _setUpPaint(paragraph._paint!.paintData); + _setUpPaint(paragraph._paint!.paintData, null); double y = offset.dy + paragraph.alphabeticBaseline; final int len = lines.length; for (int i = 0; i < len; i++) { @@ -761,7 +766,7 @@ class BitmapCanvas extends EngineCanvas { _drawPointsPaint.strokeWidth = paint.strokeWidth; _drawPointsPaint.maskFilter = paint.maskFilter; - _setUpPaint(_drawPointsPaint); + _setUpPaint(_drawPointsPaint, null); _canvasPool.drawPoints(pointMode, points, paint.strokeWidth! / 2.0); _tearDownPaint(); } @@ -771,6 +776,34 @@ class BitmapCanvas extends EngineCanvas { _canvasPool.endOfPaint(); _elementCache?.commitFrame(); } + + /// Computes paint bounds given [targetTransform] to completely cover window + /// viewport. + ui.Rect _computeScreenBounds(Matrix4 targetTransform) { + final Matrix4 inverted = targetTransform.clone()..invert(); + final double dpr = ui.window.devicePixelRatio; + final double width = ui.window.physicalSize.width * dpr; + final double height = ui.window.physicalSize.height * dpr; + Vector3 topLeft = inverted.perspectiveTransform(Vector3(0, 0, 0)); + Vector3 topRight = inverted.perspectiveTransform(Vector3(width, 0, 0)); + Vector3 bottomRight = + inverted.perspectiveTransform(Vector3(width, height, 0)); + Vector3 bottomLeft = inverted.perspectiveTransform(Vector3(0, height, 0)); + return ui.Rect.fromLTRB( + math.min(topLeft.x, + math.min(topRight.x, math.min(bottomRight.x, bottomLeft.x))) - + bounds.left, + math.min(topLeft.y, + math.min(topRight.y, math.min(bottomRight.y, bottomLeft.y))) - + bounds.top, + math.max(topLeft.x, + math.max(topRight.x, math.max(bottomRight.x, bottomLeft.x))) - + bounds.left, + math.max(topLeft.y, + math.max(topRight.y, math.max(bottomRight.y, bottomLeft.y))) - + bounds.top, + ); + } } String? _stringForBlendMode(ui.BlendMode? blendMode) { @@ -966,3 +999,4 @@ String _maskFilterToCanvasFilter(ui.MaskFilter? maskFilter) { } } + diff --git a/lib/web_ui/lib/src/engine/canvas_pool.dart b/lib/web_ui/lib/src/engine/canvas_pool.dart index b0c3a7d2f3388..7bed83613d978 100644 --- a/lib/web_ui/lib/src/engine/canvas_pool.dart +++ b/lib/web_ui/lib/src/engine/canvas_pool.dart @@ -761,7 +761,7 @@ class ContextStateHandle { /// Sets paint properties on the current canvas. /// /// [tearDownPaint] must be called after calling this method. - void setUpPaint(SurfacePaintData paint) { + void setUpPaint(SurfacePaintData paint, ui.Rect? shaderBounds) { if (assertionsEnabled) { assert(!_debugIsPaintSetUp); _debugIsPaintSetUp = true; @@ -776,7 +776,7 @@ class ContextStateHandle { if (paint.shader != null) { final EngineGradient engineShader = paint.shader as EngineGradient; final Object paintStyle = - engineShader.createPaintStyle(_canvasPool.context); + engineShader.createPaintStyle(_canvasPool.context, shaderBounds); fillStyle = paintStyle; strokeStyle = paintStyle; } else if (paint.color != null) { diff --git a/lib/web_ui/lib/src/engine/html/shaders/shader.dart b/lib/web_ui/lib/src/engine/html/shaders/shader.dart index 4dae88f674bf9..434bbb090b183 100644 --- a/lib/web_ui/lib/src/engine/html/shaders/shader.dart +++ b/lib/web_ui/lib/src/engine/html/shaders/shader.dart @@ -10,26 +10,53 @@ abstract class EngineGradient implements ui.Gradient { EngineGradient._(); /// Creates a fill style to be used in painting. - Object createPaintStyle(html.CanvasRenderingContext2D? ctx); + Object createPaintStyle(html.CanvasRenderingContext2D? ctx, + ui.Rect? shaderBounds); } class GradientSweep extends EngineGradient { GradientSweep(this.center, this.colors, this.colorStops, this.tileMode, - this.startAngle, this.endAngle, this.matrix4) + this.startAngle, this.endAngle, Float64List? matrix) : assert(_offsetIsValid(center)), assert(colors != null), // ignore: unnecessary_null_comparison assert(tileMode != null), // ignore: unnecessary_null_comparison assert(startAngle != null), // ignore: unnecessary_null_comparison assert(endAngle != null), // ignore: unnecessary_null_comparison assert(startAngle < endAngle), - assert(matrix4 == null || _matrix4IsValid(matrix4)), + this.matrix4 = matrix == null ? null : _FastMatrix64(matrix), super._() { _validateColorStops(colors, colorStops); } @override - Object createPaintStyle(html.CanvasRenderingContext2D? ctx) { - throw UnimplementedError(); + Object createPaintStyle(html.CanvasRenderingContext2D? ctx, + ui.Rect? shaderBounds) + { + _FastMatrix64? matrix4 = this.matrix4; + html.CanvasGradient gradient; + + if (matrix4 != null) { + matrix4.transform(1, 1); + double width = matrix4.transformedX; + double height = matrix4.transformedY; + gradient = ctx!.createLinearGradient(shaderBounds!.left, shaderBounds.top, + shaderBounds.right, shaderBounds.top); + } else { + gradient = ctx!.createLinearGradient(shaderBounds!.left, shaderBounds.top, + shaderBounds.right, shaderBounds.top); + } + + final List? colorStops = this.colorStops; + if (colorStops == null) { + assert(colors.length == 2); + gradient.addColorStop(0, colorToCssString(colors[0])!); + gradient.addColorStop(1, colorToCssString(colors[1])!); + return gradient; + } + for (int i = 0; i < colors.length; i++) { + gradient.addColorStop(colorStops[i], colorToCssString(colors[i])!); + } + return gradient; } final ui.Offset center; @@ -38,7 +65,7 @@ class GradientSweep extends EngineGradient { final ui.TileMode tileMode; final double startAngle; final double endAngle; - final Float32List? matrix4; + final _FastMatrix64? matrix4; } class GradientLinear extends EngineGradient { @@ -68,7 +95,8 @@ class GradientLinear extends EngineGradient { final _FastMatrix64? matrix4; @override - html.CanvasGradient createPaintStyle(html.CanvasRenderingContext2D? ctx) { + html.CanvasGradient createPaintStyle(html.CanvasRenderingContext2D? ctx, + ui.Rect? shaderBounds) { _FastMatrix64? matrix4 = this.matrix4; html.CanvasGradient gradient; if (matrix4 != null) { @@ -115,7 +143,8 @@ class GradientRadial extends EngineGradient { final Float32List? matrix4; @override - Object createPaintStyle(html.CanvasRenderingContext2D? ctx) { + Object createPaintStyle(html.CanvasRenderingContext2D? ctx, + ui.Rect? shaderBounds) { if (!experimentalUseSkia) { if (tileMode != ui.TileMode.clamp) { throw UnimplementedError( @@ -154,7 +183,8 @@ class GradientConical extends EngineGradient { final Float32List? matrix4; @override - Object createPaintStyle(html.CanvasRenderingContext2D? ctx) { + Object createPaintStyle(html.CanvasRenderingContext2D? ctx, + ui.Rect? shaderBounds) { throw UnimplementedError(); } } diff --git a/lib/web_ui/lib/src/ui/painting.dart b/lib/web_ui/lib/src/ui/painting.dart index a76cddb602714..70cf1a9a0fac3 100644 --- a/lib/web_ui/lib/src/ui/painting.dart +++ b/lib/web_ui/lib/src/ui/painting.dart @@ -321,7 +321,7 @@ abstract class Gradient extends Shader { ? engine.CkGradientSweep(center, colors, colorStops, tileMode, startAngle, endAngle, matrix4 != null ? engine.toMatrix32(matrix4) : null) : engine.GradientSweep(center, colors, colorStops, tileMode, startAngle, - endAngle, matrix4 != null ? engine.toMatrix32(matrix4) : null); + endAngle, matrix4); } abstract class Image { From 876275374333e53bf9344dfe806d45df43461a95 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Tue, 13 Oct 2020 09:30:07 -0700 Subject: [PATCH 02/14] implement normalized gradient --- ci/licenses_golden/licenses_flutter | 1 + lib/web_ui/lib/src/engine.dart | 1 + .../html/shaders/normalized_gradient.dart | 120 +++++++++++++++++ .../shaders/normalized_gradient_test.dart | 125 ++++++++++++++++++ 4 files changed, 247 insertions(+) create mode 100644 lib/web_ui/lib/src/engine/html/shaders/normalized_gradient.dart create mode 100644 lib/web_ui/test/engine/surface/shaders/normalized_gradient_test.dart diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 2553764df42c2..b7e0725c88821 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -489,6 +489,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/recording_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/render_vertices.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/scene.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/scene_builder.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/shaders/normalized_gradient.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/shaders/shader.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/shaders/shader_builder.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/surface.dart diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index d8b372ba5387a..8799a62ea4b19 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -89,6 +89,7 @@ part 'engine/html/recording_canvas.dart'; part 'engine/html/render_vertices.dart'; part 'engine/html/scene.dart'; part 'engine/html/scene_builder.dart'; +part 'engine/html/shaders/normalized_gradient.dart'; part 'engine/html/shaders/shader.dart'; part 'engine/html/shaders/shader_builder.dart'; part 'engine/html/surface.dart'; diff --git a/lib/web_ui/lib/src/engine/html/shaders/normalized_gradient.dart b/lib/web_ui/lib/src/engine/html/shaders/normalized_gradient.dart new file mode 100644 index 0000000000000..5a1b5145de4eb --- /dev/null +++ b/lib/web_ui/lib/src/engine/html/shaders/normalized_gradient.dart @@ -0,0 +1,120 @@ +// 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. + +// @dart = 2.10 +part of engine; + +/// Converts colors and stops to typed array of bias, scale and threshold to use +/// in shaders. +/// +/// A color is generated by taking a t value [0..1] and computing +/// t * scale + bias. +/// +/// Example: For stops 0.0 t1, t2, 1.0 and colors c0, c1, c2, c3 +/// Given t1 colors, {List? stops}) { + // If colorStops is not provided, then only two stops, at 0.0 and 1.0, + // are implied (and colors must therefore only have two entries). + assert(stops != null || colors.length == 2); + stops ??= const [0.0, 1.0]; + final int colorCount = colors.length; + int normalizedCount = colorCount; + bool addFirst = stops[0] != 0.0; + bool addLast = stops.last != 1.0; + if (addFirst) { + normalizedCount++; + } + if (addLast) { + normalizedCount++; + } + final Float32List bias = Float32List(normalizedCount * 4); + final Float32List scale = Float32List(normalizedCount * 4); + final Float32List thresholds = Float32List(4 * ((normalizedCount - 1)~/4 + 1)); + int targetIndex = 0; + int thresholdIndex = 0; + if (addFirst) { + ui.Color c = colors[0]; + bias[targetIndex++] = c.red / 255.0; + bias[targetIndex++] = c.green / 255.0; + bias[targetIndex++] = c.blue / 255.0; + bias[targetIndex++] = c.alpha / 255.0; + thresholds[thresholdIndex++] = 0.0; + } + for (ui.Color c in colors) { + bias[targetIndex++] = c.red / 255.0; + bias[targetIndex++] = c.green / 255.0; + bias[targetIndex++] = c.blue / 255.0; + bias[targetIndex++] = c.alpha / 255.0; + } + for (double stop in stops) { + thresholds[thresholdIndex++] = stop; + } + if (addLast) { + ui.Color c = colors.last; + bias[targetIndex++] = c.red / 255.0; + bias[targetIndex++] = c.green / 255.0; + bias[targetIndex++] = c.blue / 255.0; + bias[targetIndex++] = c.alpha / 255.0; + thresholds[thresholdIndex++] = 1.0; + } + // Now that we have bias for each color stop, we can compute scale based + // on delta between colors. + int lastColorIndex = 4 * (normalizedCount - 1); + for (int i = 0; i < lastColorIndex; i++) { + int thresholdIndex = i >> 2; + scale[i] = (bias[i + 4] - bias[i]) / + (thresholds[thresholdIndex + 1] - thresholds[thresholdIndex]); + } + scale[lastColorIndex] = 0.0; + scale[lastColorIndex + 1] = 0.0; + scale[lastColorIndex + 2] = 0.0; + scale[lastColorIndex + 3] = 0.0; + // Compute bias = colorAtStop - stopValue * (scale). + for (int i = 0; i < normalizedCount; i++) { + double t = thresholds[i]; + int colorIndex = i * 4; + bias[colorIndex] -= t * scale[colorIndex]; + bias[colorIndex + 1] -= t * scale[colorIndex + 1]; + bias[colorIndex + 2] -= t * scale[colorIndex + 2]; + bias[colorIndex + 3] -= t * scale[colorIndex + 3]; + } + return NormalizedGradient._(normalizedCount, thresholds, scale, bias); + } + + /// Sets uniforms for threshold, bias and scale for program. + void setupUniforms(_GlContext gl, _GlProgram glProgram) { + for (int i = 0; i < thresholdCount; i++) { + Object? biasId = gl.getUniformLocation(glProgram.program, 'bias_$i'); + gl.setUniform4f(biasId, _bias[i * 4], _bias[i * 4 + 1], _bias[i * 4 + 2], _bias[i * 4 + 3]); + Object? scaleId = gl.getUniformLocation(glProgram.program, 'scale_$i'); + gl.setUniform4f(scaleId, _scale[i * 4], _scale[i * 4 + 1], _scale[i * 4 + 2], _scale[i * 4 + 3]); + } + for (int i = 0; i < _thresholds.length; i += 4) { + Object? thresId = gl.getUniformLocation(glProgram.program, 'threshold_${i ~/ 4}'); + gl.setUniform4f(thresId, _thresholds[i], _thresholds[i + 1], _thresholds[i + 2], _thresholds[i + 3]); + } + } + + /// Returns bias component at index. + double biasAt(int index) => _bias[index]; + + /// Returns scale component at index. + double scaleAt(int index) => _scale[index]; + + /// Returns threshold at index. + double thresholdAt(int index) => _thresholds[index]; +} diff --git a/lib/web_ui/test/engine/surface/shaders/normalized_gradient_test.dart b/lib/web_ui/test/engine/surface/shaders/normalized_gradient_test.dart new file mode 100644 index 0000000000000..b413bb65a220f --- /dev/null +++ b/lib/web_ui/test/engine/surface/shaders/normalized_gradient_test.dart @@ -0,0 +1,125 @@ +// 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. + +// @dart = 2.6 +import 'package:test/bootstrap/browser.dart'; +import 'package:test/test.dart'; +import 'package:ui/src/engine.dart'; +import 'package:ui/ui.dart' as ui hide window; + +void main() { + internalBootstrapBrowserTest(() => testMain); +} + +void testMain() { + group('Shader Normalized Gradient', () { + test('3 stop at start', () { + NormalizedGradient gradient = NormalizedGradient([ + ui.Color(0xFF000000), ui.Color(0xFFFF7f3f) + ], stops: [0.0, 0.5]); + int res = _computeColorAt(gradient, 0.0); + assert(res == 0xFF000000); + res = _computeColorAt(gradient, 0.25); + assert(res == 0xFF7f3f1f); + res = _computeColorAt(gradient, 0.5); + assert(res == 0xFFFF7f3f); + res = _computeColorAt(gradient, 0.7); + assert(res == 0xFFFF7f3f); + res = _computeColorAt(gradient, 1.0); + assert(res == 0xFFFF7f3f); + }); + + test('3 stop at end', () { + NormalizedGradient gradient = NormalizedGradient([ + ui.Color(0xFF000000), ui.Color(0xFFFF7f3f) + ], stops: [0.5, 1.0]); + int res = _computeColorAt(gradient, 0.0); + assert(res == 0xFF000000); + res = _computeColorAt(gradient, 0.25); + assert(res == 0xFF000000); + res = _computeColorAt(gradient, 0.5); + assert(res == 0xFF000000); + res = _computeColorAt(gradient, 0.75); + assert(res == 0xFF7f3f1f); + res = _computeColorAt(gradient, 1.0); + assert(res == 0xFFFF7f3f); + }); + + test('4 stop', () { + NormalizedGradient gradient = NormalizedGradient([ + ui.Color(0xFF000000), ui.Color(0xFFFF7f3f) + ], stops: [0.25, 0.5]); + int res = _computeColorAt(gradient, 0.0); + assert(res == 0xFF000000); + res = _computeColorAt(gradient, 0.25); + assert(res == 0xFF000000); + res = _computeColorAt(gradient, 0.4); + assert(res == 0xFF994c25); + res = _computeColorAt(gradient, 0.5); + assert(res == 0xFFFF7f3f); + res = _computeColorAt(gradient, 0.75); + assert(res == 0xFFFF7f3f); + res = _computeColorAt(gradient, 1.0); + assert(res == 0xFFFF7f3f); + }); + + test('5 stop', () { + NormalizedGradient gradient = NormalizedGradient([ + ui.Color(0x10000000), ui.Color(0x20FF0000), + ui.Color(0x4000FF00), ui.Color(0x800000FF), + ui.Color(0xFFFFFFFF) + ], stops: [0.0, 0.1, 0.2, 0.5, 1.0]); + int res = _computeColorAt(gradient, 0.0); + assert(res == 0x10000000); + res = _computeColorAt(gradient, 0.05); + assert(res == 0x187f0000); + res = _computeColorAt(gradient, 0.1); + assert(res == 0x20ff0000); + res = _computeColorAt(gradient, 0.15); + assert(res == 0x307f7f00); + res = _computeColorAt(gradient, 0.2); + assert(res == 0x4000ff00); + res = _computeColorAt(gradient, 0.4); + assert(res == 0x6a0054a9); + res = _computeColorAt(gradient, 0.5); + assert(res == 0x800000fe); + res = _computeColorAt(gradient, 0.9); + assert(res == 0xe5ccccff); + res = _computeColorAt(gradient, 1.0); + assert(res == 0xffffffff); + }); + + test('2 stops at ends', () { + NormalizedGradient gradient = NormalizedGradient([ + ui.Color(0x00000000), ui.Color(0xFFFFFFFF) + ]); + int res = _computeColorAt(gradient, 0.0); + assert(res == 0); + res = _computeColorAt(gradient, 1.0); + assert(res == 0xFFFFFFFF); + res = _computeColorAt(gradient, 0.5); + assert(res == 0x7f7f7f7f); + }); + }); +} + +int _computeColorAt(NormalizedGradient gradient, double t) { + int i = 0; + while (t > gradient.thresholdAt(i + 1)) { + ++i; + } + double r = t * gradient.scaleAt(i * 4) + gradient.biasAt(i * 4); + double g = t * gradient.scaleAt(i * 4 + 1) + gradient.biasAt(i * 4 + 1); + double b = t * gradient.scaleAt(i * 4 + 2) + gradient.biasAt(i * 4 + 2); + double a = t * gradient.scaleAt(i * 4 + 3) + gradient.biasAt(i * 4 + 3); + int val = 0; + val |= (a * 0xFF).toInt() & 0xFF; + val<<=8; + val |= (r * 0xFF).toInt() & 0xFF; + val<<=8; + val |= (g * 0xFF).toInt() & 0xFF; + val<<=8; + val |= (b * 0xFF).toInt() & 0xFF; + return val; +} From 4ccd65513631a1af60a95cc434d31f3633f73f95 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Wed, 14 Oct 2020 14:55:23 -0700 Subject: [PATCH 03/14] Sweepgradient shader. NNBD update. --- .../lib/src/engine/html/render_vertices.dart | 415 ++++++++++++------ .../html/shaders/normalized_gradient.dart | 47 +- .../lib/src/engine/html/shaders/shader.dart | 120 ++++- .../engine/html/shaders/shader_builder.dart | 14 +- lib/web_ui/lib/src/engine/util.dart | 6 + lib/web_ui/lib/src/ui/painting.dart | 2 +- .../surface/shaders/shader_builder_test.dart | 2 +- 7 files changed, 436 insertions(+), 170 deletions(-) diff --git a/lib/web_ui/lib/src/engine/html/render_vertices.dart b/lib/web_ui/lib/src/engine/html/render_vertices.dart index 0720dd4a2a864..5195a8fdeae10 100644 --- a/lib/web_ui/lib/src/engine/html/render_vertices.dart +++ b/lib/web_ui/lib/src/engine/html/render_vertices.dart @@ -55,7 +55,7 @@ void initWebGl() { } void disposeWebGl() { - _OffscreenCanvas.dispose(); + _GlContextCache.dispose(); _glRenderer = null; } @@ -69,6 +69,9 @@ abstract class _GlRenderer { ui.BlendMode blendMode, SurfacePaintData paint); + Object? drawRect(ui.Rect targetRect, _GlContext gl, _GlProgram glProgram, + NormalizedGradient gradient, int widthInPixels, int heightInPixels); + void drawHairline(html.CanvasRenderingContext2D? _ctx, Float32List positions); } @@ -77,6 +80,9 @@ abstract class _GlRenderer { /// This class gets instantiated on demand by Vertices constructor. For apps /// that don't use Vertices WebGlRenderer will be removed from release binary. class _WebGlRenderer implements _GlRenderer { + + /// Cached vertex shader reused by [drawVertices] and gradients. + static String? _baseVertexShader; @override void drawVertices( html.CanvasRenderingContext2D? context, @@ -116,13 +122,13 @@ class _WebGlRenderer implements _GlRenderer { if (widthInPixels == 0 || heightInPixels == 0) { return; } - final String vertexShader = _writeVerticesVertexShader(); + final String vertexShader = writeBaseVertexShader(); final String fragmentShader = _writeVerticesFragmentShader(); - _GlContext gl = - _OffscreenCanvas.createGlContext(widthInPixels, heightInPixels)!; + _GlContext gl = _GlContextCache.createGlContext(widthInPixels, heightInPixels)!; + _GlProgram glProgram = gl.useAndCacheProgram(vertexShader, fragmentShader)!; - Object? transformUniform = gl.getUniformLocation(glProgram.program, + Object transformUniform = gl.getUniformLocation(glProgram.program, 'u_ctransform'); Matrix4 transformAtOffset = transform.clone() ..translate(-offsetX, -offsetY); @@ -130,10 +136,10 @@ class _WebGlRenderer implements _GlRenderer { // Set uniform to scale 0..width/height pixels coordinates to -1..1 // clipspace range and flip the Y axis. - Object? resolution = gl.getUniformLocation(glProgram.program, 'u_scale'); + Object resolution = gl.getUniformLocation(glProgram.program, 'u_scale'); gl.setUniform4f(resolution, 2.0 / widthInPixels.toDouble(), -2.0 / heightInPixels.toDouble(), 1, 1); - Object? shift = gl.getUniformLocation(glProgram.program, 'u_shift'); + Object shift = gl.getUniformLocation(glProgram.program, 'u_shift'); gl.setUniform4f(shift, -1, 1, 0, 0); // Setup geometry. @@ -141,9 +147,9 @@ class _WebGlRenderer implements _GlRenderer { assert(positionsBuffer != null); // ignore: unnecessary_null_comparison gl.bindArrayBuffer(positionsBuffer); gl.bufferData(positions, gl.kStaticDraw); - Object? positionLoc = gl.getAttribLocation(glProgram.program, 'position'); + Object? positionLoc = gl.getUniformLocation(glProgram.program, 'position'); js_util.callMethod( - gl.glContext!, 'vertexAttribPointer', [ + gl.glContext, 'vertexAttribPointer', [ positionLoc, 2, gl.kFloat, false, 0, 0, ]); gl.enableVertexAttribArray(0); @@ -153,8 +159,8 @@ class _WebGlRenderer implements _GlRenderer { gl.bindArrayBuffer(colorsBuffer); // Buffer kBGRA_8888. gl.bufferData(vertices._colors, gl.kStaticDraw); - Object? colorLoc = gl.getAttribLocation(glProgram.program, 'color'); - js_util.callMethod(gl.glContext!, 'vertexAttribPointer', + Object colorLoc = gl.getUniformLocation(glProgram.program, 'color'); + js_util.callMethod(gl.glContext, 'vertexAttribPointer', [colorLoc, 4, gl.kUnsignedByte, true, 0, 0]); gl.enableVertexAttribArray(1); gl.clear(); @@ -167,7 +173,88 @@ class _WebGlRenderer implements _GlRenderer { context.restore(); } - /// Vertex shader transforms pixel space [Vertices.positions] to + /// Renders a rectangle using given program into an image resource. + Object? drawRect(ui.Rect targetRect, _GlContext gl, _GlProgram glProgram, + NormalizedGradient gradient, int widthInPixels, int heightInPixels) { + // Setup rectangle coordinates. + final Float32List coordinates = Float32List.fromList( + [ + targetRect.left, targetRect.top, + targetRect.right, targetRect.top, + targetRect.right, targetRect.bottom, + targetRect.left, targetRect.bottom, + ]); + final Uint16List indices = Uint16List.fromList( + [ + 0, 1, 2, 2, 3, 0 + ] + ); + + Matrix4 transform = Matrix4.identity(); + + double offsetX = 0, + offsetY = 0; + + Object transformUniform = gl.getUniformLocation( + glProgram.program, 'u_ctransform'); + Matrix4 transformAtOffset = transform.clone() + ..translate(-offsetX, -offsetY); + gl.setUniformMatrix4fv(transformUniform, false, transformAtOffset.storage); + + // Set uniform to scale 0..width/height pixels coordinates to -1..1 + // clipspace range and flip the Y axis. + Object resolution = gl.getUniformLocation(glProgram.program, 'u_scale'); + gl.setUniform4f(resolution, 2.0 / widthInPixels.toDouble(), + -2.0 / heightInPixels.toDouble(), 1, 1); + Object shift = gl.getUniformLocation(glProgram.program, 'u_shift'); + gl.setUniform4f(shift, -1, 1, 0, 0); + + // Setup geometry. + Object positionsBuffer = gl.createBuffer()!; + assert(positionsBuffer != null); // ignore: unnecessary_null_comparison + gl.bindArrayBuffer(positionsBuffer); + gl.bufferData(coordinates, gl.kStaticDraw); + // Point an attribute to the currently bound vertex buffer object. + 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. + Int32List colors = Int32List.fromList([ + 0xFF00FF00, 0xFF0000FF, 0xFFFFFF00, 0xFF00FFFF, + ]); + gl.bufferData(colors, gl.kStaticDraw); + js_util.callMethod(gl.glContext, 'vertexAttribPointer', + [1, 4, gl.kUnsignedByte, true, 0, 0]); + gl.enableVertexAttribArray(1); + + Object? indexBuffer = gl.createBuffer(); + gl.bindElementArrayBuffer(indexBuffer); + gl.bufferElementData(indices, gl.kStaticDraw); + + // Object uTime = gl.getUniformLocation(glProgram.program, 'u_time'); + // gl.setUniform1f(uTime, timeValue); + Object uRes = gl.getUniformLocation(glProgram.program, 'u_resolution'); + gl.setUniform2f(uRes, widthInPixels.toDouble(), heightInPixels.toDouble()); + + gl.clear(); + gl.viewport(0, 0, widthInPixels.toDouble(), heightInPixels.toDouble()); + + gl.drawElements(gl.kTriangles, indices.length, gl.kUnsignedShort); + + Object? image = gl.readPatternData(); + + gl.bindArrayBuffer(null); + gl.bindElementArrayBuffer(null); + + return image; + } + + /// Creates a vertex shader transforms pixel space [Vertices.positions] to /// final clipSpace -1..1 coordinates with inverted Y Axis. /// #version 300 es /// layout (location=0) in vec4 position; @@ -180,18 +267,22 @@ class _WebGlRenderer implements _GlRenderer { /// gl_Position = ((u_ctransform * position) * u_scale) + u_shift; /// v_color = color.zyxw; /// } - String _writeVerticesVertexShader() { - ShaderBuilder builder = ShaderBuilder(webGLVersion); - builder.addIn(ShaderType.kVec4, name: 'position'); - builder.addIn(ShaderType.kVec4, name: 'color'); - builder.addUniform(ShaderType.kMat4, name: 'u_ctransform'); - builder.addUniform(ShaderType.kVec4, name: 'u_scale'); - builder.addUniform(ShaderType.kVec4, name: 'u_shift'); - builder.addOut(ShaderType.kVec4, name: 'v_color'); - ShaderMethod method = builder.addMethod('main'); - method.addStatement('gl_Position = ((u_ctransform * position) * u_scale) + u_shift;'); - method.addStatement('v_color = color.zyxw;'); - return builder.build(); + static String writeBaseVertexShader() { + if (_baseVertexShader == null) { + ShaderBuilder builder = ShaderBuilder(webGLVersion); + builder.addIn(ShaderType.kVec4, name: 'position'); + builder.addIn(ShaderType.kVec4, name: 'color'); + builder.addUniform(ShaderType.kMat4, name: 'u_ctransform'); + builder.addUniform(ShaderType.kVec4, name: 'u_scale'); + builder.addUniform(ShaderType.kVec4, name: 'u_shift'); + builder.addOut(ShaderType.kVec4, name: 'v_color'); + ShaderMethod method = builder.addMethod('main'); + method.addStatement( + 'gl_Position = ((u_ctransform * position) * u_scale) + u_shift;'); + method.addStatement('v_color = color.zyxw;'); + _baseVertexShader = builder.build(); + } + return _baseVertexShader!; } /// This fragment shader enables Int32List of colors to be passed directly @@ -339,22 +430,24 @@ Float32List _convertVertexPositions(ui.VertexMode mode, Float32List positions) { /// Compiled and cached gl program. class _GlProgram { - final Object? program; + final Object program; _GlProgram(this.program); } /// JS Interop helper for webgl apis. class _GlContext { - final Object? glContext; + final Object glContext; final bool isOffscreen; dynamic _kCompileStatus; dynamic _kArrayBuffer; + dynamic _kElementArrayBuffer; dynamic _kStaticDraw; dynamic _kFloat; dynamic _kColorBufferBit; dynamic _kTriangles; dynamic _kLinkStatus; dynamic _kUnsignedByte; + dynamic _kUnsignedShort; dynamic _kRGBA; Object? _canvas; int? _widthInPixels; @@ -362,7 +455,7 @@ class _GlContext { static late Map _programCache; _GlContext.fromOffscreenCanvas(html.OffscreenCanvas canvas) - : glContext = canvas.getContext('webgl2', {'premultipliedAlpha': false}), + : glContext = canvas.getContext('webgl2', {'premultipliedAlpha': false})!, isOffscreen = true { _programCache = {}; _canvas = canvas; @@ -370,7 +463,7 @@ class _GlContext { _GlContext.fromCanvas(html.CanvasElement canvas, bool useWebGl1) : glContext = canvas.getContext(useWebGl1 ? 'webgl' : 'webgl2', - {'premultipliedAlpha': false}), + {'premultipliedAlpha': false})!, isOffscreen = false { _programCache = {}; _canvas = canvas; @@ -388,7 +481,7 @@ class _GlContext { // source/destination to draw part of the image data. js_util.callMethod(context, 'drawImage', [_canvas, 0, 0, _widthInPixels, _heightInPixels, - left, top, _widthInPixels, _heightInPixels]); + left, top, _widthInPixels, _heightInPixels]); } _GlProgram? useAndCacheProgram( @@ -399,9 +492,9 @@ class _GlContext { // Create and compile shaders. Object vertexShader = compileShader('VERTEX_SHADER', vertexShaderSource); Object fragmentShader = - compileShader('FRAGMENT_SHADER', fragmentShaderSource); + compileShader('FRAGMENT_SHADER', fragmentShaderSource); // Create a gl program and link shaders. - Object? program = createProgram(); + Object program = createProgram(); attachShader(program, vertexShader); attachShader(program, fragmentShader); linkProgram(program); @@ -417,57 +510,65 @@ class _GlContext { if (shader == null) { throw Exception(error); } - js_util.callMethod(glContext!, 'shaderSource', [shader, source]); - js_util.callMethod(glContext!, 'compileShader', [shader]); + js_util.callMethod(glContext, 'shaderSource', [shader, source]); + js_util.callMethod(glContext, 'compileShader', [shader]); bool shaderStatus = js_util - .callMethod(glContext!, 'getShaderParameter', [shader, compileStatus]); + .callMethod(glContext, 'getShaderParameter', [shader, compileStatus]); if (!shaderStatus) { throw Exception('Shader compilation failed: ${getShaderInfoLog(shader)}'); } return shader; } - Object? createProgram() => - js_util.callMethod(glContext!, 'createProgram', const []); + Object createProgram() => + js_util.callMethod(glContext, 'createProgram', const [])!; void attachShader(Object? program, Object shader) { - js_util.callMethod(glContext!, 'attachShader', [program, shader]); + js_util.callMethod(glContext, 'attachShader', [program, shader]); } - void linkProgram(Object? program) { - js_util.callMethod(glContext!, 'linkProgram', [program]); + void linkProgram(Object program) { + js_util.callMethod(glContext, 'linkProgram', [program]); if (!js_util - .callMethod(glContext!, 'getProgramParameter', [program, kLinkStatus])) { + .callMethod(glContext, 'getProgramParameter', [program, kLinkStatus])) { throw Exception(getProgramInfoLog(program)); } } void useProgram(Object? program) { - js_util.callMethod(glContext!, 'useProgram', [program]); + js_util.callMethod(glContext, 'useProgram', [program]); } Object? createBuffer() => - js_util.callMethod(glContext!, 'createBuffer', const []); + js_util.callMethod(glContext, 'createBuffer', const []); void bindArrayBuffer(Object? buffer) { - js_util.callMethod(glContext!, 'bindBuffer', [kArrayBuffer, buffer]); + js_util.callMethod(glContext, 'bindBuffer', [kArrayBuffer, buffer]); + } + + void bindElementArrayBuffer(Object? buffer) { + js_util.callMethod(glContext, 'bindBuffer', [kElementArrayBuffer, buffer]); } void deleteBuffer(Object buffer) { - js_util.callMethod(glContext!, 'deleteBuffer', [buffer]); + js_util.callMethod(glContext, 'deleteBuffer', [buffer]); } void bufferData(TypedData? data, dynamic type) { - js_util.callMethod(glContext!, 'bufferData', [kArrayBuffer, data, type]); + js_util.callMethod(glContext, 'bufferData', [kArrayBuffer, data, type]); + } + + void bufferElementData(TypedData? data, dynamic type) { + js_util.callMethod(glContext, 'bufferData', [kElementArrayBuffer, data, type]); } void enableVertexAttribArray(int index) { - js_util.callMethod(glContext!, 'enableVertexAttribArray', [index]); + js_util.callMethod(glContext, 'enableVertexAttribArray', [index]); } /// Clear background. void clear() { - js_util.callMethod(glContext!, 'clear', [kColorBufferBit]); + js_util.callMethod(glContext, 'clear', [kColorBufferBit]); } /// Destroys gl context. @@ -476,25 +577,29 @@ class _GlContext { } void deleteProgram(Object program) { - js_util.callMethod(glContext!, 'deleteProgram', [program]); + js_util.callMethod(glContext, 'deleteProgram', [program]); } void deleteShader(Object shader) { - js_util.callMethod(glContext!, 'deleteShader', [shader]); + js_util.callMethod(glContext, 'deleteShader', [shader]); } dynamic _getExtension(String extensionName) => - js_util.callMethod(glContext!, 'getExtension', [extensionName]); + js_util.callMethod(glContext, 'getExtension', [extensionName]); void drawTriangles(int triangleCount, ui.VertexMode vertexMode) { dynamic mode = _triangleTypeFromMode(vertexMode); - js_util.callMethod(glContext!, 'drawArrays', [mode, 0, triangleCount]); + js_util.callMethod(glContext, 'drawArrays', [mode, 0, triangleCount]); + } + + void drawElements(dynamic type, int indexCount, dynamic indexType) { + js_util.callMethod(glContext, 'drawElements', [type, indexCount, indexType, 0]); } /// 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]); + js_util.callMethod(glContext, 'viewport', [x, y, width, height]); } dynamic _triangleTypeFromMode(ui.VertexMode mode) { @@ -509,92 +614,102 @@ class _GlContext { } Object? _createShader(String shaderType) => js_util.callMethod( - glContext!, 'createShader', [js_util.getProperty(glContext!, shaderType)]); + glContext, 'createShader', [js_util.getProperty(glContext, shaderType)]); /// Error state of gl context. - dynamic get error => js_util.callMethod(glContext!, 'getError', const []); + dynamic get error => js_util.callMethod(glContext, 'getError', const []); /// Shader compiler error, if this returns [kFalse], to get details use /// [getShaderInfoLog]. dynamic get compileStatus => - _kCompileStatus ??= js_util.getProperty(glContext!, 'COMPILE_STATUS'); + _kCompileStatus ??= js_util.getProperty(glContext, 'COMPILE_STATUS'); dynamic get kArrayBuffer => - _kArrayBuffer ??= js_util.getProperty(glContext!, 'ARRAY_BUFFER'); + _kArrayBuffer ??= js_util.getProperty(glContext, 'ARRAY_BUFFER'); + + dynamic get kElementArrayBuffer => + _kElementArrayBuffer ??= js_util.getProperty(glContext, + 'ELEMENT_ARRAY_BUFFER'); dynamic get kLinkStatus => - _kLinkStatus ??= js_util.getProperty(glContext!, 'LINK_STATUS'); + _kLinkStatus ??= js_util.getProperty(glContext, 'LINK_STATUS'); - dynamic get kFloat => _kFloat ??= js_util.getProperty(glContext!, 'FLOAT'); + dynamic get kFloat => _kFloat ??= js_util.getProperty(glContext, 'FLOAT'); - dynamic get kRGBA => _kRGBA ??= js_util.getProperty(glContext!, 'RGBA'); + dynamic get kRGBA => _kRGBA ??= js_util.getProperty(glContext, 'RGBA'); dynamic get kUnsignedByte => - _kUnsignedByte ??= js_util.getProperty(glContext!, 'UNSIGNED_BYTE'); + _kUnsignedByte ??= js_util.getProperty(glContext, 'UNSIGNED_BYTE'); + + dynamic get kUnsignedShort => + _kUnsignedShort ??= js_util.getProperty(glContext, 'UNSIGNED_SHORT'); dynamic get kStaticDraw => - _kStaticDraw ??= js_util.getProperty(glContext!, 'STATIC_DRAW'); + _kStaticDraw ??= js_util.getProperty(glContext, 'STATIC_DRAW'); dynamic get kTriangles => - _kTriangles ??= js_util.getProperty(glContext!, 'TRIANGLES'); + _kTriangles ??= js_util.getProperty(glContext, 'TRIANGLES'); dynamic get kTriangleFan => - _kTriangles ??= js_util.getProperty(glContext!, 'TRIANGLE_FAN'); + _kTriangles ??= js_util.getProperty(glContext, 'TRIANGLE_FAN'); dynamic get kTriangleStrip => - _kTriangles ??= js_util.getProperty(glContext!, 'TRIANGLE_STRIP'); + _kTriangles ??= js_util.getProperty(glContext, 'TRIANGLE_STRIP'); dynamic get kColorBufferBit => - _kColorBufferBit ??= js_util.getProperty(glContext!, 'COLOR_BUFFER_BIT'); + _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]); + Object getUniformLocation(Object program, String uniformName) { + Object? res = js_util + .callMethod(glContext, 'getUniformLocation', [program, uniformName]); + if (res == null) { + throw Exception('$uniformName not found'); + } else { + return res; + } } - /// Returns reference to attribute in program. - Object? getAttribLocation(Object? program, String uniformName) { + /// Sets float uniform value. + void setUniform1f(Object uniform, double value) { return js_util - .callMethod(glContext!, 'getAttribLocation', - [program, uniformName]); + .callMethod(glContext, 'uniform1f', [uniform, value]); } /// Sets vec2 uniform values. void setUniform2f(Object uniform, double value1, double value2) { return js_util - .callMethod(glContext!, 'uniform2f', [uniform, value1, value2]); + .callMethod(glContext, 'uniform2f', [uniform, value1, value2]); } /// Sets vec4 uniform values. - void setUniform4f(Object? uniform, double value1, double value2, double value3, + void setUniform4f(Object uniform, double value1, double value2, double value3, double value4) { return js_util.callMethod( - glContext!, 'uniform4f', [uniform, value1, value2, value3, value4]); + glContext, 'uniform4f', [uniform, value1, value2, value3, value4]); } /// Sets mat4 uniform values. - void setUniformMatrix4fv(Object? uniform, bool transpose, Float32List? value) { + void setUniformMatrix4fv(Object uniform, bool transpose, Float32List value) { return js_util.callMethod( - glContext!, 'uniformMatrix4fv', [uniform, transpose, value]); + glContext, 'uniformMatrix4fv', [uniform, transpose, value]); } /// Shader compile error log. dynamic getShaderInfoLog(Object glShader) { - return js_util.callMethod(glContext!, 'getShaderInfoLog', [glShader]); + return js_util.callMethod(glContext, 'getShaderInfoLog', [glShader]); } /// 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]); + String? getProgramInfoLog(Object glProgram) { + return js_util.callMethod(glContext, 'getProgramInfoLog', [glProgram]); } int? get drawingBufferWidth => - js_util.getProperty(glContext!, 'drawingBufferWidth'); + js_util.getProperty(glContext, 'drawingBufferWidth'); int? get drawingBufferHeight => - js_util.getProperty(glContext!, 'drawingBufferWidth'); + js_util.getProperty(glContext, 'drawingBufferWidth'); html.ImageData readImageData() { if (browserEngine == BrowserEngine.webkit || @@ -603,8 +718,8 @@ class _GlContext { final int bufferWidth = _widthInPixels!; final int bufferHeight = _heightInPixels!; final Uint8List pixels = - Uint8List(bufferWidth * bufferHeight * kBytesPerPixel); - js_util.callMethod(glContext!, 'readPixels', + Uint8List(bufferWidth * bufferHeight * kBytesPerPixel); + js_util.callMethod(glContext, 'readPixels', [0, 0, bufferWidth, bufferHeight, kRGBA, kUnsignedByte, pixels]); return html.ImageData( Uint8ClampedList.fromList(pixels), bufferWidth, bufferHeight); @@ -613,79 +728,101 @@ class _GlContext { final int bufferWidth = _widthInPixels!; final int bufferHeight = _heightInPixels!; final Uint8ClampedList pixels = - Uint8ClampedList(bufferWidth * bufferHeight * kBytesPerPixel); - js_util.callMethod(glContext!, 'readPixels', + Uint8ClampedList(bufferWidth * bufferHeight * kBytesPerPixel); + js_util.callMethod(glContext, 'readPixels', [0, 0, bufferWidth, bufferHeight, kRGBA, kUnsignedByte, pixels]); return html.ImageData(pixels, bufferWidth, bufferHeight); } } + + /// Returns image data in a form that can be used to create Canvas + /// context patterns. + Object? readPatternData() { + // When using OffscreenCanvas and transferToImageBitmap is supported by + // browser create ImageBitmap otherwise use more expensive canvas + // allocation. + if (_canvas != null && + js_util.hasProperty(_canvas!, 'transferToImageBitmap')) { + js_util.callMethod(_canvas!, 'getContext', ['webgl2']); + Object?imageBitmap = js_util.callMethod(_canvas!, 'transferToImageBitmap', + []); + return imageBitmap; + } else { + html.ImageData imageData = readImageData(); + html.CanvasElement canvas = html.CanvasElement(width: _widthInPixels, height: _heightInPixels); + final html.CanvasRenderingContext2D ctx = canvas.context2D; + ctx.putImageData(imageData, 0 , 0); + return canvas; + } + } } -/// Shared Cached OffscreenCanvas for webgl rendering to image. -class _OffscreenCanvas { - static html.OffscreenCanvas? _canvas; - static int _maxPixelWidth = 0; - static int _maxPixelHeight = 0; - static html.CanvasElement? _glCanvas; - static _GlContext? _cachedContext; +/// Polyfill for html.OffscreenCanvas that is not supported on some browsers. +class _OffScreenCanvas { + html.OffscreenCanvas? _canvas; + html.CanvasElement? _glCanvas; + int width; + int height; - _OffscreenCanvas(int width, int height) { - assert(width > 0 && height > 0); - if (width > _maxPixelWidth || height > _maxPixelHeight) { - // Allocate bigger offscreen canvas. + _OffScreenCanvas(this.width, this.height) { + if (_OffScreenCanvas.supported) { _canvas = html.OffscreenCanvas(width, height); - _maxPixelWidth = width; - _maxPixelHeight = height; - _cachedContext?.dispose(); - _cachedContext = null; + } else { + _glCanvas = html.CanvasElement( + width: width, + height: height, + ); + _glCanvas!.className = 'gl-canvas'; + final double cssWidth = width / EngineWindow.browserDevicePixelRatio; + final double cssHeight = height / EngineWindow.browserDevicePixelRatio; + _glCanvas!.style + ..position = 'absolute' + ..width = '${cssWidth}px' + ..height = '${cssHeight}px'; } } - static void dispose() { + void dispose() { _canvas = null; + _glCanvas = null; + } + + /// Feature detects OffscreenCanvas. + static bool get supported => + js_util.hasProperty(html.window, 'OffscreenCanvas'); +} + +/// Creates gl context from cached OffscreenCanvas for webgl rendering to image. +class _GlContextCache { + static int _maxPixelWidth = 0; + static int _maxPixelHeight = 0; + static _GlContext? _cachedContext; + static _OffScreenCanvas? _offScreenCanvas; + + static void dispose() { _maxPixelWidth = 0; _maxPixelHeight = 0; - _glCanvas = null; _cachedContext = null; + _offScreenCanvas?.dispose(); } - html.OffscreenCanvas? get canvas => _canvas; - static _GlContext? createGlContext(int widthInPixels, int heightInPixels) { - final bool isWebKit = (browserEngine == BrowserEngine.webkit); - - if (_OffscreenCanvas.supported) { - final _OffscreenCanvas offScreenCanvas = - _OffscreenCanvas(widthInPixels, heightInPixels); - _cachedContext ??= _GlContext.fromOffscreenCanvas(offScreenCanvas.canvas!); - _cachedContext!.setViewportSize(widthInPixels, heightInPixels); - return _cachedContext; + if (widthInPixels > _maxPixelWidth || heightInPixels > _maxPixelHeight) { + _cachedContext?.dispose(); + _cachedContext = null; + _offScreenCanvas = null; + _maxPixelWidth = math.max(_maxPixelWidth, widthInPixels); + _maxPixelHeight = math.max(_maxPixelHeight, widthInPixels); + } + _offScreenCanvas ??= _OffScreenCanvas(widthInPixels, heightInPixels); + if (_OffScreenCanvas.supported) { + _cachedContext ??= + _GlContext.fromOffscreenCanvas(_offScreenCanvas!._canvas!); } else { - // Allocate new canvas element is size is larger. - if (widthInPixels > _maxPixelWidth || heightInPixels > _maxPixelHeight) { - _glCanvas = html.CanvasElement( - width: widthInPixels, - height: heightInPixels, - ); - _glCanvas!.className = 'gl-canvas'; - final double cssWidth = widthInPixels / EngineWindow.browserDevicePixelRatio; - final double cssHeight = heightInPixels / EngineWindow.browserDevicePixelRatio; - _glCanvas!.style - ..position = 'absolute' - ..width = '${cssWidth}px' - ..height = '${cssHeight}px'; - _maxPixelWidth = widthInPixels; - _maxPixelHeight = heightInPixels; - _cachedContext?.dispose(); - _cachedContext = null; - } - _cachedContext ??= _GlContext.fromCanvas(_glCanvas!, isWebKit); - _cachedContext!.setViewportSize(widthInPixels, heightInPixels); - return _cachedContext; + _cachedContext ??= _GlContext.fromCanvas(_offScreenCanvas!._glCanvas!, + (browserEngine == BrowserEngine.webkit)); } + _cachedContext!.setViewportSize(widthInPixels, heightInPixels); + return _cachedContext; } - - /// Feature detects OffscreenCanvas. - static bool get supported => - js_util.hasProperty(html.window, 'OffscreenCanvas'); } diff --git a/lib/web_ui/lib/src/engine/html/shaders/normalized_gradient.dart b/lib/web_ui/lib/src/engine/html/shaders/normalized_gradient.dart index 5a1b5145de4eb..1e7881d0f88ac 100644 --- a/lib/web_ui/lib/src/engine/html/shaders/normalized_gradient.dart +++ b/lib/web_ui/lib/src/engine/html/shaders/normalized_gradient.dart @@ -98,13 +98,13 @@ class NormalizedGradient { /// Sets uniforms for threshold, bias and scale for program. void setupUniforms(_GlContext gl, _GlProgram glProgram) { for (int i = 0; i < thresholdCount; i++) { - Object? biasId = gl.getUniformLocation(glProgram.program, 'bias_$i'); + Object biasId = gl.getUniformLocation(glProgram.program, 'bias_$i'); gl.setUniform4f(biasId, _bias[i * 4], _bias[i * 4 + 1], _bias[i * 4 + 2], _bias[i * 4 + 3]); - Object? scaleId = gl.getUniformLocation(glProgram.program, 'scale_$i'); + Object scaleId = gl.getUniformLocation(glProgram.program, 'scale_$i'); gl.setUniform4f(scaleId, _scale[i * 4], _scale[i * 4 + 1], _scale[i * 4 + 2], _scale[i * 4 + 3]); } for (int i = 0; i < _thresholds.length; i += 4) { - Object? thresId = gl.getUniformLocation(glProgram.program, 'threshold_${i ~/ 4}'); + Object thresId = gl.getUniformLocation(glProgram.program, 'threshold_${i ~/ 4}'); gl.setUniform4f(thresId, _thresholds[i], _thresholds[i + 1], _thresholds[i + 2], _thresholds[i + 3]); } } @@ -118,3 +118,44 @@ class NormalizedGradient { /// Returns threshold at index. double thresholdAt(int index) => _thresholds[index]; } + +/// Writes code to search for probe value in source data and set +/// bias and scale to be used for computation. +/// +/// Source data for thresholds is provided using ceil(count/4) packed vec4 +/// uniforms. +/// bias and scale data are vec4 uniforms that hold color data. +void _writeUnrolledBinarySearch(ShaderMethod method, int start, int end, + {required String probe, + required String sourcePrefix, required String biasName, + required String scaleName}) { + if (start == end) { + String biasSource = '${biasName}_${start}'; + method.addStatement('${biasName} = ${biasSource};'); + String scaleSource = '${scaleName}_${start}'; + method.addStatement('${scaleName} = ${scaleSource};'); + } else { + // Add probe check. + int mid = (start + end) ~/ 2; + String thresholdAtMid = '${sourcePrefix}_${(mid + 1)~/4}'; + thresholdAtMid += '.${_vectorComponentIndexToName((mid + 1) % 4)}'; + method.addStatement('if ($probe < $thresholdAtMid) {'); + method.indent(); + _writeUnrolledBinarySearch(method, start, mid, + probe: probe, sourcePrefix: sourcePrefix, biasName: biasName, + scaleName: scaleName); + method.unindent(); + method.addStatement('} else {'); + method.indent(); + _writeUnrolledBinarySearch(method, mid + 1, end, + probe: probe, sourcePrefix: sourcePrefix, biasName: biasName, + scaleName: scaleName); + method.unindent(); + method.addStatement('}'); + } +} + +String _vectorComponentIndexToName(int index) { + assert(index >=0 && index <= 4); + return 'xyzw'[index]; +} diff --git a/lib/web_ui/lib/src/engine/html/shaders/shader.dart b/lib/web_ui/lib/src/engine/html/shaders/shader.dart index 434bbb090b183..22cbd441f819d 100644 --- a/lib/web_ui/lib/src/engine/html/shaders/shader.dart +++ b/lib/web_ui/lib/src/engine/html/shaders/shader.dart @@ -16,47 +16,119 @@ abstract class EngineGradient implements ui.Gradient { class GradientSweep extends EngineGradient { GradientSweep(this.center, this.colors, this.colorStops, this.tileMode, - this.startAngle, this.endAngle, Float64List? matrix) + this.startAngle, this.endAngle, this.matrix4) : assert(_offsetIsValid(center)), assert(colors != null), // ignore: unnecessary_null_comparison assert(tileMode != null), // ignore: unnecessary_null_comparison assert(startAngle != null), // ignore: unnecessary_null_comparison assert(endAngle != null), // ignore: unnecessary_null_comparison assert(startAngle < endAngle), - this.matrix4 = matrix == null ? null : _FastMatrix64(matrix), super._() { _validateColorStops(colors, colorStops); } @override Object createPaintStyle(html.CanvasRenderingContext2D? ctx, - ui.Rect? shaderBounds) - { - _FastMatrix64? matrix4 = this.matrix4; - html.CanvasGradient gradient; + ui.Rect? shaderBounds) { + assert(shaderBounds != null); + int widthInPixels = shaderBounds!.right.ceil(); + int heightInPixels = shaderBounds.bottom.ceil(); + assert(widthInPixels > 0 && heightInPixels > 0); + + initWebGl(); + // Render gradient into a bitmap and create a canvas pattern. + _OffScreenCanvas offScreenCanvas = + _OffScreenCanvas(widthInPixels, heightInPixels); + _GlContext gl = _OffScreenCanvas.supported + ? _GlContext.fromOffscreenCanvas(offScreenCanvas._canvas!) + : _GlContext.fromCanvas(offScreenCanvas._glCanvas!, + (browserEngine == BrowserEngine.webkit)); + gl.setViewportSize(widthInPixels, heightInPixels); + + NormalizedGradient normalizedGradient = NormalizedGradient( + colors, stops: colorStops); + _GlProgram glProgram = gl.useAndCacheProgram( + _WebGlRenderer.writeBaseVertexShader(), + _createSweepFragmentShader(normalizedGradient, tileMode))!; + + Object? tileOffset = gl.getUniformLocation(glProgram.program, 'u_tile_offset'); + double centerX = (center.dx - shaderBounds.left) / (shaderBounds.width); + double centerY = (center.dy - shaderBounds.top) / (shaderBounds.height); + gl.setUniform2f(tileOffset!, + shaderBounds.left + 2 * (shaderBounds.width * (centerX - 0.5)), + -shaderBounds.top - 2 * (shaderBounds.height * (centerY - 0.5))); + Object? angleRange = gl.getUniformLocation(glProgram.program, 'angle_range'); + gl.setUniform2f(angleRange!, startAngle, endAngle); + normalizedGradient.setupUniforms(gl, glProgram); if (matrix4 != null) { - matrix4.transform(1, 1); - double width = matrix4.transformedX; - double height = matrix4.transformedY; - gradient = ctx!.createLinearGradient(shaderBounds!.left, shaderBounds.top, - shaderBounds.right, shaderBounds.top); - } else { - gradient = ctx!.createLinearGradient(shaderBounds!.left, shaderBounds.top, - shaderBounds.right, shaderBounds.top); + Object gradientMatrix = gl.getUniformLocation( + glProgram.program, 'm_gradient'); + gl.setUniformMatrix4fv(gradientMatrix, false, matrix4!); } - final List? colorStops = this.colorStops; - if (colorStops == null) { - assert(colors.length == 2); - gradient.addColorStop(0, colorToCssString(colors[0])!); - gradient.addColorStop(1, colorToCssString(colors[1])!); - return gradient; + Object? imageBitmap = _glRenderer!.drawRect(shaderBounds, gl, + glProgram, normalizedGradient, widthInPixels, heightInPixels); + + return ctx!.createPattern(imageBitmap!, 'no-repeat')!; + } + + String _createSweepFragmentShader(NormalizedGradient gradient, + ui.TileMode tileMode) { + ShaderBuilder builder = ShaderBuilder.fragment(webGLVersion); + builder.floatPrecision = ShaderPrecision.kMedium; + builder.addIn(ShaderType.kVec4, name: 'v_color'); + builder.addUniform(ShaderType.kVec2, name: 'u_resolution'); + builder.addUniform(ShaderType.kVec2, name: 'u_tile_offset'); + builder.addUniform(ShaderType.kVec2, name: 'angle_range'); + builder.addUniform(ShaderType.kMat4, name: 'm_gradient'); + ShaderDeclaration fragColor = builder.fragmentColor; + ShaderMethod method = builder.addMethod('main'); + // Sweep gradient + method.addStatement( + 'vec2 center = 0.5 * (u_resolution + u_tile_offset);'); + method.addStatement( + 'vec4 localCoord = vec4(gl_FragCoord.x - center.x, center.y - gl_FragCoord.y, 0, 1) * m_gradient;'); + method.addStatement( + 'float angle = atan(-localCoord.y, -localCoord.x) + ${math.pi};'); + method.addStatement( + 'float sweep = angle_range.y - angle_range.x;'); + method.addStatement( + 'angle = (angle - angle_range.x) / sweep;'); + method.addStatement('' + 'float st = angle;'); + + method.addStatement('vec4 bias;'); + method.addStatement('vec4 scale;'); + // Write uniforms for each threshold, bias and scale. + for (int i = 0; i < (gradient.thresholdCount - 1) ~/ 4 + 1; i++) { + builder.addUniform(ShaderType.kVec4, name: 'threshold_${i}'); } - for (int i = 0; i < colors.length; i++) { - gradient.addColorStop(colorStops[i], colorToCssString(colors[i])!); + for (int i = 0; i < gradient.thresholdCount; i++) { + builder.addUniform(ShaderType.kVec4, name: 'bias_$i'); + builder.addUniform(ShaderType.kVec4, name: 'scale_$i'); } - return gradient; + String probeName = 'st'; + switch (tileMode) { + case ui.TileMode.clamp: + break; + case ui.TileMode.repeated: + method.addStatement('float tiled_st = fract(st);'); + probeName = 'tiled_st'; + break; + case ui.TileMode.mirror: + method.addStatement('float t_1 = (st - 1.0);'); + method.addStatement('float tiled_st = abs((t_1 - 2.0 * floor(t_1 * 0.5)) - 1.0);'); + probeName = 'tiled_st'; + break; + } + _writeUnrolledBinarySearch(method, 0, gradient.thresholdCount - 1, + probe: probeName, sourcePrefix: 'threshold', + biasName: 'bias', scaleName: 'scale'); + method.addStatement('${fragColor.name} = ${probeName} * scale + bias;'); + String shader = builder.build(); + print(shader); + return shader; } final ui.Offset center; @@ -65,7 +137,7 @@ class GradientSweep extends EngineGradient { final ui.TileMode tileMode; final double startAngle; final double endAngle; - final _FastMatrix64? matrix4; + final Float32List? matrix4; } class GradientLinear extends EngineGradient { diff --git a/lib/web_ui/lib/src/engine/html/shaders/shader_builder.dart b/lib/web_ui/lib/src/engine/html/shaders/shader_builder.dart index c07fe90985adc..6ca1946d9fc4c 100644 --- a/lib/web_ui/lib/src/engine/html/shaders/shader_builder.dart +++ b/lib/web_ui/lib/src/engine/html/shaders/shader_builder.dart @@ -58,7 +58,7 @@ class ShaderBuilder { ShaderDeclaration? _fragmentColorDeclaration; ShaderBuilder(this.version) : isWebGl2 = version == WebGLVersion.webgl2, - _isFragmentShader = false; + _isFragmentShader = false; ShaderBuilder.fragment(this.version) : isWebGl2 = version == WebGLVersion.webgl2, @@ -238,9 +238,19 @@ class ShaderMethod { final String returnType = 'void'; final String name; final List _statements = []; + int _indentLevel = 1; + + void indent() { + ++_indentLevel; + } + + void unindent() { + assert(_indentLevel != 1); + --_indentLevel; + } void addStatement(String statement) { - _statements.add(statement); + _statements.add(' ' * _indentLevel + statement); } void write(StringBuffer buffer) { diff --git a/lib/web_ui/lib/src/engine/util.dart b/lib/web_ui/lib/src/engine/util.dart index 277f5484e2f07..4d0951527978f 100644 --- a/lib/web_ui/lib/src/engine/util.dart +++ b/lib/web_ui/lib/src/engine/util.dart @@ -507,6 +507,12 @@ class _FastMatrix64 { transformedX = matrix[12] + (matrix[0] * x) + (matrix[4] * y); transformedY = matrix[13] + (matrix[1] * x) + (matrix[5] * y); } + + String debugToString() => + '${matrix[0].toStringAsFixed(3)}, ${matrix[4].toStringAsFixed(3)}, ${matrix[8].toStringAsFixed(3)}, ${matrix[12].toStringAsFixed(3)}\n' + '${matrix[1].toStringAsFixed(3)}, ${matrix[5].toStringAsFixed(3)}, ${matrix[9].toStringAsFixed(3)}, ${matrix[13].toStringAsFixed(3)}\n' + '${matrix[2].toStringAsFixed(3)}, ${matrix[6].toStringAsFixed(3)}, ${matrix[10].toStringAsFixed(3)}, ${matrix[14].toStringAsFixed(3)}\n' + '${matrix[3].toStringAsFixed(3)}, ${matrix[7].toStringAsFixed(3)}, ${matrix[11].toStringAsFixed(3)}, ${matrix[15].toStringAsFixed(3)}\n'; } /// Roughly the inverse of [ui.Shadow.convertRadiusToSigma]. diff --git a/lib/web_ui/lib/src/ui/painting.dart b/lib/web_ui/lib/src/ui/painting.dart index 70cf1a9a0fac3..a76cddb602714 100644 --- a/lib/web_ui/lib/src/ui/painting.dart +++ b/lib/web_ui/lib/src/ui/painting.dart @@ -321,7 +321,7 @@ abstract class Gradient extends Shader { ? engine.CkGradientSweep(center, colors, colorStops, tileMode, startAngle, endAngle, matrix4 != null ? engine.toMatrix32(matrix4) : null) : engine.GradientSweep(center, colors, colorStops, tileMode, startAngle, - endAngle, matrix4); + endAngle, matrix4 != null ? engine.toMatrix32(matrix4) : null); } abstract class Image { diff --git a/lib/web_ui/test/engine/surface/shaders/shader_builder_test.dart b/lib/web_ui/test/engine/surface/shaders/shader_builder_test.dart index 3af3db222a870..336cf4f8d8890 100644 --- a/lib/web_ui/test/engine/surface/shaders/shader_builder_test.dart +++ b/lib/web_ui/test/engine/surface/shaders/shader_builder_test.dart @@ -201,7 +201,7 @@ void testMain() { 'precision mediump float;\n' 'uniform float ${variable.name};\n' 'void main() {\n' - 'f1 = 5.0;\n' + ' f1 = 5.0;\n' '}\n'); }); }); From 9b14730e3c3a280821bc987744026e69842f48ce Mon Sep 17 00:00:00 2001 From: ferhatb Date: Wed, 14 Oct 2020 16:03:20 -0700 Subject: [PATCH 04/14] Add golden test --- .../lib/src/engine/html/shaders/shader.dart | 8 +- .../engine/gradient_golden_test.dart | 319 ++++++++++++++++++ 2 files changed, 323 insertions(+), 4 deletions(-) create mode 100644 lib/web_ui/test/golden_tests/engine/gradient_golden_test.dart diff --git a/lib/web_ui/lib/src/engine/html/shaders/shader.dart b/lib/web_ui/lib/src/engine/html/shaders/shader.dart index 22cbd441f819d..bf286affa3433 100644 --- a/lib/web_ui/lib/src/engine/html/shaders/shader.dart +++ b/lib/web_ui/lib/src/engine/html/shaders/shader.dart @@ -52,14 +52,14 @@ class GradientSweep extends EngineGradient { _WebGlRenderer.writeBaseVertexShader(), _createSweepFragmentShader(normalizedGradient, tileMode))!; - Object? tileOffset = gl.getUniformLocation(glProgram.program, 'u_tile_offset'); + Object tileOffset = gl.getUniformLocation(glProgram.program, 'u_tile_offset'); double centerX = (center.dx - shaderBounds.left) / (shaderBounds.width); double centerY = (center.dy - shaderBounds.top) / (shaderBounds.height); - gl.setUniform2f(tileOffset!, + gl.setUniform2f(tileOffset, shaderBounds.left + 2 * (shaderBounds.width * (centerX - 0.5)), -shaderBounds.top - 2 * (shaderBounds.height * (centerY - 0.5))); - Object? angleRange = gl.getUniformLocation(glProgram.program, 'angle_range'); - gl.setUniform2f(angleRange!, startAngle, endAngle); + Object angleRange = gl.getUniformLocation(glProgram.program, 'angle_range'); + gl.setUniform2f(angleRange, startAngle, endAngle); normalizedGradient.setupUniforms(gl, glProgram); if (matrix4 != null) { Object gradientMatrix = gl.getUniformLocation( diff --git a/lib/web_ui/test/golden_tests/engine/gradient_golden_test.dart b/lib/web_ui/test/golden_tests/engine/gradient_golden_test.dart new file mode 100644 index 0000000000000..1d9835896f780 --- /dev/null +++ b/lib/web_ui/test/golden_tests/engine/gradient_golden_test.dart @@ -0,0 +1,319 @@ +// 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. + +// @dart = 2.6 +import 'dart:html' as html; +import 'dart:math' as math; +import 'dart:typed_data'; + +import 'package:test/bootstrap/browser.dart'; +import 'package:test/test.dart'; +import 'package:ui/ui.dart'; +import 'package:ui/src/engine.dart'; + +import 'package:web_engine_tester/golden_tester.dart'; + +import 'scuba.dart'; + +void main() { + internalBootstrapBrowserTest(() => testMain); +} + +void testMain() 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, 240), + double maxDiffRatePercent = 0.0, bool write: false}) async { + final EngineCanvas engineCanvas = BitmapCanvas(screenRect); + + rc.endRecording(); + rc.apply(engineCanvas, screenRect); + + // 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, maxDiffRatePercent: maxDiffRatePercent, write: write); + } finally { + // The page is reused across tests, so remove the element after taking the + // Scuba screenshot. + sceneElement.remove(); + } + } + + setUp(() async { + debugEmulateFlutterTesterEnvironment = true; + }); + + setUpStableTestFonts(); + + test('Paints sweep gradient rectangles', () async { + final RecordingCanvas canvas = + RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300)); + canvas.save(); + + final Paint borderPaint = Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 1 + ..color = Color(0xFF000000); + + List colors = [ + Color(0xFF000000), + Color(0xFFFF3C38), + Color(0xFFFF8C42), + Color(0xFFFFF275), + Color(0xFF6699CC), + Color(0xFF656D78),]; + List stops = [0.0, 0.05, 0.4, 0.6, 0.9, 1.0]; + + EngineGradient sweepGradient = GradientSweep(Offset(0.5, 0.5), + colors, stops, TileMode.clamp, + 0, 360.0 / 180.0 * math.pi, + Matrix4.rotationZ(math.pi / 6.0).storage); + + EngineGradient sweepGradientRotated = GradientSweep(Offset(0.5, 0.5), + colors, stops, TileMode.clamp, + 0, 360.0 / 180.0 * math.pi, + Matrix4.rotationZ(math.pi / 6.0).storage); + + const double kBoxWidth = 150; + const double kBoxHeight = 80; + // Gradient with default center. + Rect rectBounds = Rect.fromLTWH(10, 20, kBoxWidth, kBoxHeight); + canvas.drawRect(rectBounds, + Paint()..shader = engineGradientToShader(sweepGradient, rectBounds)); + canvas.drawRect(rectBounds, borderPaint); + + // Gradient with shifted center and rotation. + rectBounds = rectBounds.translate(kBoxWidth + 10, 0); + canvas.drawRect(rectBounds, + Paint()..shader = engineGradientToShader(sweepGradientRotated, Rect.fromLTWH(rectBounds.center.dx, rectBounds.top, rectBounds.width / 2, rectBounds.height))); + canvas.drawRect(rectBounds, borderPaint); + + // Gradient with start/endangle. + sweepGradient = GradientSweep(Offset(0.5, 0.5), + colors, stops, TileMode.clamp, + math.pi / 6, 3 * math.pi / 4, + Matrix4.rotationZ(math.pi / 6.0).storage); + + rectBounds = rectBounds.translate(kBoxWidth + 10, 0); + canvas.drawRect(rectBounds, + new Paint()..shader = engineGradientToShader(sweepGradient, rectBounds)); + canvas.drawRect(rectBounds, borderPaint); + + // Tile mode repeat + rectBounds = Rect.fromLTWH(10, 110, kBoxWidth, kBoxHeight); + sweepGradient = GradientSweep(Offset(0.5, 0.5), + colors, stops, TileMode.repeated, + math.pi / 6, 3 * math.pi / 4, + Matrix4.rotationZ(math.pi / 6.0).storage); + + canvas.drawRect(rectBounds, + new Paint()..shader = engineGradientToShader(sweepGradient, rectBounds)); + canvas.drawRect(rectBounds, borderPaint); + + // Tile mode mirror + rectBounds = rectBounds.translate(kBoxWidth + 10, 0); + sweepGradient = GradientSweep(Offset(0.5, 0.5), + colors, stops, TileMode.mirror, + math.pi / 6, 3 * math.pi / 4, + Matrix4.rotationZ(math.pi / 6.0).storage); + canvas.drawRect(rectBounds, + new Paint()..shader = engineGradientToShader(sweepGradient, rectBounds)); + canvas.drawRect(rectBounds, borderPaint); + + canvas.restore(); + await _checkScreenshot(canvas, 'sweep_gradient_rect_clamp', write: true); + }); + + test('Paints sweep gradient ovals', () async { + final RecordingCanvas canvas = + RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300)); + canvas.save(); + + final Paint borderPaint = Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 1 + ..color = Color(0xFF000000); + + List colors = [ + Color(0xFF000000), + Color(0xFFFF3C38), + Color(0xFFFF8C42), + Color(0xFFFFF275), + Color(0xFF6699CC), + Color(0xFF656D78),]; + List stops = [0.0, 0.05, 0.4, 0.6, 0.9, 1.0]; + + EngineGradient sweepGradient = GradientSweep(Offset(0.5, 0.5), + colors, stops, TileMode.clamp, + 0, 360.0 / 180.0 * math.pi, + Matrix4.rotationZ(math.pi / 6.0).storage); + + EngineGradient sweepGradientRotated = GradientSweep(Offset(0.5, 0.5), + colors, stops, TileMode.clamp, + 0, 360.0 / 180.0 * math.pi, + Matrix4.rotationZ(math.pi / 6.0).storage); + + const double kBoxWidth = 150; + const double kBoxHeight = 80; + // Gradient with default center. + Rect rectBounds = Rect.fromLTWH(10, 20, kBoxWidth, kBoxHeight); + canvas.drawOval(rectBounds, + Paint()..shader = engineGradientToShader(sweepGradient, rectBounds)); + canvas.drawRect(rectBounds, borderPaint); + + // Gradient with shifted center and rotation. + rectBounds = rectBounds.translate(kBoxWidth + 10, 0); + canvas.drawOval(rectBounds, + Paint()..shader = engineGradientToShader(sweepGradientRotated, Rect.fromLTWH(rectBounds.center.dx, rectBounds.top, rectBounds.width / 2, rectBounds.height))); + canvas.drawRect(rectBounds, borderPaint); + + // Gradient with start/endangle. + sweepGradient = GradientSweep(Offset(0.5, 0.5), + colors, stops, TileMode.clamp, + math.pi / 6, 3 * math.pi / 4, + Matrix4.rotationZ(math.pi / 6.0).storage); + + rectBounds = rectBounds.translate(kBoxWidth + 10, 0); + canvas.drawOval(rectBounds, + new Paint()..shader = engineGradientToShader(sweepGradient, rectBounds)); + canvas.drawRect(rectBounds, borderPaint); + + // Tile mode repeat + rectBounds = Rect.fromLTWH(10, 110, kBoxWidth, kBoxHeight); + sweepGradient = GradientSweep(Offset(0.5, 0.5), + colors, stops, TileMode.repeated, + math.pi / 6, 3 * math.pi / 4, + Matrix4.rotationZ(math.pi / 6.0).storage); + + canvas.drawOval(rectBounds, + new Paint()..shader = engineGradientToShader(sweepGradient, rectBounds)); + canvas.drawRect(rectBounds, borderPaint); + + // Tile mode mirror + rectBounds = rectBounds.translate(kBoxWidth + 10, 0); + sweepGradient = GradientSweep(Offset(0.5, 0.5), + colors, stops, TileMode.mirror, + math.pi / 6, 3 * math.pi / 4, + Matrix4.rotationZ(math.pi / 6.0).storage); + canvas.drawOval(rectBounds, + new Paint()..shader = engineGradientToShader(sweepGradient, rectBounds)); + canvas.drawRect(rectBounds, borderPaint); + + canvas.restore(); + await _checkScreenshot(canvas, 'sweep_gradient_oval_clamp', write: true); + }); + + test('Paints sweep gradient paths', () async { + final RecordingCanvas canvas = + RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300)); + canvas.save(); + + final Paint borderPaint = Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 1 + ..color = Color(0xFF000000); + + List colors = [ + Color(0xFF000000), + Color(0xFFFF3C38), + Color(0xFFFF8C42), + Color(0xFFFFF275), + Color(0xFF6699CC), + Color(0xFF656D78),]; + List stops = [0.0, 0.05, 0.4, 0.6, 0.9, 1.0]; + + EngineGradient sweepGradient = GradientSweep(Offset(0.5, 0.5), + colors, stops, TileMode.clamp, + 0, 360.0 / 180.0 * math.pi, + Matrix4.rotationZ(math.pi / 6.0).storage); + + EngineGradient sweepGradientRotated = GradientSweep(Offset(0.5, 0.5), + colors, stops, TileMode.clamp, + 0, 360.0 / 180.0 * math.pi, + Matrix4.rotationZ(math.pi / 6.0).storage); + + const double kBoxWidth = 150; + const double kBoxHeight = 80; + // Gradient with default center. + Rect rectBounds = Rect.fromLTWH(10, 20, kBoxWidth, kBoxHeight); + Path path = samplePathFromRect(rectBounds); + canvas.drawPath(path, + Paint()..shader = engineGradientToShader(sweepGradient, rectBounds)); + canvas.drawRect(rectBounds, borderPaint); + + // Gradient with shifted center and rotation. + rectBounds = rectBounds.translate(kBoxWidth + 10, 0); + path = samplePathFromRect(rectBounds); + canvas.drawPath(path, + Paint()..shader = engineGradientToShader(sweepGradientRotated, Rect.fromLTWH(rectBounds.center.dx, rectBounds.top, rectBounds.width / 2, rectBounds.height))); + canvas.drawRect(rectBounds, borderPaint); + + // Gradient with start/endangle. + sweepGradient = GradientSweep(Offset(0.5, 0.5), + colors, stops, TileMode.clamp, + math.pi / 6, 3 * math.pi / 4, + Matrix4.rotationZ(math.pi / 6.0).storage); + + rectBounds = rectBounds.translate(kBoxWidth + 10, 0); + path = samplePathFromRect(rectBounds); + canvas.drawPath(path, + new Paint()..shader = engineGradientToShader(sweepGradient, rectBounds)); + canvas.drawRect(rectBounds, borderPaint); + + // Tile mode repeat + rectBounds = Rect.fromLTWH(10, 110, kBoxWidth, kBoxHeight); + sweepGradient = GradientSweep(Offset(0.5, 0.5), + colors, stops, TileMode.repeated, + math.pi / 6, 3 * math.pi / 4, + Matrix4.rotationZ(math.pi / 6.0).storage); + + path = samplePathFromRect(rectBounds); + canvas.drawPath(path, + new Paint()..shader = engineGradientToShader(sweepGradient, rectBounds)); + canvas.drawRect(rectBounds, borderPaint); + + // Tile mode mirror + rectBounds = rectBounds.translate(kBoxWidth + 10, 0); + sweepGradient = GradientSweep(Offset(0.5, 0.5), + colors, stops, TileMode.mirror, + math.pi / 6, 3 * math.pi / 4, + Matrix4.rotationZ(math.pi / 6.0).storage); + path = samplePathFromRect(rectBounds); + canvas.drawPath(path, + new Paint()..shader = engineGradientToShader(sweepGradient, rectBounds)); + canvas.drawRect(rectBounds, borderPaint); + + canvas.restore(); + await _checkScreenshot(canvas, 'sweep_gradient_path_clamp', write: true); + }); +} + +Shader engineGradientToShader(GradientSweep gradient, Rect rect) { + TextDirection textDirection = TextDirection.ltr; + return Gradient.sweep( + Offset(rect.left + gradient.center.dx * rect.width, + rect.top + gradient.center.dy * rect.height), + gradient.colors, gradient.colorStops, gradient.tileMode, + gradient.startAngle, + gradient.endAngle, + gradient.matrix4 == null ? null : + Float64List.fromList(gradient.matrix4), + ); +} + +Path samplePathFromRect(Rect rectBounds) => + Path() + ..moveTo(rectBounds.center.dx, rectBounds.top) + ..lineTo(rectBounds.left, rectBounds.bottom) + ..quadraticBezierTo(rectBounds.center.dx + 20, rectBounds.bottom - 40, + rectBounds.right, rectBounds.bottom) + ..close(); From 054aea64519e53dbaa6ca884e8014e79d5c2d52a Mon Sep 17 00:00:00 2001 From: ferhatb Date: Wed, 14 Oct 2020 18:08:10 -0700 Subject: [PATCH 05/14] Fix flipped image data on webgl1 --- .../lib/src/engine/html/render_vertices.dart | 18 +++++++++++++++--- .../lib/src/engine/html/shaders/shader.dart | 2 +- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/web_ui/lib/src/engine/html/render_vertices.dart b/lib/web_ui/lib/src/engine/html/render_vertices.dart index 5195a8fdeae10..28ee9c7c05a07 100644 --- a/lib/web_ui/lib/src/engine/html/render_vertices.dart +++ b/lib/web_ui/lib/src/engine/html/render_vertices.dart @@ -449,6 +449,7 @@ class _GlContext { dynamic _kUnsignedByte; dynamic _kUnsignedShort; dynamic _kRGBA; + dynamic _kUnpackFlipYWebGl; Object? _canvas; int? _widthInPixels; int? _heightInPixels; @@ -659,6 +660,9 @@ class _GlContext { dynamic get kColorBufferBit => _kColorBufferBit ??= js_util.getProperty(glContext, 'COLOR_BUFFER_BIT'); + dynamic get kUnpackFlipYWebGl => + _kUnpackFlipYWebGl ??= js_util.getProperty(glContext, 'UNPACK_FLIP_Y_WEBGL'); + /// Returns reference to uniform in program. Object getUniformLocation(Object program, String uniformName) { Object? res = js_util @@ -711,6 +715,15 @@ class _GlContext { int? get drawingBufferHeight => js_util.getProperty(glContext, 'drawingBufferWidth'); + /// Flips the y axis when uploading a texture (for webgl1). + void pixelStoreFlip(bool flip) { + js_util.callMethod(glContext, 'pixelStorei', + [kUnpackFlipYWebGl, flip]); + } + + /// Reads gl contents as image data. + /// + /// Warning: data is read bottom up (flipped). html.ImageData readImageData() { if (browserEngine == BrowserEngine.webkit || browserEngine == BrowserEngine.firefox) { @@ -718,7 +731,7 @@ class _GlContext { final int bufferWidth = _widthInPixels!; final int bufferHeight = _heightInPixels!; final Uint8List pixels = - Uint8List(bufferWidth * bufferHeight * kBytesPerPixel); + Uint8List(bufferWidth * bufferHeight * kBytesPerPixel); js_util.callMethod(glContext, 'readPixels', [0, 0, bufferWidth, bufferHeight, kRGBA, kUnsignedByte, pixels]); return html.ImageData( @@ -748,10 +761,9 @@ class _GlContext { []); return imageBitmap; } else { - html.ImageData imageData = readImageData(); html.CanvasElement canvas = html.CanvasElement(width: _widthInPixels, height: _heightInPixels); final html.CanvasRenderingContext2D ctx = canvas.context2D; - ctx.putImageData(imageData, 0 , 0); + drawImage(ctx, 0, 0); return canvas; } } diff --git a/lib/web_ui/lib/src/engine/html/shaders/shader.dart b/lib/web_ui/lib/src/engine/html/shaders/shader.dart index bf286affa3433..457f454d41a52 100644 --- a/lib/web_ui/lib/src/engine/html/shaders/shader.dart +++ b/lib/web_ui/lib/src/engine/html/shaders/shader.dart @@ -42,7 +42,7 @@ class GradientSweep extends EngineGradient { _GlContext gl = _OffScreenCanvas.supported ? _GlContext.fromOffscreenCanvas(offScreenCanvas._canvas!) : _GlContext.fromCanvas(offScreenCanvas._glCanvas!, - (browserEngine == BrowserEngine.webkit)); + webGLVersion == 1); gl.setViewportSize(widthInPixels, heightInPixels); NormalizedGradient normalizedGradient = NormalizedGradient( From 153269b17584dcd9ed647e2be0cfd498cfef8405 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Wed, 14 Oct 2020 18:15:47 -0700 Subject: [PATCH 06/14] remove print --- lib/web_ui/lib/src/engine/html/shaders/shader.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/web_ui/lib/src/engine/html/shaders/shader.dart b/lib/web_ui/lib/src/engine/html/shaders/shader.dart index 457f454d41a52..3c79c7c983cfd 100644 --- a/lib/web_ui/lib/src/engine/html/shaders/shader.dart +++ b/lib/web_ui/lib/src/engine/html/shaders/shader.dart @@ -127,7 +127,6 @@ class GradientSweep extends EngineGradient { biasName: 'bias', scaleName: 'scale'); method.addStatement('${fragColor.name} = ${probeName} * scale + bias;'); String shader = builder.build(); - print(shader); return shader; } From 56a8b3f8d0379e76aa8f7d4da2fe671281f603f5 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Thu, 15 Oct 2020 10:15:30 -0700 Subject: [PATCH 07/14] update golden locks --- lib/web_ui/dev/goldens_lock.yaml | 2 +- .../test/golden_tests/engine/gradient_golden_test.dart | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/web_ui/dev/goldens_lock.yaml b/lib/web_ui/dev/goldens_lock.yaml index 66a641aed621b..268c4943f31db 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: 672510dc52daa5b059081f6990582bccdb4ea48f +revision: 7171fa6b2844b39d08aab87ca5b4e923c18e3b39 diff --git a/lib/web_ui/test/golden_tests/engine/gradient_golden_test.dart b/lib/web_ui/test/golden_tests/engine/gradient_golden_test.dart index 1d9835896f780..2ef73fac5217e 100644 --- a/lib/web_ui/test/golden_tests/engine/gradient_golden_test.dart +++ b/lib/web_ui/test/golden_tests/engine/gradient_golden_test.dart @@ -130,7 +130,7 @@ void testMain() async { canvas.drawRect(rectBounds, borderPaint); canvas.restore(); - await _checkScreenshot(canvas, 'sweep_gradient_rect_clamp', write: true); + await _checkScreenshot(canvas, 'sweep_gradient_rect_clamp'); }); test('Paints sweep gradient ovals', () async { @@ -209,7 +209,7 @@ void testMain() async { canvas.drawRect(rectBounds, borderPaint); canvas.restore(); - await _checkScreenshot(canvas, 'sweep_gradient_oval_clamp', write: true); + await _checkScreenshot(canvas, 'sweep_gradient_oval_clamp'); }); test('Paints sweep gradient paths', () async { @@ -293,7 +293,7 @@ void testMain() async { canvas.drawRect(rectBounds, borderPaint); canvas.restore(); - await _checkScreenshot(canvas, 'sweep_gradient_path_clamp', write: true); + await _checkScreenshot(canvas, 'sweep_gradient_path_clamp'); }); } From e767618b5b7a06ee0b7628b88404f59cccb6f11c Mon Sep 17 00:00:00 2001 From: ferhatb Date: Thu, 15 Oct 2020 10:27:24 -0700 Subject: [PATCH 08/14] cleanup --- lib/web_ui/lib/src/engine/html/render_vertices.dart | 12 ++---------- .../golden_tests/engine/gradient_golden_test.dart | 1 - 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/lib/web_ui/lib/src/engine/html/render_vertices.dart b/lib/web_ui/lib/src/engine/html/render_vertices.dart index 28ee9c7c05a07..ed58e6afd59c7 100644 --- a/lib/web_ui/lib/src/engine/html/render_vertices.dart +++ b/lib/web_ui/lib/src/engine/html/render_vertices.dart @@ -184,22 +184,16 @@ class _WebGlRenderer implements _GlRenderer { targetRect.right, targetRect.bottom, targetRect.left, targetRect.bottom, ]); + // Form 2 triangles for rectangle. final Uint16List indices = Uint16List.fromList( [ 0, 1, 2, 2, 3, 0 ] ); - Matrix4 transform = Matrix4.identity(); - - double offsetX = 0, - offsetY = 0; - Object transformUniform = gl.getUniformLocation( glProgram.program, 'u_ctransform'); - Matrix4 transformAtOffset = transform.clone() - ..translate(-offsetX, -offsetY); - gl.setUniformMatrix4fv(transformUniform, false, transformAtOffset.storage); + gl.setUniformMatrix4fv(transformUniform, false, Matrix4.identity().storage); // Set uniform to scale 0..width/height pixels coordinates to -1..1 // clipspace range and flip the Y axis. @@ -236,8 +230,6 @@ class _WebGlRenderer implements _GlRenderer { gl.bindElementArrayBuffer(indexBuffer); gl.bufferElementData(indices, gl.kStaticDraw); - // Object uTime = gl.getUniformLocation(glProgram.program, 'u_time'); - // gl.setUniform1f(uTime, timeValue); Object uRes = gl.getUniformLocation(glProgram.program, 'u_resolution'); gl.setUniform2f(uRes, widthInPixels.toDouble(), heightInPixels.toDouble()); diff --git a/lib/web_ui/test/golden_tests/engine/gradient_golden_test.dart b/lib/web_ui/test/golden_tests/engine/gradient_golden_test.dart index 2ef73fac5217e..f5770b1e841cb 100644 --- a/lib/web_ui/test/golden_tests/engine/gradient_golden_test.dart +++ b/lib/web_ui/test/golden_tests/engine/gradient_golden_test.dart @@ -298,7 +298,6 @@ void testMain() async { } Shader engineGradientToShader(GradientSweep gradient, Rect rect) { - TextDirection textDirection = TextDirection.ltr; return Gradient.sweep( Offset(rect.left + gradient.center.dx * rect.width, rect.top + gradient.center.dy * rect.height), From e904cfef9de22d1a782b260ff7f9eff4d52384fa Mon Sep 17 00:00:00 2001 From: ferhatb Date: Thu, 15 Oct 2020 12:43:08 -0700 Subject: [PATCH 09/14] Fix position/color vertex attribute location --- .../lib/src/engine/html/render_vertices.dart | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/web_ui/lib/src/engine/html/render_vertices.dart b/lib/web_ui/lib/src/engine/html/render_vertices.dart index ed58e6afd59c7..59916eef87cb8 100644 --- a/lib/web_ui/lib/src/engine/html/render_vertices.dart +++ b/lib/web_ui/lib/src/engine/html/render_vertices.dart @@ -147,7 +147,7 @@ class _WebGlRenderer implements _GlRenderer { assert(positionsBuffer != null); // ignore: unnecessary_null_comparison gl.bindArrayBuffer(positionsBuffer); gl.bufferData(positions, gl.kStaticDraw); - Object? positionLoc = gl.getUniformLocation(glProgram.program, 'position'); + Object? positionLoc = gl.getAttributeLocation(glProgram.program, 'position'); js_util.callMethod( gl.glContext, 'vertexAttribPointer', [ positionLoc, 2, gl.kFloat, false, 0, 0, @@ -159,7 +159,7 @@ class _WebGlRenderer implements _GlRenderer { gl.bindArrayBuffer(colorsBuffer); // Buffer kBGRA_8888. gl.bufferData(vertices._colors, gl.kStaticDraw); - Object colorLoc = gl.getUniformLocation(glProgram.program, 'color'); + Object colorLoc = gl.getAttributeLocation(glProgram.program, 'color'); js_util.callMethod(gl.glContext, 'vertexAttribPointer', [colorLoc, 4, gl.kUnsignedByte, true, 0, 0]); gl.enableVertexAttribArray(1); @@ -666,6 +666,17 @@ class _GlContext { } } + /// Returns reference to uniform in program. + Object getAttributeLocation(Object program, String attribName) { + Object? res = js_util + .callMethod(glContext, 'getAttribLocation', [program, attribName]); + if (res == null) { + throw Exception('$attribName not found'); + } else { + return res; + } + } + /// Sets float uniform value. void setUniform1f(Object uniform, double value) { return js_util From 57fa14d2149a2aa84af8fee06480abca5db9eae4 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Thu, 15 Oct 2020 14:00:28 -0700 Subject: [PATCH 10/14] fix golden names --- .../test/golden_tests/engine/gradient_golden_test.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/web_ui/test/golden_tests/engine/gradient_golden_test.dart b/lib/web_ui/test/golden_tests/engine/gradient_golden_test.dart index f5770b1e841cb..196938ff5eeed 100644 --- a/lib/web_ui/test/golden_tests/engine/gradient_golden_test.dart +++ b/lib/web_ui/test/golden_tests/engine/gradient_golden_test.dart @@ -130,7 +130,7 @@ void testMain() async { canvas.drawRect(rectBounds, borderPaint); canvas.restore(); - await _checkScreenshot(canvas, 'sweep_gradient_rect_clamp'); + await _checkScreenshot(canvas, 'sweep_gradient_rect'); }); test('Paints sweep gradient ovals', () async { @@ -209,7 +209,7 @@ void testMain() async { canvas.drawRect(rectBounds, borderPaint); canvas.restore(); - await _checkScreenshot(canvas, 'sweep_gradient_oval_clamp'); + await _checkScreenshot(canvas, 'sweep_gradient_oval'); }); test('Paints sweep gradient paths', () async { @@ -293,7 +293,7 @@ void testMain() async { canvas.drawRect(rectBounds, borderPaint); canvas.restore(); - await _checkScreenshot(canvas, 'sweep_gradient_path_clamp'); + await _checkScreenshot(canvas, 'sweep_gradient_path'); }); } From 46b5faadb51e7bd100025471be805e79d177b683 Mon Sep 17 00:00:00 2001 From: ferhatb Date: Fri, 16 Oct 2020 15:46:49 -0700 Subject: [PATCH 11/14] addressed reviewer comment --- lib/web_ui/lib/src/engine/bitmap_canvas.dart | 27 ++------------------ 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index f1f8a0e4fc01a..41b4b8e7ab57c 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -777,32 +777,9 @@ class BitmapCanvas extends EngineCanvas { _elementCache?.commitFrame(); } - /// Computes paint bounds given [targetTransform] to completely cover window - /// viewport. + /// Computes paint bounds given [targetTransform] to completely cover picture. ui.Rect _computeScreenBounds(Matrix4 targetTransform) { - final Matrix4 inverted = targetTransform.clone()..invert(); - final double dpr = ui.window.devicePixelRatio; - final double width = ui.window.physicalSize.width * dpr; - final double height = ui.window.physicalSize.height * dpr; - Vector3 topLeft = inverted.perspectiveTransform(Vector3(0, 0, 0)); - Vector3 topRight = inverted.perspectiveTransform(Vector3(width, 0, 0)); - Vector3 bottomRight = - inverted.perspectiveTransform(Vector3(width, height, 0)); - Vector3 bottomLeft = inverted.perspectiveTransform(Vector3(0, height, 0)); - return ui.Rect.fromLTRB( - math.min(topLeft.x, - math.min(topRight.x, math.min(bottomRight.x, bottomLeft.x))) - - bounds.left, - math.min(topLeft.y, - math.min(topRight.y, math.min(bottomRight.y, bottomLeft.y))) - - bounds.top, - math.max(topLeft.x, - math.max(topRight.x, math.max(bottomRight.x, bottomLeft.x))) - - bounds.left, - math.max(topLeft.y, - math.max(topRight.y, math.max(bottomRight.y, bottomLeft.y))) - - bounds.top, - ); + return ui.Rect.fromLTRB(0, 0, _bounds.width, _bounds.height); } } From acbe5d98d22adf519dab856fe8090b63c9b532ef Mon Sep 17 00:00:00 2001 From: ferhatb Date: Fri, 16 Oct 2020 17:07:23 -0700 Subject: [PATCH 12/14] Address reviewer comments --- lib/web_ui/lib/src/engine/bitmap_canvas.dart | 2 - .../lib/src/engine/html/render_vertices.dart | 42 ++++++++++++------- .../html/shaders/normalized_gradient.dart | 5 ++- .../engine/html/shaders/shader_builder.dart | 6 ++- 4 files changed, 34 insertions(+), 21 deletions(-) diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index 41b4b8e7ab57c..20a4d8ba3c268 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -975,5 +975,3 @@ String _maskFilterToCanvasFilter(ui.MaskFilter? maskFilter) { return 'none'; } } - - diff --git a/lib/web_ui/lib/src/engine/html/render_vertices.dart b/lib/web_ui/lib/src/engine/html/render_vertices.dart index 59916eef87cb8..a9641b01f1cc9 100644 --- a/lib/web_ui/lib/src/engine/html/render_vertices.dart +++ b/lib/web_ui/lib/src/engine/html/render_vertices.dart @@ -173,23 +173,33 @@ class _WebGlRenderer implements _GlRenderer { context.restore(); } + static final Uint16List _vertexIndicesForRect = Uint16List.fromList( + [ + 0, 1, 2, 2, 3, 0 + ] + ); + /// Renders a rectangle using given program into an image resource. + /// + /// Browsers that support OffscreenCanvas and the transferToImageBitmap api + /// will return ImageBitmap, otherwise will return CanvasElement. Object? drawRect(ui.Rect targetRect, _GlContext gl, _GlProgram glProgram, NormalizedGradient gradient, int widthInPixels, int heightInPixels) { // Setup rectangle coordinates. - final Float32List coordinates = Float32List.fromList( - [ - targetRect.left, targetRect.top, - targetRect.right, targetRect.top, - targetRect.right, targetRect.bottom, - targetRect.left, targetRect.bottom, - ]); + final double left = targetRect.left; + final double top = targetRect.top; + final double right = targetRect.right; + final double bottom = targetRect.bottom; // Form 2 triangles for rectangle. - final Uint16List indices = Uint16List.fromList( - [ - 0, 1, 2, 2, 3, 0 - ] - ); + final Float32List vertices = Float32List(8); + vertices[0] = left; + vertices[1] = top; + vertices[2] = right; + vertices[3] = top; + vertices[4] = right; + vertices[5] = bottom; + vertices[6] = left; + vertices[7] = bottom; Object transformUniform = gl.getUniformLocation( glProgram.program, 'u_ctransform'); @@ -207,7 +217,7 @@ class _WebGlRenderer implements _GlRenderer { Object positionsBuffer = gl.createBuffer()!; assert(positionsBuffer != null); // ignore: unnecessary_null_comparison gl.bindArrayBuffer(positionsBuffer); - gl.bufferData(coordinates, gl.kStaticDraw); + gl.bufferData(vertices, gl.kStaticDraw); // Point an attribute to the currently bound vertex buffer object. js_util.callMethod( gl.glContext, 'vertexAttribPointer', @@ -228,7 +238,7 @@ class _WebGlRenderer implements _GlRenderer { Object? indexBuffer = gl.createBuffer(); gl.bindElementArrayBuffer(indexBuffer); - gl.bufferElementData(indices, gl.kStaticDraw); + gl.bufferElementData(_vertexIndicesForRect, gl.kStaticDraw); Object uRes = gl.getUniformLocation(glProgram.program, 'u_resolution'); gl.setUniform2f(uRes, widthInPixels.toDouble(), heightInPixels.toDouble()); @@ -236,7 +246,7 @@ class _WebGlRenderer implements _GlRenderer { gl.clear(); gl.viewport(0, 0, widthInPixels.toDouble(), heightInPixels.toDouble()); - gl.drawElements(gl.kTriangles, indices.length, gl.kUnsignedShort); + gl.drawElements(gl.kTriangles, _vertexIndicesForRect.length, gl.kUnsignedShort); Object? image = gl.readPatternData(); @@ -835,7 +845,7 @@ class _GlContextCache { _GlContext.fromOffscreenCanvas(_offScreenCanvas!._canvas!); } else { _cachedContext ??= _GlContext.fromCanvas(_offScreenCanvas!._glCanvas!, - (browserEngine == BrowserEngine.webkit)); + webGLVersion == 1); } _cachedContext!.setViewportSize(widthInPixels, heightInPixels); return _cachedContext; diff --git a/lib/web_ui/lib/src/engine/html/shaders/normalized_gradient.dart b/lib/web_ui/lib/src/engine/html/shaders/normalized_gradient.dart index 1e7881d0f88ac..f53dc4d7fc946 100644 --- a/lib/web_ui/lib/src/engine/html/shaders/normalized_gradient.dart +++ b/lib/web_ui/lib/src/engine/html/shaders/normalized_gradient.dart @@ -119,12 +119,13 @@ class NormalizedGradient { double thresholdAt(int index) => _thresholds[index]; } -/// Writes code to search for probe value in source data and set +/// Writes fragment shader code to search for probe value in source data and set /// bias and scale to be used for computation. /// /// Source data for thresholds is provided using ceil(count/4) packed vec4 /// uniforms. -/// bias and scale data are vec4 uniforms that hold color data. +/// +/// Bias and scale data are vec4 uniforms that hold color data. void _writeUnrolledBinarySearch(ShaderMethod method, int start, int end, {required String probe, required String sourcePrefix, required String biasName, diff --git a/lib/web_ui/lib/src/engine/html/shaders/shader_builder.dart b/lib/web_ui/lib/src/engine/html/shaders/shader_builder.dart index 6ca1946d9fc4c..d5d90cb93eee0 100644 --- a/lib/web_ui/lib/src/engine/html/shaders/shader_builder.dart +++ b/lib/web_ui/lib/src/engine/html/shaders/shader_builder.dart @@ -250,7 +250,11 @@ class ShaderMethod { } void addStatement(String statement) { - _statements.add(' ' * _indentLevel + statement); + if (assertionsEnabled) { + _statements.add(' ' * _indentLevel + statement); + } else { + _statements.add(statement); + } } void write(StringBuffer buffer) { From 59238f84f1d89c15c95f45c33f00cd235eb958cd Mon Sep 17 00:00:00 2001 From: ferhatb Date: Fri, 16 Oct 2020 17:08:48 -0700 Subject: [PATCH 13/14] change computeScreenBounds to picture bounds --- lib/web_ui/lib/src/engine/bitmap_canvas.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index 20a4d8ba3c268..b73d35754c315 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -302,7 +302,7 @@ class BitmapCanvas extends EngineCanvas { @override void drawPaint(SurfacePaintData paint) { ui.Rect? shaderBounds = (paint.shader != null) ? - _computeScreenBounds(_canvasPool._currentTransform) : null; + _computePictureBounds() : null; _setUpPaint(paint, shaderBounds); _canvasPool.fill(); _tearDownPaint(); @@ -777,8 +777,8 @@ class BitmapCanvas extends EngineCanvas { _elementCache?.commitFrame(); } - /// Computes paint bounds given [targetTransform] to completely cover picture. - ui.Rect _computeScreenBounds(Matrix4 targetTransform) { + /// Computes paint bounds to completely cover picture. + ui.Rect _computePictureBounds() { return ui.Rect.fromLTRB(0, 0, _bounds.width, _bounds.height); } } From c95a8d0329c40dd499083b33e178bbe13a84978a Mon Sep 17 00:00:00 2001 From: ferhatb Date: Mon, 19 Oct 2020 11:03:11 -0700 Subject: [PATCH 14/14] address review comments --- .../lib/src/engine/html/render_vertices.dart | 24 +++++-------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/lib/web_ui/lib/src/engine/html/render_vertices.dart b/lib/web_ui/lib/src/engine/html/render_vertices.dart index a9641b01f1cc9..c0d564bca1e76 100644 --- a/lib/web_ui/lib/src/engine/html/render_vertices.dart +++ b/lib/web_ui/lib/src/engine/html/render_vertices.dart @@ -228,7 +228,7 @@ class _WebGlRenderer implements _GlRenderer { Object? colorsBuffer = gl.createBuffer(); gl.bindArrayBuffer(colorsBuffer); // Buffer kBGRA_8888. - Int32List colors = Int32List.fromList([ + final Int32List colors = Int32List.fromList([ 0xFF00FF00, 0xFF0000FF, 0xFFFFFF00, 0xFF00FFFF, ]); gl.bufferData(colors, gl.kStaticDraw); @@ -451,7 +451,7 @@ class _GlContext { dynamic _kUnsignedByte; dynamic _kUnsignedShort; dynamic _kRGBA; - dynamic _kUnpackFlipYWebGl; + Object? _canvas; int? _widthInPixels; int? _heightInPixels; @@ -662,9 +662,6 @@ class _GlContext { dynamic get kColorBufferBit => _kColorBufferBit ??= js_util.getProperty(glContext, 'COLOR_BUFFER_BIT'); - dynamic get kUnpackFlipYWebGl => - _kUnpackFlipYWebGl ??= js_util.getProperty(glContext, 'UNPACK_FLIP_Y_WEBGL'); - /// Returns reference to uniform in program. Object getUniformLocation(Object program, String uniformName) { Object? res = js_util @@ -728,21 +725,15 @@ class _GlContext { int? get drawingBufferHeight => js_util.getProperty(glContext, 'drawingBufferWidth'); - /// Flips the y axis when uploading a texture (for webgl1). - void pixelStoreFlip(bool flip) { - js_util.callMethod(glContext, 'pixelStorei', - [kUnpackFlipYWebGl, flip]); - } - /// Reads gl contents as image data. /// /// Warning: data is read bottom up (flipped). html.ImageData readImageData() { + const int kBytesPerPixel = 4; + final int bufferWidth = _widthInPixels!; + final int bufferHeight = _heightInPixels!; if (browserEngine == BrowserEngine.webkit || browserEngine == BrowserEngine.firefox) { - const int kBytesPerPixel = 4; - final int bufferWidth = _widthInPixels!; - final int bufferHeight = _heightInPixels!; final Uint8List pixels = Uint8List(bufferWidth * bufferHeight * kBytesPerPixel); js_util.callMethod(glContext, 'readPixels', @@ -750,11 +741,8 @@ class _GlContext { return html.ImageData( Uint8ClampedList.fromList(pixels), bufferWidth, bufferHeight); } else { - const int kBytesPerPixel = 4; - final int bufferWidth = _widthInPixels!; - final int bufferHeight = _heightInPixels!; final Uint8ClampedList pixels = - Uint8ClampedList(bufferWidth * bufferHeight * kBytesPerPixel); + Uint8ClampedList(bufferWidth * bufferHeight * kBytesPerPixel); js_util.callMethod(glContext, 'readPixels', [0, 0, bufferWidth, bufferHeight, kRGBA, kUnsignedByte, pixels]); return html.ImageData(pixels, bufferWidth, bufferHeight);