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