Search code examples
qtqmlshaderfragment-shader

How do I disable a QML ShaderEffect?


I have a shader that I'm running and I want to be able to disable the shader on the press on the button. I want to disable it so it doesn't use any extra resources. How can I achieve this? Here I have an example where a shader slowly fades in and out, and I want to disable the shader when you click the button on the top left. Right now, I get this exception:

ShaderEffect: Property 'source' is not assigned a valid texture provider (std::nullptr_t).

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
    width: 1024
    height: 600
    visible: true
    property bool shaderEnabled: true

    Rectangle {
        id: rect
        anchors.fill: parent
        color: "gray"
        layer.enabled: shaderEnabled

        Text {
            anchors.centerIn: parent
            text: qsTr("Hello")
            font.pixelSize: 40
            color: "blue"
        }
    }

    MouseArea {
        anchors.fill: parent
        onClicked: {
            if (shader.opacity > 0) {
                shader.opacity = 0
                console.log("Hiding")
            } else {
                shader.opacity = 1
                console.log("Showing")
            }
        }
    }

    Button {
        id: shaderButton
        text: shaderEnabled ? "Disable shader" : "Enable shader"
        onClicked: {
            shaderEnabled = !shaderEnabled
        }
    }

    ShaderEffect {
        id: shader
        anchors.fill: parent
        property variant source: shaderEnabled ? rect : null
        Behavior on opacity { PropertyAnimation {} }
        opacity: 0
        fragmentShader: "
            varying highp vec2 qt_TexCoord0;
            uniform sampler2D source;
            uniform lowp float qt_Opacity;
            void main() {
                gl_FragColor = texture2D(source, qt_TexCoord0) * vec4(1.0, 1.0, 0.0, 1.0) * qt_Opacity;
            }"
    }
}

Adding a visible: ShaderEnabled to the ShaderEffect seems to work but I still see the error message which leads me to believe that it's still using the resources. Would using a Loader help?


Solution

  • I know Loader was mentioned, but, a clean way to avoid Loaders is by using a Repeater and a model. When shaderEnabled is true, it will instantiate a new ShaderEffect instance. When shaderEnabled is false, it will destroy that ShaderEffect instance. To implement this in your example, you will need to rework how you control opacity via a property similar to how you have done shaderEnabled.

    Repeater {
        model: shaderEnabled ? 1 : 0
        ShaderEffect {
        }
    }