Repository: flutter
Issue: Rasterization takes unusual long time when Paragraph objects are reused in multiple frames
Labels: framework,engine,c: performance,a: typography,has reproducible steps,P2,found in release: 2.10,found in release: 2.11,team-engine,triaged-engine

<!-- Thank you for using Flutter!

     If you are looking for support, please check out our documentation
     or consider asking a question on Stack Overflow:
      * https://flutter.dev/
      * https://api.flutter.dev/
      * https://stackoverflow.com/questions/tagged/flutter?sort=frequent

     If you have found a performance problem, then fill out the template below.
     Please read our guide to filing a bug first: https://flutter.dev/docs/resources/bug-reports
-->

<!--
1.  Please tell us exactly how to reproduce the problem you are running into.

2.  Please attach a small application (ideally just one main.dart file) that
     reproduces the problem. You could use https://gist.github.com/ for this.

3.  Switch flutter to master channel and run this app on a physical device
     using profile mode with Skia tracing enabled, as follows:
       flutter channel master
       flutter run --profile --trace-skia

     The bleeding edge master channel is encouraged here because Flutter is
     constantly fixing bugs and improving its performance. Your problem in an
     older Flutter version may have already been solved in the master channel.

4.  Record a video of the performance issue using another phone so we
     can have an intuitive understanding of what happened. Don’t use
     "adb screenrecord", as that affects the performance of the profile run.

5.  Open Observatory and save a timeline trace of the performance issue
     so we know which functions might be causing it. See "How to Collect
     and Read Timeline Traces" on this blog post:
       https://medium.com/flutter/profiling-flutter-applications-using-the-timeline-a1a434964af3#a499
     Make sure the performance overlay is turned OFF when recording the
     trace as that may affect the performance of the profile run.
     (Pressing ‘P’ on the command line toggles the overlay.)
-->

<!--
     Please tell us which target platform(s) the problem occurs (Android / iOS / Web / macOS / Linux / Windows)
     Which target OS version, for Web, browser, is the test system running?
     Does the problem occur on emulator/simulator as well as on physical devices?
-->

### Use case:

I'm writing a [terminal emulator widget](https://pub.dev/packages/xterm) in flutter. Because `Paragraph.layout` is expensive, I have to cache `Paragraph`s between frames (as suggested [here](https://github.com/flutter/flutter/issues/18656#issuecomment-405091420)).

### The problem:

When a `Paragraph` is cached and painted multiple times to different canvas, if the repaint is triggered by dragging the scrollbar or swiping the touch screen, the rasterization time will become longer and longer. However if the scrolling is triggered by the  scroll wheel or trackpad, then everything works fine.

