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
34QT_BEGIN_NAMESPACE
35
36Q_DECLARE_LOGGING_CATEGORY(lcQuick3DXr);
37Q_LOGGING_CATEGORY(lcQuick3DXr, "qt.quick3d.xr");
38
39QQuick3DXrManager::QQuick3DXrManager(QObject *parent)
40 : QObject(parent)
41 , d_ptr(new QQuick3DXrManagerPrivate(*this))
42{
43}
44
45QQuick3DXrManager::~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
56bool QQuick3DXrManager::isReady() const
57{
58 Q_D(const QQuick3DXrManager);
59 return d->isReady();
60}
61
62bool 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
82void QQuick3DXrManager::teardown()
83{
84 Q_D(QQuick3DXrManager);
85 d->teardown();
86}
87
88bool QQuick3DXrManager::isValid() const
89{
90 Q_D(const QQuick3DXrManager);
91 return d->isValid();
92}
93
94void QQuick3DXrManager::setPassthroughEnabled(bool enabled)
95{
96 Q_D(QQuick3DXrManager);
97 d->setPassthroughEnabled(enabled);
98}
99
100bool QQuick3DXrManager::isPassthroughEnabled() const
101{
102 Q_D(const QQuick3DXrManager);
103 return d->isPassthroughEnabled();
104}
105
106void QQuick3DXrManager::setMultiViewRenderingEnabled(bool enable)
107{
108 Q_D(QQuick3DXrManager);
109 d->setMultiViewRenderingEnabled(enable);
110}
111
112bool QQuick3DXrManager::isMultiViewRenderingEnabled() const
113{
114 Q_D(const QQuick3DXrManager);
115 return d->isMultiViewRenderingEnabled();
116}
117
118bool QQuick3DXrManager::isMultiViewRenderingSupported() const
119{
120 QRhi *rhi = m_renderControl->rhi();
121 return rhi ? rhi->isFeatureSupported(feature: QRhi::MultiView) : false;
122}
123
124void QQuick3DXrManager::setXROrigin(QQuick3DXrOrigin *origin)
125{
126 m_xrOrigin = origin;
127 update();
128}
129
130void QQuick3DXrManager::getDefaultClipDistances(float &nearClip, float &farClip) const
131{
132 Q_D(const QQuick3DXrManager);
133 d->getDefaultClipDistances(nearClip, farClip);
134}
135
136QtQuick3DXr::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
147void 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
163QtQuick3DXr::ReferenceSpace QQuick3DXrManager::getReferenceSpace() const
164{
165 Q_D(const QQuick3DXrManager);
166 return d->getReferenceSpace();
167}
168
169void QQuick3DXrManager::setReferenceSpace(QtQuick3DXr::ReferenceSpace newReferenceSpace)
170{
171 Q_D(QQuick3DXrManager);
172
173 d->setReferenceSpace(newReferenceSpace);
174}
175
176bool QQuick3DXrManager::isDepthSubmissionEnabled() const
177{
178 Q_D(const QQuick3DXrManager);
179 return d->isDepthSubmissionEnabled();
180}
181
182void QQuick3DXrManager::setDepthSubmissionEnabled(bool enable)
183{
184 Q_D(QQuick3DXrManager);
185 d->setDepthSubmissionEnabled(enable);
186}
187
188QString QQuick3DXrManager::errorString() const
189{
190 Q_D(const QQuick3DXrManager);
191 return d->errorString();
192}
193
194void QQuick3DXrManager::setSamples(int samples)
195{
196 Q_D(QQuick3DXrManager);
197 d->setSamples(samples);
198}
199
200void 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
208void 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
307bool 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
319bool QQuick3DXrManager::isMultiviewRenderingDisabled()
320{
321 static bool disabled = qEnvironmentVariableIntValue(varName: "QT_QUICK3D_XR_DISABLE_MULTIVIEW") != 0;
322 return disabled;
323}
324
325QQuick3DXrInputManager *QQuick3DXrManager::getInputManager() const
326{
327 Q_D(const QQuick3DXrManager);
328 return d->m_inputManager.data();
329}
330
331bool 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
357void 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
372void 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
380bool 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
408bool QQuick3DXrManager::supportsPassthrough() const
409{
410 Q_D(const QQuick3DXrManager);
411 return d->supportsPassthrough();
412}
413
414QT_END_NAMESPACE
415

source code of qtquick3d/src/xr/quick3dxr/qquick3dxrmanager.cpp