Force QQuickWindow update
-
Hello!
I am looking for a method to force an update of the visual content in qml.
With force, i mean, that it should not just schedule an update,
it should wait until the new content has actually been drawn.I have no experience with widgets, but according to the documentation, QWidget::repaint() works this way.
But i haven't found an equivalent for qml based programs.I want to trigger these updates during start-up.
My program is of course heavily multi-threaded, but a few seconds are unavoidable and i want to display the progress.
It would also be very helpful for another issue i have with ShaderEffectSource.scheduleUpdate().I created a small test project with my current solutions:
Feel free to run it.main.cpp
#include "main_handler.h" int main(int argc, char *argv[]) { MainHandler main_handler; main_handler.startProgram(argc, argv); }main_handler.h
#ifndef MAIN_HANDLER_H #define MAIN_HANDLER_H #include <QObject> #include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQuickWindow> #include <QThread> #include <QTimer> class MainHandler : public QObject { Q_OBJECT Q_PROPERTY(bool show_dummy MEMBER show_dummy NOTIFY showDummyChanged FINAL) Q_PROPERTY(bool show_gui MEMBER show_gui NOTIFY showGuiChanged FINAL) Q_PROPERTY(QString progress MEMBER progress NOTIFY progressChanged FINAL) public: explicit MainHandler(QObject *parent = nullptr); ~MainHandler(); int startProgram(int argc, char *argv[]); public slots: void startUp(); void forceUpdate(); void forceUpdateSlow(); void forceUpdateSimple(); void waitForWindow(); signals: void doStartUp(); void showDummyChanged(); void showGuiChanged(); void progressChanged(); private: QQmlApplicationEngine *engine; QGuiApplication *app; QQuickWindow *window; bool show_dummy = false; bool show_gui = false; QString progress = ""; void setShowDummy(bool value); void setShowGui(bool value); void setProgress(QString value); }; #endif // MAIN_HANDLER_Hmain_handler.cpp
#include "main_handler.h" MainHandler::MainHandler(QObject *parent) : QObject{parent} { } MainHandler::~MainHandler() { delete this->app; delete this->engine; } int MainHandler::startProgram(int argc, char *argv[]) { this->app = new QGuiApplication(argc, argv); this->engine = new QQmlApplicationEngine(); QObject::connect(this->engine, &QQmlApplicationEngine::objectCreationFailed, this->app,[](){QCoreApplication::exit(-1);}, Qt::QueuedConnection); QObject::connect(this, &MainHandler::doStartUp, this, &MainHandler::startUp, Qt::QueuedConnection); emit this->doStartUp(); return app->exec(); } void MainHandler::startUp() { qmlRegisterSingletonInstance<MainHandler>("MainHandler", 1, 0, "MainHandler", this); this->engine->loadFromModule("TestProject_ForceUpdate", "Main"); this->window = qobject_cast<QQuickWindow*>(this->app->allWindows().at(0)); this->waitForWindow(); this->setShowDummy(true); this->forceUpdate(); this->thread()->sleep(1); this->setProgress("loading core..."); this->forceUpdate(); this->thread()->sleep(1); this->setProgress("loading components..."); this->forceUpdate(); this->thread()->sleep(1); this->setProgress("loading gui..."); this->forceUpdate(); this->thread()->sleep(1); this->setProgress(""); this->setShowDummy(false); this->setShowGui(true); } void MainHandler::forceUpdate() { qDebug() << "forceUpdate"; bool finished = false; Qt::ConnectionType type = (Qt::ConnectionType)(Qt::QueuedConnection|Qt::SingleShotConnection); connect(this->window, &QQuickWindow::frameSwapped, this, [&](){finished=true;}, type); this->window->requestUpdate(); QEventLoop event_loop; while(!finished && this->window->isExposed()) { qDebug() << "forceUpdate: wait"; this->thread()->usleep(10); event_loop.processEvents(QEventLoop::ExcludeUserInputEvents); } } void MainHandler::forceUpdateSimple() { qDebug() << "forceUpdateSimple"; QEventLoop event_loop; event_loop.processEvents(QEventLoop::ExcludeUserInputEvents); } void MainHandler::forceUpdateSlow() { qDebug() << "forceUpdateSlow"; this->window->requestUpdate(); this->thread()->msleep(6); QEventLoop event_loop; event_loop.processEvents(QEventLoop::ExcludeUserInputEvents); } void MainHandler::waitForWindow() { qDebug() << "waitForWindow"; QTimer timer; timer.setSingleShot(true); timer.setInterval(1000); timer.start(); while(!this->window->isExposed()) { qDebug() << "waitForWindow: wait"; this->thread()->msleep(1); QEventLoop event_loop; event_loop.processEvents(QEventLoop::ExcludeUserInputEvents); if(!timer.isActive()) { qDebug() << "waitForWindow: abort"; return; } } qDebug() << "waitForWindow: done after:" << (1000 - timer.remainingTime()) << "ms"; } void MainHandler::setShowDummy(bool value) { if(value==this->show_dummy) return; this->show_dummy = value; emit this->showDummyChanged(); } void MainHandler::setShowGui(bool value) { if(value==this->show_gui) return; this->show_gui = value; emit this->showGuiChanged(); } void MainHandler::setProgress(QString value) { if(value==this->progress) return; this->progress = value; emit this->progressChanged(); }Main.qml
import QtQuick import MainHandler 1.0 Window { width: 640 height: 480 visible: true title: qsTr("TestProject_ForceUpdate") Rectangle { id: bg anchors.fill: parent color: "#ff00ff" } Rectangle { id: dummy anchors.fill: parent color: "#ff0000" visible: MainHandler.show_dummy } Rectangle { id: gui anchors.fill: parent color: "#00ff00" visible: MainHandler.show_gui } Text { id: progress anchors.fill: parent text: MainHandler.progress fontSizeMode: Text.Fit font.pixelSize: parent.height/10 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } }But, the scene graph topic is quite complex and just because the code works, does not mean, that it is correct...
First, i thought, that forceUpdateSimple() would be enough, because most of the time, it worked.
And because the documentation of QEventLoop::processEvents says:
"This function is especially useful if you have a long running operation and
want to show its progress without allowing user input; i.e. by using the ExcludeUserInputEvents flag."Then i came up with forceUpdateSlow(), because the documentation of QWindow::requestUpdate() says:
"The event is delivered after a delay of at most 5 ms."
So, 6ms should be enough, but i am not sure, although it works...After reading the scene graph documentation again (and again), i got forceUpdate().
IsExposed() prevents a deadlock for hidden windows, but i am not sure, if this is reliable in general...
And it looks pretty exotic. But it works.So, my questions are:
- Why is there no method like QQuickWindow::forceUpdate()?
- Is it a bug, that forceUpdateSimple() fails?
- Are my "solutions" reliable? Why not??
- This start-up pattern is pretty standard, do i miss something very obvious?
-
Could you explain what it is you are trying to optimize?
I mean, it takes a moment for the OS to load your binary, and any libraries you link against before calling your main(). After that, it takes yet a few more moments for Qt to create the application and engine objects, instantiate a native window object, initialize the rendering pipeline, parse the QML file and paint the first results on the screen.
If what you need is a "splash window as fast as possible" then you'll probably need to create a platform-specific window object in the start with some static content and put that on the screen while the "Qt infrastructure" loads. Once finished, you show the QML one in the same place and remove the native window. That QML window then updates as separate threads perform the tasks of loading the core, components, GUI and whatever modules it is you have.
There might be ways to go even faster by using a "first-stage binary" with very minimal dependencies and even more minimal code just to show a native splash window and spawn a detached process to load the QML binary, and then perform a "handover" between the two. I think that's a bit overkill, though.
-
Could you explain what it is you are trying to optimize?
I mean, it takes a moment for the OS to load your binary, and any libraries you link against before calling your main(). After that, it takes yet a few more moments for Qt to create the application and engine objects, instantiate a native window object, initialize the rendering pipeline, parse the QML file and paint the first results on the screen.
If what you need is a "splash window as fast as possible" then you'll probably need to create a platform-specific window object in the start with some static content and put that on the screen while the "Qt infrastructure" loads. Once finished, you show the QML one in the same place and remove the native window. That QML window then updates as separate threads perform the tasks of loading the core, components, GUI and whatever modules it is you have.
There might be ways to go even faster by using a "first-stage binary" with very minimal dependencies and even more minimal code just to show a native splash window and spawn a detached process to load the QML binary, and then perform a "handover" between the two. I think that's a bit overkill, though.
@AnttiK
Thank you for your reply! :)Yes, i do something like that in my real program.
First i show a dummy version of my gui (very fast and satisfying),
then i create and register a bunch of stuff (about 1s),
then i load the qml stuff (about 1s) and finally hide the dummy version.
It appears like the app is instantly open and ready.
The progress is more for eventually long running operations.I don't want to optimize.
My problem is, that the forceUpdateSimple() method fails.
The dummy version and the progress text are not displayed.And my "solutions" are more the result of trial-and-error than the result of knowledge...
-
I am not entirely familiar with all the intricate details of Qt but there's one thing which stands out from your demo: you are creating a new event loop and sleeping the "main thread". This feels wrong, as "a sleeping painter does not paint".
Maybe you should be calling QGuiApplication's processEvents instead, to run the "built-in" event loop? To simulate a wait condition while the event loop operates, start a timer, attach its "timeout" event and exit the loop that's calling processEvents when the timeout occurs.
-
I am not entirely familiar with all the intricate details of Qt but there's one thing which stands out from your demo: you are creating a new event loop and sleeping the "main thread". This feels wrong, as "a sleeping painter does not paint".
Maybe you should be calling QGuiApplication's processEvents instead, to run the "built-in" event loop? To simulate a wait condition while the event loop operates, start a timer, attach its "timeout" event and exit the loop that's calling processEvents when the timeout occurs.
@AnttiK
Do you mean:qGuiApp->processEvents(QEventLoop::ExcludeUserInputEvents);The documentation says, that the use of this function is discouraged, that's actually what made me do it the other way.
I also tried:this->thread()->eventDispatcher()->processEvents(QEventLoop::ExcludeUserInputEvents);They all seem to work similar.
The event get processed, i don't think, this is the problem.
I guess, the problem is, that the event is not in the event queue when i want to process them.
Initially, i assumed, that the event for rerendering get pushed to the queue as soon as the qml scene changes.
But it's not even after QWindow::requestUpdate() present, i have to wait an unspecific amount of time, which feels very wrong to me...Thank you for your timeout suggestion.
Yes, i will use something like that to definitely avoid any deadlocks.
If i'll actually use my approach in the end...After all, i wonder, how others deal with this very common situation.
Because you NEED to do something similar in such a start-up routine.
The docs of QEventLoop::processEvents even give the hint to use it in this situation.Maybe it's just a bug, did you test forceUpdateSimple()?
Thank you very much! -
@AnttiK
Do you mean:qGuiApp->processEvents(QEventLoop::ExcludeUserInputEvents);The documentation says, that the use of this function is discouraged, that's actually what made me do it the other way.
I also tried:this->thread()->eventDispatcher()->processEvents(QEventLoop::ExcludeUserInputEvents);They all seem to work similar.
The event get processed, i don't think, this is the problem.
I guess, the problem is, that the event is not in the event queue when i want to process them.
Initially, i assumed, that the event for rerendering get pushed to the queue as soon as the qml scene changes.
But it's not even after QWindow::requestUpdate() present, i have to wait an unspecific amount of time, which feels very wrong to me...Thank you for your timeout suggestion.
Yes, i will use something like that to definitely avoid any deadlocks.
If i'll actually use my approach in the end...After all, i wonder, how others deal with this very common situation.
Because you NEED to do something similar in such a start-up routine.
The docs of QEventLoop::processEvents even give the hint to use it in this situation.Maybe it's just a bug, did you test forceUpdateSimple()?
Thank you very much!@felsi said in Force QQuickWindow update:
After all, i wonder, how others deal with this very common situation.
Because you NEED to do something similar in such a start-up routine.
The docs of QEventLoop::processEvents even give the hint to use it in this situation.Don't call processEvents(). It's a great way to generate surprises, such as having values cached within the function invalidated by event processing, or unusual behavior because some events are not processed. For long running computation, either run it in another thread, or break the processing up into smaller chunks that are scheduled with a timer.
Definitely don't stop processing user input events. If the UI should ignore input, set the
enabledproperty to false. This gives the user a visual hint that the interface won't respond as expected, and prevents the flood of input events that will occur when the user repeatedly attempts to interact with an interface. -
@AnttiK
Do you mean:qGuiApp->processEvents(QEventLoop::ExcludeUserInputEvents);The documentation says, that the use of this function is discouraged, that's actually what made me do it the other way.
I also tried:this->thread()->eventDispatcher()->processEvents(QEventLoop::ExcludeUserInputEvents);They all seem to work similar.
The event get processed, i don't think, this is the problem.
I guess, the problem is, that the event is not in the event queue when i want to process them.
Initially, i assumed, that the event for rerendering get pushed to the queue as soon as the qml scene changes.
But it's not even after QWindow::requestUpdate() present, i have to wait an unspecific amount of time, which feels very wrong to me...Thank you for your timeout suggestion.
Yes, i will use something like that to definitely avoid any deadlocks.
If i'll actually use my approach in the end...After all, i wonder, how others deal with this very common situation.
Because you NEED to do something similar in such a start-up routine.
The docs of QEventLoop::processEvents even give the hint to use it in this situation.Maybe it's just a bug, did you test forceUpdateSimple()?
Thank you very much!@felsi The documentation indeed says that calling processEvents is discouraged. However, the sentence right after explains the rationale behind this, and to me, it's more related to doing "heavy-lifting" on the GUI thread which may cause perceived slow-downs than it is to the function itself being somehow "wrong".
I did not run your demo code as-is since I'm more interested in trying to understand what it is you want to achieve, and what the actual problem is. The demo code is your understanding on "how things should be if we want to achieve X" but what X itself is and what precise problems you encounter are still eluding me.
You mentioned that you want to show progress, so you probably already spawn a thread or a QRunnable to do the work, and then send signals between the threads to move the state machine forward. But what is the problem? Is it that these progress steps fly by so fast that the GUI just doesn't keep up? You immediately see "loading GUI" and all the other stages get omitted?
When/if the individual steps of a "progress" are too fast to be reliably detected then using a pre-recorded video to "simulate a progress" is a viable option. I know: it sounds a bit silly but it is the perception of the user that we want to fool here. Compared to a computer, the human eye is rather slow.
I've uploaded a simple example of the QRunnable approach to my repo: https://github.com/anttikes/qrunnable-progress
I tried attaching it here as code blocks but for some reason the spam filter is adamant that my code is not appropriate.The responsibility of starting the background operation rests on the text showing the progress for the user, thus it "inverses" the responsibility somewhat. Using a QRunnable frees the GUI thread to operate the window. Note that since you haven't specified what exactly it is that your runnable needs to do then I haven't passed e.g. a pointer to the QML engine to it. This would be required to do things like loading more QML modules into the engine's context, or otherwise manipulate it.
Is this even close to what you're looking for?
-
@felsi The documentation indeed says that calling processEvents is discouraged. However, the sentence right after explains the rationale behind this, and to me, it's more related to doing "heavy-lifting" on the GUI thread which may cause perceived slow-downs than it is to the function itself being somehow "wrong".
I did not run your demo code as-is since I'm more interested in trying to understand what it is you want to achieve, and what the actual problem is. The demo code is your understanding on "how things should be if we want to achieve X" but what X itself is and what precise problems you encounter are still eluding me.
You mentioned that you want to show progress, so you probably already spawn a thread or a QRunnable to do the work, and then send signals between the threads to move the state machine forward. But what is the problem? Is it that these progress steps fly by so fast that the GUI just doesn't keep up? You immediately see "loading GUI" and all the other stages get omitted?
When/if the individual steps of a "progress" are too fast to be reliably detected then using a pre-recorded video to "simulate a progress" is a viable option. I know: it sounds a bit silly but it is the perception of the user that we want to fool here. Compared to a computer, the human eye is rather slow.
I've uploaded a simple example of the QRunnable approach to my repo: https://github.com/anttikes/qrunnable-progress
I tried attaching it here as code blocks but for some reason the spam filter is adamant that my code is not appropriate.The responsibility of starting the background operation rests on the text showing the progress for the user, thus it "inverses" the responsibility somewhat. Using a QRunnable frees the GUI thread to operate the window. Note that since you haven't specified what exactly it is that your runnable needs to do then I haven't passed e.g. a pointer to the QML engine to it. This would be required to do things like loading more QML modules into the engine's context, or otherwise manipulate it.
Is this even close to what you're looking for?
I googled a bit and i was expecting this answer, that's why i explained in the first few sentences, that i use threads wherever possible. I use about 10 at start up, reading databases, parsing large files (200MB) and so on.
It would probably take minutes without...
But some operations just have to be completed before i start these threads and they just have to take place in the gui thread.This is what i would have said, but...
Oh, damn, i just realized that it IS avoidable...
Some time ago, i tried to create my backend stuff in a separate thread, but these things have to live in the gui thread.
Which doesn't necessarily mean, that they have to be created there!
I'll create them in a thread and just push them to the gui thread with moveToThread!
Oh, man, sorry...So, in the end, i might really be able to avoid any tiny blocking of the gui!
Although, i hope, the move operation will not slow down the start up...@jeremy_k
Scheduling with a timer would work pretty similar to forceUpdateSlow(), or not?
It "slices" the process into chunks and gives the gui 5ms to update in between.Thanks for the hint with the user input!
But in my case (2-3 seconds) it wouldn't be necessary, it might even be a feature. ;)@AnttiK
With progress i meant just the text you see, nothing else, sorry for the unclearness.
Just 2 or 3 single messages.Thank you very much, your code and "pointer to the QML engine" finally lead me to the solution.
I will do so! Thank you very much!Btw: Great radio project! Makes me want to try it! :)
My program is a music player...But, i am still pretty surprised, that there is no way to do such a forceUpdate().
I tried, for example, to use ShaderEffectSource.scheduleUpdate() for an item, that i need to remove from the qml scene for about 200ms. But this will render the next frame. So, how do i know, when the item can be removed? Same problem.
I ended up using the live flag, which i can set to false and remove the item immediately.
But this will render the item unnecessarily to a texture all the time... -
I googled a bit and i was expecting this answer, that's why i explained in the first few sentences, that i use threads wherever possible. I use about 10 at start up, reading databases, parsing large files (200MB) and so on.
It would probably take minutes without...
But some operations just have to be completed before i start these threads and they just have to take place in the gui thread.This is what i would have said, but...
Oh, damn, i just realized that it IS avoidable...
Some time ago, i tried to create my backend stuff in a separate thread, but these things have to live in the gui thread.
Which doesn't necessarily mean, that they have to be created there!
I'll create them in a thread and just push them to the gui thread with moveToThread!
Oh, man, sorry...So, in the end, i might really be able to avoid any tiny blocking of the gui!
Although, i hope, the move operation will not slow down the start up...@jeremy_k
Scheduling with a timer would work pretty similar to forceUpdateSlow(), or not?
It "slices" the process into chunks and gives the gui 5ms to update in between.Thanks for the hint with the user input!
But in my case (2-3 seconds) it wouldn't be necessary, it might even be a feature. ;)@AnttiK
With progress i meant just the text you see, nothing else, sorry for the unclearness.
Just 2 or 3 single messages.Thank you very much, your code and "pointer to the QML engine" finally lead me to the solution.
I will do so! Thank you very much!Btw: Great radio project! Makes me want to try it! :)
My program is a music player...But, i am still pretty surprised, that there is no way to do such a forceUpdate().
I tried, for example, to use ShaderEffectSource.scheduleUpdate() for an item, that i need to remove from the qml scene for about 200ms. But this will render the next frame. So, how do i know, when the item can be removed? Same problem.
I ended up using the live flag, which i can set to false and remove the item immediately.
But this will render the item unnecessarily to a texture all the time...@felsi said in Force QQuickWindow update:
@jeremy_k
Scheduling with a timer would work pretty similar to forceUpdateSlow(), or not?
It "slices" the process into chunks and gives the gui 5ms to update in between.Reproducing the code for easier reference:
void MainHandler::forceUpdateSlow() { qDebug() << "forceUpdateSlow"; this->window->requestUpdate(); this->thread()->msleep(6); QEventLoop event_loop; event_loop.processEvents(QEventLoop::ExcludeUserInputEvents); }- Hopefully the invocation of msleep() is an unfortunate choice for a place holder. If not, don't do that.
- If there's a processEvents(), it's not what I intended.
- Does the content of the window not automatically generate its own requests for updates?
Using a zero-timer with the main event loop instead of processEvents:
#include <QCoreApplication> #include <QTimer> #include <QList> #include <QDebug> int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QTimer workTimer; workTimer.setInterval(0); QList<int> workQueue { 1, 2, 3 }; QObject::connect(&workTimer, &QTimer::timeout, [&](){ if (workQueue.empty()) workTimer.stop(); else qDebug() << "processing job" << workQueue.takeFirst(); }); workTimer.start(); return a.exec(); }