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


Commits:
7eeab6c7 by Fatih Uzunoglu at 2025-09-22T15:10:40+00:00
qml: implement `live` optimization hint in `DualKawaseBlur`

- - - - -
9cc19cdb by Fatih Uzunoglu at 2025-09-22T15:10:40+00:00
qml: expose the source texture provider observer in `DualKawaseBlur`

- - - - -
9c0bfb07 by Fatih Uzunoglu at 2025-09-22T15:10:40+00:00
qml: disable `live` for the blur effect in player page

The source is static, we can use the optimization hint here.

- - - - -
298d1060 by Fatih Uzunoglu at 2025-09-22T15:10:40+00:00
qml: revise the todo regarding disabling `live` in blur effect in 
`ArtistTopBanner`

- - - - -


3 changed files:

- modules/gui/qt/medialibrary/qml/ArtistTopBanner.qml
- modules/gui/qt/player/qml/Player.qml
- modules/gui/qt/widgets/qml/DualKawaseBlur.qml


Changes:

=====================================
modules/gui/qt/medialibrary/qml/ArtistTopBanner.qml
=====================================
@@ -95,7 +95,7 @@ FocusScope {
             anchors.left: parent.left
             anchors.right: parent.right
 
-            // TODO: Disable `live`, consider asynchronous loading and size 
changes.
+            // NOTE: No need to disable `live`, as this uses two pass mode so 
there is no video memory saving benefit.
 
             // If source image is tiled, layering is necessary:
             readonly property bool sourceNeedsLayering: (background.fillMode 
=== Image.Tile)


=====================================
modules/gui/qt/player/qml/Player.qml
=====================================
@@ -290,7 +290,7 @@ FocusScope {
 
                         radius: 3
 
-                        // TODO: Disable `live`, consider asynchronous loading.
+                        live: false
                         
                         //destination aspect ratio
                         readonly property real dar: parent.width / 
parent.height
@@ -322,6 +322,33 @@ FocusScope {
                             mode: bgtheme.palette.isDark ? 
Widgets.FastBlend.Mode.Multiply // multiply makes darker
                                                          : 
Widgets.FastBlend.Mode.Screen // screen (inverse multiply) makes lighter
                         }
+
+                        Component.onCompleted: {
+                            // Blur layers are effect-size dependent, so once 
the user starts resizing the window (hence the effect),
+                            // we should either momentarily turn on live, or 
repeatedly call `scheduleUpdate()`. Due to the optimization,
+                            // calling `scheduleUpdate()` would continuously 
create and release intermediate layers, which would be a
+                            // really bad idea. So instead, we turn on live 
and after some time passes turn it off again.
+                            widthChanged.connect(liveTimer, 
liveTimer.transientTurnOnLive)
+                            heightChanged.connect(liveTimer, 
liveTimer.transientTurnOnLive)
+                        }
+
+                        Timer {
+                            id: liveTimer
+
+                            repeat: false
+                            interval: VLCStyle.duration_humanMoment
+
+                            function transientTurnOnLive() {
+                                if (!blurredBackground.sourceTextureIsValid)
+                                    return
+                                blurredBackground.live = true
+                                liveTimer.restart()
+                            }
+
+                            onTriggered: {
+                                blurredBackground.live = false
+                            }
+                        }
                     }
                 }
 
