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


Commits:
03b0de26 by Fatih Uzunoglu at 2025-08-16T07:40:59+00:00
Reapply "qml: use QSGTextureView (TextureProviderItem) in PartialEffect 
instead of layering"

This reverts commit 9fe08fbfa3af0fea704cf04ad6e4feb404166a8d.

- - - - -
4b0cec9f by Fatih Uzunoglu at 2025-08-16T07:40:59+00:00
qt: introduce `DualKawaseBlur.frag`

- - - - -
8cb040c9 by Fatih Uzunoglu at 2025-08-16T07:40:59+00:00
qt: introduce `TextureProviderObserver`

- - - - -
4ef5fad5 by Fatih Uzunoglu at 2025-08-16T07:40:59+00:00
qt: register `TextureProviderObserver`

- - - - -
6487fb62 by Fatih Uzunoglu at 2025-08-16T07:40:59+00:00
qt: introduce `DualKawaseBlur.qml`

- - - - -
6f08b8d6 by Fatih Uzunoglu at 2025-08-16T07:40:59+00:00
qml: use `DualKawaseBlur` in `FrostedGlassEffect`

- - - - -
27ea3c6c by Fatih Uzunoglu at 2025-08-16T07:40:59+00:00
qml: use `DualKawaseBlur` in `Player`

- - - - -
c94ce1ca by Fatih Uzunoglu at 2025-08-16T07:40:59+00:00
qml: use `DualKawaseBlur` in `ArtistTopBanner`

- - - - -
a253e6c9 by Fatih Uzunoglu at 2025-08-16T07:40:59+00:00
qml: use `DualKawaseBlur` in `ModalDialog`

- - - - -
004e3c57 by Fatih Uzunoglu at 2025-08-16T07:40:59+00:00
qml: do not delay `textureSubRect` in `PartialEffect`

- - - - -
1b36007f by Fatih Uzunoglu at 2025-08-16T07:40:59+00:00
qml: get rid of `BlurEffect`

- - - - -
e558cb55 by Fatih Uzunoglu at 2025-08-16T07:40:59+00:00
qt: do not use `Qt5Compat.GraphicalEffects` anymore

- - - - -
047f56d0 by Fatih Uzunoglu at 2025-08-16T07:40:59+00:00
qt: do not use `QtQuick.Effects` anymore

- - - - -


23 changed files:

- configure.ac
- modules/gui/qt/Makefile.am
- modules/gui/qt/dialogs/dialogs/qml/ModalDialog.qml
- modules/gui/qt/maininterface/mainui.cpp
- modules/gui/qt/maininterface/qml/MainDisplay.qml
- modules/gui/qt/maininterface/qml/MainInterface.qml
- modules/gui/qt/medialibrary/qml/ArtistTopBanner.qml
- modules/gui/qt/meson.build
- modules/gui/qt/player/qml/Player.qml
- modules/gui/qt/plugins.hpp
- modules/gui/qt/qt.cpp
- + modules/gui/qt/shaders/DualKawaseBlur.frag
- + modules/gui/qt/shaders/DualKawaseBlur_downsample.frag
- + modules/gui/qt/shaders/DualKawaseBlur_upsample.frag
- modules/gui/qt/shaders/SubTexture.vert
- modules/gui/qt/shaders/meson.build
- modules/gui/qt/shaders/shaders.qrc
- + modules/gui/qt/util/textureproviderobserver.cpp
- modules/gui/qt/widgets/qml/BlurEffect.qml → 
modules/gui/qt/util/textureproviderobserver.hpp
- + modules/gui/qt/widgets/qml/DualKawaseBlur.qml
- modules/gui/qt/widgets/qml/FrostedGlassEffect.qml
- modules/gui/qt/widgets/qml/PartialEffect.qml
- − modules/gui/qt/widgets/qml/compat/BlurEffect.qml


Changes:

