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

source code of qtquick3d/src/xr/quick3dxr/openxr/qquick3dxrmanager_openxr.cpp