Repository navigation
Open
Description
I use flutter-pi on my raspberry pi 4 compute module.
I had performance issues and started profiling on my pi.
I detected that I had tons of raster jank even if nothing is rebuilt.
I replaced my app with this test widget, to have some basic animation on my screen:
import 'package:flutter/material.dart';
import 'dart:math' as math;
class VsyncRasterProbe extends StatefulWidget {
const VsyncRasterProbe({super.key});
@override
State<VsyncRasterProbe> createState() => _VsyncRasterProbeState();
}
class _VsyncRasterProbeState extends State<VsyncRasterProbe>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
bool _running = true;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
)..repeat();
}
void _toggleAnimation() {
setState(() {
_running = !_running;
if (_running) {
_controller.repeat();
} else {
_controller.stop();
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text('Vsync / raster probe'),
actions: <Widget>[
TextButton.icon(
onPressed: _toggleAnimation,
icon: Icon(
_running ? Icons.pause : Icons.play_arrow,
color: Colors.white,
),
label: Text(
_running ? 'Pause' : 'Run',
style: const TextStyle(color: Colors.white),
),
),
],
),
body: RepaintBoundary(
child: CustomPaint(
painter: _ProbePainter(_controller),
child: const SizedBox.expand(),
),
),
);
}
}
class _ProbePainter extends CustomPainter {
_ProbePainter(this.animation) : super(repaint: animation);
final Animation<double> animation;
static final Paint _gridPaint = Paint()
..color = const Color(0xFF263238)
..strokeWidth = 1;
static final Paint _trackPaint = Paint()
..color = const Color(0xFF455A64)
..strokeWidth = 4;
static final Paint _movingPaint = Paint()..color = const Color(0xFF00E5FF);
static final Paint _orbitPaint = Paint()..color = const Color(0xFFFFC400);
@override
void paint(Canvas canvas, Size size) {
canvas.drawColor(const Color(0xFF101418), BlendMode.color);
_drawGrid(canvas, size);
final double phase = animation.value * math.pi * 2;
final double horizontalProgress = 0.5 - 0.5 * math.cos(phase);
const double squareSize = 64;
final double availableWidth = math.max(0, size.width - squareSize - 32);
final double squareX = 16 + availableWidth * horizontalProgress;
final double squareY = size.height * 0.35 - squareSize / 2;
canvas.drawLine(
Offset(16, size.height * 0.35),
Offset(size.width - 16, size.height * 0.35),
_trackPaint,
);
canvas.drawRect(
Rect.fromLTWH(squareX, squareY, squareSize, squareSize),
_movingPaint,
);
final Offset center = Offset(size.width / 2, size.height * 0.7);
final double radius = math.min(size.width, size.height) * 0.14;
final Offset orbitPosition = Offset(
center.dx + math.cos(phase) * radius,
center.dy + math.sin(phase) * radius,
);
canvas.drawCircle(center, radius, _trackPaint);
canvas.drawCircle(orbitPosition, 18, _orbitPaint);
}
void _drawGrid(Canvas canvas, Size size) {
const double spacing = 40;
for (double x = 0; x <= size.width; x += spacing) {
canvas.drawLine(Offset(x, 0), Offset(x, size.height), _gridPaint);
}
for (double y = 0; y <= size.height; y += spacing) {
canvas.drawLine(Offset(0, y), Offset(size.width, y), _gridPaint);
}
}
@override
bool shouldRepaint(covariant _ProbePainter oldDelegate) => false;
}
I still have constant raster jank with this example.
Here is a screenshot of my profiler with the running example:

