Flutter CustomPainter with drawRawPoints still low FPS (Web + Android emulator) for 128x128 indexed framebuffer


I'm rendering a retro-style 128x128 framebuffer in Flutter using CustomPainter + canvas.drawRawPoints.

I already removed per-frame allocations by preallocating typed buffers per palette index, but FPS is still very low on Web (~12 FPS) and also low on Android emulator after testing. iOS is much better.

I profiled the engine loop separately and it seems fast enough (simulation step is around 1-2ms per frame), so this looks like a presentation/render bottleneck.

Simplified code (anonymized):

class FrameScreen extends StatefulWidget { const FrameScreen({super.key, required this.frames}); final Stream<FrameData> frames; @override State<FrameScreen> createState() => _FrameScreenState(); } class _FrameScreenState extends State<FrameScreen> { final PixelBuckets _pixelBuckets = PixelBuckets(); FrameData? _lastFrame; StreamSubscription<FrameData>? _sub; @override void initState() { super.initState(); _sub = widget.frames.listen((frame) { _lastFrame = frame; _pixelBuckets.update(frame.indices); // 16 color buckets, reused arrays if (mounted) setState(() {}); }); } @override Widget build(BuildContext context) { if (_lastFrame == null) return const SizedBox.shrink(); return CustomPaint( painter: PixelPainter( buckets: _pixelBuckets, palette: _lastFrame!.palette, ), child: const SizedBox.expand(), ); } } class PixelPainter extends CustomPainter { PixelPainter({required this.buckets, required this.palette}); final PixelBuckets buckets; final List<int> palette; @override void paint(Canvas canvas, Size size) { canvas.scale(size.width / 128, size.height / 128); final paint = Paint() ..strokeWidth = 1.0 ..strokeCap = StrokeCap.square; for (var i = 0; i < 16; i++) { final count = buckets.counts[i]; if (count == 0) continue; paint.color = Color(palette[i]); final points = Float32List.sublistView(buckets.points[i], 0, count * 2); canvas.drawRawPoints(ui.PointMode.points, points, paint); } } @override bool shouldRepaint(covariant PixelPainter oldDelegate) => true; }

I also draw optional overlays (grid/vignette/shader mode), but low FPS occurs even in mostly "pixel-perfect" mode.

Questions:

  1. Is drawRawPoints expected to be this slow on Web/Android emulator even for 128x128?
  2. Is there a better Flutter-native approach for this workload (e.g. drawAtlas, RawImage with updated texture, SceneBuilder, etc.)?
  3. Any known pitfalls with setState on every frame for this pattern?

Constraints:

  • Pure Dart/Flutter rendering path
  • 60 FPS target
  • Fixed 16-color indexed framebuffer
1
Feb 17 at 6:53 AM
User Avatarquarks
#android#flutter#dart#google-chrome

No answer found for this question yet.