Property Binding on Animated Property vs Multiple Animations - qt

Consider this example:
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
id: appWindow
width: 1024
height: 800
visible: true
Rectangle {
id: rect1
property bool active: true
opacity: active ? 1 : 0
height: 300 * opacity
width: 300 * opacity
Behavior on opacity { NumberAnimation { duration: 1000 } }
MouseArea { anchors.fill: parent; onClicked: parent.active = false }
color: 'cornflowerblue'
}
Rectangle {
id: rect2
property bool active: true
x: 305
opacity: active ? 1 : 0
height: active ? 300 : 0
width: active ? 300 : 0
Behavior on opacity { NumberAnimation { duration: 1000 } }
Behavior on height { NumberAnimation { duration: 1000 } }
Behavior on width { NumberAnimation { duration: 1000 } }
MouseArea { anchors.fill: parent; onClicked: parent.active = false }
color: 'steelblue'
}
}
I have two Rectangles with the same observable behavior: when clicked, they fade both in opacity and size.
Internally, it differs in the amount of Animations, that are running concurrently - either 1 or 3:
As of now, I mainly use the pattern form rect1 and only in cases where the bindings would get unneccessarily complex rect2. However I wonder, if the animation system has some magic, that optimizes the animation of a single property, while the binding might be less performant.
In which usecases it is beneficial to use pattern rect1 and when it would be wiser to use the method of rect2?
EDIT There is also a third option which moves, what possible, to the render thread via OpacityAnimator. Now I can't bind to the opacity anymore, as it will jump to 0 at the end of the animation.
Rectangle {
id: rect3
property bool active: true
opacity: active ? 1 : 0
height: active ? 300 : 0
x: 610
width: height
Behavior on opacity { OpacityAnimator { duration: 1000 } }
Behavior on height { NumberAnimation { duration: 1000 } }
MouseArea { anchors.fill: parent; onClicked: parent.active = false }
color: 'dodgerblue'
}
EDIT 2 To adress the Answer of Ansh Kumar:
This is an excerpt from the QML Profiler. You can see, that during the animation of rect2 there are neither bindings nor JavaScript running, unlike during the times where height and width are (efficiently) bound to the opacity in rect1 or the width is (efficiently) bound to the height in rect3.
Further the source of the animations shows little trace of JS. I couldn't examine it into all it's depths, but it seems, that only a ScriptAction gets a QQMLScriptString and the rest has only the cost of converting the input from var to the right type (if a type is specified by using a concrete animation such as NumberAnimation).
Further, as far as I can see, there is not a loop per animation involved, but all animations feature some kind of update()-function or so, that is called (when running/registered) by a single loop (AnimationTimer). But this is where I am already unsure about.
Now the question remains: Is the implementation of the animations more efficient than the optimized JS environment especially as multiple objects are created and stuff.

There are two types of bindings in QML: optimized and non-optimized bindings. It is a good idea to keep binding expressions as simple as possible, since the QML engine makes use of an optimized binding expression evaluator which can evaluate simple binding expressions without needing to switch into a full JavaScript execution environment. These optimized bindings are evaluated far more efficiently than more complex (non-optimized) bindings. The basic requirement for optimization of bindings is that the type information of every symbol accessed must be known at compile time.
Bindings are quickest when they know the type of objects and properties they are working with. Animating a property will cause any bindings which reference that property to be re-evaluated. Usually, this is what is desired. The opacity, height and width in rect2 are re-evaluated into a full JavaScript execution environment whereas in rect1; width and height goes through an optimized binding expression evaluator and optimized to give more efficient binding since their type of object is known at compile time. Check binding and also animations for more details.
EDIT
You were right about evaluation being done in C++ environment. I found following informations.
Rendering engine should achieve a consistent 60 frames-per-second refresh rate. 60 FPS means that there is approximately 16 milliseconds (exactly 16.6667 milliseconds) between each frame in which processing can be done, which includes the processing required to upload the draw primitives to the graphics hardware. This shows that the animation is in sync with the vertical refresh, so once every 16.66 ms, and exactly once pr frame.
while (animationIsRunning) {
processEvents();
advanceAnimations();
paintQMLScene();
swapAndBlockForNextVSync();
}
So, in rect1 you have set duration: 1000 and binded height with opacity (height: 300 * opacity) similarly width with opacity, so binding should be called around 60 times ? If you see QML profiler output of statistics you will find following
As expected number of calls are around 60 (exactly 63). Now if you change duration to 2000, number of calls will be doubled.
Since, 300 * opacity has to be calculated, so QML should call JavaScript environment around 60 times (when duration: 1000)
As expected it was called around 60 times.
What about the NumberAnimation, is it implemented in JavaScript or C++ ? Definitely, you were right about it being implemented in C++, Here is the link to its declaration . So, in rect1 we have used NumberAnimation one time and in rect2 we have used it 3 times. So, total of 4 instances of NumberAnimation should be created.
So, rect1 has a total of around 120 bindings and JavaScript calls whereas in in rect2 there is no binding and JavaScript calls, so animation of rect2 should be faster, but the question is, will there be any significant improvements? Since, free version of QtCreator does not comes with CPU analyzer I was not able to study that part of the question (CPU Usage Qt). If anyone has commercial version of Qt, please update me about my hypothesis. I really think that rect2 is the best for usage as number of calls are reduced.

