[vlc-commits] [Git][videolan/vlc][master] 5 commits: qt: introduce `exclude()` to `Common.glsl`
Steve Lhomme (@robUx4)
gitlab at videolan.org
Fri Sep 26 14:53:06 UTC 2025
Steve Lhomme pushed to branch master at VideoLAN / VLC
Commits:
d9ed33e7 by Fatih Uzunoglu at 2025-09-26T14:29:55+00:00
qt: introduce `exclude()` to `Common.glsl`
- - - - -
6535528e by Fatih Uzunoglu at 2025-09-26T14:29:55+00:00
qt: introduce fragment-shader-level postprocessing in `DualKawaseBlur.frag`
Currently supporting tinting, noise, and exclusion.
These are to be used by the frosted glass effect.
Co-authored-by: Pierre Lamot <pierre at videolabs.io>
- - - - -
046e9b82 by Fatih Uzunoglu at 2025-09-26T14:29:55+00:00
qml: expose postprocessing in `DualKawaseBlur.qml`
- - - - -
4b7e7760 by Fatih Uzunoglu at 2025-09-26T14:29:55+00:00
qml: make use of built-in postprocessing in `FrostedGlassEffect`
This also reverts the frosted glass look back to the time when
we used an additional layer to have exclusion and tint (988b7bb2).
Noise-wise, it should not have any changes.
Thanks to the custom blur effect, we can now have shader-level
adjustments as we prefer directly in the blur effect without
needing an additional layer, similar to Qt's `MultiEffect`.
This also eases the work of the scene graph (batch) renderer.
- - - - -
c666a8b0 by Fatih Uzunoglu at 2025-09-26T14:29:55+00:00
qml: make use of built-in blur postprocessing in `Player.qml`
Multiply/screen fits better, but tinting is also fine here
as we can choose pure black and white tint colors with strength
0.5, which would mathematically be the same as screen/multiply
with rgb(0.5, 0.5, 0.5).
If necessary, multiply/screen can also later be added to the
blur shader.
- - - - -
11 changed files:
- modules/gui/qt/Makefile.am
- modules/gui/qt/player/qml/Player.qml
- modules/gui/qt/shaders/Common.glsl
- modules/gui/qt/shaders/DualKawaseBlur.frag
- modules/gui/qt/shaders/DualKawaseBlur_downsample.frag
- modules/gui/qt/shaders/DualKawaseBlur_upsample.frag
- + modules/gui/qt/shaders/DualKawaseBlur_upsample_postprocess.frag
- modules/gui/qt/shaders/meson.build
- modules/gui/qt/shaders/shaders.qrc
- modules/gui/qt/widgets/qml/DualKawaseBlur.qml
- modules/gui/qt/widgets/qml/FrostedGlassEffect.qml
Changes:
=====================================
modules/gui/qt/Makefile.am
=====================================
@@ -1361,7 +1361,8 @@ libqt_plugin_la_SHADER := shaders/FadingEdge.frag \
shaders/RoundedRectangleShadow_hollow.frag \
shaders/VoronoiSnow.frag \
shaders/DualKawaseBlur_upsample.frag \
- shaders/DualKawaseBlur_downsample.frag
+ shaders/DualKawaseBlur_downsample.frag \
+ shaders/DualKawaseBlur_upsample_postprocess.frag
if ENABLE_QT
libqt_plugin_la_LIBADD += libqml_module_dialogs.a \
=====================================
modules/gui/qt/player/qml/Player.qml
=====================================
@@ -301,6 +301,10 @@ FocusScope {
source: textureProviderItem
+ postprocess: true
+ tint: bgtheme.palette.isDark ? "black" : "white"
+ tintStrength: 0.5
+
Widgets.TextureProviderItem {
id: textureProviderItem
@@ -314,15 +318,6 @@ FocusScope {
source: cover
}
- Widgets.FastBlend {
- anchors.fill: parent
-
- color: Qt.rgba(0.5, 0.5, 0.5, 1.0)
-
- mode: bgtheme.palette.isDark ? Widgets.FastBlend.Mode.Multiply // multiply makes darker
- : Widgets.FastBlend.Mode.Screen // screen (inverse multiply) makes lighter
- }
-
Component.onCompleted: {
// Blur layers are effect-size dependent, so once the user starts resizing the window (hence the effect),
// we should either momentarily turn on live, or repeatedly call `scheduleUpdate()`. Due to the optimization,
=====================================
modules/gui/qt/shaders/Common.glsl
=====================================
@@ -61,6 +61,11 @@
#define DITHERING_CUTOFF 0.01
#define DITHERING_STRENGTH 0.01
+vec4 exclude(vec4 src, vec4 dst)
+{
+ return src + dst - 2.0 * src * dst;
+}
+
vec4 fromPremult(vec4 color) {
vec4 result = vec4(0.0);
result.rgb = color.rgb / max(1.0/256.0, color.a);
=====================================
modules/gui/qt/shaders/DualKawaseBlur.frag
=====================================
@@ -32,6 +32,13 @@ layout(std140, binding = 0) uniform qt_buf {
vec4 normalRect; // unused, but Qt needs it as it used in first-pass vertex shader for sub-texturing (Qt bug?)
vec2 sourceTextureSize;
int radius;
+
+#ifdef POSTPROCESS
+ vec4 tint;
+ float tintStrength;
+ float exclusionStrength;
+ float noiseStrength;
+#endif
};
layout(binding = 1) uniform sampler2D source;
@@ -53,7 +60,12 @@ vec4 upsample(vec2 uv, vec2 halfpixel)
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);
+ sum = sum / 12.0;
+#ifdef POSTPROCESS
+ return sum;
+#else
+ return toPremult(sum);
+#endif
}
#endif
@@ -66,7 +78,12 @@ vec4 downsample(vec2 uv, vec2 halfpixel)
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);
+ sum = sum / 8.0;
+#ifdef POSTPROCESS
+ return sum;
+#else
+ return toPremult(sum);
+#endif
}
#endif
@@ -98,5 +115,23 @@ void main()
// 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
+ vec4 result = SAMPLE(uv, halfpixel);
+
+#ifdef POSTPROCESS
+ // Exclusion:
+ vec4 exclColor = vec4(exclusionStrength, exclusionStrength, exclusionStrength, 0.0);
+ result = exclude(result, exclColor);
+
+ // Tint:
+ result = mix(result, fromPremult(tint), tintStrength);
+
+ // Noise:
+ float r = rand(qt_TexCoord0) - 0.5;
+ vec4 noise = vec4(r,r,r,1.0) * noiseStrength;
+ result += noise;
+
+ result = toPremult(result);
+#endif
+
+ fragColor = result * qt_Opacity; // premultiplied alpha
}
=====================================
modules/gui/qt/shaders/DualKawaseBlur_downsample.frag
=====================================
@@ -36,6 +36,13 @@ layout(std140, binding = 0) uniform qt_buf {
vec4 normalRect; // unused, but Qt needs it as it used in first-pass vertex shader for sub-texturing (Qt bug?)
vec2 sourceTextureSize;
int radius;
+
+#ifdef POSTPROCESS
+ vec4 tint;
+ float tintStrength;
+ float exclusionStrength;
+ float noiseStrength;
+#endif
};
layout(binding = 1) uniform sampler2D source;
@@ -57,7 +64,12 @@ vec4 upsample(vec2 uv, vec2 halfpixel)
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);
+ sum = sum / 12.0;
+#ifdef POSTPROCESS
+ return sum;
+#else
+ return toPremult(sum);
+#endif
}
#endif
@@ -70,7 +82,12 @@ vec4 downsample(vec2 uv, vec2 halfpixel)
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);
+ sum = sum / 8.0;
+#ifdef POSTPROCESS
+ return sum;
+#else
+ return toPremult(sum);
+#endif
}
#endif
@@ -102,5 +119,23 @@ void main()
// 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
+ vec4 result = SAMPLE(uv, halfpixel);
+
+#ifdef POSTPROCESS
+ // Exclusion:
+ vec4 exclColor = vec4(exclusionStrength, exclusionStrength, exclusionStrength, 0.0);
+ result = exclude(result, exclColor);
+
+ // Tint:
+ result = mix(result, fromPremult(tint), tintStrength);
+
+ // Noise:
+ float r = rand(qt_TexCoord0) - 0.5;
+ vec4 noise = vec4(r,r,r,1.0) * noiseStrength;
+ result += noise;
+
+ result = toPremult(result);
+#endif
+
+ fragColor = result * qt_Opacity; // premultiplied alpha
}
=====================================
modules/gui/qt/shaders/DualKawaseBlur_upsample.frag
=====================================
@@ -36,6 +36,13 @@ layout(std140, binding = 0) uniform qt_buf {
vec4 normalRect; // unused, but Qt needs it as it used in first-pass vertex shader for sub-texturing (Qt bug?)
vec2 sourceTextureSize;
int radius;
+
+#ifdef POSTPROCESS
+ vec4 tint;
+ float tintStrength;
+ float exclusionStrength;
+ float noiseStrength;
+#endif
};
layout(binding = 1) uniform sampler2D source;
@@ -57,7 +64,12 @@ vec4 upsample(vec2 uv, vec2 halfpixel)
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);
+ sum = sum / 12.0;
+#ifdef POSTPROCESS
+ return sum;
+#else
+ return toPremult(sum);
+#endif
}
#endif
@@ -70,7 +82,12 @@ vec4 downsample(vec2 uv, vec2 halfpixel)
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);
+ sum = sum / 8.0;
+#ifdef POSTPROCESS
+ return sum;
+#else
+ return toPremult(sum);
+#endif
}
#endif
@@ -102,5 +119,23 @@ void main()
// 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
+ vec4 result = SAMPLE(uv, halfpixel);
+
+#ifdef POSTPROCESS
+ // Exclusion:
+ vec4 exclColor = vec4(exclusionStrength, exclusionStrength, exclusionStrength, 0.0);
+ result = exclude(result, exclColor);
+
+ // Tint:
+ result = mix(result, fromPremult(tint), tintStrength);
+
+ // Noise:
+ float r = rand(qt_TexCoord0) - 0.5;
+ vec4 noise = vec4(r,r,r,1.0) * noiseStrength;
+ result += noise;
+
+ result = toPremult(result);
+#endif
+
+ fragColor = result * qt_Opacity; // premultiplied alpha
}
=====================================
modules/gui/qt/shaders/DualKawaseBlur_upsample_postprocess.frag
=====================================
@@ -0,0 +1,142 @@
+#version 440
+
+// WARNING: This file must be in sync with DualKawaseBlur.frag
+// TODO: Generate this shader at build time.
+#define UPSAMPLE
+#define POSTPROCESS
+
+/*****************************************************************************
+ * 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;
+
+#ifdef POSTPROCESS
+ vec4 tint;
+ float tintStrength;
+ float exclusionStrength;
+ float noiseStrength;
+#endif
+};
+
+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;
+ sum = sum / 12.0;
+#ifdef POSTPROCESS
+ return sum;
+#else
+ return toPremult(sum);
+#endif
+}
+#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)));
+ sum = sum / 8.0;
+#ifdef POSTPROCESS
+ return sum;
+#else
+ return toPremult(sum);
+#endif
+}
+#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;
+
+ vec4 result = SAMPLE(uv, halfpixel);
+
+#ifdef POSTPROCESS
+ // Exclusion:
+ vec4 exclColor = vec4(exclusionStrength, exclusionStrength, exclusionStrength, 0.0);
+ result = exclude(result, exclColor);
+
+ // Tint:
+ result = mix(result, fromPremult(tint), tintStrength);
+
+ // Noise:
+ float r = rand(qt_TexCoord0) - 0.5;
+ vec4 noise = vec4(r,r,r,1.0) * noiseStrength;
+ result += noise;
+
+ result = toPremult(result);
+#endif
+
+ fragColor = result * qt_Opacity; // premultiplied alpha
+}
=====================================
modules/gui/qt/shaders/meson.build
=====================================
@@ -25,6 +25,7 @@ shader_sources = [
'VoronoiSnow.frag',
'DualKawaseBlur_downsample.frag',
'DualKawaseBlur_upsample.frag',
+ 'DualKawaseBlur_upsample_postprocess.frag',
]
shader_files = files(shader_sources)
=====================================
modules/gui/qt/shaders/shaders.qrc
=====================================
@@ -19,5 +19,6 @@
<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>
+ <file alias="DualKawaseBlur_upsample_postprocess.frag.qsb">DualKawaseBlur_upsample_postprocess.frag.qsb</file>
</qresource>
</RCC>
=====================================
modules/gui/qt/widgets/qml/DualKawaseBlur.qml
=====================================
@@ -39,6 +39,16 @@ Item {
TwoPass // 1 downsample + 1 upsample (1 layer/buffer)
}
+ /// <postprocess>
+ // The following property must be set in order to make other properties respected:
+ property bool postprocess: false
+
+ property alias tint: us2.tint
+ property alias tintStrength: us2.tintStrength
+ property alias noiseStrength: us2.noiseStrength
+ property alias exclusionStrength: us2.exclusionStrength
+ /// </postprocess>
+
property int configuration: DualKawaseBlur.Configuration.FourPass
// NOTE: This property is also an optimization hint. When it is false, the
@@ -58,7 +68,8 @@ Item {
// used even if it is set false here. For that reason, it should not be
// necessary to check for opacity (well, accumulated opacity can not be
// checked directly in QML anyway).
- property bool blending: (!sourceTextureIsValid || sourceTextureProviderObserver.hasAlphaChannel)
+ property bool blending: (!sourceTextureIsValid || sourceTextureProviderObserver.hasAlphaChannel ||
+ (postprocess && (tintStrength > 0.0 && tint.a < 1.0)))
// source must be a texture provider item. Some items such as `Image` and
// `ShaderEffectSource` are inherently texture provider. Other items needs
@@ -420,7 +431,13 @@ Item {
// cullMode: ShaderEffect.BackFaceCulling // QTBUG-136611 (Layering breaks culling with OpenGL)
- fragmentShader: "qrc:///shaders/DualKawaseBlur_upsample.frag.qsb"
+ property color tint: "transparent"
+ property real tintStrength: 0.0
+ property real noiseStrength: 0.0
+ property real exclusionStrength: 0.0
+
+ fragmentShader: root.postprocess ? "qrc:///shaders/DualKawaseBlur_upsample_postprocess.frag.qsb"
+ : "qrc:///shaders/DualKawaseBlur_upsample.frag.qsb"
supportsAtlasTextures: true
=====================================
modules/gui/qt/widgets/qml/FrostedGlassEffect.qml
=====================================
@@ -18,8 +18,6 @@
import QtQuick
-import VLC.Style
-import VLC.Util
import VLC.Widgets as Widgets
// This item can be used as a layer effect.
@@ -29,45 +27,8 @@ Widgets.DualKawaseBlur {
radius: 3
- property color tint: "transparent"
- property real tintStrength: Qt.colorEqual(tint, "transparent") ? 0.0 : 0.7
- property real noiseStrength: 0.02
-
- // Have a semi-opaque filter blended with the source.
- // This is intended to act as both colorization (tint),
- // and exclusion effects.
- Rectangle {
- id: filter
-
- // Underlay for the blur effect:
- parent: root.source?.sourceItem ?? root.source
-
- // 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
-
- color: Qt.alpha(root.tint, root.tintStrength)
- }
-
- ShaderEffect {
- id: noise
-
- // Overlay for the blur effect:
- anchors.fill: parent
- z: 999
-
- visible: root.noiseStrength > 0.0
-
- readonly property real strength: root.noiseStrength
-
- fragmentShader: "qrc:///shaders/Noise.frag.qsb"
- }
+ postprocess: true
+ tintStrength: Qt.colorEqual(tint, "transparent") ? 0.0 : 0.7
+ noiseStrength: 0.02
+ exclusionStrength: 0.09
}
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/520ccf8912d862f7476d787de41ef362b6a1b6e5...c666a8b0acb26092520a1646d4c03d07e40e6711
--
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/520ccf8912d862f7476d787de41ef362b6a1b6e5...c666a8b0acb26092520a1646d4c03d07e40e6711
You're receiving this email because of your account on code.videolan.org.
VideoLAN code repository instance
More information about the vlc-commits
mailing list