1 | // Copyright (C) 2024 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include "qquick3dxrmanager_p.h" |
5 | |
6 | #include <QtCore/QCoreApplication> |
7 | #include <QtCore/QDebug> |
8 | #include <QtCore/qjsonobject.h> |
9 | #include <QtCore/qjsonarray.h> |
10 | |
11 | #include <rhi/qrhi.h> |
12 | |
13 | #include <QtQuick/private/qquickwindow_p.h> |
14 | #include <QtQuick/QQuickRenderControl> |
15 | #include <QtQuick/QQuickRenderTarget> |
16 | #include <QtQuick/QQuickItem> |
17 | |
18 | #include <QtQuick3D/private/qquick3dnode_p.h> |
19 | #include <QtQuick3D/private/qquick3dviewport_p.h> |
20 | |
21 | |
22 | // #include "qquick3dxrcamera_p.h" |
23 | #include "qquick3dxranimationdriver_p.h" |
24 | |
25 | #if defined(Q_OS_VISIONOS) |
26 | # include <QtQuick3DXr/private/qquick3dxrmanager_visionos_p.h> |
27 | #else |
28 | # include "openxr/qquick3dxrmanager_openxr_p.h" |
29 | #endif |
30 | |
31 | #include "qquick3dxrorigin_p.h" |
32 | #include "qquick3dxrinputmanager_p.h" |
33 | |
34 | QT_BEGIN_NAMESPACE |
35 | |
36 | Q_DECLARE_LOGGING_CATEGORY(lcQuick3DXr); |
37 | Q_LOGGING_CATEGORY(lcQuick3DXr, "qt.quick3d.xr" ); |
38 | |
39 | QQuick3DXrManager::QQuick3DXrManager(QObject *parent) |
40 | : QObject(parent) |
41 | , d_ptr(new QQuick3DXrManagerPrivate(*this)) |
42 | { |
43 | } |
44 | |
45 | QQuick3DXrManager::~QQuick3DXrManager() |
46 | { |
47 | teardown(); |
48 | |
49 | // maintain the correct order |
50 | delete m_vrViewport; |
51 | delete m_quickWindow; |
52 | delete m_renderControl; |
53 | delete m_animationDriver; |
54 | } |
55 | |
56 | bool QQuick3DXrManager::isReady() const |
57 | { |
58 | Q_D(const QQuick3DXrManager); |
59 | return d->isReady(); |
60 | } |
61 | |
62 | bool QQuick3DXrManager::initialize() |
63 | { |
64 | Q_D(QQuick3DXrManager); |
65 | |
66 | QString m_errorString; |
67 | |
68 | // TODO: Handle visionos being async a bit better |
69 | if (!d->initialize()) { |
70 | if (!d->isReady()) |
71 | m_errorString = QStringLiteral("Waiting for the renderer to start." ); |
72 | else |
73 | m_errorString = QStringLiteral("Failed to initialize the XR manager." ); |
74 | |
75 | return false; |
76 | } |
77 | |
78 | // Setup Graphics |
79 | return setupGraphics(); |
80 | } |
81 | |
82 | void QQuick3DXrManager::teardown() |
83 | { |
84 | Q_D(QQuick3DXrManager); |
85 | d->teardown(); |
86 | } |
87 | |
88 | bool QQuick3DXrManager::isValid() const |
89 | { |
90 | Q_D(const QQuick3DXrManager); |
91 | return d->isValid(); |
92 | } |
93 | |
94 | void QQuick3DXrManager::setPassthroughEnabled(bool enabled) |
95 | { |
96 | Q_D(QQuick3DXrManager); |
97 | d->setPassthroughEnabled(enabled); |
98 | } |
99 | |
100 | bool QQuick3DXrManager::isPassthroughEnabled() const |
101 | { |
102 | Q_D(const QQuick3DXrManager); |
103 | return d->isPassthroughEnabled(); |
104 | } |
105 | |
106 | void QQuick3DXrManager::setMultiViewRenderingEnabled(bool enable) |
107 | { |
108 | Q_D(QQuick3DXrManager); |
109 | d->setMultiViewRenderingEnabled(enable); |
110 | } |
111 | |
112 | bool QQuick3DXrManager::isMultiViewRenderingEnabled() const |
113 | { |
114 | Q_D(const QQuick3DXrManager); |
115 | return d->isMultiViewRenderingEnabled(); |
116 | } |
117 | |
118 | bool QQuick3DXrManager::isMultiViewRenderingSupported() const |
119 | { |
120 | QRhi *rhi = m_renderControl->rhi(); |
121 | return rhi ? rhi->isFeatureSupported(feature: QRhi::MultiView) : false; |
122 | } |
123 | |
124 | void QQuick3DXrManager::setXROrigin(QQuick3DXrOrigin *origin) |
125 | { |
126 | m_xrOrigin = origin; |
127 | update(); |
128 | } |
129 | |
130 | void QQuick3DXrManager::getDefaultClipDistances(float &nearClip, float &farClip) const |
131 | { |
132 | Q_D(const QQuick3DXrManager); |
133 | d->getDefaultClipDistances(nearClip, farClip); |
134 | } |
135 | |
136 | QtQuick3DXr::FoveationLevel QQuick3DXrManager::getFixedFoveationLevel() const |
137 | { |
138 | #if defined(Q_OS_VISIONOS) |
139 | // Foveation is not configurable on VisionOS |
140 | return QtQuick3DXr::FoveationLevel::HighFoveation; |
141 | #else |
142 | Q_D(const QQuick3DXrManager); |
143 | return QtQuick3DXr::FoveationLevel(d->m_foveationLevel); |
144 | #endif |
145 | } |
146 | |
147 | void QQuick3DXrManager::setFixedFoveationLevel(QtQuick3DXr::FoveationLevel level) |
148 | { |
149 | #if defined(Q_OS_VISIONOS) |
150 | // Foveation is not configurable on VisionOS |
151 | Q_UNUSED(level); |
152 | #else |
153 | Q_D(QQuick3DXrManager); |
154 | const XrFoveationLevelFB xrLevel = XrFoveationLevelFB(level); |
155 | if (d->m_foveationLevel == xrLevel) |
156 | return; |
157 | |
158 | d->m_foveationLevel = xrLevel; |
159 | d->setupMetaQuestFoveation(); |
160 | #endif |
161 | } |
162 | |
163 | QtQuick3DXr::ReferenceSpace QQuick3DXrManager::getReferenceSpace() const |
164 | { |
165 | Q_D(const QQuick3DXrManager); |
166 | return d->getReferenceSpace(); |
167 | } |
168 | |
169 | void QQuick3DXrManager::setReferenceSpace(QtQuick3DXr::ReferenceSpace newReferenceSpace) |
170 | { |
171 | Q_D(QQuick3DXrManager); |
172 | |
173 | d->setReferenceSpace(newReferenceSpace); |
174 | } |
175 | |
176 | bool QQuick3DXrManager::isDepthSubmissionEnabled() const |
177 | { |
178 | Q_D(const QQuick3DXrManager); |
179 | return d->isDepthSubmissionEnabled(); |
180 | } |
181 | |
182 | void QQuick3DXrManager::setDepthSubmissionEnabled(bool enable) |
183 | { |
184 | Q_D(QQuick3DXrManager); |
185 | d->setDepthSubmissionEnabled(enable); |
186 | } |
187 | |
188 | QString QQuick3DXrManager::errorString() const |
189 | { |
190 | Q_D(const QQuick3DXrManager); |
191 | return d->errorString(); |
192 | } |
193 | |
194 | void QQuick3DXrManager::setSamples(int samples) |
195 | { |
196 | Q_D(QQuick3DXrManager); |
197 | d->setSamples(samples); |
198 | } |
199 | |
200 | void QQuick3DXrManager::update() |
201 | { |
202 | if (m_quickWindow && m_renderControl && m_xrOrigin && m_vrViewport) { |
203 | QEvent *request = new QEvent(QEvent::UpdateRequest); |
204 | QCoreApplication::postEvent(receiver: this, event: request); |
205 | } |
206 | } |
207 | |
208 | void QQuick3DXrManager::processSpatialEvents(const QJsonObject &events) |
209 | { |
210 | static qint64 lastId = -1; |
211 | |
212 | QJsonArray eventArray = events.value(QStringLiteral("events" )).toArray(); |
213 | for (const auto &event : eventArray) { |
214 | QJsonObject eventObj = event.toObject(); |
215 | // qDebug() << eventObj; |
216 | |
217 | // ID (unique per event) |
218 | const qint64 id = eventObj.value(QStringLiteral("id" )).toDouble(); |
219 | // timestamp (in seconds) |
220 | //const double timestamp = eventObj.value(QStringLiteral("timestamp")).toDouble(); |
221 | // kind |
222 | const QString kind = eventObj.value(QStringLiteral("kind" )).toString(); |
223 | if (kind != QStringLiteral("indirectPinch" )) |
224 | qWarning() << "kind is " << kind << "!" ; |
225 | |
226 | |
227 | // phase |
228 | const QString phase = eventObj.value(QStringLiteral("phase" )).toString(); |
229 | |
230 | // selectionRay (check if exists first) |
231 | QJsonObject selectionRayObj = eventObj.value(QStringLiteral("selectionRay" )).toObject(); |
232 | if (!selectionRayObj.isEmpty()) { |
233 | // origin |
234 | QJsonObject originObj = selectionRayObj.value(QStringLiteral("origin" )).toObject(); |
235 | QVector3D origin(originObj.value(QStringLiteral("x" )).toDouble(), originObj.value(QStringLiteral("y" )).toDouble(), originObj.value(QStringLiteral("z" )).toDouble()); |
236 | // convert meters to cm |
237 | origin *= 100.0; |
238 | |
239 | // direction |
240 | QJsonObject directionObj = selectionRayObj.value(QStringLiteral("direction" )).toObject(); |
241 | QVector3D direction(directionObj.value(QStringLiteral("x" )).toDouble(), directionObj.value(QStringLiteral("y" )).toDouble(), directionObj.value(QStringLiteral("z" )).toDouble()); |
242 | |
243 | QEvent::Type eventType; |
244 | |
245 | if (phase == QStringLiteral("active" )) { |
246 | if (lastId != id) { |
247 | // Press |
248 | lastId = id; |
249 | eventType = QEvent::MouseButtonPress; |
250 | } else { |
251 | // Move |
252 | eventType = QEvent::MouseMove; |
253 | } |
254 | } else { |
255 | // Release |
256 | lastId = -1; |
257 | eventType = QEvent::MouseButtonRelease; |
258 | } |
259 | |
260 | QMouseEvent *event = new QMouseEvent(eventType, QPointF(), QPointF(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); |
261 | m_vrViewport->processPointerEventFromRay(origin, direction, event); |
262 | delete event; |
263 | } |
264 | } |
265 | // An example of the input data |
266 | // { |
267 | // "id":4404736049417834088, |
268 | // "inputDevicePose": { |
269 | // "altitude":0, |
270 | // "azimuth":1.5707963267948966, |
271 | // "pose3D":{ |
272 | // "position":{ |
273 | // "x":0.227996826171875, |
274 | // "y":0.957000732421875, |
275 | // "z":-0.55999755859375 |
276 | // }, |
277 | // "rotation":{ |
278 | // "vector":[0,0,0,1] |
279 | // } |
280 | // } |
281 | // }, |
282 | // "kind":"indirectPinch", |
283 | // "location":[0,0], |
284 | // "location3D":{ |
285 | // "x":0, |
286 | // "y":0, |
287 | // "z":0 |
288 | // }, |
289 | // "modifierKeys":0, |
290 | // "phase":"ended", |
291 | // "selectionRay":{ |
292 | // "direction":{ |
293 | // "x":0.3321685791015625, |
294 | // "y":0.25982666015625, |
295 | // "z":-0.9067230224609375 |
296 | // }, |
297 | // "origin":{ |
298 | // "x":0.227996826171875, |
299 | // "y":0.957000732421875, |
300 | // "z":0 |
301 | // } |
302 | // }, |
303 | // "timestamp":74368.590710375 |
304 | // } |
305 | } |
306 | |
307 | bool QQuick3DXrManager::event(QEvent *e) |
308 | { |
309 | Q_D(QQuick3DXrManager); |
310 | |
311 | if (e->type() == QEvent::UpdateRequest) { |
312 | d->processXrEvents(); |
313 | update(); |
314 | return true; |
315 | } |
316 | return QObject::event(event: e); |
317 | } |
318 | |
319 | bool QQuick3DXrManager::isMultiviewRenderingDisabled() |
320 | { |
321 | static bool disabled = qEnvironmentVariableIntValue(varName: "QT_QUICK3D_XR_DISABLE_MULTIVIEW" ) != 0; |
322 | return disabled; |
323 | } |
324 | |
325 | QQuick3DXrInputManager *QQuick3DXrManager::getInputManager() const |
326 | { |
327 | Q_D(const QQuick3DXrManager); |
328 | return d->m_inputManager.data(); |
329 | } |
330 | |
331 | bool QQuick3DXrManager::setupGraphics() |
332 | { |
333 | Q_D(QQuick3DXrManager); |
334 | |
335 | // FIXME: Should probably make sure we don't accidentally get here more then once |
336 | // or if we're re-initializing, in which case: make sure to clean up properly first. |
337 | if (d->isGraphicsInitialized()) |
338 | return true; |
339 | |
340 | preSetupQuickScene(); |
341 | |
342 | if (!d->setupGraphics(m_quickWindow)) |
343 | return false; |
344 | |
345 | if (!setupQuickScene()) |
346 | return false; |
347 | |
348 | QRhi *rhi = m_quickWindow->rhi(); |
349 | QSSG_ASSERT_X(rhi != nullptr, "No RHI handle!" , return false); |
350 | |
351 | if (!d->isMultiViewRenderingEnabled()) |
352 | emit multiViewRenderingEnabledChanged(); |
353 | |
354 | return d->finalizeGraphics(rhi); |
355 | } |
356 | |
357 | void QQuick3DXrManager::renderFrame() |
358 | { |
359 | Q_D(QQuick3DXrManager); |
360 | |
361 | if (!m_xrOrigin) { |
362 | if (!m_xrOriginWarningShown) { |
363 | qWarning() << "No XrOrigin found!" ; |
364 | m_xrOriginWarningShown = true; |
365 | } |
366 | return; |
367 | } |
368 | |
369 | d->doRenderFrame(); |
370 | } |
371 | |
372 | void QQuick3DXrManager::preSetupQuickScene() |
373 | { |
374 | if (!m_renderControl) |
375 | m_renderControl = new QQuickRenderControl; |
376 | if (!m_quickWindow) |
377 | m_quickWindow = new QQuickWindow(m_renderControl); |
378 | } |
379 | |
380 | bool QQuick3DXrManager::setupQuickScene() |
381 | { |
382 | Q_D(QQuick3DXrManager); |
383 | |
384 | d->setupWindow(m_quickWindow); |
385 | |
386 | if (!m_animationDriver) { |
387 | m_animationDriver = new QQuick3DXrAnimationDriver; |
388 | m_animationDriver->install(); |
389 | } |
390 | |
391 | const bool initSuccess = m_renderControl->initialize(); |
392 | if (!initSuccess) { |
393 | qWarning(msg: "Quick 3D XR: Failed to create renderControl (failed to initialize RHI?)" ); |
394 | return false; |
395 | } |
396 | |
397 | QRhi *rhi = m_renderControl->rhi(); |
398 | if (!rhi) { |
399 | qWarning(msg: "Quick3D XR: No QRhi from renderControl. This should not happen." ); |
400 | return false; |
401 | } |
402 | |
403 | qCDebug(lcQuick3DXr, "Quick 3D XR: QRhi initialized with backend %s" , rhi->backendName()); |
404 | |
405 | return true; |
406 | } |
407 | |
408 | bool QQuick3DXrManager::supportsPassthrough() const |
409 | { |
410 | Q_D(const QQuick3DXrManager); |
411 | return d->supportsPassthrough(); |
412 | } |
413 | |
414 | QT_END_NAMESPACE |
415 | |