Related

How qml MediaPlayer VideoOutput run in separate thread?

Suppose there is a code like this (sorry for the contrived code)
Window {
id: window
visible: true
width: 700
height: 700
MediaPlayer {
id: mediaplayer
source: "test.avi"
autoPlay: true
}
VideoOutput {
anchors.fill: parent
source: mediaplayer
}
Repeater {
id: repeater
property int n: 1
model: 1
Label {
function getRandomInt(max) {
return Math.floor(Math.random() * Math.floor(max))
}
id: label
y: getRandomInt(window.width)
x: getRandomInt(window.height)
text: "label"
}
}
Timer {
interval: 10
running: true
repeat: true
onTriggered: {
repeater.n += 1
if (!(repeater.n % 100)) {
repeater.model = repeater.n
}
}
}
}
When the number of labels increases, the video starts to break. How to start a video in a separate thread so that manipulations with any widgets do not interrupt it (video).
You should not overload main thread (with it's event loop). Otherwise, whole the software will lag, not only VideoOutput.
Don't move to another thread well-optimized and efficient controls. Move to another thread huge things, hard & long computations.
So
avoid using timers with low interval or if you use them, don't attach to them hard things
if you should create many or hard controls in QML, then use Loader or QQmlIncubator, they allow to create QML controls in a separate threads. Also Loader allows dynamically load and unload needed/unneeded controls. So, QML engine will not render too many controls, most of which even are not visible.
Note about Loader/QQmlIncubator: they create control in a separate thread, not run it there.
avoid writing custom properties and functions inside QML controls, place them in some single QtObject: QtObject { id: internals; ...all your custom data... }
Use ListView instead of Repeater. It's more efficient because it instantiates only visible items (and several out of visible area) - probably the most important for your sample
More advice and samples you can find here:
Performance Considerations And Suggestions: https://doc.qt.io/qt-5/qtquick-performance.html
QML performance tips and tricks: https://katastrophos.net/harmattan-dev/html/guide/html/Developer_Library_Best_practices_for_application_development_QML_performance_tips_and_tricks.html
QtObject: https://doc.qt.io/qt-5/qml-qtqml-qtobject.html

How to achieve better caching control for ListView?

I tried to play with cacheBuffer, but it's only help me to increase count of cached delegates, when I want to disable caching at all.
Now with zero caching buffer my example (only one item stretched on all ListView) behaves like this:
At the start ListView creates two delegates: currently visible and
next one.
When I scrolling list forward it creates and keep up to 4 delegates without beginning destroying them.
When I start scrolling list backward it begin immediately destroying delegates without looking on cacheBuffer.
If you replace "height: root.height" to "height: listView.height", it will create delegates for all model items at the start.
Is this behaviour normal? Can I change it some way?
You can tried it yourself:
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtQuick.Window 2.11
import Qt.labs.calendar 1.0
Window {
id: root
visible: true
width: 640
height: 480
title: qsTr("Hello World")
ListView {
id: listView
anchors.fill: parent
snapMode: ListView.SnapOneItem
cacheBuffer: 0
model: 10
delegate: Rectangle {
width: parent.width
height: root.height
// height: listView.height
border.color: "black"
Text {
anchors.centerIn: parent
text: modelData
}
Component.onCompleted: {
console.log("Delegate completed")
}
Component.onDestruction: {
console.log("Delegate destruction")
}
}
}
}
Replace
delegate: MyVeryComplexDelegate {
}
by
delegate: Loader {
width: expectedDelegateWidth
height: expectedDelegateHeight // Otherwise you might create all...
sourceComponent: MyVeryComplexDelegate {
}
active: someCriteriaYouFeelGoodAbout()
}
Now you will only have simple Loaders in your cache and you can decide which ones of those in the cache are active.
Probably better: Have parts of the MyVeryComplexDelegate loaded as the ListView wants, and just hide the most complex parts behind a Loader that turns active only if you really need the full complexity.
On your strange findings as far as I can explain them:
Regarding the difference between root.height and listView.height, the explanation is an issue that is subject to many questions:
While root.height references the property height of the window, which you have explicitly set, listView.height is determined by anchors.fill: parent, which results in setting the height to root.contentItem.height - and that is initially 0. Therefore the delegates, initially all have a height of 0, all of them would fit in the view and therefor have to be created, even if you load as lazy as possible. Later they will resize together with the root.contentItem and some will be destroyed again.
You can see that, when monitoring the height changes of your delegates and your ListView
The next thing is, that even if the delegate really fills the ListView from the beginning, a second delegate is instantiated. The reason for that is, the condition used by the ListView, when to create new delegates. For that the sum of heights - the displacement of the first has to be larger than the ListView. That is not fulfilled when it is equal to the height.
Increase the height of your delegate by a fraction of a pixel, and you are good.
height: root.height + 0.0001

Qt - QML Screen Mirroring in a dual monitor setup

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"
}
}
}
}

Zero width or height versus visible property in QML

