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
qquick3dxrmanager_openxr.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5
12
16
18
19#include <QtQuick3DUtils/private/qssgassert_p.h>
20#include <QtQuick3D/private/qquick3dviewport_p.h>
21
22#include <QtQuick/qquickwindow.h>
23#include <QtQuick/qquickrendercontrol.h>
24
25#include <QtGui/qquaternion.h>
26
27#include <QtCore/qobject.h>
28
29#include <openxr/openxr_reflection.h>
30
31#ifdef XR_USE_GRAPHICS_API_VULKAN
32# include "qopenxrgraphics_vulkan_p.h"
33#endif
34
35#ifdef XR_USE_GRAPHICS_API_D3D11
36# include "qopenxrgraphics_d3d11_p.h"
37#endif
38
39#ifdef XR_USE_GRAPHICS_API_D3D12
40# include "qopenxrgraphics_d3d12_p.h"
41#endif
42
43#ifdef XR_USE_GRAPHICS_API_OPENGL
44# include "qopenxrgraphics_opengl_p.h"
45#endif
46
47#ifdef XR_USE_PLATFORM_ANDROID
48# include <QtCore/qnativeinterface.h>
49# include <QtCore/QJniEnvironment>
50# include <QtCore/QJniObject>
51# ifdef XR_USE_GRAPHICS_API_OPENGL_ES
52# include "qopenxrgraphics_opengles_p.h"
53# endif // XR_USE_GRAPHICS_API_OPENGL_ES
54#endif // XR_USE_PLATFORM_ANDROID
55
56#ifdef XR_USE_GRAPHICS_API_METAL
57# include "qopenxrgraphics_metal_p.h"
58#endif // XR_USE_GRAPHICS_API_METAL
59
60static XrReferenceSpaceType getXrReferenceSpaceType(QtQuick3DXr::ReferenceSpace referenceSpace)
61{
62 switch (referenceSpace) {
63 case QtQuick3DXr::ReferenceSpace::ReferenceSpaceLocal:
64 return XR_REFERENCE_SPACE_TYPE_LOCAL;
65 case QtQuick3DXr::ReferenceSpace::ReferenceSpaceStage:
66 return XR_REFERENCE_SPACE_TYPE_STAGE;
67 case QtQuick3DXr::ReferenceSpace::ReferenceSpaceLocalFloor:
68 return XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT;
69 default:
70 return XR_REFERENCE_SPACE_TYPE_LOCAL;
71 }
72}
73
74static QtQuick3DXr::ReferenceSpace getReferenceSpaceType(XrReferenceSpaceType referenceSpace)
75{
76 switch (referenceSpace) {
77 case XR_REFERENCE_SPACE_TYPE_LOCAL:
79 case XR_REFERENCE_SPACE_TYPE_STAGE:
81 case XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT:
83 default:
85 }
86}
87
88// Macro to generate stringify functions for OpenXR enumerations based data provided in openxr_reflection.h
89#define ENUM_CASE_STR(name, val) case name: return #name;
90#define MAKE_TO_STRING_FUNC(enumType)
91 static inline const char* to_string(enumType e) {
92 switch (e) {
93 XR_LIST_ENUM_##enumType(ENUM_CASE_STR)
94 default: return "Unknown " #enumType;
95 }
96 }
97
98MAKE_TO_STRING_FUNC(XrReferenceSpaceType)
99MAKE_TO_STRING_FUNC(XrViewConfigurationType)
100MAKE_TO_STRING_FUNC(XrEnvironmentBlendMode)
101MAKE_TO_STRING_FUNC(XrSessionState)
102MAKE_TO_STRING_FUNC(XrResult)
103
104static bool isExtensionSupported(const char *extensionName, const QVector<XrExtensionProperties> &instanceExtensionProperties, uint32_t *extensionVersion = nullptr)
105{
106 for (const auto &extensionProperty : instanceExtensionProperties) {
107 if (!strcmp(extensionName, extensionProperty.extensionName)) {
108 if (extensionVersion)
109 *extensionVersion = extensionProperty.extensionVersion;
110 return true;
111 }
112 }
113 return false;
114}
115
116static bool isApiLayerSupported(const char *layerName, const QVector<XrApiLayerProperties> &apiLayerProperties)
117{
118 for (const auto &prop : apiLayerProperties) {
119 if (!strcmp(layerName, prop.layerName))
120 return true;
121 }
122 return false;
123}
124
125// OpenXR's debug messenger stuff is a carbon copy of the Vulkan one, hence we
126// replicate the same behavior on Qt side as well, i.e. route by default
127// everything to qDebug. Filtering or further control (that is supported with
128// the C++ APIS in the QVulkan* stuff) is not provided here for now.
129#ifdef XR_EXT_debug_utils
130static XRAPI_ATTR XrBool32 XRAPI_CALL defaultDebugCallbackFunc(XrDebugUtilsMessageSeverityFlagsEXT messageSeverity,
131 XrDebugUtilsMessageTypeFlagsEXT messageType,
132 const XrDebugUtilsMessengerCallbackDataEXT *callbackData,
133 void *userData)
134{
135 Q_UNUSED(messageSeverity);
136 Q_UNUSED(messageType);
137 QQuick3DXrManager *self = static_cast<QQuick3DXrManager *>(userData);
138 // this is qDebug intentionally, not categorized
139 qDebug("xrDebug [QOpenXRManager %p] %s", self, callbackData->message);
140 return XR_FALSE;
141}
142#endif // XR_EXT_debug_utils
143
144static std::pair<XrResult, XrFrameState> waitForNextFrame(XrSession session)
145{
146 XrFrameWaitInfo frameWaitInfo {};
147 frameWaitInfo.type = XR_TYPE_FRAME_WAIT_INFO;
148 XrFrameState frameState {};
149 frameState.type = XR_TYPE_FRAME_STATE;
150 auto result = xrWaitFrame(session, &frameWaitInfo, &frameState);
151 return { result, frameState };
152}
153
155
157
158static const char s_workerThreadName[] = "QQuick3DXrWorkerThread";
159
161{
163public:
168
170 enum Event : EventT {
172 };
173
175 {
176 return static_cast<QEvent::Type>(event);
177 }
178
180 {
181 QCoreApplication::postEvent(this, new QEvent(QQuick3DOpenXRThreadWorker::asQEvent(QQuick3DOpenXRThreadWorker::Event::WaitForFrame)));
182 }
183
184protected:
185 bool event(QEvent *event) override
186 {
187 switch (static_cast<Event>(event->type())) {
188 case Event::WaitForFrame: {
189 auto result = waitForNextFrame(m_session);
190 emit frameWaited(result.first, result.second);
191 return true;
192 }
193 default:
194 return QObject::event(event);
195 }
196 }
197
198signals:
200
201private:
202 XrSession m_session = XR_NULL_HANDLE;
203};
204
205void QQuick3DXrManagerPrivate::setErrorString(XrResult result, const char *callName)
206{
207 if (m_runtimeName.isEmpty())
208 m_errorString = QObject::tr("%1 failed: no OpenXR runtime found").arg(QLatin1StringView(callName));
209 else
210 m_errorString = QObject::tr("%1 for runtime %2 %3 failed with %4.")
211 .arg(QLatin1StringView(callName),
212 m_runtimeName,
213 m_runtimeVersion.toString(),
214 OpenXRHelpers::getXrResultAsString(result, m_instance));
215 if (result == XR_ERROR_FORM_FACTOR_UNAVAILABLE) // this is very common
216 m_errorString += QObject::tr("\nThe OpenXR runtime has no connection to the headset; check if connection is active and functional.");
217}
218
224
226{
227 delete m_graphics; // last, with Vulkan this may own the VkInstance
228}
229
230QQuick3DXrManagerPrivate *QQuick3DXrManagerPrivate::get(QQuick3DXrManager *manager)
231{
232 QSSG_ASSERT(manager != nullptr, return nullptr);
233 return manager->d_func();
234}
235
236void QQuick3DXrManagerPrivate::updateCameraHelper(QQuick3DXrEyeCamera *camera, const XrCompositionLayerProjectionView &layerView)
237{
238 camera->setLeftTangent(qTan(layerView.fov.angleLeft));
239 camera->setRightTangent(qTan(layerView.fov.angleRight));
240 camera->setUpTangent(qTan(layerView.fov.angleUp));
241 camera->setDownTangent(qTan(layerView.fov.angleDown));
242
243 camera->setPosition(QVector3D(layerView.pose.position.x,
244 layerView.pose.position.y,
245 layerView.pose.position.z) * 100.0f); // convert m to cm
246
247 camera->setRotation(QQuaternion(layerView.pose.orientation.w,
248 layerView.pose.orientation.x,
249 layerView.pose.orientation.y,
250 layerView.pose.orientation.z));
251}
252
253// Set the active camera for the view to the camera for the eye value
254// This is set right before updateing/rendering for that eye's view
255void QQuick3DXrManagerPrivate::updateCameraNonMultiview(int eye, const XrCompositionLayerProjectionView &layerView)
256{
257 Q_Q(QQuick3DXrManager);
258
259 QQuick3DXrOrigin *xrOrigin = q->m_xrOrigin;
260
261 QQuick3DXrEyeCamera *eyeCamera = xrOrigin ? xrOrigin->eyeCamera(eye) : nullptr;
262
263 if (eyeCamera)
264 updateCameraHelper(eyeCamera, layerView);
265
266 q->m_vrViewport->setCamera(eyeCamera);
267}
268
269// The multiview version sets multiple cameras.
270void QQuick3DXrManagerPrivate::updateCameraMultiview(int projectionLayerViewStartIndex, int count)
271{
272 Q_Q(QQuick3DXrManager);
273
274 QQuick3DXrOrigin *xrOrigin = q->m_xrOrigin;
275 QQuick3DViewport *vrViewport = q->m_vrViewport;
276
277 QVarLengthArray<QQuick3DCamera *, 4> cameras;
278 for (int i = projectionLayerViewStartIndex; i < projectionLayerViewStartIndex + count; ++i) {
279 QQuick3DXrEyeCamera *eyeCamera = xrOrigin ? xrOrigin->eyeCamera(i) : nullptr;
280 if (eyeCamera)
281 updateCameraHelper(eyeCamera, m_projectionLayerViews[i]);
282 cameras.append(eyeCamera);
283 }
284 vrViewport->setMultiViewCameras(cameras.data(), cameras.count());
285}
286
288{
289 return m_blendModes.contains(XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND)
290 || m_blendModes.contains(XR_ENVIRONMENT_BLEND_MODE_ADDITIVE);
291}
292
294{
295 bool supported = false;
296 XrSystemPassthroughProperties2FB passthroughSystemProperties{};
297 passthroughSystemProperties.type = XR_TYPE_SYSTEM_PASSTHROUGH_PROPERTIES2_FB;
298
299 XrSystemProperties systemProperties{};
300 systemProperties.type = XR_TYPE_SYSTEM_PROPERTIES;
301 systemProperties.next = &passthroughSystemProperties;
302
303 XrSystemGetInfo systemGetInfo{};
304 systemGetInfo.type = XR_TYPE_SYSTEM_GET_INFO;
305 systemGetInfo.formFactor = m_formFactor;
306
307 XrSystemId systemId = XR_NULL_SYSTEM_ID;
308 xrGetSystem(m_instance, &systemGetInfo, &systemId);
309 xrGetSystemProperties(m_instance, systemId, &systemProperties);
310
311 supported = (passthroughSystemProperties.capabilities & XR_PASSTHROUGH_CAPABILITY_BIT_FB) == XR_PASSTHROUGH_CAPABILITY_BIT_FB;
312
313 if (!supported) {
314 // Try the old one. (the Simulator reports spec version 3 for
315 // XR_FB_passthrough, yet the capabilities in
316 // XrSystemPassthroughProperties2FB are 0)
317 XrSystemPassthroughPropertiesFB oldPassthroughSystemProperties{};
318 oldPassthroughSystemProperties.type = XR_TYPE_SYSTEM_PASSTHROUGH_PROPERTIES_FB;
319 systemProperties.next = &oldPassthroughSystemProperties;
320 xrGetSystemProperties(m_instance, systemId, &systemProperties);
321 supported = oldPassthroughSystemProperties.supportsPassthrough;
322 }
323
324 return supported;
325}
326
328{
329 // Passthrough is supported if either XR_FB_passthrough extension is available
330 // or if transparent blend modes (ALPHA_BLEND/ADDITIVE) are supported
332}
333
334void QQuick3DXrManagerPrivate::setupWindow(QQuickWindow *window)
335{
336 QSSG_ASSERT(window != nullptr, return);
337 if (m_graphics)
338 m_graphics->setupWindow(window);
339}
340
342{
343 return m_graphics && m_graphics->rhi();
344}
345
346bool QQuick3DXrManagerPrivate::setupGraphics(QQuickWindow *window)
347{
348 QSSG_ASSERT(window != nullptr, return false);
349 QSSG_ASSERT(m_graphics != nullptr, return false);
350
351 return m_graphics->setupGraphics(m_instance, m_systemId, window->graphicsConfiguration());
352}
353
355{
356 Q_Q(QQuick3DXrManager);
357 if (m_waitingForFrame) {
358 m_wantUpdate = true;
359 return;
360 }
361
362 QCoreApplication::postEvent(q, new QEvent(QEvent::UpdateRequest));
363}
364
366{
367 Q_Q(QQuick3DXrManager);
368
369 bool exitRenderLoop = false;
370 bool requestrestart = false;
371 pollEvents(&exitRenderLoop, &requestrestart);
372
373 if (exitRenderLoop)
374 emit q->sessionEnded();
375
376 if (m_sessionRunning && m_inputManager) {
377 QQuick3DXrInputManagerPrivate::get(m_inputManager)->pollActions();
378 q->renderFrame();
379 }
380}
381
382void QQuick3DXrManagerPrivate::destroySwapchain()
383{
384 for (const Swapchain &swapchain : m_swapchains)
385 xrDestroySwapchain(swapchain.handle);
386
387 m_swapchains.clear();
388 m_swapchainImages.clear();
389 m_configViews.clear();
390
391 for (const Swapchain &swapchain : m_depthSwapchains)
392 xrDestroySwapchain(swapchain.handle);
393
394 m_depthSwapchains.clear();
395 m_depthSwapchainImages.clear();
396}
397
399{
400 Q_ASSERT(m_session != XR_NULL_HANDLE);
401
402 if (m_waitingForFrame)
403 return;
404 m_waitingForFrame = true;
405
406 if (m_worker) {
408 // onFrameWaitCompleted() will be called asynchronoiusly
409 } else {
410 auto result = waitForNextFrame(m_session);
411 onFrameWaitCompleted(result.first, result.second);
412 }
413}
414
415void QQuick3DXrManagerPrivate::onFrameWaitCompleted(XrResult result, const XrFrameState &frameState)
416{
417 if (!m_waitingForFrame)
418 return;
419 m_waitingForFrame = false;
420
421 if (!checkXrResult(result)) {
422 qWarning("xrWaitFrame failed");
423 } else {
424 doRenderFrameAferWait(frameState);
425 }
426
427 if (m_wantUpdate) {
428 m_wantUpdate = false;
429 update();
430 }
431}
432
433void QQuick3DXrManagerPrivate::doRenderFrameAferWait(const XrFrameState &frameState)
434{
435 Q_ASSERT(m_session != XR_NULL_HANDLE);
436
437 XrFrameBeginInfo frameBeginInfo{};
438 frameBeginInfo.type = XR_TYPE_FRAME_BEGIN_INFO;
439 if (!checkXrResult(xrBeginFrame(m_session, &frameBeginInfo))) {
440 qWarning("xrBeginFrame failed");
441 return;
442 }
443
444 QVector<XrCompositionLayerBaseHeader*> layers;
445
446 XrCompositionLayerPassthroughFB passthroughCompLayer{};
447 passthroughCompLayer.type = XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_FB;
448 if (m_fbPassthroughEnabled) {
449 if (m_passthroughLayer == XR_NULL_HANDLE)
450 createMetaQuestPassthroughLayer();
451 passthroughCompLayer.layerHandle = m_passthroughLayer;
452 passthroughCompLayer.flags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
453 passthroughCompLayer.space = XR_NULL_HANDLE;
454 layers.push_back(reinterpret_cast<XrCompositionLayerBaseHeader*>(&passthroughCompLayer));
455 }
456
457 XrCompositionLayerProjection layer{};
458 layer.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION;
459 layer.layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
460 layer.layerFlags |= XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT;
461 layer.layerFlags |= XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT;
462
463 if (frameState.shouldRender == XR_TRUE) {
464 if (renderLayer(frameState.predictedDisplayTime, frameState.predictedDisplayPeriod, layer)) {
465 layers.push_back(reinterpret_cast<XrCompositionLayerBaseHeader*>(&layer));
466 }
467 }
468
469 XrFrameEndInfo frameEndInfo{};
470 frameEndInfo.type = XR_TYPE_FRAME_END_INFO;
471 frameEndInfo.displayTime = frameState.predictedDisplayTime;
472 if (!m_fbPassthroughEnabled)
473 frameEndInfo.environmentBlendMode = m_environmentBlendMode;
474 else
475 frameEndInfo.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
476 frameEndInfo.layerCount = (uint32_t)layers.size();
477 frameEndInfo.layers = layers.data();
478 if (!checkXrResult(xrEndFrame(m_session, &frameEndInfo)))
479 qWarning("xrEndFrame failed");
480}
481
483{
484 QSSG_ASSERT(rhi != nullptr && m_graphics != nullptr, return false);
485
486 if (m_multiviewRendering && !rhi->isFeatureSupported(QRhi::MultiView)) {
487 qCDebug(lcQuick3DXr) << "Multiview rendering is not supported with the current graphics API";
488 m_multiviewRendering = false;
489 }
490
491#if QT_CONFIG(graphicsframecapture)
492 if (m_frameCapture) {
493 m_frameCapture->setCapturePath(QLatin1String("."));
494 m_frameCapture->setCapturePrefix(QLatin1String("quick3dxr"));
495 m_frameCapture->setRhi(rhi);
496 if (!m_frameCapture->isLoaded()) {
497 qWarning("Quick 3D XR: Frame capture was requested but QGraphicsFrameCapture is not initialized"
498 " (or has no backends enabled in the Qt build)");
499 } else {
500 qCDebug(lcQuick3DXr, "Quick 3D XR: Frame capture initialized");
501 }
502 }
503#endif
504
505 m_isGraphicsInitialized = m_graphics->finializeGraphics(rhi);
506 return m_isGraphicsInitialized;
507}
508
509bool QQuick3DXrManagerPrivate::initialize()
510{
511 Q_Q(QQuick3DXrManager);
512 // This, meaning constructing the QGraphicsFrameCapture if we'll want it,
513 // must be done as early as possible, before initalizing graphics. In fact
514 // in hybrid apps it might be too late at this point if Qt Quick (so someone
515 // outside our control) has initialized graphics which then makes
516 // RenderDoc's hooking mechanisms disfunctional.
517 if (qEnvironmentVariableIntValue("QT_QUICK3D_XR_FRAME_CAPTURE")) {
518#if QT_CONFIG(graphicsframecapture)
519 m_frameCapture.reset(new QGraphicsFrameCapture);
520#else
521 qWarning("Quick 3D XR: Frame capture was requested, but Qt is built without QGraphicsFrameCapture");
522#endif
523 }
524
525#ifdef XR_USE_PLATFORM_ANDROID
526 // Initialize the Loader
527 PFN_xrInitializeLoaderKHR xrInitializeLoaderKHR;
528 xrGetInstanceProcAddr(
529 XR_NULL_HANDLE, "xrInitializeLoaderKHR", (PFN_xrVoidFunction*)&xrInitializeLoaderKHR);
530 if (xrInitializeLoaderKHR != NULL) {
531 m_javaVM = QJniEnvironment::javaVM();
532 m_androidActivity = QNativeInterface::QAndroidApplication::context();
533
534 XrLoaderInitInfoAndroidKHR loaderInitializeInfoAndroid;
535 memset(&loaderInitializeInfoAndroid, 0, sizeof(loaderInitializeInfoAndroid));
536 loaderInitializeInfoAndroid.type = XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR;
537 loaderInitializeInfoAndroid.next = NULL;
538 loaderInitializeInfoAndroid.applicationVM = m_javaVM;
539 loaderInitializeInfoAndroid.applicationContext = m_androidActivity.object();
540 XrResult xrResult = xrInitializeLoaderKHR((XrLoaderInitInfoBaseHeaderKHR*)&loaderInitializeInfoAndroid);
541 if (xrResult != XR_SUCCESS) {
542 qWarning("Failed to initialize OpenXR Loader: %s", to_string(xrResult));
543 return false;
544 }
545 }
546#endif
547
548 // Init the Graphics Backend
549 auto graphicsAPI = QQuickWindow::graphicsApi();
550
551 m_graphics = nullptr;
552#ifdef XR_USE_GRAPHICS_API_VULKAN
553 if (graphicsAPI == QSGRendererInterface::Vulkan)
554 m_graphics = new QOpenXRGraphicsVulkan;
555#endif
556#ifdef XR_USE_GRAPHICS_API_D3D11
557 if (graphicsAPI == QSGRendererInterface::Direct3D11)
558 m_graphics = new QOpenXRGraphicsD3D11;
559#endif
560#ifdef XR_USE_GRAPHICS_API_D3D12
561 if (graphicsAPI == QSGRendererInterface::Direct3D12)
562 m_graphics = new QOpenXRGraphicsD3D12;
563#endif
564#ifdef XR_USE_GRAPHICS_API_OPENGL
565 if (graphicsAPI == QSGRendererInterface::OpenGL)
566 m_graphics = new QOpenXRGraphicsOpenGL;
567#endif
568#ifdef XR_USE_GRAPHICS_API_OPENGL_ES
569 if (graphicsAPI == QSGRendererInterface::OpenGL)
570 m_graphics = new QOpenXRGraphicsOpenGLES;
571#endif
572#ifdef XR_USE_GRAPHICS_API_METAL
573 if (graphicsAPI == QSGRendererInterface::Metal)
574 m_graphics = new QOpenXRGraphicsMetal;
575#endif
576
577 if (!m_graphics) {
578 qWarning() << "The Qt Quick Scenegraph is not using a supported RHI mode:" << graphicsAPI;
579 return false;
580 }
581
582 // Print out extension and layer information
583 checkXrExtensions(nullptr);
584 checkXrLayers();
585
586 m_spaceExtension = QQuick3DXrAnchorManager::instance();
587
588 // Create Instance
589 XrResult result = createXrInstance();
590 if (result != XR_SUCCESS) {
591 setErrorString(result, "xrCreateInstance");
592 delete m_graphics;
593 m_graphics = nullptr;
594 return false;
595 } else {
596 checkXrInstance();
597 }
598
599 // Catch OpenXR runtime messages via XR_EXT_debug_utils and route them to qDebug
600 setupDebugMessenger();
601
602 // Load System
603 result = initializeSystem();
604 if (result != XR_SUCCESS) {
605 setErrorString(result, "xrGetSystem");
606 delete m_graphics;
607 m_graphics = nullptr;
608 return false;
609 }
610
611 // Setup Graphics
612 if (!q->setupGraphics()) {
613 m_errorString = QObject::tr("Failed to set up 3D API integration");
614 delete m_graphics;
615 m_graphics = nullptr;
616 return false;
617 }
618
619 // Create Session
620 XrSessionCreateInfo xrSessionInfo{};
621 xrSessionInfo.type = XR_TYPE_SESSION_CREATE_INFO;
622 xrSessionInfo.systemId = m_systemId;
623
624 auto overlayLayerPlacement = qEnvironmentVariableIntValue("QT_QUICK3D_XR_OVERLAY_PLACEMENT");
625 if (m_overlayExtensionSupported && overlayLayerPlacement > 0) {
626 XrSessionCreateInfoOverlayEXTX overlayInfo {};
627 overlayInfo.type = XR_TYPE_SESSION_CREATE_INFO_OVERLAY_EXTX;
628 overlayInfo.sessionLayersPlacement = overlayLayerPlacement;
629 overlayInfo.next = m_graphics->handle();
630 xrSessionInfo.next = &overlayInfo;
631 qCDebug(lcQuick3DXr, "requesting overlay placement: %d", overlayLayerPlacement);
632 } else {
633 xrSessionInfo.next = m_graphics->handle();
634 }
635
636 result = xrCreateSession(m_instance, &xrSessionInfo, &m_session);
637 if (result != XR_SUCCESS) {
638 setErrorString(result, "xrCreateSession");
639 delete m_graphics;
640 m_graphics = nullptr;
641 return false;
642 }
643
644 const bool disableAsyncWait = qEnvironmentVariableIntValue("QT_QUICK3D_XR_DISABLE_ASYNC_WAIT") != 0;
645 if (Q_LIKELY(!disableAsyncWait))
646 initWorkerThread();
647
648 // Meta Quest Specific Setup
649 if (m_colorspaceExtensionSupported)
650 setupMetaQuestColorSpaces();
651 if (m_displayRefreshRateExtensionSupported)
652 setupMetaQuestRefreshRates();
653 if (m_spaceExtensionSupported)
654 m_spaceExtension->initialize(m_instance, m_session);
655
656 checkReferenceSpaces();
657
658 // Setup Input
659 m_inputManager = QQuick3DXrInputManager::instance();
660 if (QSSG_GUARD(m_inputManager != nullptr))
661 QQuick3DXrInputManagerPrivate::get(m_inputManager)->init(m_instance, m_session);
662
663 if (!setupAppSpace())
664 return false;
665 if (!setupViewSpace())
666 return false;
667
668 if (!createSwapchains())
669 return false;
670
671 return true;
672}
673
674void QQuick3DXrManagerPrivate::teardown()
675{
676 m_waitingForFrame = false;
677 m_wantUpdate = false;
678 destroyWorkerThread();
679
680 if (m_inputManager) {
681 QQuick3DXrInputManagerPrivate::get(m_inputManager)->teardown();
682 m_inputManager = nullptr;
683 }
684
685 if (m_spaceExtension) {
686 m_spaceExtension->teardown();
687 m_spaceExtension = nullptr;
688 }
689
690 if (m_passthroughLayer)
691 destroyMetaQuestPassthroughLayer();
692 if (m_passthroughFeature)
693 destroyMetaQuestPassthrough();
694
695 destroySwapchain();
696
697 if (m_appSpace != XR_NULL_HANDLE) {
698 xrDestroySpace(m_appSpace);
699 }
700
701 if (m_viewSpace != XR_NULL_HANDLE)
702 xrDestroySpace(m_viewSpace);
703
704 xrDestroySession(m_session);
705
706#ifdef XR_EXT_debug_utils
707 if (m_debugMessenger) {
708 m_xrDestroyDebugUtilsMessengerEXT(m_debugMessenger);
709 m_debugMessenger = XR_NULL_HANDLE;
710 }
711#endif // XR_EXT_debug_utils
712
713 xrDestroyInstance(m_instance);
714
715 // early deinit for graphics, so it can destroy owned QRhi resources
716 // Note: Used to be part of the XRmanager dtor.
717 if (m_graphics)
718 m_graphics->releaseResources();
719}
720
721void QQuick3DXrManagerPrivate::initWorkerThread()
722{
723 Q_Q(QQuick3DXrManager);
724
725 qCDebug(lcQuick3DXr, "Initializing OpenXR worker thread");
726 m_workerThread = new QThread();
727 m_workerThread->setObjectName(QLatin1StringView(s_workerThreadName));
728
729 m_worker = new QQuick3DOpenXRThreadWorker(m_session);
730 m_worker->moveToThread(m_workerThread);
731
732 QObject::connect(
733 m_worker,
734 &QQuick3DOpenXRThreadWorker::frameWaited,
735 q,
736 [this](XrResult result, const XrFrameState &frameState) { onFrameWaitCompleted(result, frameState); },
737 Qt::QueuedConnection);
738
739 QObject::connect(m_workerThread, &QThread::finished, m_worker, &QObject::deleteLater);
740
741 m_workerThread->start();
742}
743
744void QQuick3DXrManagerPrivate::destroyWorkerThread()
745{
746 if (m_workerThread) {
747 m_workerThread->quit();
748 m_workerThread->wait();
749 delete m_workerThread;
750 m_workerThread = nullptr;
751 m_worker = nullptr;
752 qCDebug(lcQuick3DXr, "OpenXR worker thread destroyed");
753 }
754}
755
756void QQuick3DXrManagerPrivate::checkReferenceSpaces()
757{
758 Q_ASSERT(m_session != XR_NULL_HANDLE);
759
760 uint32_t spaceCount;
761 if (!checkXrResult(xrEnumerateReferenceSpaces(m_session, 0, &spaceCount, nullptr))) {
762 qWarning("Failed to enumerate reference spaces");
763 return;
764 }
765 m_availableReferenceSpace.resize(spaceCount);
766 if (!checkXrResult(xrEnumerateReferenceSpaces(m_session, spaceCount, &spaceCount, m_availableReferenceSpace.data()))) {
767 qWarning("Failed to enumerate reference spaces");
768 return;
769 }
770
771 qCDebug(lcQuick3DXr, "Available reference spaces: %d", spaceCount);
772 for (XrReferenceSpaceType space : m_availableReferenceSpace) {
773 qCDebug(lcQuick3DXr, " Name: %s", to_string(space));
774 }
775}
776
777bool QQuick3DXrManagerPrivate::isReferenceSpaceAvailable(XrReferenceSpaceType type)
778{
779 return m_availableReferenceSpace.contains(type);
780}
781
782bool QQuick3DXrManagerPrivate::setupAppSpace()
783{
784 Q_Q(QQuick3DXrManager);
785
786 Q_ASSERT(m_session != XR_NULL_HANDLE);
787
788 XrPosef identityPose;
789 identityPose.orientation.w = 1;
790 identityPose.orientation.x = 0;
791 identityPose.orientation.y = 0;
792 identityPose.orientation.z = 0;
793 identityPose.position.x = 0;
794 identityPose.position.y = 0;
795 identityPose.position.z = 0;
796
797 XrReferenceSpaceType newReferenceSpace;
798 XrSpace newAppSpace = XR_NULL_HANDLE;
799 m_isEmulatingLocalFloor = false;
800
801 if (isReferenceSpaceAvailable(m_requestedReferenceSpace)) {
802 newReferenceSpace = m_requestedReferenceSpace;
803 } else if (m_requestedReferenceSpace == XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT &&
804 isReferenceSpaceAvailable(XR_REFERENCE_SPACE_TYPE_STAGE)) {
805 m_isEmulatingLocalFloor = true;
806 m_isFloorResetPending = true;
807 newReferenceSpace = XR_REFERENCE_SPACE_TYPE_LOCAL;
808 } else {
809 qWarning("Requested reference space is not available");
810 newReferenceSpace = XR_REFERENCE_SPACE_TYPE_LOCAL;
811 }
812
813 // App Space
814 qCDebug(lcQuick3DXr, "Creating new reference space for app space: %s", to_string(newReferenceSpace));
815 XrReferenceSpaceCreateInfo referenceSpaceCreateInfo{};
816 referenceSpaceCreateInfo.type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO;
817 referenceSpaceCreateInfo.poseInReferenceSpace = identityPose;
818 referenceSpaceCreateInfo.referenceSpaceType = newReferenceSpace;
819 if (!checkXrResult(xrCreateReferenceSpace(m_session, &referenceSpaceCreateInfo, &newAppSpace))) {
820 qWarning("Failed to create app space");
821 return false;
822 }
823
824 if (m_appSpace)
825 xrDestroySpace(m_appSpace);
826
827 m_appSpace = newAppSpace;
828 m_referenceSpace = newReferenceSpace;
829 // only broadcast the reference space change if we are not emulating the local floor
830 // since we'll try and change the referenceSpace again once we have tracking
831 if (!m_isFloorResetPending)
832 emit q->referenceSpaceChanged();
833
834 return true;
835
836}
837
838void QQuick3DXrManagerPrivate::updateAppSpace(XrTime predictedDisplayTime)
839{
840 Q_Q(QQuick3DXrManager);
841
842 // If the requested reference space is not the current one, we need to
843 // re-create the app space now
844 if (m_requestedReferenceSpace != m_referenceSpace && !m_isFloorResetPending) {
845 if (!setupAppSpace()) {
846 // If we can't set the requested reference space, use the current one
847 qWarning("Setting requested reference space failed");
848 m_requestedReferenceSpace = m_referenceSpace;
849 return;
850 }
851 }
852
853 // This happens when we setup the emulated LOCAL_FLOOR mode
854 // We may have requested it on app setup, but we need to have
855 // some tracking information to calculate the floor height so
856 // that will only happen once we get here.
857 if (m_isFloorResetPending) {
858 if (!resetEmulatedFloorHeight(predictedDisplayTime)) {
859 // It didn't work, so give up and use local space (which is already setup).
860 m_requestedReferenceSpace = XR_REFERENCE_SPACE_TYPE_LOCAL;
861 emit q->referenceSpaceChanged();
862 }
863 return;
864 }
865
866}
867
868bool QQuick3DXrManagerPrivate::setupViewSpace()
869{
870 Q_ASSERT(m_session != XR_NULL_HANDLE);
871
872 XrPosef identityPose;
873 identityPose.orientation.w = 1;
874 identityPose.orientation.x = 0;
875 identityPose.orientation.y = 0;
876 identityPose.orientation.z = 0;
877 identityPose.position.x = 0;
878 identityPose.position.y = 0;
879 identityPose.position.z = 0;
880
881 XrSpace newViewSpace = XR_NULL_HANDLE;
882
883 XrReferenceSpaceCreateInfo referenceSpaceCreateInfo{};
884 referenceSpaceCreateInfo.type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO;
885 referenceSpaceCreateInfo.poseInReferenceSpace = identityPose;
886 referenceSpaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW;
887 if (!checkXrResult(xrCreateReferenceSpace(m_session, &referenceSpaceCreateInfo, &newViewSpace))) {
888 qWarning("Failed to create view space");
889 return false;
890 }
891
892 if (m_viewSpace != XR_NULL_HANDLE)
893 xrDestroySpace(m_viewSpace);
894
895 m_viewSpace = newViewSpace;
896
897 return true;
898}
899
900bool QQuick3DXrManagerPrivate::resetEmulatedFloorHeight(XrTime predictedDisplayTime)
901{
902 Q_Q(QQuick3DXrManager);
903
904 Q_ASSERT(m_isEmulatingLocalFloor);
905
906 m_isFloorResetPending = false;
907
908 XrPosef identityPose;
909 identityPose.orientation.w = 1;
910 identityPose.orientation.x = 0;
911 identityPose.orientation.y = 0;
912 identityPose.orientation.z = 0;
913 identityPose.position.x = 0;
914 identityPose.position.y = 0;
915 identityPose.position.z = 0;
916
917 XrSpace localSpace = XR_NULL_HANDLE;
918 XrSpace stageSpace = XR_NULL_HANDLE;
919
920 XrReferenceSpaceCreateInfo createInfo{};
921 createInfo.type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO;
922 createInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
923 createInfo.poseInReferenceSpace = identityPose;
924
925 if (!checkXrResult(xrCreateReferenceSpace(m_session, &createInfo, &localSpace))) {
926 qWarning("Failed to create local space (for emulated LOCAL_FLOOR space)");
927 return false;
928 }
929
930 createInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE;
931 if (!checkXrResult(xrCreateReferenceSpace(m_session, &createInfo, &stageSpace))) {
932 qWarning("Failed to create stage space (for emulated LOCAL_FLOOR space)");
933 xrDestroySpace(localSpace);
934 return false;
935 }
936
937 XrSpaceLocation stageLocation{};
938 stageLocation.type = XR_TYPE_SPACE_LOCATION;
939 stageLocation.pose = identityPose;
940
941 if (!checkXrResult(xrLocateSpace(stageSpace, localSpace, predictedDisplayTime, &stageLocation))) {
942 qWarning("Failed to locate STAGE space in LOCAL space, in order to emulate LOCAL_FLOOR");
943 xrDestroySpace(localSpace);
944 xrDestroySpace(stageSpace);
945 return false;
946 }
947
948 xrDestroySpace(localSpace);
949 xrDestroySpace(stageSpace);
950
951 XrSpace newAppSpace = XR_NULL_HANDLE;
952 createInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
953 createInfo.poseInReferenceSpace.position.y = stageLocation.pose.position.y;
954 if (!checkXrResult(xrCreateReferenceSpace(m_session, &createInfo, &newAppSpace))) {
955 qWarning("Failed to recreate emulated LOCAL_FLOOR play space with latest floor estimate");
956 return false;
957 }
958
959 xrDestroySpace(m_appSpace);
960 m_appSpace = newAppSpace;
961 m_referenceSpace = XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT;
962 emit q->referenceSpaceChanged();
963
964 return true;
965}
966
967bool QQuick3DXrManagerPrivate::createSwapchains()
968{
969 Q_ASSERT(m_session != XR_NULL_HANDLE);
970 Q_ASSERT(m_configViews.isEmpty());
971 Q_ASSERT(m_swapchains.isEmpty());
972
973 XrSystemProperties systemProperties{};
974 systemProperties.type = XR_TYPE_SYSTEM_PROPERTIES;
975
976 XrSystemHandTrackingPropertiesEXT handTrackingSystemProperties{};
977 handTrackingSystemProperties.type = XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT;
978 systemProperties.next = &handTrackingSystemProperties;
979
980 if (!checkXrResult(xrGetSystemProperties(m_instance, m_systemId, &systemProperties))) {
981 qWarning("Failed to get OpenXR system properties");
982 return false;
983 }
984 qCDebug(lcQuick3DXr, "System Properties: Name=%s VendorId=%d", systemProperties.systemName, systemProperties.vendorId);
985 qCDebug(lcQuick3DXr, "System Graphics Properties: MaxWidth=%d MaxHeight=%d MaxLayers=%d",
986 systemProperties.graphicsProperties.maxSwapchainImageWidth,
987 systemProperties.graphicsProperties.maxSwapchainImageHeight,
988 systemProperties.graphicsProperties.maxLayerCount);
989 qCDebug(lcQuick3DXr, "System Tracking Properties: OrientationTracking=%s PositionTracking=%s",
990 systemProperties.trackingProperties.orientationTracking == XR_TRUE ? "True" : "False",
991 systemProperties.trackingProperties.positionTracking == XR_TRUE ? "True" : "False");
992 qCDebug(lcQuick3DXr, "System Hand Tracking Properties: handTracking=%s",
993 handTrackingSystemProperties.supportsHandTracking == XR_TRUE ? "True" : "False");
994
995 quint32 viewCount;
996 if (!checkXrResult(xrEnumerateViewConfigurationViews(m_instance,
997 m_systemId,
998 m_viewConfigType,
999 0,
1000 &viewCount,
1001 nullptr)))
1002 {
1003 qWarning("Failed to enumerate view configurations");
1004 return false;
1005 }
1006 m_configViews.resize(viewCount, {XR_TYPE_VIEW_CONFIGURATION_VIEW, nullptr, 0, 0, 0, 0, 0, 0});
1007 if (!checkXrResult(xrEnumerateViewConfigurationViews(m_instance,
1008 m_systemId,
1009 m_viewConfigType,
1010 viewCount,
1011 &viewCount,
1012 m_configViews.data())))
1013 {
1014 qWarning("Failed to enumerate view configurations");
1015 return false;
1016 }
1017 m_views.resize(viewCount, {XR_TYPE_VIEW, nullptr, {}, {}});
1018 m_projectionLayerViews.resize(viewCount, {});
1019 m_layerDepthInfos.resize(viewCount, {});
1020
1021 // Create the swapchain and get the images.
1022 if (viewCount > 0) {
1023 // Select a swapchain format.
1024 uint32_t swapchainFormatCount;
1025 if (!checkXrResult(xrEnumerateSwapchainFormats(m_session, 0, &swapchainFormatCount, nullptr))) {
1026 qWarning("Failed to enumerate swapchain formats");
1027 return false;
1028 }
1029 QVector<int64_t> swapchainFormats(swapchainFormatCount);
1030 if (!checkXrResult(xrEnumerateSwapchainFormats(m_session,
1031 swapchainFormats.size(),
1032 &swapchainFormatCount,
1033 swapchainFormats.data())))
1034 {
1035 qWarning("Failed to enumerate swapchain formats");
1036 return false;
1037 }
1038
1039 Q_ASSERT(static_cast<qsizetype>(swapchainFormatCount) == swapchainFormats.size());
1040 m_colorSwapchainFormat = m_graphics->colorSwapchainFormat(swapchainFormats);
1041 if (m_compositionLayerDepthSupported)
1042 m_depthSwapchainFormat = m_graphics->depthSwapchainFormat(swapchainFormats);
1043
1044 // Print swapchain formats and the selected one.
1045 {
1046 QString swapchainFormatsString;
1047 for (int64_t format : swapchainFormats) {
1048 const bool selectedColor = format == m_colorSwapchainFormat;
1049 const bool selectedDepth = format == m_depthSwapchainFormat;
1050 swapchainFormatsString += u" ";
1051 if (selectedColor)
1052 swapchainFormatsString += u"[";
1053 else if (selectedDepth)
1054 swapchainFormatsString += u"<";
1055 swapchainFormatsString += QString::number(format);
1056 if (selectedColor)
1057 swapchainFormatsString += u"]";
1058 else if (selectedDepth)
1059 swapchainFormatsString += u">";
1060 }
1061 qCDebug(lcQuick3DXr, "Swapchain formats: %s", qPrintable(swapchainFormatsString));
1062 }
1063
1064 const XrViewConfigurationView &vp = m_configViews[0]; // use the first view for all views, the sizes should be the same
1065
1066 // sampleCount for the XrSwapchain is always 1. We could take m_samples
1067 // here, clamp it to vp.maxSwapchainSampleCount, and pass it in to the
1068 // swapchain to get multisample textures (or a multisample texture
1069 // array) out of the swapchain. This we do not do, because it was only
1070 // supported with 1 out of 5 OpenXR(+streaming) combination tested on
1071 // the Quest 3. In most cases, incl. Quest 3 native Android,
1072 // maxSwapchainSampleCount is 1. Therefore, we do MSAA on our own, and
1073 // do not rely on the XrSwapchain for this.
1074
1075 if (m_multiviewRendering) {
1076 // Create a single swapchain with array size > 1
1077 XrSwapchainCreateInfo swapchainCreateInfo{};
1078 swapchainCreateInfo.type = XR_TYPE_SWAPCHAIN_CREATE_INFO;
1079 swapchainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT;
1080 swapchainCreateInfo.format = m_colorSwapchainFormat;
1081 swapchainCreateInfo.sampleCount = 1; // we do MSAA on our own, do not need ms textures from the swapchain
1082 swapchainCreateInfo.width = vp.recommendedImageRectWidth;
1083 swapchainCreateInfo.height = vp.recommendedImageRectHeight;
1084 swapchainCreateInfo.faceCount = 1;
1085 swapchainCreateInfo.arraySize = viewCount;
1086 swapchainCreateInfo.mipCount = 1;
1087
1088 qCDebug(lcQuick3DXr, "Creating multiview swapchain for %u view(s) with dimensions Width=%d Height=%d SampleCount=%d Format=%llx",
1089 viewCount,
1090 vp.recommendedImageRectWidth,
1091 vp.recommendedImageRectHeight,
1092 1,
1093 static_cast<long long unsigned int>(m_colorSwapchainFormat));
1094
1095 Swapchain swapchain;
1096 swapchain.width = swapchainCreateInfo.width;
1097 swapchain.height = swapchainCreateInfo.height;
1098 swapchain.arraySize = swapchainCreateInfo.arraySize;
1099 if (checkXrResult(xrCreateSwapchain(m_session, &swapchainCreateInfo, &swapchain.handle))) {
1100 uint32_t imageCount = 0;
1101 if (!checkXrResult(xrEnumerateSwapchainImages(swapchain.handle, 0, &imageCount, nullptr))) {
1102 qWarning("Failed to enumerate swapchain images");
1103 return false;
1104 }
1105
1106 auto swapchainImages = m_graphics->allocateSwapchainImages(imageCount, swapchain.handle);
1107 if (!checkXrResult(xrEnumerateSwapchainImages(swapchain.handle, imageCount, &imageCount, swapchainImages[0]))) {
1108 qWarning("Failed to enumerate swapchain images");
1109 return false;
1110 }
1111
1112 m_swapchains.append(swapchain);
1113 m_swapchainImages.insert(swapchain.handle, swapchainImages);
1114 } else {
1115 qWarning("xrCreateSwapchain failed (multiview)");
1116 return false;
1117 }
1118
1119 // Create the depth swapchain always when
1120 // XR_KHR_composition_layer_depth is supported. If we are going to
1121 // submit (use the depth image), that's a different question, and is
1122 // dynamically controlled by the user.
1123 if (m_compositionLayerDepthSupported && m_depthSwapchainFormat > 0) {
1124 swapchainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
1125 swapchainCreateInfo.format = m_depthSwapchainFormat;
1126 if (checkXrResult(xrCreateSwapchain(m_session, &swapchainCreateInfo, &swapchain.handle))) {
1127 uint32_t imageCount = 0;
1128 if (!checkXrResult(xrEnumerateSwapchainImages(swapchain.handle, 0, &imageCount, nullptr))) {
1129 qWarning("Failed to enumerate depth swapchain images");
1130 return false;
1131 }
1132
1133 auto swapchainImages = m_graphics->allocateSwapchainImages(imageCount, swapchain.handle);
1134 if (!checkXrResult(xrEnumerateSwapchainImages(swapchain.handle, imageCount, &imageCount, swapchainImages[0]))) {
1135 qWarning("Failed to enumerate depth swapchain images");
1136 return false;
1137 }
1138
1139 m_depthSwapchains.append(swapchain);
1140 m_depthSwapchainImages.insert(swapchain.handle, swapchainImages);
1141 } else {
1142 qWarning("xrCreateSwapchain failed for depth swapchain (multiview)");
1143 return false;
1144 }
1145 }
1146 } else {
1147 // Create a swapchain for each view.
1148 for (uint32_t i = 0; i < viewCount; i++) {
1149 qCDebug(lcQuick3DXr, "Creating swapchain for view %u with dimensions Width=%d Height=%d SampleCount=%d Format=%llx",
1150 i,
1151 vp.recommendedImageRectWidth,
1152 vp.recommendedImageRectHeight,
1153 1,
1154 static_cast<long long unsigned int>(m_colorSwapchainFormat));
1155
1156 // Create the swapchain.
1157 XrSwapchainCreateInfo swapchainCreateInfo{};
1158 swapchainCreateInfo.type = XR_TYPE_SWAPCHAIN_CREATE_INFO;
1159 swapchainCreateInfo.arraySize = 1;
1160 swapchainCreateInfo.format = m_colorSwapchainFormat;
1161 swapchainCreateInfo.width = vp.recommendedImageRectWidth;
1162 swapchainCreateInfo.height = vp.recommendedImageRectHeight;
1163 swapchainCreateInfo.mipCount = 1;
1164 swapchainCreateInfo.faceCount = 1;
1165 swapchainCreateInfo.sampleCount = 1; // we do MSAA on our own, do not need ms textures from the swapchain
1166 swapchainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT;
1167 Swapchain swapchain;
1168 swapchain.width = swapchainCreateInfo.width;
1169 swapchain.height = swapchainCreateInfo.height;
1170 if (checkXrResult(xrCreateSwapchain(m_session, &swapchainCreateInfo, &swapchain.handle))) {
1171 uint32_t imageCount = 0;
1172 if (!checkXrResult(xrEnumerateSwapchainImages(swapchain.handle, 0, &imageCount, nullptr))) {
1173 qWarning("Failed to enumerate swapchain images");
1174 return false;
1175 }
1176
1177 auto swapchainImages = m_graphics->allocateSwapchainImages(imageCount, swapchain.handle);
1178 if (!checkXrResult(xrEnumerateSwapchainImages(swapchain.handle, imageCount, &imageCount, swapchainImages[0]))) {
1179 qWarning("Failed to enumerate swapchain images");
1180 return false;
1181 }
1182
1183 m_swapchains.append(swapchain);
1184 m_swapchainImages.insert(swapchain.handle, swapchainImages);
1185 } else {
1186 qWarning("xrCreateSwapchain failed (view %u)", viewCount);
1187 return false;
1188 }
1189
1190 if (m_compositionLayerDepthSupported && m_depthSwapchainFormat > 0) {
1191 swapchainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
1192 swapchainCreateInfo.format = m_depthSwapchainFormat;
1193 if (checkXrResult(xrCreateSwapchain(m_session, &swapchainCreateInfo, &swapchain.handle))) {
1194 uint32_t imageCount = 0;
1195 if (!checkXrResult(xrEnumerateSwapchainImages(swapchain.handle, 0, &imageCount, nullptr))) {
1196 qWarning("Failed to enumerate depth swapchain images");
1197 return false;
1198 }
1199
1200 auto swapchainImages = m_graphics->allocateSwapchainImages(imageCount, swapchain.handle);
1201 if (!checkXrResult(xrEnumerateSwapchainImages(swapchain.handle, imageCount, &imageCount, swapchainImages[0]))) {
1202 qWarning("Failed to enumerate depth swapchain images");
1203 return false;
1204 }
1205
1206 m_depthSwapchains.append(swapchain);
1207 m_depthSwapchainImages.insert(swapchain.handle, swapchainImages);
1208 } else {
1209 qWarning("xrCreateSwapchain failed for depth swapchain (view %u)", viewCount);
1210 return false;
1211 }
1212 }
1213 }
1214 }
1215
1216 if (m_multiviewRendering) {
1217 if (m_swapchains.isEmpty())
1218 return false;
1219 if (m_compositionLayerDepthSupported && m_depthSwapchains.isEmpty())
1220 return false;
1221 } else {
1222 if (m_swapchains.count() != qsizetype(viewCount))
1223 return false;
1224 if (m_compositionLayerDepthSupported && m_depthSwapchains.count() != qsizetype(viewCount))
1225 return false;
1226 }
1227
1228 // Setup the projection layer views.
1229 for (uint32_t i = 0; i < viewCount; ++i) {
1230 m_projectionLayerViews[i].type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW;
1231 m_projectionLayerViews[i].next = nullptr;
1232 m_projectionLayerViews[i].subImage.swapchain = m_swapchains[0].handle; // for non-multiview this gets overwritten later
1233 m_projectionLayerViews[i].subImage.imageArrayIndex = i; // this too
1234 m_projectionLayerViews[i].subImage.imageRect.offset.x = 0;
1235 m_projectionLayerViews[i].subImage.imageRect.offset.y = 0;
1236 m_projectionLayerViews[i].subImage.imageRect.extent.width = vp.recommendedImageRectWidth;
1237 m_projectionLayerViews[i].subImage.imageRect.extent.height = vp.recommendedImageRectHeight;
1238
1239 if (m_compositionLayerDepthSupported) {
1240 m_layerDepthInfos[i].type = XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR;
1241 m_layerDepthInfos[i].next = nullptr;
1242 m_layerDepthInfos[i].subImage.swapchain = m_depthSwapchains[0].handle; // for non-multiview this gets overwritten later
1243 m_layerDepthInfos[i].subImage.imageArrayIndex = i; // this too
1244 m_layerDepthInfos[i].subImage.imageRect.offset.x = 0;
1245 m_layerDepthInfos[i].subImage.imageRect.offset.y = 0;
1246 m_layerDepthInfos[i].subImage.imageRect.extent.width = vp.recommendedImageRectWidth;
1247 m_layerDepthInfos[i].subImage.imageRect.extent.height = vp.recommendedImageRectHeight;
1248 }
1249 }
1250 }
1251
1252 if (m_foveationExtensionSupported)
1253 setupMetaQuestFoveation();
1254
1255 return true;
1256}
1257
1259{
1260 if (m_samples == samples)
1261 return;
1262
1263 m_samples = samples;
1264
1265 // No need to do anything more here (such as destroying and recreating the
1266 // XrSwapchain) since we do not do MSAA through the swapchain.
1267}
1268
1270{
1271 return m_enabledExtensions;
1272}
1273
1275{
1276 return m_runtimeName;
1277}
1278
1280{
1281 return m_runtimeVersion;
1282}
1283
1285{
1286 Q_Q(QQuick3DXrManager);
1287 QRhi *rhi = q->m_renderControl->rhi();
1288 if (m_multiviewRendering == enable || !rhi)
1289 return;
1290 if (enable && !rhi->isFeatureSupported(QRhi::MultiView)) {
1291 qWarning("Quick 3D XR: Multiview rendering was enabled, but is reported as unsupported from the current QRhi backend (%s)",
1292 rhi->backendName());
1293 return;
1294 }
1295 m_multiviewRendering = enable;
1296 qCDebug(lcQuick3DXr, "Multiview rendering %s", m_multiviewRendering ? "enabled" : "disabled");
1297 if (!m_swapchains.isEmpty()) {
1298 qCDebug(lcQuick3DXr, "OpenXR swapchain already exists, creating new one due to change in multiview mode");
1299 destroySwapchain();
1300 createSwapchains();
1301
1302 emit q->multiViewRenderingEnabledChanged();
1303 }
1304}
1305
1307{
1308 // FB passthrough is enabled
1309 if (m_fbPassthroughEnabled)
1310 return true;
1311
1312 // Blend mode passthrough is enabled (ALPHA_BLEND or ADDITIVE)
1313 return m_environmentBlendMode == XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND
1314 || m_environmentBlendMode == XR_ENVIRONMENT_BLEND_MODE_ADDITIVE;
1315}
1316
1318{
1319 m_fbPassthroughEnabled = enable;
1320 if (enable) {
1321 if (m_passthroughFeature == XR_NULL_HANDLE)
1322 createMetaQuestPassthrough();
1323 else
1324 startMetaQuestPassthrough();
1325
1326 if (m_passthroughLayer == XR_NULL_HANDLE)
1327 createMetaQuestPassthroughLayer();
1328 else
1329 resumeMetaQuestPassthroughLayer();
1330 } else {
1331 if (m_passthroughLayer)
1332 pauseMetaQuestPassthroughLayer();
1333 if (m_passthroughFeature)
1334 pauseMetaQuestPassthrough();
1335 }
1336}
1337
1339{
1340 if (enable) {
1341 if (m_blendModes.contains(XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND))
1342 m_environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND;
1343 else if (m_blendModes.contains(XR_ENVIRONMENT_BLEND_MODE_ADDITIVE))
1344 m_environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_ADDITIVE;
1345 else
1346 return false;
1347 } else {
1348 if (m_blendModes.contains(XR_ENVIRONMENT_BLEND_MODE_OPAQUE))
1349 m_environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
1350 else
1351 return false;
1352 }
1353 qCDebug(lcQuick3DXr, "Environment blend mode set to: %s", to_string(m_environmentBlendMode));
1354 return true;
1355}
1356
1358{
1361 return true;
1362 }
1363
1364 return setTransparentBlendMode(enable);
1365}
1366
1368{
1369 if (m_submitLayerDepth == enable)
1370 return;
1371
1372 if (m_compositionLayerDepthSupported) {
1373 if (enable)
1374 qCDebug(lcQuick3DXr, "Enabling submitLayerDepth");
1375
1376 m_submitLayerDepth = enable;
1377 }
1378}
1379
1380void QQuick3DXrManagerPrivate::setReferenceSpace(QtQuick3DXr::ReferenceSpace newReferenceSpace)
1381{
1382 XrReferenceSpaceType referenceSpace = getXrReferenceSpaceType(newReferenceSpace);
1383 if (m_referenceSpace == referenceSpace)
1384 return;
1385
1386 m_requestedReferenceSpace = referenceSpace;
1387
1388 // we do not emit a changed signal here because it hasn't
1389 // changed yet.
1390}
1391
1393{
1394 return getReferenceSpaceType(m_referenceSpace);
1395}
1396
1397void QQuick3DXrManagerPrivate::getDefaultClipDistances(float &nearClip, float &farClip) const
1398{
1399 // Hardcoded defaults
1400 nearClip = 1.0f;
1401 farClip = 10000.0f;
1402}
1403
1404void QQuick3DXrManagerPrivate::pollEvents(bool *exitRenderLoop, bool *requestRestart) {
1405 *exitRenderLoop = false;
1406 *requestRestart = false;
1407
1408 auto readNextEvent = [this]() {
1409 // It is sufficient to clear the just the XrEventDataBuffer header to
1410 // XR_TYPE_EVENT_DATA_BUFFER
1411 XrEventDataBaseHeader* baseHeader = reinterpret_cast<XrEventDataBaseHeader*>(&m_eventDataBuffer);
1412 *baseHeader = {XR_TYPE_EVENT_DATA_BUFFER, nullptr};
1413 const XrResult xr = xrPollEvent(m_instance, &m_eventDataBuffer);
1414 if (xr == XR_SUCCESS) {
1415 if (baseHeader->type == XR_TYPE_EVENT_DATA_EVENTS_LOST) {
1416 const XrEventDataEventsLost* const eventsLost = reinterpret_cast<const XrEventDataEventsLost*>(baseHeader);
1417 qCDebug(lcQuick3DXr, "%d events lost", eventsLost->lostEventCount);
1418 }
1419
1420 return baseHeader;
1421 }
1422
1423 return static_cast<XrEventDataBaseHeader*>(nullptr);
1424 };
1425
1426 auto handleSessionStateChangedEvent = [this](const XrEventDataSessionStateChanged& stateChangedEvent,
1427 bool* exitRenderLoop,
1428 bool* requestRestart) {
1429 const XrSessionState oldState = m_sessionState;
1430 m_sessionState = stateChangedEvent.state;
1431
1432 qCDebug(lcQuick3DXr, "XrEventDataSessionStateChanged: state %s->%s time=%lld",
1433 to_string(oldState),
1434 to_string(m_sessionState),
1435 static_cast<long long int>(stateChangedEvent.time));
1436
1437 if ((stateChangedEvent.session != XR_NULL_HANDLE) && (stateChangedEvent.session != m_session)) {
1438 qCDebug(lcQuick3DXr, "XrEventDataSessionStateChanged for unknown session");
1439 return;
1440 }
1441
1442 switch (m_sessionState) {
1443 case XR_SESSION_STATE_READY: {
1444 Q_ASSERT(m_session != XR_NULL_HANDLE);
1445 XrSessionBeginInfo sessionBeginInfo{};
1446 sessionBeginInfo.type = XR_TYPE_SESSION_BEGIN_INFO;
1447 sessionBeginInfo.primaryViewConfigurationType = m_viewConfigType;
1448 if (!checkXrResult(xrBeginSession(m_session, &sessionBeginInfo))) {
1449 qWarning("xrBeginSession failed");
1450 break;
1451 }
1452 m_sessionRunning = true;
1453 break;
1454 }
1455 case XR_SESSION_STATE_STOPPING: {
1456 Q_ASSERT(m_session != XR_NULL_HANDLE);
1457 m_sessionRunning = false;
1458 if (!checkXrResult(xrEndSession(m_session)))
1459 qWarning("xrEndSession failed");
1460 break;
1461 }
1462 case XR_SESSION_STATE_EXITING: {
1463 *exitRenderLoop = true;
1464 // Do not attempt to restart because user closed this session.
1465 *requestRestart = false;
1466 break;
1467 }
1468 case XR_SESSION_STATE_LOSS_PENDING: {
1469 *exitRenderLoop = true;
1470 // Poll for a new instance.
1471 *requestRestart = true;
1472 break;
1473 }
1474 default:
1475 break;
1476 }
1477 };
1478
1479 // Process all pending messages.
1480 while (const XrEventDataBaseHeader* event = readNextEvent()) {
1481 switch (event->type) {
1482 case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: {
1483 const auto& instanceLossPending = *reinterpret_cast<const XrEventDataInstanceLossPending*>(event);
1484 qCDebug(lcQuick3DXr, "XrEventDataInstanceLossPending by %lld", static_cast<long long int>(instanceLossPending.lossTime));
1485 *exitRenderLoop = true;
1486 *requestRestart = true;
1487 return;
1488 }
1489 case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: {
1490 auto sessionStateChangedEvent = *reinterpret_cast<const XrEventDataSessionStateChanged*>(event);
1491 handleSessionStateChangedEvent(sessionStateChangedEvent, exitRenderLoop, requestRestart);
1492 break;
1493 }
1494 case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED:
1495 break;
1496 case XR_TYPE_EVENT_DATA_SPACE_SET_STATUS_COMPLETE_FB:
1497 case XR_TYPE_EVENT_DATA_SPACE_QUERY_RESULTS_AVAILABLE_FB:
1498 case XR_TYPE_EVENT_DATA_SPACE_QUERY_COMPLETE_FB:
1499 case XR_TYPE_EVENT_DATA_SCENE_CAPTURE_COMPLETE_FB:
1500 // Handle these events in the space extension
1501 if (m_spaceExtension)
1502 m_spaceExtension->handleEvent(event);
1503 break;
1504 case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING:
1505 default: {
1506 qCDebug(lcQuick3DXr, "Ignoring event type %d", event->type);
1507 break;
1508 }
1509 }
1510 }
1511}
1512
1513bool QQuick3DXrManagerPrivate::renderLayer(XrTime predictedDisplayTime,
1514 XrDuration predictedDisplayPeriod,
1515 XrCompositionLayerProjection &layer)
1516{
1517 Q_Q(QQuick3DXrManager);
1518 auto *xrOrigin = q->m_xrOrigin;
1519 auto *animationDriver = q->m_animationDriver;
1520 auto *renderControl = q->m_renderControl;
1521
1522 XrResult res;
1523
1524 XrViewState viewState{};
1525 viewState.type = XR_TYPE_VIEW_STATE;
1526 quint32 viewCapacityInput = m_views.size();
1527 quint32 viewCountOutput;
1528
1529 // Check if we need to update the app space before we use it
1530 updateAppSpace(predictedDisplayTime);
1531
1532 XrViewLocateInfo viewLocateInfo{};
1533 viewLocateInfo.type = XR_TYPE_VIEW_LOCATE_INFO;
1534 viewLocateInfo.viewConfigurationType = m_viewConfigType;
1535 viewLocateInfo.displayTime = predictedDisplayTime;
1536 viewLocateInfo.space = m_appSpace;
1537
1538 res = xrLocateViews(m_session, &viewLocateInfo, &viewState, viewCapacityInput, &viewCountOutput, m_views.data());
1539 if (XR_UNQUALIFIED_SUCCESS(res)) {
1540 Q_ASSERT(viewCountOutput == viewCapacityInput);
1541 Q_ASSERT(static_cast<qsizetype>(viewCountOutput) == m_configViews.size());
1542 Q_ASSERT(static_cast<qsizetype>(viewCountOutput) == m_projectionLayerViews.size());
1543 Q_ASSERT(m_multiviewRendering ? viewCountOutput == m_swapchains[0].arraySize : static_cast<qsizetype>(viewCountOutput) == m_swapchains.size());
1544
1545 // Update the camera/head position
1546 XrSpaceLocation location{};
1547 location.type = XR_TYPE_SPACE_LOCATION;
1548 if (checkXrResult(xrLocateSpace(m_viewSpace, m_appSpace, predictedDisplayTime, &location))) {
1549 QVector3D position = QVector3D(location.pose.position.x,
1550 location.pose.position.y,
1551 location.pose.position.z) * 100.0f; // convert m to cm
1552 QQuaternion rotation(location.pose.orientation.w,
1553 location.pose.orientation.x,
1554 location.pose.orientation.y,
1555 location.pose.orientation.z);
1556
1557 xrOrigin->updateTrackedCamera(position, rotation);
1558 }
1559
1560 // Set the hand positions
1561 if (QSSG_GUARD(m_inputManager != nullptr))
1562 QQuick3DXrInputManagerPrivate::get(m_inputManager)->updatePoses(predictedDisplayTime, m_appSpace);
1563
1564 // Spatial Anchors
1565 if (m_spaceExtension)
1566 m_spaceExtension->updateAnchors(predictedDisplayTime, m_appSpace);
1567
1568 if (m_handtrackingExtensionSupported && m_inputManager)
1569 QQuick3DXrInputManagerPrivate::get(m_inputManager)->updateHandtracking(predictedDisplayTime, m_appSpace, m_handtrackingAimExtensionSupported);
1570
1571 // Before rendering individual views, advance the animation driver once according
1572 // to the expected display time
1573
1574 const qint64 displayPeriodMS = predictedDisplayPeriod / 1000000;
1575 const qint64 displayDeltaMS = (predictedDisplayTime - m_previousTime) / 1000000;
1576
1577 if (m_previousTime == 0 || !animationDriver->isRunning()) {
1578 animationDriver->setStep(displayPeriodMS);
1579 } else {
1580 if (displayDeltaMS > displayPeriodMS)
1581 animationDriver->setStep(displayPeriodMS);
1582 else
1583 animationDriver->setStep(displayDeltaMS);
1584 animationDriver->advance();
1585 }
1586 m_previousTime = predictedDisplayTime;
1587
1588#if QT_CONFIG(graphicsframecapture)
1589 if (m_frameCapture)
1590 m_frameCapture->startCaptureFrame();
1591#endif
1592
1593 if (m_submitLayerDepth && m_samples > 1) {
1594 if (!renderControl->rhi()->isFeatureSupported(QRhi::ResolveDepthStencil)) {
1595 static bool warned = false;
1596 if (!warned) {
1597 warned = true;
1598 qWarning("Quick3D XR: Submitting depth buffer with MSAA cannot be enabled"
1599 " when depth-stencil resolve is not supported by the underlying 3D API (%s)",
1600 renderControl->rhi()->backendName());
1601 }
1602 m_submitLayerDepth = false;
1603 }
1604 }
1605
1606 if (m_multiviewRendering) {
1607 const Swapchain swapchain = m_swapchains[0];
1608
1609 // Acquire the swapchain image array
1610 XrSwapchainImageAcquireInfo acquireInfo{};
1611 acquireInfo.type = XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO;
1612 uint32_t swapchainImageIndex = 0;
1613 if (!checkXrResult(xrAcquireSwapchainImage(swapchain.handle, &acquireInfo, &swapchainImageIndex))) {
1614 qWarning("Failed to acquire swapchain image (multiview)");
1615 return false;
1616 }
1617 XrSwapchainImageWaitInfo waitInfo{};
1618 waitInfo.type = XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO;
1619 waitInfo.timeout = XR_INFINITE_DURATION;
1620 if (!checkXrResult(xrWaitSwapchainImage(swapchain.handle, &waitInfo))) {
1621 qWarning("Failed to wait for swapchain image (multiview)");
1622 return false;
1623 }
1624 XrSwapchainImageBaseHeader *swapchainImage = m_swapchainImages[swapchain.handle][swapchainImageIndex];
1625
1626 XrSwapchainImageBaseHeader *depthSwapchainImage = nullptr;
1627 if (m_submitLayerDepth && !m_depthSwapchains.isEmpty()) {
1628 if (checkXrResult(xrAcquireSwapchainImage(m_depthSwapchains[0].handle, &acquireInfo, &swapchainImageIndex))) {
1629 if (checkXrResult(xrWaitSwapchainImage(m_depthSwapchains[0].handle, &waitInfo)))
1630 depthSwapchainImage = m_depthSwapchainImages[m_depthSwapchains[0].handle][swapchainImageIndex];
1631 else
1632 qWarning("Failed to wait for depth swapchain image (multiview)");
1633 } else {
1634 qWarning("Failed to acquire depth swapchain image (multiview)");
1635 }
1636 }
1637
1638 // First update both cameras with the latest view information and
1639 // then set them on the viewport (since this is going to be
1640 // multiview rendering).
1641 for (uint32_t i = 0; i < viewCountOutput; i++) {
1642 // subImage.swapchain and imageArrayIndex are already set and correct
1643 m_projectionLayerViews[i].pose = m_views[i].pose;
1644 m_projectionLayerViews[i].fov = m_views[i].fov;
1645 }
1646 updateCameraMultiview(0, viewCountOutput);
1647
1648 // Perform the rendering. In multiview mode it is done just once,
1649 // targeting all the views (outputting simultaneously to all texture
1650 // array layers). The subImage dimensions are the same, that's why
1651 // passing in the first layerView's subImage works.
1652 doRender(m_projectionLayerViews[0].subImage,
1653 swapchainImage,
1654 depthSwapchainImage);
1655
1656 for (uint32_t i = 0; i < viewCountOutput; i++) {
1657 if (m_submitLayerDepth) {
1658 m_layerDepthInfos[i].minDepth = 0;
1659 m_layerDepthInfos[i].maxDepth = 1;
1660 QQuick3DXrEyeCamera *cam = xrOrigin ? xrOrigin->eyeCamera(i) : nullptr;
1661 m_layerDepthInfos[i].nearZ = cam ? cam->clipNear() : 1.0f;
1662 m_layerDepthInfos[i].farZ = cam ? cam->clipFar() : 10000.0f;
1663 m_projectionLayerViews[i].next = &m_layerDepthInfos[i];
1664 } else {
1665 m_projectionLayerViews[i].next = nullptr;
1666 }
1667 }
1668
1669 // release the swapchain image array
1670 XrSwapchainImageReleaseInfo releaseInfo{};
1671 releaseInfo.type = XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO;
1672 if (!checkXrResult(xrReleaseSwapchainImage(swapchain.handle, &releaseInfo)))
1673 qWarning("Failed to release swapchain image");
1674 if (depthSwapchainImage) {
1675 if (!checkXrResult(xrReleaseSwapchainImage(m_depthSwapchains[0].handle, &releaseInfo)))
1676 qWarning("Failed to release depth swapchain image");
1677 }
1678 } else {
1679 for (uint32_t i = 0; i < viewCountOutput; i++) {
1680 // Each view has a separate swapchain which is acquired, rendered to, and released.
1681 const Swapchain viewSwapchain = m_swapchains[i];
1682
1683 // Render view to the appropriate part of the swapchain image.
1684 XrSwapchainImageAcquireInfo acquireInfo{};
1685 acquireInfo.type = XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO;
1686 uint32_t swapchainImageIndex = 0;
1687 if (!checkXrResult(xrAcquireSwapchainImage(viewSwapchain.handle, &acquireInfo, &swapchainImageIndex))) {
1688 qWarning("Failed to acquire swapchain image");
1689 return false;
1690 }
1691 XrSwapchainImageWaitInfo waitInfo{};
1692 waitInfo.type = XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO;
1693 waitInfo.timeout = XR_INFINITE_DURATION;
1694 if (!checkXrResult(xrWaitSwapchainImage(viewSwapchain.handle, &waitInfo))) {
1695 qWarning("Failed to wait for swapchain image");
1696 return false;
1697 }
1698 XrSwapchainImageBaseHeader *swapchainImage = m_swapchainImages[viewSwapchain.handle][swapchainImageIndex];
1699
1700 XrSwapchainImageBaseHeader *depthSwapchainImage = nullptr;
1701 if (m_submitLayerDepth && !m_depthSwapchains.isEmpty()) {
1702 if (checkXrResult(xrAcquireSwapchainImage(m_depthSwapchains[i].handle, &acquireInfo, &swapchainImageIndex))) {
1703 if (checkXrResult(xrWaitSwapchainImage(m_depthSwapchains[i].handle, &waitInfo)))
1704 depthSwapchainImage = m_depthSwapchainImages[m_depthSwapchains[i].handle][swapchainImageIndex];
1705 else
1706 qWarning("Failed to wait for depth swapchain image");
1707 } else {
1708 qWarning("Failed to acquire depth swapchain image");
1709 }
1710 }
1711
1712 m_projectionLayerViews[i].subImage.swapchain = viewSwapchain.handle;
1713 m_projectionLayerViews[i].subImage.imageArrayIndex = 0;
1714 m_projectionLayerViews[i].pose = m_views[i].pose;
1715 m_projectionLayerViews[i].fov = m_views[i].fov;
1716
1717 updateCameraNonMultiview(i, m_projectionLayerViews[i]);
1718
1719 doRender(m_projectionLayerViews[i].subImage,
1720 swapchainImage,
1721 depthSwapchainImage);
1722
1723 if (depthSwapchainImage) {
1724 m_layerDepthInfos[i].subImage.swapchain = m_depthSwapchains[i].handle;
1725 m_layerDepthInfos[i].subImage.imageArrayIndex = 0;
1726 m_layerDepthInfos[i].minDepth = 0;
1727 m_layerDepthInfos[i].maxDepth = 1;
1728 QQuick3DXrEyeCamera *cam = xrOrigin ? xrOrigin->eyeCamera(i) : nullptr;
1729 m_layerDepthInfos[i].nearZ = cam ? cam->clipNear() : 1.0f;
1730 m_layerDepthInfos[i].farZ = cam ? cam->clipFar() : 10000.0f;
1731 m_projectionLayerViews[i].next = &m_layerDepthInfos[i];
1732 } else {
1733 m_projectionLayerViews[i].next = nullptr;
1734 }
1735
1736 XrSwapchainImageReleaseInfo releaseInfo{};
1737 releaseInfo.type = XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO;
1738 if (!checkXrResult(xrReleaseSwapchainImage(viewSwapchain.handle, &releaseInfo)))
1739 qWarning("Failed to release swapchain image");
1740 if (depthSwapchainImage) {
1741 if (!checkXrResult(xrReleaseSwapchainImage(m_depthSwapchains[i].handle, &releaseInfo)))
1742 qWarning("Failed to release depth swapchain image");
1743 }
1744 }
1745 }
1746
1747#if QT_CONFIG(graphicsframecapture)
1748 if (m_frameCapture)
1749 m_frameCapture->endCaptureFrame();
1750#endif
1751
1752 layer.space = m_appSpace;
1753 layer.viewCount = (uint32_t)m_projectionLayerViews.size();
1754 layer.views = m_projectionLayerViews.data();
1755 return true;
1756 }
1757
1758 qCDebug(lcQuick3DXr, "xrLocateViews returned qualified success code: %s", to_string(res));
1759 return false;
1760}
1761
1762void QQuick3DXrManagerPrivate::doRender(const XrSwapchainSubImage &subImage,
1763 const XrSwapchainImageBaseHeader *swapchainImage,
1764 const XrSwapchainImageBaseHeader *depthSwapchainImage)
1765{
1766 Q_Q(QQuick3DXrManager);
1767
1768 auto *quickWindow = q->m_quickWindow;
1769 auto *renderControl = q->m_renderControl;
1770
1771 const int arraySize = m_multiviewRendering ? m_swapchains[0].arraySize : 1;
1772 quickWindow->setRenderTarget(m_graphics->renderTarget(subImage,
1773 swapchainImage,
1774 m_colorSwapchainFormat,
1775 m_samples,
1776 arraySize,
1777 depthSwapchainImage,
1778 m_depthSwapchainFormat));
1779
1780 quickWindow->setGeometry(0,
1781 0,
1782 subImage.imageRect.extent.width,
1783 subImage.imageRect.extent.height);
1784 quickWindow->contentItem()->setSize(QSizeF(subImage.imageRect.extent.width,
1785 subImage.imageRect.extent.height));
1786
1787 renderControl->polishItems();
1788 renderControl->beginFrame();
1789 renderControl->sync();
1790 renderControl->render();
1791 renderControl->endFrame();
1792
1793 // With multiview this indicates that the frame with both eyes is ready from
1794 // the 3D APIs perspective. Without multiview this is done - and so the
1795 // signal is emitted - multiple times (twice) per "frame" (eye).
1796 QRhiRenderTarget *rt = QQuickWindowPrivate::get(quickWindow)->activeCustomRhiRenderTarget();
1797 if (rt->resourceType() == QRhiResource::TextureRenderTarget && static_cast<QRhiTextureRenderTarget *>(rt)->description().colorAttachmentAt(0)->texture())
1798 emit q->frameReady();
1799}
1800
1801void QQuick3DXrManagerPrivate::setupMetaQuestColorSpaces()
1802{
1803 PFN_xrEnumerateColorSpacesFB pfnxrEnumerateColorSpacesFB = NULL;
1804 OpenXRHelpers::resolveXrFunction(m_instance, "xrEnumerateColorSpacesFB", (PFN_xrVoidFunction*)(&pfnxrEnumerateColorSpacesFB));
1805 if (!pfnxrEnumerateColorSpacesFB) // simulator
1806 return;
1807
1808 uint32_t colorSpaceCountOutput = 0;
1809 if (!checkXrResult(pfnxrEnumerateColorSpacesFB(m_session, 0, &colorSpaceCountOutput, nullptr))) {
1810 qWarning("Failed to enumerate color spaces");
1811 return;
1812 }
1813
1814 XrColorSpaceFB* colorSpaces =
1815 (XrColorSpaceFB*)malloc(colorSpaceCountOutput * sizeof(XrColorSpaceFB));
1816
1817 if (!checkXrResult(pfnxrEnumerateColorSpacesFB(m_session, colorSpaceCountOutput, &colorSpaceCountOutput, colorSpaces))) {
1818 qWarning("Failed to enumerate color spaces");
1819 return;
1820 }
1821
1822 qCDebug(lcQuick3DXr, "Supported color spaces:");
1823 for (uint32_t i = 0; i < colorSpaceCountOutput; i++) {
1824 qCDebug(lcQuick3DXr,"%d:%d", i, colorSpaces[i]);
1825 }
1826
1827 const XrColorSpaceFB requestColorSpace = XR_COLOR_SPACE_QUEST_FB;
1828
1829 PFN_xrSetColorSpaceFB pfnxrSetColorSpaceFB = NULL;
1830 OpenXRHelpers::resolveXrFunction(m_instance, "xrSetColorSpaceFB", (PFN_xrVoidFunction*)(&pfnxrSetColorSpaceFB));
1831
1832 if (!checkXrResult(pfnxrSetColorSpaceFB(m_session, requestColorSpace)))
1833 qWarning("Failed to set color space");
1834
1835 free(colorSpaces);
1836}
1837
1838void QQuick3DXrManagerPrivate::setupMetaQuestRefreshRates()
1839{
1840 PFN_xrEnumerateDisplayRefreshRatesFB pfnxrEnumerateDisplayRefreshRatesFB = NULL;
1841 OpenXRHelpers::resolveXrFunction(m_instance, "xrEnumerateDisplayRefreshRatesFB", (PFN_xrVoidFunction*)(&pfnxrEnumerateDisplayRefreshRatesFB));
1842 if (!pfnxrEnumerateDisplayRefreshRatesFB)
1843 return;
1844
1845 uint32_t numSupportedDisplayRefreshRates;
1846 QVector<float> supportedDisplayRefreshRates;
1847
1848 if (!checkXrResult(pfnxrEnumerateDisplayRefreshRatesFB(m_session, 0, &numSupportedDisplayRefreshRates, nullptr))) {
1849 qWarning("Failed to enumerate display refresh rates");
1850 return;
1851 }
1852
1853 supportedDisplayRefreshRates.resize(numSupportedDisplayRefreshRates);
1854
1855 if (!checkXrResult(pfnxrEnumerateDisplayRefreshRatesFB(
1856 m_session,
1857 numSupportedDisplayRefreshRates,
1858 &numSupportedDisplayRefreshRates,
1859 supportedDisplayRefreshRates.data())))
1860 {
1861 qWarning("Failed to enumerate display refresh rates");
1862 return;
1863 }
1864
1865 qCDebug(lcQuick3DXr, "Supported Refresh Rates:");
1866 for (uint32_t i = 0; i < numSupportedDisplayRefreshRates; i++) {
1867 qCDebug(lcQuick3DXr, "%d:%f", i, supportedDisplayRefreshRates[i]);
1868 }
1869
1870 PFN_xrGetDisplayRefreshRateFB pfnGetDisplayRefreshRate;
1871 OpenXRHelpers::resolveXrFunction(m_instance, "xrGetDisplayRefreshRateFB", (PFN_xrVoidFunction*)(&pfnGetDisplayRefreshRate));
1872
1873 float currentDisplayRefreshRate = 0.0f;
1874 if (!checkXrResult(pfnGetDisplayRefreshRate(m_session, &currentDisplayRefreshRate)))
1875 qWarning("Failed to get display refresh rate");
1876
1877 qCDebug(lcQuick3DXr, "Current System Display Refresh Rate: %f", currentDisplayRefreshRate);
1878
1879 PFN_xrRequestDisplayRefreshRateFB pfnRequestDisplayRefreshRate;
1880 OpenXRHelpers::resolveXrFunction(m_instance, "xrRequestDisplayRefreshRateFB", (PFN_xrVoidFunction*)(&pfnRequestDisplayRefreshRate));
1881
1882 // Test requesting the system default.
1883 if (!checkXrResult(pfnRequestDisplayRefreshRate(m_session, 0.0f)))
1884 qWarning("Failed to request display refresh rate");
1885
1886 qCDebug(lcQuick3DXr, "Requesting system default display refresh rate");
1887}
1888
1889void QQuick3DXrManagerPrivate::setupMetaQuestFoveation()
1890{
1891 PFN_xrCreateFoveationProfileFB pfnCreateFoveationProfileFB;
1892 OpenXRHelpers::resolveXrFunction(m_instance, "xrCreateFoveationProfileFB", (PFN_xrVoidFunction*)(&pfnCreateFoveationProfileFB));
1893 if (!pfnCreateFoveationProfileFB) // simulator
1894 return;
1895
1896 PFN_xrDestroyFoveationProfileFB pfnDestroyFoveationProfileFB;
1897 OpenXRHelpers::resolveXrFunction(m_instance, "xrDestroyFoveationProfileFB", (PFN_xrVoidFunction*)(&pfnDestroyFoveationProfileFB));
1898
1899 PFN_xrUpdateSwapchainFB pfnUpdateSwapchainFB;
1900 OpenXRHelpers::resolveXrFunction(m_instance, "xrUpdateSwapchainFB", (PFN_xrVoidFunction*)(&pfnUpdateSwapchainFB));
1901
1902 for (auto swapchain : m_swapchains) {
1903 XrFoveationLevelProfileCreateInfoFB levelProfileCreateInfo = {};
1904 levelProfileCreateInfo.type = XR_TYPE_FOVEATION_LEVEL_PROFILE_CREATE_INFO_FB;
1905 levelProfileCreateInfo.level = m_foveationLevel;
1906 levelProfileCreateInfo.verticalOffset = 0;
1907 levelProfileCreateInfo.dynamic = XR_FOVEATION_DYNAMIC_DISABLED_FB;
1908
1909 XrFoveationProfileCreateInfoFB profileCreateInfo = {};
1910 profileCreateInfo.type = XR_TYPE_FOVEATION_PROFILE_CREATE_INFO_FB;
1911 profileCreateInfo.next = &levelProfileCreateInfo;
1912
1913 XrFoveationProfileFB foveationProfile;
1914 pfnCreateFoveationProfileFB(m_session, &profileCreateInfo, &foveationProfile);
1915
1916 XrSwapchainStateFoveationFB foveationUpdateState = {};
1917 memset(&foveationUpdateState, 0, sizeof(foveationUpdateState));
1918 foveationUpdateState.type = XR_TYPE_SWAPCHAIN_STATE_FOVEATION_FB;
1919 foveationUpdateState.profile = foveationProfile;
1920
1921 XrResult updateSwapchainFBResult = OpenXRHelpers::safeCall(pfnUpdateSwapchainFB, swapchain.handle, (const XrSwapchainStateBaseHeaderFB*)(&foveationUpdateState));
1922 if (updateSwapchainFBResult == XR_ERROR_FUNCTION_UNSUPPORTED)
1923 return;
1924
1925 XrResult destroyFoveationProfileFBResult = OpenXRHelpers::safeCall(pfnDestroyFoveationProfileFB, foveationProfile);
1926 if (destroyFoveationProfileFBResult == XR_ERROR_FUNCTION_UNSUPPORTED)
1927 return;
1928
1929 qCDebug(lcQuick3DXr, "Fixed foveated rendering requested with level %d", int(m_foveationLevel));
1930 }
1931}
1932
1933void QQuick3DXrManagerPrivate::createMetaQuestPassthrough()
1934{
1935 // According to the validation layer 'flags' cannot be 0, thus we make sure
1936 // this function is only ever called when we know passthrough is actually
1937 // enabled by the app.
1938 Q_ASSERT(m_fbPassthroughEnabled);
1939
1940 PFN_xrCreatePassthroughFB pfnXrCreatePassthroughFBX = nullptr;
1941 OpenXRHelpers::resolveXrFunction(m_instance, "xrCreatePassthroughFB", (PFN_xrVoidFunction*)(&pfnXrCreatePassthroughFBX));
1942
1943 XrPassthroughCreateInfoFB passthroughCreateInfo{};
1944 passthroughCreateInfo.type = XR_TYPE_PASSTHROUGH_CREATE_INFO_FB;
1945 passthroughCreateInfo.flags = XR_PASSTHROUGH_IS_RUNNING_AT_CREATION_BIT_FB;
1946
1947 XrResult xrCreatePassthroughFBXResult = OpenXRHelpers::safeCall(pfnXrCreatePassthroughFBX, m_session, static_cast<const XrPassthroughCreateInfoFB*>(&passthroughCreateInfo), &m_passthroughFeature);
1948 if (!checkXrResult(xrCreatePassthroughFBXResult))
1949 qWarning("Failed to create passthrough object");
1950}
1951
1952void QQuick3DXrManagerPrivate::destroyMetaQuestPassthrough()
1953{
1954 PFN_xrDestroyPassthroughFB pfnXrDestroyPassthroughFBX = nullptr;
1955 OpenXRHelpers::resolveXrFunction(m_instance, "xrDestroyPassthroughFB", (PFN_xrVoidFunction*)(&pfnXrDestroyPassthroughFBX));
1956
1957 XrResult xrDestroyPassthroughFBXResult = OpenXRHelpers::safeCall(pfnXrDestroyPassthroughFBX, m_passthroughFeature);
1958 if (!checkXrResult(xrDestroyPassthroughFBXResult))
1959 qWarning("Failed to destroy passthrough object");
1960
1961 m_passthroughFeature = XR_NULL_HANDLE;
1962}
1963
1964void QQuick3DXrManagerPrivate::startMetaQuestPassthrough()
1965{
1966 PFN_xrPassthroughStartFB pfnXrPassthroughStartFBX = nullptr;
1967 OpenXRHelpers::resolveXrFunction(m_instance, "xrPassthroughStartFB", (PFN_xrVoidFunction*)(&pfnXrPassthroughStartFBX));
1968
1969 XrResult xrPassthroughStartFBXResult = OpenXRHelpers::safeCall(pfnXrPassthroughStartFBX, m_passthroughFeature);
1970 if (!checkXrResult(xrPassthroughStartFBXResult))
1971 qWarning("Failed to start passthrough");
1972}
1973
1974void QQuick3DXrManagerPrivate::pauseMetaQuestPassthrough()
1975{
1976 PFN_xrPassthroughPauseFB pfnXrPassthroughPauseFBX = nullptr;
1977 OpenXRHelpers::resolveXrFunction(m_instance, "xrPassthroughPauseFB", (PFN_xrVoidFunction*)(&pfnXrPassthroughPauseFBX));
1978
1979 XrResult xrPassthroughPauseFBXResult = OpenXRHelpers::safeCall(pfnXrPassthroughPauseFBX, m_passthroughFeature);
1980 if (!checkXrResult(xrPassthroughPauseFBXResult))
1981 qWarning("Failed to pause passthrough");
1982}
1983
1984void QQuick3DXrManagerPrivate::createMetaQuestPassthroughLayer()
1985{
1986 PFN_xrCreatePassthroughLayerFB pfnXrCreatePassthroughLayerFBX = nullptr;
1987 OpenXRHelpers::resolveXrFunction(m_instance, "xrCreatePassthroughLayerFB", (PFN_xrVoidFunction*)(&pfnXrCreatePassthroughLayerFBX));
1988
1989 XrPassthroughLayerCreateInfoFB layerCreateInfo{};
1990 layerCreateInfo.type = XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB;
1991 layerCreateInfo.passthrough = m_passthroughFeature;
1992 layerCreateInfo.purpose = XR_PASSTHROUGH_LAYER_PURPOSE_RECONSTRUCTION_FB;
1993 if (m_fbPassthroughEnabled)
1994 layerCreateInfo.flags = XR_PASSTHROUGH_IS_RUNNING_AT_CREATION_BIT_FB;
1995
1996 XrResult xrCreatePassthroughLayerFBXResult = OpenXRHelpers::safeCall(pfnXrCreatePassthroughLayerFBX, m_session, static_cast<const XrPassthroughLayerCreateInfoFB*>(&layerCreateInfo), &m_passthroughLayer);
1997 if (!checkXrResult(xrCreatePassthroughLayerFBXResult))
1998 qWarning("Failed to create passthrough layer");
1999}
2000
2001void QQuick3DXrManagerPrivate::destroyMetaQuestPassthroughLayer()
2002{
2003 PFN_xrDestroyPassthroughLayerFB pfnXrDestroyPassthroughLayerFBX = nullptr;
2004 OpenXRHelpers::resolveXrFunction(m_instance, "xrDestroyPassthroughLayerFB", (PFN_xrVoidFunction*)(&pfnXrDestroyPassthroughLayerFBX));
2005
2006 XrResult xrDestroyPassthroughLayerFBXResult = OpenXRHelpers::safeCall(pfnXrDestroyPassthroughLayerFBX, m_passthroughLayer);
2007 if (!checkXrResult(xrDestroyPassthroughLayerFBXResult))
2008 qWarning("Failed to destroy passthrough layer");
2009
2010 m_passthroughLayer = XR_NULL_HANDLE;
2011}
2012
2013void QQuick3DXrManagerPrivate::pauseMetaQuestPassthroughLayer()
2014{
2015 PFN_xrPassthroughLayerPauseFB pfnXrPassthroughLayerPauseFBX = nullptr;
2016 OpenXRHelpers::resolveXrFunction(m_instance, "xrPassthroughLayerPauseFB", (PFN_xrVoidFunction*)(&pfnXrPassthroughLayerPauseFBX));
2017
2018 XrResult xrPassthroughLayerPauseFBXResult = OpenXRHelpers::safeCall(pfnXrPassthroughLayerPauseFBX, m_passthroughLayer);
2019 if (!checkXrResult(xrPassthroughLayerPauseFBXResult))
2020 qWarning("Failed to pause passthrough layer");
2021}
2022
2023void QQuick3DXrManagerPrivate::resumeMetaQuestPassthroughLayer()
2024{
2025 PFN_xrPassthroughLayerResumeFB pfnXrPassthroughLayerResumeFBX = nullptr;
2026 OpenXRHelpers::resolveXrFunction(m_instance, "xrPassthroughLayerResumeFB", (PFN_xrVoidFunction*)(&pfnXrPassthroughLayerResumeFBX));
2027
2028 XrResult xrPassthroughLayerResumeFBXResult = OpenXRHelpers::safeCall(pfnXrPassthroughLayerResumeFBX, m_passthroughLayer);
2029 if (!checkXrResult(xrPassthroughLayerResumeFBXResult))
2030 qWarning("Failed to resume passthrough layer");
2031}
2032
2033void QQuick3DXrManagerPrivate::checkXrExtensions(const char *layerName, int indent)
2034{
2035 quint32 instanceExtensionCount;
2036 if (!checkXrResult(xrEnumerateInstanceExtensionProperties(layerName, 0, &instanceExtensionCount, nullptr))) {
2037 qWarning("Failed to enumerate instance extension properties");
2038 return;
2039 }
2040
2041 QVector<XrExtensionProperties> extensions(instanceExtensionCount);
2042 for (XrExtensionProperties& extension : extensions) {
2043 extension.type = XR_TYPE_EXTENSION_PROPERTIES;
2044 extension.next = nullptr;
2045 }
2046
2047 if (!checkXrResult(xrEnumerateInstanceExtensionProperties(layerName,
2048 quint32(extensions.size()),
2049 &instanceExtensionCount,
2050 extensions.data())))
2051 {
2052 qWarning("Failed to enumerate instance extension properties");
2053 }
2054
2055 const QByteArray indentStr(indent, ' ');
2056 qCDebug(lcQuick3DXr, "%sAvailable Extensions: (%d)", indentStr.data(), instanceExtensionCount);
2057 for (const XrExtensionProperties& extension : extensions) {
2058 qCDebug(lcQuick3DXr, "%s Name=%s Version=%d.%d.%d",
2059 indentStr.data(),
2060 extension.extensionName,
2061 XR_VERSION_MAJOR(extension.extensionVersion),
2062 XR_VERSION_MINOR(extension.extensionVersion),
2063 XR_VERSION_PATCH(extension.extensionVersion));
2064 }
2065}
2066
2067void QQuick3DXrManagerPrivate::checkXrLayers()
2068{
2069 quint32 layerCount;
2070 if (!checkXrResult(xrEnumerateApiLayerProperties(0, &layerCount, nullptr))) {
2071 qWarning("Failed to enumerate API layer properties");
2072 return;
2073 }
2074
2075 QVector<XrApiLayerProperties> layers(layerCount);
2076 for (XrApiLayerProperties& layer : layers) {
2077 layer.type = XR_TYPE_API_LAYER_PROPERTIES;
2078 layer.next = nullptr;
2079 }
2080
2081 if (!checkXrResult(xrEnumerateApiLayerProperties(quint32(layers.size()), &layerCount, layers.data()))) {
2082 qWarning("Failed to enumerate API layer properties");
2083 return;
2084 }
2085
2086 qCDebug(lcQuick3DXr, "Available Layers: (%d)", layerCount);
2087 for (const XrApiLayerProperties& layer : layers) {
2088 qCDebug(lcQuick3DXr, " Name=%s SpecVersion=%d.%d.%d LayerVersion=%d.%d.%d Description=%s",
2089 layer.layerName,
2090 XR_VERSION_MAJOR(layer.specVersion),
2091 XR_VERSION_MINOR(layer.specVersion),
2092 XR_VERSION_PATCH(layer.specVersion),
2093 XR_VERSION_MAJOR(layer.layerVersion),
2094 XR_VERSION_MINOR(layer.layerVersion),
2095 XR_VERSION_PATCH(layer.layerVersion),
2096 layer.description);
2097 checkXrExtensions(layer.layerName, 4);
2098 }
2099}
2100
2101XrResult QQuick3DXrManagerPrivate::createXrInstance()
2102{
2103 // Setup Info
2104 XrApplicationInfo appInfo;
2105 strcpy(appInfo.applicationName, QCoreApplication::applicationName().toUtf8());
2106 appInfo.applicationVersion = 7;
2107 strcpy(appInfo.engineName, QStringLiteral("Qt").toUtf8());
2108 appInfo.engineVersion = 6;
2109
2110 // apiVersion must not be XR_CURRENT_API_VERSION. Consider what happens when
2111 // building against 1.1 headers and running on an 1.0-only runtime. (it all
2112 // breaks down) For now, use a known, fixed version: the last 1.0 release.
2113 appInfo.apiVersion = XR_MAKE_VERSION(1, 0, 34);
2114
2115 // Query available API layers
2116 uint32_t apiLayerCount = 0;
2117 xrEnumerateApiLayerProperties(0, &apiLayerCount, nullptr);
2118 QVector<XrApiLayerProperties> apiLayerProperties(apiLayerCount);
2119 for (uint32_t i = 0; i < apiLayerCount; i++) {
2120 apiLayerProperties[i].type = XR_TYPE_API_LAYER_PROPERTIES;
2121 apiLayerProperties[i].next = nullptr;
2122 }
2123 xrEnumerateApiLayerProperties(apiLayerCount, &apiLayerCount, apiLayerProperties.data());
2124
2125 // Decide which API layers to enable
2126 QVector<const char*> enabledApiLayers;
2127
2128 // Now it would be nice if we could use
2129 // QQuickGraphicsConfiguration::isDebugLayerEnabled() but the quickWindow is
2130 // nowhere yet, so just replicate the env.var. for now.
2131 const bool wantsValidationLayer = qEnvironmentVariableIntValue("QSG_RHI_DEBUG_LAYER");
2132 if (wantsValidationLayer) {
2133 if (isApiLayerSupported("XR_APILAYER_LUNARG_core_validation", apiLayerProperties))
2134 enabledApiLayers.append("XR_APILAYER_LUNARG_core_validation");
2135 else
2136 qCDebug(lcQuick3DXr, "OpenXR validation layer requested, but not available");
2137 }
2138
2139 qCDebug(lcQuick3DXr) << "Requesting to enable XR API layers:" << enabledApiLayers;
2140
2141 m_enabledApiLayers.clear();
2142 for (const char *layer : enabledApiLayers)
2143 m_enabledApiLayers.append(QString::fromLatin1(layer));
2144
2145 // Load extensions
2146 uint32_t extensionCount = 0;
2147 xrEnumerateInstanceExtensionProperties(nullptr, 0, &extensionCount, nullptr);
2148 QVector<XrExtensionProperties> extensionProperties(extensionCount);
2149 for (uint32_t i = 0; i < extensionCount; i++) {
2150 // we usually have to fill in the type (for validation) and set
2151 // next to NULL (or a pointer to an extension specific struct)
2152 extensionProperties[i].type = XR_TYPE_EXTENSION_PROPERTIES;
2153 extensionProperties[i].next = nullptr;
2154 }
2155 xrEnumerateInstanceExtensionProperties(nullptr, extensionCount, &extensionCount, extensionProperties.data());
2156
2157 QVector<const char*> enabledExtensions;
2158 if (!m_graphics->initialize(extensionProperties)) {
2159 qWarning("Failed to initialize OpenXR graphics module");
2160 return XR_ERROR_GRAPHICS_DEVICE_INVALID;
2161 }
2162 enabledExtensions.append(m_graphics->getRequiredExtensions());
2163
2164 if (isExtensionSupported("XR_EXT_debug_utils", extensionProperties))
2165 enabledExtensions.append("XR_EXT_debug_utils");
2166
2167 if (isExtensionSupported(XR_EXT_PERFORMANCE_SETTINGS_EXTENSION_NAME, extensionProperties))
2168 enabledExtensions.append(XR_EXT_PERFORMANCE_SETTINGS_EXTENSION_NAME);
2169
2170 m_handtrackingExtensionSupported = isExtensionSupported(XR_EXT_HAND_TRACKING_EXTENSION_NAME, extensionProperties);
2171 if (m_handtrackingExtensionSupported)
2172 enabledExtensions.append(XR_EXT_HAND_TRACKING_EXTENSION_NAME);
2173
2174 m_compositionLayerDepthSupported = isExtensionSupported(XR_KHR_COMPOSITION_LAYER_DEPTH_EXTENSION_NAME, extensionProperties);
2175 if (m_compositionLayerDepthSupported) {
2176 // The extension is enabled, whenever supported; however, if we actually
2177 // submit depth in xrEndFrame(), is a different question.
2178 enabledExtensions.append(XR_KHR_COMPOSITION_LAYER_DEPTH_EXTENSION_NAME);
2179 m_submitLayerDepth = qEnvironmentVariableIntValue("QT_QUICK3D_XR_SUBMIT_DEPTH");
2180 if (m_submitLayerDepth)
2181 qCDebug(lcQuick3DXr, "submitLayerDepth defaults to true due to env.var.");
2182 } else {
2183 m_submitLayerDepth = false;
2184 }
2185
2186 // Oculus Quest Specific Extensions
2187
2188 m_handtrackingAimExtensionSupported = isExtensionSupported(XR_FB_HAND_TRACKING_AIM_EXTENSION_NAME, extensionProperties);
2189 if (m_handtrackingAimExtensionSupported)
2190 enabledExtensions.append(XR_FB_HAND_TRACKING_AIM_EXTENSION_NAME);
2191
2192 if (isExtensionSupported(XR_MSFT_HAND_INTERACTION_EXTENSION_NAME, extensionProperties))
2193 enabledExtensions.append(XR_MSFT_HAND_INTERACTION_EXTENSION_NAME);
2194
2195 if (isExtensionSupported(XR_FB_HAND_TRACKING_MESH_EXTENSION_NAME, extensionProperties))
2196 enabledExtensions.append(XR_FB_HAND_TRACKING_MESH_EXTENSION_NAME);
2197
2198 // Passthrough extensions (require manifest feature to work)
2199 // <uses-feature android:name="com.oculus.feature.PASSTHROUGH" android:required="true" />
2200 uint32_t passthroughSpecVersion = 0;
2201 if (isExtensionSupported(XR_FB_PASSTHROUGH_EXTENSION_NAME, extensionProperties, &passthroughSpecVersion)) {
2202 qCDebug(lcQuick3DXr, "Passthrough extension is supported, spec version %u", passthroughSpecVersion);
2203 enabledExtensions.append(XR_FB_PASSTHROUGH_EXTENSION_NAME);
2204 } else {
2205 qCDebug(lcQuick3DXr, "Passthrough extension is NOT supported");
2206 }
2207
2208 if (isExtensionSupported(XR_FB_TRIANGLE_MESH_EXTENSION_NAME, extensionProperties))
2209 enabledExtensions.append(XR_FB_TRIANGLE_MESH_EXTENSION_NAME);
2210
2211 m_displayRefreshRateExtensionSupported = isExtensionSupported(XR_FB_DISPLAY_REFRESH_RATE_EXTENSION_NAME, extensionProperties);
2212 if (m_displayRefreshRateExtensionSupported)
2213 enabledExtensions.append(XR_FB_DISPLAY_REFRESH_RATE_EXTENSION_NAME);
2214
2215 m_colorspaceExtensionSupported = isExtensionSupported(XR_FB_COLOR_SPACE_EXTENSION_NAME, extensionProperties);
2216 if (m_colorspaceExtensionSupported)
2217 enabledExtensions.append(XR_FB_COLOR_SPACE_EXTENSION_NAME);
2218
2219 if (isExtensionSupported(XR_FB_SWAPCHAIN_UPDATE_STATE_EXTENSION_NAME, extensionProperties))
2220 enabledExtensions.append(XR_FB_SWAPCHAIN_UPDATE_STATE_EXTENSION_NAME);
2221
2222 m_foveationExtensionSupported = isExtensionSupported(XR_FB_FOVEATION_EXTENSION_NAME, extensionProperties);
2223 if (m_foveationExtensionSupported)
2224 enabledExtensions.append(XR_FB_FOVEATION_EXTENSION_NAME);
2225
2226 if (isExtensionSupported(XR_FB_FOVEATION_CONFIGURATION_EXTENSION_NAME, extensionProperties))
2227 enabledExtensions.append(XR_FB_FOVEATION_CONFIGURATION_EXTENSION_NAME);
2228
2229 if (m_spaceExtension) {
2230 const auto requiredExtensions = m_spaceExtension->requiredExtensions();
2231 bool isSupported = true;
2232 for (const auto extension : requiredExtensions) {
2233 isSupported = isExtensionSupported(extension, extensionProperties) && isSupported;
2234 if (!isSupported)
2235 break;
2236 }
2237 m_spaceExtensionSupported = isSupported;
2238 if (isSupported)
2239 enabledExtensions.append(requiredExtensions);
2240 }
2241
2242 if (isExtensionSupported(XR_EXTX_OVERLAY_EXTENSION_NAME, extensionProperties)) {
2243 m_overlayExtensionSupported = true;
2244 enabledExtensions.append(XR_EXTX_OVERLAY_EXTENSION_NAME);
2245 }
2246
2247#ifdef Q_OS_ANDROID
2248 if (isExtensionSupported(XR_KHR_ANDROID_THREAD_SETTINGS_EXTENSION_NAME, extensionProperties))
2249 enabledExtensions.append(XR_KHR_ANDROID_THREAD_SETTINGS_EXTENSION_NAME);
2250
2251 m_androidCreateInstanceExtensionSupported = isExtensionSupported(XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME, extensionProperties);
2252 if (m_androidCreateInstanceExtensionSupported)
2253 enabledExtensions.append(XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME);
2254
2255 auto graphicsAPI = QQuickWindow::graphicsApi();
2256 if (graphicsAPI == QSGRendererInterface::Vulkan) {
2257 if (isExtensionSupported(XR_FB_SWAPCHAIN_UPDATE_STATE_VULKAN_EXTENSION_NAME, extensionProperties))
2258 enabledExtensions.append(XR_FB_SWAPCHAIN_UPDATE_STATE_VULKAN_EXTENSION_NAME);
2259 } else if (graphicsAPI == QSGRendererInterface::OpenGL) {
2260 if (isExtensionSupported(XR_FB_SWAPCHAIN_UPDATE_STATE_OPENGL_ES_EXTENSION_NAME, extensionProperties))
2261 enabledExtensions.append(XR_FB_SWAPCHAIN_UPDATE_STATE_OPENGL_ES_EXTENSION_NAME);
2262 }
2263#endif
2264
2265 qCDebug(lcQuick3DXr) << "Requesting to enable XR extensions:" << enabledExtensions;
2266
2267 m_enabledExtensions.clear();
2268 for (const char *extension : enabledExtensions)
2269 m_enabledExtensions.append(QString::fromLatin1(extension));
2270
2271 // Create Instance
2272 XrInstanceCreateInfo xrInstanceInfo{};
2273 xrInstanceInfo.type = XR_TYPE_INSTANCE_CREATE_INFO;
2274
2275#ifdef Q_OS_ANDROID
2276 XrInstanceCreateInfoAndroidKHR xrInstanceCreateInfoAndroid {};
2277 if (m_androidCreateInstanceExtensionSupported) {
2278 xrInstanceCreateInfoAndroid.type = XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR;
2279 xrInstanceCreateInfoAndroid.applicationVM = m_javaVM;
2280 xrInstanceCreateInfoAndroid.applicationActivity = m_androidActivity.object();
2281
2282 xrInstanceInfo.next = &xrInstanceCreateInfoAndroid;
2283 }
2284#endif
2285
2286
2287 xrInstanceInfo.createFlags = 0;
2288 xrInstanceInfo.applicationInfo = appInfo;
2289 xrInstanceInfo.enabledApiLayerCount = enabledApiLayers.count();
2290 xrInstanceInfo.enabledApiLayerNames = enabledApiLayers.constData();
2291 xrInstanceInfo.enabledExtensionCount = enabledExtensions.count();
2292 xrInstanceInfo.enabledExtensionNames = enabledExtensions.constData();
2293
2294 return xrCreateInstance(&xrInstanceInfo, &m_instance);
2295}
2296
2297void QQuick3DXrManagerPrivate::checkXrInstance()
2298{
2299 Q_ASSERT(m_instance != XR_NULL_HANDLE);
2300 XrInstanceProperties instanceProperties{};
2301 instanceProperties.type = XR_TYPE_INSTANCE_PROPERTIES;
2302 if (!checkXrResult(xrGetInstanceProperties(m_instance, &instanceProperties))) {
2303 qWarning("Failed to get instance properties");
2304 return;
2305 }
2306
2307 m_runtimeName = QString::fromUtf8(instanceProperties.runtimeName);
2308 m_runtimeVersion = QVersionNumber(XR_VERSION_MAJOR(instanceProperties.runtimeVersion),
2309 XR_VERSION_MINOR(instanceProperties.runtimeVersion),
2310 XR_VERSION_PATCH(instanceProperties.runtimeVersion));
2311
2312 qCDebug(lcQuick3DXr, "Instance RuntimeName=%s RuntimeVersion=%d.%d.%d",
2313 qPrintable(m_runtimeName),
2314 m_runtimeVersion.majorVersion(),
2315 m_runtimeVersion.minorVersion(),
2316 m_runtimeVersion.microVersion());
2317}
2318
2319void QQuick3DXrManagerPrivate::setupDebugMessenger()
2320{
2321 if (!m_enabledExtensions.contains(QString::fromUtf8("XR_EXT_debug_utils"))) {
2322 qCDebug(lcQuick3DXr, "No debug utils extension, message redirection not set up");
2323 return;
2324 }
2325
2326#ifdef XR_EXT_debug_utils
2327 PFN_xrCreateDebugUtilsMessengerEXT xrCreateDebugUtilsMessengerEXT = nullptr;
2328 OpenXRHelpers::resolveXrFunction(m_instance, "xrCreateDebugUtilsMessengerEXT", reinterpret_cast<PFN_xrVoidFunction *>(&xrCreateDebugUtilsMessengerEXT));
2329 if (!xrCreateDebugUtilsMessengerEXT)
2330 return;
2331
2332 OpenXRHelpers::resolveXrFunction(m_instance, "xrDestroyDebugUtilsMessengerEXT", reinterpret_cast<PFN_xrVoidFunction *>(&m_xrDestroyDebugUtilsMessengerEXT));
2333
2334 XrDebugUtilsMessengerCreateInfoEXT messengerInfo = {};
2335 messengerInfo.type = XR_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
2336 messengerInfo.messageSeverities = XR_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
2337 | XR_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
2338 messengerInfo.messageTypes = XR_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT
2339 | XR_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT
2340 | XR_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT
2341 | XR_DEBUG_UTILS_MESSAGE_TYPE_CONFORMANCE_BIT_EXT;
2342 messengerInfo.userCallback = defaultDebugCallbackFunc;
2343 messengerInfo.userData = this;
2344
2345 XrResult err = xrCreateDebugUtilsMessengerEXT(m_instance, &messengerInfo, &m_debugMessenger);
2346 if (!checkXrResult(err))
2347 qWarning("Quick 3D XR: Failed to create debug report callback, OpenXR messages will not get redirected (%d)", err);
2348#endif // XR_EXT_debug_utils
2349}
2350
2351XrResult QQuick3DXrManagerPrivate::initializeSystem()
2352{
2353 Q_ASSERT(m_instance != XR_NULL_HANDLE);
2354 Q_ASSERT(m_systemId == XR_NULL_SYSTEM_ID);
2355
2356 XrSystemGetInfo hmdInfo{};
2357 hmdInfo.type = XR_TYPE_SYSTEM_GET_INFO;
2358 hmdInfo.next = nullptr;
2359 hmdInfo.formFactor = m_formFactor;
2360
2361 const XrResult result = xrGetSystem(m_instance, &hmdInfo, &m_systemId);
2362 const bool success = checkXrResult(result);
2363
2364 if (!success)
2365 return result;
2366
2367 // Check View Configuration
2368 checkViewConfiguration();
2369
2370 return result;
2371}
2372
2373void QQuick3DXrManagerPrivate::checkViewConfiguration()
2374{
2375 quint32 viewConfigTypeCount;
2376 if (!checkXrResult(xrEnumerateViewConfigurations(m_instance,
2377 m_systemId,
2378 0,
2379 &viewConfigTypeCount,
2380 nullptr)))
2381 {
2382 qWarning("Failed to enumerate view configurations");
2383 return;
2384 }
2385 QVector<XrViewConfigurationType> viewConfigTypes(viewConfigTypeCount);
2386 if (!checkXrResult(xrEnumerateViewConfigurations(m_instance,
2387 m_systemId,
2388 viewConfigTypeCount,
2389 &viewConfigTypeCount,
2390 viewConfigTypes.data())))
2391 {
2392 qWarning("Failed to enumerate view configurations");
2393 return;
2394 }
2395
2396 qCDebug(lcQuick3DXr, "Available View Configuration Types: (%d)", viewConfigTypeCount);
2397 for (XrViewConfigurationType viewConfigType : viewConfigTypes) {
2398 qCDebug(lcQuick3DXr, " View Configuration Type: %s %s", to_string(viewConfigType), viewConfigType == m_viewConfigType ? "(Selected)" : "");
2399 XrViewConfigurationProperties viewConfigProperties{};
2400 viewConfigProperties.type = XR_TYPE_VIEW_CONFIGURATION_PROPERTIES;
2401 if (!checkXrResult(xrGetViewConfigurationProperties(m_instance,
2402 m_systemId,
2403 viewConfigType,
2404 &viewConfigProperties)))
2405 {
2406 qWarning("Failed to get view configuration properties");
2407 return;
2408 }
2409
2410 qCDebug(lcQuick3DXr, " View configuration FovMutable=%s", viewConfigProperties.fovMutable == XR_TRUE ? "True" : "False");
2411
2412 uint32_t viewCount;
2413 if (!checkXrResult(xrEnumerateViewConfigurationViews(m_instance,
2414 m_systemId,
2415 viewConfigType,
2416 0,
2417 &viewCount,
2418 nullptr)))
2419 {
2420 qWarning("Failed to enumerate configuration views");
2421 return;
2422 }
2423
2424 if (viewCount > 0) {
2425 QVector<XrViewConfigurationView> views(viewCount, {XR_TYPE_VIEW_CONFIGURATION_VIEW, nullptr, 0, 0, 0, 0, 0, 0});
2426 if (!checkXrResult(xrEnumerateViewConfigurationViews(m_instance,
2427 m_systemId,
2428 viewConfigType,
2429 viewCount,
2430 &viewCount,
2431 views.data())))
2432 {
2433 qWarning("Failed to enumerate configuration views");
2434 return;
2435 }
2436
2437 for (int i = 0; i < views.size(); ++i) {
2438 const XrViewConfigurationView& view = views[i];
2439 qCDebug(lcQuick3DXr, " View [%d]: Recommended Width=%d Height=%d SampleCount=%d",
2440 i,
2441 view.recommendedImageRectWidth,
2442 view.recommendedImageRectHeight,
2443 view.recommendedSwapchainSampleCount);
2444 qCDebug(lcQuick3DXr, " View [%d]: Maximum Width=%d Height=%d SampleCount=%d",
2445 i,
2446 view.maxImageRectWidth,
2447 view.maxImageRectHeight,
2448 view.maxSwapchainSampleCount);
2449 }
2450 } else {
2451 qCDebug(lcQuick3DXr, "Empty view configuration type");
2452 }
2453 checkEnvironmentBlendMode(viewConfigType);
2454 }
2455
2456 // Select view configuration type based on what the runtime supports.
2457 if (!viewConfigTypes.contains(XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO)
2458 && viewConfigTypes.contains(XR_VIEW_CONFIGURATION_TYPE_PRIMARY_MONO)) {
2459 m_viewConfigType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_MONO;
2460 m_multiviewRendering = false;
2461 qCDebug(lcQuick3DXr, "Mono view mode selected (only mode supported by runtime)");
2462 }
2463}
2464
2465bool QQuick3DXrManagerPrivate::checkXrResult(const XrResult &result)
2466{
2467 return OpenXRHelpers::checkXrResult(result, m_instance);
2468}
2469
2470void QQuick3DXrManagerPrivate::checkEnvironmentBlendMode(XrViewConfigurationType type)
2471{
2472 uint32_t count;
2473 if (!checkXrResult(xrEnumerateEnvironmentBlendModes(m_instance,
2474 m_systemId,
2475 type,
2476 0,
2477 &count,
2478 nullptr)))
2479 {
2480 qWarning("Failed to enumerate blend modes");
2481 return;
2482 }
2483
2484 qCDebug(lcQuick3DXr, "Available Environment Blend Mode count : (%d)", count);
2485
2486 QVector<XrEnvironmentBlendMode> blendModes(count);
2487 if (!checkXrResult(xrEnumerateEnvironmentBlendModes(m_instance,
2488 m_systemId,
2489 type,
2490 count,
2491 &count,
2492 blendModes.data())))
2493 {
2494 qWarning("Failed to enumerate blend modes");
2495 return;
2496 }
2497
2498 m_blendModes = blendModes;
2499
2500 // Set initial blend mode, prefer opaque (passthrough disabled)
2501 if (!setTransparentBlendMode(false))
2503}
2504
2505QT_END_NAMESPACE
2506
2507#include "qquick3dxrmanager_openxr.moc"
bool event(QEvent *event) override
This virtual function receives events to an object and should return true if the event e was recogniz...
static constexpr QEvent::Type asQEvent(QQuick3DOpenXRThreadWorker::Event event)
static QQuick3DXrInputManagerPrivate * get(QQuick3DXrInputManager *inputManager)
QQuick3DXrManagerPrivate(QQuick3DXrManager &manager)
void setupWindow(QQuickWindow *window)
QtQuick3DXr::ReferenceSpace getReferenceSpace() const
bool setupGraphics(QQuickWindow *window)
void getDefaultClipDistances(float &nearClip, float &farClip) const
static bool isMultiviewRenderingDisabled()
QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(lcQIORing)
#define MAKE_TO_STRING_FUNC(enumType)
static bool isApiLayerSupported(const char *layerName, const QVector< XrApiLayerProperties > &apiLayerProperties)
#define ENUM_CASE_STR(name, val)
static const char s_workerThreadName[]
static QtQuick3DXr::ReferenceSpace getReferenceSpaceType(XrReferenceSpaceType referenceSpace)
static std::pair< XrResult, XrFrameState > waitForNextFrame(XrSession session)
static bool isExtensionSupported(const char *extensionName, const QVector< XrExtensionProperties > &instanceExtensionProperties, uint32_t *extensionVersion=nullptr)