Recursion trick in ShaderEffectSource fails when I destroy the source object in onStateChanged

本小妞迷上赌 提交于 2019-12-12 03:27:47

问题


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 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.




回答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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!