[vlc-commits] [Git][videolan/vlc][master] 3 commits: qml: fix double layering in `FadingEdge`

Felix Paul Kühne (@fkuehne) gitlab at videolan.org
Sat May 30 16:10:19 UTC 2026



Felix Paul Kühne pushed to branch master at VideoLAN / VLC


Commits:
31795ba1 by Fatih Uzunoglu at 2026-05-30T17:19:57+02:00
qml: fix double layering in `FadingEdge`

Even though the `ShaderEffectSource` is a dynamic
texture provider and represents a layer itself,
using `layer.enabled` makes Qt create another
layer on top of that layer.

- - - - -
4f1ee897 by Fatih Uzunoglu at 2026-05-30T17:19:57+02:00
qml: release the layer when applicable in `FadingEdge`

It covers these cases:

- If `useLayer` is always on, neither the layer nor the
  associated items (`ShaderEffect` and `ShaderEffectSource`)
  are never created.
- If `useLayer` becomes off at a later time, the layer
  texture is released but the associated items are not
  destroyed.

Regarding the items (`ShaderEffectSource` and `ShaderEffect`),
once `useLayer` becomes `true`, they are created and are not
destroyed if `useLayer` becomes `false` later on. This is
because `useLayer` can change many times depending on the
background color (such as backdrop blur is no longer available
because the interface window lost focus).

- - - - -
663e5b9a by Fatih Uzunoglu at 2026-05-30T17:19:57+02:00
qml: add property `enforceClipping` in `FadingEdge`

- - - - -


1 changed file:

- modules/gui/qt/widgets/qml/FadingEdge.qml


Changes:

