Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquick3dxrmanager_visionos.mm
Go to the documentation of this file.
1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
9
10#include "../qquick3dxrinputmanager_p.h"
11#include "../qquick3dxranimationdriver_p.h"
12
13#include <QtQuick3D/private/qquick3dviewport_p.h>
14#include <QtQuick3D/private/qquick3dnode_p_p.h>
15
16#include <QtQuick3DUtils/private/qssgassert_p.h>
17
18#include <QQuickGraphicsDevice>
19#include <rhi/qrhi.h>
20
21#include <CompositorServices/CompositorServices.h>
22#include <QtGui/qguiapplication_platform.h>
23
24#include <QtCore/qoperatingsystemversion.h>
25#include <QtCore/qloggingcategory.h>
26
27#include <TargetConditionals.h>
28
30
31Q_DECLARE_LOGGING_CATEGORY(lcQuick3DXr);
32
33static const char s_renderThreadName[] = "QQuick3DXrRenderThread";
34
35// NOTE: This only affects visionOS
37{
38 static bool foveationDisabled = qEnvironmentVariableIntValue("QT_QUICK3D_XR_DISABLE_FOVEATION") != 0;
39 return foveationDisabled;
40}
41
43{
45public:
46 CompositorLayer() = default;
48 {
49 m_xrManager = nullptr;
50 [m_layerRenderer release];
51 m_layerRenderer = nullptr;
52 m_worldTrackingProvider = nullptr;
53 m_arSession = nullptr;
54 m_initialized = false;
55 }
56
65
66 static constexpr QEvent::Type asQEvent(CompositorLayer::Event event) { return static_cast<QEvent::Type>(event); }
67
68 static bool supportsLayoutType(cp_layer_renderer_capabilities_t capabilities, cp_layer_renderer_layout layout)
69 {
70 const size_t layoutCount = cp_layer_renderer_capabilities_supported_layouts_count(capabilities, cp_supported_layouts_options_none);
71 bool found = false;
72 for (size_t i = 0; i < layoutCount && !found; ++i)
73 found = (layout == cp_layer_renderer_capabilities_supported_layout(capabilities, cp_supported_layouts_options_none, i));
74
75 return found;
76 }
77
78 void configure(cp_layer_renderer_capabilities_t capabilities, cp_layer_renderer_configuration_t configuration) const override
79 {
80 QMutexLocker locker(&m_compositorLayerMtx);
81
83
84 // If multiview isn't disabled we need to check if the target supports it.
85 // NOTE: We're only doing layered or dedicated rendering, so shared is not supported (untested).
86 m_multiviewSupported = supportsLayoutType(capabilities, cp_layer_renderer_layout_layered);
87 m_multiviewEnabled = m_multiviewEnabled && m_multiviewSupported;
88
89 // NOTE: If foviation is supported we enable it.
90 m_foveationSupported = cp_layer_renderer_capabilities_supports_foveation(capabilities);
91 // ... but we can disable it with an environment variable.
92 m_foveationEnabled = m_foveationSupported && !qssgDisableFoveation();
93
94 // NOTE: We're only doing layered or dedicated rendering, so shared is not supported, even though we technically
95 // could support it. Since the only target we currently have (visionOS on the Vision Pro) support all these
96 // modes anyways we only need to care about these two (dedicated is required for the emulator).
97 cp_layer_renderer_layout textureLayout = m_multiviewEnabled ? cp_layer_renderer_layout_layered
98 : cp_layer_renderer_layout_dedicated;
99
100 cp_layer_renderer_configuration_set_layout(configuration, textureLayout);
101 cp_layer_renderer_configuration_set_foveation_enabled(configuration, m_foveationEnabled);
102 cp_layer_renderer_configuration_set_color_format(configuration, MTLPixelFormatRGBA16Float);
103 simd_float2 depthRange = cp_layer_renderer_configuration_get_default_depth_range(configuration);
104 // NOTE: The depth range is inverted for VisionOS (x = far, y = near)
105 m_depthRange[0] = depthRange.y;
106 m_depthRange[1] = depthRange.x;
107
108 qCDebug(lcQuick3DXr) << "Configuring with the following settings:"
109 << "\n\tMultiview supported: " << m_multiviewSupported
110 << "\n\tMultiview enabled: " << m_multiviewEnabled
111 << "\n\tFoveation supported: " << m_foveationSupported
112 << "\n\tFoveation enabled: " << m_foveationEnabled
113 << "\n\tDepth Range: " << m_depthRange[0] << " to: " << m_depthRange[1];
114 }
115
116 void render(cp_layer_renderer_t renderer) override
117 {
118 if (m_layerRenderer != renderer) {
119 m_layerRenderer = renderer;
120 emit layerRendererChanged();
121 }
122
123 if (m_layerRenderer) {
124 QMutexLocker locker(&m_mutex);
125 // NOTE: The layer renderer is our handle to the compositor layer, so we need to ensure that
126 // we need to ensure that it stays alive long enough for us to know if we should tear down
127 // the compositor layer or not.
128 [m_layerRenderer retain];
129 checkRenderState();
130 emit layerRendererReady();
131 }
132 }
133
134 // Listen for spatial events (these are native gestures like pinch click/drag coming from SwiftUI)
135 void handleSpatialEvents(const QJsonObject &events) override
136 {
137 if (m_xrManager)
138 m_xrManager->processSpatialEvents(events);
139 }
140
141 bool isInitialized() const { return m_initialized; }
142
143 bool isMultiviewSupported() const { return m_multiviewSupported; }
144 bool isMultiviewEnabled() const { return m_multiviewEnabled; }
145 bool isFoveationSupported() const { return m_foveationSupported; }
146 bool isFoveationEnabled() const { return m_foveationEnabled; }
147
149 {
150 return m_layerRenderer;
151 }
152
153 void getDefaultDepthRange(float &near, float &far) const
154 {
155 near = m_depthRange[0];
156 far = m_depthRange[1];
157 }
158
159 // Must be called from the GUI thread
161 {
162 Q_ASSERT(qApp->thread() == QThread::currentThread());
163 QSSG_ASSERT(!m_initialized, return);
164
165 m_xrManager = xrManager;
166 runWorldTrackingARSession();
167 m_initialized = true;
168 }
169
170 // Called from the gui thread while holding the render lock.
171 // For now we only let the thread know that it should stop rendering,
172 // as we're going to tear down the compositor layer on the render thread.
173 void teardown() { m_teardown = true; }
174
176 {
177 Q_ASSERT(qApp->thread() == QThread::currentThread());
178
179 QMutexLocker locker(&m_arSessionMtx);
180
181 if (m_arSession) {
182 qCDebug(lcQuick3DXr, "Stopping AR session");
183 // NOTE: We're stopping the AR session, so we need to ensure that the AR session
184 // doesn't get posted at some later point, so we change the dispatch queue to
185 // use the main queue (which is the GUI thread) before calling stop.
186 ar_session_set_data_provider_state_change_handler_f(m_arSession, dispatch_get_main_queue(), this, &onArStateChanged);
187 ar_session_stop(m_arSession);
188 m_arSession = nullptr;
190 }
191 }
192
193 QMutex &renderLock() { return m_mutex; }
195 {
196 return m_waitCondition.wait(&m_mutex);
197 }
198
199 Q_INVOKABLE static void destroy(QQuick3DXrManagerPrivate *mngr, QQuickWindow *window, CompositorLayer *compositorLayer)
200 {
201 QSSG_ASSERT(mngr != nullptr, return);
202
203 qCDebug(lcQuick3DXr) << "Destroying compositor layer";
204
205 if (auto *visionOSApplicaton = qGuiApp->nativeInterface<QNativeInterface::QVisionOSApplication>())
206 visionOSApplicaton->setImmersiveSpaceCompositorLayer(nullptr);
207
208 // NOTE: This is a bit of a hack, but we need to cleanup the nodes
209 // on the render thread while the GUI thread is blocked (as documented.)
210 // Since we cannot destruct the window on the render thread, and we cannot
211 // cleanup all nodes on the GUI thread due to dependencies on the render
212 // thread and resources created on it. Instead we just invalidate the render
213 // control and cleanup the nodes on the render thread. This is not ideal,
214 // but it's what we have for now...
215 if (window) { // If we never got past the setup we don't have a window.
216 auto *d = QQuickWindowPrivate::get(window);
217 d->cleanupNodesOnShutdown();
218 mngr->releaseResources();
219 if (auto *rc = d->renderControl)
220 rc->invalidate();
221 }
222
223 // We're holding onto the lock, so defer the delete until we're done here.
224 // All reasources should be released at this point anyways.
225 compositorLayer->deleteLater();
226 }
227
233
234protected:
235 bool event(QEvent *event) override
236 {
237 {
238 // NOTE: Intentionally scoped to avoid locking the mutex for events we're not handling
239 // and that has side-effects (e.g. cleanup due to a deferred delete).
240 QMutexLocker locker(&m_mutex);
241
242 if (m_teardown) {
243 qCDebug(lcQuick3DXr) << "Teardown in progress, skipping event handling";
244 // If we're tearing down we're about to be destroyed, so we don't want to
245 // ensure that the mutex is unlocked, as we're going to be destroyed anyways.
246 if (event->type() == QEvent::DeferredDelete)
247 locker.unlock();
248 return QObject::event(event);
249 }
250
251 switch (static_cast<Event>(event->type())) {
252 case Event::Render:
253 {
254 const bool success = renderFrame(locker);
255 // Check if we successfully rendered the frame, if not the GUI thread
256 // is likely waiting for the render thread to complete rendering, so we
257 // need to wake it up.
258 if (!success) {
259 qCDebug(lcQuick3DXr) << "Waking up the GUI thread, rendering returned early...";
260 m_xrManager->m_syncDone = false;
261 m_waitCondition.wakeOne();
262 }
263 }
264 return true;
265 case Event::Stop:
266 cleanup();
267 return true;
268 case Event::Pause:
269 pause();
270 return true;
271 case Event::Pulse:
272 checkRenderState();
273 return true;
274 }
275 }
276
277 return QObject::event(event);
278 }
279
280private:
281 friend bool QQuick3DXrManagerPrivate::renderFrameImpl(QMutexLocker<QMutex> &locker, QWaitCondition &waitCondition);
283
284 static void onArStateChanged(void *context,
285 ar_data_providers_t data_providers,
286 ar_data_provider_state_t new_state,
287 ar_error_t error,
288 ar_data_provider_t failed_data_provider)
289 {
290 Q_UNUSED(context);
291 Q_UNUSED(data_providers);
292 Q_UNUSED(error);
293 Q_UNUSED(failed_data_provider);
294
295 auto *that = reinterpret_cast<CompositorLayer *>(context);
296
297 QMutexLocker lock(&that->m_arSessionMtx);
298
299 // If the ar session is null we're called after we tore down the session.
300 QSSG_ASSERT_X(that->m_arSession != nullptr, "AR session is not running, skipping state change!", return);
301
302 const auto oldState = that->m_arTrackingState;
303 switch (new_state) {
304 case ar_data_provider_state_initialized:
306 break;
307 case ar_data_provider_state_running:
309 break;
310 case ar_data_provider_state_paused:
312 break;
313 case ar_data_provider_state_stopped:
315 break;
316 }
317
318
319 if (oldState != that->m_arTrackingState) {
320 QMutexLocker renderLocker(&that->m_mutex);
321 emit that->arStateChanged(that->m_arTrackingState);
322 }
323 }
324
325 enum class RenderStateAction
326 {
327 CheckOnly,
328 WaitUntilRunning,
329 };
330
331 template<RenderStateAction Action = RenderStateAction::WaitUntilRunning>
332 void checkRenderState()
333 {
334 QSSG_ASSERT(m_layerRenderer != nullptr, return);
335
336 const auto oldState = m_renderState;
337 switch (cp_layer_renderer_get_state(m_layerRenderer)) {
338 case cp_layer_renderer_state_paused:
339 m_renderState = QQuick3DXrManagerPrivate::RenderState::Paused;
340 break;
341 case cp_layer_renderer_state_running:
342 m_renderState = QQuick3DXrManagerPrivate::RenderState::Running;
343 break;
344 case cp_layer_renderer_state_invalidated:
345 m_renderState = QQuick3DXrManagerPrivate::RenderState::Invalidated;
346 break;
347 }
348
349 if (oldState != m_renderState)
350 emit renderStateChanged(m_renderState);
351
352 if (m_renderState == QQuick3DXrManagerPrivate::RenderState::Paused) {
353 if constexpr (Action == RenderStateAction::WaitUntilRunning) {
354 qCDebug(lcQuick3DXr) << "Waiting for rendering to resume...";
355 cp_layer_renderer_wait_until_running(m_layerRenderer);
356 QCoreApplication::postEvent(this, new QEvent(asQEvent(CompositorLayer::Event::Pulse)));
357 }
358 }
359 }
360
361 ar_device_anchor_t createPoseForTiming(cp_frame_timing_t timing)
362 {
363 QSSG_ASSERT(m_worldTrackingProvider != nullptr, return nullptr);
364
365 ar_device_anchor_t outAnchor = ar_device_anchor_create();
366 cp_time_t presentationTime = cp_frame_timing_get_presentation_time(timing);
367 CFTimeInterval queryTime = cp_time_to_cf_time_interval(presentationTime);
368 ar_device_anchor_query_status_t status = ar_world_tracking_provider_query_device_anchor_at_timestamp(m_worldTrackingProvider, queryTime, outAnchor);
369 if (status != ar_device_anchor_query_status_success) {
370 NSLog(@"Failed to get estimated pose from world tracking provider for presentation timestamp %0.3f", queryTime);
371 }
372 return outAnchor;
373 }
374
375 void runWorldTrackingARSession()
376 {
377 QMutexLocker locker(&m_arSessionMtx);
378
379 ar_world_tracking_configuration_t worldTrackingConfiguration = ar_world_tracking_configuration_create();
380 m_worldTrackingProvider = ar_world_tracking_provider_create(worldTrackingConfiguration);
381
382 ar_data_providers_t dataProviders = ar_data_providers_create();
383 ar_data_providers_add_data_provider(dataProviders, m_worldTrackingProvider);
384
386 QQuick3DXrAnchorManager *anchorManager = QQuick3DXrAnchorManager::instance();
387
388 // 1. prepare
389 QQuick3DXrInputManagerPrivate *pim = nullptr;
390 if (QSSG_GUARD_X(inputManager != nullptr, "No InputManager available!")) {
391 pim = QQuick3DXrInputManagerPrivate::get(inputManager);
392 if (QSSG_GUARD(pim != nullptr))
393 pim->prepareHandtracking(dataProviders);
394 }
395
396 if (QSSG_GUARD_X(anchorManager != nullptr, "No AnchorManager available!"))
397 QQuick3DXrManagerPrivate::prepareAnchorManager(anchorManager, dataProviders);
398
399 m_arSession = ar_session_create();
400 ar_session_set_data_provider_state_change_handler_f(m_arSession, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), this, &onArStateChanged);
401 ar_session_run(m_arSession, dataProviders);
402
403 // 2. initialize
404 QQuick3DXrManagerPrivate::initInputManager(inputManager);
405 QQuick3DXrManagerPrivate::initAnchorManager(anchorManager);
406 }
407
408 void pause()
409 {
410 qCDebug(lcQuick3DXr, "Pausing rendering");
411 }
412
413 void cleanup()
414 {
415 qCDebug(lcQuick3DXr, "Cleaning up");
416 if (m_xrManager)
417 m_xrManager->releaseResources();
418 }
419
420 [[nodiscard]] bool renderFrame(QMutexLocker<QMutex> &locker)
421 {
422 Q_ASSERT(locker.isLocked());
423 checkRenderState();
424
425 if (m_renderState != QQuick3DXrManagerPrivate::RenderState::Running) {
426 qCDebug(lcQuick3DXr, "Rendering is not running, skipping frame rendering!");
427 return false;
428 }
429
430 // Lock the AR session mutex and render the frame. We hold onto the lock until we're done rendering
431 // to ensure that the AR session isn't stopped while we're rendering (this can happen!)
432 QMutexLocker arLocker(&m_arSessionMtx);
434 qCDebug(lcQuick3DXr, "AR tracking is not running, skipping frame rendering!");
435 return false;
436 }
437
438 // Lock the compositor layer mutex and render the frame. We hold onto the lock until we're done rendering
439 // as the configure call comes from the GUI thread and we need to ensure that the configuration is not
440 // changed while we're rendering.
441 QMutexLocker compositorLayer(&m_compositorLayerMtx);
442 return m_xrManager->renderFrameImpl(locker, m_waitCondition);
443 }
444
445 ar_world_tracking_provider_t worldTrackingProvider() const
446 {
447 return m_worldTrackingProvider;
448 }
449
450 // Rendering
451 mutable QMutex m_mutex;
452 QWaitCondition m_waitCondition;
453
454 mutable QMutex m_compositorLayerMtx;
455
456 // AR Session
457 mutable QMutex m_arSessionMtx;
458
459 QQuick3DXrManagerPrivate *m_xrManager = nullptr;
460
461 cp_layer_renderer_t m_layerRenderer = nullptr;
462 ar_world_tracking_provider_t m_worldTrackingProvider = nullptr;
463 ar_session_t m_arSession = nullptr;
464 mutable float m_depthRange[2] {1.0f, 10000.0f}; // NOTE: Near, Far
465 QQuick3DXrManagerPrivate::RenderState m_renderState = QQuick3DXrManagerPrivate::RenderState::Uninitialized;
467 bool m_initialized = false;
468 bool m_teardown = false;
469 mutable bool m_multiviewSupported = true;
470 mutable bool m_multiviewEnabled = true;
471 mutable bool m_foveationSupported = true;
472 mutable bool m_foveationEnabled = true;
473};
474
479
480Q_GLOBAL_STATIC(QSSGCompositionLayerInstance, s_compositorLayer)
481
482// FIXME: Maybe unify with the openxr implementation?!
483void QQuick3DXrManagerPrivate::updateCameraImp(simd_float4x4 headTransform, cp_drawable_t drawable, QQuick3DXrOrigin *xrOrigin, int i)
484{
485 cp_view_t view = cp_drawable_get_view(drawable, i);
486 simd_float2 depth_range = cp_drawable_get_depth_range(drawable);
487 const float clipNear = depth_range[1];
488 const float clipFar = depth_range[0];
489
490 xrOrigin->eyeCamera(i)->setClipNear(clipNear);
491 xrOrigin->eyeCamera(i)->setClipFar(clipFar);
492
493 simd_float4x4 projection = cp_drawable_compute_projection(drawable, cp_axis_direction_convention_right_up_forward, i);
494 QMatrix4x4 proj{projection.columns[0].x, projection.columns[1].x, projection.columns[2].x, projection.columns[3].x,
495 projection.columns[0].y, projection.columns[1].y, projection.columns[2].y, projection.columns[3].y,
496 projection.columns[0].z, projection.columns[1].z, projection.columns[2].z, projection.columns[3].z,
497 projection.columns[0].w, projection.columns[1].w, projection.columns[2].w, projection.columns[3].w};
498 xrOrigin->eyeCamera(i)->setProjection(proj);
499
500 simd_float4x4 localEyeTransform = cp_view_get_transform(view);
501 simd_float4x4 eyeCameraTransform = simd_mul(headTransform, localEyeTransform);
502 // NOTE: We need to convert from meters to centimeters here
503 QMatrix4x4 transform{eyeCameraTransform.columns[0].x, eyeCameraTransform.columns[1].x, eyeCameraTransform.columns[2].x, eyeCameraTransform.columns[3].x * 100,
504 eyeCameraTransform.columns[0].y, eyeCameraTransform.columns[1].y, eyeCameraTransform.columns[2].y, eyeCameraTransform.columns[3].y * 100,
505 eyeCameraTransform.columns[0].z, eyeCameraTransform.columns[1].z, eyeCameraTransform.columns[2].z, eyeCameraTransform.columns[3].z * 100,
506 0.0f, 0.0f, 0.0f, 1.0f};
507 QQuick3DNodePrivate::get(xrOrigin->eyeCamera(i))->setLocalTransform(transform);
508}
509
510void QQuick3DXrManagerPrivate::updateCamera(QQuick3DViewport *xrViewport, simd_float4x4 headTransform, cp_drawable_t drawable, QQuick3DXrOrigin *xrOrigin, int i)
511{
512 updateCameraImp(headTransform, drawable, xrOrigin, i);
513 xrViewport->setCamera(xrOrigin->eyeCamera(i));
514}
515
516void QQuick3DXrManagerPrivate::updateCameraMultiview(QQuick3DViewport *xrViewport, simd_float4x4 headTransform, cp_drawable_t drawable, QQuick3DXrOrigin *xrOrigin)
517{
518 QQuick3DCamera *cameras[2] {xrOrigin->eyeCamera(0), xrOrigin->eyeCamera(1)};
519
520 for (int i = 0; i < 2; ++i)
521 updateCameraImp(headTransform, drawable, xrOrigin, i);
522
523 xrViewport->setMultiViewCameras(cameras);
524}
525
526
528 : q_ptr(&manager)
529{
530}
531
533{
534}
535
536QQuick3DXrManagerPrivate *QQuick3DXrManagerPrivate::get(QQuick3DXrManager *manager)
537{
538 QSSG_ASSERT(manager != nullptr, return nullptr);
539 return manager->d_func();
540}
541
542bool QQuick3DXrManagerPrivate::initialize()
543{
544 Q_Q(QQuick3DXrManager);
545
546 // NOTE: Check if there's a global instance of the compositor layer
547 // already created, if not create one. Should probably move this over
548 // to the native interface.
549 if (!m_compositorLayer) {
550 m_compositorLayer = s_compositorLayer->instance;
551 if (!m_compositorLayer) {
552 m_compositorLayer = new CompositorLayer;
553 s_compositorLayer->instance = m_compositorLayer;
554 }
555 }
556
557 if (!m_inputManager)
558 m_inputManager = QQuick3DXrInputManager::instance();
559 if (!m_anchorManager)
560 m_anchorManager = QQuick3DXrAnchorManager::instance();
561
562 // NOTE: Check if the compository layer proxy is already active.
563 if (!m_compositorLayer->isInitialized()) {
564 if (auto *visionOSApplicaton = qGuiApp->nativeInterface<QNativeInterface::QVisionOSApplication>()) {
565 visionOSApplicaton->setImmersiveSpaceCompositorLayer(m_compositorLayer);
566 // FIXME: We don't actually handle the case where the rendere changes or we get multiple calls should do something.
567 QObject::connect(m_compositorLayer, &CompositorLayer::layerRendererReady, q, &QQuick3DXrManager::initialized, Qt::ConnectionType(Qt::SingleShotConnection | Qt::QueuedConnection));
568 QObject::connect(m_compositorLayer, &CompositorLayer::renderStateChanged, q, [q](QQuick3DXrManagerPrivate::RenderState state) {
569 switch (state) {
570 case QQuick3DXrManagerPrivate::RenderState::Uninitialized:
571 qCDebug(lcQuick3DXr, "Render state: Uninitialized");
572 QQuick3DXrManagerPrivate::get(q)->m_running = false;
573 break;
574 case QQuick3DXrManagerPrivate::RenderState::Running:
575 qCDebug(lcQuick3DXr, "Render state: Running");
576 QQuick3DXrManagerPrivate::get(q)->m_running = true;
577 QCoreApplication::postEvent(q, new QEvent(QEvent::UpdateRequest));
578 break;
579 case QQuick3DXrManagerPrivate::RenderState::Invalidated:
580 qCDebug(lcQuick3DXr, "Render state: Invalidated");
581 QQuick3DXrManagerPrivate::get(q)->m_running = false;
582 emit q->sessionEnded();
583 break;
584 case QQuick3DXrManagerPrivate::RenderState::Paused:
585 QQuick3DXrManagerPrivate::get(q)->m_running = false;
586 qCDebug(lcQuick3DXr, "Render state: Paused");
587 break;
588 }
589 }, Qt::DirectConnection);
590
591 QObject::connect(m_compositorLayer, &CompositorLayer::arStateChanged, q, [q](QQuick3DXrManagerPrivate::ArTrackingState state) {
592 switch (state) {
593 case QQuick3DXrManagerPrivate::ArTrackingState::Uninitialized:
594 qCDebug(lcQuick3DXr, "AR state: Uninitialized");
595 QQuick3DXrManagerPrivate::get(q)->m_arRunning = false;
596 break;
597 case QQuick3DXrManagerPrivate::ArTrackingState::Initialized:
598 qCDebug(lcQuick3DXr, "AR state: Initialized");
599 QQuick3DXrManagerPrivate::get(q)->m_arRunning = false;
600 break;
601 case QQuick3DXrManagerPrivate::ArTrackingState::Running:
602 qCDebug(lcQuick3DXr, "AR state: Running");
603 QQuick3DXrManagerPrivate::get(q)->m_arRunning = true;
604 QCoreApplication::postEvent(q, new QEvent(QEvent::UpdateRequest));
605 break;
606 case QQuick3DXrManagerPrivate::ArTrackingState::Paused:
607 qCDebug(lcQuick3DXr, "AR state: Paused");
608 QQuick3DXrManagerPrivate::get(q)->m_arRunning = false;
609 break;
610 case QQuick3DXrManagerPrivate::ArTrackingState::Stopped:
611 qCDebug(lcQuick3DXr, "AR state: Stopped");
612 QQuick3DXrManagerPrivate::get(q)->m_arRunning = false;
613 break;
614 }
615 }, Qt::DirectConnection);
616
617 m_compositorLayer->init(this);
618 }
619 return false;
620 }
621
622 return true;
623}
624
625void QQuick3DXrManagerPrivate::setupWindow(QQuickWindow *window)
626{
627 if (!window) {
628 qWarning("QQuick3DXrManagerPrivate: Window is null!");
629 return;
630 }
631
632 QSSG_ASSERT_X(m_compositorLayer != nullptr, "No composition layer!", return);
633
634 cp_layer_renderer_t renderer = m_compositorLayer->layerRenderer();
635 if (!renderer) {
636 qWarning("QQuick3DXrManagerPrivate: Layer renderer is not available.");
637 return;
638 }
639
640 auto device = cp_layer_renderer_get_device(renderer);
641 auto commandQueue = [device newCommandQueue];
642
643 auto qqGraphicsDevice = QQuickGraphicsDevice::fromDeviceAndCommandQueue(static_cast<MTLDevice*>(device), static_cast<MTLCommandQueue *>(commandQueue));
644
645 window->setGraphicsDevice(qqGraphicsDevice);
646}
647
648bool QQuick3DXrManagerPrivate::finalizeGraphics(QRhi *rhi)
649{
650 Q_UNUSED(rhi);
651
652 m_isGraphicsInitialized = true;
653 return m_isGraphicsInitialized;
654}
655
657{
658 return m_compositorLayer && (m_compositorLayer->layerRenderer() != nullptr);
659}
660
662{
663 return m_isGraphicsInitialized;
664}
665
666bool QQuick3DXrManagerPrivate::setupGraphics(QQuickWindow *window)
667{
668 QSSG_ASSERT(window != nullptr && m_compositorLayer != nullptr, return false);
669
670 if (m_compositorLayer->isMultiviewEnabled()) {
671 if (!m_renderThread) {
672 m_renderThread = new QThread;
673 m_renderThread->setObjectName(QLatin1StringView(s_renderThreadName));
674 m_renderThread->start();
675 }
676
677 if (m_compositorLayer->thread() != m_renderThread)
678 m_compositorLayer->moveToThread(m_renderThread);
679
680 Q_ASSERT(m_renderThread != nullptr);
681 QQuickWindowPrivate::get(window)->renderControl->prepareThread(m_renderThread);
682 }
683
684 // NOTE: See QQuick3DXrManager::setupGraphics() for emission of the value changed signal
685 m_multiviewRenderingEnabled = m_compositorLayer->isMultiviewEnabled();
686
687 return true;
688}
689
690void QQuick3DXrManagerPrivate::getDefaultClipDistances(float &nearClip, float &farClip) const
691{
692 m_compositorLayer->getDefaultDepthRange(nearClip, farClip);
693}
694
695void QQuick3DXrManagerPrivate::teardown()
696{
697 Q_Q(QQuick3DXrManager);
698
699 qCDebug(lcQuick3DXr) << "Tearing down XR session";
700
701 QMutexLocker<QMutex> locker { nullptr };
702 if (m_compositorLayer)
703 locker = QMutexLocker<QMutex>{ &m_compositorLayer->renderLock() };
704
705 m_running = false;
706
707 if (m_inputManager)
708 QQuick3DXrInputManagerPrivate::get(m_inputManager)->teardown();
709
710 if (m_anchorManager)
711 m_anchorManager->teardown();
712
713 if (m_compositorLayer) {
714 m_compositorLayer->stopArSession();
715 m_compositorLayer->teardown();
716
717 // NOTE: Unlock the render mutex. The compositor layer will be destroyed on the render thread now!
718 // Also, be aware of the race condition here once the lock is released (see: teardownStarted())...
719 locker.unlock();
720
721 Qt::ConnectionType connection = (m_compositorLayer->thread() == QThread::currentThread())
722 ? Qt::DirectConnection : Qt::BlockingQueuedConnection;
723 QMetaObject::invokeMethod(m_compositorLayer, &CompositorLayer::destroy, connection, this, q->m_quickWindow, m_compositorLayer);
724 m_compositorLayer = nullptr;
725 }
726
727 if (m_renderThread) {
728 m_renderThread->quit();
729 m_renderThread->wait();
730 delete m_renderThread;
731 m_renderThread = nullptr;
732 }
733}
734
736{
737 Q_UNUSED(enable);
738 qCWarning(lcQuick3DXr) << "Changing multiview rendering is not supported at runtime on visionOS!";
739}
740
742{
743 return m_multiviewRenderingEnabled;
744}
745
747{
748 Q_UNUSED(enable);
749 qCWarning(lcQuick3DXr) << "Changing passthrough is not supported at runtime on visionOS!";
751}
752
754{
755 // FIXME: Not sure exactly what reference space is default or what is supported etc.
756 return QtQuick3DXr::ReferenceSpace::ReferenceSpaceLocalFloor;
757}
758
759void QQuick3DXrManagerPrivate::setReferenceSpace(QtQuick3DXr::ReferenceSpace newReferenceSpace)
760{
761 Q_UNUSED(newReferenceSpace);
762 qCWarning(lcQuick3DXr) << "Changing reference space is not supported at runtime on visionOS!";
763}
764
766{
767 if (!enable)
768 qCWarning(lcQuick3DXr, "Depth submission is required on visionOS");
769}
770
772{
773 Q_Q(QQuick3DXrManager);
774
775 // Lock the render mutex and request a new frame to be rendered.
776 QMutexLocker<QMutex> locker { &m_compositorLayer->renderLock() };
777
778 if (!m_running || !m_arRunning || !m_isGraphicsInitialized) {
779 qCDebug(lcQuick3DXr, "Not running, skipping update");
780 return;
781 }
782
783 if (m_compositorLayer->isMultiviewEnabled()) {
784 // polish (GUI thread)
785 q->m_renderControl->polishItems();
786 m_syncDone = false;
787 QCoreApplication::postEvent(m_compositorLayer, new QEvent(CompositorLayer::asQEvent(CompositorLayer::Event::Render)));
788 // Wait for the sync to complete.
789 bool waitCompleted = m_compositorLayer->waitForSyncToComplete();
790 // The gui thread can now continue.
791
792 QQuick3DXrAnimationDriver *animationDriver = q->m_animationDriver;
793
794 if (Q_LIKELY(waitCompleted && m_syncDone && animationDriver)) {
795 animationDriver->setStep(m_nextStepSize);
796 animationDriver->advance();
797 } else {
798 qCDebug(lcQuick3DXr) << "Failed to wait for sync to complete" << "\nWaitCompleted:" << waitCompleted << "\nsyncDone" << m_syncDone;
799 }
800 } else {
801 [[maybe_unused]] bool ret = m_compositorLayer->renderFrame(locker);
802 }
803
804 QCoreApplication::postEvent(q, new QEvent(QEvent::UpdateRequest));
805}
806
808{
809 // NOTE: This is not used on visionOS
810}
811
812void QQuick3DXrManagerPrivate::processSpatialEvents(const QJsonObject &events)
813{
814 Q_Q(QQuick3DXrManager);
815 QSSG_ASSERT(q->m_vrViewport != nullptr, return);
816 QQuick3DXrInputManagerPrivate::processSpatialEvents(*q->m_vrViewport, events);
817}
818
819void QQuick3DXrManagerPrivate::prepareAnchorManager(QQuick3DXrAnchorManager *anchorManager, ar_data_providers_t dataProviders)
820{
821 anchorManager->prepareAnchorManager(dataProviders);
822}
823
824void QQuick3DXrManagerPrivate::initAnchorManager(QQuick3DXrAnchorManager *anchorManager)
825{
826 anchorManager->initAnchorManager();
827}
828
829void QQuick3DXrManagerPrivate::initInputManager(QQuick3DXrInputManager *im)
830{
832}
833
834void QQuick3DXrManagerPrivate::setSamples(int samples)
835{
836 Q_UNUSED(samples);
837 qCWarning(lcQuick3DXr) << "Changing sample count is not supported on visionOS!";
838}
839
841{
842 return QStringLiteral("visionOS");
843}
844
846{
847 static const auto versionNumber = QOperatingSystemVersion::current().version();
848 return versionNumber;
849}
850
852{
853 return QString();
854}
855
856void QQuick3DXrManagerPrivate::setupShadingRateMap(QQuickWindow *window, QRhiShadingRateMap *srm)
857{
858 if (QRhiSwapChain *swapchain = window->swapChain()) {
859 swapchain->setShadingRateMap(srm);
860 if (!m_srmRenderPassDesc) {
861 qCDebug(lcQuick3DXr) << "Creating render pass descriptor suitable for shading rate map use";
862 m_srmRenderPassDesc = swapchain->newCompatibleRenderPassDescriptor();
863 swapchain->setRenderPassDescriptor(m_srmRenderPassDesc);
864 }
865 } else {
866 QSGRendererInterface *rif = window->rendererInterface();
867 QRhiTextureRenderTarget *rt = static_cast<QRhiTextureRenderTarget *>(rif->getResource(window, QSGRendererInterface::RhiRedirectRenderTarget));
868 if (QSSG_GUARD_X(rt, "No render target!")) {
869 QRhiTextureRenderTargetDescription desc = rt->description();
870 desc.setShadingRateMap(srm);
871 rt->setDescription(desc);
872 if (!m_srmRenderPassDesc) {
873 qCDebug(lcQuick3DXr) << "Creating render pass descriptor suitable for shading rate map use";
874 QRhiRenderPassDescriptor *rpd = rt->renderPassDescriptor();
875 m_srmRenderPassDesc = rpd->newCompatibleRenderPassDescriptor();
876 rt->setRenderPassDescriptor(m_srmRenderPassDesc);
877 rt->create();
878 }
879 }
880 }
881}
882
883void QQuick3DXrManagerPrivate::releaseResources()
884{
885 qCDebug(lcQuick3DXr) << "Releasing resources";
886 Q_ASSERT((m_renderThread == nullptr) || QThread::currentThread() == m_renderThread);
887 delete m_srmRenderPassDesc;
888 m_srmRenderPassDesc = nullptr;
889 delete m_rhiDepthTexture;
890 m_rhiDepthTexture = nullptr;
891 for (size_t i = 0, end = std::size(m_srm); i < end; ++i) {
892 delete m_srm[i];
893 m_srm[i] = nullptr;
894 }
895}
896
897bool QQuick3DXrManagerPrivate::renderFrameImpl(QMutexLocker<QMutex> &locker, QWaitCondition &waitCondition)
898{
899 Q_Q(QQuick3DXrManager);
900
901 // NOTE: The GUI thread is locked at this point
902 QQuickWindow *window = q->m_quickWindow;
903 QQuickRenderControl *renderControl = q->m_renderControl;
904 QQuick3DXrOrigin *xrOrigin = q->m_xrOrigin;
905 QQuick3DViewport *xrViewport = q->m_vrViewport;
906 QQuick3DXrAnimationDriver *animationDriver = q->m_animationDriver;
907
908 QSSG_ASSERT_X(window && renderControl && xrViewport && xrOrigin && animationDriver, "Invalid state, rendering aborted", return false);
909
910 const bool multiviewRenderingEnabled = isMultiViewRenderingEnabled();
911
912 Q_ASSERT(!multiviewRenderingEnabled || QThread::currentThread() != window->thread());
913
914 auto layerRenderer = m_compositorLayer->layerRenderer();
915 cp_frame_t frame = cp_layer_renderer_query_next_frame(layerRenderer);
916 if (Q_UNLIKELY(frame == nullptr)) {
917 qWarning("Failed to get next frame");
918 return false;
919 }
920
921 cp_frame_timing_t timing = cp_frame_predict_timing(frame);
922 if (Q_UNLIKELY(timing == nullptr)) {
923 qWarning("Failed to get timing for frame");
924 return false;
925 }
926
927 cp_frame_start_update(frame);
928
929 cp_frame_end_update(frame);
930
931 cp_time_t optimalInputTime = cp_frame_timing_get_optimal_input_time(timing);
932 cp_time_wait_until(optimalInputTime);
933
934 cp_frame_start_submission(frame);
935 cp_drawable_t drawable = cp_frame_query_drawable(frame);
936 if (Q_UNLIKELY(drawable == nullptr)) {
937 qWarning("Failed to get drawable for frame");
938 return false;
939 }
940
941 cp_frame_timing_t actualTiming = cp_drawable_get_frame_timing(drawable);
942 ar_device_anchor_t anchor = m_compositorLayer->createPoseForTiming(actualTiming);
943 cp_drawable_set_device_anchor(drawable, anchor);
944
945 // Get the pose transform from the anchor
946 simd_float4x4 headTransform = ar_anchor_get_origin_from_anchor_transform(anchor);
947
948 // NOTE: We need to convert from meters to centimeters here
949 QMatrix4x4 qtHeadTransform{headTransform.columns[0].x, headTransform.columns[1].x, headTransform.columns[2].x, headTransform.columns[3].x * 100,
950 headTransform.columns[0].y, headTransform.columns[1].y, headTransform.columns[2].y, headTransform.columns[3].y * 100,
951 headTransform.columns[0].z, headTransform.columns[1].z, headTransform.columns[2].z, headTransform.columns[3].z * 100,
952 0.0f, 0.0f, 0.0f, 1.0f};
953 xrOrigin->updateTrackedCamera(qtHeadTransform);
954
955 // Update the hands
956 if (QSSG_GUARD(m_inputManager != nullptr))
957 QQuick3DXrInputManagerPrivate::get(m_inputManager)->updateHandtracking();
958
959 // Animation driver
960 // Convert the cp_frame_timing_t ticks to milliseconds
961 enum : size_t { DisplayPeriod = 0, DisplayDelta };
962 qint64 stepSizes[2] {0, 0};
963 auto &[displayPeriodMS, displayDeltaMS] = stepSizes;
964 displayPeriodMS = qint64(cp_time_to_cf_time_interval(optimalInputTime) * 1000.0);
965 displayDeltaMS = ((qint64(cp_time_to_cf_time_interval(cp_frame_timing_get_optimal_input_time(actualTiming)) * 1000.0)) - m_previousTime);
966 const size_t selector = ((m_previousTime == 0) || (displayDeltaMS > displayPeriodMS)) ? DisplayPeriod : DisplayDelta;
967 m_nextStepSize = stepSizes[selector];
968 m_previousTime = displayPeriodMS;
969
970 if (!multiviewRenderingEnabled) {
971 animationDriver->setStep(m_nextStepSize);
972 animationDriver->advance();
973 }
974
975 QRhi *rhi = renderControl->rhi();
976
977 const auto viewCount = cp_drawable_get_view_count(drawable);
978 const auto textureCount = cp_drawable_get_texture_count(drawable);
979 const auto renderCalls = textureCount; // To make it less confusing...
980
981 bool foveationEnabled = m_compositorLayer->isFoveationEnabled();
982 Q_ASSERT(!foveationEnabled || rhi->isFeatureSupported(QRhi::VariableRateShadingMap));
983 // We still should make sure we have a rate map!
984 if (foveationEnabled) {
985 const size_t rrmapcount = cp_drawable_get_rasterization_rate_map_count(drawable);
986 const bool validMapCount = (rrmapcount == 1 || rrmapcount == 2);
987 QSSG_GUARD_X(validMapCount, "Invalid rate map count!");
988 foveationEnabled = validMapCount;
989
990 if (foveationEnabled) {
991 for (size_t i = 0; i < rrmapcount; ++i) {
992 if (!m_srm[i])
993 m_srm[i] = rhi->newShadingRateMap();
994 id<MTLRasterizationRateMap> rrm = cp_drawable_get_rasterization_rate_map(drawable, i);
995 const bool wasCreated = m_srm[i]->createFrom({ quint64(rrm) });
996 QSSG_GUARD_X(wasCreated, "Failed to create shading rate map!");
997 foveationEnabled = (foveationEnabled && wasCreated);
998 }
999 }
1000 }
1001
1002 // NOTE: Expectation is that when multiview rendering is enabled we get a multiple drawables with a single texture array,
1003 // each view/eye is then rendered to a slice in the texture array. If multiview rendering is _not_ enabled we get
1004 // two drawables with two textures, one for each eye. In this case we render each eye to its own texture.
1005 //
1006 // And just to keep things interesting the emulator will use one view and one texture, so even if we hit the
1007 // "dedicated"/non-multiview case we need to handle that as well. NOTE: We don't do threaded rendering when
1008 // multiview rendering is disabled!
1009
1010 for (size_t i = 0, end = renderCalls; i != end ; ++i) {
1011 // Setup the RenderTarget based on the current drawable
1012 id<MTLTexture> colorMetalTexture = cp_drawable_get_color_texture(drawable, i);
1013 auto textureSize = QSize([colorMetalTexture width], [colorMetalTexture height]);
1014
1015 QQuickRenderTarget renderTarget;
1016
1017 if (multiviewRenderingEnabled)
1018 renderTarget = QQuickRenderTarget::fromMetalTexture(static_cast<MTLTexture*>(colorMetalTexture), [colorMetalTexture pixelFormat], [colorMetalTexture pixelFormat]/*viewFormat*/, textureSize, 1 /*sampleCount*/, viewCount, {});
1019 else
1020 renderTarget = QQuickRenderTarget::fromMetalTexture(static_cast<MTLTexture*>(colorMetalTexture), [colorMetalTexture pixelFormat], textureSize);
1021
1022 auto depthMetalTexture = cp_drawable_get_depth_texture(drawable, i);
1023 auto depthTextureSize = QSize([depthMetalTexture width], [depthMetalTexture height]);
1024 MTLPixelFormat depthTextureFormat = [depthMetalTexture pixelFormat];
1025 static const auto convertFormat = [](MTLPixelFormat format) -> QRhiTexture::Format {
1026 switch (format) {
1027 case MTLPixelFormatDepth16Unorm:
1028 return QRhiTexture::D16;
1029 case MTLPixelFormatDepth32Float:
1030 return QRhiTexture::D32F;
1031 default:
1032 qWarning("Unsupported depth texture format");
1033 return QRhiTexture::UnknownFormat;
1034 }
1035 };
1036 auto depthFormat = convertFormat(depthTextureFormat);
1037 if (depthFormat != QRhiTexture::UnknownFormat) {
1038 if (m_rhiDepthTexture && (m_rhiDepthTexture->format() != depthFormat || m_rhiDepthTexture->pixelSize() != depthTextureSize)) {
1039 delete m_rhiDepthTexture;
1040 m_rhiDepthTexture = nullptr;
1041 }
1042
1043 if (!m_rhiDepthTexture) {
1044 if (multiviewRenderingEnabled)
1045 m_rhiDepthTexture = rhi->newTextureArray(depthFormat, viewCount, depthTextureSize, 1, QRhiTexture::RenderTarget);
1046 else
1047 m_rhiDepthTexture = rhi->newTexture(depthFormat, depthTextureSize, 1, QRhiTexture::RenderTarget);
1048 }
1049
1050
1051 m_rhiDepthTexture->createFrom({ quint64(static_cast<MTLTexture*>(depthMetalTexture)), 0});
1052 renderTarget.setDepthTexture(m_rhiDepthTexture);
1053 }
1054
1055 window->setRenderTarget(renderTarget);
1056
1057 // Initial window size is the size of the texture, but we also need to check the viewport size
1058 // as this can be different. If the viewport size is different we need to adjust the window size.
1059 // This is the case when we render to a smaller texture and then scale it up to the window (i.e. foveated rendering).
1060 QSize renderSize = textureSize;
1061 if (foveationEnabled) {
1062 cp_view_t view = cp_drawable_get_view(drawable, i);
1063 cp_view_texture_map_t texture_map = cp_view_get_view_texture_map(view);
1064 auto vp = cp_view_texture_map_get_viewport(texture_map);
1065 renderSize = QSize(vp.width, vp.height);
1066 }
1067
1068 window->setGeometry(0,
1069 0,
1070 renderSize.width(),
1071 renderSize.height());
1072 window->contentItem()->setSize(renderSize);
1073 // NOTE: We set the size here as we need to make sure the window size is correct before
1074 // the sync, or the xrView's size will be incorrect. This happens because we're on the
1075 // render thread (GUI thread is blocked) and any updates to the contentItem size will
1076 // not be updated (see: QQuick3DXrView::init)...
1077 xrViewport->setSize(renderSize);
1078
1079 // Update the camera pose
1080 if (QSSG_GUARD(xrOrigin)) {
1081 if (multiviewRenderingEnabled)
1082 updateCameraMultiview(xrViewport, headTransform, drawable, xrOrigin);
1083 else
1084 updateCamera(xrViewport, headTransform, drawable, xrOrigin, i);
1085 }
1086
1087 // We only do this if we are rendering on a separate thread and with multiview rendering enabled,
1088 // or else the beginFrame and sync will be done on the GUI thread after both eyes have been
1089 // rendered...
1090 if (multiviewRenderingEnabled) {
1091 // Marks the start of the frame.
1092 renderControl->beginFrame();
1093 // Synchronization happens here on the render thread (with the GUI thread locked)
1094 m_syncDone = renderControl->sync();
1095
1096 if (Q_UNLIKELY(!m_syncDone))
1097 return false;
1098
1099 if (foveationEnabled)
1100 setupShadingRateMap(window, m_srm[i]);
1101
1102 // Signal the GUI thread that the sync is done, so it can continue.
1103 waitCondition.wakeOne();
1104 locker.unlock();
1105
1106 // Render the frame
1107 renderControl->render();
1108 // Marks the end of the frame.
1109 renderControl->endFrame();
1110 } else {
1111 renderControl->polishItems();
1112 renderControl->beginFrame();
1113 renderControl->sync();
1114 if (foveationEnabled)
1115 setupShadingRateMap(window, m_srm[i]);
1116 renderControl->render();
1117 renderControl->endFrame();
1118 }
1119
1120#if TARGET_OS_SIMULATOR == 1
1121 // With multiview this indicates that the frame with both eyes is ready from
1122 // the 3D APIs perspective. Without multiview this is done - and so the
1123 // signal is emitted - multiple times (twice) per "frame" (eye).
1124 QRhiRenderTarget *rt = QQuickWindowPrivate::get(window)->activeCustomRhiRenderTarget();
1125 if (rt->resourceType() == QRhiResource::TextureRenderTarget && static_cast<QRhiTextureRenderTarget *>(rt)->description().colorAttachmentAt(0)->texture())
1126 emit q->frameReady();
1127#endif // TARGET_OS_SIMULATOR
1128 }
1129
1130 id<MTLCommandBuffer> commandBuffer = [static_cast<const QRhiMetalNativeHandles*>(renderControl->rhi()->nativeHandles())->cmdQueue commandBuffer];
1131
1132 cp_drawable_encode_present(drawable, commandBuffer);
1133 [commandBuffer commit];
1134
1135 cp_frame_end_submission(frame);
1136
1137 return true;
1138}
1139
1140QT_END_NAMESPACE
1141
1142#include "qquick3dxrmanager_visionos.moc"
void arStateChanged(QQuick3DXrManagerPrivate::ArTrackingState)
void configure(cp_layer_renderer_capabilities_t capabilities, cp_layer_renderer_configuration_t configuration) const override
static constexpr QEvent::Type asQEvent(CompositorLayer::Event event)
friend bool QQuick3DXrManagerPrivate::renderFrameImpl(QMutexLocker< QMutex > &locker, QWaitCondition &waitCondition)
void getDefaultDepthRange(float &near, float &far) const
static Q_INVOKABLE void destroy(QQuick3DXrManagerPrivate *mngr, QQuickWindow *window, CompositorLayer *compositorLayer)
void layerRendererChanged()
static bool supportsLayoutType(cp_layer_renderer_capabilities_t capabilities, cp_layer_renderer_layout layout)
bool event(QEvent *event) override
This virtual function receives events to an object and should return true if the event e was recogniz...
void renderStateChanged(QQuick3DXrManagerPrivate::RenderState)
cp_layer_renderer_t layerRenderer() const
void render(cp_layer_renderer_t renderer) override
void handleSpatialEvents(const QJsonObject &events) override
void init(QQuick3DXrManagerPrivate *xrManager)
\inmodule QtCore
Definition qmutex.h:346
void prepareHandtracking(ar_data_providers_t dataProviders)
static QQuick3DXrInputManagerPrivate * get(QQuick3DXrInputManager *inputManager)
static QQuick3DXrInputManager * instance()
QQuick3DXrManagerPrivate(QQuick3DXrManager &manager)
void setupWindow(QQuickWindow *window)
QtQuick3DXr::ReferenceSpace getReferenceSpace() const
void setReferenceSpace(QtQuick3DXr::ReferenceSpace newReferenceSpace)
bool setupGraphics(QQuickWindow *window)
void processSpatialEvents(const QJsonObject &events)
void getDefaultClipDistances(float &nearClip, float &farClip) const
static bool isMultiviewRenderingDisabled()
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:621
static const char s_renderThreadName[]
static bool qssgDisableFoveation()