Repository: flutter
Issue: drawPoints is slow for large numbers of points
Labels: engine,has reproducible steps,P3,team-engine,triaged-engine,found in release: 3.22,found in release: 3.24

Beyond about 10,000 points, the raster overhead of a drawPoints call becomes too slow to be usable in interactive UIs or animations.

It seems to be worse on Skia on Linux than on Impeller and Android, though it is a problem in both cases.

<details>

<summary>Benchmark code</summary>

```dart
import 'dart:math' as math;
import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

void main() {
  runApp(const Test());
}

class Test extends StatefulWidget {
  const Test({super.key});

  @override
  State<Test> createState() => _TestState();
}

class _TestState extends State<Test> {
  final List<Offset> points = [];

  final math.Random random = math.Random(0);
  
  @override
  void initState() {
    super.initState();
    print('count,vsyncOverhead,buildDuration,rasterDuration,totalSpan');
    addPoints(1024);
    SchedulerBinding.instance.addTimingsCallback(_timings);
  }

  @override
  void reassemble() {
    super.reassemble();
  }

  @override
  void dispose() {
    SchedulerBinding.instance.removeTimingsCallback(_timings);
    super.dispose();
  }

  void _timings(List<FrameTiming> timings) {
    for (FrameTiming timing in timings) {
      print('${points.length},${timing.vsyncOverhead.inMilliseconds},${timing.buildDuration.inMilliseconds},${timing.rasterDuration.inMilliseconds},${timing.totalSpan.inMilliseconds}');
    }
    setState(() {
      int more = (points.length / 16).ceil();
      addPoints(more);
    });
  }
  
  void addPoints(int more) {
    for (int index = 0; index < more; index += 1) {
      points.add(Offset(random.nextDouble() * 1000, random.nextDouble() * 1000));
    }
    if (points.length >= 1<<21) {
      points.clear();
      addPoints(1024);
    }
  }
      
  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: Painter(
        points: points.toList(),
      ),
    );
  }
}

class Painter extends CustomPainter {
  Painter({
    required this.points,
  });

  final List<Offset> points;
  
  @override
  void paint(Canvas canvas, Size size) {
    canvas.drawPaint(Paint()..color = const Color(0xFF000000));
    canvas.drawPoints(PointMode.points, points, Paint()..strokeWidth = 2.0..color = const Color(0xFFFFFFFF));
  }
  
  @override
  bool shouldRepaint(Painter oldDelegate) => points != oldDelegate.points;
}
```

</details>

Measuring the raster time for drawPoints at various numbers of points gives the following results:

![raster time for drawPoints, by number of points - linux _ skia (1)](https://github.com/user-attachments/assets/2c36f2b9-8498-475b-9a4b-3c0ad8c87a7d)
![raster time for drawPoints, by number of points - android (Pixel 6A) _ impeller (1)](https://github.com/user-attachments/assets/d3a05bb6-09e0-4626-a632-89cc2cc8e1cd)
_Source: [raw data](https://docs.google.com/spreadsheets/d/1D3-dRdn20Bl6Ebs6NYPGarkRL_pA4NMm8HdfiPlXY5U/edit)_

Drawing many points is useful when rendering extensive data sets, such as stars in a galaxy:

![sample screenshot](https://github.com/user-attachments/assets/fad94674-c8c9-4e78-954c-c592100372f5)

...but unfortunately the current performance makes this kind of UI impractical.