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