diff --git a/lib/web_ui/dev/goldens_lock.yaml b/lib/web_ui/dev/goldens_lock.yaml index 9f569f3ce3d6d..63f215878dfb2 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: 578ecb91ea33004cd0ba0af513884cc28ba88cf4 +revision: a1e78be938fe79544eeacccce1e2a5ace3d4058e 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 10b714ff74303..81f785bb6fa53 100644 --- a/lib/web_ui/lib/src/engine/html/render_vertices.dart +++ b/lib/web_ui/lib/src/engine/html/render_vertices.dart @@ -251,8 +251,11 @@ class _WebGlRenderer implements _GlRenderer { gl.bindElementArrayBuffer(indexBuffer); gl.bufferElementData(_vertexIndicesForRect, gl.kStaticDraw); - Object uRes = gl.getUniformLocation(glProgram.program, 'u_resolution'); - gl.setUniform2f(uRes, widthInPixels.toDouble(), heightInPixels.toDouble()); + if (gl.containsUniform(glProgram.program, 'u_resolution')) { + 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()); @@ -684,6 +687,13 @@ class _GlContext { } } + /// Returns true if uniform exists. + bool containsUniform(Object program, String uniformName) { + Object? res = js_util + .callMethod(glContext, 'getUniformLocation', [program, uniformName]); + return res != null; + } + /// Returns reference to uniform in program. Object getAttributeLocation(Object program, String attribName) { Object? res = js_util 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 1a24820ee51b9..f87e753cb8635 100644 --- a/lib/web_ui/lib/src/engine/html/shaders/shader.dart +++ b/lib/web_ui/lib/src/engine/html/shaders/shader.dart @@ -99,7 +99,7 @@ class GradientSweep extends EngineGradient { 'float st = angle;'); final String probeName = - _writeSharedGradientShader(builder, method, gradient, tileMode, false); + _writeSharedGradientShader(builder, method, gradient, tileMode); method.addStatement('${fragColor.name} = ${probeName} * scale + bias;'); String shader = builder.build(); @@ -172,7 +172,8 @@ class GradientLinear extends EngineGradient { from.dx - offsetX, from.dy - offsetY, to.dx - offsetX, to.dy - offsetY); } - _addColorStopsToCanvasGradient(gradient, colors, colorStops, tileMode == ui.TileMode.decal); + _addColorStopsToCanvasGradient(gradient, colors, colorStops, + tileMode == ui.TileMode.decal); return gradient; } @@ -210,32 +211,63 @@ class GradientLinear extends EngineGradient { // 1- Shift from,to vector to origin. // 2- Rotate the vector to align with x axis. // 3- Scale it to unit vector. - double dx = to.dx - from.dx; - double dy = to.dy - from.dy; - double length = math.sqrt(dx * dx + dy * dy); + final double fromX = from.dx; + final double fromY = from.dy; + final double toX = to.dx; + final double toY = to.dy; + + final double dx = toX - fromX; + final double dy = toY - fromY; + final double length = math.sqrt(dx * dx + dy * dy); // sin(theta) = dy / length. // cos(theta) = dx / length. // Flip dy for gl flip. - double sinVal = length < kFltEpsilon ? 0 : -dy / length; - double cosVal = length < kFltEpsilon ? 1 : dx / length; - final Matrix4 translateToOrigin = matrix4 == null - ? Matrix4.translationValues(-from.dx, -from.dy, 0) - : Matrix4.fromFloat32List(matrix4!.matrix) - ..translate(-from.dx, -from.dy); + final double sinVal = length < kFltEpsilon ? 0 : -dy / length; + final double cosVal = length < kFltEpsilon ? 1 : dx / length; + // If tile mode is repeated we need to shift the center of from->to + // vector to the center of shader bounds. + final bool isRepeated = tileMode != ui.TileMode.clamp; + double originX = isRepeated + ? (shaderBounds.width / 2) + : (fromX + toX) / 2.0 - shaderBounds.left; + double originY = isRepeated + ? (shaderBounds.height / 2) + : (fromY + toY) / 2.0 - shaderBounds.top; + + final Matrix4 translateToOrigin = Matrix4.translationValues(-originX, -originY, 0); // Rotate around Z axis. final Matrix4 rotationZ = Matrix4.identity(); final Float32List storage = rotationZ.storage; storage[0] = cosVal; - storage[1] = -sinVal; - storage[4] = sinVal; + // Sign is flipped since gl coordinate system is flipped around y axis. + storage[1] = sinVal; + storage[4] = -sinVal; storage[5] = cosVal; Matrix4 gradientTransform = Matrix4.identity(); + // We compute location based on gl_FragCoord to center distance which + // returns 0.0 at center. To make sure we align center of gradient to this + // point, we shift by 0.5 to get st value for center of gradient. + if (tileMode != ui.TileMode.repeated) { + gradientTransform.translate(0.5, 0); + } if (length > kFltEpsilon) { gradientTransform.scale(1.0 / length); } + if (matrix4 != null) { + // Flutter GradientTransform is defined in shaderBounds coordinate system + // with flipped y axis. + // We flip y axis, translate to center, multiply matrix and translate + // and flip back so it is applied correctly. + final Matrix4 m4 = Matrix4.fromFloat32List(matrix4!.matrix); + gradientTransform.scale(1, -1); + gradientTransform.translate(-shaderBounds.center.dx, -shaderBounds.center.dy); + gradientTransform.multiply(m4); + gradientTransform.translate(shaderBounds.center.dx, shaderBounds.center.dy); + gradientTransform.scale(1, -1); + } + gradientTransform.multiply(rotationZ); gradientTransform.multiply(translateToOrigin); - // Setup gradient uniforms for t search. normalizedGradient.setupUniforms(gl, glProgram); // Setup matrix transform uniform. @@ -269,11 +301,11 @@ class GradientLinear extends EngineGradient { // Multiply with m_gradient transform to convert from fragment coordinate to // distance on the from-to line. method.addStatement( - 'vec4 localCoord = vec4(gl_FragCoord.x, ' - 'u_resolution.y - gl_FragCoord.y, 0, 1) * m_gradient;'); + 'vec4 localCoord = m_gradient * vec4(gl_FragCoord.x, ' + 'u_resolution.y - gl_FragCoord.y, 0, 1);'); method.addStatement('float st = localCoord.x;'); final String probeName = - _writeSharedGradientShader(builder, method, gradient, tileMode, true); + _writeSharedGradientShader(builder, method, gradient, tileMode); method.addStatement('${fragColor.name} = ${probeName} * scale + bias;'); String shader = builder.build(); return shader; @@ -311,7 +343,7 @@ void _addColorStopsToCanvasGradient(html.CanvasGradient gradient, String _writeSharedGradientShader(ShaderBuilder builder, ShaderMethod method, NormalizedGradient gradient, - ui.TileMode tileMode, bool shiftOrigin) { + ui.TileMode tileMode) { method.addStatement('vec4 bias;'); method.addStatement('vec4 scale;'); // Write uniforms for each threshold, bias and scale. @@ -328,21 +360,20 @@ String _writeSharedGradientShader(ShaderBuilder builder, String probeName = 'st'; switch (tileMode) { case ui.TileMode.clamp: + method.addStatement('float tiled_st = clamp(st, 0.0, 1.0);'); + probeName = 'tiled_st'; + break; case ui.TileMode.decal: break; case ui.TileMode.repeated: // st represents our distance from center. Flutter maps the center to // center of gradient ramp so we need to add 0.5 to make sure repeated // pattern center is at origin. - method.addStatement(shiftOrigin ? - 'float tiled_st = fract(st + 0.5);' - : 'float tiled_st = fract(st);'); + method.addStatement('float tiled_st = fract(st);'); probeName = 'tiled_st'; break; case ui.TileMode.mirror: - method.addStatement(shiftOrigin ? - 'float t_1 = (st - 0.5);' - : 'float t_1 = (st - 1.0);'); + 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'; @@ -453,7 +484,7 @@ class GradientRadial extends EngineGradient { method.addStatement('' 'float st = abs(dist / u_radius);'); final String probeName = - _writeSharedGradientShader(builder, method, gradient, tileMode, false); + _writeSharedGradientShader(builder, method, gradient, tileMode); method.addStatement('${fragColor.name} = ${probeName} * scale + bias;'); String shader = builder.build(); return shader; 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 c20c646d3cce1..a0615fd36b66f 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 @@ -296,6 +296,54 @@ void testMain() async { canvas.restore(); await _checkScreenshot(canvas, 'sweep_gradient_path'); }); + + /// Regression test for https://github.com/flutter/flutter/issues/74137. + test('Paints rotated and shifted linear gradient', () 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 linearGradient = GradientLinear(Offset(50, 50), + Offset(200,130), + colors, stops, TileMode.clamp, + Matrix4.identity().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 = engineLinearGradientToShader(linearGradient, rectBounds)); + canvas.drawRect(rectBounds, borderPaint); + + // Tile mode repeat + rectBounds = Rect.fromLTWH(10, 110, kBoxWidth, kBoxHeight); + linearGradient = GradientLinear(Offset(50, 50), + Offset(200,130), + colors, stops, TileMode.repeated, + Matrix4.identity().storage); + + canvas.drawRect(rectBounds, + new Paint()..shader = engineLinearGradientToShader(linearGradient, rectBounds)); + canvas.drawRect(rectBounds, borderPaint); + + canvas.restore(); + await _checkScreenshot(canvas, 'linear_gradient_rect_shifted'); + }); } Shader engineGradientToShader(GradientSweep gradient, Rect rect) { @@ -310,6 +358,14 @@ Shader engineGradientToShader(GradientSweep gradient, Rect rect) { ); } +Shader engineLinearGradientToShader(GradientLinear gradient, Rect rect) { + return Gradient.linear(gradient.from, gradient.to, + gradient.colors, gradient.colorStops, gradient.tileMode, + gradient.matrix4 == null ? null : Float64List.fromList( + gradient.matrix4.matrix), + ); +} + Path samplePathFromRect(Rect rectBounds) => Path() ..moveTo(rectBounds.center.dx, rectBounds.top)