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