@@ -385,18 +412,46 @@ FocusScope {
                                 cache: false
                                 asynchronous: true
 
-                                sourceSize: Qt.size(maximumSize, maximumSize)
-
-                                Accessible.role: Accessible.Graphic
-                                Accessible.name: qsTr("Cover")
-
                                 onTargetSourceChanged: {
                                     cover.source = targetSource
                                 }
 
                                 onStatusChanged: {
-                                    if (status === Image.Error)
+                                    if (status === Image.Ready) {
+                                        // This also covers source (and other 
parameters) change and not only initial loading
+                                        if 
(blurredBackground.sourceTextureIsValid) {
+                                            // Possible image switch and stale 
texture (especially old Qt without patch c871a52), we
+                                            // should wait one frame for the 
texture to be updated to avoid applying blur on stale one.
+                                            
blurredBackground.scheduleUpdate(true)
+                                        } else {
+                                            // If not valid, the blur effect 
is going to wait appropriately until valid itself:
+                                            // Initial case (such as switching 
to player page), or switching images with recent Qt.
+                                            
blurredBackground.scheduleUpdate(false)
+                                        }
+                                    } else if (status === Image.Error) {
                                         cover.source = VLCStyle.noArtAlbumCover
+                                    }
+                                }
+
+                                sourceSize: Qt.size(maximumSize, maximumSize)
+
+                                Accessible.role: Accessible.Graphic
+                                Accessible.name: qsTr("Cover")
+
+                                Component.onCompleted: {
+                                    // After the update on source change, 
there can be another update when the mipmaps are generated.
+                                    // We intentionally do not wait for this, 
initially using non-mipmapped source should be okay. As
+                                    // the user should not be greeted with a 
black background until the mipmaps are ready, let alone
+                                    // the possibility of knowing if the 
mipmaps are actually going to be ready as expected.
+                                    // If the texture is not valid yet (which 
is signalled the latest), blur effect is going to queue
+                                    // an update itself similar to the case 
when the source itself changes, so we do not check validity
+                                    // of the texture here.
+                                    
blurredBackground.sourceTextureProviderObserver.hasMipmapsChanged.connect(blurredBackground,
+                                                                               
                               (hasMipmaps /*: bool */) => {
+                                                                               
                                   if (hasMipmaps) {
+                                                                               
                                       blurredBackground.scheduleUpdate()
+                                                                               
                                   }
+                                                                               
                               })
                                 }
 
                                 Widgets.RoundedRectangleShadow {


=====================================
modules/gui/qt/widgets/qml/DualKawaseBlur.qml
=====================================
@@ -41,10 +41,12 @@ Item {
 
     property int configuration: DualKawaseBlur.Configuration.FourPass
 
-    // NOTE: This property is an optimization hint. When it is false, the 
result
-    //       may be cached, and the intermediate buffers for the blur passes 
may
-    //       be released.
-    // TODO: This is pending implementation.
+    // NOTE: This property is also an optimization hint. When it is false, the
+    //       intermediate buffers for the blur passes may be released (only
+    //       the two intermediate layers in four pass mode, we must have one
+    //       layer regardless of the mode, so optimization-wise it has no
+    //       benefit in two pass mode thus should be used solely as behavior
+    //       instead):
     property bool live: true
 
     // Do not hesitate to use an odd number for the radius, there is virtually
@@ -56,7 +58,7 @@ Item {
     //       used even if it is set false here. For that reason, it should not 
be
     //       necessary to check for opacity (well, accumulated opacity can not 
be
     //       checked directly in QML anyway).
-    property bool blending: (!ds1SourceObserver.isValid || 
ds1SourceObserver.hasAlphaChannel)
+    property bool blending: (!sourceTextureIsValid || 
sourceTextureProviderObserver.hasAlphaChannel)
 
     // source must be a texture provider item. Some items such as `Image` and
     // `ShaderEffectSource` are inherently texture provider. Other items needs
@@ -72,6 +74,62 @@ Item {
     // `QSGTextureView` can also be used instead of sub-texturing here.
     property rect sourceRect
 
+    property alias sourceTextureProviderObserver: ds1SourceObserver // for 
accessory
+
+    readonly property bool sourceTextureIsValid: 
sourceTextureProviderObserver.isValid
+
+    onSourceTextureIsValidChanged: {
+        if (root.sourceTextureIsValid) {
+            if (root._queuedScheduledUpdate) {
+                root._queuedScheduledUpdate = false
+
+                // Normally it should be fine to call `scheduleUpdate()` 
directly for
+                // the initial layer, even though the subsequent layers must 
be chained
+                // for update scheduling regardless, but old Qt seems to want 
it:
+                root.scheduleUpdate(true)
+            }
+        }
+    }
+
+    property var /*QtWindow*/ _window: null // captured window used for 
chaining through `afterAnimating()`
+
+    property bool _queuedScheduledUpdate: false
+
+    function scheduleUpdate(onNextAfterAnimating /* : bool */ = false) {
+        if (live)
+            return // no-op
+
+        if (!root.sourceTextureIsValid) {
+            root._queuedScheduledUpdate = true // if source texture is not 
valid, delay the update until valid
+            return
+        }
+
+        if (root._window) {
+            // One possible case for this is that the mipmaps for the source 
texture were generated too fast, and
+            // the consumer wants to update the blur to make use of the 
mipmaps before the blur finished chained
+            // updates for the previous source texture which is the 
non-mipmapped version of the same texture.
+            console.debug(root, "scheduleUpdate(): There is an already ongoing 
chained update, re-scheduling...")
+            root._queuedScheduledUpdate = true
+            return
+        }
+
+        root._window = root.Window.window
+        if (onNextAfterAnimating) {
+            root._window.afterAnimating.connect(ds1layer, 
ds1layer.scheduleChainedUpdate)
+        } else {
+            ds1layer.scheduleChainedUpdate()
+        }
+    }
+
+    onLiveChanged: {
+        if (live) {
+            ds1layer.parent = root
+            ds2layer.inhibitParent = false
+        } else {
+            root.scheduleUpdate(false) // this triggers releasing intermediate 
layers (when applicable)
+        }
+    }
+    
     // TODO: Get rid of this in favor of GLSL 1.30's `textureSize()`
     Connections {
         target: root.Window.window
@@ -166,6 +224,32 @@ Item {
         sourceItem: ds1
         visible: false
         smooth: true
+
+        live: root.live
+
+        function scheduleChainedUpdate() {
+            if (!ds1layer) // context is lost, Qt bug (reproduced with 6.2)
+                return
+
+            // Common for both four and two pass mode:
+            ds1layer.parent = root
+            ds1layer.scheduleUpdate()
+
+            if (root._window) {
+                root._window.afterAnimating.disconnect(ds1layer, 
ds1layer.scheduleChainedUpdate)
+
+                // In four pass mode, we can release the two intermediate 
layers:
+                if (root.configuration === 
DualKawaseBlur.Configuration.FourPass) {
+                    // Scheduling update must be done sequentially for each 
layer in
+                    // a chain. It seems that each layer needs one frame for 
it to be
+                    // used as a source in another layer, so we can not 
schedule
+                    // update for each layer at the same time:
+                    root._window.afterAnimating.connect(ds2layer, 
ds2layer.scheduleChainedUpdate)
+                } else {
+                    root._window = null
+                }
+            }
+        }
     }
 
     ShaderEffect {
@@ -175,7 +259,8 @@ Item {
         width: ds1.width / 2
         height: ds1.height / 2
 
-        readonly property Item source: ds1layer
+        // Qt uses reference counting, otherwise ds1layer may not be released, 
even if it has no parent (see `QQuickItemPrivate::derefWindow()`):
+        readonly property Item source: ((root.configuration === 
DualKawaseBlur.Configuration.TwoPass) || !ds1layer.parent) ? null : ds1layer
         property rect normalRect // not necessary here, added because of the 
warning
         readonly property int radius: root.radius
 
@@ -193,7 +278,7 @@ Item {
 
         visible: false
 
-        fragmentShader: "qrc:///shaders/DualKawaseBlur_downsample.frag.qsb"
+        fragmentShader: source ? 
"qrc:///shaders/DualKawaseBlur_downsample.frag.qsb" : "" // to prevent warning 
if source becomes null
 
         supportsAtlasTextures: true
 
@@ -208,9 +293,27 @@ Item {
         // never visible and was never used as texture provider, it should 
have never allocated
         // resources to begin with.
         sourceItem: (root.configuration === 
DualKawaseBlur.Configuration.FourPass) ? ds2 : null
+        parent: (!inhibitParent && sourceItem) ? root : null // this seems 
necessary to release resources even if sourceItem becomes null (non-live case)
 
         visible: false
         smooth: true
+
+        live: root.live
+
+        property bool inhibitParent: false
+
+        function scheduleChainedUpdate() {
+            if (!ds2layer) // context is lost, Qt bug (reproduced with 6.2)
+                return
+
+            ds2layer.inhibitParent = false
+            ds2layer.scheduleUpdate()
+
+            if (root._window) {
+                root._window.afterAnimating.disconnect(ds2layer, 
ds2layer.scheduleChainedUpdate)
+                root._window.afterAnimating.connect(us1layer, 
us1layer.scheduleChainedUpdate)
+            }
+        }
     }
 
     ShaderEffect {
@@ -219,7 +322,8 @@ Item {
         width: ds2.width * 2
         height: ds2.height * 2
 
-        readonly property Item source: ds2layer
+        // Qt uses reference counting, otherwise ds2layer may not be released, 
even if it has no parent (see `QQuickItemPrivate::derefWindow()`):
+        readonly property Item source: ((root.configuration === 
DualKawaseBlur.Configuration.TwoPass) || !ds2layer.parent) ? null : ds2layer
         property rect normalRect // not necessary here, added because of the 
warning
         readonly property int radius: root.radius
 
@@ -237,7 +341,7 @@ Item {
 
         visible: false
 
-        fragmentShader: "qrc:///shaders/DualKawaseBlur_upsample.frag.qsb"
+        fragmentShader: source ? 
"qrc:///shaders/DualKawaseBlur_upsample.frag.qsb" : "" // to prevent warning if 
source becomes null
 
         supportsAtlasTextures: true
 
@@ -252,9 +356,47 @@ Item {
         // never visible and was never used as texture provider, it should 
have never allocated
         // resources to begin with.
         sourceItem: (root.configuration === 
DualKawaseBlur.Configuration.FourPass) ? us1 : null
+        parent: sourceItem ? root : null // this seems necessary to release 
resources even if sourceItem becomes null (non-live case)
 
         visible: false
         smooth: true
+
+        live: root.live
+
+        function scheduleChainedUpdate() {
+            if (!us1layer) // context is lost, Qt bug (reproduced with 6.2)
+                return
+
+            us1layer.scheduleUpdate()
+
+            if (root._window) {
+                root._window.afterAnimating.disconnect(us1layer, 
us1layer.scheduleChainedUpdate)
+                root._window.afterAnimating.connect(us1layer, 
us1layer.releaseResourcesOfIntermediateLayers)
+            }
+        }
+
+        function releaseResourcesOfIntermediateLayers() {
+            if (!ds1layer || !ds2layer) // context is lost, Qt bug (reproduced 
with 6.2)
+                return
+
+            // Last layer is updated, now it is time to release the 
intermediate buffers:
+            console.debug(root, ": releasing intermediate layers, expect the 
video memory consumption to drop.")
+
+            // 
https://doc.qt.io/qt-6/qquickitem.html#graphics-resource-handling
+            ds1layer.parent = null
+            ds2layer.inhibitParent = true
+
+            if (root._window) {
+                root._window.afterAnimating.disconnect(us1layer, 
us1layer.releaseResourcesOfIntermediateLayers)
+                root._window = null
+            }
+
+            if (root._queuedScheduledUpdate) {
+                // Tried calling `scheduleUpdate()` before the ongoing chained 
updates completed.
+                root._queuedScheduledUpdate = false
+                root.scheduleUpdate(false)
+            }
+        }
     }
 
     ShaderEffect {



View it on GitLab: 
https://code.videolan.org/videolan/vlc/-/compare/8deaea34ae3e0fbe2914eca6abc47d1adf37dcb3...298d10606b1a476ed97b42ff96dd429f13761918

-- 
View it on GitLab: 
https://code.videolan.org/videolan/vlc/-/compare/8deaea34ae3e0fbe2914eca6abc47d1adf37dcb3...298d10606b1a476ed97b42ff96dd429f13761918
You're receiving this email because of your account on code.videolan.org.


VideoLAN code repository instance
_______________________________________________
vlc-commits mailing list
vlc-commits@videolan.org
https://mailman.videolan.org/listinfo/vlc-commits

Reply via email to