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