[vlc-commits] [Git][videolan/vlc][master] 6 commits: qt: introduce `QSGTextureView`

Steve Lhomme (@robUx4) gitlab at videolan.org
Fri Jan 24 09:03:47 UTC 2025



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
590be7ce by Fatih Uzunoglu at 2025-01-24T08:48:16+00:00
qt: introduce `QSGTextureView`

This class can be used to represent a certain sub-part of an
existing `QSGTexture`, without creating a new texture.

- - - - -
7829cbcf by Fatih Uzunoglu at 2025-01-24T08:48:16+00:00
qt: introduce `TextureProviderItem`

An invisible item that acts as a texture provider. It
uses `QSGTextureView` under the hood, and allows defining
a viewport on the source item (which needs to be texture
provider itself) with the property `textureSubRect`.

This is similar to `sourceRect` with layering. However,
this consumes no extra resources unlike layering where
it needs to allocate a frame buffer object and do offscreen
rendering (even though the source is a texture itself, it
is still not necessary).

- - - - -
e7f0ad4a by Fatih Uzunoglu at 2025-01-24T08:48:16+00:00
qt: register `TextureProviderItem`

- - - - -
2baca715 by Fatih Uzunoglu at 2025-01-24T08:48:16+00:00
qml: set children ShaderEffects' `supportsAtlasTextures` in BlurEffect

- - - - -
e476329d by Fatih Uzunoglu at 2025-01-24T08:48:16+00:00
qt: add `MainCtx::effectiveDevicePixelRatio(const QQuickWindow*)`

`Screen.devicePixelRatio` is not reported correctly on Wayland.

- - - - -
60e95ad5 by Fatih Uzunoglu at 2025-01-24T08:48:16+00:00
qml: use QSGTextureView (TextureProviderItem) in PartialEffect instead of layering

We already have the source texture (dynamic texture),
because the source is layered. Currently we are using
layering again for applying effect in the area denoted
by effect rect, however we don't need to do that.

With QSGTextureView, we can simply use the area denoted
by effect rect, without layering (thus additional frame
buffer object).

- - - - -


13 changed files:

- modules/gui/qt/Makefile.am
- modules/gui/qt/maininterface/mainctx.hpp
- modules/gui/qt/maininterface/mainui.cpp
- modules/gui/qt/maininterface/qml/MainDisplay.qml
- modules/gui/qt/meson.build
- + modules/gui/qt/util/qsgtextureview.cpp
- + modules/gui/qt/util/qsgtextureview.hpp
- + modules/gui/qt/widgets/native/textureprovideritem.cpp
- + modules/gui/qt/widgets/native/textureprovideritem.hpp
- modules/gui/qt/widgets/qml/BlurEffect.qml
- modules/gui/qt/widgets/qml/FrostedGlassEffect.qml
- modules/gui/qt/widgets/qml/PartialEffect.qml
- modules/gui/qt/widgets/qml/compat/BlurEffect.qml


Changes:

=====================================
modules/gui/qt/Makefile.am
=====================================
@@ -323,6 +323,8 @@ libqt_plugin_la_SOURCES = \
 	util/effects_image_provider.hpp \
 	util/qsgroundedrectangularimagenode.cpp \
 	util/qsgroundedrectangularimagenode.hpp \
+	util/qsgtextureview.cpp \
+	util/qsgtextureview.hpp \
 	util/dismiss_popup_event_filter.cpp \
 	util/dismiss_popup_event_filter.hpp \
 	util/list_selection_model.cpp \
@@ -345,7 +347,8 @@ libqt_plugin_la_SOURCES = \
 	widgets/native/qvlcframe.hpp \
 	widgets/native/searchlineedit.cpp widgets/native/searchlineedit.hpp \
 	widgets/native/viewblockingrectangle.cpp widgets/native/viewblockingrectangle.hpp \
-	widgets/native/doubleclickignoringitem.hpp
+	widgets/native/doubleclickignoringitem.hpp \
+	widgets/native/textureprovideritem.cpp widgets/native/textureprovideritem.hpp
 
 # Meta-object compilation
 
