1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qwltexturesharingextension_p.h"
5
6#include <QWaylandSurface>
7
8#include <QDebug>
9
10#include <QQuickWindow>
11
12#include <QPainter>
13#include <QPen>
14#include <QTimer>
15
16#include <QtGui/private/qtexturefilereader_p.h>
17
18#include <QtOpenGL/QOpenGLTexture>
19#include <QtGui/QImageReader>
20
21#include <QtQuick/QSGTexture>
22#include <QQmlContext>
23#include <QThread>
24
25QT_BEGIN_NAMESPACE
26
27class SharedTextureFactory : public QQuickTextureFactory
28{
29public:
30 SharedTextureFactory(const QtWayland::ServerBuffer *buffer)
31 : m_buffer(buffer)
32 {
33 }
34
35 ~SharedTextureFactory() override
36 {
37 if (m_buffer && !QCoreApplication::closingDown())
38 const_cast<QtWayland::ServerBuffer*>(m_buffer)->releaseOpenGlTexture();
39 }
40
41 QSize textureSize() const override
42 {
43 return m_buffer ? m_buffer->size() : QSize();
44 }
45
46 int textureByteCount() const override
47 {
48 return m_buffer ? (m_buffer->size().width() * m_buffer->size().height() * 4) : 0;
49 }
50
51 QSGTexture *createTexture(QQuickWindow *window) const override
52 {
53 if (m_buffer != nullptr) {
54 QOpenGLTexture *texture = const_cast<QtWayland::ServerBuffer *>(m_buffer)->toOpenGlTexture();
55 return QNativeInterface::QSGOpenGLTexture::fromNative(textureId: texture->textureId(),
56 window,
57 size: m_buffer->size(),
58 options: QQuickWindow::TextureHasAlphaChannel);
59 }
60
61 return nullptr;
62 }
63
64private:
65 const QtWayland::ServerBuffer *m_buffer = nullptr;
66};
67
68class SharedTextureImageResponse : public QQuickImageResponse
69{
70 Q_OBJECT
71public:
72 SharedTextureImageResponse(QWaylandTextureSharingExtension *extension, const QString &id)
73 : m_id(id)
74 {
75 if (extension)
76 doRequest(extension);
77 }
78
79 void doRequest(QWaylandTextureSharingExtension *extension)
80 {
81 m_extension = extension;
82 connect(sender: extension, signal: &QWaylandTextureSharingExtension::bufferResult, context: this, slot: &SharedTextureImageResponse::doResponse);
83 QMetaObject::invokeMethod(object: extension, function: [this] { m_extension->requestBuffer(key: m_id); }, type: Qt::AutoConnection);
84 }
85
86 QQuickTextureFactory *textureFactory() const override
87 {
88 if (m_buffer) {
89// qDebug() << "Creating shared buffer texture for" << m_id;
90 return new SharedTextureFactory(m_buffer);
91 }
92// qDebug() << "Shared buffer NOT found for" << m_id;
93 m_errorString = QLatin1String("Shared buffer not found");
94 return nullptr;
95 }
96
97 QString errorString() const override
98 {
99 return m_errorString;
100 }
101
102public slots:
103 void doResponse(const QString &key, QtWayland::ServerBuffer *buffer)
104 {
105 if (key != m_id)
106 return; //somebody else's texture
107
108 m_buffer = buffer;
109
110 if (m_extension)
111 disconnect(sender: m_extension, signal: &QWaylandTextureSharingExtension::bufferResult, receiver: this, slot: &SharedTextureImageResponse::doResponse);
112
113 emit finished();
114 }
115
116private:
117 QString m_id;
118 QWaylandTextureSharingExtension *m_extension = nullptr;
119 mutable QString m_errorString;
120 QtWayland::ServerBuffer *m_buffer = nullptr;
121};
122
123QWaylandSharedTextureProvider::QWaylandSharedTextureProvider()
124{
125}
126
127QWaylandSharedTextureProvider::~QWaylandSharedTextureProvider()
128{
129}
130
131QQuickImageResponse *QWaylandSharedTextureProvider::requestImageResponse(const QString &id, const QSize &requestedSize)
132{
133 Q_UNUSED(requestedSize);
134
135// qDebug() << "Provider: got request for" << id;
136
137 auto *extension = QWaylandTextureSharingExtension::self();
138 auto *response = new SharedTextureImageResponse(extension, id);
139 if (!extension)
140 m_pendingResponses << response;
141
142 return response;
143}
144
145void QWaylandSharedTextureProvider::setExtensionReady(QWaylandTextureSharingExtension *extension)
146{
147 for (auto *response : std::as_const(m_pendingResponses))
148 response->doRequest(extension);
149 m_pendingResponses.clear();
150 m_pendingResponses.squeeze();
151}
152
153QWaylandTextureSharingExtension *QWaylandTextureSharingExtension::s_self = nullptr; // theoretical race conditions, but OK as long as we don't delete it while we are running
154
155QWaylandTextureSharingExtension::QWaylandTextureSharingExtension()
156{
157 s_self = this;
158}
159
160QWaylandTextureSharingExtension::QWaylandTextureSharingExtension(QWaylandCompositor *compositor)
161 :QWaylandCompositorExtensionTemplate(compositor)
162{
163 s_self = this;
164}
165
166QWaylandTextureSharingExtension::~QWaylandTextureSharingExtension()
167{
168 //qDebug() << Q_FUNC_INFO;
169 //dumpBufferInfo();
170
171 for (auto b : m_server_buffers)
172 delete b.buffer;
173
174 if (s_self == this)
175 s_self = nullptr;
176}
177
178void QWaylandTextureSharingExtension::setImageSearchPath(const QString &path)
179{
180 m_image_dirs = path.split(sep: QLatin1Char(';'));
181
182 for (auto it = m_image_dirs.begin(); it != m_image_dirs.end(); ++it)
183 if (!(*it).endsWith(c: QLatin1Char('/')))
184 (*it) += QLatin1Char('/');
185}
186
187void QWaylandTextureSharingExtension::initialize()
188{
189 QWaylandCompositorExtensionTemplate::initialize();
190 QWaylandCompositor *compositor = static_cast<QWaylandCompositor *>(extensionContainer());
191 init(compositor->display(), 1);
192
193 QString image_search_path = qEnvironmentVariable(varName: "QT_WAYLAND_SHAREDTEXTURE_SEARCH_PATH");
194 if (!image_search_path.isEmpty())
195 setImageSearchPath(image_search_path);
196
197 if (m_image_dirs.isEmpty())
198 m_image_dirs << QLatin1String(":/") << QLatin1String("./");
199
200 auto suffixes = QTextureFileReader::supportedFileFormats();
201 suffixes.append(other: QImageReader::supportedImageFormats());
202 for (auto ext : std::as_const(t&: suffixes))
203 m_image_suffixes << QLatin1Char('.') + QString::fromLatin1(ba: ext);
204
205 //qDebug() << "m_image_suffixes" << m_image_suffixes << "m_image_dirs" << m_image_dirs;
206
207 auto *ctx = QQmlEngine::contextForObject(this);
208 if (ctx) {
209 QQmlEngine *engine = ctx->engine();
210 if (engine) {
211 auto *provider = static_cast<QWaylandSharedTextureProvider*>(engine->imageProvider(id: QLatin1String("wlshared")));
212 if (provider)
213 provider->setExtensionReady(this);
214 }
215 }
216}
217
218QString QWaylandTextureSharingExtension::getExistingFilePath(const QString &key) const
219{
220 // The default search path blocks absolute pathnames, but this does not prevent relative
221 // paths containing '../'. We handle that here, at the price of also blocking directory
222 // names ending with two or more dots.
223
224 if (key.contains(s: QLatin1String("../")))
225 return QString();
226
227 for (auto dir : std::as_const(t: m_image_dirs)) {
228 QString path = dir + key;
229 if (QFileInfo::exists(file: path))
230 return path;
231 }
232
233 for (auto dir : std::as_const(t: m_image_dirs)) {
234 for (auto ext : m_image_suffixes) {
235 QString fp = dir + key + ext;
236 //qDebug() << "trying" << fp;
237 if (QFileInfo::exists(file: fp))
238 return fp;
239 }
240 }
241 return QString();
242}
243
244QtWayland::ServerBuffer *QWaylandTextureSharingExtension::getBuffer(const QString &key)
245{
246 if (!initServerBufferIntegration())
247 return nullptr;
248
249//qDebug() << "getBuffer" << key;
250
251 QtWayland::ServerBuffer *buffer = nullptr;
252
253 if ((buffer = m_server_buffers.value(key).buffer))
254 return buffer;
255
256 QByteArray pixelData;
257 QSize size;
258 uint glInternalFormat = GL_NONE;
259
260 if (customPixelData(key, data: &pixelData, size: &size, glInternalFormat: &glInternalFormat)) {
261 if (!pixelData.isEmpty()) {
262 buffer = m_server_buffer_integration->createServerBufferFromData(view: pixelData, size, glInternalFormat);
263 if (!buffer)
264 qWarning() << "QWaylandTextureSharingExtension: could not create buffer from custom data for key:" << key;
265 }
266 } else {
267 QString pathName = getExistingFilePath(key);
268 //qDebug() << "pathName" << pathName;
269 if (pathName.isEmpty())
270 return nullptr;
271
272 buffer = getCompressedBuffer(key: pathName);
273 //qDebug() << "getCompressedBuffer" << buffer;
274
275 if (!buffer) {
276 QImage img(pathName);
277 if (!img.isNull()) {
278 img = img.convertToFormat(f: QImage::Format_RGBA8888_Premultiplied);
279 buffer = m_server_buffer_integration->createServerBufferFromImage(qimage: img, format: QtWayland::ServerBuffer::RGBA32);
280 }
281 //qDebug() << "createServerBufferFromImage" << buffer;
282 }
283 }
284 if (buffer)
285 m_server_buffers.insert(key, value: BufferInfo(buffer));
286
287 //qDebug() << ">>>>" << key << buffer;
288
289 return buffer;
290}
291
292// Compositor requesting image for its own UI
293void QWaylandTextureSharingExtension::requestBuffer(const QString &key)
294{
295 //qDebug() << "requestBuffer" << key;
296
297 if (thread() != QThread::currentThread())
298 qWarning(msg: "QWaylandTextureSharingExtension::requestBuffer() called from outside main thread: possible race condition");
299
300 auto *buffer = getBuffer(key);
301
302 if (buffer)
303 m_server_buffers[key].usedLocally = true;
304
305 //dumpBufferInfo();
306
307 emit bufferResult(key, buffer);
308}
309
310void QWaylandTextureSharingExtension::zqt_texture_sharing_v1_request_image(Resource *resource, const QString &key)
311{
312 //qDebug() << "texture_sharing_request_image" << key;
313 auto *buffer = getBuffer(key);
314 if (buffer) {
315 struct ::wl_client *client = resource->client();
316 struct ::wl_resource *buffer_resource = buffer->resourceForClient(client);
317 //qDebug() << " server_buffer resource" << buffer_resource;
318 if (buffer_resource)
319 send_provide_buffer(resource->handle, buffer_resource, key);
320 else
321 qWarning() << "QWaylandTextureSharingExtension: no buffer resource for client";
322 } else {
323 send_image_failed(resource->handle, key, QString());
324 }
325 //dumpBufferInfo();
326}
327
328void QWaylandTextureSharingExtension::zqt_texture_sharing_v1_abandon_image(Resource *resource, const QString &key)
329{
330 Q_UNUSED(resource);
331 Q_UNUSED(key);
332// qDebug() << Q_FUNC_INFO << resource << key;
333 QTimer::singleShot(interval: 100, receiver: this, slot: &QWaylandTextureSharingExtension::cleanupBuffers);
334}
335
336// A client has disconnected
337void QWaylandTextureSharingExtension::zqt_texture_sharing_v1_destroy_resource(Resource *resource)
338{
339 Q_UNUSED(resource);
340// qDebug() << "texture_sharing_destroy_resource" << resource->handle << resource->handle->object.id << "client" << resource->client();
341// dumpBufferInfo();
342 QTimer::singleShot(interval: 1000, receiver: this, slot: &QWaylandTextureSharingExtension::cleanupBuffers);
343}
344
345bool QWaylandTextureSharingExtension::initServerBufferIntegration()
346{
347 if (!m_server_buffer_integration) {
348 QWaylandCompositor *compositor = static_cast<QWaylandCompositor *>(extensionContainer());
349
350 m_server_buffer_integration = QWaylandCompositorPrivate::get(compositor)->serverBufferIntegration();
351 if (!m_server_buffer_integration) {
352 qWarning(msg: "QWaylandTextureSharingExtension initialization failed: No Server Buffer Integration");
353 if (qEnvironmentVariableIsEmpty(varName: "QT_WAYLAND_SERVER_BUFFER_INTEGRATION"))
354 qWarning(msg: "Set the environment variable 'QT_WAYLAND_SERVER_BUFFER_INTEGRATION' to specify.");
355 return false;
356 }
357 }
358 return true;
359}
360
361QtWayland::ServerBuffer *QWaylandTextureSharingExtension::getCompressedBuffer(const QString &pathName)
362{
363 QFile f(pathName);
364 if (!f.open(flags: QIODevice::ReadOnly))
365 return nullptr;
366
367 QTextureFileReader r(&f, pathName);
368
369 if (!r.canRead())
370 return nullptr;
371
372 QTextureFileData td(r.read());
373
374 //qDebug() << "QWaylandTextureSharingExtension: reading compressed texture data" << td;
375
376 if (!td.isValid()) {
377 qWarning() << "VulkanServerBufferIntegration:" << pathName << "not valid compressed texture";
378 return nullptr;
379 }
380
381 return m_server_buffer_integration->createServerBufferFromData(view: td.getDataView(), size: td.size(),
382 glInternalFormat: td.glInternalFormat());
383}
384
385void QWaylandTextureSharingExtension::cleanupBuffers()
386{
387 for (auto it = m_server_buffers.begin(); it != m_server_buffers.end(); ) {
388 auto *buffer = it.value().buffer;
389 if (!it.value().usedLocally && !buffer->bufferInUse()) {
390 //qDebug() << "deleting buffer for" << it.key();
391 it = m_server_buffers.erase(it);
392 delete buffer;
393 } else {
394 ++it;
395 }
396 }
397 //dumpBufferInfo();
398}
399
400void QWaylandTextureSharingExtension::dumpBufferInfo()
401{
402 qDebug() << "shared buffers:" << m_server_buffers.size();
403 for (auto it = m_server_buffers.cbegin(); it != m_server_buffers.cend(); ++it)
404 qDebug() << " " << it.key() << ":" << it.value().buffer << "in use" << it.value().buffer->bufferInUse() << "usedLocally" << it.value().usedLocally ;
405}
406
407QT_END_NAMESPACE
408
409#include "moc_qwltexturesharingextension_p.cpp"
410
411#include "qwltexturesharingextension.moc"
412

source code of qtwayland/src/compositor/extensions/qwltexturesharingextension.cpp