=====================================
configure.ac
=====================================
@@ -4151,11 +4151,6 @@ AS_IF([test "${enable_qt}" != "no"], [
 
       AC_MSG_CHECKING([if required Qt plugins are installed with ${QMAKE6} and 
conf ${with_qtconf}])
 
-      AS_IF([test "${have_qt65}" = "yes"], [
-        qt_qml_effects_module="QtQuick.Effects"
-      ], [
-        qt_qml_effects_module="Qt5Compat.GraphicalEffects"
-      ])
       AS_IF([${PYTHON3} ${srcdir}/buildsystem/check_qml_module.py \
             --qmake "${QMAKE6}" \
             --qtconf "${with_qtconf}" \
@@ -4165,7 +4160,6 @@ AS_IF([test "${enable_qt}" != "no"], [
             QtQuick.Layouts="" \
             QtQuick.Window="" \
             QtQuick.Controls="" \
-            ${qt_qml_effects_module}="" \
             >&AS_MESSAGE_FD ], [
         AC_MSG_RESULT([yes])
       ],[


=====================================
modules/gui/qt/Makefile.am
=====================================
@@ -343,6 +343,8 @@ libqt_plugin_la_SOURCES = \
        util/vlcqtmessagehandler.hpp \
        util/colorizedsvgicon.cpp \
        util/colorizedsvgicon.hpp \
+       util/textureproviderobserver.cpp \
+       util/textureproviderobserver.hpp \
        widgets/native/animators.cpp \
        widgets/native/animators.hpp \
        widgets/native/customwidgets.cpp widgets/native/customwidgets.hpp \
@@ -501,6 +503,7 @@ nodist_libqt_plugin_la_SOURCES = \
        util/list_selection_model.moc.cpp \
        util/vlchotkeyconverter.moc.cpp \
        util/qsgtextureview.moc.cpp \
+       util/textureproviderobserver.moc.cpp \
        widgets/native/animators.moc.cpp \
        widgets/native/csdthemeimage.moc.cpp \
        widgets/native/customwidgets.moc.cpp \
@@ -1305,14 +1308,8 @@ libqml_module_widgets_a_QML = \
        widgets/qml/FastBlend.qml \
        widgets/qml/RadioButtonExt.qml \
        widgets/qml/RoundedRectangleShadow.qml \
-       widgets/qml/VoronoiSnow.qml
-if HAVE_QT65
-libqml_module_widgets_a_QML += \
-       widgets/qml/BlurEffect.qml
-else
-libqml_module_widgets_a_QML += \
-       widgets/qml/compat/BlurEffect.qml
-endif
+       widgets/qml/VoronoiSnow.qml \
+       widgets/qml/DualKawaseBlur.qml
 nodist_libqml_module_widgets_a_SOURCES = widgets_qmlassets.cpp
 $(libqml_module_widgets_a_QML:.qml=.cpp) : $(builddir)/widgets/res.qrc
 $(libqml_module_widgets_a_QML:.qml=.cpp) : 
QML_CACHEGEN_ARGS=--resource=$(builddir)/widgets/res.qrc
@@ -1362,7 +1359,9 @@ libqt_plugin_la_SHADER := shaders/FadingEdge.frag \
                           shaders/FastBlend_screen.frag \
                           shaders/RoundedRectangleShadow.frag \
                           shaders/RoundedRectangleShadow_hollow.frag \
-                          shaders/VoronoiSnow.frag
+                          shaders/VoronoiSnow.frag \
+                          shaders/DualKawaseBlur_upsample.frag \
+                          shaders/DualKawaseBlur_downsample.frag
 if ENABLE_QT
 
 libqt_plugin_la_LIBADD += libqml_module_dialogs.a \


=====================================
modules/gui/qt/dialogs/dialogs/qml/ModalDialog.qml
=====================================
@@ -53,7 +53,7 @@ Dialog {
     }
 
     Overlay.modal: Item {
-        Widgets.BlurEffect {
+        Widgets.DualKawaseBlur {
             anchors.fill: parent
             anchors.topMargin: MainCtx.windowExtendedMargin
             anchors.leftMargin: MainCtx.windowExtendedMargin
@@ -65,7 +65,7 @@ Dialog {
                 live: true
                 hideSource: true
             }
-            radius: 12
+            radius: 3
         }
     }
 


=====================================
modules/gui/qt/maininterface/mainui.cpp
=====================================
@@ -48,6 +48,7 @@
 #include "util/vlctick.hpp"
 #include "util/list_selection_model.hpp"
 #include "util/ui_notifier.hpp"
+#include "util/textureproviderobserver.hpp"
 
 #include "dialogs/help/aboutmodel.hpp"
 #include "dialogs/dialogs_provider.hpp"
@@ -363,6 +364,7 @@ void MainUI::registerQMLTypes()
         qmlRegisterType<FlickableScrollHandler>( uri, versionMajor, 
versionMinor, "FlickableScrollHandler" );
         qmlRegisterType<ListSelectionModel>( uri, versionMajor, versionMinor, 
"ListSelectionModel" );
         qmlRegisterType<DoubleClickIgnoringItem>( uri, versionMajor, 
versionMinor, "DoubleClickIgnoringItem" );
+        qmlRegisterType<TextureProviderObserver>( uri, versionMajor, 
versionMinor, "TextureProviderObserver" );
 
         qmlRegisterModule(uri, versionMajor, versionMinor);
         qmlProtectModule(uri, versionMajor);
@@ -421,14 +423,4 @@ void MainUI::registerQMLTypes()
         qmlRegisterModule(uri, versionMajor, versionMinor);
         qmlProtectModule(uri, versionMajor);
     }
-
-#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0)
-    // Dummy QtQuick.Effects module
-    qmlRegisterModule("QtQuick.Effects", 0, 0);
-    // Do not protect, types can still be registered.
-#else
-    // Dummy Qt5Compat.GraphicalEffects module
-    qmlRegisterModule("Qt5Compat.GraphicalEffects", 0, 0);
-    // Do not protect, types can still be registered.
-#endif
 }


=====================================
modules/gui/qt/maininterface/qml/MainDisplay.qml
=====================================
@@ -263,16 +263,18 @@ FocusScope {
                                         width,
                                         height - stackView.height)
 
-                    effectLayer.effect: Component {
-                        Widgets.FrostedGlassEffect {
-                            ColorContext {
-                                id: frostedTheme
-                                palette: VLCStyle.palette
-                                colorSet: ColorContext.Window
-                            }
+                    effect: frostedGlassEffect
+
+                    Widgets.FrostedGlassEffect {
+                        id: frostedGlassEffect
 
-                            tint: frostedTheme.bg.secondary
+                        ColorContext {
+                            id: frostedTheme
+                            palette: VLCStyle.palette
+                            colorSet: ColorContext.Window
                         }
+
+                        tint: frostedTheme.bg.secondary
                     }
                 }
 


=====================================
modules/gui/qt/maininterface/qml/MainInterface.qml
=====================================
@@ -25,9 +25,6 @@ import QtQuick.Layouts
 import QtQuick.Templates
 import QtQuick.Controls
 import QtQuick.Window
-import QtQuick.Effects // Unconditionally available, dummy if Qt version is 
less than 6.5.0
-import Qt5Compat.GraphicalEffects // Unconditionally available, dummy if Qt 
version is equal to or greater than 6.5.0
-
 
 import VLC.MainInterface
 import VLC.Widgets as Widgets


=====================================
modules/gui/qt/medialibrary/qml/ArtistTopBanner.qml
=====================================
@@ -62,17 +62,8 @@ FocusScope {
         sourceSize: artist.cover ? 
Qt.size(Helpers.alignUp(Screen.desktopAvailableWidth, 32), 0)
                                  : undefined
         mipmap: !!artist.cover
-        // Fill mode is stretch when blur effect is used, otherwise an 
implicit layer is created.
-        // Having the fill mode stretch does not have a side effect here, 
because source size
-        // is still calculated as to preserve the aspect ratio as height is 
left empty (0) and the
-        // image is not shown stretched because it is invisible when blur 
effect is used:
-        // "If only one dimension of the size is set to greater than 0, the 
other dimension is
-        //  set in proportion to preserve the source image's aspect ratio. 
(The fillMode is
-        //  independent of this.)". Unfortunately with old Qt versions we can 
not do this
-        // because it does not seem to create a layer when fill mode (tiling 
is wanted)
-        // is changed at a later point.
-        fillMode: artist.cover ? ((visible || (MainCtx.qtVersion() < 
MainCtx.qtVersionCheck(6, 5, 0))) ? Image.PreserveAspectCrop : Image.Stretch)
-                                : Image.Tile
+
+        fillMode: artist.cover ? Image.PreserveAspectCrop : Image.Tile
 
         visible: !blurEffect.visible
         cache: (source === VLCStyle.noArtArtist)
@@ -86,12 +77,9 @@ FocusScope {
     Item {
         anchors.fill: background
 
-        // The texture is big, and the blur item should only draw the portion 
of it.
-        // If the blur effect creates an implicit layer, it properly adjusts 
the
-        // area that it needs to cover. However, as we don't want an additional
-        // layer that keeps getting updated every time the size changes, we 
feed
-        // the whole static texture. For that reason, we need clipping because
-        // the blur effect is applied to the whole texture and shown as whole:
+        // TODO: Clipping should not be necessary, we can crop like
+        //       `ImageExt` is doing for `PreserveAspectCrop`, but
+        //       `DualKawaseBlur` does not support that for now.
         clip: !blurEffect.sourceNeedsLayering
 
         visible: (GraphicsInfo.shaderType === GraphicsInfo.RhiShader)
@@ -100,24 +88,50 @@ FocusScope {
         // each time the size changes. The source texture is static, so the 
blur
         // is applied only once and we adjust the viewport through the parent 
item
         // with clipping.
-        Widgets.BlurEffect {
+        Widgets.DualKawaseBlur {
             id: blurEffect
 
             anchors.verticalCenter: parent.verticalCenter
             anchors.left: parent.left
             anchors.right: parent.right
 
+            // TODO: Disable `live`, consider asynchronous loading and size 
changes.
+
             // If source image is tiled, layering is necessary:
-            readonly property bool sourceNeedsLayering: (background.fillMode 
!== Image.Stretch) ||
-                                                        (MainCtx.qtVersion() < 
MainCtx.qtVersionCheck(6, 5, 0))
+            readonly property bool sourceNeedsLayering: (background.fillMode 
=== Image.Tile)
 
             readonly property real aspectRatio: (background.implicitHeight / 
background.implicitWidth)
 
             height: sourceNeedsLayering ? background.height : (aspectRatio * 
width)
 
-            source: background
+            source: textureProviderItem
+
+            Widgets.TextureProviderItem {
+                id: textureProviderItem
+
+                // Like in `Player.qml`, this is used because when the source 
is
+                // mipmapped, sometimes it can not be sampled. This is 
considered
+                // a Qt bug, but `QSGTextureView` has a workaround for that. 
So,
+                // we can have an indirection here through 
`TextureProviderItem`.
+                // This is totally acceptable as there is virtually no 
overhead.
+
+                source: blurEffect.sourceNeedsLayering ? backgroundLayer : 
background
+            }
+
+            ShaderEffectSource {
+                id: backgroundLayer
+
+                sourceItem: background
+
+                // We hope that Qt does not create the layer unless this is 
actually used as
+                // texture provider (it is already invisible). GammaRay tells 
that this is
+                // already the case (I can not access the texture).
+                visible: false
+            }
 
-            radius: VLCStyle.dp(4, VLCStyle.scale)
+            // Strong blurring is not wanted here:
+            configuration: Widgets.DualKawaseBlur.Configuration.TwoPass
+            radius: 1
         }
     }
 


=====================================
modules/gui/qt/meson.build
=====================================
@@ -146,6 +146,7 @@ moc_headers = files(
     'util/dismiss_popup_event_filter.hpp',
     'util/list_selection_model.hpp',
     'util/qsgtextureview.hpp',
+    'util/textureproviderobserver.hpp',
     'widgets/native/animators.hpp',
     'widgets/native/csdthemeimage.hpp',
     'widgets/native/customwidgets.hpp',
@@ -479,6 +480,8 @@ qt_plugin_sources = files(
     'util/qsgroundedrectangularimagenode.hpp',
     'util/qsgtextureview.cpp',
     'util/qsgtextureview.hpp',
+    'util/textureproviderobserver.cpp',
+    'util/textureproviderobserver.hpp',
     'util/dismiss_popup_event_filter.cpp',
     'util/dismiss_popup_event_filter.hpp',
     'util/list_selection_model.cpp',
@@ -805,12 +808,6 @@ qml_modules += {
     ),
 }
 
-if qt6_dep.version().version_compare('>=6.5.0')
-    qml_blureffect_file = 'widgets/qml/BlurEffect.qml'
-else
-    qml_blureffect_file = 'widgets/qml/compat/BlurEffect.qml'
-endif
-
 qml_modules += {
     'name' : 'VLC.Widgets',
     'target' : 'widgets',
@@ -896,13 +893,13 @@ qml_modules += {
         'widgets/qml/PartialEffect.qml',
         'widgets/qml/ViewHeader.qml',
         'widgets/qml/ProgressIndicator.qml',
-        qml_blureffect_file,
         'widgets/qml/ImageExt.qml',
         'widgets/qml/ScrollBarExt.qml',
         'widgets/qml/FastBlend.qml',
         'widgets/qml/RadioButtonExt.qml',
         'widgets/qml/RoundedRectangleShadow.qml',
         'widgets/qml/VoronoiSnow.qml',
+        'widgets/qml/DualKawaseBlur.qml',
     ),
 }
 


=====================================
modules/gui/qt/player/qml/Player.qml
=====================================
@@ -283,11 +283,13 @@ FocusScope {
                         colorSet: ColorContext.View
                     }
 
-                    Widgets.BlurEffect {
+                    Widgets.DualKawaseBlur {
                         id: blurredBackground
 
-                        radius: 64
+                        radius: 3
 
+                        // TODO: Disable `live`, consider asynchronous loading.
+                        
                         //destination aspect ratio
                         readonly property real dar: parent.width / 
parent.height
 
@@ -298,23 +300,15 @@ FocusScope {
                         source: textureProviderItem
 
                         Widgets.TextureProviderItem {
-                            // Texture indirection to fool Qt into not 
creating an implicit
-                            // ShaderEffectSource because source image does 
not have fill mode
-                            // stretch. "If needed, MultiEffect will 
internally generate a
-                            // ShaderEffectSource as the texture source.": Qt 
creates a layer
-                            // if source has children, source is image and 
does not have
-                            // fill mode stretch or source size is null. In 
this case,
-                            // we really don't need Qt to create an implicit 
layer.
-
-                            // Note that this item does not create a new 
texture, it simply
-                            // represents the source image provider.
                             id: textureProviderItem
 
-                            // Do not set textureSubRect, because we don't 
want blur to be
-                            // updated everytime the viewport changes. It is 
better to have
-                            // the static source texture blurred once, and 
adjust the blur
-                            // than to blur each time the viewport changes.
-
+                            // This should not be necessary anymore since 
`DualKawaseBlur`
+                            // does not create layer for the source implicitly 
as `MultiEffect`
+                            // or `FastBlur` does as they deem necessary (in 
this case, it
+                            // is not necessary). But due to a Qt bug when 
`mipmap: true` is
+                            // used where texture sampling becomes broken, we 
need this as
+                            // `QSGTextureView` has a workaround for that bug. 
This is totally
+                            // acceptable as there is virtually no overhead.
                             source: cover
                         }
 


=====================================
modules/gui/qt/plugins.hpp
=====================================
@@ -51,12 +51,6 @@
     Q_IMPORT_QML_PLUGIN(QtQuickLayoutsPlugin)
     Q_IMPORT_QML_PLUGIN(QtQuick_WindowPlugin)
     Q_IMPORT_QML_PLUGIN(QtQuickTemplates2Plugin)
-#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
-    Q_IMPORT_QML_PLUGIN(QtQuickEffectsPlugin)
-#else
-    Q_IMPORT_QML_PLUGIN(QtGraphicalEffectsPlugin)
-    Q_IMPORT_QML_PLUGIN(QtGraphicalEffectsPrivatePlugin)
-#endif
     Q_IMPORT_QML_PLUGIN(QtQmlModelsPlugin)
     Q_IMPORT_QML_PLUGIN(QtQmlPlugin)
     Q_IMPORT_QML_PLUGIN(QtQmlWorkerScriptPlugin)


=====================================
modules/gui/qt/qt.cpp
=====================================
@@ -855,31 +855,6 @@ static void *Thread( void *obj )
     Q_INIT_RESOURCE( qmake_QtQuick_Controls_Basic_impl );
     Q_INIT_RESOURCE( qmake_QtQuick_Layouts );
     Q_INIT_RESOURCE( qmake_QtQuick_Templates );
-#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
-    // Qt Quick Effects:
-    Q_INIT_RESOURCE( qmake_QtQuick_Effects );
-    Q_INIT_RESOURCE( effects );
-    Q_INIT_RESOURCE( multieffect_shaders1 );
-    Q_INIT_RESOURCE( multieffect_shaders2 );
-    Q_INIT_RESOURCE( multieffect_shaders3 );
-    Q_INIT_RESOURCE( multieffect_shaders4 );
-    Q_INIT_RESOURCE( multieffect_shaders6 );
-    Q_INIT_RESOURCE( multieffect_shaders8 );
-    Q_INIT_RESOURCE( multieffect_shaders9 );
-    Q_INIT_RESOURCE( multieffect_shaders12 );
-    Q_INIT_RESOURCE( multieffect_shaders14 );
-    Q_INIT_RESOURCE( multieffect_shaders15 );
-    Q_INIT_RESOURCE( multieffect_shaders18 );
-    Q_INIT_RESOURCE( multieffect_shaders20 );
-    Q_INIT_RESOURCE( multieffect_shaders21 );
-    Q_INIT_RESOURCE( multieffect_shaders24 );
-#else
-    Q_INIT_RESOURCE( qmake_Qt5Compat_GraphicalEffects );
-    Q_INIT_RESOURCE( qmake_Qt5Compat_GraphicalEffects_private );
-    Q_INIT_RESOURCE( qtgraphicaleffectsplugin_raw_qml_0 );
-    Q_INIT_RESOURCE( qtgraphicaleffectsprivate_raw_qml_0 );
-    Q_INIT_RESOURCE( qtgraphicaleffectsshaders );
-#endif
 
 #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
     Q_INIT_RESOURCE( QuickControls2Basic_raw_qml_0 );


=====================================
modules/gui/qt/shaders/DualKawaseBlur.frag
=====================================
@@ -0,0 +1,102 @@
+#version 440
+
+/*****************************************************************************
+ * Copyright (C) 2025 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * ( at your option ) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, 
USA.
+ *****************************************************************************/
+
+#extension GL_GOOGLE_include_directive : enable
+
+#include "Common.glsl"
+
+layout(location = 0) in vec2 qt_TexCoord0;
+layout(location = 0) out vec4 fragColor;
+
+layout(std140, binding = 0) uniform qt_buf {
+    mat4 qt_Matrix;
+    float qt_Opacity;
+
+    vec4 normalRect; // unused, but Qt needs it as it used in first-pass 
vertex shader for sub-texturing (Qt bug?)
+    vec2 sourceTextureSize;
+    int radius;
+};
+
+layout(binding = 1) uniform sampler2D source;
+
+// "Dual Kawase"
+// SIGGRAPH 2015, "Bandwidth Efficient Rendering", Marius Bjorge (ARM)
+// 
https://community.arm.com/cfs-file/__key/communityserver-blogs-components-weblogfiles/00-00-00-20-66/siggraph2015_2D00_mmg_2D00_marius_2D00_notes.pdf
+/// <dualkawaseblur>
+
+#ifdef UPSAMPLE
+#define SAMPLE upsample
+vec4 upsample(vec2 uv, vec2 halfpixel)
+{
+    vec4 sum = fromPremult(texture(source, uv + vec2(-halfpixel.x * 2.0, 
0.0)));
+    sum += fromPremult(texture(source, uv + vec2(-halfpixel.x, halfpixel.y))) 
* 2.0;
+    sum += fromPremult(texture(source, uv + vec2(0.0, halfpixel.y * 2.0)));
+    sum += fromPremult(texture(source, uv + vec2(halfpixel.x, halfpixel.y))) * 
2.0;
+    sum += fromPremult(texture(source, uv + vec2(halfpixel.x * 2.0, 0.0)));
+    sum += fromPremult(texture(source, uv + vec2(halfpixel.x, -halfpixel.y))) 
* 2.0;
+    sum += fromPremult(texture(source, uv + vec2(0.0, -halfpixel.y * 2.0)));
+    sum += fromPremult(texture(source, uv + vec2(-halfpixel.x, -halfpixel.y))) 
* 2.0;
+    return toPremult(sum / 12.0);
+}
+#endif
+
+#ifdef DOWNSAMPLE
+#define SAMPLE downsample
+vec4 downsample(vec2 uv, vec2 halfpixel)
+{
+    vec4 sum = fromPremult(texture(source, uv)) * 4.0;
+    sum += fromPremult(texture(source, uv - halfpixel.xy));
+    sum += fromPremult(texture(source, uv + halfpixel.xy));
+    sum += fromPremult(texture(source, uv + vec2(halfpixel.x, -halfpixel.y)));
+    sum += fromPremult(texture(source, uv - vec2(halfpixel.x, -halfpixel.y)));
+    return toPremult(sum / 8.0);
+}
+#endif
+
+/// </dualkawaseblur>
+
+void main()
+{
+    // When `supportsAtlasTextures` is set, proper `uv` is provided, even for 
arbitrary
+    // sub-textures, so we don't need to calculate here:
+    vec2 uv = qt_TexCoord0;
+
+    // We need to be careful to calculate the halfpixel properly for sub- and 
atlas textures:
+    // If sourceTextureSize is sourced from QML, such as `Image`'s implicit 
size which normally
+    // matches the texture size 1:1, if the texture is a sub-texture or atlas 
texture, the
+    // texture size would not reflect the actual texture size. For that, we 
need to divide
+    // `sourceTextureSize` by `qt_SubRect_source.zw` to get the actual texture 
size (which
+    // means the atlas size, for example). This was the case in the first 
iteration, however
+    // we started using a C++ utility class to get the texture size directly, 
so now we can
+    // use it as is. The disadvantage is that the size needs to hop through 
the QML engine,
+    // essentially SG -> QML -> SG (here), which may delay having the correct 
size here. If
+    // we use GLSL 1.30 feature `textureSize()` instead, this would not be an 
issue. Currently
+    // we can not do that because even though the shaders are written in GLSL 
4.40, we target
+    // as low as GLSL 1.20/ESSL 1.0. But maybe this is not a big deal, because 
if the size
+    // (or texture altogether) changes, `QSGTextureProvider::textureChanged()` 
may need to
+    // be processed in QML anyway (so the new size comes at the same time as 
the texture updates).
+    // TODO: Ditch targeting GLSL 1.20/ESSL 1.0, and use `(radius - 0.5) / 
textureSize(source, 0)` instead.
+    // TODO: This may be done in the vertex shader. I have not done that as 
this is a very simple
+    //       calculation, and custom vertex shader in `ShaderEffect` breaks 
batching (which is not
+    //       really important with the blur effect, so maybe it makes sense).
+    vec2 halfpixel = (radius - 0.5) / sourceTextureSize;
+
+    fragColor = SAMPLE(uv, halfpixel) * qt_Opacity; // premultiplied alpha
+}


=====================================
modules/gui/qt/shaders/DualKawaseBlur_downsample.frag
=====================================
@@ -0,0 +1,106 @@
+#version 440
+
+// WARNING: This file must be in sync with DualKawaseBlur.frag
+// TODO: Generate this shader at build time.
+#define DOWNSAMPLE
+
+/*****************************************************************************
+ * Copyright (C) 2025 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * ( at your option ) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, 
USA.
+ *****************************************************************************/
+
+#extension GL_GOOGLE_include_directive : enable
+
+#include "Common.glsl"
+
+layout(location = 0) in vec2 qt_TexCoord0;
+layout(location = 0) out vec4 fragColor;
+
+layout(std140, binding = 0) uniform qt_buf {
+    mat4 qt_Matrix;
+    float qt_Opacity;
+
+    vec4 normalRect; // unused, but Qt needs it as it used in first-pass 
vertex shader for sub-texturing (Qt bug?)
+    vec2 sourceTextureSize;
+    int radius;
+};
+
+layout(binding = 1) uniform sampler2D source;
+
+// "Dual Kawase"
+// SIGGRAPH 2015, "Bandwidth Efficient Rendering", Marius Bjorge (ARM)
+// 
https://community.arm.com/cfs-file/__key/communityserver-blogs-components-weblogfiles/00-00-00-20-66/siggraph2015_2D00_mmg_2D00_marius_2D00_notes.pdf
+/// <dualkawaseblur>
+
+#ifdef UPSAMPLE
+#define SAMPLE upsample
+vec4 upsample(vec2 uv, vec2 halfpixel)
+{
+    vec4 sum = fromPremult(texture(source, uv + vec2(-halfpixel.x * 2.0, 
0.0)));
+    sum += fromPremult(texture(source, uv + vec2(-halfpixel.x, halfpixel.y))) 
* 2.0;
+    sum += fromPremult(texture(source, uv + vec2(0.0, halfpixel.y * 2.0)));
+    sum += fromPremult(texture(source, uv + vec2(halfpixel.x, halfpixel.y))) * 
2.0;
+    sum += fromPremult(texture(source, uv + vec2(halfpixel.x * 2.0, 0.0)));
+    sum += fromPremult(texture(source, uv + vec2(halfpixel.x, -halfpixel.y))) 
* 2.0;
+    sum += fromPremult(texture(source, uv + vec2(0.0, -halfpixel.y * 2.0)));
+    sum += fromPremult(texture(source, uv + vec2(-halfpixel.x, -halfpixel.y))) 
* 2.0;
+    return toPremult(sum / 12.0);
+}
+#endif
+
+#ifdef DOWNSAMPLE
+#define SAMPLE downsample
+vec4 downsample(vec2 uv, vec2 halfpixel)
+{
+    vec4 sum = fromPremult(texture(source, uv)) * 4.0;
+    sum += fromPremult(texture(source, uv - halfpixel.xy));
+    sum += fromPremult(texture(source, uv + halfpixel.xy));
+    sum += fromPremult(texture(source, uv + vec2(halfpixel.x, -halfpixel.y)));
+    sum += fromPremult(texture(source, uv - vec2(halfpixel.x, -halfpixel.y)));
+    return toPremult(sum / 8.0);
+}
+#endif
+
+/// </dualkawaseblur>
+
+void main()
+{
+    // When `supportsAtlasTextures` is set, proper `uv` is provided, even for 
arbitrary
+    // sub-textures, so we don't need to calculate here:
+    vec2 uv = qt_TexCoord0;
+
+    // We need to be careful to calculate the halfpixel properly for sub- and 
atlas textures:
+    // If sourceTextureSize is sourced from QML, such as `Image`'s implicit 
size which normally
+    // matches the texture size 1:1, if the texture is a sub-texture or atlas 
texture, the
+    // texture size would not reflect the actual texture size. For that, we 
need to divide
+    // `sourceTextureSize` by `qt_SubRect_source.zw` to get the actual texture 
size (which
+    // means the atlas size, for example). This was the case in the first 
iteration, however
+    // we started using a C++ utility class to get the texture size directly, 
so now we can
+    // use it as is. The disadvantage is that the size needs to hop through 
the QML engine,
+    // essentially SG -> QML -> SG (here), which may delay having the correct 
size here. If
+    // we use GLSL 1.30 feature `textureSize()` instead, this would not be an 
issue. Currently
+    // we can not do that because even though the shaders are written in GLSL 
4.40, we target
+    // as low as GLSL 1.20/ESSL 1.0. But maybe this is not a big deal, because 
if the size
+    // (or texture altogether) changes, `QSGTextureProvider::textureChanged()` 
may need to
+    // be processed in QML anyway (so the new size comes at the same time as 
the texture updates).
+    // TODO: Ditch targeting GLSL 1.20/ESSL 1.0, and use `(radius - 0.5) / 
textureSize(source, 0)` instead.
+    // TODO: This may be done in the vertex shader. I have not done that as 
this is a very simple
+    //       calculation, and custom vertex shader in `ShaderEffect` breaks 
batching (which is not
+    //       really important with the blur effect, so maybe it makes sense).
+    vec2 halfpixel = (radius - 0.5) / sourceTextureSize;
+
+    fragColor = SAMPLE(uv, halfpixel) * qt_Opacity; // premultiplied alpha
+}


=====================================
modules/gui/qt/shaders/DualKawaseBlur_upsample.frag
=====================================
@@ -0,0 +1,106 @@
+#version 440
+
+// WARNING: This file must be in sync with DualKawaseBlur.frag
+// TODO: Generate this shader at build time.
+#define UPSAMPLE
+
+/*****************************************************************************
+ * Copyright (C) 2025 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * ( at your option ) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, 
USA.
+ *****************************************************************************/
+
+#extension GL_GOOGLE_include_directive : enable
+
+#include "Common.glsl"
+
+layout(location = 0) in vec2 qt_TexCoord0;
+layout(location = 0) out vec4 fragColor;
+
+layout(std140, binding = 0) uniform qt_buf {
+    mat4 qt_Matrix;
+    float qt_Opacity;
+
+    vec4 normalRect; // unused, but Qt needs it as it used in first-pass 
vertex shader for sub-texturing (Qt bug?)
+    vec2 sourceTextureSize;
+    int radius;
+};
+
+layout(binding = 1) uniform sampler2D source;
+
+// "Dual Kawase"
+// SIGGRAPH 2015, "Bandwidth Efficient Rendering", Marius Bjorge (ARM)
+// 
https://community.arm.com/cfs-file/__key/communityserver-blogs-components-weblogfiles/00-00-00-20-66/siggraph2015_2D00_mmg_2D00_marius_2D00_notes.pdf
+/// <dualkawaseblur>
+
+#ifdef UPSAMPLE
+#define SAMPLE upsample
+vec4 upsample(vec2 uv, vec2 halfpixel)
+{
+    vec4 sum = fromPremult(texture(source, uv + vec2(-halfpixel.x * 2.0, 
0.0)));
+    sum += fromPremult(texture(source, uv + vec2(-halfpixel.x, halfpixel.y))) 
* 2.0;
+    sum += fromPremult(texture(source, uv + vec2(0.0, halfpixel.y * 2.0)));
+    sum += fromPremult(texture(source, uv + vec2(halfpixel.x, halfpixel.y))) * 
2.0;
+    sum += fromPremult(texture(source, uv + vec2(halfpixel.x * 2.0, 0.0)));
+    sum += fromPremult(texture(source, uv + vec2(halfpixel.x, -halfpixel.y))) 
* 2.0;
+    sum += fromPremult(texture(source, uv + vec2(0.0, -halfpixel.y * 2.0)));
+    sum += fromPremult(texture(source, uv + vec2(-halfpixel.x, -halfpixel.y))) 
* 2.0;
+    return toPremult(sum / 12.0);
+}
+#endif
+
+#ifdef DOWNSAMPLE
+#define SAMPLE downsample
+vec4 downsample(vec2 uv, vec2 halfpixel)
+{
+    vec4 sum = fromPremult(texture(source, uv)) * 4.0;
+    sum += fromPremult(texture(source, uv - halfpixel.xy));
+    sum += fromPremult(texture(source, uv + halfpixel.xy));
+    sum += fromPremult(texture(source, uv + vec2(halfpixel.x, -halfpixel.y)));
+    sum += fromPremult(texture(source, uv - vec2(halfpixel.x, -halfpixel.y)));
+    return toPremult(sum / 8.0);
+}
+#endif
+
+/// </dualkawaseblur>
+
+void main()
+{
+    // When `supportsAtlasTextures` is set, proper `uv` is provided, even for 
arbitrary
+    // sub-textures, so we don't need to calculate here:
+    vec2 uv = qt_TexCoord0;
+
+    // We need to be careful to calculate the halfpixel properly for sub- and 
atlas textures:
+    // If sourceTextureSize is sourced from QML, such as `Image`'s implicit 
size which normally
+    // matches the texture size 1:1, if the texture is a sub-texture or atlas 
texture, the
+    // texture size would not reflect the actual texture size. For that, we 
need to divide
+    // `sourceTextureSize` by `qt_SubRect_source.zw` to get the actual texture 
size (which
+    // means the atlas size, for example). This was the case in the first 
iteration, however
+    // we started using a C++ utility class to get the texture size directly, 
so now we can
+    // use it as is. The disadvantage is that the size needs to hop through 
the QML engine,
+    // essentially SG -> QML -> SG (here), which may delay having the correct 
size here. If
+    // we use GLSL 1.30 feature `textureSize()` instead, this would not be an 
issue. Currently
+    // we can not do that because even though the shaders are written in GLSL 
4.40, we target
+    // as low as GLSL 1.20/ESSL 1.0. But maybe this is not a big deal, because 
if the size
+    // (or texture altogether) changes, `QSGTextureProvider::textureChanged()` 
may need to
+    // be processed in QML anyway (so the new size comes at the same time as 
the texture updates).
+    // TODO: Ditch targeting GLSL 1.20/ESSL 1.0, and use `(radius - 0.5) / 
textureSize(source, 0)` instead.
+    // TODO: This may be done in the vertex shader. I have not done that as 
this is a very simple
+    //       calculation, and custom vertex shader in `ShaderEffect` breaks 
batching (which is not
+    //       really important with the blur effect, so maybe it makes sense).
+    vec2 halfpixel = (radius - 0.5) / sourceTextureSize;
+
+    fragColor = SAMPLE(uv, halfpixel) * qt_Opacity; // premultiplied alpha
+}


=====================================
modules/gui/qt/shaders/SubTexture.vert
=====================================
@@ -24,10 +24,13 @@ layout(location = 0) out vec2 qt_TexCoord0;
 layout(std140, binding = 0) uniform buf {
     mat4 qt_Matrix;
     float qt_Opacity;
+
     vec4 normalRect;
 };
 
 void main() {
+    // TODO: With GLSL 1.30, we can use `textureSize()` and normalize the 
coordinate here,
+    //       rather than asking an already normalized rectangle.
     qt_TexCoord0 = normalRect.xy + normalRect.zw * qt_MultiTexCoord0;
     gl_Position = qt_Matrix * qt_Vertex;
 }


=====================================
modules/gui/qt/shaders/meson.build
=====================================
@@ -23,6 +23,8 @@ shader_sources = [
     'RoundedRectangleShadow.frag',
     'RoundedRectangleShadow_hollow.frag',
     'VoronoiSnow.frag',
+    'DualKawaseBlur_downsample.frag',
+    'DualKawaseBlur_upsample.frag',
 ]
 
 shader_files = files(shader_sources)


=====================================
modules/gui/qt/shaders/shaders.qrc
=====================================
@@ -17,5 +17,7 @@
         <file 
alias="RoundedRectangleShadow.frag.qsb">RoundedRectangleShadow.frag.qsb</file>
         <file 
alias="RoundedRectangleShadow_hollow.frag.qsb">RoundedRectangleShadow_hollow.frag.qsb</file>
         <file alias="VoronoiSnow.frag.qsb">VoronoiSnow.frag.qsb</file>
+        <file 
alias="DualKawaseBlur_upsample.frag.qsb">DualKawaseBlur_upsample.frag.qsb</file>
+        <file 
alias="DualKawaseBlur_downsample.frag.qsb">DualKawaseBlur_downsample.frag.qsb</file>
     </qresource>
 </RCC>


=====================================
modules/gui/qt/util/textureproviderobserver.cpp
=====================================
@@ -0,0 +1,110 @@
+/*****************************************************************************
+ * Copyright (C) 2025 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * ( at your option ) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, 
USA.
+ *****************************************************************************/
+#include "textureproviderobserver.hpp"
+
+#include <QSGTextureProvider>
+
+TextureProviderObserver::TextureProviderObserver(QObject *parent)
+    : QObject{parent}
+{
+
+}
+
+void TextureProviderObserver::setSource(const QQuickItem *source)
+{
+    if (m_source == source)
+        return;
+
+    {
+        QMutexLocker locker(&m_textureMutex);
+        m_textureSize = {};
+
+        if (m_source)
+        {
+            if (Q_LIKELY(m_provider))
+            {
+                disconnect(m_provider, nullptr, this, nullptr);
+                m_provider = nullptr;
+            }
+            else
+            {
+                // source changed before we got its `QSGTextureProvider`
+                disconnect(m_source, nullptr, this, nullptr);
+            }
+        }
+    }
+
+    m_source = source;
+
+    if (m_source)
+    {
+        assert(m_source->isTextureProvider());
+
+        const auto init = [this]() {
+            const auto window = m_source->window();
+            assert(window);
+
+            connect(window, &QQuickWindow::beforeSynchronizing, this, [this, 
window]() {
+                assert(m_source->window() == window);
+                assert(!m_provider);
+
+                m_provider = m_source->textureProvider(); // This can only be 
called in the rendering thread.
+                assert(m_provider);
+
+                connect(m_provider, &QSGTextureProvider::textureChanged, this, 
&TextureProviderObserver::updateTextureSize, Qt::DirectConnection);
+                connect(m_provider, &QSGTextureProvider::textureChanged, this, 
&TextureProviderObserver::textureChanged);
+
+                updateTextureSize();
+
+                emit textureChanged(); // This should be safe if QML engine 
uses auto connection (default), otherwise we need to queue ourselves.
+            }, static_cast<Qt::ConnectionType>(Qt::SingleShotConnection | 
Qt::DirectConnection));
+        };
+
+        if (m_source->window())
+            init();
+        else
+            connect(m_source, &QQuickItem::windowChanged, this, init, 
Qt::SingleShotConnection);
+    }
+
+    emit sourceChanged();
+}
+
+QSize TextureProviderObserver::textureSize() const
+{
+    QMutexLocker locker(&m_textureMutex);
+    return m_textureSize;
+}
+
+void TextureProviderObserver::updateTextureSize()
+{
+    // Better to lock as early as possible, QML can wait until the
+    // update completes to get the up-to-date size (if it requests
+    // read for any reason meanwhile).
+    QMutexLocker locker(&m_textureMutex);
+
+    if (m_provider)
+    {
+        if (const auto texture = m_provider->texture())
+        {
+            m_textureSize = texture->textureSize();
+            return;
+        }
+    }
+
+    m_textureSize = {};
+}


=====================================
modules/gui/qt/widgets/qml/BlurEffect.qml → 
modules/gui/qt/util/textureproviderobserver.hpp
=====================================
@@ -1,5 +1,5 @@
 /*****************************************************************************
- * Copyright (C) 2024 VLC authors and VideoLAN
+ * Copyright (C) 2025 VLC authors and VideoLAN
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -15,20 +15,56 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, 
USA.
  *****************************************************************************/
-import QtQuick
-import QtQuick.Effects
+#ifndef TEXTUREPROVIDEROBSERVER_HPP
+#define TEXTUREPROVIDEROBSERVER_HPP
 
-MultiEffect {
-    id: effect
+#include <QObject>
+#include <QMutex>
+#include <QPointer>
+#include <QSize>
+#include <QQuickItem>
 
-    implicitWidth: source ? Math.min(source.paintedWidth ?? Number.MAX_VALUE, 
source.width) : 0
-    implicitHeight: source ? Math.min(source.paintedHeight ?? 
Number.MAX_VALUE, source.height) : 0
+class QSGTextureProvider;
 
-    blurEnabled: true
-    blur: 1.0
+// This utility class observes a texture provider and exposes its texture's 
properties
+// so that they can be accessed in QML where texture introspection is not 
possible.
+class TextureProviderObserver : public QObject
+{
+    Q_OBJECT
 
-    // Avoid using padding, as Qt thinks that it needs to layering implicitly.
-    autoPaddingEnabled: false
+    // NOTE: source must be a texture provider item.
+    Q_PROPERTY(const QQuickItem* source MEMBER m_source WRITE setSource NOTIFY 
sourceChanged FINAL)
 
-    property alias radius: effect.blurMax
-}
+    // WARNING: Texture properties are updated in the rendering thread.
+    Q_PROPERTY(QSize textureSize READ textureSize NOTIFY textureChanged FINAL)
+
+public:
+    explicit TextureProviderObserver(QObject *parent = nullptr);
+
+    void setSource(const QQuickItem* source);
+    QSize textureSize() const;
+
+signals:
+    void sourceChanged();
+    void textureChanged();
+
+private slots:
+    void updateTextureSize();
+
+private:
+    QPointer<const QQuickItem> m_source;
+    QPointer<QSGTextureProvider> m_provider;
+
+    // It is not clear when `QSGTextureProvider::textureChanged()` can be 
signalled.
+    // If it is only signalled during SG synchronization where Qt blocks the 
GUI thread,
+    // we do not need explicit synchronization here. If it can be signalled at 
any time,
+    // we can still rely on SG synchronization (instead of explicit 
synchronization) by
+    // waiting until the next synchronization, but the delay might be more in 
that case.
+    // At the same time, the source might be living in a different window 
where the SG
+    // synchronization would not be blocking the (GUI) thread where this 
observer lives.
+    mutable QMutex m_textureMutex; // Maybe QReadWriteLock would be better.
+
+    QSize m_textureSize; // invalid by default
+};
+
+#endif // TEXTUREPROVIDEROBSERVER_HPP


=====================================
modules/gui/qt/widgets/qml/DualKawaseBlur.qml
=====================================
@@ -0,0 +1,240 @@
+/*****************************************************************************
+ * Copyright (C) 2025 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * ( at your option ) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, 
USA.
+ *****************************************************************************/
+import QtQuick
+import QtQuick.Window
+
+import VLC.Util
+
+// This item provides the novel "Dual Kawase" effect [1], which offers a very 
ideal
+// balance of quality and performance. It has been used by many applications 
since
+// its introduction in 2015, including the KWin compositor. Qt 5's `FastBlur` 
from
+// 2011, and its Qt 6 port `MultiEffect` already offers a performant blur 
effect,
+// utilizing a similar down/up sampling trick, but it does not use the 
half-pixel
+// trick, and does not work for textures in the atlas, or sub-textures 
(detaching
+// or additional layer incurs an additional buffer).
+// [1] SIGGRAPH 2015, "Bandwidth Efficient Rendering", Marius Bjorge (ARM).
+Item {
+    id: root
+
+    implicitWidth: source ? Math.min(source.paintedWidth ?? Number.MAX_VALUE, 
source.width) : 0
+    implicitHeight: source ? Math.min(source.paintedHeight ?? 
Number.MAX_VALUE, source.height) : 0
+
+    enum Configuration {
+        FourPass, // 2 downsample + 2 upsamples (3 layers/buffers)
+        TwoPass // 1 downsample + 1 upsample (1 layer/buffer)
+    }
+
+    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.
+    property bool live: true
+
+    // Do not hesitate to use an odd number for the radius, there is virtually
+    // no difference between odd or even numbers due to the halfpixel trick.
+    // The effective radius is always going to be a half-integer.
+    property int radius: 1
+
+    property bool blending: true
+
+    // source must be a texture provider item. Some items such as `Image` and
+    // `ShaderEffectSource` are inherently texture provider. Other items needs
+    // layering with either `layer.enabled: true` or `ShaderEffectSource`.
+    // We purposefully are not going to create a layer on behalf of the source
+    // here, unlike `MultiEffect` (see `hasProxySource`), because it is 
impossible
+    // to determine whether the new layer is actually wanted (when the source 
is
+    // already a texture provider), and it is very trivial to have a layer when
+    // it is wanted or necessary anyway.
+    property Item source
+
+    // Arbitrary sub-texturing (no need to be set for atlas textures):
+    // `QSGTextureView` can also be used instead of sub-texturing here.
+    property rect sourceRect
+
+    ShaderEffect {
+        id: ds1
+
+        // When downsampled, we can decrease the size here so that the layer 
occupies less VRAM:
+        width: parent.width / 2
+        height: parent.height / 2
+
+        readonly property Item source: root.source
+
+        // TODO: Instead of normalizing here, we could use GLSL 1.30's 
`textureSize()`
+        //       and normalize in the vertex shader, but we can not because we 
are
+        //       targeting GLSL 1.20/ESSL 1.0, even though the shader is 
written in
+        //       GLSL 4.40.
+        readonly property rect normalRect: (root.sourceRect.width > 0.0 && 
root.sourceRect.height > 0.0) ? Qt.rect(root.sourceRect.x / 
sourceTextureSize.width,
+                                                                               
                                    root.sourceRect.y / 
sourceTextureSize.height,
+                                                                               
                                    root.sourceRect.width / 
sourceTextureSize.width,
+                                                                               
                                    root.sourceRect.height / 
sourceTextureSize.height)
+                                                                               
                          : Qt.rect(0.0, 0.0, 0.0, 0.0)
+
+        readonly property int radius: root.radius
+
+        // TODO: We could use `textureSize()` and get rid of this, but we
+        //       can not because we are targeting GLSL 1.20/ESSL 1.0, even
+        //       though the shader is written in GLSL 4.40.
+        TextureProviderObserver {
+            id: ds1SourceObserver
+            source: ds1.source
+        }
+
+        readonly property size sourceTextureSize: ds1SourceObserver.textureSize
+
+        // cullMode: ShaderEffect.BackFaceCulling // QTBUG-136611 (Layering 
breaks culling with OpenGL)
+
+        fragmentShader: "qrc:///shaders/DualKawaseBlur_downsample.frag.qsb"
+        // Maybe we should have vertex shader unconditionally, and calculate 
the half pixel there instead of fragment shader?
+        vertexShader: (normalRect.width > 0.0 && normalRect.height > 0.0) ? 
"qrc:///shaders/SubTexture.vert.qsb"
+                                                                          : ""
+
+        visible: false
+
+        supportsAtlasTextures: true
+
+        blending: root.blending
+    }
+
+    ShaderEffectSource {
+        id: ds1layer
+
+        sourceItem: ds1
+        visible: false
+        smooth: true
+    }
+
+    ShaderEffect {
+        id: ds2
+
+        // When downsampled, we can decrease the size here so that the layer 
occupies less VRAM:
+        width: ds1.width / 2
+        height: ds1.height / 2
+
+        readonly property Item source: ds1layer
+        property rect normalRect // not necessary here, added because of the 
warning
+        readonly property int radius: root.radius
+
+        // TODO: We could use `textureSize()` and get rid of this, but we
+        //       can not because we are targeting GLSL 1.20/ESSL 1.0, even
+        //       though the shader is written in GLSL 4.40.
+        TextureProviderObserver {
+            id: ds2SourceObserver
+            source: ds2.source
+        }
+
+        readonly property size sourceTextureSize: ds2SourceObserver.textureSize
+
+        // cullMode: ShaderEffect.BackFaceCulling // QTBUG-136611 (Layering 
breaks culling with OpenGL)
+
+        visible: false
+
+        fragmentShader: "qrc:///shaders/DualKawaseBlur_downsample.frag.qsb"
+
+        supportsAtlasTextures: true
+
+        blending: root.blending
+    }
+
+    ShaderEffectSource {
+        id: ds2layer
+
+        // So that if configuration is two pass (this is not used), the buffer 
is released:
+        // This is mainly relevant for switching configuration case, as 
initially if this was
+        // 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
+
+        visible: false
+        smooth: true
+    }
+
+    ShaderEffect {
+        id: us1
+
+        width: ds2.width * 2
+        height: ds2.height * 2
+
+        readonly property Item source: ds2layer
+        property rect normalRect // not necessary here, added because of the 
warning
+        readonly property int radius: root.radius
+
+        // TODO: We could use `textureSize()` and get rid of this, but we
+        //       can not because we are targeting GLSL 1.20/ESSL 1.0, even
+        //       though the shader is written in GLSL 4.40.
+        TextureProviderObserver {
+            id: us1SourceObserver
+            source: us1.source
+        }
+
+        readonly property size sourceTextureSize: us1SourceObserver.textureSize
+
+        // cullMode: ShaderEffect.BackFaceCulling // QTBUG-136611 (Layering 
breaks culling with OpenGL)
+
+        visible: false
+
+        fragmentShader: "qrc:///shaders/DualKawaseBlur_upsample.frag.qsb"
+
+        supportsAtlasTextures: true
+
+        blending: root.blending
+    }
+
+    ShaderEffectSource {
+        id: us1layer
+
+        // So that if configuration is two pass (this is not used), the buffer 
is released:
+        // This is mainly relevant for switching configuration case, as 
initially if this was
+        // 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
+
+        visible: false
+        smooth: true
+    }
+
+    ShaderEffect {
+        id: us2
+
+        anchors.fill: parent // {us1/ds1}.size * 2
+
+        readonly property Item source: (root.configuration === 
DualKawaseBlur.Configuration.TwoPass) ? ds1layer : us1layer
+        property rect normalRect // not necessary here, added because of the 
warning
+        readonly property int radius: root.radius
+
+        // TODO: We could use `textureSize()` and get rid of this, but we
+        //       can not because we are targeting GLSL 1.20/ESSL 1.0, even
+        //       though the shader is written in GLSL 4.40.
+        TextureProviderObserver {
+            id: us2SourceObserver
+            source: us2.source
+        }
+
+        readonly property size sourceTextureSize: us2SourceObserver.textureSize
+
+        // cullMode: ShaderEffect.BackFaceCulling // QTBUG-136611 (Layering 
breaks culling with OpenGL)
+
+        fragmentShader: "qrc:///shaders/DualKawaseBlur_upsample.frag.qsb"
+
+        supportsAtlasTextures: true
+
+        blending: root.blending
+    }
+}


=====================================
modules/gui/qt/widgets/qml/FrostedGlassEffect.qml
=====================================
@@ -24,10 +24,10 @@ import VLC.Widgets as Widgets
 
 // This item can be used as a layer effect.
 // Make sure that the sampler name is set to "source" (default).
-Widgets.BlurEffect {
+Widgets.DualKawaseBlur {
     id: root
 
-    radius: 64
+    radius: 3
 
     property color tint: "transparent"
     property real tintStrength: Qt.colorEqual(tint, "transparent") ? 0.0 : 0.7
@@ -41,7 +41,15 @@ Widgets.BlurEffect {
 
         // Underlay for the blur effect:
         parent: root.source?.sourceItem ?? root.source
-        anchors.fill: parent
+
+        // Since we don't use layering for the effect area anymore,
+        // we need to restrict the filter to correspond to the effect
+        // area instead of the whole source:
+        x: root.x
+        y: root.y
+        width: root.width
+        height: root.height
+
         z: 999
 
         visible: root.tintStrength > 0.0


=====================================
modules/gui/qt/widgets/qml/PartialEffect.qml
=====================================
@@ -17,6 +17,10 @@
  *****************************************************************************/
 
 import QtQuick
+import QtQuick.Window
+
+import VLC.MainInterface
+import VLC.Widgets as Widgets
 
 // This item can be used as a layer effect.
 // The purpose of this item is to apply an effect to a partial
@@ -32,14 +36,13 @@ Item {
     // a texture provider without creating an extra layer.
     // Make sure that the sampler name is set to "source" (default) if
     // this is used as a layer effect.
-    property Item source
-
-    // Default layer properties are used except that `enabled` is set by 
default.
-    // The geometry of the effect will be adjusted to `effectRect` 
automatically.
-    property alias effectLayer: effectProxy.layer
+    property alias source: textureProviderItem.source
 
     // Rectangular area where the effect should be applied:
-    property alias effectRect: effectProxy.effectRect
+    property alias effectRect: textureProviderItem.effectRect
+
+    property Item effect
+    property string samplerName: "source"
 
     // Enable blending if background of source is not opaque.
     // This comes with a performance penalty.
@@ -58,10 +61,10 @@ Item {
 
         readonly property rect discardRect: {
             if (blending)
-                return Qt.rect(effectProxy.x / root.width,
-                               effectProxy.y / root.height,
-                               (effectProxy.x + effectProxy.width) / 
root.width,
-                               (effectProxy.y + effectProxy.height) / 
root.height)
+                return Qt.rect(textureProviderItem.x / root.width,
+                               textureProviderItem.y / root.height,
+                               (textureProviderItem.x + 
textureProviderItem.width) / root.width,
+                               (textureProviderItem.y + 
textureProviderItem.height) / root.height)
             else // If blending is not enabled, no need to make the 
normalization calculations
                 return Qt.rect(0, 0, 0, 0)
         }
@@ -76,37 +79,74 @@ Item {
         fragmentShader: blending ? "qrc:///shaders/RectFilter.frag.qsb" : ""
     }
 
-    // This item represents the region where the effect is applied.
-    // `effectLayer` only has access to the area denoted with
-    // the property `effectRect`
-    ShaderEffect {
-        id: effectProxy
+    // We use texture provider that uses QSGTextureView.
+    // QSGTextureView is able to denote a viewport that
+    // covers a certain area in the source texture.
+    // This way, we don't need to have another layer just
+    // to clip the source texture.
+    Widgets.TextureProviderItem {
+        id: textureProviderItem
 
         x: effectRect.x
         y: effectRect.y
         width: effectRect.width
         height: effectRect.height
 
-        blending: root.blending
+        readonly property Item sourceItem: source?.sourceItem ?? source
 
-        // This item is used to show effect
-        // so it is pointless to show if
-        // there is no effect to show.
-        visible: layer.enabled
+        property rect effectRect
 
-        // cullMode: ShaderEffect.BackFaceCulling // QTBUG-136611 (Layering 
breaks culling with OpenGL)
+        property real eDPR: MainCtx.effectiveDevicePixelRatio(Window.window)
 
-        property rect effectRect
+        textureSubRect: Qt.rect(effectRect.x * textureProviderItem.eDPR,
+                                effectRect.y * textureProviderItem.eDPR,
+                                effectRect.width * textureProviderItem.eDPR,
+                                effectRect.height * textureProviderItem.eDPR)
 
-        property alias source: root.source
+        onDprChanged: {
+            eDPR = MainCtx.effectiveDevicePixelRatio(Window.window)
+        }
+
+        onChildrenChanged: {
+            // Do not add visual(QQuickItem) children to this item,
+            // because Qt thinks that it needs to use implicit layering.
+            // "If needed, MultiEffect will internally generate a
+            // ShaderEffectSource as the texture source."
+            // Adding children to a texture provider item is not going
+            // make them rendered in the texture. Instead, simply add
+            // to the source. If source is layered, the source would
+            // be a `ShaderEffectSource`, in that case `sourceItem`
+            // can be used to reach to the real source.
+            console.assert(textureProviderItem.children.length === 0)
+        }
+
+        // Effect's source is sub-texture through the texture provider:
+        Binding {
+            target: root.effect
+            property: root.samplerName
+            value: textureProviderItem
+        }
 
-        readonly property rect normalRect: Qt.rect(effectRect.x / root.width,
-                                                   effectRect.y / root.height,
-                                                   effectRect.width / 
root.width,
-                                                   effectRect.height / 
root.height)
+        // Adjust the blending. Currently MultiEffect/FastBlur does not
+        // support adjusting it:
+        Binding {
+            target: root.effect
+            when: root.effect && (typeof root.effect.blending === "boolean")
+            property: "blending"
+            value: root.blending
+        }
 
-        vertexShader: "qrc:///shaders/SubTexture.vert.qsb"
+        // Positioning:
+        Binding {
+            target: root.effect
+            property: "parent"
+            value: root
+        }
 
-        layer.enabled: layer.effect
+        Binding {
+            target: root.effect
+            property: "anchors.fill"
+            value: textureProviderItem
+        }
     }
 }


=====================================
modules/gui/qt/widgets/qml/compat/BlurEffect.qml deleted
=====================================
@@ -1,29 +0,0 @@
-/*****************************************************************************
- * Copyright (C) 2024 VLC authors and VideoLAN
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * ( at your option ) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, 
USA.
- *****************************************************************************/
-import QtQuick
-import Qt5Compat.GraphicalEffects
-
-FastBlur {
-    id: effect
-
-    implicitWidth: source ? Math.min(source.paintedWidth ?? Number.MAX_VALUE, 
source.width) : 0
-    implicitHeight: source ? Math.min(source.paintedHeight ?? 
Number.MAX_VALUE, source.height) : 0
-
-    // Avoid using padding, as Qt thinks that it needs to layering implicitly.
-    transparentBorder: false
-}



View it on GitLab: 
https://code.videolan.org/videolan/vlc/-/compare/560134941dfcd56b060d96f082eab5d6e5f4c25d...047f56d00af5b25657c1eccbaff1384b1cda5d10

-- 
View it on GitLab: 
https://code.videolan.org/videolan/vlc/-/compare/560134941dfcd56b060d96f082eab5d6e5f4c25d...047f56d00af5b25657c1eccbaff1384b1cda5d10
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