=====================================
modules/gui/qt/widgets/qml/FadingEdge.qml
=====================================
@@ -32,7 +32,7 @@ Item {
     // which results lower memory consumption:
     property alias backgroundColor: backgroundRect.color
 
-    property alias sourceItem: shaderEffectSource.sourceItem
+    property Item sourceItem
 
     property real beginningMargin
     property real endMargin
@@ -60,7 +60,40 @@ Item {
 
     readonly property bool effectCompatible: (GraphicsInfo.shaderType === GraphicsInfo.RhiShader)
 
-    readonly property bool implicitClipping: shaderEffectSource.visible
+    readonly property bool implicitClipping: !!_shaderEffect?.visible
+
+    property bool useLayering: effectCompatible && (enforceClipping || backgroundColor.a < 1.0)
+
+    // Sometimes `Item::clip` may not be suitable to use with item views,
+    // this includes, but not limited to, when display margins are set.
+    // In this case, `enforceClipping` may be set. Note that setting this
+    // property does not guarantee that clipping is done, it is advised
+    // to check `implicitClipping` to know if clipping is actually done.
+    // NOTE: `useLayering` must be set for this to be respected, by
+    //       default it is set when `enforceClipping` is set.
+    // TODO: Get rid of this once we can adjust the clip rect directly
+    //       in QML (currently requires overriding `QQuickItem::clipRect()`).
+    property bool enforceClipping: false
+
+    property Item _shaderEffectSource
+    property Item _shaderEffect
+
+    onUseLayeringChanged: {
+        if (useLayering) {
+            // We create the items needed for layer once it is needed, and keep it.
+            // Note that, while we keep the items, the layer texture should still
+            // be released if `useLayer` becomes `false` after that (since we set
+            // `sourceItem` to `null` while `live` is set).
+
+            if (!_shaderEffectSource) {
+                _shaderEffectSource = shaderEffectSourceComponent.createObject(positionerParent)
+            }
+
+            if (!_shaderEffect) {
+                _shaderEffect = shaderEffectComponent.createObject(positionerParent)
+            }
+        }
+    }
 
     Rectangle {
         id: backgroundRect
@@ -73,7 +106,7 @@ Item {
 
         z: -100
 
-        visible: shaderEffectSource.visible && (color.a > 0.0)
+        visible: !!root._shaderEffectSource?.visible && (color.a > 0.0)
     }
 
     // Fading edge effect provider when background is opaque:
@@ -85,7 +118,7 @@ Item {
         implicitWidth: root.fadeSize
 
         // NOTE: `OpacityAnimator` updates the opacity once the animation finishes.
-        visible: (opacity > 0.0 || enabled) && (root.backgroundColor.a > (1.0 - Number.EPSILON))
+        visible: (opacity > 0.0 || enabled) && !root.useLayering
 
         opacity: enabled ? 1.0 : 0.0
 
@@ -134,78 +167,94 @@ Item {
         }
     }
 
-    FadingEdgeRectangleForOpaqueBackground {
-        anchors {
-            top: shaderEffectSource.top
-            left: shaderEffectSource.left
+    Item {
+        id: positionerParent
 
-            bottom: (root.orientation === Qt.Horizontal) ? shaderEffectSource.bottom : undefined
-            right: (root.orientation === Qt.Vertical) ? shaderEffectSource.right : undefined
+        anchors {
+            fill: parent
+            leftMargin: (root.orientation === Qt.Horizontal ? -root.beginningMargin : 0)
+            rightMargin: (root.orientation === Qt.Horizontal ? -root.endMargin : 0)
+            topMargin: (root.orientation === Qt.Vertical ? -root.beginningMargin : 0)
+            bottomMargin: (root.orientation === Qt.Vertical ? -root.endMargin : 0)
         }
 
-        enabled: root.enableBeginningFade
-        colorStop0: root.backgroundColor
-        colorStop1: "transparent"
-    }
+        FadingEdgeRectangleForOpaqueBackground {
+            anchors {
+                top: parent.top
+                left: parent.left
 
-    FadingEdgeRectangleForOpaqueBackground {
-        anchors {
-            bottom: shaderEffectSource.bottom
-            right: shaderEffectSource.right
+                bottom: (root.orientation === Qt.Horizontal) ? parent.bottom : undefined
+                right: (root.orientation === Qt.Vertical) ? parent.right : undefined
+            }
 
-            top: (root.orientation === Qt.Horizontal) ? shaderEffectSource.top : undefined
-            left: (root.orientation === Qt.Vertical) ? shaderEffectSource.left : undefined
+            enabled: root.enableBeginningFade
+            colorStop0: root.backgroundColor
+            colorStop1: "transparent"
         }
 
-        enabled: root.enableEndFade
-        colorStop0: "transparent"
-        colorStop1: root.backgroundColor
-    }
+        FadingEdgeRectangleForOpaqueBackground {
+            anchors {
+                bottom: parent.bottom
+                right: parent.right
 
-    // Fading edge effect provider when background is not opaque:
-    ShaderEffectSource {
-        id: shaderEffectSource
+                top: (root.orientation === Qt.Horizontal) ? parent.top : undefined
+                left: (root.orientation === Qt.Vertical) ? parent.left : undefined
+            }
 
-        anchors {
-            top: parent.top
-            left: parent.left
-            leftMargin: (root.orientation === Qt.Horizontal ? -root.beginningMargin : 0)
-            topMargin: (root.orientation === Qt.Vertical ? -root.beginningMargin : 0)
+            enabled: root.enableEndFade
+            colorStop0: "transparent"
+            colorStop1: root.backgroundColor
         }
+    }
+
+    Component {
+        id: shaderEffectSourceComponent
+
+        // Fading edge effect provider when background is not opaque:
+        ShaderEffectSource {
+            id: shaderEffectSource
+
+            anchors.top: parent.top
+            anchors.left: parent.left
 
-        property real eDPR: MainCtx.effectiveDevicePixelRatio(Window.window) || 1.0
-        readonly property int alignNumber: pixelAlignedForDPR ? Helpers.denominatorForFloat(eDPR) : 1
+            property real eDPR: MainCtx.effectiveDevicePixelRatio(Window.window) || 1.0
+            readonly property int alignNumber: pixelAlignedForDPR ? Helpers.denominatorForFloat(eDPR) : 1
 
-        Connections {
-            target: MainCtx
+            Connections {
+                target: MainCtx
 
-            function onIntfDevicePixelRatioChanged() {
-                shaderEffectSource.eDPR = MainCtx.effectiveDevicePixelRatio(shaderEffectSource.Window.window) || 1.0
+                function onIntfDevicePixelRatioChanged() {
+                    shaderEffectSource.eDPR = MainCtx.effectiveDevicePixelRatio(shaderEffectSource.Window.window) || 1.0
+                }
             }
-        }
 
-        implicitWidth: Helpers.alignUp((parent.width + (root.orientation === Qt.Horizontal ? (root.beginningMargin + root.endMargin) : 0)), alignNumber)
-        implicitHeight: Helpers.alignUp((parent.height + (root.orientation === Qt.Vertical ? (root.beginningMargin + root.endMargin) : 0)), alignNumber)
+            implicitWidth: Helpers.alignUp((parent.width + (root.orientation === Qt.Horizontal ? (root.beginningMargin + root.endMargin) : 0)), alignNumber)
+            implicitHeight: Helpers.alignUp((parent.height + (root.orientation === Qt.Vertical ? (root.beginningMargin + root.endMargin) : 0)), alignNumber)
 
-        sourceRect: Qt.rect(root.sourceX - (root.orientation === Qt.Horizontal ? root.beginningMargin : 0),
-                            root.sourceY - (root.orientation === Qt.Vertical ? root.beginningMargin : 0),
-                            width, // Texture width is (width * dpr)
-                            height) // Texture height is (height * dpr)
+            // When `live` is set, Qt releases the layer when `sourceItem` becomes `null`. For that reason
+            // we do not need to set `parent` to null. The important thing is to get rid  of the layer
+            // texture, we don't need to care about getting rid of the texture provider. See
+            // `QQuickShaderEffectSource::updatePaintNode()` and `QSGRhiLayer::setItem()`.
+            sourceItem: root.useLayering ? root.sourceItem : null
 
-        // Make sure sourceItem is not rendered twice:
-        hideSource: visible
+            sourceRect: Qt.rect(root.sourceX - (root.orientation === Qt.Horizontal ? root.beginningMargin : 0),
+                                root.sourceY - (root.orientation === Qt.Vertical ? root.beginningMargin : 0),
+                                width, // Texture width is (width * dpr)
+                                height) // Texture height is (height * dpr)
 
-        smooth: false
+            // Make sure sourceItem is not rendered twice:
+            hideSource: root._shaderEffect && (root._shaderEffect.visible && root._shaderEffect.source === this)
 
-        visible: effectCompatible &&
-                 (root.backgroundColor.a < 1.0) &&
-                 ((root.enableBeginningFade || root.enableEndFade) ||
-                 ((shaderEffect) && (shaderEffect.beginningFadeSize > 0 || shaderEffect.endFadeSize > 0)))
+            visible: false
+
+            smooth: false
+        }
+    }
 
-        property ShaderEffect shaderEffect
+    Component {
+        id: shaderEffectComponent
 
-        layer.enabled: true
-        layer.effect: ShaderEffect {
+        ShaderEffect {
             // It makes sense to use the effect for only in the fading part.
             // However, it would complicate things in the QML side. As it
             // would require two additional items, as well as two more texture
@@ -215,6 +264,10 @@ Item {
 
             id: effect
 
+            anchors.fill: root._shaderEffectSource
+
+            readonly property Item source: root._shaderEffectSource
+
             readonly property bool vertical: (root.orientation === Qt.Vertical)
             readonly property real normalFadeSize: root.fadeSize / (vertical ? height : width)
 
@@ -222,6 +275,11 @@ Item {
             property real endFadeSize: root.enableEndFade ? normalFadeSize : 0
             readonly property real endFadePos: 1.0 - endFadeSize
 
+            visible: root.useLayering &&
+                     (root.enforceClipping ||
+                      ((root.enableBeginningFade || root.enableEndFade) ||
+                       (beginningFadeSize > 0 || endFadeSize > 0)))
+
             onBeginningFadeSizeChanged: {
                 if (!beginningFadeBehavior.enabled) {
                     Qt.callLater(effect._enableBeginningFadeBehavior)
@@ -242,16 +300,6 @@ Item {
                 endFadeBehavior.enabled = true
             }
 
-            Component.onCompleted: {
-                console.assert(shaderEffectSource.shaderEffect === null)
-                shaderEffectSource.shaderEffect = this
-            }
-
-            Component.onDestruction: {
-                console.assert(shaderEffectSource.shaderEffect === this)
-                shaderEffectSource.shaderEffect = null
-            }
-
             component FadeBehavior : Behavior {
                 enabled: false
 



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/1ee9c4139eacf5ac562795f089ffc5aec1c500ad...663e5b9a0e0520323cedd82499b802a8e2645fce

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/1ee9c4139eacf5ac562795f089ffc5aec1c500ad...663e5b9a0e0520323cedd82499b802a8e2645fce
You're receiving this email because of your account on code.videolan.org. Manage all notifications: https://code.videolan.org/-/profile/notifications | Help: https://code.videolan.org/help




More information about the vlc-commits mailing list