Repository: flutter
Issue: Building Android app with SkSL warmup introduces rendering bug
Labels: platform-android,engine,c: performance,dependency: skia,c: rendering,has reproducible steps,P2,found in release: 2.10,team-android,triaged-android

## Steps to Reproduce

1. First, build app below without SkSL shader bundle: `flutter build apk`
2. Install APK: `adb install ...`
3. Verify that the UI looks as expected.
<img src="https://user-images.githubusercontent.com/919717/165543115-e15a7004-ed1e-4fa1-b0aa-4410310d50ff.png" width ="300"> 

4. 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](https://github.com/flutter/flutter/files/8575320/warmup_2022-04-27_android_pixel5.sksl.json.zip)

6. Verify that the UI still looks  as expected
4. 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](https://github.com/flutter/flutter/files/8575322/warmup_2022-04-27_android_nokia13.sksl.json.zip)

7. Install this new APK: `adb install ...`
8. Look at the UI now.

**Expected results:** Same UI as without the shader warmup performance optimization, or with a different SkSL warmup file.

**Actual results:** 
<img src="https://user-images.githubusercontent.com/919717/165543592-f107837c-c699-48fc-8cfc-b9e7baeff5e2.png" width ="300"> 

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.

<details>
<summary>Code sample</summary>

~~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:


```dart
// 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;
    }
  }
}
```



</details>

<details>
  <summary>Logs</summary>

<!--
      Run your application with `flutter run --verbose` and attach all the
      log output below between the lines with the backticks. If there is an
      exception, please see if the error message includes enough information
      to explain how to solve the issue.
-->

```
```

<!--
     Run `flutter analyze` and attach any output of that command below.
     If there are any analysis errors, try resolving them before filing this issue.
-->

```
% flutter analyze
Analyzing flutter_game_sample...
No issues found! (ran in 3.3s)
```

<!-- Finally, paste the output of running `flutter doctor -v` here. -->

```
% 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!
```

</details>