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