问题
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.
回答1:
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 theShaderEffectSource
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.
回答2:
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.
来源:https://stackoverflow.com/questions/43101121/recursion-trick-in-shadereffectsource-fails-when-i-destroy-the-source-object-in