![image](https://user-images.githubusercontent.com/15033141/155981520-2e42105d-87e8-4736-b2f3-e2e285c8fb27.png)

### How to reproduce:

Run the code sample below in Android or MacOS (may also reproducible in other platforms, not tested), then scroll by dragging the scrollbar or swiping the touch screen.

![image](https://user-images.githubusercontent.com/15033141/155983320-7471e009-aa69-4a30-bb4e-ec102a3b6f8b.png)

## Details

**Target Platform:** Android, MacOS (perhaps also reproducible in other platforms, not tested)
**Target OS version/browser:** Android 11, MacOS Monterey
**Devices:** Google Pixel 2 XL, MacBook Pro 2015
**Flutter**: 2.5.3, 2.10.2, 2.11.0-0.0.pre.695

**DevTools screenshot:**(with `--profile` and `--trace-skia` on)

![image](https://user-images.githubusercontent.com/15033141/155981900-b2b30dee-dee7-421d-8e34-e812a06ad115.png)

**Screen record:**

https://user-images.githubusercontent.com/15033141/155980378-25ce91b9-0047-48ea-9301-829d71e10692.mp4



## Minimal reproducible code sample:

Gist: https://gist.github.com/xtyxtyx/2e1eb0ceecb23676472c4d4c81d02a42

<details>
<summary>Code</summary>

```dart
import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Flutter Demo',
      home: Demo(),
    );
  }
}

class Demo extends StatelessWidget {
  const Demo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      clipBehavior: Clip.antiAlias,
      decoration: const BoxDecoration(
        color: Colors.transparent,
      ),
      child: Scrollable(
        viewportBuilder: (context, offset) {
          return DemoViewport(offset: offset);
        },
      ),
    );
  }
}

class DemoViewport extends LeafRenderObjectWidget {
  const DemoViewport({Key? key, required this.offset}) : super(key: key);

  final ViewportOffset offset;

  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderDemoViewport(offset);
  }

  @override
  void updateRenderObject(
      BuildContext context, RenderDemoViewport renderObject) {
    renderObject.offset = offset;
  }
}

class RenderDemoViewport extends RenderBox {
  RenderDemoViewport(this._offset);

  ViewportOffset _offset;
  set offset(ViewportOffset value) {
    if (value == _offset) return;
    if (attached) _offset.removeListener(_hasScrolled);
    _offset = value;
    if (attached) _offset.addListener(_hasScrolled);
    markNeedsLayout();
  }

  @override
  void attach(PipelineOwner owner) {
    super.attach(owner);
    _offset.addListener(_hasScrolled);
  }

  @override
  void detach() {
    super.detach();
    _offset.removeListener(_hasScrolled);
  }

  void _hasScrolled() {
    markNeedsLayout();
  }

  @override
  void performLayout() {
    size = constraints.biggest;
    _offset.applyViewportDimension(size.height);
    _offset.applyContentDimensions(0, 1000);
  }

  Paragraph? _paragraph;
  Paragraph get paragraph {
    if (_paragraph == null) {
      const style = TextStyle(color: Colors.black);
      final paragraphBuilder = ParagraphBuilder(style.getParagraphStyle());
      paragraphBuilder.pushStyle(style.getTextStyle());
      paragraphBuilder.addText('m');

      final paragraph = paragraphBuilder.build();
      paragraph.layout(ParagraphConstraints(width: size.width));
      _paragraph = paragraph;
    }
    return _paragraph!;
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    for (var row = 0; row < 60; row++) {
      for (var col = 0; col < 60; col++) {
        final offset = Offset(col * 12.0, row * 15.0 - _offset.pixels);
        context.canvas.drawParagraph(paragraph, offset);
      }
    }
  }
}
```
</details>

## Timeline trace 

[dart-timeline-2022-2-28 .json.zip](https://github.com/flutter/flutter/files/8154419/dart-timeline-2022-2-28.json.zip)


## Logs

<details>
<summary>Logs</summary>

<!--
     Run `flutter analyze` and attach any output of that command below.
     If there are any analysis errors, try resolving them before filing this issue.
-->

```
Analyzing flutter_scroll_issue...                                       
No issues found! (ran in 23.0s)
```

<!-- Finally, paste the output of running `flutter doctor -v` here, with your device plugged in. -->

```
[✓] Flutter (Channel master, 2.11.0-0.0.pre.695, on macOS 12.2.1 21D62 darwin-x64, locale en-CN)
    • Flutter version 2.11.0-0.0.pre.695 at /Users/xuty/codehouse/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 3a6d00e5f3 (13 hours ago), 2022-02-26 19:56:16 -0500
    • Engine revision f93dc3d786
    • Dart version 2.17.0 (build 2.17.0-150.0.dev)
    • DevTools version 2.11.1

[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
    • Android SDK at /Users/xuty/Library/Android/sdk
    • Platform android-31, build-tools 30.0.3
    • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 13.2.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 4.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 1.8.0_242-release-1644-b3-6222593)

[✓] IntelliJ IDEA Community Edition (version 2021.1.2)
    • IntelliJ at /Applications/IntelliJ IDEA CE.app
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin version 211.7792

[✓] VS Code (version 1.64.2)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.29.0

[✓] Connected device (2 available)
    • macOS (desktop) • macos  • darwin-x64     • macOS 12.2.1 21D62 darwin-x64
    • Chrome (web)    • chrome • web-javascript • Google Chrome 94.0.4606.61

[✓] HTTP Host Availability
    • All required HTTP hosts are available

• No issues found!
```

</details>