Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

[web] Fix rendering of gradients in html mode #40345

Merged
merged 11 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 39 additions & 16 deletions lib/web_ui/lib/src/engine/html/shaders/shader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,23 @@ class GradientSweep extends EngineGradient {
final double centerX = (center.dx - shaderBounds.left) / (shaderBounds.width);
final double centerY = (center.dy - shaderBounds.top) / (shaderBounds.height);
gl.setUniform2f(tileOffset, 2 * (shaderBounds.width * (centerX - 0.5)),
2 * (shaderBounds.height * (centerY - 0.5)));
2 * (shaderBounds.height * (0.5 - centerY)));
final Object angleRange = gl.getUniformLocation(glProgram.program, 'angle_range');
gl.setUniform2f(angleRange, startAngle, endAngle);
normalizedGradient.setupUniforms(gl, glProgram);

final Object gradientMatrix =
gl.getUniformLocation(glProgram.program, 'm_gradient');
gl.setUniformMatrix4fv(gradientMatrix, false, matrix4 ?? Matrix4.identity().storage);
final Matrix4 gradientTransform = Matrix4.identity();
if (matrix4 != null) {
final Matrix4 m4 = Matrix4.zero()
..copyInverse(Matrix4.fromFloat32List(matrix4!));
gradientTransform.translate(-center.dx, -center.dy);
gradientTransform.multiply(m4);
gradientTransform.translate(center.dx, center.dy);
}
gl.setUniformMatrix4fv(gradientMatrix, false, gradientTransform.storage);