If I run it somewhere else, everything is fine. It only happens on my pi using flutter-pi.
Reacted by leragequit
Metadata
Metadata
Assignees
Labels
No labels
Activity
leragequit commentedon Sep 17, 2026
@Jack24601
Can you try this commit I am working on? I am facing a smilar issue on a CM4 with a custom display and broken vsync.
leragequit@d9dce5b
Jack24601 commentedon Sep 18, 2026
@leragequit
I tested your change with the same example.
This seems to be the fix.
Thank you very much.
Could you make a PR for that?
leragequit commentedon Sep 18, 2026
Maybe @ardera needs to take a look at that :D?
#529
ardera commentedon Sep 18, 2026
hey, which displays are you using? @leragequit @Jack24601
leragequit commentedon Sep 18, 2026
Hey, I am using custom display from winstar (WF70A9SWAGDNB0#) , RGB parallel interface driven by a TFP401 (https://www.ti.com/product/TFP401) which is fed by the Pi over HDMI.
The display is 800x480@60Hz and we have to use custom HDMI timings coded into the config.txt from the Pi.
Flutter is quite performant, so the issue only arose when we added a very complex scene which lead to performance degradation and we started profiling. Then we realized that even static scenes had the raster jank present even if they didn't rebuild.
This PR gives us around 20% better perfmance in the complex scene with 90% of the raster jank gone.
danielkastberg commentedon Sep 27, 2026
I'm also having similar issues with raster. I'm using https://www.waveshare.com/wiki/11.9inch_HDMI_LCD?srsltid=AU7gw4X8LSrscsmG1saLivGCojwYijeQrDQbsvgKK113T9o2WGDUQucp but I don't think the screen itself is the issue. The issues arise when multiple widgets with animations simultaneously.
Which drivers are you using?
Changing from the FMKS to the KMS driver removed the raster for me.
leragequit commentedon Sep 27, 2026
@danielkastberg On what platform are you? Rpi4 or 5?I wasn't aware that KMS got fixed. I'll check on Monday
danielkastberg commentedon Sep 27, 2026
Using Rpi4, latest Raspberry Pi OS
leragequit commentedon Sep 29, 2026
I'll have to investigate a bit. for me KMS still doesn't work, but we are bulding a full yocto image so there will be some differences when
compared to Raspberry Pi OS.
Thanks!
frog-fest commentedon Sep 30, 2026
We're seeing the same underlying issue on completely different hardware: a custom SOM with a Vivante GPU (Etnaviv/Mesa/DRM), driving a small 480x272 LVDS panel at ~69Hz (measured via the panel's DRM modeline: 11MHz clock / (535*296) ≈ 69.5Hz, so ~14.4ms budget per vblank).
Even on a fully idle screen (no animations, only occasional live data updates, static widget tree otherwise) we consistently measure:
top -Hshows theio.flutter.rasterthread sitting in kernel stateD(uninterruptible sleep) at only ~15-18% CPU while this happens - i.e. it's blocked waiting on the page-flip/KMS completion, not doing real GPU work.We also tried switching the pixel format from ARGB8888 to RGB565 (half the bytes/pixel). That reduced the raster median by about 20% (less bandwidth per flip), but the heavy tail (p95/max) and the fundamental "always 2 vblanks, not 1" pattern stayed the same - consistent with this being a present/flip-latency floor rather than a fill-rate/bandwidth problem.
This matches the mechanism #529 targets (synchronous atomic-commit wait in the raster path) rather than app content complexity, GPU fill rate, or resolution - just another confirmed data point that this reproduces across very different display/GPU combinations, not just RPi + specific panels.
leragequit commentedon Sep 30, 2026
Would you be able to test the PR and see if the raster jank disappears?
Renaud-Barrau commentedon Oct 5, 2026
Hello,
Not an expert on this topic, but I observe the same behaviour on an IMX95 SoC, the raster seems capped at 33ms, even on a simple no-animation screen.
The pixel format is not on the scope.
Following a before/after the patch with the flutter widget PerformanceOverlay.allEnabled() :
On other heavy process screens (with lottie animations for example), the graph "jitter" between 2 and 3 "bars" :
Looks like its always cap to 16,66ms, 33ms or 50ms (which is vblank time if I understand well ?)
Applying this fix #528 (comment) helps
EDIT : After a bit of digging, I understood this is the normal behaviour of the double buffering currently in place in flutter-pi, and the fix is implementing a triple buffering with "mailbox" type 😅.
But I would say it would be nice to give the possibility to use this feature, as a command line argument (with a default to the current behaviour), as @ardera already mention here