@Ronel_qtmaster @GrecKo
Thank you both for the suggestions — I'll work through them and report back.
Unfortunately I can't share much code as I initially said due to project confidentiality, but here's as much context as I can give (feel free to ask for more if there's any confusion):
Architecture overview:
The backend runs a ROS2 classification node using Ultralytics YOLOv8 + PyTorch. This is understandably CPU-heavy.
My GUI class subscribes to a ROS topic that fires a signal each time the classification of one frame completes.
A camera bridge class is connected to the GUI class and forwards frames to QML.
The QML GUI itself runs as a dedicated ROS2 node, launched inside a QThread.
Comparison with the old Widget-based GUI:
The previous implementation used QPixmap inside a standard Qt Widgets GUI. It worked — the FPS dropped from ~30 to ~10 due to the ML overhead, but it was fully usable. My QML implementation is significantly worse: ~1 FPS with a ~20-second rendering delay, making camera control completely unusable.
Since the backend is identical in both cases, the bottleneck must be in how I'm bridging frames from the ROS2 subscription into QML.
Potential issue I'm investigating:
I suspect the QML rendering pipeline is either blocking on frame delivery, accumulating a backlog of unprocessed frames, or doing unnecessary work on the UI thread — but I haven't pinpointed it yet.
Could there be a potential fault in how I configure the main.cpp file? I've provided the core logic from my implementation below.
int main( argc, *argv[])
{
qputenv("QSG_RENDER_LOOP", "thread"); //tried to use basic, virtually no difference
rclcpp::init(argc, argv);
qRegisterMetaType<QImage>("QImage")
.....
QApplication app(argc, argv);
.....
QNode qnode;
if (!qnode.init()) return -1;
....//Initializing camera
auto cameraProvider = new CameraBridge();
engine.rootContext()->setContextProperty("cameraBridge", cameraProvider)
......
engine.load(url);
if (engine.rootObjects().isEmpty()) return -1;
int result = app.exec();
rclcpp::shutdown();
qnode.wait();
return result;
}
@Ronel_qtmaster — I tried applying your suggestion to my paint function but didn't see an improvement. It's possible I'm not applying it at the right point in the pipeline, but I belive my implementation is correct.
@GrecKo — I'll implement your approach and report back today or tomorrow.
Fallback plan:
If I can't resolve this in QML, I may try embedding the old QPixmap-based camera widget into my QML app using QQuickWidget or similar. I know mixing Widgets and QML is generally discouraged in that direction, but it may be the pragmatic solution here. Any experience or tips with that approach would be welcomed.