I use a BusyIndicator as a placeholder for an image until it has been completely downloaded.
BusyIndicator {
id: bI
running: img.status !== img.Ready
anchors.centerIn: parent
width: 50
height: 50
}
And here's the Image :
Image {
id: img
visible: true
source: "https://i.ytimg.com/vi/NeHi1Tg86fE/maxresdefault.jpg"
onStateChanged: if(img.status === img.Ready){
bI.running = false
}else{
bI.running = true
}
But after running the above code I noticed high CPU usage (one full cpu thread) and my entire GPU was also involved before downloading the image completely.
Also another problem is that when the image finishes loading , the busyIndicator is still running.
Any solutions ?
Related
I'm working on a QML based app. where I dynamically load the content. However when running the application it takes quite a long time (5-10 secs), so I need to show any loading screen or indicator while the whole content is being loaded. Can anyone suggest me how to do it ?
For example, after I login in my application it took some time to load the next page so within that oeriod of time i want to show the loading screen.
App {
id: app
height: 400
width: 200
Rectangle {
id: rectangle
Button {
id: button
text: qsTr("GO TO NEXT PAGE")
onClicked:stackView.push("page2.qml")
}
Image {
id: image
fillMode: Image.PreserveAspectFit
source: "default-app.png"
}
}
}
Suppose this is my code then where can i use loader ? I never used it before
You can use the status from a Loader component (I'm guessing you are using that since you are loading "dynamically"). Then use that in a BusyIndicator.
Loader {
id: loader
asynchronous: true
...
BusyIndicator {
anchors.centerIn: parent
running: loader.status == Loader.Loading
}
}
Heck, the Qt docs for BusyIndicator should have get you going!
I've developed an application, with the UI done using QML. What I was asked to do now is to make it so that when a second monitor is used, the second monitor shows everything that the program is doing. At first I thought of just telling the client to configure Windows to clone its screens. However when the applications uses some of its functionalities I need for the cloned screen to display certain indicators in the cloned screens but not on the original screen.
So my question is, How can I accomplish this. How can mirror what is happening in one screen, while maintaing enough control to draw in one and not in the other.
My only idea is to use a timer to take as screen shot at regular intervals and show that image in the second screen.
Is this doable?
Taking periodic screenshots, although perfectly doable, is undesired because of the impact to performance. Instead you should make use the main window's onFrameSwapped() signal, to grab images only when a new frame is generated.
Ideally, you'd want to make use of Layer or ShaderEffectSource, as suggested by #dtech, to read and re-render the frame straight from the GPU. Unfortunately, due to limitations in Qt Quick's Scene Graph, it is not possible to accomplish this across separate windows without destabilizing the source window's scene graph.
Update: Nevertheless, I've found a way to emulate the desired features of ShaderEffectSource using ShaderEffect instead. Your other options are to either copy frames through the CPU using Item's grabToImage function, or to re-implement QQuickView (your QML window) in C++ to grab images from the QML Engine when a new frame is generated.
Working Solution 1: Use ShaderEffect (Update: New Solution)
The new approach I've found consists of using ShaderEffect on a child Window and forcing it to update by calling that Window's update() method, which is inherited from QWindow. By using ShaderEffect, you guarantee the source image will be accessed only through the GPU, increasing performance by orders of magnitude over solution #2.
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
id: mainWindow
title: qsTr("Main Window")
visible: true
width: 400
height: 200
color: "#0F0"
Rectangle {
id: viewport
color: "#00F"
width: parent.width/2
height: parent.height/2
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
// Draw QML Item into an off screen layer
layer.enabled: true
// Performance tweaks
layer.smooth: false
layer.mipmap: false
}
onFrameSwapped: {
// Update mirror window manually with each new frame
projectionWindow.update()
}
Window {
id: projectionWindow
property int screenID: 1
transientParent: mainWindow
visible: true
x: Qt.application.screens[screenID].virtualX
y: Qt.application.screens[screenID].virtualY
width: Qt.application.screens[screenID].desktopAvailableWidth
height: Qt.application.screens[screenID].desktopAvailableHeight
flags: Qt.FramelessWindowHint
color: "#000"
visibility: Window.Maximized
ShaderEffect {
id: img
// Set source to copy visuals from
property variant source: viewport
// Setting shader to the same resolution as the source may improve performance
width: viewport.width
height: viewport.height
// Performance tweak
blending: false
}
}
}
Triggering update() manually has the side effect of preventing the child from updating on its own. Calling the update method on the source window from the child Window once whenever needed works around this limitation.
It is worth noting that this solution only works with Qt's basic renderer, which is single threaded. Qt prevents the texture from being accessed from a diferent rendering threads, giving the following error message and returning a null pointer:
QQuickShaderEffectSource::textureProvider: can only be queried on the rendering thread of an exposed window
The performance gained from this method is far greater than the performance loss of using the single threaded renderer. You can force use of the single threaded renderer by setting an environment variable at the start of your application. This can be done from inside the app by adding the following code at the very start of your main function:
#if defined(Q_OS_WINDOWS)
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
putenv("QSG_RENDER_LOOP=windows");
#else
putenv("QSG_RENDER_LOOP=basic");
#endif
#elif defined(Q_OS_MACOS) || defined(Q_OS_LINUX)
setenv("QSG_RENDER_LOOP", "basic", 1);
#endif
Qt's documentation states that using the basic renderer reduces portability, but I can attest this approach works fine on Windows, macOS, and Linux and that it does not work on Haiku OS.
I've switched to this solution on the most recent versions of a project I develop. You can study the full implementation at:
https://github.com/Cuperino/QPrompt/blob/072f0a7808f3a361cb93bca8961e884ac9d8bb56/src/kirigami_ui/PrompterPage.qml#L760
https://github.com/Cuperino/QPrompt/blob/072f0a7808f3a361cb93bca8961e884ac9d8bb56/src/kirigami_ui/main.qml#L847
https://github.com/Cuperino/QPrompt/blob/072f0a7808f3a361cb93bca8961e884ac9d8bb56/src/prompter/ProjectionsManager.qml#L261
Working Solution 2: Use Item's grabToImage (original answer)
Another easy approach is to grab frames from QML, using an Item's grabToImage() function. Since Window is not an item in itself, you'd have to grab the image from one of its elements. In this example, I grab the image from an item called viewport each time a frame is swapped on mainWindow, using the onFrameSwapped() signal. Then the path to that image in memory is set as the source for the image in the second window, named projectionWindow. The second window will open on the screen set by the screenID variable; it is also set to be a frame-less window with its visibility is set to either Maximized or FullScreen, such that it is the only window seen on the second screen.
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
id: mainWindow
title: qsTr("Main Window")
visible: true
width: 400
height: 200
color: "#0F0"
Rectangle {
id: viewport
color: "#00F"
width: parent.width/2
height: parent.height/2
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
}
onFrameSwapped: {
viewport.grabToImage(function(result) {
projectionWindow.frame = String(result.url);
});
}
Window {
id: projectionWindow
property int screenID: 1
property alias frame: img.source
transientParent: mainWindow
visible: true
x: Qt.application.screens[screenID].virtualX
y: Qt.application.screens[screenID].virtualY
width: Qt.application.screens[screenID].desktopAvailableWidth
height: Qt.application.screens[screenID].desktopAvailableHeight
flags: Qt.FramelessWindowHint
color: "#000"
visibility: Window.Maximized
Image {
id: img
anchors.fill: parent
fillMode: Image.PreserveAspectFit
// Performance tweaks
asynchronous: true
cache: false
}
}
}
I used to employed this solution on a project I develop. You can study the full implementation at:
https://github.com/Cuperino/QPrompt/blob/a873a2ec9b0619ec1bfdbde22fe90850e76969a5/src/kirigami_ui/main.qml#L828
https://github.com/Cuperino/QPrompt/blob/a873a2ec9b0619ec1bfdbde22fe90850e76969a5/src/prompter/ProjectionsManager.qml#L260
Alternate, incomplete, solution
The third, more manual, and unfinished solution consists of the following: You'd inherit from QQuickView or QQuickWindow, and then send the image to a second window on either the QQuickWindow::afterRendering() or the QQuickWindow::frameSwapped() signal. You would use a QQuickFramebufferObject or some other rendering pipeline such as DirectX, Metal or Vulkan, to render and grab the frame off screen. Using OpenGL as your renderer, means negating all the performance advantages that come from using the native rendering pipelines supported by Qt 6. You may need to implement the frame grab once per pipeline to get all performance benefits.
https://doc.qt.io/qt-6/qtquick-visualcanvas-scenegraph.html
The following talk by Giuseppe D’Angelo shows how some of this is setup in Qt 5. It doesn't show how to copy the contents to another window/screen but it could aid in this regard.
https://www.youtube.com/watch?v=V_idc9BBRuI
https://www.youtube.com/watch?v=D-7fVGIBz6k
You can use particular QML elements as texture sources and easily duplicate them via trivial fragment shaders.
You definitely do not want to take screenshots and draw that image back, it is wildly inefficient.
Keep in mind that it will be just a visual duplicate, it will not take user input. If you want it to be interactive in both windows, then you should simply use a single data object and connect it to two individual GUI elements.
OK, here is the code, but unfortunately, it evidently uncovers a bug in QML, as the implementation doesn't seem to work across different windows:
Window {
id: mainw
visible: true
width: 640
height: 480
title: qsTr("main window")
Row {
spacing: 5
Rectangle {
id: source
width: 100
height: 100
color: ma.containsPress ? "red" : "cyan"
Text {
text: "adoy"
anchors.centerIn: parent
}
MouseArea {
id: ma
anchors.fill: parent
}
}
ShaderEffectSource {
width: source.width
height: source.height
live: true
sourceItem: source
}
}
Window {
visible: true
width: 640
height: 480
title: qsTr("another window")
x: mainw.x + width + 10
y: mainw.y
Row {
ShaderEffectSource {
width: source.width
height: source.height
live: true
sourceItem: source
}
Rectangle {
width: 100
height: 100
color: "blue"
}
}
}
}
I am facing serious issues while scolling ListView when there is costly delegate.
When i am scrolling listview, its jerky.
Some once please help on this.
Here is my sample code
/LIstview with some dummy costly delegate, I tried using loader to load the delegate then its a bit better but still its jerky/
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Window 2.2
ApplicationWindow {
visible: true
width: Screen.width
height: Screen.height
ListView {
width: Screen.width
height: Screen.height
model: 500
spacing: 10
highlightMoveVelocity: 50
flickDeceleration: 500
delegate: Loader{
asynchronous: true
sourceComponent: Image {
width: Screen.width
asynchronous: true
height: index %2 === 0 ? 500: 200
source: "file:///home/Downloads/4k.jpg"
///Just for show
Image {
anchors.fill: parent
asynchronous: true
source: "file:///home/Downloads/4k.jpg"
}
Image {
anchors.fill: parent
asynchronous: true
source: "file:///home/Downloads/4k.jpg"
}
Image {
anchors.fill: parent
asynchronous: true
source: "file:///home/Downloads/4k.jpg"
}
Image {
anchors.fill: parent
asynchronous: true
source: "file:///home/Downloads/4k.jpg"
}
Image {
anchors.fill: parent
asynchronous: true
source: "file:///home/Downloads/4k.jpg"
}
Image {
anchors.fill: parent
asynchronous: true
source: "file:///home/Downloads/4k.jpg"
}
Image {
anchors.fill: parent
asynchronous: true
source: "file:///home/Downloads/4k.jpg"
}
}
}
}
}
Since it is unclear what is exactly is costly about your model/delegate, I cannot give exact advice, but:
If you have to create a list that is very long, containing items that require some rather costly code to run to fully show them:
You could run the costly code in a background thread, then update the display as the calculations are finished. In the meantime, you can present a non-finished version of the data. Once the calculations for one item are finished, you update the display.
This could be improved even more by caching the result.
Make use of QAbstractListModel's fetchMore functionality to fill the list slowly as the scrolling progresses. Here is a good example.
Both approaches can be combined, of course.
And naturally C++ is required for the solutions, but that's rather unavoidable once the limitations of QML are hit.
Maybe try increasing the cacheBuffer property. And remember - the value is pixels you want to pre-load.
Other than that, streamlining the delegate is the way to go, but that would require your actual use case rather than just some dummy costly delegate.
I want a ShaderEffectSource called snapshotter that updates on every change of a source item, but when the source item is destroyed, I want snapshotter to retain the last state of the source item.
In my code I use a workaround for a Qt deficiency - setting snapshotter.sourceItem = snapshotter when the previous source item gets destroyed. This works fine when I destroy the old source item e.g. on key press. But when I do it in an onStateChanged handler, I get this error:
ShaderEffectSource: 'recursive' must be set to true when rendering recursively.
But I don't want to set recursive: true, because then snapshotter would start repainting as fast as possible, wasting a lot of processing power.
Any idea why that problem happens considering it works fine when using key press, and/or a workaround?
I realize I'm asking why a hacky solution fails in some cases, but since it works in some cases, I'd like to use the solution anyway as it's very useful.
My code: (main.qml)
import QtQuick 2.6
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
Loader {
active: true
id: loader
sourceComponent:
Rectangle {
color: "red"
border.color: "white"
width: 100
height: 100
parent: row
states: [
State {
// the condition is always true, in this example code
name: "theOnlyState"; when: width === 100
}
]
onStateChanged: {
if(state === "theOnlyState") {
loader.active = false;
}
}
}
}
Row {
id: row
ShaderEffectSource {
id: snapshotItem
sourceItem: {
if(loader.status === Loader.Ready) {
return loader.item;
} else {
return snapshotItem;
}
}
live: loader.status === Loader.Ready
width: 100
height: 100
}
}
}
Note: I just had an idea: maybe setting recursive: true will not create the problem I mentioned, considering I'd only set it when live == false. Maybe Qt is smart enough not constantly redraw in that case. But I'm not sure how to check if that's true.
Ok, I found an 99%-authorative answer.
The worry about recursive: true that I expressed in the question was provoked by my vague memory of reading something like that in the Qt docs.
I now went ahead and looked up the relevant passage again, and here's what it says:
Setting both this property and live to true will cause the scene graph to render continuously. Since the ShaderEffectSource depends on itself, updating it means that it immediately becomes dirty again.
Note how they say that for the bad scenario to occur, live has to be true.
So the solution is to simply use recursive: true, which makes the solution much less hacky.
Note: I'm not gonna mark this answer accepted, because I'd like people to still go over it now and then and maybe, just maybe, prove me wrong (e.g. show that I'm misinterpreting the text).
More evidence in favor of the above conclusion:
I set the env var QSG_VISUALIZE to changes and ran this trivival test app:
import QtQuick 2.6
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
ShaderEffectSource {
width: 100
height: 100
id: shaderEffectSource
live: false
sourceItem: shaderEffectSource
recursive: true
}
}
It showed an unchanging colored square. But when I changed live to true in this code, it started flickering in random colors.
As you found out yourself, when you have live = false and recursive = true it won't be redrawn all the time.
I think the problem of yours might arise due to the magic, QML uses for its state machine, going back and forth and so on...
As far as I understand your problem, you want to create an object, take a snapshot, and delete it right after that again.
This is easier achieved by using methods and signals than by some declarative state changes and so on.
Specifically you might be looking for scheduleUpdate() to render the next frame, without the need of setting live = true. The next thing you will be interested in is the signal: scheduledUpdateCompleted to delete your object again.
Something like this:
import QtQuick 2.0
import QtQuick.Controls 2.0
import QtGraphicalEffects 1.0
ApplicationWindow {
width: 1024
height: 800
visible: true
Button {
text: 'Next'
onClicked: {
ses.sourceItem = prototype.createObject(ses)
}
}
ShaderEffectSource {
id: ses
y: 100
width: 50
height: 50
live: false
onSourceItemChanged: if (sourceItem !== this) scheduleUpdate()
onScheduledUpdateCompleted: {
sourceItem.destroy()
sourceItem = this
}
}
Component {
id: prototype
Rectangle {
width: 50
height: 50
color: Qt.rgba(Math.random(-1), Math.random(0), Math.random(1))
visible: false
Component.onCompleted: console.log('Created new Rectangle with color', color)
Component.onDestruction: console.log('Destroy Rectangle with color', color)
}
}
}
Remember: The sourceItem does not have to be visible to be rendered into an ShaderEffectSource. If it is not necessary for other reasons, I would keep it invisible, so it is not rendered twice.
I want to create a toolbar, which will rotate and expand when hovered. But the memory leak is so serious that may consume arbitrary RAM, the initial memory usage is about 20MB and reaches 300mb after 1000 animations. I just uses basic components, like ListView, Rectangle and MouseArea. Here is the whole source code. I'm new to qml. Any suggestions would be appreciated, Thanks.
//MyListView.qml
//The ListView will expand when activated.
ListView{
id:root
orientation: ListView.Horizontal
property alias list: listModel
width: calcWidth()
signal optionSelected(string option)
function enableItems(big){
for(var i in root.contentItem.children){
var child = root.contentItem.children[i]
if(child.objectName!=="delegate") continue
if(big){ child.state="big" }
else child.state=""
}
}
function calcWidth(){
var w = 0
for(var i in root.contentItem.children) {
var child = root.contentItem.children[i]
if(child.objectName==="delegate"){
w+=child.width
}
}
return w
}
model: ListModel{
id:listModel
}
delegate: Rectangle{
id: rect
objectName: "delegate"
height: parent.height
width: 0
color:"transparent"
border.color:"grey"; border.width:1; radius: 5
rotation: width/root.height * 360
states:State{
name: "big"
PropertyChanges{
target: rect; width: root.height
}
}
transitions: Transition {
from: "*"
to: "big"
reversible: true
NumberAnimation{
properties: "width";easing.type: Easing.OutQuad; duration: 400
}
}
Image{
anchors.fill: parent
source: imgSrc
}
MouseArea{
anchors.fill: parent
hoverEnabled: true
onEntered: enableItems(true)
onExited: enableItems(false)
onClicked: root.optionSelected(option)
}
}
}
//MyRow.qml
Row{
id:root
width: mainImg.width + listView.width
property alias list: listView.list
property alias mainImg: mainImg.source
signal optionSelected(string option)
Image{
id: mainImg; height: parent.height; width: parent.height
MouseArea{
anchors.fill: parent
hoverEnabled: true
onEntered: listView.enableItems(true)
onExited: listView.enableItems(false)
}
}
MyListView{
id: listView
height: root.height
onOptionSelected: root.optionSelected(option)
}
//test memory usage
Timer{
id: timer
running: true; interval: 400; repeat: true
property real times:0
property bool big :false
onTriggered: {
times ++
big = !big
listView.enableItems(big)
console.log(big+""+times)
if(times>500){
timer.running = false
gc()
}
}
}
}
//main.qml
Rectangle {
width: 360
height: 360
MyRow{
height: 128
mainImg: "qrc:/4.png"
onOptionSelected: console.log(option)
Component.onCompleted: {
list.append({imgSrc:"qrc:/1.png",option:"111"})
list.append({imgSrc:"qrc:/2.png",option:"222"})
list.append({imgSrc:"qrc:/3.png",option:"333"})
}
}
}
Edit1: I was using Qt 5.5.0 + Linux Mint 17.1 + KDE. Everything works fine after I switched to Qt 5.4.2 + windows 10.
I am not convinced that you have actually established a memory leak. The QML engine will cache and it is a common thing to see memory usage rising by 20-30 mb for no apparent reason.
But the memory leak is serious
Without any concrete numbers, one can only guess what you mean by that. Does memory usage rise with more than 30 MB and keep rising? Does it reach a 100? Or more?
Keep in mind JS is garbage collected, and collection in my own experience is far from logical, at least in QML. Sometimes I get non-referenced objects living all the way through the application life cycle, sometimes I get objects which are still in use when QML decides for some reason to delete them and the result is segfaults and crashes.
BTW you can force garbage collection with gc() and see if that does any good.
As already established in this similar post, the "normal" behavior is that memory will go up to a certain point and then stabilize, which I suspect is also happening in your case. You should investigate further and post concrete numbers, and if necessary - a bug report.
EDIT: I tested your example code on Qt 5.42, memory usage quickly reaches 47.068 MB, and after 500 animations it is still there, hasn't moved up even a single kb according to the task manager, it actually goes down a little if the animation is stopped, and returns to 47.068 MB if resumed.
With Qt 5.5 it stabilizes at 51.672 MB
In case you are running literally the same code, your Qt version may have a bug.