diff --git a/modules/gui/qt/Makefile.am b/modules/gui/qt/Makefile.am index f6a2a0c2967226ebef3a81f9bee66f31cf56022d..52924bb3c97968fd14862821a1f3b12bf588b222 100644 --- a/modules/gui/qt/Makefile.am +++ b/modules/gui/qt/Makefile.am @@ -1353,7 +1353,13 @@ libqt_plugin_la_SHADER := shaders/FadingEdge.frag \ shaders/RectangularGlow.frag \ shaders/SDFAARoundedTexture.frag \ shaders/SDFAARoundedTexture_cropsupport.frag \ - shaders/DitheredTexture.frag + shaders/DitheredTexture.frag \ + shaders/VideoProgressBar.frag + +shaders/SDFAARoundedTexture.frag: shaders/SDF.glsl +shaders/SDFAARoundedTexture_cropsupport.frag: shaders/SDF.glsl +shaders/VideoProgressBar.frag: shaders/SDF.glsl + if ENABLE_QT libqt_plugin_la_LIBADD += libqml_module_dialogs.a \ @@ -1407,7 +1413,7 @@ QSB_PARAMS_VERBOSE_ = $(QSB_PARAMS_VERBOSE__$(AM_DEFAULT_VERBOSITY)) QSB_PARAMS_VERBOSE_0 = -s QSB_PARAMS_VERBOSE__0 = $(QSB_PARAMS_VERBOSE_0) -EXTRA_DIST += shaders/Common.glsl +EXTRA_DIST += shaders/Common.glsl shaders/SDF.glsl %.frag.qsb: %.frag shaders/Common.glsl $(qsb_verbose)PATH="$(QSB_EXTRA_PATH)$$PATH" $(QSB) $(QSB_PARAMS) $(QSB_PARAMS_VERBOSE) -o $@ $< diff --git a/modules/gui/qt/medialibrary/qml/VideoGridItem.qml b/modules/gui/qt/medialibrary/qml/VideoGridItem.qml index 9730d652a629ecea93c13c16c7c4db20c9a3caef..e286fd990de52996627340eec828efa877076c77 100644 --- a/modules/gui/qt/medialibrary/qml/VideoGridItem.qml +++ b/modules/gui/qt/medialibrary/qml/VideoGridItem.qml @@ -88,7 +88,7 @@ Widgets.GridItem { visible: (model.progress > 0) - radius: root.pictureRadius + radius: root.effectiveRadius value: Helpers.clamp(model.progress !== undefined ? model.progress : 0, 0, 1) } } diff --git a/modules/gui/qt/network/qml/NetworkGridItem.qml b/modules/gui/qt/network/qml/NetworkGridItem.qml index 1e9fe8ece5d126a053689a0372134ad8cf2aa5e7..1837abc8d67e5a9f189a1e98d87acc172e479249 100644 --- a/modules/gui/qt/network/qml/NetworkGridItem.qml +++ b/modules/gui/qt/network/qml/NetworkGridItem.qml @@ -85,7 +85,7 @@ Widgets.GridItem { visible: (model.progress ?? - 1) > 0 - radius: root.pictureRadius + radius: root.effectiveRadius value: visible ? Helpers.clamp(model.progress, 0, 1) : 0 diff --git a/modules/gui/qt/shaders/SDF.glsl b/modules/gui/qt/shaders/SDF.glsl new file mode 100644 index 0000000000000000000000000000000000000000..e8f69e44aa2fa1d57e6963bc4166f5c17e075c7b --- /dev/null +++ b/modules/gui/qt/shaders/SDF.glsl @@ -0,0 +1,27 @@ +/***************************************************************************** + * Copyright (C) 2015 Inigo Quilez + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the “Softwareâ€), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + *****************************************************************************/ + +// Signed distance function by Inigo Quilez (https://iquilezles.org/articles/distfunctions2d) +// b.x = width +// b.y = height +// r.x = roundness top-right +// r.y = roundness boottom-right +// r.z = roundness top-left +// r.w = roundness bottom-left +float sdRoundBox( in vec2 p, in vec2 b, in vec4 r ) +{ + r.xy = (p.x>0.0)?r.xy : r.zw; + r.x = (p.y>0.0)?r.x : r.y; + vec2 q = abs(p)-b+r.x; + return min(max(q.x,q.y),0.0) + length(max(q,0.0)) - r.x; +} diff --git a/modules/gui/qt/shaders/SDFAARoundedTexture.frag b/modules/gui/qt/shaders/SDFAARoundedTexture.frag index 88438b601ba273367aa1307a17e7cce64c0ccfd2..ae18ef833c4a00ce6d509640dd483cd90b99e79a 100644 --- a/modules/gui/qt/shaders/SDFAARoundedTexture.frag +++ b/modules/gui/qt/shaders/SDFAARoundedTexture.frag @@ -22,18 +22,9 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ -/***************************************************************************** - * Copyright (C) 2015 Inigo Quilez - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the “Softwareâ€), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is furnished - * to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - *****************************************************************************/ +#extension GL_GOOGLE_include_directive : enable + +#include "SDF.glsl" layout(location = 0) in vec2 qt_TexCoord0; @@ -62,21 +53,6 @@ layout(std140, binding = 0) uniform buf { layout(binding = 1) uniform sampler2D source; -// Signed distance function by Inigo Quilez (https://iquilezles.org/articles/distfunctions2d) -// b.x = width -// b.y = height -// r.x = roundness top-right -// r.y = roundness boottom-right -// r.z = roundness top-left -// r.w = roundness bottom-left -float sdRoundBox( in vec2 p, in vec2 b, in vec4 r ) -{ - r.xy = (p.x>0.0)?r.xy : r.zw; - r.x = (p.y>0.0)?r.x : r.y; - vec2 q = abs(p)-b+r.x; - return min(max(q.x,q.y),0.0) + length(max(q,0.0)) - r.x; -} - void main() { // The signed distance function works when the primitive is centered. diff --git a/modules/gui/qt/shaders/SDFAARoundedTexture_cropsupport.frag b/modules/gui/qt/shaders/SDFAARoundedTexture_cropsupport.frag index f3ba567bc40b42c415b6a265166c3497f101a304..803e37cdd8fc34da4a9cf069e490a7a0a40998cc 100644 --- a/modules/gui/qt/shaders/SDFAARoundedTexture_cropsupport.frag +++ b/modules/gui/qt/shaders/SDFAARoundedTexture_cropsupport.frag @@ -34,18 +34,9 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ -/***************************************************************************** - * Copyright (C) 2015 Inigo Quilez - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the “Softwareâ€), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is furnished - * to do so, subject to the following conditions: - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - *****************************************************************************/ +#extension GL_GOOGLE_include_directive : enable + +#include "SDF.glsl" layout(location = 0) in vec2 qt_TexCoord0; @@ -74,21 +65,6 @@ layout(std140, binding = 0) uniform buf { layout(binding = 1) uniform sampler2D source; -// Signed distance function by Inigo Quilez (https://iquilezles.org/articles/distfunctions2d) -// b.x = width -// b.y = height -// r.x = roundness top-right -// r.y = roundness boottom-right -// r.z = roundness top-left -// r.w = roundness bottom-left -float sdRoundBox( in vec2 p, in vec2 b, in vec4 r ) -{ - r.xy = (p.x>0.0)?r.xy : r.zw; - r.x = (p.y>0.0)?r.x : r.y; - vec2 q = abs(p)-b+r.x; - return min(max(q.x,q.y),0.0) + length(max(q,0.0)) - r.x; -} - void main() { // The signed distance function works when the primitive is centered. diff --git a/modules/gui/qt/shaders/VideoProgressBar.frag b/modules/gui/qt/shaders/VideoProgressBar.frag new file mode 100644 index 0000000000000000000000000000000000000000..a256e47cbb1a6f4a1c634362a861e4cddcce49bd --- /dev/null +++ b/modules/gui/qt/shaders/VideoProgressBar.frag @@ -0,0 +1,75 @@ +#version 440 + +#define ANTIALIASING + +/***************************************************************************** + * 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 "SDF.glsl" + +layout(location = 0) out vec4 fragColor; + +// NOTE: Even though there is no texture, `qt_TexCoord0` is still provided. +// This is useful, because we don't need to normalize ourselves from +// gl_FragCoord using qt_Matrix and viewport size. As there is no +// texture sampler, this is always in normal form (no need to care +// about the texture atlas): +layout(location = 0) in vec2 qt_TexCoord0; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + + float progress; + float radius; + + vec2 size; + + vec4 elapsedColor; + vec4 remainingColor; +}; + +void main() +{ + vec2 p = (size.xy * ((2.0 * qt_TexCoord0) - 1)) / size.y; + + // Signed distance: + // TODO: Round box distance function may be reducted when radius for top left and top right are always infinite. + float dist = sdRoundBox(p, vec2(size.x / size.y, 1.0), vec4(radius, 999., radius, 999.)); + + vec4 color = (qt_TexCoord0.x <= progress) ? elapsedColor : remainingColor; + +#ifdef ANTIALIASING + float softEdgeMax = fwidth(dist) * 0.75; + float softEdgeMin = -softEdgeMax; + + // Soften the outline, as recommended by the Valve paper, using smoothstep: + // "Improved Alpha-Tested Magnification for Vector Textures and Special Effects" + // NOTE: The whole texel is multiplied, because of premultiplied alpha. + float factor = smoothstep(softEdgeMin, softEdgeMax, dist); +#else + float factor = step(0.0, dist); +#endif + + // NOTE: Colors are in premultiplied alpha format + color *= (1.0 - factor); + + fragColor = color * qt_Opacity; +} diff --git a/modules/gui/qt/shaders/meson.build b/modules/gui/qt/shaders/meson.build index b23d7fc9d014d13171737c54076d0ae8ab6f37c7..714b057cb08f82d400357b2b32f1a55290b65804 100644 --- a/modules/gui/qt/shaders/meson.build +++ b/modules/gui/qt/shaders/meson.build @@ -7,21 +7,23 @@ env = environment() env.append('PATH', contrib_dir + '/bin') shader_sources = [ - 'FadingEdge.frag', - 'FadingEdgeX.vert', - 'FadingEdgeY.vert', - 'Noise.frag', - 'RectFilter.frag', - 'SubTexture.vert', - 'PlayerBlurredBackground.frag', - 'HollowRectangularGlow.frag', - 'RectangularGlow.frag', - 'SDFAARoundedTexture.frag', - 'SDFAARoundedTexture_cropsupport.frag', - 'DitheredTexture.frag' + {'shader': 'FadingEdge.frag', 'dependencies': []}, + {'shader': 'FadingEdgeX.vert', 'dependencies': []}, + {'shader': 'FadingEdgeY.vert', 'dependencies': []}, + {'shader': 'Noise.frag', 'dependencies': []}, + {'shader': 'RectFilter.frag', 'dependencies': []}, + {'shader': 'SubTexture.vert', 'dependencies': []}, + {'shader': 'PlayerBlurredBackground.frag', 'dependencies': []}, + {'shader': 'HollowRectangularGlow.frag', 'dependencies': []}, + {'shader': 'RectangularGlow.frag', 'dependencies': []}, + {'shader': 'DitheredTexture.frag', 'dependencies': []}, + {'shader': 'SDFAARoundedTexture.frag', 'dependencies': ['SDF.glsl']}, + {'shader': 'SDFAARoundedTexture_cropsupport.frag', 'dependencies': ['SDF.glsl']}, + {'shader': 'VideoProgressBar.frag', 'dependencies': ['SDF.glsl']}, ] -shader_files = files(shader_sources) +# This is populated in the for-each loop below: +shader_files = [] qt_bin_directory = qt6_dep.get_variable(pkgconfig: 'bindir', configtool: 'QT_HOST_BINS') qsb = find_program('qsb', dirs: qt_bin_directory, required: true) @@ -35,11 +37,14 @@ endif shader_targets = [] foreach shader: shader_sources - shader_target_name = shader + '.qsb' + shader_file = shader['shader'] + shader_files += files(shader_file) + + shader_target_name = shader_file + '.qsb' target = custom_target(shader_target_name, - depend_files: ['Common.glsl'], - input: shader, + depend_files: shader['dependencies'] + 'Common.glsl', + input: shader_file, output: shader_target_name, command: [qsb, qsb_params, '--output', '@OUTPUT@', '@INPUT@'] ) diff --git a/modules/gui/qt/shaders/shaders.qrc b/modules/gui/qt/shaders/shaders.qrc index 58f3ff53afec26ef30e05cdd6e8666e84dc7528c..5412a2b2966b230ba39f6db7908ca9b420940981 100644 --- a/modules/gui/qt/shaders/shaders.qrc +++ b/modules/gui/qt/shaders/shaders.qrc @@ -13,5 +13,6 @@ <file alias="SDFAARoundedTexture.frag.qsb">SDFAARoundedTexture.frag.qsb</file> <file alias="SDFAARoundedTexture_cropsupport.frag.qsb">SDFAARoundedTexture_cropsupport.frag.qsb</file> <file alias="DitheredTexture.frag.qsb">DitheredTexture.frag.qsb</file> + <file alias="VideoProgressBar.frag.qsb">VideoProgressBar.frag.qsb</file> </qresource> </RCC> diff --git a/modules/gui/qt/widgets/qml/VideoProgressBar.qml b/modules/gui/qt/widgets/qml/VideoProgressBar.qml index f10918b3b32a75a17f087f82068ad1307dae7d96..e94953cf9b338867b35fb8f79c49f01a4b440308 100644 --- a/modules/gui/qt/widgets/qml/VideoProgressBar.qml +++ b/modules/gui/qt/widgets/qml/VideoProgressBar.qml @@ -24,21 +24,61 @@ import VLC.Style Item { id: progressBar - implicitHeight: VLCStyle.dp(4, VLCStyle.scale) + // Shader effect needs double height, but it does not paint anything in the upper half: + implicitHeight: VLCStyle.dp(4, VLCStyle.scale) * (shaderEffect.readyForVisibility ? 2.0 : 1.0) - clip :true + clip: !shaderEffect.readyForVisibility property real value: 0 - property int radius: implicitHeight + property real radius: implicitHeight + + property color elapsedColor: theme.fg.primary + //FIXME: do we want it always white or do we want it to change with the theme + property color remainingColor: "white" readonly property ColorContext colorContext: ColorContext { id: theme colorSet: ColorContext.Slider } + // WARNING: We can not override QQuickItem's antialiasing + // property as readonly because Qt 6.6 marks it + // as `FINAL`... + // FIXME: The shader can be generated without + // the define that enables antialiasing. + // It should be done when the build system + // starts supporting shader defines. + antialiasing: true + + ShaderEffect { + id: shaderEffect + + anchors.fill: parent + + antialiasing: progressBar.antialiasing + + blending: radius > 0.0 + + cullMode: ShaderEffect.BackFaceCulling + + readonly property double progress: progressBar.value + readonly property double radius: progressBar.radius / (height / 2) + readonly property size size: Qt.size(width, height) + readonly property color elapsedColor: progressBar.elapsedColor + readonly property color remainingColor: progressBar.remainingColor + + visible: readyForVisibility + + readonly property bool readyForVisibility: (GraphicsInfo.shaderType === GraphicsInfo.RhiShader) + + fragmentShader: "qrc:///shaders/VideoProgressBar.frag.qsb" + } + Rectangle { id: rectangle + visible: !shaderEffect.readyForVisibility + anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom @@ -46,13 +86,14 @@ Item { height: Math.max(progressBar.radius * 2, //to have at least the proper radius applied parent.height + radius) //the top radius section should be always clipped - //FIXME do we want it always white or do we want it to change with the theme - color: "white" + color: progressBar.remainingColor radius: progressBar.radius + antialiasing: progressBar.antialiasing + gradient: Gradient { orientation: Gradient.Horizontal - GradientStop { position: progressBar.value; color: theme.fg.primary } + GradientStop { position: progressBar.value; color: progressBar.elapsedColor } GradientStop { position: progressBar.value + (1. / rectangle.width); color: rectangle.color } } }