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