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

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