Proper way to pass QImage from thread through signal?
-
Hi,
The calls to paintEvent are completely unrelated to your displayFrame slot being called. There are a lot of reasons for which paintEvent is called. If you want basic debugging, you should put your qDebug statement in the slot there.
By the way, why not use the new syntax when connecting m_renderer ?
-
I'm sorry to spoil the party but I'm afraid it's a no-go.
From https://doc.qt.io/qt-5/thread-basics.html#gui-thread-and-worker-thread
All widgets and several related classes, for example
QPixmap
, don't work in secondary threads.QPainter::drawImage
ultimately callsQPaintEngine::drawImage
that creates aQPixmap
from that image and draws it.P.S.
- You call
VWidget::requestRender
when the timer timeouts but your code showsGLWidget::requestRender
instead (class name is different) VWidget::stopRendering()
is currently a race condition.Renderer::m_exiting
should be declared as typestd::atomic_bool
to make it safe
- You call
-
@VRonin said in Proper way to pass QImage from thread through signal?:
QPainter::drawImage ultimately calls QPaintEngine::drawImage that crates a QPixmap from that image and draws it.
Nice one ! I forgot about that low level detail.
-
So I can't do this?! I've tried EVERYTHING and always seem to run into an issue. :( I saw some examples where something similar was done, supposedly successfully. The GLWidget is a leftover but not that way in my code.
I'm out of options.
So what I have ultimately is a video relay app built on an SDK. The SDK supplies up to 3 raw images at a time, I need to display them on my video surface. Currently, the app uses 2 classes I wrote. On Mac and Linux, a QGLWidget based class, renders completely, using QPainter and QImage on a separate thread. Performs great, but has 3 issues. Known bug where styles are lost after some time on Windows, Mac is dropping OpenGL, and it's deprecated, running into issues with Catalina, works for now. For Windows, I use D2D but it's not threaded so there are some performance issues.
So I'm looking for a replacement, preferably threaded, and preferably one widget for all OS now.
I have tried and failed with (using Qt 5.12.X and 5.13.X):- QOpenGLWidget, ran into this bug preventing me from using it since we must run full screen: https://bugreports.qt.io/browse/QTBUG-49657
- RHI, no idea where to start. Apparently it's beta, there's one example, and it uses QML while our app is a QWidget based desktop app.
- Tried QWidget + QPainter with NO threading. I stopped half way because I got a test working and it seems it impacted UI performance enough it was a no go.
- I feel like I've gone down many other rabbit holes too. QGraphicsView/Scene, etc. Each time I get half way and something just won't work.
Any bright ideas for me then? I really thought I was close this time!
-
Actually, this should still be possible, this is where I started:
https://doc.qt.io/qt-5/threads-modules.html"QPainter can be used in a thread to paint onto QImage, QPrinter, and QPicture paint devices. Painting onto QPixmaps and QWidgets is not supported. On macOS the automatic progress dialog will not be displayed if you are printing from outside the GUI thread."
I put a qDebug in displayFrame and it is firing off around 50 times a second. So, I think I still am passing the QImage incorrectly?
-
Your issue is specific to drawImage with the default paint engine. To test, create a simple QImage, fill it with one colour and draw a rect if another's colour on it.
By the way, why are you loading your image and then calling drawImage ? Why not directly load the image itself in the target QImage ?
-
Have you debugged the signal-slot connection? Do you arrive in the displayFrame slot? What size does the QImage you sent when emitting the signal as opposed the image you receive in the displayFrame slot?
-
@SGaist said in Proper way to pass QImage from thread through signal?:
Your issue is specific to drawImage with the default paint engine. To test, create a simple QImage, fill it with one colour and draw a rect if another's colour on it.
By the way, why are you loading your image and then calling drawImage ? Why not directly load the image itself in the target QImage ?
Because in my final code I won't be loading an image, I will be drawing the composite image. That was just a test with a known image so I could see if anything is getting through. I already tried the rectangles too and nothing comes through.
@Asperamanca I did by putting a qDebug in the slot and I see it getting called. I will add some more in and check image size, etc. Thanks.
-
@wesblake said in Proper way to pass QImage from thread through signal?:
I already tried the rectangles too and nothing comes through.
Change
connect(m_renderer, SIGNAL(frameReady(QImage)), this, SLOT(displayFrame(QImage)));
toconnect(m_renderer, &Renderer::frameReady, this, &VWidget::displayFrame);
so that we are sure the connection is workingchange
Renderer::render
tovoid Renderer::render() { if(m_exiting){ emit finished(); return; } const auto colors[] = {Qt::red,Qt::blue,Qt::black,Qt::green}; QImage videoFrame; videoFrame.fill(colors[std::uniform_int_distribution<int>(0,3)(std::default_random_engine)]); frameReady(videoFrame); }
and see if anything changed
nothing comes through.
Define "nothing comes through"
-
Thanks all for your help! How do I mark solved?
I feel dumb, it was simple. After adding in qDebug on the size of the image, both in Renderer and VWidget, both sides were 0.
So, it was simple, I was not initializing the QImage to paint into correctly in Renderer::render! I needed:QImage videoFrame(m_glwidget->width(), m_glwidget->height(), QImage::Format_ARGB32_Premultiplied);
And now everything is working beautifully!