You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

222 lines
8.1 KiB

/*****************************************************************************
* 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>
#if __has_include(<rhi/qrhi.h>) // RHI is semi-public since Qt 6.6
#define RHI_HEADER_AVAILABLE
#include <rhi/qrhi.h>
#endif
TextureProviderObserver::TextureProviderObserver(QObject *parent)
: QObject{parent}
{
}
void TextureProviderObserver::setSource(const QQuickItem *source)
{
if (m_source == source)
return;
{
m_textureSize = QSize{}; // memory order does not matter, `setSource()` is not called frequently.
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::updateProperties, Qt::DirectConnection);
updateProperties();
}, 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
{
// This is likely called in the QML/GUI thread.
// QML/GUI thread can freely block the rendering thread to the extent the time is reasonable and a
// fraction of `1/FPS`, because it is already throttled by v-sync (so it would just throttle less).
return m_textureSize.load(std::memory_order_acquire);
}
QSize TextureProviderObserver::nativeTextureSize() const
{
// This is likely called in the QML/GUI thread.
// QML/GUI thread can freely block the rendering thread to the extent the time is reasonable and a
// fraction of `1/FPS`, because it is already throttled by v-sync (so it would just throttle less).
return m_nativeTextureSize.load(std::memory_order_acquire);
}
bool TextureProviderObserver::hasAlphaChannel() const
{
// This is likely called in the QML/GUI thread.
// QML/GUI thread can freely block the rendering thread to the extent the time is reasonable and a
// fraction of `1/FPS`, because it is already throttled by v-sync (so it would just throttle less).
return m_hasAlphaChannel.load(std::memory_order_acquire);
}
bool TextureProviderObserver::hasMipmaps() const
{
// This is likely called in the QML/GUI thread.
// QML/GUI thread can freely block the rendering thread to the extent the time is reasonable and a
// fraction of `1/FPS`, because it is already throttled by v-sync (so it would just throttle less).
return m_hasMipmaps.load(std::memory_order_acquire);
}
bool TextureProviderObserver::isAtlasTexture() const
{
// This is likely called in the QML/GUI thread.
// QML/GUI thread can freely block the rendering thread to the extent the time is reasonable and a
// fraction of `1/FPS`, because it is already throttled by v-sync (so it would just throttle less).
return m_isAtlasTexture.load(std::memory_order_acquire);
}
bool TextureProviderObserver::isValid() const
{
// This is likely called in the QML/GUI thread.
// QML/GUI thread can freely block the rendering thread to the extent the time is reasonable and a
// fraction of `1/FPS`, because it is already throttled by v-sync (so it would just throttle less).
return m_isValid.load(std::memory_order_acquire);
}
void TextureProviderObserver::updateProperties()
{
// This is likely called in the rendering thread.
// Rendering thread should avoid blocking the QML/GUI thread. In this case, unlike the high precision
// timer case, it should be fine because the size may be inaccurate in the worst case until the next
// frame when the size is sampled again. In high precision timer case, accuracy is favored over
// potential stuttering.
constexpr auto memoryOrder = std::memory_order_relaxed;
if (m_provider)
{
if (const auto texture = m_provider->texture())
{
{
// SG and native texture size
// SG texture size:
const auto textureSize = texture->textureSize();
m_textureSize.store(textureSize, memoryOrder);
{
// Native texture size
const auto legacyUpdateNativeTextureSize = [&]() {
const auto ntsr = texture->normalizedTextureSubRect();
m_nativeTextureSize.store({static_cast<int>(textureSize.width() / ntsr.width()),
static_cast<int>(textureSize.height() / ntsr.height())},
memoryOrder);
};
#ifdef RHI_HEADER_AVAILABLE
const QRhiTexture* const rhiTexture = texture->rhiTexture();
if (Q_LIKELY(rhiTexture))
m_nativeTextureSize.store(rhiTexture->pixelSize(), memoryOrder);
else
legacyUpdateNativeTextureSize();
#else
legacyUpdateNativeTextureSize();
#endif
}
}
{
// Alpha channel
const bool hasAlphaChannel = texture->hasAlphaChannel();
if (m_hasAlphaChannel.exchange(hasAlphaChannel, memoryOrder) != hasAlphaChannel)
emit hasAlphaChannelChanged(hasAlphaChannel);
}
{
// Mipmaps
const bool hasMipmaps = texture->hasMipmaps();
if (m_hasMipmaps.exchange(hasMipmaps, memoryOrder) != hasMipmaps)
emit hasMipmapsChanged(hasMipmaps);
}
{
// Atlas texture
const bool isAtlasTexture = texture->isAtlasTexture();
if (m_isAtlasTexture.exchange(isAtlasTexture, memoryOrder) != isAtlasTexture)
emit isAtlasTextureChanged(isAtlasTexture);
}
if (!m_isValid.exchange(true, memoryOrder))
emit isValidChanged(true);
return;
}
}
m_textureSize.store({}, memoryOrder);
if (m_hasAlphaChannel.exchange(false, memoryOrder))
emit hasAlphaChannelChanged(false);
if (m_hasMipmaps.exchange(false, memoryOrder))
emit hasMipmapsChanged(false);
if (m_isAtlasTexture.exchange(false, memoryOrder))
emit isAtlasTextureChanged(false);
if (m_isValid.exchange(false, memoryOrder))
emit isValidChanged(false);
}