Does setting an component's width or height to zero has the same effect as setting its visible property to false?
An example use-case:
I have an item, which slides into the window. The sliding happens by animating its height from 0 to x and when I dismiss this item from x to 0. Don't want to go in depth why I am not animating the position of the item instead. When the item has 0 height, should I set its visible property to false or it doesn't make any difference?
Not really, unless you clip. And it is better to avoid clipping as much as possible.
An Item with zero size will still have its children visible.
Whereas setting visible to false will hide the entire object tree.
In your particular case it seems like it doesn't matter as long as it doesn't cause you to have unwanted visible leftovers. You certainly do not want to have a binding such as visible: height as that would needlessly execute on every step of the animation.
Just to be on the safe side, you can install handlers on the animation to toggle visibility:
// in the animation
onStarted: if (!item.height) item.visible = true // show if start at 0
onStopped: if (!item.height) item.visible = false // hide if end at 0
This will avoid the continuous reevaluations you'd get if you bind visibility to height directly, but will still ensure visibility on before your object begins expanding and off after it has finished contracting.
As dtech already pointed out, the dimensions of the root node of a component do not automatically represent the dimensions of the underlying object tree. As an example take this:
Item {
id: root
Text {
id: txt
text: 'some text produces implicit width'
}
}
In this example the text of txt will be shown, though the dimensions of root are width: 0; height: 0.
As dtech already mentioned, you might set clip to true, but this is not advisable, as then it would be passed to the renderer, which renders the Item and its tree and finally applies clipping to it - in a seperate batch.
If you have something like that:
Item {
Rectangle {
anchors.fill: parent
color: 'red'
}
}
The renderer would do nothing extra when rendering, as it could be processed in the same batch as the rest. However as a developer it is hard to tell, whether something is visible when the size is set to 0 or not. Therefore it is adivsable to always set visible properly.
We might simply set
visible: width > 0 && height > 0 && opacity > 0
which works fine, as long as we don't animate on any of those properties or change them frequently. At least for animations we might have good knowledge, when the any of those properties might become 0 and use this information to reduce the amount of evaluations.
The nice thing about QML is, that the logical expression is evaluated from the left to the right, which means in our last example:
If width === 0 and height changes, it wont trigger reevaluation
If height === 0 and width changes, each change triggers reevaluation.
This means, we need to put the most stable condition first. This might be our information about when any of those values might change. I propose, using the animation.running property, to prevent reevaluation of the binding, while the animation is running.
Let's take a more complete example: Upon click, this Rectangle will shrink from width: 800 to width: 0 - which shall set it invisible.
Or three additional properties binding1, binding2, binding3 are bound to expressions, that we might use to set visible. When ever a particular part of the binding is reeavluated, we log this.
Rectangle {
id: rect
color: 'red'
width: 800
height: 600
NumberAnimation {
id: ani1
target: rect
property: 'width'
from: 800
to: 0
duration: 3000
}
}
MouseArea {
anchors.fill: parent
onClicked: ani1.running = true
}
property bool binding1: {console.log("1", !rect.width); return !rect.width}
property bool binding2: {!ani1.running && (function() { console.log("2", !rect.width); return !rect.width })()}
property bool binding3: {(function() { console.log("3", !rect.width); return !rect.width })() && !ani1.running}
// equivalent, stripped of the logging:
// property bool binding1: !rect.width
// property bool binding2: !ani1.running && !rect.width
// property bool binding3: !rect.width && !ani1.running
As we can see, binding1 is constantly reevaluated, when ever the width changes. This is not desirable.
We can see, that binding2 is only evaluated once at creation, and whenever ani stops running.
In binding3 we have it the other way around and we first evaluate the width, and then whether the ani is running. This means, we have a reevaluation whenever the width is changing.
We could also use the signal handlers ani.onStarted and ani.onStopped and explicitly set the visiblity then, but that would not be declarative and QML encourages you to always strive to stay declarativ.

QML OnHighligt movement completed

I have this ListView:
ListView {
id: topList
focus: true
width: parent.width
height: parent.height
preferredHighlightBegin: height - 70
preferredHighlightEnd: height
highlightMoveSpeed: 150
highlight: Rectangle { width: theListView.width; height: 22; color: "yellow" }
highlightRangeMode: ListView.StrictlyEnforceRange
}
Now, when I use up or down arrow the list moves nice and fine. However, when the movement is done, I would like to trigger another animation. Have tried listening on onMovementEnded and onFlickEnded but neither of them seems to be triggered when the animation is done.
The QtQuick examples include an example of rolling your own: http://doc.qt.nokia.com/4.7-snapshot/declarative-modelviews-listview-highlight-qml.html
The basic idea is to prevent the view from moving the highlight itself:
highlightFollowsCurrentItem: false
and use an animation to make the highlight follow the currentItem. You will probably want to stick to SmoothedAnimation or SpringAnimation since they handle the target position being moved before the animation is complete.
I believe it is not possible. highlightPosAnimator and highlightSizeAnimator are part of QDeclarativeListView private implementation, and not accesible by "user" code.
If you need custom/combined highlight animation, looks like you have to roll your own.

Resources