@@ -484,6 +487,7 @@ nodist_libqt_plugin_la_SOURCES = \
 	util/dismiss_popup_event_filter.moc.cpp \
 	util/list_selection_model.moc.cpp \
 	util/vlchotkeyconverter.moc.cpp \
+	util/qsgtextureview.moc.cpp \
 	widgets/native/animators.moc.cpp \
 	widgets/native/csdthemeimage.moc.cpp \
 	widgets/native/customwidgets.moc.cpp \
@@ -491,7 +495,8 @@ nodist_libqt_plugin_la_SOURCES = \
 	widgets/native/navigation_attached.moc.cpp \
 	widgets/native/mlfolderseditor.moc.cpp \
 	widgets/native/searchlineedit.moc.cpp \
-	widgets/native/viewblockingrectangle.moc.cpp
+	widgets/native/viewblockingrectangle.moc.cpp \
+	widgets/native/textureprovideritem.moc.cpp
 
 nodist_libqt_plugin_la_SOURCES += \
 	dialogs/extended/ui_equalizer.h \


=====================================
modules/gui/qt/maininterface/mainctx.hpp
=====================================
@@ -300,6 +300,34 @@ public:
         item->setFiltersChildMouseEvents(enable);
     }
 
+    Q_INVOKABLE qreal effectiveDevicePixelRatio(const QQuickWindow* window) {
+        if (window)
+            return window->effectiveDevicePixelRatio();
+        else
+        {
+            // This is useful when item no longer has window when getting removed from the scene,
+            // which happens when the view changes. We should return the same value so that the
+            // images are not loaded just before getting destroyed due to source size change.
+            // Different windows have the same device pixel ratio, so this should not be a
+            // problem. At the same time, don't use `QGuiApplication::devicePixelRatio()` as
+            // it is not valid on Wayland and we prefer to return 0.0 rather than an intermediate
+            // or invalid value here to intent not to load an image just to discard them afterwards.
+            const auto window = intfMainWindow();
+            const auto quickWindow = qobject_cast<QQuickWindow*>(window);
+            if (quickWindow)
+                return quickWindow->effectiveDevicePixelRatio();
+            else if (window)
+                return window->devicePixelRatio();
+        }
+
+        // Return 0.0 to indicate not to load the image, as we know that this is an intermediate
+        // situation and we don't want to load image just to discard them after there is a window.
+        // QQuickImage still loads image with (0, 0) source size, but at least we indicate our
+        // intention and this can be handled manually in the future if necessary. Currently,
+        // intfMainWindow() is always available, so in only rare cases this would return 0.0.
+        return 0.0;
+    }
+
     /**
      * @brief ask for the application to terminate
      */


=====================================
modules/gui/qt/maininterface/mainui.cpp
=====================================
@@ -60,6 +60,7 @@
 
 #include "menus/qml_menu_wrapper.hpp"
 
+#include "widgets/native/textureprovideritem.hpp"
 #include "widgets/native/csdthemeimage.hpp"
 #include "widgets/native/navigation_attached.hpp"
 #include "widgets/native/viewblockingrectangle.hpp"
@@ -349,6 +350,7 @@ void MainUI::registerQMLTypes()
         // @uri VLC.Widgets
         qmlRegisterType<CSDThemeImage>(uri, versionMajor, versionMinor, "CSDThemeImage");
         qmlRegisterType<ViewBlockingRectangle>( uri, versionMajor, versionMinor, "ViewBlockingRectangle" );
+        qmlRegisterType<TextureProviderItem>( uri, versionMajor, versionMinor, "TextureProviderItem" );
 
         qmlRegisterModule(uri, versionMajor, versionMinor);
         qmlProtectModule(uri, versionMajor);


