Revision control

++ // Copyright (C) 2024 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++
++ #include "qquick3dxrmanager_openxr_p.h"
++ #include "qquick3dxrcamera_p.h"
++ #include "qquick3dxrorigin_p.h"
++ #include "qquick3dxranimationdriver_p.h"
++ #include "qquick3dxrmanager_p.h"
++ #include "qquick3dxrinputmanager_p.h"
++
++ #include "qopenxrhelpers_p.h"
++ #include "qopenxrinputmanager_p.h"
++ #include "qquick3dxranchormanager_openxr_p.h"
++
++ #include "qtquick3dxrglobal_p.h"
++
++ #include <QtQuick3DUtils/private/qssgassert_p.h>
++ #include <QtQuick3D/private/qquick3dviewport_p.h>
++
++ #include <QtQuick/qquickwindow.h>
++ #include <QtQuick/qquickrendercontrol.h>
++
++ #include <QtGui/qquaternion.h>
++
++ #include <QtCore/qobject.h>
++
++ #include <openxr/openxr_reflection.h>
++
++ #ifdef XR_USE_GRAPHICS_API_VULKAN
++ # include "qopenxrgraphics_vulkan_p.h"
++ #endif
++
++ #ifdef XR_USE_GRAPHICS_API_D3D11
++ # include "qopenxrgraphics_d3d11_p.h"
++ #endif
++
++ #ifdef XR_USE_GRAPHICS_API_D3D12
++ # include "qopenxrgraphics_d3d12_p.h"
++ #endif
++
++ #ifdef XR_USE_GRAPHICS_API_OPENGL
++ # include "qopenxrgraphics_opengl_p.h"
++ #endif
++
++ #ifdef XR_USE_PLATFORM_ANDROID
++ # include <QtCore/qnativeinterface.h>
++ # include <QtCore/QJniEnvironment>
++ # include <QtCore/QJniObject>
++ # ifdef XR_USE_GRAPHICS_API_OPENGL_ES
++ # include "qopenxrgraphics_opengles_p.h"
++ # endif // XR_USE_GRAPHICS_API_OPENGL_ES
++ #endif // XR_USE_PLATFORM_ANDROID
++
++ #ifdef XR_USE_GRAPHICS_API_METAL
++ # include "qopenxrgraphics_metal_p.h"
++ #endif // XR_USE_GRAPHICS_API_METAL
++
++ static XrReferenceSpaceType getXrReferenceSpaceType(QtQuick3DXr::ReferenceSpace referenceSpace)
++ {
++ switch (referenceSpace) {
++ case QtQuick3DXr::ReferenceSpace::ReferenceSpaceLocal:
++ return XR_REFERENCE_SPACE_TYPE_LOCAL;
++ case QtQuick3DXr::ReferenceSpace::ReferenceSpaceStage:
++ return XR_REFERENCE_SPACE_TYPE_STAGE;
++ case QtQuick3DXr::ReferenceSpace::ReferenceSpaceLocalFloor:
++ return XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT;
++ default:
++ return XR_REFERENCE_SPACE_TYPE_LOCAL;
++ }
++ }
++
++ static QtQuick3DXr::ReferenceSpace getReferenceSpaceType(XrReferenceSpaceType referenceSpace)
++ {
++ switch (referenceSpace) {
++ case XR_REFERENCE_SPACE_TYPE_LOCAL:
++ return QtQuick3DXr::ReferenceSpace::ReferenceSpaceLocal;
++ case XR_REFERENCE_SPACE_TYPE_STAGE:
++ return QtQuick3DXr::ReferenceSpace::ReferenceSpaceStage;
++ case XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT:
++ return QtQuick3DXr::ReferenceSpace::ReferenceSpaceLocalFloor;
++ default:
++ return QtQuick3DXr::ReferenceSpace::ReferenceSpaceUnknown;
++ }
++ }
++
++ // Macro to generate stringify functions for OpenXR enumerations based data provided in openxr_reflection.h
++ #define ENUM_CASE_STR(name, val) case name: return #name;
++ #define MAKE_TO_STRING_FUNC(enumType) \
++ static inline const char* to_string(enumType e) { \
++ switch (e) { \
++ XR_LIST_ENUM_##enumType(ENUM_CASE_STR) \
++ default: return "Unknown " #enumType; \
++ } \
++ }
++
++ MAKE_TO_STRING_FUNC(XrReferenceSpaceType)
++ MAKE_TO_STRING_FUNC(XrViewConfigurationType)
++ MAKE_TO_STRING_FUNC(XrEnvironmentBlendMode)
++ MAKE_TO_STRING_FUNC(XrSessionState)
++ MAKE_TO_STRING_FUNC(XrResult)
++
++ static bool isExtensionSupported(const char *extensionName, const QVector<XrExtensionProperties> &instanceExtensionProperties, uint32_t *extensionVersion = nullptr)
++ {
++ for (const auto &extensionProperty : instanceExtensionProperties) {
++ if (!strcmp(extensionName, extensionProperty.extensionName)) {
++ if (extensionVersion)
++ *extensionVersion = extensionProperty.extensionVersion;
++ return true;
++ }
++ }
++ return false;
++ }
++
++ static bool isApiLayerSupported(const char *layerName, const QVector<XrApiLayerProperties> &apiLayerProperties)
++ {
++ for (const auto &prop : apiLayerProperties) {
++ if (!strcmp(layerName, prop.layerName))
++ return true;
++ }
++ return false;
++ }
++
++ // OpenXR's debug messenger stuff is a carbon copy of the Vulkan one, hence we
++ // replicate the same behavior on Qt side as well, i.e. route by default
++ // everything to qDebug. Filtering or further control (that is supported with
++ // the C++ APIS in the QVulkan* stuff) is not provided here for now.
++ #ifdef XR_EXT_debug_utils
++ static XRAPI_ATTR XrBool32 XRAPI_CALL defaultDebugCallbackFunc(XrDebugUtilsMessageSeverityFlagsEXT messageSeverity,
++ XrDebugUtilsMessageTypeFlagsEXT messageType,
++ const XrDebugUtilsMessengerCallbackDataEXT *callbackData,
++ void *userData)
++ {
++ Q_UNUSED(messageSeverity);
++ Q_UNUSED(messageType);
++ QQuick3DXrManager *self = static_cast<QQuick3DXrManager *>(userData);
++ // this is qDebug intentionally, not categorized
++ qDebug("xrDebug [QOpenXRManager %p] %s", self, callbackData->message);
++ return XR_FALSE;
++ }
++ #endif // XR_EXT_debug_utils
++
++ static std::pair<XrResult, XrFrameState> waitForNextFrame(XrSession session)
++ {
++ XrFrameWaitInfo frameWaitInfo {};
++ frameWaitInfo.type = XR_TYPE_FRAME_WAIT_INFO;
++ XrFrameState frameState {};
++ frameState.type = XR_TYPE_FRAME_STATE;
++ auto result = xrWaitFrame(session, &frameWaitInfo, &frameState);
++ return { result, frameState };
++ }
++
++ QT_BEGIN_NAMESPACE
++
++ Q_DECLARE_LOGGING_CATEGORY(lcQuick3DXr);
++
++ static const char s_workerThreadName[] = "QQuick3DXrWorkerThread";
++
++ class QQuick3DOpenXRThreadWorker : public QObject
++ {
++ Q_OBJECT
++ public:
++ explicit QQuick3DOpenXRThreadWorker(XrSession session, QObject *parent = nullptr)
++ : QObject(parent), m_session(session)
++ {
++ }
++
++ using EventT = std::underlying_type_t<QEvent::Type>;
++ enum Event : EventT {
++ WaitForFrame = QEvent::User + 1,
++ };
++
++ static constexpr QEvent::Type asQEvent(QQuick3DOpenXRThreadWorker::Event event)
++ {
++ return static_cast<QEvent::Type>(event);
++ }
++
++ void startWaitingForFrame()
++ {
++ QCoreApplication::postEvent(this, new QEvent(QQuick3DOpenXRThreadWorker::asQEvent(QQuick3DOpenXRThreadWorker::Event::WaitForFrame)));
++ }
++
++ protected:
++ bool event(QEvent *event) override
++ {
++ switch (static_cast<Event>(event->type())) {
++ case Event::WaitForFrame: {
++ auto result = waitForNextFrame(m_session);
++ emit frameWaited(result.first, result.second);
++ return true;
++ }
++ default:
++ return QObject::event(event);
++ }
++ }
++
++ signals:
++ void frameWaited(XrResult result, const XrFrameState &frameState);
++
++ private:
++ XrSession m_session = XR_NULL_HANDLE;
++ };
++
++ void QQuick3DXrManagerPrivate::setErrorString(XrResult result, const char *callName)
++ {
++ if (m_runtimeName.isEmpty())
++ m_errorString = QObject::tr("%1 failed: no OpenXR runtime found").arg(QLatin1StringView(callName));
++ else
++ m_errorString = QObject::tr("%1 for runtime %2 %3 failed with %4.")
++ .arg(QLatin1StringView(callName),
++ m_runtimeName,
++ m_runtimeVersion.toString(),
++ OpenXRHelpers::getXrResultAsString(result, m_instance));
++ if (result == XR_ERROR_FORM_FACTOR_UNAVAILABLE) // this is very common
++ m_errorString += QObject::tr("\nThe OpenXR runtime has no connection to the headset; check if connection is active and functional.");
++ }
++
++ QQuick3DXrManagerPrivate::QQuick3DXrManagerPrivate(QQuick3DXrManager &manager)
++ : q_ptr(&manager)
++ {
++ m_multiviewRendering = !QQuick3DXrManager::isMultiviewRenderingDisabled();
++ }
++
++ QQuick3DXrManagerPrivate::~QQuick3DXrManagerPrivate()
++ {
++ delete m_graphics; // last, with Vulkan this may own the VkInstance
++ }
++
++ QQuick3DXrManagerPrivate *QQuick3DXrManagerPrivate::get(QQuick3DXrManager *manager)
++ {
++ QSSG_ASSERT(manager != nullptr, return nullptr);
++ return manager->d_func();
++ }
++
++ void QQuick3DXrManagerPrivate::updateCameraHelper(QQuick3DXrEyeCamera *camera, const XrCompositionLayerProjectionView &layerView)
++ {
++ camera->setLeftTangent(qTan(layerView.fov.angleLeft));
++ camera->setRightTangent(qTan(layerView.fov.angleRight));
++ camera->setUpTangent(qTan(layerView.fov.angleUp));
++ camera->setDownTangent(qTan(layerView.fov.angleDown));
++
++ camera->setPosition(QVector3D(layerView.pose.position.x,
++ layerView.pose.position.y,
++ layerView.pose.position.z) * 100.0f); // convert m to cm
++
++ camera->setRotation(QQuaternion(layerView.pose.orientation.w,
++ layerView.pose.orientation.x,
++ layerView.pose.orientation.y,
++ layerView.pose.orientation.z));
++ }
++
++ // Set the active camera for the view to the camera for the eye value
++ // This is set right before updateing/rendering for that eye's view
++ void QQuick3DXrManagerPrivate::updateCameraNonMultiview(int eye, const XrCompositionLayerProjectionView &layerView)
++ {
++ Q_Q(QQuick3DXrManager);
++
++ QQuick3DXrOrigin *xrOrigin = q->m_xrOrigin;
++
++ QQuick3DXrEyeCamera *eyeCamera = xrOrigin ? xrOrigin->eyeCamera(eye) : nullptr;
++
++ if (eyeCamera)
++ updateCameraHelper(eyeCamera, layerView);
++
++ q->m_vrViewport->setCamera(eyeCamera);
++ }
++
++ // The multiview version sets multiple cameras.
++ void QQuick3DXrManagerPrivate::updateCameraMultiview(int projectionLayerViewStartIndex, int count)
++ {
++ Q_Q(QQuick3DXrManager);
++
++ QQuick3DXrOrigin *xrOrigin = q->m_xrOrigin;
++ QQuick3DViewport *vrViewport = q->m_vrViewport;
++
++ QVarLengthArray<QQuick3DCamera *, 4> cameras;
++ for (int i = projectionLayerViewStartIndex; i < projectionLayerViewStartIndex + count; ++i) {
++ QQuick3DXrEyeCamera *eyeCamera = xrOrigin ? xrOrigin->eyeCamera(i) : nullptr;
++ if (eyeCamera)
++ updateCameraHelper(eyeCamera, m_projectionLayerViews[i]);
++ cameras.append(eyeCamera);
++ }
++ vrViewport->setMultiViewCameras(cameras.data(), cameras.count());
++ }
++
++ bool QQuick3DXrManagerPrivate::supportsTransparentBlendMode() const
++ {
++ return m_blendModes.contains(XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND)
++ || m_blendModes.contains(XR_ENVIRONMENT_BLEND_MODE_ADDITIVE);
++ }
++
++ bool QQuick3DXrManagerPrivate::supportsFBPassthrough() const
++ {
++ bool supported = false;
++ XrSystemPassthroughProperties2FB passthroughSystemProperties{};
++ passthroughSystemProperties.type = XR_TYPE_SYSTEM_PASSTHROUGH_PROPERTIES2_FB;
++
++ XrSystemProperties systemProperties{};
++ systemProperties.type = XR_TYPE_SYSTEM_PROPERTIES;
++ systemProperties.next = &passthroughSystemProperties;
++
++ XrSystemGetInfo systemGetInfo{};
++ systemGetInfo.type = XR_TYPE_SYSTEM_GET_INFO;
++ systemGetInfo.formFactor = m_formFactor;
++
++ XrSystemId systemId = XR_NULL_SYSTEM_ID;
++ xrGetSystem(m_instance, &systemGetInfo, &systemId);
++ xrGetSystemProperties(m_instance, systemId, &systemProperties);
++
++ supported = (passthroughSystemProperties.capabilities & XR_PASSTHROUGH_CAPABILITY_BIT_FB) == XR_PASSTHROUGH_CAPABILITY_BIT_FB;
++
++ if (!supported) {
++ // Try the old one. (the Simulator reports spec version 3 for
++ // XR_FB_passthrough, yet the capabilities in
++ // XrSystemPassthroughProperties2FB are 0)
++ XrSystemPassthroughPropertiesFB oldPassthroughSystemProperties{};
++ oldPassthroughSystemProperties.type = XR_TYPE_SYSTEM_PASSTHROUGH_PROPERTIES_FB;
++ systemProperties.next = &oldPassthroughSystemProperties;
++ xrGetSystemProperties(m_instance, systemId, &systemProperties);
++ supported = oldPassthroughSystemProperties.supportsPassthrough;
++ }
++
++ return supported;
++ }
++
++ bool QQuick3DXrManagerPrivate::supportsPassthrough() const
++ {
++ // Passthrough is supported if either XR_FB_passthrough extension is available
++ // or if transparent blend modes (ALPHA_BLEND/ADDITIVE) are supported
++ return supportsFBPassthrough() || supportsTransparentBlendMode();
++ }
++
++ void QQuick3DXrManagerPrivate::setupWindow(QQuickWindow *window)
++ {
++ QSSG_ASSERT(window != nullptr, return);
++ if (m_graphics)
++ m_graphics->setupWindow(window);
++ }
++
++ bool QQuick3DXrManagerPrivate::isGraphicsInitialized() const
++ {
++ return m_graphics && m_graphics->rhi();
++ }
++
++ bool QQuick3DXrManagerPrivate::setupGraphics(QQuickWindow *window)
++ {
++ QSSG_ASSERT(window != nullptr, return false);
++ QSSG_ASSERT(m_graphics != nullptr, return false);
++
++ return m_graphics->setupGraphics(m_instance, m_systemId, window->graphicsConfiguration());
++ }
++
++ void QQuick3DXrManagerPrivate::update()
++ {
++ Q_Q(QQuick3DXrManager);
++ if (m_waitingForFrame) {
++ m_wantUpdate = true;
++ return;
++ }
++
++ QCoreApplication::postEvent(q, new QEvent(QEvent::UpdateRequest));
++ }
++
++ void QQuick3DXrManagerPrivate::processXrEvents()
++ {
++ Q_Q(QQuick3DXrManager);
++
++ bool exitRenderLoop = false;
++ bool requestrestart = false;
++ pollEvents(&exitRenderLoop, &requestrestart);
++
++ if (exitRenderLoop)
++ emit q->sessionEnded();
++
++ if (m_sessionRunning && m_inputManager) {
++ QQuick3DXrInputManagerPrivate::get(m_inputManager)->pollActions();
++ q->renderFrame();
++ }
++ }
++
++ void QQuick3DXrManagerPrivate::destroySwapchain()
++ {
++ for (const Swapchain &swapchain : std::as_const(m_swapchains))
++ xrDestroySwapchain(swapchain.handle);
++
++ m_swapchains.clear();
++ m_swapchainImages.clear();
++ m_configViews.clear();
++
++ for (const Swapchain &swapchain : std::as_const(m_depthSwapchains))
++ xrDestroySwapchain(swapchain.handle);
++
++ m_depthSwapchains.clear();
++ m_depthSwapchainImages.clear();
++ }
++
++ void QQuick3DXrManagerPrivate::doRenderFrame()
++ {
++ Q_ASSERT(m_session != XR_NULL_HANDLE);
++
++ if (m_waitingForFrame)
++ return;
++ m_waitingForFrame = true;
++
++ if (m_worker) {
++ m_worker->startWaitingForFrame();
++ // onFrameWaitCompleted() will be called asynchronoiusly
++ } else {
++ auto result = waitForNextFrame(m_session);
++ onFrameWaitCompleted(result.first, result.second);
++ }
++ }
++
++ void QQuick3DXrManagerPrivate::onFrameWaitCompleted(XrResult result, const XrFrameState &frameState)
++ {
++ if (!m_waitingForFrame)
++ return;
++ m_waitingForFrame = false;
++
++ if (!checkXrResult(result)) {
++ qWarning("xrWaitFrame failed");
++ } else {
++ doRenderFrameAferWait(frameState);
++ }
++
++ if (m_wantUpdate) {
++ m_wantUpdate = false;
++ update();
++ }
++ }
++
++ void QQuick3DXrManagerPrivate::doRenderFrameAferWait(const XrFrameState &frameState)
++ {
++ Q_ASSERT(m_session != XR_NULL_HANDLE);
++
++ XrFrameBeginInfo frameBeginInfo{};
++ frameBeginInfo.type = XR_TYPE_FRAME_BEGIN_INFO;
++ if (!checkXrResult(xrBeginFrame(m_session, &frameBeginInfo))) {
++ qWarning("xrBeginFrame failed");
++ return;
++ }
++
++ QVector<XrCompositionLayerBaseHeader*> layers;
++
++ XrCompositionLayerPassthroughFB passthroughCompLayer{};
++ passthroughCompLayer.type = XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_FB;
++ if (m_fbPassthroughEnabled) {
++ if (m_passthroughLayer == XR_NULL_HANDLE)
++ createMetaQuestPassthroughLayer();
++ passthroughCompLayer.layerHandle = m_passthroughLayer;
++ passthroughCompLayer.flags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
++ passthroughCompLayer.space = XR_NULL_HANDLE;
++ layers.push_back(reinterpret_cast<XrCompositionLayerBaseHeader*>(&passthroughCompLayer));
++ }
++
++ XrCompositionLayerProjection layer{};
++ layer.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION;
++ layer.layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
++ layer.layerFlags |= XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT;
++ layer.layerFlags |= XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT;
++
++ if (frameState.shouldRender == XR_TRUE) {
++ if (renderLayer(frameState.predictedDisplayTime, frameState.predictedDisplayPeriod, layer)) {
++ layers.push_back(reinterpret_cast<XrCompositionLayerBaseHeader*>(&layer));
++ }
++ }
++
++ XrFrameEndInfo frameEndInfo{};
++ frameEndInfo.type = XR_TYPE_FRAME_END_INFO;
++ frameEndInfo.displayTime = frameState.predictedDisplayTime;
++ if (!m_fbPassthroughEnabled)
++ frameEndInfo.environmentBlendMode = m_environmentBlendMode;
++ else
++ frameEndInfo.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
++ frameEndInfo.layerCount = (uint32_t)layers.size();
++ frameEndInfo.layers = layers.data();
++ if (!checkXrResult(xrEndFrame(m_session, &frameEndInfo)))
++ qWarning("xrEndFrame failed");
++ }
++
++ bool QQuick3DXrManagerPrivate::finalizeGraphics(QRhi *rhi)
++ {
++ QSSG_ASSERT(rhi != nullptr && m_graphics != nullptr, return false);
++
++ if (m_multiviewRendering && !rhi->isFeatureSupported(QRhi::MultiView)) {
++ qCDebug(lcQuick3DXr) << "Multiview rendering is not supported with the current graphics API";
++ m_multiviewRendering = false;
++ }
++
++ #if QT_CONFIG(graphicsframecapture)
++ if (m_frameCapture) {
++ m_frameCapture->setCapturePath(QLatin1String("."));
++ m_frameCapture->setCapturePrefix(QLatin1String("quick3dxr"));
++ m_frameCapture->setRhi(rhi);
++ if (!m_frameCapture->isLoaded()) {
++ qWarning("Quick 3D XR: Frame capture was requested but QGraphicsFrameCapture is not initialized"
++ " (or has no backends enabled in the Qt build)");
++ } else {
++ qCDebug(lcQuick3DXr, "Quick 3D XR: Frame capture initialized");
++ }
++ }
++ #endif
++
++ m_isGraphicsInitialized = m_graphics->finializeGraphics(rhi);
++ return m_isGraphicsInitialized;
++ }
++
++ bool QQuick3DXrManagerPrivate::initialize()
++ {
++ Q_Q(QQuick3DXrManager);
++ // This, meaning constructing the QGraphicsFrameCapture if we'll want it,
++ // must be done as early as possible, before initalizing graphics. In fact
++ // in hybrid apps it might be too late at this point if Qt Quick (so someone
++ // outside our control) has initialized graphics which then makes
++ // RenderDoc's hooking mechanisms disfunctional.
++ if (qEnvironmentVariableIntValue("QT_QUICK3D_XR_FRAME_CAPTURE")) {
++ #if QT_CONFIG(graphicsframecapture)
++ m_frameCapture.reset(new QGraphicsFrameCapture);
++ #else
++ qWarning("Quick 3D XR: Frame capture was requested, but Qt is built without QGraphicsFrameCapture");
++ #endif
++ }
++
++ #ifdef XR_USE_PLATFORM_ANDROID
++ // Initialize the Loader
++ PFN_xrInitializeLoaderKHR xrInitializeLoaderKHR;
++ xrGetInstanceProcAddr(
++ XR_NULL_HANDLE, "xrInitializeLoaderKHR", (PFN_xrVoidFunction*)&xrInitializeLoaderKHR);
++ if (xrInitializeLoaderKHR != NULL) {
++ m_javaVM = QJniEnvironment::javaVM();
++ m_androidActivity = QNativeInterface::QAndroidApplication::context();
++
++ XrLoaderInitInfoAndroidKHR loaderInitializeInfoAndroid;
++ memset(&loaderInitializeInfoAndroid, 0, sizeof(loaderInitializeInfoAndroid));
++ loaderInitializeInfoAndroid.type = XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR;
++ loaderInitializeInfoAndroid.next = NULL;
++ loaderInitializeInfoAndroid.applicationVM = m_javaVM;
++ loaderInitializeInfoAndroid.applicationContext = m_androidActivity.object();
++ XrResult xrResult = xrInitializeLoaderKHR((XrLoaderInitInfoBaseHeaderKHR*)&loaderInitializeInfoAndroid);
++ if (xrResult != XR_SUCCESS) {
++ qWarning("Failed to initialize OpenXR Loader: %s", to_string(xrResult));
++ return false;
++ }
++ }
++ #endif
++
++ // Init the Graphics Backend
++ auto graphicsAPI = QQuickWindow::graphicsApi();
++
++ m_graphics = nullptr;
++ #ifdef XR_USE_GRAPHICS_API_VULKAN
++ if (graphicsAPI == QSGRendererInterface::Vulkan)
++ m_graphics = new QOpenXRGraphicsVulkan;
++ #endif
++ #ifdef XR_USE_GRAPHICS_API_D3D11
++ if (graphicsAPI == QSGRendererInterface::Direct3D11)
++ m_graphics = new QOpenXRGraphicsD3D11;
++ #endif
++ #ifdef XR_USE_GRAPHICS_API_D3D12
++ if (graphicsAPI == QSGRendererInterface::Direct3D12)
++ m_graphics = new QOpenXRGraphicsD3D12;
++ #endif
++ #ifdef XR_USE_GRAPHICS_API_OPENGL
++ if (graphicsAPI == QSGRendererInterface::OpenGL)
++ m_graphics = new QOpenXRGraphicsOpenGL;
++ #endif
++ #ifdef XR_USE_GRAPHICS_API_OPENGL_ES
++ if (graphicsAPI == QSGRendererInterface::OpenGL)
++ m_graphics = new QOpenXRGraphicsOpenGLES;
++ #endif
++ #ifdef XR_USE_GRAPHICS_API_METAL
++ if (graphicsAPI == QSGRendererInterface::Metal)
++ m_graphics = new QOpenXRGraphicsMetal;
++ #endif
++
++ if (!m_graphics) {
++ qWarning() << "The Qt Quick Scenegraph is not using a supported RHI mode:" << graphicsAPI;
++ return false;
++ }
++
++ // Print out extension and layer information
++ checkXrExtensions(nullptr);
++ checkXrLayers();
++
++ m_spaceExtension = QQuick3DXrAnchorManager::instance();
++
++ // Create Instance
++ XrResult result = createXrInstance();
++ if (result != XR_SUCCESS) {
++ setErrorString(result, "xrCreateInstance");
++ delete m_graphics;
++ m_graphics = nullptr;
++ return false;
++ } else {
++ checkXrInstance();
++ }
++
++ // Catch OpenXR runtime messages via XR_EXT_debug_utils and route them to qDebug
++ setupDebugMessenger();
++
++ // Load System
++ result = initializeSystem();
++ if (result != XR_SUCCESS) {
++ setErrorString(result, "xrGetSystem");
++ delete m_graphics;
++ m_graphics = nullptr;
++ return false;
++ }
++
++ // Setup Graphics
++ if (!q->setupGraphics()) {
++ m_errorString = QObject::tr("Failed to set up 3D API integration");
++ delete m_graphics;
++ m_graphics = nullptr;
++ return false;
++ }
++
++ // Create Session
++ XrSessionCreateInfo xrSessionInfo{};
++ xrSessionInfo.type = XR_TYPE_SESSION_CREATE_INFO;
++ xrSessionInfo.systemId = m_systemId;
++
++ auto overlayLayerPlacement = qEnvironmentVariableIntValue("QT_QUICK3D_XR_OVERLAY_PLACEMENT");
++ if (m_overlayExtensionSupported && overlayLayerPlacement > 0) {
++ XrSessionCreateInfoOverlayEXTX overlayInfo {};
++ overlayInfo.type = XR_TYPE_SESSION_CREATE_INFO_OVERLAY_EXTX;
++ overlayInfo.sessionLayersPlacement = overlayLayerPlacement;
++ overlayInfo.next = m_graphics->handle();
++ xrSessionInfo.next = &overlayInfo;
++ qCDebug(lcQuick3DXr, "requesting overlay placement: %d", overlayLayerPlacement);
++ } else {
++ xrSessionInfo.next = m_graphics->handle();
++ }
++
++ result = xrCreateSession(m_instance, &xrSessionInfo, &m_session);
++ if (result != XR_SUCCESS) {
++ setErrorString(result, "xrCreateSession");
++ delete m_graphics;
++ m_graphics = nullptr;
++ return false;
++ }
++
++ const bool disableAsyncWait = qEnvironmentVariableIntValue("QT_QUICK3D_XR_DISABLE_ASYNC_WAIT") != 0;
++ if (Q_LIKELY(!disableAsyncWait))
++ initWorkerThread();
++
++ // Meta Quest Specific Setup
++ if (m_colorspaceExtensionSupported)
++ setupMetaQuestColorSpaces();
++ if (m_displayRefreshRateExtensionSupported)
++ setupMetaQuestRefreshRates();
++ if (m_spaceExtensionSupported)
++ m_spaceExtension->initialize(m_instance, m_session);
++
++ checkReferenceSpaces();
++
++ // Setup Input
++ m_inputManager = QQuick3DXrInputManager::instance();
++ if (QSSG_GUARD(m_inputManager != nullptr))
++ QQuick3DXrInputManagerPrivate::get(m_inputManager)->init(m_instance, m_session);
++
++ if (!setupAppSpace())
++ return false;
++ if (!setupViewSpace())
++ return false;
++
++ if (!createSwapchains())
++ return false;
++
++ return true;
++ }
++
++ void QQuick3DXrManagerPrivate::teardown()
++ {
++ m_waitingForFrame = false;
++ m_wantUpdate = false;
++ destroyWorkerThread();
++
++ if (m_inputManager) {
++ QQuick3DXrInputManagerPrivate::get(m_inputManager)->teardown();
++ m_inputManager = nullptr;
++ }
++
++ if (m_spaceExtension) {
++ m_spaceExtension->teardown();
++ m_spaceExtension = nullptr;
++ }
++
++ if (m_passthroughLayer)
++ destroyMetaQuestPassthroughLayer();
++ if (m_passthroughFeature)
++ destroyMetaQuestPassthrough();
++
++ destroySwapchain();
++
++ if (m_appSpace != XR_NULL_HANDLE) {
++ xrDestroySpace(m_appSpace);
++ }
++
++ if (m_viewSpace != XR_NULL_HANDLE)
++ xrDestroySpace(m_viewSpace);
++
++ xrDestroySession(m_session);
++
++ #ifdef XR_EXT_debug_utils
++ if (m_debugMessenger) {
++ m_xrDestroyDebugUtilsMessengerEXT(m_debugMessenger);
++ m_debugMessenger = XR_NULL_HANDLE;
++ }
++ #endif // XR_EXT_debug_utils
++
++ xrDestroyInstance(m_instance);
++
++ // early deinit for graphics, so it can destroy owned QRhi resources
++ // Note: Used to be part of the XRmanager dtor.
++ if (m_graphics)
++ m_graphics->releaseResources();
++ }
++
++ void QQuick3DXrManagerPrivate::initWorkerThread()
++ {
++ Q_Q(QQuick3DXrManager);
++
++ qCDebug(lcQuick3DXr, "Initializing OpenXR worker thread");
++ m_workerThread = new QThread();
++ m_workerThread->setObjectName(QLatin1StringView(s_workerThreadName));
++
++ m_worker = new QQuick3DOpenXRThreadWorker(m_session);
++ m_worker->moveToThread(m_workerThread);
++
++ QObject::connect(
++ m_worker,
++ &QQuick3DOpenXRThreadWorker::frameWaited,
++ q,
++ [this](XrResult result, const XrFrameState &frameState) { onFrameWaitCompleted(result, frameState); },
++ Qt::QueuedConnection);
++
++ QObject::connect(m_workerThread, &QThread::finished, m_worker, &QObject::deleteLater);
++
++ m_workerThread->start();
++ }
++
++ void QQuick3DXrManagerPrivate::destroyWorkerThread()
++ {
++ if (m_workerThread) {
++ m_workerThread->quit();
++ m_workerThread->wait();
++ delete m_workerThread;
++ m_workerThread = nullptr;
++ m_worker = nullptr;
++ qCDebug(lcQuick3DXr, "OpenXR worker thread destroyed");
++ }
++ }
++
++ void QQuick3DXrManagerPrivate::checkReferenceSpaces()
++ {
++ Q_ASSERT(m_session != XR_NULL_HANDLE);
++
++ uint32_t spaceCount;
++ if (!checkXrResult(xrEnumerateReferenceSpaces(m_session, 0, &spaceCount, nullptr))) {
++ qWarning("Failed to enumerate reference spaces");
++ return;
++ }
++ m_availableReferenceSpace.resize(spaceCount);
++ if (!checkXrResult(xrEnumerateReferenceSpaces(m_session, spaceCount, &spaceCount, m_availableReferenceSpace.data()))) {
++ qWarning("Failed to enumerate reference spaces");
++ return;
++ }
++
++ qCDebug(lcQuick3DXr, "Available reference spaces: %d", spaceCount);
++ for (XrReferenceSpaceType space : std::as_const(m_availableReferenceSpace)) {
++ qCDebug(lcQuick3DXr, " Name: %s", to_string(space));
++ }
++ }
++
++ bool QQuick3DXrManagerPrivate::isReferenceSpaceAvailable(XrReferenceSpaceType type)
++ {
++ return m_availableReferenceSpace.contains(type);
++ }
++
++ bool QQuick3DXrManagerPrivate::setupAppSpace()
++ {
++ Q_Q(QQuick3DXrManager);
++
++ Q_ASSERT(m_session != XR_NULL_HANDLE);
++
++ XrPosef identityPose;
++ identityPose.orientation.w = 1;
++ identityPose.orientation.x = 0;
++ identityPose.orientation.y = 0;
++ identityPose.orientation.z = 0;
++ identityPose.position.x = 0;
++ identityPose.position.y = 0;
++ identityPose.position.z = 0;
++
++ XrReferenceSpaceType newReferenceSpace;
++ XrSpace newAppSpace = XR_NULL_HANDLE;
++ m_isEmulatingLocalFloor = false;
++
++ if (isReferenceSpaceAvailable(m_requestedReferenceSpace)) {
++ newReferenceSpace = m_requestedReferenceSpace;
++ } else if (m_requestedReferenceSpace == XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT &&
++ isReferenceSpaceAvailable(XR_REFERENCE_SPACE_TYPE_STAGE)) {
++ m_isEmulatingLocalFloor = true;
++ m_isFloorResetPending = true;
++ newReferenceSpace = XR_REFERENCE_SPACE_TYPE_LOCAL;
++ } else {
++ qWarning("Requested reference space is not available");
++ newReferenceSpace = XR_REFERENCE_SPACE_TYPE_LOCAL;
++ }
++
++ // App Space
++ qCDebug(lcQuick3DXr, "Creating new reference space for app space: %s", to_string(newReferenceSpace));
++ XrReferenceSpaceCreateInfo referenceSpaceCreateInfo{};
++ referenceSpaceCreateInfo.type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO;
++ referenceSpaceCreateInfo.poseInReferenceSpace = identityPose;
++ referenceSpaceCreateInfo.referenceSpaceType = newReferenceSpace;
++ if (!checkXrResult(xrCreateReferenceSpace(m_session, &referenceSpaceCreateInfo, &newAppSpace))) {
++ qWarning("Failed to create app space");
++ return false;
++ }
++
++ if (m_appSpace)
++ xrDestroySpace(m_appSpace);
++
++ m_appSpace = newAppSpace;
++ m_referenceSpace = newReferenceSpace;
++ // only broadcast the reference space change if we are not emulating the local floor
++ // since we'll try and change the referenceSpace again once we have tracking
++ if (!m_isFloorResetPending)
++ emit q->referenceSpaceChanged();
++
++ return true;
++
++ }
++
++ void QQuick3DXrManagerPrivate::updateAppSpace(XrTime predictedDisplayTime)
++ {
++ Q_Q(QQuick3DXrManager);
++
++ // If the requested reference space is not the current one, we need to
++ // re-create the app space now
++ if (m_requestedReferenceSpace != m_referenceSpace && !m_isFloorResetPending) {
++ if (!setupAppSpace()) {
++ // If we can't set the requested reference space, use the current one
++ qWarning("Setting requested reference space failed");
++ m_requestedReferenceSpace = m_referenceSpace;
++ return;
++ }
++ }
++
++ // This happens when we setup the emulated LOCAL_FLOOR mode
++ // We may have requested it on app setup, but we need to have
++ // some tracking information to calculate the floor height so
++ // that will only happen once we get here.
++ if (m_isFloorResetPending) {
++ if (!resetEmulatedFloorHeight(predictedDisplayTime)) {
++ // It didn't work, so give up and use local space (which is already setup).
++ m_requestedReferenceSpace = XR_REFERENCE_SPACE_TYPE_LOCAL;
++ emit q->referenceSpaceChanged();
++ }
++ return;
++ }
++
++ }
++
++ bool QQuick3DXrManagerPrivate::setupViewSpace()
++ {
++ Q_ASSERT(m_session != XR_NULL_HANDLE);
++
++ XrPosef identityPose;
++ identityPose.orientation.w = 1;
++ identityPose.orientation.x = 0;
++ identityPose.orientation.y = 0;
++ identityPose.orientation.z = 0;
++ identityPose.position.x = 0;
++ identityPose.position.y = 0;
++ identityPose.position.z = 0;
++
++ XrSpace newViewSpace = XR_NULL_HANDLE;
++
++ XrReferenceSpaceCreateInfo referenceSpaceCreateInfo{};
++ referenceSpaceCreateInfo.type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO;
++ referenceSpaceCreateInfo.poseInReferenceSpace = identityPose;
++ referenceSpaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW;
++ if (!checkXrResult(xrCreateReferenceSpace(m_session, &referenceSpaceCreateInfo, &newViewSpace))) {
++ qWarning("Failed to create view space");
++ return false;
++ }
++
++ if (m_viewSpace != XR_NULL_HANDLE)
++ xrDestroySpace(m_viewSpace);
++
++ m_viewSpace = newViewSpace;
++
++ return true;
++ }
++
++ bool QQuick3DXrManagerPrivate::resetEmulatedFloorHeight(XrTime predictedDisplayTime)
++ {
++ Q_Q(QQuick3DXrManager);
++
++ Q_ASSERT(m_isEmulatingLocalFloor);
++
++ m_isFloorResetPending = false;
++
++ XrPosef identityPose;
++ identityPose.orientation.w = 1;
++ identityPose.orientation.x = 0;
++ identityPose.orientation.y = 0;
++ identityPose.orientation.z = 0;
++ identityPose.position.x = 0;
++ identityPose.position.y = 0;
++ identityPose.position.z = 0;
++
++ XrSpace localSpace = XR_NULL_HANDLE;
++ XrSpace stageSpace = XR_NULL_HANDLE;
++
++ XrReferenceSpaceCreateInfo createInfo{};
++ createInfo.type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO;
++ createInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
++ createInfo.poseInReferenceSpace = identityPose;
++
++ if (!checkXrResult(xrCreateReferenceSpace(m_session, &createInfo, &localSpace))) {
++ qWarning("Failed to create local space (for emulated LOCAL_FLOOR space)");
++ return false;
++ }
++
++ createInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE;
++ if (!checkXrResult(xrCreateReferenceSpace(m_session, &createInfo, &stageSpace))) {
++ qWarning("Failed to create stage space (for emulated LOCAL_FLOOR space)");
++ xrDestroySpace(localSpace);
++ return false;
++ }
++
++ XrSpaceLocation stageLocation{};
++ stageLocation.type = XR_TYPE_SPACE_LOCATION;
++ stageLocation.pose = identityPose;
++
++ if (!checkXrResult(xrLocateSpace(stageSpace, localSpace, predictedDisplayTime, &stageLocation))) {
++ qWarning("Failed to locate STAGE space in LOCAL space, in order to emulate LOCAL_FLOOR");
++ xrDestroySpace(localSpace);
++ xrDestroySpace(stageSpace);
++ return false;
++ }
++
++ xrDestroySpace(localSpace);
++ xrDestroySpace(stageSpace);
++
++ XrSpace newAppSpace = XR_NULL_HANDLE;
++ createInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
++ createInfo.poseInReferenceSpace.position.y = stageLocation.pose.position.y;
++ if (!checkXrResult(xrCreateReferenceSpace(m_session, &createInfo, &newAppSpace))) {
++ qWarning("Failed to recreate emulated LOCAL_FLOOR play space with latest floor estimate");
++ return false;
++ }
++
++ xrDestroySpace(m_appSpace);
++ m_appSpace = newAppSpace;
++ m_referenceSpace = XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT;
++ emit q->referenceSpaceChanged();
++
++ return true;
++ }
++
++ bool QQuick3DXrManagerPrivate::createSwapchains()
++ {
++ Q_ASSERT(m_session != XR_NULL_HANDLE);
++ Q_ASSERT(m_configViews.isEmpty());
++ Q_ASSERT(m_swapchains.isEmpty());
++
++ XrSystemProperties systemProperties{};
++ systemProperties.type = XR_TYPE_SYSTEM_PROPERTIES;
++
++ XrSystemHandTrackingPropertiesEXT handTrackingSystemProperties{};
++ handTrackingSystemProperties.type = XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT;
++ systemProperties.next = &handTrackingSystemProperties;
++
++ if (!checkXrResult(xrGetSystemProperties(m_instance, m_systemId, &systemProperties))) {
++ qWarning("Failed to get OpenXR system properties");
++ return false;
++ }
++ qCDebug(lcQuick3DXr, "System Properties: Name=%s VendorId=%d", systemProperties.systemName, systemProperties.vendorId);
++ qCDebug(lcQuick3DXr, "System Graphics Properties: MaxWidth=%d MaxHeight=%d MaxLayers=%d",
++ systemProperties.graphicsProperties.maxSwapchainImageWidth,
++ systemProperties.graphicsProperties.maxSwapchainImageHeight,
++ systemProperties.graphicsProperties.maxLayerCount);
++ qCDebug(lcQuick3DXr, "System Tracking Properties: OrientationTracking=%s PositionTracking=%s",
++ systemProperties.trackingProperties.orientationTracking == XR_TRUE ? "True" : "False",
++ systemProperties.trackingProperties.positionTracking == XR_TRUE ? "True" : "False");
++ qCDebug(lcQuick3DXr, "System Hand Tracking Properties: handTracking=%s",
++ handTrackingSystemProperties.supportsHandTracking == XR_TRUE ? "True" : "False");
++
++ quint32 viewCount;
++ if (!checkXrResult(xrEnumerateViewConfigurationViews(m_instance,
++ m_systemId,
++ m_viewConfigType,
++ 0,
++ &viewCount,
++ nullptr)))
++ {
++ qWarning("Failed to enumerate view configurations");
++ return false;
++ }
++ m_configViews.resize(viewCount, {XR_TYPE_VIEW_CONFIGURATION_VIEW, nullptr, 0, 0, 0, 0, 0, 0});
++ if (!checkXrResult(xrEnumerateViewConfigurationViews(m_instance,
++ m_systemId,
++ m_viewConfigType,
++ viewCount,
++ &viewCount,
++ m_configViews.data())))
++ {
++ qWarning("Failed to enumerate view configurations");
++ return false;
++ }
++ m_views.resize(viewCount, {XR_TYPE_VIEW, nullptr, {}, {}});
++ m_projectionLayerViews.resize(viewCount, {});
++ m_layerDepthInfos.resize(viewCount, {});
++
++ // Create the swapchain and get the images.
++ if (viewCount > 0) {
++ // Select a swapchain format.
++ uint32_t swapchainFormatCount;
++ if (!checkXrResult(xrEnumerateSwapchainFormats(m_session, 0, &swapchainFormatCount, nullptr))) {
++ qWarning("Failed to enumerate swapchain formats");
++ return false;
++ }
++ QVector<int64_t> swapchainFormats(swapchainFormatCount);
++ if (!checkXrResult(xrEnumerateSwapchainFormats(m_session,
++ swapchainFormats.size(),
++ &swapchainFormatCount,
++ swapchainFormats.data())))
++ {
++ qWarning("Failed to enumerate swapchain formats");
++ return false;
++ }
++
++ Q_ASSERT(static_cast<qsizetype>(swapchainFormatCount) == swapchainFormats.size());
++ m_colorSwapchainFormat = m_graphics->colorSwapchainFormat(swapchainFormats);
++ if (m_compositionLayerDepthSupported)
++ m_depthSwapchainFormat = m_graphics->depthSwapchainFormat(swapchainFormats);
++
++ // Print swapchain formats and the selected one.
++ {
++ QString swapchainFormatsString;
++ for (int64_t format : std::as_const(swapchainFormats)) {
++ const bool selectedColor = format == m_colorSwapchainFormat;
++ const bool selectedDepth = format == m_depthSwapchainFormat;
++ swapchainFormatsString += u" ";
++ if (selectedColor)
++ swapchainFormatsString += u"[";
++ else if (selectedDepth)
++ swapchainFormatsString += u"<";
++ swapchainFormatsString += QString::number(format);
++ if (selectedColor)
++ swapchainFormatsString += u"]";
++ else if (selectedDepth)
++ swapchainFormatsString += u">";
++ }
++ qCDebug(lcQuick3DXr, "Swapchain formats: %s", qPrintable(swapchainFormatsString));
++ }
++
++ const XrViewConfigurationView &vp = m_configViews[0]; // use the first view for all views, the sizes should be the same
++
++ // sampleCount for the XrSwapchain is always 1. We could take m_samples
++ // here, clamp it to vp.maxSwapchainSampleCount, and pass it in to the
++ // swapchain to get multisample textures (or a multisample texture
++ // array) out of the swapchain. This we do not do, because it was only
++ // supported with 1 out of 5 OpenXR(+streaming) combination tested on
++ // the Quest 3. In most cases, incl. Quest 3 native Android,
++ // maxSwapchainSampleCount is 1. Therefore, we do MSAA on our own, and
++ // do not rely on the XrSwapchain for this.
++
++ if (m_multiviewRendering) {
++ // Create a single swapchain with array size > 1
++ XrSwapchainCreateInfo swapchainCreateInfo{};
++ swapchainCreateInfo.type = XR_TYPE_SWAPCHAIN_CREATE_INFO;
++ swapchainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT;
++ swapchainCreateInfo.format = m_colorSwapchainFormat;
++ swapchainCreateInfo.sampleCount = 1; // we do MSAA on our own, do not need ms textures from the swapchain
++ swapchainCreateInfo.width = vp.recommendedImageRectWidth;
++ swapchainCreateInfo.height = vp.recommendedImageRectHeight;
++ swapchainCreateInfo.faceCount = 1;
++ swapchainCreateInfo.arraySize = viewCount;
++ swapchainCreateInfo.mipCount = 1;
++
++ qCDebug(lcQuick3DXr, "Creating multiview swapchain for %u view(s) with dimensions Width=%d Height=%d SampleCount=%d Format=%llx",
++ viewCount,
++ vp.recommendedImageRectWidth,
++ vp.recommendedImageRectHeight,
++ 1,
++ static_cast<long long unsigned int>(m_colorSwapchainFormat));
++
++ Swapchain swapchain;
++ swapchain.width = swapchainCreateInfo.width;
++ swapchain.height = swapchainCreateInfo.height;
++ swapchain.arraySize = swapchainCreateInfo.arraySize;
++ if (checkXrResult(xrCreateSwapchain(m_session, &swapchainCreateInfo, &swapchain.handle))) {
++ uint32_t imageCount = 0;
++ if (!checkXrResult(xrEnumerateSwapchainImages(swapchain.handle, 0, &imageCount, nullptr))) {
++ qWarning("Failed to enumerate swapchain images");
++ return false;
++ }
++
++ auto swapchainImages = m_graphics->allocateSwapchainImages(imageCount, swapchain.handle);
++ if (!checkXrResult(xrEnumerateSwapchainImages(swapchain.handle, imageCount, &imageCount, swapchainImages[0]))) {
++ qWarning("Failed to enumerate swapchain images");
++ return false;
++ }
++
++ m_swapchains.append(swapchain);
++ m_swapchainImages.insert(swapchain.handle, swapchainImages);
++ } else {
++ qWarning("xrCreateSwapchain failed (multiview)");
++ return false;
++ }
++
++ // Create the depth swapchain always when
++ // XR_KHR_composition_layer_depth is supported. If we are going to
++ // submit (use the depth image), that's a different question, and is
++ // dynamically controlled by the user.
++ if (m_compositionLayerDepthSupported && m_depthSwapchainFormat > 0) {
++ swapchainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
++ swapchainCreateInfo.format = m_depthSwapchainFormat;
++ if (checkXrResult(xrCreateSwapchain(m_session, &swapchainCreateInfo, &swapchain.handle))) {
++ uint32_t imageCount = 0;
++ if (!checkXrResult(xrEnumerateSwapchainImages(swapchain.handle, 0, &imageCount, nullptr))) {
++ qWarning("Failed to enumerate depth swapchain images");
++ return false;
++ }
++
++ auto swapchainImages = m_graphics->allocateSwapchainImages(imageCount, swapchain.handle);
++ if (!checkXrResult(xrEnumerateSwapchainImages(swapchain.handle, imageCount, &imageCount, swapchainImages[0]))) {
++ qWarning("Failed to enumerate depth swapchain images");
++ return false;
++ }
++
++ m_depthSwapchains.append(swapchain);
++ m_depthSwapchainImages.insert(swapchain.handle, swapchainImages);
++ } else {
++ qWarning("xrCreateSwapchain failed for depth swapchain (multiview)");
++ return false;
++ }
++ }
++ } else {
++ // Create a swapchain for each view.
++ for (uint32_t i = 0; i < viewCount; i++) {
++ qCDebug(lcQuick3DXr, "Creating swapchain for view %u with dimensions Width=%d Height=%d SampleCount=%d Format=%llx",
++ i,
++ vp.recommendedImageRectWidth,
++ vp.recommendedImageRectHeight,
++ 1,
++ static_cast<long long unsigned int>(m_colorSwapchainFormat));
++
++ // Create the swapchain.
++ XrSwapchainCreateInfo swapchainCreateInfo{};
++ swapchainCreateInfo.type = XR_TYPE_SWAPCHAIN_CREATE_INFO;
++ swapchainCreateInfo.arraySize = 1;
++ swapchainCreateInfo.format = m_colorSwapchainFormat;
++ swapchainCreateInfo.width = vp.recommendedImageRectWidth;
++ swapchainCreateInfo.height = vp.recommendedImageRectHeight;
++ swapchainCreateInfo.mipCount = 1;
++ swapchainCreateInfo.faceCount = 1;
++ swapchainCreateInfo.sampleCount = 1; // we do MSAA on our own, do not need ms textures from the swapchain
++ swapchainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT;
++ Swapchain swapchain {};
++ swapchain.width = swapchainCreateInfo.width;
++ swapchain.height = swapchainCreateInfo.height;
++ if (checkXrResult(xrCreateSwapchain(m_session, &swapchainCreateInfo, &swapchain.handle))) {
++ uint32_t imageCount = 0;
++ if (!checkXrResult(xrEnumerateSwapchainImages(swapchain.handle, 0, &imageCount, nullptr))) {
++ qWarning("Failed to enumerate swapchain images");
++ return false;
++ }
++
++ auto swapchainImages = m_graphics->allocateSwapchainImages(imageCount, swapchain.handle);
++ if (!checkXrResult(xrEnumerateSwapchainImages(swapchain.handle, imageCount, &imageCount, swapchainImages[0]))) {
++ qWarning("Failed to enumerate swapchain images");
++ return false;
++ }
++
++ m_swapchains.append(swapchain);
++ m_swapchainImages.insert(swapchain.handle, swapchainImages);
++ } else {
++ qWarning("xrCreateSwapchain failed (view %u)", viewCount);
++ return false;
++ }
++
++ if (m_compositionLayerDepthSupported && m_depthSwapchainFormat > 0) {
++ swapchainCreateInfo.usageFlags = XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
++ swapchainCreateInfo.format = m_depthSwapchainFormat;
++ if (checkXrResult(xrCreateSwapchain(m_session, &swapchainCreateInfo, &swapchain.handle))) {
++ uint32_t imageCount = 0;
++ if (!checkXrResult(xrEnumerateSwapchainImages(swapchain.handle, 0, &imageCount, nullptr))) {
++ qWarning("Failed to enumerate depth swapchain images");
++ return false;
++ }
++
++ auto swapchainImages = m_graphics->allocateSwapchainImages(imageCount, swapchain.handle);
++ if (!checkXrResult(xrEnumerateSwapchainImages(swapchain.handle, imageCount, &imageCount, swapchainImages[0]))) {
++ qWarning("Failed to enumerate depth swapchain images");
++ return false;
++ }
++
++ m_depthSwapchains.append(swapchain);
++ m_depthSwapchainImages.insert(swapchain.handle, swapchainImages);
++ } else {
++ qWarning("xrCreateSwapchain failed for depth swapchain (view %u)", viewCount);
++ return false;
++ }
++ }
++ }
++ }
++
++ if (m_multiviewRendering) {
++ if (m_swapchains.isEmpty())
++ return false;
++ if (m_compositionLayerDepthSupported && m_depthSwapchains.isEmpty())
++ return false;
++ } else {
++ if (m_swapchains.count() != qsizetype(viewCount))
++ return false;
++ if (m_compositionLayerDepthSupported && m_depthSwapchains.count() != qsizetype(viewCount))
++ return false;
++ }
++
++ // Setup the projection layer views.
++ for (uint32_t i = 0; i < viewCount; ++i) {
++ m_projectionLayerViews[i].type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW;
++ m_projectionLayerViews[i].next = nullptr;
++ m_projectionLayerViews[i].subImage.swapchain = m_swapchains[0].handle; // for non-multiview this gets overwritten later
++ m_projectionLayerViews[i].subImage.imageArrayIndex = i; // this too
++ m_projectionLayerViews[i].subImage.imageRect.offset.x = 0;
++ m_projectionLayerViews[i].subImage.imageRect.offset.y = 0;
++ m_projectionLayerViews[i].subImage.imageRect.extent.width = vp.recommendedImageRectWidth;
++ m_projectionLayerViews[i].subImage.imageRect.extent.height = vp.recommendedImageRectHeight;
++
++ if (m_compositionLayerDepthSupported) {
++ m_layerDepthInfos[i].type = XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR;
++ m_layerDepthInfos[i].next = nullptr;
++ m_layerDepthInfos[i].subImage.swapchain = m_depthSwapchains[0].handle; // for non-multiview this gets overwritten later
++ m_layerDepthInfos[i].subImage.imageArrayIndex = i; // this too
++ m_layerDepthInfos[i].subImage.imageRect.offset.x = 0;
++ m_layerDepthInfos[i].subImage.imageRect.offset.y = 0;
++ m_layerDepthInfos[i].subImage.imageRect.extent.width = vp.recommendedImageRectWidth;
++ m_layerDepthInfos[i].subImage.imageRect.extent.height = vp.recommendedImageRectHeight;
++ }
++ }
++ }
++
++ if (m_foveationExtensionSupported)
++ setupMetaQuestFoveation();
++
++ return true;
++ }
++
++ void QQuick3DXrManagerPrivate::setSamples(int samples)
++ {
++ if (m_samples == samples)
++ return;
++
++ m_samples = samples;
++
++ // No need to do anything more here (such as destroying and recreating the
++ // XrSwapchain) since we do not do MSAA through the swapchain.
++ }
++
++ QStringList QQuick3DXrManagerPrivate::enabledExtensions() const
++ {
++ return m_enabledExtensions;
++ }
++
++ QString QQuick3DXrManagerPrivate::runtimeName() const
++ {
++ return m_runtimeName;
++ }
++
++ QVersionNumber QQuick3DXrManagerPrivate::runtimeVersion() const
++ {
++ return m_runtimeVersion;
++ }
++
++ void QQuick3DXrManagerPrivate::setMultiViewRenderingEnabled(bool enable)
++ {
++ Q_Q(QQuick3DXrManager);
++ QRhi *rhi = q->m_renderControl->rhi();
++ if (m_multiviewRendering == enable || !rhi)
++ return;
++ if (enable && !rhi->isFeatureSupported(QRhi::MultiView)) {
++ qWarning("Quick 3D XR: Multiview rendering was enabled, but is reported as unsupported from the current QRhi backend (%s)",
++ rhi->backendName());
++ return;
++ }
++ m_multiviewRendering = enable;
++ qCDebug(lcQuick3DXr, "Multiview rendering %s", m_multiviewRendering ? "enabled" : "disabled");
++ if (!m_swapchains.isEmpty()) {
++ qCDebug(lcQuick3DXr, "OpenXR swapchain already exists, creating new one due to change in multiview mode");
++ destroySwapchain();
++ createSwapchains();
++
++ emit q->multiViewRenderingEnabledChanged();
++ }
++ }
++
++ bool QQuick3DXrManagerPrivate::isPassthroughEnabled() const
++ {
++ // FB passthrough is enabled
++ if (m_fbPassthroughEnabled)
++ return true;
++
++ // Blend mode passthrough is enabled (ALPHA_BLEND or ADDITIVE)
++ return m_environmentBlendMode == XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND
++ || m_environmentBlendMode == XR_ENVIRONMENT_BLEND_MODE_ADDITIVE;
++ }
++
++ void QQuick3DXrManagerPrivate::setFBPassthroughEnabled(bool enable)
++ {
++ m_fbPassthroughEnabled = enable;
++ if (enable) {
++ if (m_passthroughFeature == XR_NULL_HANDLE)
++ createMetaQuestPassthrough();
++ else
++ startMetaQuestPassthrough();
++
++ if (m_passthroughLayer == XR_NULL_HANDLE)
++ createMetaQuestPassthroughLayer();
++ else
++ resumeMetaQuestPassthroughLayer();
++ } else {
++ if (m_passthroughLayer)
++ pauseMetaQuestPassthroughLayer();
++ if (m_passthroughFeature)
++ pauseMetaQuestPassthrough();
++ }
++ }
++
++ bool QQuick3DXrManagerPrivate::setTransparentBlendMode(bool enable)
++ {
++ if (enable) {
++ if (m_blendModes.contains(XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND))
++ m_environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND;
++ else if (m_blendModes.contains(XR_ENVIRONMENT_BLEND_MODE_ADDITIVE))
++ m_environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_ADDITIVE;
++ else
++ return false;
++ } else {
++ if (m_blendModes.contains(XR_ENVIRONMENT_BLEND_MODE_OPAQUE))
++ m_environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
++ else
++ return false;
++ }
++ qCDebug(lcQuick3DXr, "Environment blend mode set to: %s", to_string(m_environmentBlendMode));
++ return true;
++ }
++
++ bool QQuick3DXrManagerPrivate::setPassthroughEnabled(bool enable)
++ {
++ if (supportsFBPassthrough()) {
++ setFBPassthroughEnabled(enable);
++ return true;
++ }
++
++ return setTransparentBlendMode(enable);
++ }
++
++ void QQuick3DXrManagerPrivate::setDepthSubmissionEnabled(bool enable)
++ {
++ if (m_submitLayerDepth == enable)
++ return;
++
++ if (m_compositionLayerDepthSupported) {
++ if (enable)
++ qCDebug(lcQuick3DXr, "Enabling submitLayerDepth");
++
++ m_submitLayerDepth = enable;
++ }
++ }
++
++ void QQuick3DXrManagerPrivate::setReferenceSpace(QtQuick3DXr::ReferenceSpace newReferenceSpace)
++ {
++ XrReferenceSpaceType referenceSpace = getXrReferenceSpaceType(newReferenceSpace);
++ if (m_referenceSpace == referenceSpace)
++ return;
++
++ m_requestedReferenceSpace = referenceSpace;
++
++ // we do not emit a changed signal here because it hasn't
++ // changed yet.
++ }
++
++ QtQuick3DXr::ReferenceSpace QQuick3DXrManagerPrivate::getReferenceSpace() const
++ {
++ return getReferenceSpaceType(m_referenceSpace);
++ }
++
++ void QQuick3DXrManagerPrivate::getDefaultClipDistances(float &nearClip, float &farClip) const
++ {
++ // Hardcoded defaults
++ nearClip = 1.0f;
++ farClip = 10000.0f;
++ }
++
++ void QQuick3DXrManagerPrivate::pollEvents(bool *exitRenderLoop, bool *requestRestart) {
++ *exitRenderLoop = false;
++ *requestRestart = false;
++
++ auto readNextEvent = [this]() {
++ // It is sufficient to clear the just the XrEventDataBuffer header to
++ // XR_TYPE_EVENT_DATA_BUFFER
++ XrEventDataBaseHeader* baseHeader = reinterpret_cast<XrEventDataBaseHeader*>(&m_eventDataBuffer);
++ *baseHeader = {XR_TYPE_EVENT_DATA_BUFFER, nullptr};
++ const XrResult xr = xrPollEvent(m_instance, &m_eventDataBuffer);
++ if (xr == XR_SUCCESS) {
++ if (baseHeader->type == XR_TYPE_EVENT_DATA_EVENTS_LOST) {
++ const XrEventDataEventsLost* const eventsLost = reinterpret_cast<const XrEventDataEventsLost*>(baseHeader);
++ qCDebug(lcQuick3DXr, "%d events lost", eventsLost->lostEventCount);
++ }
++
++ return baseHeader;
++ }
++
++ return static_cast<XrEventDataBaseHeader*>(nullptr);
++ };
++
++ auto handleSessionStateChangedEvent = [this](const XrEventDataSessionStateChanged& stateChangedEvent,
++ bool* exitRenderLoop,
++ bool* requestRestart) {
++ const XrSessionState oldState = m_sessionState;
++ m_sessionState = stateChangedEvent.state;
++
++ qCDebug(lcQuick3DXr, "XrEventDataSessionStateChanged: state %s->%s time=%lld",
++ to_string(oldState),
++ to_string(m_sessionState),
++ static_cast<long long int>(stateChangedEvent.time));
++
++ if ((stateChangedEvent.session != XR_NULL_HANDLE) && (stateChangedEvent.session != m_session)) {
++ qCDebug(lcQuick3DXr, "XrEventDataSessionStateChanged for unknown session");
++ return;
++ }
++
++ switch (m_sessionState) {
++ case XR_SESSION_STATE_READY: {
++ Q_ASSERT(m_session != XR_NULL_HANDLE);
++ XrSessionBeginInfo sessionBeginInfo{};
++ sessionBeginInfo.type = XR_TYPE_SESSION_BEGIN_INFO;
++ sessionBeginInfo.primaryViewConfigurationType = m_viewConfigType;
++ if (!checkXrResult(xrBeginSession(m_session, &sessionBeginInfo))) {
++ qWarning("xrBeginSession failed");
++ break;
++ }
++ m_sessionRunning = true;
++ break;
++ }
++ case XR_SESSION_STATE_STOPPING: {
++ Q_ASSERT(m_session != XR_NULL_HANDLE);
++ m_sessionRunning = false;
++ if (!checkXrResult(xrEndSession(m_session)))
++ qWarning("xrEndSession failed");
++ break;
++ }
++ case XR_SESSION_STATE_EXITING: {
++ *exitRenderLoop = true;
++ // Do not attempt to restart because user closed this session.
++ *requestRestart = false;
++ break;
++ }
++ case XR_SESSION_STATE_LOSS_PENDING: {
++ *exitRenderLoop = true;
++ // Poll for a new instance.
++ *requestRestart = true;
++ break;
++ }
++ default:
++ break;
++ }
++ };
++
++ // Process all pending messages.
++ while (const XrEventDataBaseHeader* event = readNextEvent()) {
++ switch (event->type) {
++ case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: {
++ const auto& instanceLossPending = *reinterpret_cast<const XrEventDataInstanceLossPending*>(event);
++ qCDebug(lcQuick3DXr, "XrEventDataInstanceLossPending by %lld", static_cast<long long int>(instanceLossPending.lossTime));
++ *exitRenderLoop = true;
++ *requestRestart = true;
++ return;
++ }
++ case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: {
++ auto sessionStateChangedEvent = *reinterpret_cast<const XrEventDataSessionStateChanged*>(event);
++ handleSessionStateChangedEvent(sessionStateChangedEvent, exitRenderLoop, requestRestart);
++ break;
++ }
++ case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED:
++ break;
++ case XR_TYPE_EVENT_DATA_SPACE_SET_STATUS_COMPLETE_FB:
++ case XR_TYPE_EVENT_DATA_SPACE_QUERY_RESULTS_AVAILABLE_FB:
++ case XR_TYPE_EVENT_DATA_SPACE_QUERY_COMPLETE_FB:
++ case XR_TYPE_EVENT_DATA_SCENE_CAPTURE_COMPLETE_FB:
++ // Handle these events in the space extension
++ if (m_spaceExtension)
++ m_spaceExtension->handleEvent(event);
++ break;
++ case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING:
++ default: {
++ qCDebug(lcQuick3DXr, "Ignoring event type %d", event->type);
++ break;
++ }
++ }
++ }
++ }
++
++ bool QQuick3DXrManagerPrivate::renderLayer(XrTime predictedDisplayTime,
++ XrDuration predictedDisplayPeriod,
++ XrCompositionLayerProjection &layer)
++ {
++ Q_Q(QQuick3DXrManager);
++ auto *xrOrigin = q->m_xrOrigin;
++ auto *animationDriver = q->m_animationDriver;
++ auto *renderControl = q->m_renderControl;
++
++ XrResult res;
++
++ XrViewState viewState{};
++ viewState.type = XR_TYPE_VIEW_STATE;
++ quint32 viewCapacityInput = m_views.size();
++ quint32 viewCountOutput;
++
++ // Check if we need to update the app space before we use it
++ updateAppSpace(predictedDisplayTime);
++
++ XrViewLocateInfo viewLocateInfo{};
++ viewLocateInfo.type = XR_TYPE_VIEW_LOCATE_INFO;
++ viewLocateInfo.viewConfigurationType = m_viewConfigType;
++ viewLocateInfo.displayTime = predictedDisplayTime;
++ viewLocateInfo.space = m_appSpace;
++
++ res = xrLocateViews(m_session, &viewLocateInfo, &viewState, viewCapacityInput, &viewCountOutput, m_views.data());
++ if (XR_UNQUALIFIED_SUCCESS(res)) {
++ Q_ASSERT(viewCountOutput == viewCapacityInput);
++ Q_ASSERT(static_cast<qsizetype>(viewCountOutput) == m_configViews.size());
++ Q_ASSERT(static_cast<qsizetype>(viewCountOutput) == m_projectionLayerViews.size());
++ Q_ASSERT(m_multiviewRendering ? viewCountOutput == m_swapchains[0].arraySize : static_cast<qsizetype>(viewCountOutput) == m_swapchains.size());
++
++ // Update the camera/head position
++ XrSpaceLocation location{};
++ location.type = XR_TYPE_SPACE_LOCATION;
++ if (checkXrResult(xrLocateSpace(m_viewSpace, m_appSpace, predictedDisplayTime, &location))) {
++ QVector3D position = QVector3D(location.pose.position.x,
++ location.pose.position.y,
++ location.pose.position.z) * 100.0f; // convert m to cm
++ QQuaternion rotation(location.pose.orientation.w,
++ location.pose.orientation.x,
++ location.pose.orientation.y,
++ location.pose.orientation.z);
++
++ xrOrigin->updateTrackedCamera(position, rotation);
++ }
++
++ // Set the hand positions
++ if (QSSG_GUARD(m_inputManager != nullptr))
++ QQuick3DXrInputManagerPrivate::get(m_inputManager)->updatePoses(predictedDisplayTime, m_appSpace);
++
++ // Spatial Anchors
++ if (m_spaceExtension)
++ m_spaceExtension->updateAnchors(predictedDisplayTime, m_appSpace);
++
++ if (m_handtrackingExtensionSupported && m_inputManager)
++ QQuick3DXrInputManagerPrivate::get(m_inputManager)->updateHandtracking(predictedDisplayTime, m_appSpace, m_handtrackingAimExtensionSupported);
++
++ // Before rendering individual views, advance the animation driver once according
++ // to the expected display time
++
++ const qint64 displayPeriodMS = predictedDisplayPeriod / 1000000;
++ const qint64 displayDeltaMS = (predictedDisplayTime - m_previousTime) / 1000000;
++
++ if (m_previousTime == 0 || !animationDriver->isRunning()) {
++ animationDriver->setStep(displayPeriodMS);
++ } else {
++ if (displayDeltaMS > displayPeriodMS)
++ animationDriver->setStep(displayPeriodMS);
++ else
++ animationDriver->setStep(displayDeltaMS);
++ animationDriver->advance();
++ }
++ m_previousTime = predictedDisplayTime;
++
++ #if QT_CONFIG(graphicsframecapture)
++ if (m_frameCapture)
++ m_frameCapture->startCaptureFrame();
++ #endif
++
++ if (m_submitLayerDepth && m_samples > 1) {
++ if (!renderControl->rhi()->isFeatureSupported(QRhi::ResolveDepthStencil)) {
++ static bool warned = false;
++ if (!warned) {
++ warned = true;
++ qWarning("Quick3D XR: Submitting depth buffer with MSAA cannot be enabled"
++ " when depth-stencil resolve is not supported by the underlying 3D API (%s)",
++ renderControl->rhi()->backendName());
++ }
++ m_submitLayerDepth = false;
++ }
++ }
++
++ if (m_multiviewRendering) {
++ const Swapchain swapchain = m_swapchains[0];
++
++ // Acquire the swapchain image array
++ XrSwapchainImageAcquireInfo acquireInfo{};
++ acquireInfo.type = XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO;
++ uint32_t swapchainImageIndex = 0;
++ if (!checkXrResult(xrAcquireSwapchainImage(swapchain.handle, &acquireInfo, &swapchainImageIndex))) {
++ qWarning("Failed to acquire swapchain image (multiview)");
++ return false;
++ }
++ XrSwapchainImageWaitInfo waitInfo{};
++ waitInfo.type = XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO;
++ waitInfo.timeout = XR_INFINITE_DURATION;
++ if (!checkXrResult(xrWaitSwapchainImage(swapchain.handle, &waitInfo))) {
++ qWarning("Failed to wait for swapchain image (multiview)");
++ return false;
++ }
++ XrSwapchainImageBaseHeader *swapchainImage = m_swapchainImages[swapchain.handle][swapchainImageIndex];
++
++ XrSwapchainImageBaseHeader *depthSwapchainImage = nullptr;
++ if (m_submitLayerDepth && !m_depthSwapchains.isEmpty()) {
++ if (checkXrResult(xrAcquireSwapchainImage(m_depthSwapchains[0].handle, &acquireInfo, &swapchainImageIndex))) {
++ if (checkXrResult(xrWaitSwapchainImage(m_depthSwapchains[0].handle, &waitInfo)))
++ depthSwapchainImage = m_depthSwapchainImages[m_depthSwapchains[0].handle][swapchainImageIndex];
++ else
++ qWarning("Failed to wait for depth swapchain image (multiview)");
++ } else {
++ qWarning("Failed to acquire depth swapchain image (multiview)");
++ }
++ }
++
++ // First update both cameras with the latest view information and
++ // then set them on the viewport (since this is going to be
++ // multiview rendering).
++ for (uint32_t i = 0; i < viewCountOutput; i++) {
++ // subImage.swapchain and imageArrayIndex are already set and correct
++ m_projectionLayerViews[i].pose = m_views[i].pose;
++ m_projectionLayerViews[i].fov = m_views[i].fov;
++ }
++ updateCameraMultiview(0, viewCountOutput);
++
++ // Perform the rendering. In multiview mode it is done just once,
++ // targeting all the views (outputting simultaneously to all texture
++ // array layers). The subImage dimensions are the same, that's why
++ // passing in the first layerView's subImage works.
++ doRender(m_projectionLayerViews[0].subImage,
++ swapchainImage,
++ depthSwapchainImage);
++
++ for (uint32_t i = 0; i < viewCountOutput; i++) {
++ if (m_submitLayerDepth) {
++ m_layerDepthInfos[i].minDepth = 0;
++ m_layerDepthInfos[i].maxDepth = 1;
++ QQuick3DXrEyeCamera *cam = xrOrigin ? xrOrigin->eyeCamera(i) : nullptr;
++ m_layerDepthInfos[i].nearZ = cam ? cam->clipNear() : 1.0f;
++ m_layerDepthInfos[i].farZ = cam ? cam->clipFar() : 10000.0f;
++ m_projectionLayerViews[i].next = &m_layerDepthInfos[i];
++ } else {
++ m_projectionLayerViews[i].next = nullptr;
++ }
++ }
++
++ // release the swapchain image array
++ XrSwapchainImageReleaseInfo releaseInfo{};
++ releaseInfo.type = XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO;
++ if (!checkXrResult(xrReleaseSwapchainImage(swapchain.handle, &releaseInfo)))
++ qWarning("Failed to release swapchain image");
++ if (depthSwapchainImage) {
++ if (!checkXrResult(xrReleaseSwapchainImage(m_depthSwapchains[0].handle, &releaseInfo)))
++ qWarning("Failed to release depth swapchain image");
++ }
++ } else {
++ for (uint32_t i = 0; i < viewCountOutput; i++) {
++ // Each view has a separate swapchain which is acquired, rendered to, and released.
++ const Swapchain viewSwapchain = m_swapchains[i];
++
++ // Render view to the appropriate part of the swapchain image.
++ XrSwapchainImageAcquireInfo acquireInfo{};
++ acquireInfo.type = XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO;
++ uint32_t swapchainImageIndex = 0;
++ if (!checkXrResult(xrAcquireSwapchainImage(viewSwapchain.handle, &acquireInfo, &swapchainImageIndex))) {
++ qWarning("Failed to acquire swapchain image");
++ return false;
++ }
++ XrSwapchainImageWaitInfo waitInfo{};
++ waitInfo.type = XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO;
++ waitInfo.timeout = XR_INFINITE_DURATION;
++ if (!checkXrResult(xrWaitSwapchainImage(viewSwapchain.handle, &waitInfo))) {
++ qWarning("Failed to wait for swapchain image");
++ return false;
++ }
++ XrSwapchainImageBaseHeader *swapchainImage = m_swapchainImages[viewSwapchain.handle][swapchainImageIndex];
++
++ XrSwapchainImageBaseHeader *depthSwapchainImage = nullptr;
++ if (m_submitLayerDepth && !m_depthSwapchains.isEmpty()) {
++ if (checkXrResult(xrAcquireSwapchainImage(m_depthSwapchains[i].handle, &acquireInfo, &swapchainImageIndex))) {
++ if (checkXrResult(xrWaitSwapchainImage(m_depthSwapchains[i].handle, &waitInfo)))
++ depthSwapchainImage = m_depthSwapchainImages[m_depthSwapchains[i].handle][swapchainImageIndex];
++ else
++ qWarning("Failed to wait for depth swapchain image");
++ } else {
++ qWarning("Failed to acquire depth swapchain image");
++ }
++ }
++
++ m_projectionLayerViews[i].subImage.swapchain = viewSwapchain.handle;
++ m_projectionLayerViews[i].subImage.imageArrayIndex = 0;
++ m_projectionLayerViews[i].pose = m_views[i].pose;
++ m_projectionLayerViews[i].fov = m_views[i].fov;
++
++ updateCameraNonMultiview(i, m_projectionLayerViews[i]);
++
++ doRender(m_projectionLayerViews[i].subImage,
++ swapchainImage,
++ depthSwapchainImage);
++
++ if (depthSwapchainImage) {
++ m_layerDepthInfos[i].subImage.swapchain = m_depthSwapchains[i].handle;
++ m_layerDepthInfos[i].subImage.imageArrayIndex = 0;
++ m_layerDepthInfos[i].minDepth = 0;
++ m_layerDepthInfos[i].maxDepth = 1;
++ QQuick3DXrEyeCamera *cam = xrOrigin ? xrOrigin->eyeCamera(i) : nullptr;
++ m_layerDepthInfos[i].nearZ = cam ? cam->clipNear() : 1.0f;
++ m_layerDepthInfos[i].farZ = cam ? cam->clipFar() : 10000.0f;
++ m_projectionLayerViews[i].next = &m_layerDepthInfos[i];
++ } else {
++ m_projectionLayerViews[i].next = nullptr;
++ }
++
++ XrSwapchainImageReleaseInfo releaseInfo{};
++ releaseInfo.type = XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO;
++ if (!checkXrResult(xrReleaseSwapchainImage(viewSwapchain.handle, &releaseInfo)))
++ qWarning("Failed to release swapchain image");
++ if (depthSwapchainImage) {
++ if (!checkXrResult(xrReleaseSwapchainImage(m_depthSwapchains[i].handle, &releaseInfo)))
++ qWarning("Failed to release depth swapchain image");
++ }
++ }
++ }
++
++ #if QT_CONFIG(graphicsframecapture)
++ if (m_frameCapture)
++ m_frameCapture->endCaptureFrame();
++ #endif
++
++ layer.space = m_appSpace;
++ layer.viewCount = (uint32_t)m_projectionLayerViews.size();
++ layer.views = m_projectionLayerViews.data();
++ return true;
++ }
++
++ qCDebug(lcQuick3DXr, "xrLocateViews returned qualified success code: %s", to_string(res));
++ return false;
++ }
++
++ void QQuick3DXrManagerPrivate::doRender(const XrSwapchainSubImage &subImage,
++ const XrSwapchainImageBaseHeader *swapchainImage,
++ const XrSwapchainImageBaseHeader *depthSwapchainImage)
++ {
++ Q_Q(QQuick3DXrManager);
++
++ auto *quickWindow = q->m_quickWindow;
++ auto *renderControl = q->m_renderControl;
++
++ const int arraySize = m_multiviewRendering ? m_swapchains[0].arraySize : 1;
++ quickWindow->setRenderTarget(m_graphics->renderTarget(subImage,
++ swapchainImage,
++ m_colorSwapchainFormat,
++ m_samples,
++ arraySize,
++ depthSwapchainImage,
++ m_depthSwapchainFormat));
++
++ quickWindow->setGeometry(0,
++ 0,
++ subImage.imageRect.extent.width,
++ subImage.imageRect.extent.height);
++ quickWindow->contentItem()->setSize(QSizeF(subImage.imageRect.extent.width,
++ subImage.imageRect.extent.height));
++
++ renderControl->polishItems();
++ renderControl->beginFrame();
++ renderControl->sync();
++ renderControl->render();
++ renderControl->endFrame();
++
++ // With multiview this indicates that the frame with both eyes is ready from
++ // the 3D APIs perspective. Without multiview this is done - and so the
++ // signal is emitted - multiple times (twice) per "frame" (eye).
++ QRhiRenderTarget *rt = QQuickWindowPrivate::get(quickWindow)->activeCustomRhiRenderTarget();
++ if (rt->resourceType() == QRhiResource::TextureRenderTarget && static_cast<QRhiTextureRenderTarget *>(rt)->description().colorAttachmentAt(0)->texture())
++ emit q->frameReady();
++ }
++
++ void QQuick3DXrManagerPrivate::setupMetaQuestColorSpaces()
++ {
++ PFN_xrEnumerateColorSpacesFB pfnxrEnumerateColorSpacesFB = NULL;
++ OpenXRHelpers::resolveXrFunction(m_instance, "xrEnumerateColorSpacesFB", (PFN_xrVoidFunction*)(&pfnxrEnumerateColorSpacesFB));
++ if (!pfnxrEnumerateColorSpacesFB) // simulator
++ return;
++
++ uint32_t colorSpaceCountOutput = 0;
++ if (!checkXrResult(pfnxrEnumerateColorSpacesFB(m_session, 0, &colorSpaceCountOutput, nullptr))) {
++ qWarning("Failed to enumerate color spaces");
++ return;
++ }
++
++ XrColorSpaceFB* colorSpaces =
++ (XrColorSpaceFB*)malloc(colorSpaceCountOutput * sizeof(XrColorSpaceFB));
++
++ if (!checkXrResult(pfnxrEnumerateColorSpacesFB(m_session, colorSpaceCountOutput, &colorSpaceCountOutput, colorSpaces))) {
++ qWarning("Failed to enumerate color spaces");
++ return;
++ }
++
++ qCDebug(lcQuick3DXr, "Supported color spaces:");
++ for (uint32_t i = 0; i < colorSpaceCountOutput; i++) {
++ qCDebug(lcQuick3DXr,"%d:%d", i, colorSpaces[i]);
++ }
++
++ const XrColorSpaceFB requestColorSpace = XR_COLOR_SPACE_QUEST_FB;
++
++ PFN_xrSetColorSpaceFB pfnxrSetColorSpaceFB = NULL;
++ OpenXRHelpers::resolveXrFunction(m_instance, "xrSetColorSpaceFB", (PFN_xrVoidFunction*)(&pfnxrSetColorSpaceFB));
++
++ if (!checkXrResult(pfnxrSetColorSpaceFB(m_session, requestColorSpace)))
++ qWarning("Failed to set color space");
++
++ free(colorSpaces);
++ }
++
++ void QQuick3DXrManagerPrivate::setupMetaQuestRefreshRates()
++ {
++ PFN_xrEnumerateDisplayRefreshRatesFB pfnxrEnumerateDisplayRefreshRatesFB = NULL;
++ OpenXRHelpers::resolveXrFunction(m_instance, "xrEnumerateDisplayRefreshRatesFB", (PFN_xrVoidFunction*)(&pfnxrEnumerateDisplayRefreshRatesFB));
++ if (!pfnxrEnumerateDisplayRefreshRatesFB)
++ return;
++
++ uint32_t numSupportedDisplayRefreshRates;
++ QVector<float> supportedDisplayRefreshRates;
++
++ if (!checkXrResult(pfnxrEnumerateDisplayRefreshRatesFB(m_session, 0, &numSupportedDisplayRefreshRates, nullptr))) {
++ qWarning("Failed to enumerate display refresh rates");
++ return;
++ }
++
++ supportedDisplayRefreshRates.resize(numSupportedDisplayRefreshRates);
++
++ if (!checkXrResult(pfnxrEnumerateDisplayRefreshRatesFB(
++ m_session,
++ numSupportedDisplayRefreshRates,
++ &numSupportedDisplayRefreshRates,
++ supportedDisplayRefreshRates.data())))
++ {
++ qWarning("Failed to enumerate display refresh rates");
++ return;
++ }
++
++ qCDebug(lcQuick3DXr, "Supported Refresh Rates:");
++ for (uint32_t i = 0; i < numSupportedDisplayRefreshRates; i++) {
++ qCDebug(lcQuick3DXr, "%d:%f", i, supportedDisplayRefreshRates[i]);
++ }
++
++ PFN_xrGetDisplayRefreshRateFB pfnGetDisplayRefreshRate;
++ OpenXRHelpers::resolveXrFunction(m_instance, "xrGetDisplayRefreshRateFB", (PFN_xrVoidFunction*)(&pfnGetDisplayRefreshRate));
++
++ float currentDisplayRefreshRate = 0.0f;
++ if (!checkXrResult(pfnGetDisplayRefreshRate(m_session, &currentDisplayRefreshRate)))
++ qWarning("Failed to get display refresh rate");
++
++ qCDebug(lcQuick3DXr, "Current System Display Refresh Rate: %f", currentDisplayRefreshRate);
++
++ PFN_xrRequestDisplayRefreshRateFB pfnRequestDisplayRefreshRate;
++ OpenXRHelpers::resolveXrFunction(m_instance, "xrRequestDisplayRefreshRateFB", (PFN_xrVoidFunction*)(&pfnRequestDisplayRefreshRate));
++
++ // Test requesting the system default.
++ if (!checkXrResult(pfnRequestDisplayRefreshRate(m_session, 0.0f)))
++ qWarning("Failed to request display refresh rate");
++
++ qCDebug(lcQuick3DXr, "Requesting system default display refresh rate");
++ }
++
++ void QQuick3DXrManagerPrivate::setupMetaQuestFoveation()
++ {
++ PFN_xrCreateFoveationProfileFB pfnCreateFoveationProfileFB;
++ OpenXRHelpers::resolveXrFunction(m_instance, "xrCreateFoveationProfileFB", (PFN_xrVoidFunction*)(&pfnCreateFoveationProfileFB));
++ if (!pfnCreateFoveationProfileFB) // simulator
++ return;
++
++ PFN_xrDestroyFoveationProfileFB pfnDestroyFoveationProfileFB;
++ OpenXRHelpers::resolveXrFunction(m_instance, "xrDestroyFoveationProfileFB", (PFN_xrVoidFunction*)(&pfnDestroyFoveationProfileFB));
++
++ PFN_xrUpdateSwapchainFB pfnUpdateSwapchainFB;
++ OpenXRHelpers::resolveXrFunction(m_instance, "xrUpdateSwapchainFB", (PFN_xrVoidFunction*)(&pfnUpdateSwapchainFB));
++
++ for (const auto &swapchain : std::as_const(m_swapchains)) {
++ XrFoveationLevelProfileCreateInfoFB levelProfileCreateInfo = {};
++ levelProfileCreateInfo.type = XR_TYPE_FOVEATION_LEVEL_PROFILE_CREATE_INFO_FB;
++ levelProfileCreateInfo.level = m_foveationLevel;
++ levelProfileCreateInfo.verticalOffset = 0;
++ levelProfileCreateInfo.dynamic = XR_FOVEATION_DYNAMIC_DISABLED_FB;
++
++ XrFoveationProfileCreateInfoFB profileCreateInfo = {};
++ profileCreateInfo.type = XR_TYPE_FOVEATION_PROFILE_CREATE_INFO_FB;
++ profileCreateInfo.next = &levelProfileCreateInfo;
++
++ XrFoveationProfileFB foveationProfile;
++ pfnCreateFoveationProfileFB(m_session, &profileCreateInfo, &foveationProfile);
++
++ XrSwapchainStateFoveationFB foveationUpdateState = {};
++ memset(&foveationUpdateState, 0, sizeof(foveationUpdateState));
++ foveationUpdateState.type = XR_TYPE_SWAPCHAIN_STATE_FOVEATION_FB;
++ foveationUpdateState.profile = foveationProfile;
++
++ XrResult updateSwapchainFBResult = OpenXRHelpers::safeCall(pfnUpdateSwapchainFB, swapchain.handle, (const XrSwapchainStateBaseHeaderFB*)(&foveationUpdateState));
++ if (updateSwapchainFBResult == XR_ERROR_FUNCTION_UNSUPPORTED)
++ return;
++
++ XrResult destroyFoveationProfileFBResult = OpenXRHelpers::safeCall(pfnDestroyFoveationProfileFB, foveationProfile);
++ if (destroyFoveationProfileFBResult == XR_ERROR_FUNCTION_UNSUPPORTED)
++ return;
++
++ qCDebug(lcQuick3DXr, "Fixed foveated rendering requested with level %d", int(m_foveationLevel));
++ }
++ }
++
++ void QQuick3DXrManagerPrivate::createMetaQuestPassthrough()
++ {
++ // According to the validation layer 'flags' cannot be 0, thus we make sure
++ // this function is only ever called when we know passthrough is actually
++ // enabled by the app.
++ Q_ASSERT(m_fbPassthroughEnabled);
++
++ PFN_xrCreatePassthroughFB pfnXrCreatePassthroughFBX = nullptr;
++ OpenXRHelpers::resolveXrFunction(m_instance, "xrCreatePassthroughFB", (PFN_xrVoidFunction*)(&pfnXrCreatePassthroughFBX));
++
++ XrPassthroughCreateInfoFB passthroughCreateInfo{};
++ passthroughCreateInfo.type = XR_TYPE_PASSTHROUGH_CREATE_INFO_FB;
++ passthroughCreateInfo.flags = XR_PASSTHROUGH_IS_RUNNING_AT_CREATION_BIT_FB;
++
++ XrResult xrCreatePassthroughFBXResult = OpenXRHelpers::safeCall(pfnXrCreatePassthroughFBX, m_session, static_cast<const XrPassthroughCreateInfoFB*>(&passthroughCreateInfo), &m_passthroughFeature);
++ if (!checkXrResult(xrCreatePassthroughFBXResult))
++ qWarning("Failed to create passthrough object");
++ }
++
++ void QQuick3DXrManagerPrivate::destroyMetaQuestPassthrough()
++ {
++ PFN_xrDestroyPassthroughFB pfnXrDestroyPassthroughFBX = nullptr;
++ OpenXRHelpers::resolveXrFunction(m_instance, "xrDestroyPassthroughFB", (PFN_xrVoidFunction*)(&pfnXrDestroyPassthroughFBX));
++
++ XrResult xrDestroyPassthroughFBXResult = OpenXRHelpers::safeCall(pfnXrDestroyPassthroughFBX, m_passthroughFeature);
++ if (!checkXrResult(xrDestroyPassthroughFBXResult))
++ qWarning("Failed to destroy passthrough object");
++
++ m_passthroughFeature = XR_NULL_HANDLE;
++ }
++
++ void QQuick3DXrManagerPrivate::startMetaQuestPassthrough()
++ {
++ PFN_xrPassthroughStartFB pfnXrPassthroughStartFBX = nullptr;
++ OpenXRHelpers::resolveXrFunction(m_instance, "xrPassthroughStartFB", (PFN_xrVoidFunction*)(&pfnXrPassthroughStartFBX));
++
++ XrResult xrPassthroughStartFBXResult = OpenXRHelpers::safeCall(pfnXrPassthroughStartFBX, m_passthroughFeature);
++ if (!checkXrResult(xrPassthroughStartFBXResult))
++ qWarning("Failed to start passthrough");
++ }
++
++ void QQuick3DXrManagerPrivate::pauseMetaQuestPassthrough()
++ {
++ PFN_xrPassthroughPauseFB pfnXrPassthroughPauseFBX = nullptr;
++ OpenXRHelpers::resolveXrFunction(m_instance, "xrPassthroughPauseFB", (PFN_xrVoidFunction*)(&pfnXrPassthroughPauseFBX));
++
++ XrResult xrPassthroughPauseFBXResult = OpenXRHelpers::safeCall(pfnXrPassthroughPauseFBX, m_passthroughFeature);
++ if (!checkXrResult(xrPassthroughPauseFBXResult))
++ qWarning("Failed to pause passthrough");
++ }
++
++ void QQuick3DXrManagerPrivate::createMetaQuestPassthroughLayer()
++ {
++ PFN_xrCreatePassthroughLayerFB pfnXrCreatePassthroughLayerFBX = nullptr;
++ OpenXRHelpers::resolveXrFunction(m_instance, "xrCreatePassthroughLayerFB", (PFN_xrVoidFunction*)(&pfnXrCreatePassthroughLayerFBX));
++
++ XrPassthroughLayerCreateInfoFB layerCreateInfo{};
++ layerCreateInfo.type = XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB;
++ layerCreateInfo.passthrough = m_passthroughFeature;
++ layerCreateInfo.purpose = XR_PASSTHROUGH_LAYER_PURPOSE_RECONSTRUCTION_FB;
++ if (m_fbPassthroughEnabled)
++ layerCreateInfo.flags = XR_PASSTHROUGH_IS_RUNNING_AT_CREATION_BIT_FB;
++
++ XrResult xrCreatePassthroughLayerFBXResult = OpenXRHelpers::safeCall(pfnXrCreatePassthroughLayerFBX, m_session, static_cast<const XrPassthroughLayerCreateInfoFB*>(&layerCreateInfo), &m_passthroughLayer);
++ if (!checkXrResult(xrCreatePassthroughLayerFBXResult))
++ qWarning("Failed to create passthrough layer");
++ }
++
++ void QQuick3DXrManagerPrivate::destroyMetaQuestPassthroughLayer()
++ {
++ PFN_xrDestroyPassthroughLayerFB pfnXrDestroyPassthroughLayerFBX = nullptr;
++ OpenXRHelpers::resolveXrFunction(m_instance, "xrDestroyPassthroughLayerFB", (PFN_xrVoidFunction*)(&pfnXrDestroyPassthroughLayerFBX));
++
++ XrResult xrDestroyPassthroughLayerFBXResult = OpenXRHelpers::safeCall(pfnXrDestroyPassthroughLayerFBX, m_passthroughLayer);
++ if (!checkXrResult(xrDestroyPassthroughLayerFBXResult))
++ qWarning("Failed to destroy passthrough layer");
++
++ m_passthroughLayer = XR_NULL_HANDLE;
++ }
++
++ void QQuick3DXrManagerPrivate::pauseMetaQuestPassthroughLayer()
++ {
++ PFN_xrPassthroughLayerPauseFB pfnXrPassthroughLayerPauseFBX = nullptr;
++ OpenXRHelpers::resolveXrFunction(m_instance, "xrPassthroughLayerPauseFB", (PFN_xrVoidFunction*)(&pfnXrPassthroughLayerPauseFBX));
++
++ XrResult xrPassthroughLayerPauseFBXResult = OpenXRHelpers::safeCall(pfnXrPassthroughLayerPauseFBX, m_passthroughLayer);
++ if (!checkXrResult(xrPassthroughLayerPauseFBXResult))
++ qWarning("Failed to pause passthrough layer");
++ }
++
++ void QQuick3DXrManagerPrivate::resumeMetaQuestPassthroughLayer()
++ {
++ PFN_xrPassthroughLayerResumeFB pfnXrPassthroughLayerResumeFBX = nullptr;
++ OpenXRHelpers::resolveXrFunction(m_instance, "xrPassthroughLayerResumeFB", (PFN_xrVoidFunction*)(&pfnXrPassthroughLayerResumeFBX));
++
++ XrResult xrPassthroughLayerResumeFBXResult = OpenXRHelpers::safeCall(pfnXrPassthroughLayerResumeFBX, m_passthroughLayer);
++ if (!checkXrResult(xrPassthroughLayerResumeFBXResult))
++ qWarning("Failed to resume passthrough layer");
++ }
++
++ void QQuick3DXrManagerPrivate::checkXrExtensions(const char *layerName, int indent)
++ {
++ quint32 instanceExtensionCount;
++ if (!checkXrResult(xrEnumerateInstanceExtensionProperties(layerName, 0, &instanceExtensionCount, nullptr))) {
++ qWarning("Failed to enumerate instance extension properties");
++ return;
++ }
++
++ QVector<XrExtensionProperties> extensions(instanceExtensionCount);
++ for (XrExtensionProperties& extension : extensions) {
++ extension.type = XR_TYPE_EXTENSION_PROPERTIES;
++ extension.next = nullptr;
++ }
++
++ if (!checkXrResult(xrEnumerateInstanceExtensionProperties(layerName,
++ quint32(extensions.size()),
++ &instanceExtensionCount,
++ extensions.data())))
++ {
++ qWarning("Failed to enumerate instance extension properties");
++ }
++
++ const QByteArray indentStr(indent, ' ');
++ qCDebug(lcQuick3DXr, "%sAvailable Extensions: (%d)", indentStr.data(), instanceExtensionCount);
++ for (const XrExtensionProperties& extension : extensions) {
++ qCDebug(lcQuick3DXr, "%s Name=%s Version=%d.%d.%d",
++ indentStr.data(),
++ extension.extensionName,
++ XR_VERSION_MAJOR(extension.extensionVersion),
++ XR_VERSION_MINOR(extension.extensionVersion),
++ XR_VERSION_PATCH(extension.extensionVersion));
++ }
++ }
++
++ void QQuick3DXrManagerPrivate::checkXrLayers()
++ {
++ quint32 layerCount;
++ if (!checkXrResult(xrEnumerateApiLayerProperties(0, &layerCount, nullptr))) {
++ qWarning("Failed to enumerate API layer properties");
++ return;
++ }
++
++ QVector<XrApiLayerProperties> layers(layerCount);
++ for (XrApiLayerProperties& layer : layers) {
++ layer.type = XR_TYPE_API_LAYER_PROPERTIES;
++ layer.next = nullptr;
++ }
++
++ if (!checkXrResult(xrEnumerateApiLayerProperties(quint32(layers.size()), &layerCount, layers.data()))) {
++ qWarning("Failed to enumerate API layer properties");
++ return;
++ }
++
++ qCDebug(lcQuick3DXr, "Available Layers: (%d)", layerCount);
++ for (const XrApiLayerProperties& layer : layers) {
++ qCDebug(lcQuick3DXr, " Name=%s SpecVersion=%d.%d.%d LayerVersion=%d.%d.%d Description=%s",
++ layer.layerName,
++ XR_VERSION_MAJOR(layer.specVersion),
++ XR_VERSION_MINOR(layer.specVersion),
++ XR_VERSION_PATCH(layer.specVersion),
++ XR_VERSION_MAJOR(layer.layerVersion),
++ XR_VERSION_MINOR(layer.layerVersion),
++ XR_VERSION_PATCH(layer.layerVersion),
++ layer.description);
++ checkXrExtensions(layer.layerName, 4);
++ }
++ }
++
++ XrResult QQuick3DXrManagerPrivate::createXrInstance()
++ {
++ // Setup Info
++ XrApplicationInfo appInfo;
++ strcpy(appInfo.applicationName, QCoreApplication::applicationName().toUtf8());
++ appInfo.applicationVersion = 7;
++ strcpy(appInfo.engineName, QStringLiteral("Qt").toUtf8());
++ appInfo.engineVersion = 6;
++
++ // apiVersion must not be XR_CURRENT_API_VERSION. Consider what happens when
++ // building against 1.1 headers and running on an 1.0-only runtime. (it all
++ // breaks down) For now, use a known, fixed version: the last 1.0 release.
++ appInfo.apiVersion = XR_MAKE_VERSION(1, 0, 34);
++
++ // Query available API layers
++ uint32_t apiLayerCount = 0;
++ xrEnumerateApiLayerProperties(0, &apiLayerCount, nullptr);
++ QVector<XrApiLayerProperties> apiLayerProperties(apiLayerCount);
++ for (uint32_t i = 0; i < apiLayerCount; i++) {
++ apiLayerProperties[i].type = XR_TYPE_API_LAYER_PROPERTIES;
++ apiLayerProperties[i].next = nullptr;
++ }
++ xrEnumerateApiLayerProperties(apiLayerCount, &apiLayerCount, apiLayerProperties.data());
++
++ // Decide which API layers to enable
++ QVector<const char*> enabledApiLayers;
++
++ // Now it would be nice if we could use
++ // QQuickGraphicsConfiguration::isDebugLayerEnabled() but the quickWindow is
++ // nowhere yet, so just replicate the env.var. for now.
++ const bool wantsValidationLayer = qEnvironmentVariableIntValue("QSG_RHI_DEBUG_LAYER");
++ if (wantsValidationLayer) {
++ if (isApiLayerSupported("XR_APILAYER_LUNARG_core_validation", apiLayerProperties))
++ enabledApiLayers.append("XR_APILAYER_LUNARG_core_validation");
++ else
++ qCDebug(lcQuick3DXr, "OpenXR validation layer requested, but not available");
++ }
++
++ qCDebug(lcQuick3DXr) << "Requesting to enable XR API layers:" << enabledApiLayers;
++
++ m_enabledApiLayers.clear();
++ for (const char *layer : std::as_const(enabledApiLayers))
++ m_enabledApiLayers.append(QString::fromLatin1(layer));
++
++ // Load extensions
++ uint32_t extensionCount = 0;
++ xrEnumerateInstanceExtensionProperties(nullptr, 0, &extensionCount, nullptr);
++ QVector<XrExtensionProperties> extensionProperties(extensionCount);
++ for (uint32_t i = 0; i < extensionCount; i++) {
++ // we usually have to fill in the type (for validation) and set
++ // next to NULL (or a pointer to an extension specific struct)
++ extensionProperties[i].type = XR_TYPE_EXTENSION_PROPERTIES;
++ extensionProperties[i].next = nullptr;
++ }
++ xrEnumerateInstanceExtensionProperties(nullptr, extensionCount, &extensionCount, extensionProperties.data());
++
++ QVector<const char*> enabledExtensions;
++ if (!m_graphics->initialize(extensionProperties)) {
++ qWarning("Failed to initialize OpenXR graphics module");
++ return XR_ERROR_GRAPHICS_DEVICE_INVALID;
++ }
++ enabledExtensions.append(m_graphics->getRequiredExtensions());
++
++ if (isExtensionSupported("XR_EXT_debug_utils", extensionProperties))
++ enabledExtensions.append("XR_EXT_debug_utils");
++
++ if (isExtensionSupported(XR_EXT_PERFORMANCE_SETTINGS_EXTENSION_NAME, extensionProperties))
++ enabledExtensions.append(XR_EXT_PERFORMANCE_SETTINGS_EXTENSION_NAME);
++
++ m_handtrackingExtensionSupported = isExtensionSupported(XR_EXT_HAND_TRACKING_EXTENSION_NAME, extensionProperties);
++ if (m_handtrackingExtensionSupported)
++ enabledExtensions.append(XR_EXT_HAND_TRACKING_EXTENSION_NAME);
++
++ m_compositionLayerDepthSupported = isExtensionSupported(XR_KHR_COMPOSITION_LAYER_DEPTH_EXTENSION_NAME, extensionProperties);
++ if (m_compositionLayerDepthSupported) {
++ // The extension is enabled, whenever supported; however, if we actually
++ // submit depth in xrEndFrame(), is a different question.
++ enabledExtensions.append(XR_KHR_COMPOSITION_LAYER_DEPTH_EXTENSION_NAME);
++ m_submitLayerDepth = qEnvironmentVariableIntValue("QT_QUICK3D_XR_SUBMIT_DEPTH");
++ if (m_submitLayerDepth)
++ qCDebug(lcQuick3DXr, "submitLayerDepth defaults to true due to env.var.");
++ } else {
++ m_submitLayerDepth = false;
++ }
++
++ // Oculus Quest Specific Extensions
++
++ m_handtrackingAimExtensionSupported = isExtensionSupported(XR_FB_HAND_TRACKING_AIM_EXTENSION_NAME, extensionProperties);
++ if (m_handtrackingAimExtensionSupported)
++ enabledExtensions.append(XR_FB_HAND_TRACKING_AIM_EXTENSION_NAME);
++
++ if (isExtensionSupported(XR_MSFT_HAND_INTERACTION_EXTENSION_NAME, extensionProperties))
++ enabledExtensions.append(XR_MSFT_HAND_INTERACTION_EXTENSION_NAME);
++
++ if (isExtensionSupported(XR_FB_HAND_TRACKING_MESH_EXTENSION_NAME, extensionProperties))
++ enabledExtensions.append(XR_FB_HAND_TRACKING_MESH_EXTENSION_NAME);
++
++ // Passthrough extensions (require manifest feature to work)
++ // <uses-feature android:name="com.oculus.feature.PASSTHROUGH" android:required="true" />
++ uint32_t passthroughSpecVersion = 0;
++ if (isExtensionSupported(XR_FB_PASSTHROUGH_EXTENSION_NAME, extensionProperties, &passthroughSpecVersion)) {
++ qCDebug(lcQuick3DXr, "Passthrough extension is supported, spec version %u", passthroughSpecVersion);
++ enabledExtensions.append(XR_FB_PASSTHROUGH_EXTENSION_NAME);
++ } else {
++ qCDebug(lcQuick3DXr, "Passthrough extension is NOT supported");
++ }
++
++ if (isExtensionSupported(XR_FB_TRIANGLE_MESH_EXTENSION_NAME, extensionProperties))
++ enabledExtensions.append(XR_FB_TRIANGLE_MESH_EXTENSION_NAME);
++
++ m_displayRefreshRateExtensionSupported = isExtensionSupported(XR_FB_DISPLAY_REFRESH_RATE_EXTENSION_NAME, extensionProperties);
++ if (m_displayRefreshRateExtensionSupported)
++ enabledExtensions.append(XR_FB_DISPLAY_REFRESH_RATE_EXTENSION_NAME);
++
++ m_colorspaceExtensionSupported = isExtensionSupported(XR_FB_COLOR_SPACE_EXTENSION_NAME, extensionProperties);
++ if (m_colorspaceExtensionSupported)
++ enabledExtensions.append(XR_FB_COLOR_SPACE_EXTENSION_NAME);
++
++ if (isExtensionSupported(XR_FB_SWAPCHAIN_UPDATE_STATE_EXTENSION_NAME, extensionProperties))
++ enabledExtensions.append(XR_FB_SWAPCHAIN_UPDATE_STATE_EXTENSION_NAME);
++
++ m_foveationExtensionSupported = isExtensionSupported(XR_FB_FOVEATION_EXTENSION_NAME, extensionProperties);
++ if (m_foveationExtensionSupported)
++ enabledExtensions.append(XR_FB_FOVEATION_EXTENSION_NAME);
++
++ if (isExtensionSupported(XR_FB_FOVEATION_CONFIGURATION_EXTENSION_NAME, extensionProperties))
++ enabledExtensions.append(XR_FB_FOVEATION_CONFIGURATION_EXTENSION_NAME);
++
++ if (m_spaceExtension) {
++ const auto requiredExtensions = m_spaceExtension->requiredExtensions();
++ bool isSupported = true;
++ for (const auto extension : requiredExtensions) {
++ isSupported = isExtensionSupported(extension, extensionProperties) && isSupported;
++ if (!isSupported)
++ break;
++ }
++ m_spaceExtensionSupported = isSupported;
++ if (isSupported)
++ enabledExtensions.append(requiredExtensions);
++ }
++
++ if (isExtensionSupported(XR_EXTX_OVERLAY_EXTENSION_NAME, extensionProperties)) {
++ m_overlayExtensionSupported = true;
++ enabledExtensions.append(XR_EXTX_OVERLAY_EXTENSION_NAME);
++ }
++
++ #ifdef Q_OS_ANDROID
++ if (isExtensionSupported(XR_KHR_ANDROID_THREAD_SETTINGS_EXTENSION_NAME, extensionProperties))
++ enabledExtensions.append(XR_KHR_ANDROID_THREAD_SETTINGS_EXTENSION_NAME);
++
++ m_androidCreateInstanceExtensionSupported = isExtensionSupported(XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME, extensionProperties);
++ if (m_androidCreateInstanceExtensionSupported)
++ enabledExtensions.append(XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME);
++
++ auto graphicsAPI = QQuickWindow::graphicsApi();
++ if (graphicsAPI == QSGRendererInterface::Vulkan) {
++ if (isExtensionSupported(XR_FB_SWAPCHAIN_UPDATE_STATE_VULKAN_EXTENSION_NAME, extensionProperties))
++ enabledExtensions.append(XR_FB_SWAPCHAIN_UPDATE_STATE_VULKAN_EXTENSION_NAME);
++ } else if (graphicsAPI == QSGRendererInterface::OpenGL) {
++ if (isExtensionSupported(XR_FB_SWAPCHAIN_UPDATE_STATE_OPENGL_ES_EXTENSION_NAME, extensionProperties))
++ enabledExtensions.append(XR_FB_SWAPCHAIN_UPDATE_STATE_OPENGL_ES_EXTENSION_NAME);
++ }
++ #endif
++
++ qCDebug(lcQuick3DXr) << "Requesting to enable XR extensions:" << enabledExtensions;
++
++ m_enabledExtensions.clear();
++ for (const char *extension : std::as_const(enabledExtensions))
++ m_enabledExtensions.append(QString::fromLatin1(extension));
++
++ // Create Instance
++ XrInstanceCreateInfo xrInstanceInfo{};
++ xrInstanceInfo.type = XR_TYPE_INSTANCE_CREATE_INFO;
++
++ #ifdef Q_OS_ANDROID
++ XrInstanceCreateInfoAndroidKHR xrInstanceCreateInfoAndroid {};
++ if (m_androidCreateInstanceExtensionSupported) {
++ xrInstanceCreateInfoAndroid.type = XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR;
++ xrInstanceCreateInfoAndroid.applicationVM = m_javaVM;
++ xrInstanceCreateInfoAndroid.applicationActivity = m_androidActivity.object();
++
++ xrInstanceInfo.next = &xrInstanceCreateInfoAndroid;
++ }
++ #endif
++
++
++ xrInstanceInfo.createFlags = 0;
++ xrInstanceInfo.applicationInfo = appInfo;
++ xrInstanceInfo.enabledApiLayerCount = enabledApiLayers.count();
++ xrInstanceInfo.enabledApiLayerNames = enabledApiLayers.constData();
++ xrInstanceInfo.enabledExtensionCount = enabledExtensions.count();
++ xrInstanceInfo.enabledExtensionNames = enabledExtensions.constData();
++
++ return xrCreateInstance(&xrInstanceInfo, &m_instance);
++ }
++
++ void QQuick3DXrManagerPrivate::checkXrInstance()
++ {
++ Q_ASSERT(m_instance != XR_NULL_HANDLE);
++ XrInstanceProperties instanceProperties{};
++ instanceProperties.type = XR_TYPE_INSTANCE_PROPERTIES;
++ if (!checkXrResult(xrGetInstanceProperties(m_instance, &instanceProperties))) {
++ qWarning("Failed to get instance properties");
++ return;
++ }
++
++ m_runtimeName = QString::fromUtf8(instanceProperties.runtimeName);
++ m_runtimeVersion = QVersionNumber(XR_VERSION_MAJOR(instanceProperties.runtimeVersion),
++ XR_VERSION_MINOR(instanceProperties.runtimeVersion),
++ XR_VERSION_PATCH(instanceProperties.runtimeVersion));
++
++ qCDebug(lcQuick3DXr, "Instance RuntimeName=%s RuntimeVersion=%d.%d.%d",
++ qPrintable(m_runtimeName),
++ m_runtimeVersion.majorVersion(),
++ m_runtimeVersion.minorVersion(),
++ m_runtimeVersion.microVersion());
++ }
++
++ void QQuick3DXrManagerPrivate::setupDebugMessenger()
++ {
++ if (!m_enabledExtensions.contains(QString::fromUtf8("XR_EXT_debug_utils"))) {
++ qCDebug(lcQuick3DXr, "No debug utils extension, message redirection not set up");
++ return;
++ }
++
++ #ifdef XR_EXT_debug_utils
++ PFN_xrCreateDebugUtilsMessengerEXT xrCreateDebugUtilsMessengerEXT = nullptr;
++ OpenXRHelpers::resolveXrFunction(m_instance, "xrCreateDebugUtilsMessengerEXT", reinterpret_cast<PFN_xrVoidFunction *>(&xrCreateDebugUtilsMessengerEXT));
++ if (!xrCreateDebugUtilsMessengerEXT)
++ return;
++
++ OpenXRHelpers::resolveXrFunction(m_instance, "xrDestroyDebugUtilsMessengerEXT", reinterpret_cast<PFN_xrVoidFunction *>(&m_xrDestroyDebugUtilsMessengerEXT));
++
++ XrDebugUtilsMessengerCreateInfoEXT messengerInfo = {};
++ messengerInfo.type = XR_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
++ messengerInfo.messageSeverities = XR_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
++ | XR_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
++ messengerInfo.messageTypes = XR_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT
++ | XR_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT
++ | XR_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT
++ | XR_DEBUG_UTILS_MESSAGE_TYPE_CONFORMANCE_BIT_EXT;
++ messengerInfo.userCallback = defaultDebugCallbackFunc;
++ messengerInfo.userData = this;
++
++ XrResult err = xrCreateDebugUtilsMessengerEXT(m_instance, &messengerInfo, &m_debugMessenger);
++ if (!checkXrResult(err))
++ qWarning("Quick 3D XR: Failed to create debug report callback, OpenXR messages will not get redirected (%d)", err);
++ #endif // XR_EXT_debug_utils
++ }
++
++ XrResult QQuick3DXrManagerPrivate::initializeSystem()
++ {
++ Q_ASSERT(m_instance != XR_NULL_HANDLE);
++ Q_ASSERT(m_systemId == XR_NULL_SYSTEM_ID);
++
++ XrSystemGetInfo hmdInfo{};
++ hmdInfo.type = XR_TYPE_SYSTEM_GET_INFO;
++ hmdInfo.next = nullptr;
++ hmdInfo.formFactor = m_formFactor;
++
++ const XrResult result = xrGetSystem(m_instance, &hmdInfo, &m_systemId);
++ const bool success = checkXrResult(result);
++
++ if (!success)
++ return result;
++
++ // Check View Configuration
++ checkViewConfiguration();
++
++ return result;
++ }
++
++ void QQuick3DXrManagerPrivate::checkViewConfiguration()
++ {
++ quint32 viewConfigTypeCount;
++ if (!checkXrResult(xrEnumerateViewConfigurations(m_instance,
++ m_systemId,
++ 0,
++ &viewConfigTypeCount,
++ nullptr)))
++ {
++ qWarning("Failed to enumerate view configurations");
++ return;
++ }
++ QVector<XrViewConfigurationType> viewConfigTypes(viewConfigTypeCount);
++ if (!checkXrResult(xrEnumerateViewConfigurations(m_instance,
++ m_systemId,
++ viewConfigTypeCount,
++ &viewConfigTypeCount,
++ viewConfigTypes.data())))
++ {
++ qWarning("Failed to enumerate view configurations");
++ return;
++ }
++
++ qCDebug(lcQuick3DXr, "Available View Configuration Types: (%d)", viewConfigTypeCount);
++ for (XrViewConfigurationType viewConfigType : viewConfigTypes) {
++ qCDebug(lcQuick3DXr, " View Configuration Type: %s %s", to_string(viewConfigType), viewConfigType == m_viewConfigType ? "(Selected)" : "");
++ XrViewConfigurationProperties viewConfigProperties{};
++ viewConfigProperties.type = XR_TYPE_VIEW_CONFIGURATION_PROPERTIES;
++ if (!checkXrResult(xrGetViewConfigurationProperties(m_instance,
++ m_systemId,
++ viewConfigType,
++ &viewConfigProperties)))
++ {
++ qWarning("Failed to get view configuration properties");
++ return;
++ }
++
++ qCDebug(lcQuick3DXr, " View configuration FovMutable=%s", viewConfigProperties.fovMutable == XR_TRUE ? "True" : "False");
++
++ uint32_t viewCount;
++ if (!checkXrResult(xrEnumerateViewConfigurationViews(m_instance,
++ m_systemId,
++ viewConfigType,
++ 0,
++ &viewCount,
++ nullptr)))
++ {
++ qWarning("Failed to enumerate configuration views");
++ return;
++ }
++
++ if (viewCount > 0) {
++ QVector<XrViewConfigurationView> views(viewCount, {XR_TYPE_VIEW_CONFIGURATION_VIEW, nullptr, 0, 0, 0, 0, 0, 0});
++ if (!checkXrResult(xrEnumerateViewConfigurationViews(m_instance,
++ m_systemId,
++ viewConfigType,
++ viewCount,
++ &viewCount,
++ views.data())))
++ {
++ qWarning("Failed to enumerate configuration views");
++ return;
++ }
++
++ for (int i = 0; i < views.size(); ++i) {
++ const XrViewConfigurationView& view = views[i];
++ qCDebug(lcQuick3DXr, " View [%d]: Recommended Width=%d Height=%d SampleCount=%d",
++ i,
++ view.recommendedImageRectWidth,
++ view.recommendedImageRectHeight,
++ view.recommendedSwapchainSampleCount);
++ qCDebug(lcQuick3DXr, " View [%d]: Maximum Width=%d Height=%d SampleCount=%d",
++ i,
++ view.maxImageRectWidth,
++ view.maxImageRectHeight,
++ view.maxSwapchainSampleCount);
++ }
++ } else {
++ qCDebug(lcQuick3DXr, "Empty view configuration type");
++ }
++ checkEnvironmentBlendMode(viewConfigType);
++ }
++
++ // Select view configuration type based on what the runtime supports.
++ if (!viewConfigTypes.contains(XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO)
++ && viewConfigTypes.contains(XR_VIEW_CONFIGURATION_TYPE_PRIMARY_MONO)) {
++ m_viewConfigType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_MONO;
++ m_multiviewRendering = false;
++ qCDebug(lcQuick3DXr, "Mono view mode selected (only mode supported by runtime)");
++ }
++ }
++
++ bool QQuick3DXrManagerPrivate::checkXrResult(const XrResult &result)
++ {
++ return OpenXRHelpers::checkXrResult(result, m_instance);
++ }
++
++ void QQuick3DXrManagerPrivate::checkEnvironmentBlendMode(XrViewConfigurationType type)
++ {
++ uint32_t count;
++ if (!checkXrResult(xrEnumerateEnvironmentBlendModes(m_instance,
++ m_systemId,
++ type,
++ 0,
++ &count,
++ nullptr)))
++ {
++ qWarning("Failed to enumerate blend modes");
++ return;
++ }
++
++ qCDebug(lcQuick3DXr, "Available Environment Blend Mode count : (%d)", count);
++
++ QVector<XrEnvironmentBlendMode> blendModes(count);
++ if (!checkXrResult(xrEnumerateEnvironmentBlendModes(m_instance,
++ m_systemId,
++ type,
++ count,
++ &count,
++ blendModes.data())))
++ {
++ qWarning("Failed to enumerate blend modes");
++ return;
++ }
++
++ m_blendModes = blendModes;
++
++ // Set initial blend mode, prefer opaque (passthrough disabled)
++ if (!setTransparentBlendMode(false))
++ setTransparentBlendMode(true);
++ }
++
++ QT_END_NAMESPACE
++
++ #include "qquick3dxrmanager_openxr.moc"