1 | // Copyright (C) 2019 The Qt Company Ltd. |
---|---|
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | |
5 | #include "sharedtextureprovider_p.h" |
6 | |
7 | #include <QFile> |
8 | #include <QDebug> |
9 | #include <qopenglfunctions.h> |
10 | #include <QQuickWindow> |
11 | |
12 | #include <QtWaylandClient/private/qwaylandintegration_p.h> |
13 | #include <QtWaylandClient/private/qwaylandserverbufferintegration_p.h> |
14 | #include <QtGui/QGuiApplication> |
15 | #include <QtGui/private/qguiapplication_p.h> |
16 | #include <QtQuick/private/qsgrhisupport_p.h> |
17 | #include <QtGui/qpa/qplatformnativeinterface.h> |
18 | #include <QtGui/QWindow> |
19 | #include <QOpenGLTexture> |
20 | #include <QImageReader> |
21 | |
22 | #include <QtCore/qpointer.h> |
23 | #include <QTimer> |
24 | |
25 | #include "texturesharingextension_p.h" |
26 | |
27 | QT_BEGIN_NAMESPACE |
28 | |
29 | class SharedTextureFactory : public QQuickTextureFactory |
30 | { |
31 | public: |
32 | SharedTextureFactory(const QtWaylandClient::QWaylandServerBuffer *buffer, const QString &id, SharedTextureRegistry *registry) |
33 | : m_buffer(buffer), m_id(id), m_registry(registry) |
34 | { |
35 | } |
36 | |
37 | ~SharedTextureFactory() override |
38 | { |
39 | //qDebug() << "====> DESTRUCTOR SharedTextureFactory" << this; |
40 | if (m_registry) |
41 | m_registry->abandonBuffer(id: m_id); |
42 | delete m_buffer; // TODO: make sure we are not keeping references to this elsewhere |
43 | //qDebug() << "buffer deleted"; |
44 | } |
45 | |
46 | QSize textureSize() const override |
47 | { |
48 | return m_buffer ? m_buffer->size() : QSize(); |
49 | } |
50 | |
51 | int textureByteCount() const override |
52 | { |
53 | return m_buffer ? (m_buffer->size().width() * m_buffer->size().height() * 4) : 0; |
54 | } |
55 | |
56 | QSGTexture *createTexture(QQuickWindow *window) const override |
57 | { |
58 | if (m_buffer != nullptr) { |
59 | QOpenGLTexture *texture = const_cast<QtWaylandClient::QWaylandServerBuffer *>(m_buffer)->toOpenGlTexture(); |
60 | return QNativeInterface::QSGOpenGLTexture::fromNative(textureId: texture->textureId(), |
61 | window, |
62 | size: m_buffer->size(), |
63 | options: QQuickWindow::TextureHasAlphaChannel); |
64 | } |
65 | return nullptr; |
66 | } |
67 | |
68 | private: |
69 | const QtWaylandClient::QWaylandServerBuffer *m_buffer = nullptr; |
70 | QString m_id; |
71 | QPointer<SharedTextureRegistry> m_registry; |
72 | }; |
73 | |
74 | |
75 | SharedTextureRegistry::SharedTextureRegistry() |
76 | : m_extension(new TextureSharingExtension) |
77 | { |
78 | connect(sender: m_extension, signal: &TextureSharingExtension::bufferReceived, context: this, slot: &SharedTextureRegistry::receiveBuffer); |
79 | connect(sender: m_extension, signal: &TextureSharingExtension::activeChanged, context: this, slot: &SharedTextureRegistry::handleExtensionActive); |
80 | } |
81 | |
82 | SharedTextureRegistry::~SharedTextureRegistry() |
83 | { |
84 | delete m_extension; |
85 | } |
86 | |
87 | const QtWaylandClient::QWaylandServerBuffer *SharedTextureRegistry::bufferForId(const QString &id) const |
88 | { |
89 | return m_buffers.value(key: id); |
90 | } |
91 | |
92 | void SharedTextureRegistry::requestBuffer(const QString &id) |
93 | { |
94 | if (!m_extension->isActive()) { |
95 | //qDebug() << "Extension not active, adding" << id << "to queue"; |
96 | m_pendingBuffers << id; |
97 | return; |
98 | } |
99 | m_extension->requestImage(key: id); |
100 | } |
101 | |
102 | void SharedTextureRegistry::abandonBuffer(const QString &id) |
103 | { |
104 | m_buffers.remove(key: id); |
105 | m_extension->abandonImage(key: id); |
106 | } |
107 | |
108 | |
109 | void SharedTextureRegistry::handleExtensionActive() |
110 | { |
111 | //qDebug() << "handleExtensionActive, queue:" << m_pendingBuffers; |
112 | if (m_extension->isActive()) |
113 | while (!m_pendingBuffers.isEmpty()) |
114 | requestBuffer(id: m_pendingBuffers.takeFirst()); |
115 | } |
116 | |
117 | bool SharedTextureRegistry::preinitialize() |
118 | { |
119 | if (QSGRhiSupport::instance()->rhiBackend() != QRhi::OpenGLES2) { |
120 | qWarning() << "The shared-texture extension is only supported on OpenGL. Use QQuickWindow::setSceneGraphBackend() to override the default."; |
121 | return false; |
122 | } |
123 | |
124 | auto *serverBufferIntegration = QGuiApplicationPrivate::platformIntegration()->nativeInterface()->nativeResourceForIntegration(resource: "server_buffer_integration"); |
125 | |
126 | if (!serverBufferIntegration) { |
127 | qWarning() << "Wayland Server Buffer Integration not available."; |
128 | return false; |
129 | } |
130 | |
131 | return true; |
132 | } |
133 | |
134 | void SharedTextureRegistry::receiveBuffer(QtWaylandClient::QWaylandServerBuffer *buffer, const QString& id) |
135 | { |
136 | //qDebug() << "ReceiveBuffer for id" << id; |
137 | if (buffer) |
138 | m_buffers.insert(key: id, value: buffer); |
139 | emit replyReceived(id); |
140 | } |
141 | |
142 | class SharedTextureImageResponse : public QQuickImageResponse |
143 | { |
144 | Q_OBJECT |
145 | public: |
146 | SharedTextureImageResponse(SharedTextureRegistry *registry, const QString &id) |
147 | : m_id(id), m_registry(registry) |
148 | { |
149 | if (!m_registry || m_registry->bufferForId(id)) { |
150 | // Shortcut: no server roundtrip needed, just let the event loop call the slot |
151 | QMetaObject::invokeMethod(obj: this, member: "doResponse", c: Qt::QueuedConnection, Q_ARG(QString, id)); |
152 | |
153 | } else { |
154 | // TBD: timeout? |
155 | connect(sender: registry, signal: &SharedTextureRegistry::replyReceived, context: this, slot: &SharedTextureImageResponse::doResponse); |
156 | registry->requestBuffer(id); |
157 | } |
158 | } |
159 | |
160 | QQuickTextureFactory *textureFactory() const override |
161 | { |
162 | if (m_registry) { |
163 | const QtWaylandClient::QWaylandServerBuffer *buffer = m_registry->bufferForId(id: m_id); |
164 | if (buffer) { |
165 | //qDebug() << "Creating shared buffer texture for" << m_id; |
166 | return new SharedTextureFactory(buffer, m_id, m_registry); |
167 | } |
168 | //qDebug() << "Shared buffer NOT found for" << m_id; |
169 | } |
170 | |
171 | // No shared buffer, do fallback |
172 | QString fbPath = fallbackPath(); |
173 | if (fbPath.isEmpty()) { |
174 | m_errorString = QStringLiteral("Shared buffer not found, and no fallback path set."); |
175 | return nullptr; |
176 | } |
177 | |
178 | QImageReader reader(fbPath + m_id); |
179 | QImage img = reader.read(); |
180 | if (img.isNull()) { |
181 | qWarning() << "Could not load local image from id/path"<< reader.fileName(); |
182 | m_errorString = QStringLiteral("Shared buffer not found, and fallback local file loading failed: ") + reader.errorString(); |
183 | return nullptr; |
184 | } |
185 | return QQuickTextureFactory::textureFactoryForImage(image: img); |
186 | } |
187 | |
188 | QString errorString() const override |
189 | { |
190 | return m_errorString; |
191 | } |
192 | |
193 | static QString fallbackPath() |
194 | { |
195 | static QString fbPath; |
196 | static bool isInit = false; |
197 | if (!isInit) { |
198 | isInit = true; |
199 | QByteArray envVal = qgetenv(varName: "QT_SHAREDTEXTURE_FALLBACK_DIR"); |
200 | if (!envVal.isEmpty()) { |
201 | fbPath = QString::fromLocal8Bit(ba: envVal); |
202 | if (!fbPath.endsWith(c: QLatin1Char('/'))) |
203 | fbPath.append(c: QLatin1Char('/')); |
204 | } |
205 | } |
206 | return fbPath; |
207 | } |
208 | |
209 | |
210 | public Q_SLOTS: |
211 | void doResponse(const QString &key) { |
212 | if (key != m_id) |
213 | return; // not our buffer |
214 | |
215 | // No need to be called again |
216 | if (m_registry) |
217 | disconnect(sender: m_registry, signal: &SharedTextureRegistry::replyReceived, receiver: this, slot: &SharedTextureImageResponse::doResponse); |
218 | |
219 | emit finished(); |
220 | } |
221 | |
222 | private: |
223 | QString m_id; |
224 | SharedTextureRegistry *m_registry = nullptr; |
225 | mutable QString m_errorString; |
226 | }; |
227 | |
228 | |
229 | SharedTextureProvider::SharedTextureProvider() |
230 | { |
231 | m_sharingAvailable = SharedTextureRegistry::preinitialize(); |
232 | if (!m_sharingAvailable) { |
233 | if (SharedTextureImageResponse::fallbackPath().isEmpty()) |
234 | qWarning() << "Shared buffer images not available, and no fallback directory set."; |
235 | else |
236 | qWarning() << "Shared buffer images not available, will fallback to local image files from"<< SharedTextureImageResponse::fallbackPath(); |
237 | } |
238 | } |
239 | |
240 | SharedTextureProvider::~SharedTextureProvider() |
241 | { |
242 | delete m_registry; |
243 | } |
244 | |
245 | QQuickImageResponse *SharedTextureProvider::requestImageResponse(const QString &id, const QSize &requestedSize) |
246 | { |
247 | Q_UNUSED(requestedSize); |
248 | |
249 | //qDebug() << "Provider: got request for" << id; |
250 | |
251 | if (m_sharingAvailable && !m_registry) |
252 | m_registry = new SharedTextureRegistry; |
253 | |
254 | return new SharedTextureImageResponse(m_registry, id); |
255 | } |
256 | |
257 | QT_END_NAMESPACE |
258 | |
259 | #include "moc_sharedtextureprovider_p.cpp" |
260 | |
261 | #include "sharedtextureprovider.moc" |
262 |
Definitions
- SharedTextureFactory
- SharedTextureFactory
- ~SharedTextureFactory
- textureSize
- textureByteCount
- createTexture
- SharedTextureRegistry
- ~SharedTextureRegistry
- bufferForId
- requestBuffer
- abandonBuffer
- handleExtensionActive
- preinitialize
- receiveBuffer
- SharedTextureImageResponse
- SharedTextureImageResponse
- textureFactory
- errorString
- fallbackPath
- doResponse
- SharedTextureProvider
- ~SharedTextureProvider
Learn Advanced QML with KDAB
Find out more