| /**************************************************************************** |
| ** |
| ** Copyright (C) 2019 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtWaylandCompositor module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 or (at your option) any later version |
| ** approved by the KDE Free Qt Foundation. The licenses are as published by |
| ** the Free Software Foundation and appearing in the file LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qwltexturesharingextension_p.h" |
| |
| #include <QWaylandSurface> |
| |
| #include <QDebug> |
| |
| #include <QQuickWindow> |
| |
| #include <QPainter> |
| #include <QPen> |
| #include <QTimer> |
| |
| #include <QtGui/private/qtexturefilereader_p.h> |
| #include <QtGui/QOpenGLTexture> |
| #include <QtGui/QImageReader> |
| |
| #include <QtQuick/QSGTexture> |
| #include <QQmlContext> |
| #include <QThread> |
| |
| QT_BEGIN_NAMESPACE |
| |
| class SharedTexture : public QSGTexture |
| { |
| Q_OBJECT |
| public: |
| SharedTexture(QtWayland::ServerBuffer *buffer); |
| |
| int textureId() const override; |
| QSize textureSize() const override; |
| bool hasAlphaChannel() const override; |
| bool hasMipmaps() const override; |
| |
| void bind() override; |
| |
| private: |
| void updateGLTexture() const; |
| QtWayland::ServerBuffer *m_buffer = nullptr; |
| mutable QOpenGLTexture *m_tex = nullptr; |
| }; |
| |
| SharedTexture::SharedTexture(QtWayland::ServerBuffer *buffer) |
| : m_buffer(buffer), m_tex(nullptr) |
| { |
| } |
| |
| int SharedTexture::textureId() const |
| { |
| updateGLTexture(); |
| return m_tex ? m_tex->textureId() : 0; |
| } |
| |
| QSize SharedTexture::textureSize() const |
| { |
| updateGLTexture(); |
| return m_tex ? QSize(m_tex->width(), m_tex->height()) : QSize(); |
| } |
| |
| bool SharedTexture::hasAlphaChannel() const |
| { |
| return true; |
| } |
| |
| bool SharedTexture::hasMipmaps() const |
| { |
| updateGLTexture(); |
| return m_tex ? (m_tex->mipLevels() > 1) : false; |
| } |
| |
| void SharedTexture::bind() |
| { |
| updateGLTexture(); |
| if (m_tex) |
| m_tex->bind(); |
| } |
| |
| inline void SharedTexture::updateGLTexture() const |
| { |
| if (!m_tex && m_buffer) |
| m_tex = m_buffer->toOpenGlTexture(); |
| } |
| |
| class SharedTextureFactory : public QQuickTextureFactory |
| { |
| public: |
| SharedTextureFactory(const QtWayland::ServerBuffer *buffer) |
| : m_buffer(buffer) |
| { |
| } |
| |
| ~SharedTextureFactory() override |
| { |
| if (m_buffer && !QCoreApplication::closingDown()) |
| const_cast<QtWayland::ServerBuffer*>(m_buffer)->releaseOpenGlTexture(); |
| } |
| |
| QSize textureSize() const override |
| { |
| return m_buffer ? m_buffer->size() : QSize(); |
| } |
| |
| int textureByteCount() const override |
| { |
| return m_buffer ? (m_buffer->size().width() * m_buffer->size().height() * 4) : 0; |
| } |
| |
| QSGTexture *createTexture(QQuickWindow *) const override |
| { |
| return new SharedTexture(const_cast<QtWayland::ServerBuffer *>(m_buffer)); |
| } |
| |
| private: |
| const QtWayland::ServerBuffer *m_buffer = nullptr; |
| }; |
| |
| class SharedTextureImageResponse : public QQuickImageResponse |
| { |
| Q_OBJECT |
| public: |
| SharedTextureImageResponse(QWaylandTextureSharingExtension *extension, const QString &id) |
| : m_id(id) |
| { |
| if (extension) |
| doRequest(extension); |
| } |
| |
| void doRequest(QWaylandTextureSharingExtension *extension) |
| { |
| m_extension = extension; |
| connect(extension, &QWaylandTextureSharingExtension::bufferResult, this, &SharedTextureImageResponse::doResponse); |
| QMetaObject::invokeMethod(extension, [this] { m_extension->requestBuffer(m_id); }, Qt::AutoConnection); |
| } |
| |
| QQuickTextureFactory *textureFactory() const override |
| { |
| if (m_buffer) { |
| // qDebug() << "Creating shared buffer texture for" << m_id; |
| return new SharedTextureFactory(m_buffer); |
| } |
| // qDebug() << "Shared buffer NOT found for" << m_id; |
| m_errorString = QLatin1String("Shared buffer not found"); |
| return nullptr; |
| } |
| |
| QString errorString() const override |
| { |
| return m_errorString; |
| } |
| |
| public slots: |
| void doResponse(const QString &key, QtWayland::ServerBuffer *buffer) |
| { |
| if (key != m_id) |
| return; //somebody else's texture |
| |
| m_buffer = buffer; |
| |
| if (m_extension) |
| disconnect(m_extension, &QWaylandTextureSharingExtension::bufferResult, this, &SharedTextureImageResponse::doResponse); |
| |
| emit finished(); |
| } |
| |
| private: |
| QString m_id; |
| QWaylandTextureSharingExtension *m_extension = nullptr; |
| mutable QString m_errorString; |
| QtWayland::ServerBuffer *m_buffer = nullptr; |
| }; |
| |
| QWaylandSharedTextureProvider::QWaylandSharedTextureProvider() |
| { |
| } |
| |
| QWaylandSharedTextureProvider::~QWaylandSharedTextureProvider() |
| { |
| } |
| |
| QQuickImageResponse *QWaylandSharedTextureProvider::requestImageResponse(const QString &id, const QSize &requestedSize) |
| { |
| Q_UNUSED(requestedSize); |
| |
| // qDebug() << "Provider: got request for" << id; |
| |
| auto *extension = QWaylandTextureSharingExtension::self(); |
| auto *response = new SharedTextureImageResponse(extension, id); |
| if (!extension) |
| m_pendingResponses << response; |
| |
| return response; |
| } |
| |
| void QWaylandSharedTextureProvider::setExtensionReady(QWaylandTextureSharingExtension *extension) |
| { |
| for (auto *response : qAsConst(m_pendingResponses)) |
| response->doRequest(extension); |
| m_pendingResponses.clear(); |
| m_pendingResponses.squeeze(); |
| } |
| |
| QWaylandTextureSharingExtension *QWaylandTextureSharingExtension::s_self = nullptr; // theoretical race conditions, but OK as long as we don't delete it while we are running |
| |
| QWaylandTextureSharingExtension::QWaylandTextureSharingExtension() |
| { |
| s_self = this; |
| } |
| |
| QWaylandTextureSharingExtension::QWaylandTextureSharingExtension(QWaylandCompositor *compositor) |
| :QWaylandCompositorExtensionTemplate(compositor) |
| { |
| s_self = this; |
| } |
| |
| QWaylandTextureSharingExtension::~QWaylandTextureSharingExtension() |
| { |
| //qDebug() << Q_FUNC_INFO; |
| //dumpBufferInfo(); |
| |
| for (auto b : m_server_buffers) |
| delete b.buffer; |
| |
| if (s_self == this) |
| s_self = nullptr; |
| } |
| |
| void QWaylandTextureSharingExtension::setImageSearchPath(const QString &path) |
| { |
| m_image_dirs = path.split(QLatin1Char(';')); |
| |
| for (auto it = m_image_dirs.begin(); it != m_image_dirs.end(); ++it) |
| if (!(*it).endsWith(QLatin1Char('/'))) |
| (*it) += QLatin1Char('/'); |
| } |
| |
| void QWaylandTextureSharingExtension::initialize() |
| { |
| QWaylandCompositorExtensionTemplate::initialize(); |
| QWaylandCompositor *compositor = static_cast<QWaylandCompositor *>(extensionContainer()); |
| init(compositor->display(), 1); |
| |
| QString image_search_path = qEnvironmentVariable("QT_WAYLAND_SHAREDTEXTURE_SEARCH_PATH"); |
| if (!image_search_path.isEmpty()) |
| setImageSearchPath(image_search_path); |
| |
| if (m_image_dirs.isEmpty()) |
| m_image_dirs << QLatin1String(":/") << QLatin1String("./"); |
| |
| auto suffixes = QTextureFileReader::supportedFileFormats(); |
| suffixes.append(QImageReader::supportedImageFormats()); |
| for (auto ext : qAsConst(suffixes)) |
| m_image_suffixes << QLatin1Char('.') + QString::fromLatin1(ext); |
| |
| //qDebug() << "m_image_suffixes" << m_image_suffixes << "m_image_dirs" << m_image_dirs; |
| |
| auto *ctx = QQmlEngine::contextForObject(this); |
| if (ctx) { |
| QQmlEngine *engine = ctx->engine(); |
| if (engine) { |
| auto *provider = static_cast<QWaylandSharedTextureProvider*>(engine->imageProvider(QLatin1String("wlshared"))); |
| if (provider) |
| provider->setExtensionReady(this); |
| } |
| } |
| } |
| |
| QString QWaylandTextureSharingExtension::getExistingFilePath(const QString &key) const |
| { |
| // The default search path blocks absolute pathnames, but this does not prevent relative |
| // paths containing '../'. We handle that here, at the price of also blocking directory |
| // names ending with two or more dots. |
| |
| if (key.contains(QLatin1String("../"))) |
| return QString(); |
| |
| for (auto dir : m_image_dirs) { |
| QString path = dir + key; |
| if (QFileInfo::exists(path)) |
| return path; |
| } |
| |
| for (auto dir : m_image_dirs) { |
| for (auto ext : m_image_suffixes) { |
| QString fp = dir + key + ext; |
| //qDebug() << "trying" << fp; |
| if (QFileInfo::exists(fp)) |
| return fp; |
| } |
| } |
| return QString(); |
| } |
| |
| QtWayland::ServerBuffer *QWaylandTextureSharingExtension::getBuffer(const QString &key) |
| { |
| if (!initServerBufferIntegration()) |
| return nullptr; |
| |
| //qDebug() << "getBuffer" << key; |
| |
| QtWayland::ServerBuffer *buffer = nullptr; |
| |
| if ((buffer = m_server_buffers.value(key).buffer)) |
| return buffer; |
| |
| QByteArray pixelData; |
| QSize size; |
| uint glInternalFormat = GL_NONE; |
| |
| if (customPixelData(key, &pixelData, &size, &glInternalFormat)) { |
| if (!pixelData.isEmpty()) { |
| buffer = m_server_buffer_integration->createServerBufferFromData(pixelData, size, glInternalFormat); |
| if (!buffer) |
| qWarning() << "QWaylandTextureSharingExtension: could not create buffer from custom data for key:" << key; |
| } |
| } else { |
| QString pathName = getExistingFilePath(key); |
| //qDebug() << "pathName" << pathName; |
| if (pathName.isEmpty()) |
| return nullptr; |
| |
| buffer = getCompressedBuffer(pathName); |
| //qDebug() << "getCompressedBuffer" << buffer; |
| |
| if (!buffer) { |
| QImage img(pathName); |
| if (!img.isNull()) { |
| img = img.convertToFormat(QImage::Format_RGBA8888_Premultiplied); |
| buffer = m_server_buffer_integration->createServerBufferFromImage(img, QtWayland::ServerBuffer::RGBA32); |
| } |
| //qDebug() << "createServerBufferFromImage" << buffer; |
| } |
| } |
| if (buffer) |
| m_server_buffers.insert(key, BufferInfo(buffer)); |
| |
| //qDebug() << ">>>>" << key << buffer; |
| |
| return buffer; |
| } |
| |
| // Compositor requesting image for its own UI |
| void QWaylandTextureSharingExtension::requestBuffer(const QString &key) |
| { |
| //qDebug() << "requestBuffer" << key; |
| |
| if (thread() != QThread::currentThread()) |
| qWarning("QWaylandTextureSharingExtension::requestBuffer() called from outside main thread: possible race condition"); |
| |
| auto *buffer = getBuffer(key); |
| |
| if (buffer) |
| m_server_buffers[key].usedLocally = true; |
| |
| //dumpBufferInfo(); |
| |
| emit bufferResult(key, buffer); |
| } |
| |
| void QWaylandTextureSharingExtension::zqt_texture_sharing_v1_request_image(Resource *resource, const QString &key) |
| { |
| //qDebug() << "texture_sharing_request_image" << key; |
| auto *buffer = getBuffer(key); |
| if (buffer) { |
| struct ::wl_client *client = resource->client(); |
| struct ::wl_resource *buffer_resource = buffer->resourceForClient(client); |
| //qDebug() << " server_buffer resource" << buffer_resource; |
| if (buffer_resource) |
| send_provide_buffer(resource->handle, buffer_resource, key); |
| else |
| qWarning() << "QWaylandTextureSharingExtension: no buffer resource for client"; |
| } else { |
| send_image_failed(resource->handle, key, QString()); |
| } |
| //dumpBufferInfo(); |
| } |
| |
| void QWaylandTextureSharingExtension::zqt_texture_sharing_v1_abandon_image(Resource *resource, const QString &key) |
| { |
| Q_UNUSED(resource); |
| Q_UNUSED(key); |
| // qDebug() << Q_FUNC_INFO << resource << key; |
| QTimer::singleShot(100, this, &QWaylandTextureSharingExtension::cleanupBuffers); |
| } |
| |
| // A client has disconnected |
| void QWaylandTextureSharingExtension::zqt_texture_sharing_v1_destroy_resource(Resource *resource) |
| { |
| Q_UNUSED(resource); |
| // qDebug() << "texture_sharing_destroy_resource" << resource->handle << resource->handle->object.id << "client" << resource->client(); |
| // dumpBufferInfo(); |
| QTimer::singleShot(1000, this, &QWaylandTextureSharingExtension::cleanupBuffers); |
| } |
| |
| bool QWaylandTextureSharingExtension::initServerBufferIntegration() |
| { |
| if (!m_server_buffer_integration) { |
| QWaylandCompositor *compositor = static_cast<QWaylandCompositor *>(extensionContainer()); |
| |
| m_server_buffer_integration = QWaylandCompositorPrivate::get(compositor)->serverBufferIntegration(); |
| if (!m_server_buffer_integration) { |
| qWarning("QWaylandTextureSharingExtension initialization failed: No Server Buffer Integration"); |
| if (qEnvironmentVariableIsEmpty("QT_WAYLAND_SERVER_BUFFER_INTEGRATION")) |
| qWarning("Set the environment variable 'QT_WAYLAND_SERVER_BUFFER_INTEGRATION' to specify."); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| QtWayland::ServerBuffer *QWaylandTextureSharingExtension::getCompressedBuffer(const QString &pathName) |
| { |
| QFile f(pathName); |
| if (!f.open(QIODevice::ReadOnly)) |
| return nullptr; |
| |
| QTextureFileReader r(&f, pathName); |
| |
| if (!r.canRead()) |
| return nullptr; |
| |
| QTextureFileData td(r.read()); |
| |
| //qDebug() << "QWaylandTextureSharingExtension: reading compressed texture data" << td; |
| |
| if (!td.isValid()) { |
| qWarning() << "VulkanServerBufferIntegration:" << pathName << "not valid compressed texture"; |
| return nullptr; |
| } |
| |
| QByteArray pixelData = QByteArray::fromRawData(td.data().constData() + td.dataOffset(), td.dataLength()); |
| |
| return m_server_buffer_integration->createServerBufferFromData(pixelData, td.size(), td.glInternalFormat()); |
| } |
| |
| void QWaylandTextureSharingExtension::cleanupBuffers() |
| { |
| for (auto it = m_server_buffers.begin(); it != m_server_buffers.end(); ) { |
| auto *buffer = it.value().buffer; |
| if (!it.value().usedLocally && !buffer->bufferInUse()) { |
| //qDebug() << "deleting buffer for" << it.key(); |
| it = m_server_buffers.erase(it); |
| delete buffer; |
| } else { |
| ++it; |
| } |
| } |
| //dumpBufferInfo(); |
| } |
| |
| void QWaylandTextureSharingExtension::dumpBufferInfo() |
| { |
| qDebug() << "shared buffers:" << m_server_buffers.count(); |
| for (auto it = m_server_buffers.cbegin(); it != m_server_buffers.cend(); ++it) |
| qDebug() << " " << it.key() << ":" << it.value().buffer << "in use" << it.value().buffer->bufferInUse() << "usedLocally" << it.value().usedLocally ; |
| } |
| |
| QT_END_NAMESPACE |
| |
| #include "qwltexturesharingextension.moc" |