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

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