Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwltexturesharingextension.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
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(texture->textureId(),
56 window,
57 m_buffer->size(),
58 QQuickWindow::TextureHasAlphaChannel);
59 }
60
61 return nullptr;
62 }
63
64private:
65 const QtWayland::ServerBuffer *m_buffer = nullptr;
66};
67
69{
71public:
78
85
87 {
88 if (m_buffer) {
89// qDebug() << "Creating shared buffer texture for" << m_id;
91 }
92// qDebug() << "Shared buffer NOT found for" << m_id;
93 m_errorString = QLatin1String("Shared buffer not found");
94 return nullptr;
95 }
96
98 {
99 return m_errorString;
100 }
101
102public Q_SLOTS:
104 {
105 if (key != m_id)
106 return; //somebody else's texture
107
109
110 if (m_extension)
112
113 emit finished();
114 }
115
116private:
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(QLatin1Char(';'));
181
182 for (auto it = m_image_dirs.begin(); it != m_image_dirs.end(); ++it)
183 if (!(*it).endsWith(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("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(QImageReader::supportedImageFormats());
202 for (auto ext : std::as_const(suffixes))
203 m_image_suffixes << QLatin1Char('.') + QString::fromLatin1(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(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(QLatin1String("../")))
225 return QString();
226
227 for (auto dir : std::as_const(m_image_dirs)) {
228 QString path = dir + key;
229 if (QFileInfo::exists(path))
230 return path;
231 }
232
233 for (auto dir : std::as_const(m_image_dirs)) {
234 for (auto ext : m_image_suffixes) {
235 QString fp = dir + key + ext;
236 //qDebug() << "trying" << fp;
237 if (QFileInfo::exists(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, &pixelData, &size, &glInternalFormat)) {
261 if (!pixelData.isEmpty()) {
262 buffer = m_server_buffer_integration->createServerBufferFromData(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(pathName);
273 //qDebug() << "getCompressedBuffer" << buffer;
274
275 if (!buffer) {
276 QImage img(pathName);
277 if (!img.isNull()) {
278 img = img.convertToFormat(QImage::Format_RGBA8888_Premultiplied);
279 buffer = m_server_buffer_integration->createServerBufferFromImage(img, QtWayland::ServerBuffer::RGBA32);
280 }
281 //qDebug() << "createServerBufferFromImage" << buffer;
282 }
283 }
284 if (buffer)
285 m_server_buffers.insert(key, 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("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(100, this, &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(1000, this, &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("QWaylandTextureSharingExtension initialization failed: No Server Buffer Integration");
353 if (qEnvironmentVariableIsEmpty("QT_WAYLAND_SERVER_BUFFER_INTEGRATION"))
354 qWarning("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(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(td.getDataView(), td.size(),
382 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"