-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Description
Steps to Reproduce
- First, build app below without SkSL shader bundle:
flutter build apk - Install APK:
adb install ... - Verify that the UI looks as expected.
- Now, rebuild with a shader file made with a Pixel 5 device:
flutter build apk --bundle-sksl-path warmup_2022-04-27_android_pixel5.sksl.json
warmup_2022-04-27_android_pixel5.sksl.json.zip
- Verify that the UI still looks as expected
- Now, rebuild with a shader file made with a Nokia 1.3:
flutter build apk --bundle-sksl-path warmup_2022-04-27_android_nokia13.sksl.json(this file was created by running the exact same map, mind you)
warmup_2022-04-27_android_nokia13.sksl.json.zip
- Install this new APK:
adb install ... - Look at the UI now.
Expected results: Same UI as without the shader warmup performance optimization, or with a different SkSL warmup file.
It looks like the shader somehow elides lines. (The grid above is made "roughly drawn" by painting many lines close to each other, in segments.)
I personally find this issue very serious. There's no indication there might be a problem until the app is built. I caught this after submitting the app for review to the stores.
Code sample
Sorry, I don't have time to make a completely standalone example, but at least I'm showing the CustomPainter-using widget that seems to have this problem. I'll try to whittle it down later.
EDIT: I have provided a small main.dart file in a comment below. Here it is:
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:flutter/material.dart';
import 'dart:math';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: const Center(
child: AspectRatio(
aspectRatio: 1 / 1,
child: RoughGrid(8, 8),
),
),
);
}
}
class RoughGrid extends StatelessWidget {
final int width;
final int height;
const RoughGrid(this.width, this.height, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// This is normally provided from above.
// ignore: prefer_const_constructors
final lineColor = Color(0x88000000);
return Stack(
fit: StackFit.expand,
children: [
// First, "draw" (reveal) the horizontal lines
TweenAnimationBuilder(
tween: Tween<double>(begin: 0, end: 1),
duration: const Duration(milliseconds: 900),
curve: Curves.easeOutCubic,
child: RepaintBoundary(
child: CustomPaint(
painter: _RoughGridPainter(
width,
height,
lineColor: lineColor,
paintOnly: Axis.horizontal,
),
),
),
builder: (BuildContext context, double progress, Widget? child) {
return ShaderMask(
// BlendMode.dstIn means that opacity of the linear
// gradient below will be applied to the child (the horizontal
// lines).
blendMode: BlendMode.dstIn,
shaderCallback: (Rect bounds) {
// A linear gradient that sweeps from
// "top-slightly-left-off-center" to
// "bottom-slightly-right-of-center". This achieves the
// quick "drawing" of the lines.
return LinearGradient(
begin: const Alignment(-0.1, -1),
end: const Alignment(0.1, 1),
colors: [
Colors.black,
Colors.white.withOpacity(0),
],
stops: [
progress,
progress + 0.05,
],
).createShader(bounds);
},
child: child!,
);
},
),
// Same as above, but for vertical lines.
TweenAnimationBuilder(
// The tween start's with a negative number to achieve
// a bit of delay before drawing. This is quite dirty, so maybe
// optimize later?
tween: Tween<double>(begin: -1, end: 1),
// Take longer to draw.
duration: const Duration(milliseconds: 1200),
curve: Curves.easeOut,
child: RepaintBoundary(
child: CustomPaint(
painter: _RoughGridPainter(
width,
height,
lineColor: lineColor,
paintOnly: Axis.vertical,
),
),
),
builder: (BuildContext context, double progress, Widget? child) {
return ShaderMask(
blendMode: BlendMode.dstIn,
shaderCallback: (Rect bounds) {
return LinearGradient(
begin: const Alignment(-1, -0.1),
end: const Alignment(1, 0.1),
colors: [
Colors.black,
Colors.white.withOpacity(0),
],
stops: [
progress,
progress + 0.05,
],
).createShader(bounds);
},
child: child!,
);
},
),
],
);
}
}
class _RoughGridPainter extends CustomPainter {
final int width;
final int height;
final Color lineColor;
final Axis? paintOnly;
late final Paint pathPaint = Paint()
..colorFilter = ColorFilter.mode(lineColor, BlendMode.srcIn);
final Random _random = Random();
_RoughGridPainter(
this.width,
this.height, {
this.lineColor = Colors.black,
this.paintOnly,
});
@override
void paint(Canvas canvas, Size size) {
const padding = 10.0;
const maxCrossDisplacement = 1.5;
const gridLineThicknessRatio = 0.1;
final lineThickness =
size.longestSide / max(width, height) * gridLineThicknessRatio;
final widthStep = size.width / width;
// Draw vertical lines.
if (paintOnly == null || paintOnly == Axis.vertical) {
for (var i = 1; i < width; i++) {
_roughLine(
canvas: canvas,
start: Offset(i * widthStep, padding),
direction: Axis.vertical,
length: size.height - 2 * padding,
maxLineThickness: lineThickness,
maxCrossAxisDisplacement: maxCrossDisplacement,
paint: pathPaint,
random: _random,
);
}
}
// Draw horizontal lines.
final heightStep = size.height / height;
if (paintOnly == null || paintOnly == Axis.horizontal) {
for (var i = 1; i < height; i++) {
_roughLine(
canvas: canvas,
start: Offset(padding, i * heightStep),
direction: Axis.horizontal,
length: size.width - 2 * padding,
maxLineThickness: lineThickness,
maxCrossAxisDisplacement: maxCrossDisplacement,
paint: pathPaint,
random: _random,
);
}
}
}
@override
bool shouldRepaint(_RoughGridPainter oldDelegate) {
return oldDelegate.width != width ||
oldDelegate.height != height ||
oldDelegate.paintOnly != paintOnly ||
oldDelegate.lineColor != lineColor;
}
static void _roughLine({
required Canvas canvas,
required Offset start,
required Axis direction,
required double length,
required double maxLineThickness,
required double maxCrossAxisDisplacement,
required Paint paint,
Random? random,
}) {
const segmentLength = 50.0;
const brushCount = 7;
final Offset straightSegment;
final Offset end;
if (direction == Axis.horizontal) {
straightSegment = const Offset(segmentLength, 0);
end = start + Offset(length, 0);
} else {
straightSegment = const Offset(0, segmentLength);
end = start + Offset(0, length);
}
final _random = random ?? Random();
var angle = _random.nextDouble() * 2 * pi;
final angleChange = 0.3 + 0.4 * _random.nextDouble();
// Generate a displacement of "strands" that constitute the whole brush.
// Each strand will make its own line.
final strandOffsets = List.generate(brushCount, (index) {
var angle = _random.nextDouble() * 2 * pi;
return Offset.fromDirection(
angle, _random.nextDouble() * maxLineThickness / 3);
});
var straightPoint = start;
final fuzziness = Offset.fromDirection(angle, maxCrossAxisDisplacement);
var fuzzyPoint = start + fuzziness;
for (var i = 0; straightPoint != end; i++) {
angle += angleChange;
var nextStraightPoint = straightPoint + straightSegment;
if ((nextStraightPoint - start).distance >= length) {
nextStraightPoint = end;
}
final fuzziness = Offset.fromDirection(angle, maxCrossAxisDisplacement);
final nextFuzzyPoint = nextStraightPoint + fuzziness;
if (i == 0 || nextStraightPoint == end) {
paint.strokeCap = StrokeCap.round;
} else {
paint.strokeCap = StrokeCap.butt;
}
// Drawing individual "strands" makes the line more natural.
for (final strandOffset in strandOffsets) {
paint.strokeWidth = (0.8 + 0.4 * _random.nextDouble()) *
maxLineThickness /
brushCount *
2;
canvas.drawLine(
fuzzyPoint + strandOffset, nextFuzzyPoint + strandOffset, paint);
}
straightPoint = nextStraightPoint;
fuzzyPoint = nextFuzzyPoint;
}
}
}Logs
% flutter analyze
Analyzing flutter_game_sample...
No issues found! (ran in 3.3s)
% fvm flutter doctor -v
[✓] Flutter (Channel stable, 2.10.4, on macOS 12.2 21D49 darwin-arm, locale en-US)
• Flutter version 2.10.4 at /Users/filiph/fvm/versions/stable
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision c860cba910 (5 weeks ago), 2022-03-25 00:23:12 -0500
• Engine revision 57d3bac3dd
• Dart version 2.16.2
• DevTools version 2.9.2
[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0-rc1)
• Android SDK at /Users/filiph/Library/Android/sdk
• Platform android-31, build-tools 33.0.0-rc1
• Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build 11.0.11+0-b60-7772763)
• All Android licenses accepted.
[✓] Xcode - develop for iOS and macOS (Xcode 13.3.1)
• Xcode at /Applications/Xcode.app/Contents/Developer
• CocoaPods version 1.11.2
[✓] Chrome - develop for the web
• Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
[✓] Android Studio (version 2021.1)
• Android Studio at /Applications/Android Studio.app/Contents
• Flutter plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/6351-dart
• Java version OpenJDK Runtime Environment (build 11.0.11+0-b60-7772763)
[✓] VS Code (version 1.56.2)
• VS Code at /Applications/Visual Studio Code.app/Contents
• Flutter extension can be installed from:
🔨 https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter
[✓] Connected device (3 available)
• Pixel 5 (mobile) • 15131FDD4000QY • android-arm64 • Android 12 (API 32)
• macOS (desktop) • macos • darwin-arm64 • macOS 12.2 21D49 darwin-arm
• Chrome (web) • chrome • web-javascript • Google Chrome 100.0.4896.127
[✓] HTTP Host Availability
• All required HTTP hosts are available
• No issues found!