=====================================
modules/gui/qt/maininterface/qml/MainDisplay.qml
=====================================
@@ -244,16 +244,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/meson.build
=====================================
@@ -144,6 +144,7 @@ moc_headers = files(
     'util/vlctick.hpp',
     'util/dismiss_popup_event_filter.hpp',
     'util/list_selection_model.hpp',
+    'util/qsgtextureview.hpp',
     'widgets/native/animators.hpp',
     'widgets/native/csdthemeimage.hpp',
     'widgets/native/customwidgets.hpp',
@@ -152,6 +153,7 @@ moc_headers = files(
     'widgets/native/mlfolderseditor.hpp',
     'widgets/native/searchlineedit.hpp',
     'widgets/native/viewblockingrectangle.hpp',
+    'widgets/native/textureprovideritem.hpp',
 )
 
 if host_system == 'windows'
@@ -470,6 +472,8 @@ some_sources = files(
     'util/effects_image_provider.hpp',
     'util/qsgroundedrectangularimagenode.cpp',
     'util/qsgroundedrectangularimagenode.hpp',
+    'util/qsgtextureview.cpp',
+    'util/qsgtextureview.hpp',
     'util/dismiss_popup_event_filter.cpp',
     'util/dismiss_popup_event_filter.hpp',
     'util/list_selection_model.cpp',
@@ -496,6 +500,8 @@ some_sources = files(
     'widgets/native/viewblockingrectangle.cpp',
     'widgets/native/viewblockingrectangle.hpp',
     'widgets/native/doubleclickignoringitem.hpp',
+    'widgets/native/textureprovideritem.cpp',
+    'widgets/native/textureprovideritem.hpp',
 )
 
 if host_system == 'windows'


=====================================
modules/gui/qt/util/qsgtextureview.cpp
=====================================
@@ -0,0 +1,228 @@
+/*****************************************************************************
+ * 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.
+ *****************************************************************************/
+#include "qsgtextureview.hpp"
+
+QSGTextureView::QSGTextureView(QSGTexture *texture)
+{
+    setTexture(texture);
+}
+
+QSGTexture *QSGTextureView::texture() const
+{
+    return m_texture;
+}
+
+void QSGTextureView::setTexture(QSGTexture *texture)
+{
+    if (m_texture == texture)
+        return;
+
+    if (m_texture)
+    {
+        disconnect(m_texture, nullptr, this, nullptr);
+    }
+
+    m_texture = texture;
+
+    if (texture)
+    {
+        setFiltering(texture->filtering());
+        if (!texture->isAtlasTexture())
+        {
+            // Atlas textures are not rendered if they have mipmap filtering.
+            // Normally it should not happen because atlas textures are not
+            // mipmapped, mipmap filtering should not change anything.
+            setMipmapFiltering(texture->mipmapFiltering());
+        }
+
+        setAnisotropyLevel(texture->anisotropyLevel());
+        setHorizontalWrapMode(texture->horizontalWrapMode());
+        setVerticalWrapMode(texture->verticalWrapMode());
+
+        if (texture->inherits("QSGLayer"))
+        {
+            connect(texture, SIGNAL(updateRequested()), this, SLOT(adjustNormalRect()));
+            connect(texture, SIGNAL(updateRequested()), this, SIGNAL(updateRequested()));
+        }
+    }
+
+    adjustNormalRect();
+    emit updateRequested();
+}
+
+bool QSGTextureView::adjustNormalRect() const
+{
+    if (!m_rect.isValid())
+        return false;
+
+    assert(m_texture);
+
+    QSizeF size = m_texture->textureSize();
+
+    if (size.isValid())
+    {
+        m_normalRect = QRectF(m_rect.x() / size.width(),
+                              m_rect.y() / size.height(),
+                              m_rect.width() / size.width(),
+                              m_rect.height() / size.height());
+        m_normalRectChanged = true;
+        return true;
+    }
+
+    // Do not emit `updateRequested()` here, as this method may be called
+    // from `normalizedTextureSubRect()`. At the same time, this method is
+    // called when `updateRequested()` is emitted, it should not emit the
+    // same signal. Instead, `setRect()` emits `updateRequested()` when
+    // applicable.
+
+    return false;
+}
+
+QRect QSGTextureView::rect() const
+{
+    return m_rect;
+}
+
+void QSGTextureView::setRect(const QRect &rect)
+{
+    if (m_rect == rect)
+        return;
+
+    if (!rect.isValid())
+        return;
+
+    m_rect = rect;
+
+    // We need the source texture in order to calculate the normal rect.
+    // If texture is not available when the rect is set, it will be done
+    // later in `normalizedTextureSubRect()`.
+    if (m_texture)
+    {
+        adjustNormalRect();
+        emit updateRequested();
+    }
+    else
+    {
+        // Invalidate the normal rect, so that it is calculated via
+        // `normalizedTextureSubRect()` when there is a texture:
+        m_normalRect.reset();
+    }
+}
+
+qint64 QSGTextureView::comparisonKey() const
+{
+    assert(m_texture);
+    return m_texture->comparisonKey();
+}
+
+QRhiTexture *QSGTextureView::rhiTexture() const
+{
+    assert(m_texture);
+    return m_texture->rhiTexture();
+}
+
+QSize QSGTextureView::textureSize() const
+{
+    assert(m_texture);
+    if (m_rect.isNull())
+        return m_texture->textureSize();
+    else
+        return m_rect.size();
+}
+
+bool QSGTextureView::hasAlphaChannel() const
+{
+    assert(m_texture);
+    return m_texture->hasAlphaChannel();
+}
+
+bool QSGTextureView::hasMipmaps() const
+{
+    assert(m_texture);
+    return m_texture->hasMipmaps();
+}
+
+QRectF QSGTextureView::normalizedTextureSubRect() const
+{
+    assert(m_texture);
+
+    if (!m_normalRect.has_value())
+        adjustNormalRect();
+
+    QRectF subRect = m_texture->normalizedTextureSubRect();
+
+    if (m_normalRect.has_value() && m_normalRect->isValid())
+    {
+        // Sub texture:
+        // NOTE: The source texture might be in the atlas.
+        subRect = QRectF(subRect.x() + m_normalRect->x() * subRect.width(),
+                         subRect.y() + m_normalRect->y() * subRect.height(),
+                         m_normalRect->width() * subRect.width(),
+                         m_normalRect->height() * subRect.height());
+    }
+
+    return subRect;
+}
+
+bool QSGTextureView::isAtlasTexture() const
+{
+    assert(m_texture);
+    // If Qt does not respect normalizedTextureSubRect() of a QSGTexture
+    // that does not report to be in the atlas texture, we should return
+    // true regardless. Experiments show that Qt cares about the normalized
+    // sub rect even if the texture is not in the atlas.
+    return m_texture->isAtlasTexture();
+}
+
+QSGTexture *QSGTextureView::removedFromAtlas(QRhiResourceUpdateBatch *batch) const
+{
+    Q_UNUSED(batch);
+
+    // QSGTextureView does not remove the source texture from the atlas. Removing
+    // from atlas means copying the texture, and this is not necessary. Shaders
+    // support atlas texture by having `qt_SubRect_X` uniform, or, in the case
+    // of `ShaderEffect`, `supportsAtlasTextures` is set.
+
+    return nullptr;
+}
+
+void QSGTextureView::commitTextureOperations(QRhi *rhi, QRhiResourceUpdateBatch *resourceUpdates)
+{
+    if (m_texture)
+        m_texture->commitTextureOperations(rhi, resourceUpdates);
+}
+
+bool QSGTextureView::updateTexture()
+{
+    bool ret = false;
+
+    if (const auto dynamicTexture = qobject_cast<QSGDynamicTexture*>(m_texture))
+    {
+        if (dynamicTexture->updateTexture())
+            ret = true;
+    }
+
+    if (m_normalRectChanged)
+    {
+        ret = true;
+        m_normalRectChanged = false;
+    }
+
+    return ret;
+}
+


=====================================
modules/gui/qt/util/qsgtextureview.hpp
=====================================
@@ -0,0 +1,68 @@
+/*****************************************************************************
+ * 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.
+ *****************************************************************************/
+#ifndef QSGTEXTUREVIEW_HPP
+#define QSGTEXTUREVIEW_HPP
+
+#include <QSGTexture>
+
+#include <optional>
+
+class QSGTextureView : public QSGDynamicTexture
+{
+    Q_OBJECT
+
+    QSGTexture* m_texture = nullptr;
+    QRect m_rect;
+    mutable std::optional<QRectF> m_normalRect;
+    mutable bool m_normalRectChanged = false;
+
+private slots:
+    bool adjustNormalRect() const;
+
+public:
+    QSGTextureView() = default;
+    explicit QSGTextureView(QSGTexture* texture);
+
+    QSGTexture* texture() const;
+    void setTexture(QSGTexture* texture);
+
+    // Subtexturing:
+    QRect rect() const;
+    void setRect(const QRect& rect);
+
+    qint64 comparisonKey() const override;
+    QRhiTexture *rhiTexture() const override;
+    QSize textureSize() const override;
+    bool hasAlphaChannel() const override;
+    bool hasMipmaps() const override;
+
+    QRectF normalizedTextureSubRect() const override;
+
+    bool isAtlasTexture() const override;
+
+    QSGTexture *removedFromAtlas(QRhiResourceUpdateBatch *resourceUpdates = nullptr) const override;
+
+    void commitTextureOperations(QRhi *rhi, QRhiResourceUpdateBatch *resourceUpdates) override;
+
+    bool updateTexture() override;
+
+signals:
+    void updateRequested();
+};
+
+#endif // QSGTEXTUREVIEW_HPP


=====================================
modules/gui/qt/widgets/native/textureprovideritem.cpp
=====================================
@@ -0,0 +1,239 @@
+/*****************************************************************************
+ * 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.
+ *****************************************************************************/
+#include "textureprovideritem.hpp"
+
+#include <QSGTextureProvider>
+#include <QRunnable>
+#include <QMutexLocker>
+
+class TextureProviderCleaner : public QRunnable
+{
+public:
+    explicit TextureProviderCleaner(QSGTextureProvider *textureProvider)
+        : m_textureProvider(textureProvider) { }
+
+    void run() override
+    {
+        delete m_textureProvider;
+    }
+
+private:
+    const QPointer<QSGTextureProvider> m_textureProvider;
+};
+
+TextureProviderItem::~TextureProviderItem()
+{
+    {
+        QMutexLocker lock(&m_textureProviderMutex);
+        if (m_textureProvider)
+        {
+            // https://doc.qt.io/qt-6/qquickitem.html#graphics-resource-handling
+
+            // `QQuickItem::releaseResources()` is called before the item is disassociated from its window:
+            // "This happens when the item is about to be removed from the window it was previously rendering to."
+            // Therefore, we can not have a texture provider during destruction, but not the window:
+            assert(window());
+            window()->scheduleRenderJob(new TextureProviderCleaner(m_textureProvider), QQuickWindow::BeforeSynchronizingStage);
+            m_textureProvider = nullptr;
+        }
+    }
+}
+
+bool TextureProviderItem::isTextureProvider() const
+{
+    return true;
+}
+
+QSGTextureProvider *TextureProviderItem::textureProvider() const
+{
+    // This method is called from the rendering thread.
+
+    QMutexLocker lock(&m_textureProviderMutex);
+    if (!m_textureProvider)
+    {
+        m_textureProvider = new QSGTextureViewProvider;
+
+        const auto adjustSource = [provider = m_textureProvider](const QQuickItem *source) {
+            if (source)
+            {
+                assert(source->isTextureProvider() &&
+                       "TextureProviderItem: " \
+                       "TextureProviderItem's source item is not a texture provider. " \
+                       "Layering can be enabled for the source item in order to make " \
+                       "it a texture provider.");
+
+                provider->setTextureProvider(source->textureProvider());
+            }
+            else
+            {
+                provider->setTextureProvider(nullptr);
+            }
+        };
+
+        const auto adjustRect = [provider = m_textureProvider](const QRect& rect) {
+            if (rect.isValid())
+                provider->setRect(rect);
+        };
+
+        connect(this, &TextureProviderItem::sourceChanged, m_textureProvider, adjustSource);
+        connect(this, &TextureProviderItem::rectChanged, m_textureProvider, adjustRect);
+
+        // Initial adjustments:
+        adjustSource(m_source);
+        adjustRect(m_rect);
+    }
+    return m_textureProvider;
+}
+
+void TextureProviderItem::invalidateSceneGraph()
+{
+    // https://doc.qt.io/qt-6/qquickitem.html#graphics-resource-handling
+
+    // This slot is called from the rendering thread.
+    {
+        QMutexLocker lock(&m_textureProviderMutex);
+        if (m_textureProvider)
+        {
+            delete m_textureProvider;
+        }
+    }
+}
+
+void TextureProviderItem::releaseResources()
+{
+    // https://doc.qt.io/qt-6/qquickitem.html#graphics-resource-handling
+
+    // This method is called from the GUI thread.
+
+    // QQuickItem::releaseResources() is guaranteed to have a valid window when it is called:
+    assert(window());
+    {
+        QMutexLocker lock(&m_textureProviderMutex);
+        if (m_textureProvider)
+        {
+            window()->scheduleRenderJob(new TextureProviderCleaner(m_textureProvider), QQuickWindow::BeforeSynchronizingStage);
+            m_textureProvider = nullptr;
+        }
+    }
+
+    QQuickItem::releaseResources();
+}
+
+void TextureProviderItem::itemChange(ItemChange change, const ItemChangeData &value)
+{
+    if (change == ItemDevicePixelRatioHasChanged)
+    {
+        emit dprChanged();
+    }
+
+    QQuickItem::itemChange(change, value);
+}
+
+void QSGTextureViewProvider::adjustTexture()
+{
+    if (m_textureProvider)
+        m_textureView.setTexture(m_textureProvider->texture());
+    else
+        m_textureView.setTexture(nullptr);
+
+    // `textureChanged()` is emitted implicitly, no need to emit here explicitly again.
+}
+
+QSGTextureViewProvider::QSGTextureViewProvider()
+    : QSGTextureProvider()
+{
+    connect(&m_textureView, &QSGTextureView::updateRequested, this, &QSGTextureProvider::textureChanged);
+}
+
+QSGTexture *QSGTextureViewProvider::texture() const
+{
+    if (m_textureProvider && m_textureView.texture())
+        return &m_textureView;
+    else
+        return nullptr;
+}
+
+void QSGTextureViewProvider::setTextureProvider(const QSGTextureProvider *textureProvider)
+{
+    if (m_textureProvider == textureProvider)
+        return;
+
+    if (m_textureProvider)
+        disconnect(m_textureProvider, &QSGTextureProvider::textureChanged, this, &QSGTextureViewProvider::adjustTexture);
+
+    m_textureProvider = textureProvider;
+
+    if (m_textureProvider)
+    {
+        connect(m_textureProvider, &QSGTextureProvider::textureChanged, this, &QSGTextureViewProvider::adjustTexture);
+        connect(m_textureProvider, &QObject::destroyed, this, [this]() { setTextureProvider(nullptr); });
+    }
+
+    adjustTexture();
+}
+
+void QSGTextureViewProvider::setRect(const QRect &rect)
+{
+    m_textureView.setRect(rect);
+    // `textureChanged()` is emitted implicitly, no need to emit here explicitly again
+}
+
+void QSGTextureViewProvider::setMipmapFiltering(QSGTexture::Filtering filter)
+{
+    if (m_textureView.mipmapFiltering() == filter)
+        return;
+
+    m_textureView.setMipmapFiltering(filter);
+    emit textureChanged();
+}
+
+void QSGTextureViewProvider::setFiltering(QSGTexture::Filtering filter)
+{
+    if (m_textureView.filtering() == filter)
+        return;
+
+    m_textureView.setFiltering(filter);
+    emit textureChanged();
+}
+
+void QSGTextureViewProvider::setAnisotropyLevel(QSGTexture::AnisotropyLevel level)
+{
+    if (m_textureView.anisotropyLevel() == level)
+        return;
+
+    m_textureView.setAnisotropyLevel(level);
+    emit textureChanged();
+}
+
+void QSGTextureViewProvider::setHorizontalWrapMode(QSGTexture::WrapMode hwrap)
+{
+    if (m_textureView.horizontalWrapMode() == hwrap)
+        return;
+
+    m_textureView.setHorizontalWrapMode(hwrap);
+    emit textureChanged();
+}
+
+void QSGTextureViewProvider::setVerticalWrapMode(QSGTexture::WrapMode vwrap)
+{
+    if (m_textureView.verticalWrapMode() == vwrap)
+        return;
+
+    m_textureView.setVerticalWrapMode(vwrap);
+    emit textureChanged();
+}


=====================================
modules/gui/qt/widgets/native/textureprovideritem.hpp
=====================================
@@ -0,0 +1,90 @@
+/*****************************************************************************
+ * 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.
+ *****************************************************************************/
+#ifndef TEXTUREPROVIDERITEM_HPP
+#define TEXTUREPROVIDERITEM_HPP
+
+#include <QQuickItem>
+#include <QSGTextureProvider>
+#include <QMutex>
+
+#include "util/qsgtextureview.hpp"
+
+class QSGTextureViewProvider : public QSGTextureProvider
+{
+    Q_OBJECT
+
+    mutable QSGTextureView m_textureView;
+
+    const QSGTextureProvider* m_textureProvider = nullptr;
+
+private:
+    void adjustTexture();
+
+public:
+    QSGTextureViewProvider();
+
+    QSGTexture *texture() const override;
+
+    void setTextureProvider(const QSGTextureProvider *textureProvider);
+
+    void setRect(const QRect& rect);
+
+    void setMipmapFiltering(QSGTexture::Filtering filter);
+    void setFiltering(QSGTexture::Filtering filter);
+    void setAnisotropyLevel(QSGTexture::AnisotropyLevel level);
+    void setHorizontalWrapMode(QSGTexture::WrapMode hwrap);
+    void setVerticalWrapMode(QSGTexture::WrapMode vwrap);
+};
+
+class TextureProviderItem : public QQuickItem
+{
+    Q_OBJECT
+
+    Q_PROPERTY(const QQuickItem* source MEMBER m_source NOTIFY sourceChanged FINAL)
+    Q_PROPERTY(QRect textureSubRect MEMBER m_rect NOTIFY rectChanged FINAL)
+
+    QML_ELEMENT
+public:
+    TextureProviderItem() = default;
+    virtual ~TextureProviderItem();
+
+    bool isTextureProvider() const override;
+
+    QSGTextureProvider *textureProvider() const override;
+
+public slots:
+    void invalidateSceneGraph();
+
+signals:
+    void sourceChanged(const QQuickItem *source);
+    void rectChanged(const QRect& rect);
+    void dprChanged();
+
+protected:
+    void releaseResources() override;
+    void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) override;
+
+private:
+    QPointer<const QQuickItem> m_source;
+    QRect m_rect;
+
+    mutable QPointer<QSGTextureViewProvider> m_textureProvider;
+    mutable QMutex m_textureProviderMutex; // I'm not sure if this mutex is necessary
+};
+
+#endif // TEXTUREPROVIDERITEM_HPP


=====================================
modules/gui/qt/widgets/qml/BlurEffect.qml
=====================================
@@ -27,7 +27,19 @@ MultiEffect {
     blurEnabled: true
     blur: 1.0
 
+    // Avoid using padding, as Qt thinks that it needs to layering implicitly.
     autoPaddingEnabled: false
 
     property alias radius: effect.blurMax
+
+    onChildrenChanged: {
+        for (let i in children) {
+            if (children[i] instanceof ShaderEffect) {
+                // Qt creates multiple ShaderEffect, depending
+                // on parameters. They support atlas/sub textures
+                // but Qt does not set `supportsAtlasTextures`:
+                children[i].supportsAtlasTextures = true
+            }
+        }
+    }
 }


=====================================
modules/gui/qt/widgets/qml/FrostedGlassEffect.qml
=====================================
@@ -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,77 @@ 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
+        property real eDPR: MainCtx.effectiveDevicePixelRatio(Window.window)
 
-        property rect effectRect
+        Binding on textureSubRect {
+            delayed: true
+            value: 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
=====================================
@@ -23,4 +23,18 @@ FastBlur {
 
     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
+
+    onChildrenChanged: {
+        for (let i in children) {
+            if (children[i] instanceof ShaderEffect) {
+                // Qt creates multiple ShaderEffect, depending
+                // on parameters. They support atlas/sub textures
+                // but Qt does not set `supportsAtlasTextures`:
+                children[i].supportsAtlasTextures = true
+            }
+        }
+    }
 }



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/6d8273022f08f20d59c947387351f728b6d51246...60e95ad5c2ab53dec17112fc9caab6bf52a90db8

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/6d8273022f08f20d59c947387351f728b6d51246...60e95ad5c2ab53dec17112fc9caab6bf52a90db8
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