final Object result = () {
if (createDataUrl) {
return glRenderer!.drawRectToImageUrl(
Expand Down Expand Up @@ -149,7 +159,7 @@ class GradientSweep extends EngineGradient {
// 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;');
'vec4 localCoord = m_gradient * vec4(gl_FragCoord.x - center.x, center.y - gl_FragCoord.y, 0, 1);');
method.addStatement(
'float angle = atan(-localCoord.y, -localCoord.x) + ${math.pi};');
method.addStatement('float sweep = angle_range.y - angle_range.x;');
Expand Down Expand Up @@ -317,14 +327,12 @@ class GradientLinear extends EngineGradient {
// 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);
final Matrix4 m4 = Matrix4.zero()
..copyInverse(Matrix4.fromFloat32List(matrix4!.matrix));
final ui.Offset center = shaderBounds.center;
gradientTransform.translate(-center.dx, -center.dy);
gradientTransform.multiply(m4);
gradientTransform.translate(
shaderBounds.center.dx, shaderBounds.center.dy);
gradientTransform.scale(1, -1);
gradientTransform.translate(center.dx, center.dy);
}

gradientTransform.multiply(rotationZ);
Expand Down Expand Up @@ -465,6 +473,12 @@ String _writeSharedGradientShader(ShaderBuilder builder, ShaderMethod method,
sourcePrefix: 'threshold',
biasName: 'bias',
scaleName: 'scale');
if (tileMode == ui.TileMode.decal) {
method.addStatement('if (st < 0.0 || st > 1.0) {');
method.addStatement(' ${builder.fragmentColor.name} = vec4(0, 0, 0, 0);');
method.addStatement(' return;');
method.addStatement('}');
}
return probeName;
}

Expand All @@ -483,7 +497,7 @@ class GradientRadial extends EngineGradient {
@override
Object createPaintStyle(DomCanvasRenderingContext2D? ctx,
ui.Rect? shaderBounds, double density) {
if (tileMode == ui.TileMode.clamp || tileMode == ui.TileMode.decal) {
if (matrix4 == null && (tileMode == ui.TileMode.clamp || tileMode == ui.TileMode.decal)) {
return _createCanvasGradient(ctx, shaderBounds, density);
} else {
return _createGlGradient(ctx, shaderBounds, density);
Expand Down Expand Up @@ -533,15 +547,24 @@ class GradientRadial extends EngineGradient {
final double centerX = (center.dx - shaderBounds.left) / (shaderBounds.width);
final double centerY = (center.dy - shaderBounds.top) / (shaderBounds.height);
gl.setUniform2f(tileOffset, 2 * (shaderBounds.width * (centerX - 0.5)),
2 * (shaderBounds.height * (centerY - 0.5)));
2 * (shaderBounds.height * (0.5 - centerY)));
final Object radiusUniform = gl.getUniformLocation(glProgram.program, 'u_radius');
gl.setUniform1f(radiusUniform, radius);
normalizedGradient.setupUniforms(gl, glProgram);

final Object gradientMatrix =
gl.getUniformLocation(glProgram.program, 'm_gradient');
gl.setUniformMatrix4fv(gradientMatrix, false,
matrix4 == null ? Matrix4.identity().storage : matrix4!);

final Matrix4 gradientTransform = Matrix4.identity();

if (matrix4 != null) {
final Matrix4 m4 = Matrix4.zero()
..copyInverse(Matrix4.fromFloat32List(matrix4!));
gradientTransform.translate(-center.dx, -center.dy);
gradientTransform.multiply(m4);
gradientTransform.translate(center.dx, center.dy);
}
gl.setUniformMatrix4fv(gradientMatrix, false, gradientTransform.storage);

final Object result = () {
if (createDataUrl) {
Expand Down Expand Up @@ -587,7 +610,7 @@ class GradientRadial extends EngineGradient {
// 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;');
'vec4 localCoord = m_gradient * vec4(gl_FragCoord.x - center.x, center.y - gl_FragCoord.y, 0, 1);');
method.addStatement('float dist = length(localCoord);');
method.addStatement(
'float st = abs(dist / u_radius);');
Expand Down Expand Up @@ -666,7 +689,7 @@ class GradientConical extends GradientRadial {
// 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;');
'vec4 localCoord = m_gradient * vec4(gl_FragCoord.x - center.x, center.y - gl_FragCoord.y, 0, 1);');
method.addStatement('float dist = length(localCoord);');
final String f = (focalRadius /
(math.min(shaderBounds.width, shaderBounds.height) / 2.0))
Expand Down
187 changes: 184 additions & 3 deletions lib/web_ui/test/html/shaders/gradient_golden_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ Future<void> testMain() async {
RenderStrategy());
canvas.endRecording();
canvas.apply(engineCanvas, screenRect);
});
}, skip: isFirefox);

test("Creating lots of gradients doesn't create too many webgl contexts",
() async {
Expand Down Expand Up @@ -431,7 +431,7 @@ Future<void> testMain() async {

canvas.restore();
await canvasScreenshot(canvas, 'linear_gradient_rect_clamp_rotated', canvasRect: screenRect, region: region);
});
}, skip: isFirefox);

test('Paints linear gradient properly when within svg context', () async {
final RecordingCanvas canvas =
Expand Down Expand Up @@ -465,7 +465,176 @@ Future<void> testMain() async {

canvas.restore();
await canvasScreenshot(canvas, 'linear_gradient_in_svg_context', canvasRect: screenRect, region: region);
});
}, skip: isFirefox);

test('Paints transformed linear gradient', () async {
final RecordingCanvas canvas =
RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
canvas.save();

const List<Color> colors = <Color>[
Color(0xFF000000),
Color(0xFFFF3C38),
Color(0xFFFF8C42),
Color(0xFFFFF275),
Color(0xFF6699CC),
Color(0xFF656D78),
];

const List<double> stops = <double>[0.0, 0.05, 0.4, 0.6, 0.9, 1.0];

final Matrix4 transform = Matrix4.identity()
..translate(50, 50)
..scale(0.3, 0.7)
..rotateZ(0.5);

final GradientLinear linearGradient = GradientLinear(
const Offset(5, 5),
const Offset(200, 130),
colors,
stops,
TileMode.clamp,
transform.storage,
);

const double kBoxWidth = 150;
const double kBoxHeight = 80;

Rect rectBounds = const Rect.fromLTWH(10, 20, kBoxWidth, kBoxHeight);
canvas.drawRect(
rectBounds,
SurfacePaint()
..shader = engineLinearGradientToShader(linearGradient, rectBounds),
);

rectBounds = const Rect.fromLTWH(10, 110, kBoxWidth, kBoxHeight);
canvas.drawOval(
rectBounds,
SurfacePaint()
..shader = engineLinearGradientToShader(linearGradient, rectBounds),
);

canvas.restore();
await canvasScreenshot(
canvas,
'linear_gradient_clamp_transformed',
canvasRect: screenRect,
region: region,
);
}, skip: isFirefox);

test('Paints transformed sweep gradient', () async {
final RecordingCanvas canvas =
RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
canvas.save();

const List<Color> colors = <Color>[
Color(0xFF000000),
Color(0xFFFF3C38),
Color(0xFFFF8C42),
Color(0xFFFFF275),
Color(0xFF6699CC),
Color(0xFF656D78),
];

const List<double> stops = <double>[0.0, 0.05, 0.4, 0.6, 0.9, 1.0];

final Matrix4 transform = Matrix4.identity()
..translate(100, 150)
..scale(0.3, 0.7)
..rotateZ(0.5);

final GradientSweep sweepGradient = GradientSweep(
const Offset(0.5, 0.5),
colors,
stops,
TileMode.clamp,
0.0,
2 * math.pi,
transform.storage,
);

const double kBoxWidth = 150;
const double kBoxHeight = 80;

Rect rectBounds = const Rect.fromLTWH(10, 20, kBoxWidth, kBoxHeight);
canvas.drawRect(
rectBounds,
SurfacePaint()
..shader = engineGradientToShader(sweepGradient, rectBounds),
);

rectBounds = const Rect.fromLTWH(10, 110, kBoxWidth, kBoxHeight);
canvas.drawOval(
rectBounds,
SurfacePaint()
..shader = engineGradientToShader(sweepGradient, rectBounds),
);

canvas.restore();
await canvasScreenshot(
canvas,
'sweep_gradient_clamp_transformed',
canvasRect: screenRect,
region: region,
);
}, skip: isFirefox);

test('Paints transformed radial gradient', () async {
final RecordingCanvas canvas =
RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
canvas.save();

const List<Color> colors = <Color>[
Color(0xFF000000),
Color(0xFFFF3C38),
Color(0xFFFF8C42),
Color(0xFFFFF275),
Color(0xFF6699CC),
Color(0xFF656D78),
];

const List<double> stops = <double>[0.0, 0.05, 0.4, 0.6, 0.9, 1.0];

final Matrix4 transform = Matrix4.identity()
..translate(50, 50)
..scale(0.3, 0.7)
..rotateZ(0.5);

final GradientRadial radialGradient = GradientRadial(
const Offset(0.5, 0.5),
400,
colors,
stops,
TileMode.clamp,
transform.storage,
);

const double kBoxWidth = 150;
const double kBoxHeight = 80;

Rect rectBounds = const Rect.fromLTWH(10, 20, kBoxWidth, kBoxHeight);
canvas.drawRect(
rectBounds,
SurfacePaint()
..shader = engineRadialGradientToShader(radialGradient, rectBounds),
);

rectBounds = const Rect.fromLTWH(10, 110, kBoxWidth, kBoxHeight);
canvas.drawOval(
rectBounds,
SurfacePaint()
..shader = engineRadialGradientToShader(radialGradient, rectBounds),
);

canvas.restore();
await canvasScreenshot(
canvas,
'radial_gradient_clamp_transformed',
canvasRect: screenRect,
region: region,
);
}, skip: isFirefox);
}

Shader engineGradientToShader(GradientSweep gradient, Rect rect) {
Expand All @@ -488,6 +657,18 @@ Shader engineLinearGradientToShader(GradientLinear gradient, Rect rect) {
);
}

Shader engineRadialGradientToShader(GradientRadial gradient, Rect rect) {
return Gradient.radial(
Offset(rect.left + gradient.center.dx * rect.width,
rect.top + gradient.center.dy * rect.height),
gradient.radius,
gradient.colors,
gradient.colorStops,
gradient.tileMode,
gradient.matrix4 == null ? null : Float64List.fromList(gradient.matrix4!),
);
}

Path samplePathFromRect(Rect rectBounds) =>
Path()
..moveTo(rectBounds.center.dx, rectBounds.top)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ Future<void> testMain() async {
}
expect(rc.renderStrategy.hasArbitraryPaint, isTrue);
await canvasScreenshot(rc, 'linear_gradient_oval_matrix');
});
}, skip: isFirefox);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did this suddenly start failing on Firefox? Thoughts @yjbanov ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised this wasn't failing before. We don't have infra to golden-test Firefox. @eyebrowsoffire reorged our tests recently and might know more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think these tests will fail on Firefox, but they will not take screenshots or send them to skia gold. However, it may be of at least some utility to just run the tests on Firefox anyway just to make sure you can actually do the drawing commands and not crash or throw an exception. I'll leave it up to you though, it might not be worthwhile to run them.


// Regression test for https://github.com/flutter/flutter/issues/50010
test('Should draw linear gradient using rounded rect.', () async {
Expand Down