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