Blender V4.5
GHOST_XrSession.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2020-2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include <algorithm>
10#include <cassert>
11#include <chrono>
12#include <cstdio>
13#include <list>
14#include <sstream>
15
16#include "GHOST_C-api.h"
17
19#include "GHOST_XrAction.hh"
20#include "GHOST_XrContext.hh"
22#include "GHOST_XrException.hh"
23#include "GHOST_XrSwapchain.hh"
24#include "GHOST_Xr_intern.hh"
25
26#include "GHOST_XrSession.hh"
27
29 XrSystemId system_id = XR_NULL_SYSTEM_ID;
30 XrSession session = XR_NULL_HANDLE;
31 XrSessionState session_state = XR_SESSION_STATE_UNKNOWN;
32
33 /* Use stereo rendering by default. */
34 XrViewConfigurationType view_type = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
35 bool foveation_supported = false;
36
38 XrSpace view_space;
40 std::vector<XrView> views;
41 std::vector<GHOST_XrSwapchain> swapchains;
42
43 std::map<std::string, GHOST_XrActionSet> action_sets;
44 /* Controller models identified by subaction path. */
45 std::map<std::string, GHOST_XrControllerModel> controller_models;
46
47 /* Meta Quest passthrough support. */
49 XrCompositionLayerPassthroughFB passthrough_layer;
50};
51
53 XrFrameState frame_state;
54
56 std::chrono::high_resolution_clock::time_point frame_begin_time;
57 /* Time previous frames took for rendering (in ms). */
58 std::list<double> last_frame_times;
59
60 /* Whether foveation is active for the frame. */
62};
63
64/* -------------------------------------------------------------------- */
67
69 : m_context(&xr_context), m_oxr(std::make_unique<OpenXRSessionData>())
70{
71}
72
74{
76
77 m_oxr->swapchains.clear();
78 m_oxr->action_sets.clear();
79
80 if (m_oxr->reference_space != XR_NULL_HANDLE) {
81 CHECK_XR_ASSERT(xrDestroySpace(m_oxr->reference_space));
82 }
83 if (m_oxr->view_space != XR_NULL_HANDLE) {
84 CHECK_XR_ASSERT(xrDestroySpace(m_oxr->view_space));
85 }
86 if (m_oxr->combined_eye_space != XR_NULL_HANDLE) {
87 CHECK_XR_ASSERT(xrDestroySpace(m_oxr->combined_eye_space));
88 }
89 if (m_oxr->session != XR_NULL_HANDLE) {
90 CHECK_XR_ASSERT(xrDestroySession(m_oxr->session));
91 }
92
93 m_oxr->session = XR_NULL_HANDLE;
94 m_oxr->session_state = XR_SESSION_STATE_UNKNOWN;
95
96 m_oxr->passthrough_supported = false;
97 m_oxr->passthrough_layer.layerHandle = XR_NULL_HANDLE;
98
99 m_context->getCustomFuncs().session_exit_fn(m_context->getCustomFuncs().session_exit_customdata);
100}
101
106void GHOST_XrSession::initSystem()
107{
108 assert(m_context->getInstance() != XR_NULL_HANDLE);
109 assert(m_oxr->system_id == XR_NULL_SYSTEM_ID);
110
111 XrSystemGetInfo system_info = {};
112 system_info.type = XR_TYPE_SYSTEM_GET_INFO;
113 system_info.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
114
115 CHECK_XR(xrGetSystem(m_context->getInstance(), &system_info, &m_oxr->system_id),
116 "Failed to get device information. Is a device plugged in?");
117}
118 /* Create, Initialize and Destruct */
120
121/* -------------------------------------------------------------------- */
124
126 const GHOST_XrPose &base_pose,
127 bool isDebugMode)
128{
129 XrReferenceSpaceCreateInfo create_info = {XR_TYPE_REFERENCE_SPACE_CREATE_INFO};
130 create_info.poseInReferenceSpace.orientation.w = 1.0f;
131
132 create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE;
133#if 0
134/* TODO
135 *
136 * Proper reference space set up is not supported yet. We simply hand OpenXR
137 * the global space as reference space and apply its pose onto the active
138 * camera matrix to get a basic viewing experience going. If there's no active
139 * camera with stick to the world origin.
140 *
141 * Once we have proper reference space set up (i.e. a way to define origin, up-
142 * direction and an initial view rotation perpendicular to the up-direction),
143 * we can hand OpenXR a proper reference pose/space.
144 */
145 create_info.poseInReferenceSpace.position.x = base_pose->position[0];
146 create_info.poseInReferenceSpace.position.y = base_pose->position[1];
147 create_info.poseInReferenceSpace.position.z = base_pose->position[2];
148 create_info.poseInReferenceSpace.orientation.x = base_pose->orientation_quat[1];
149 create_info.poseInReferenceSpace.orientation.y = base_pose->orientation_quat[2];
150 create_info.poseInReferenceSpace.orientation.z = base_pose->orientation_quat[3];
151 create_info.poseInReferenceSpace.orientation.w = base_pose->orientation_quat[0];
152#else
153 (void)base_pose;
154#endif
155
156 XrResult result = xrCreateReferenceSpace(oxr.session, &create_info, &oxr.reference_space);
157
158 if (XR_FAILED(result)) {
159 /* One of the rare cases where we don't want to immediately throw an exception on failure,
160 * since runtimes are not required to support the stage reference space. If the runtime
161 * doesn't support it then just fall back to the local space. */
162 if (result == XR_ERROR_REFERENCE_SPACE_UNSUPPORTED) {
163 if (isDebugMode) {
164 printf(
165 "Warning: XR runtime does not support stage reference space, falling back to local "
166 "reference space.\n");
167 }
168 create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
169 CHECK_XR(xrCreateReferenceSpace(oxr.session, &create_info, &oxr.reference_space),
170 "Failed to create local reference space.");
171 }
172 else {
173 throw GHOST_XrException("Failed to create stage reference space.", result);
174 }
175 }
176 else {
177 /* Check if tracking bounds are valid. Tracking bounds may be invalid if the user did not
178 * define a tracking space via the XR runtime. */
179 XrExtent2Df extents;
180 CHECK_XR(xrGetReferenceSpaceBoundsRect(oxr.session, XR_REFERENCE_SPACE_TYPE_STAGE, &extents),
181 "Failed to get stage reference space bounds.");
182 if (extents.width == 0.0f || extents.height == 0.0f) {
183 if (isDebugMode) {
184 printf(
185 "Warning: Invalid stage reference space bounds, falling back to local reference "
186 "space. To use the stage reference space, please define a tracking space via the XR "
187 "runtime.\n");
188 }
189 /* Fall back to local space. */
190 if (oxr.reference_space != XR_NULL_HANDLE) {
191 CHECK_XR(xrDestroySpace(oxr.reference_space), "Failed to destroy stage reference space.");
192 }
193
194 create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
195 CHECK_XR(xrCreateReferenceSpace(oxr.session, &create_info, &oxr.reference_space),
196 "Failed to create local reference space.");
197 }
198 }
199
200 create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW;
201 CHECK_XR(xrCreateReferenceSpace(oxr.session, &create_info, &oxr.view_space),
202 "Failed to create view reference space.");
203
204 /* Foveation reference spaces. */
205 if (oxr.foveation_supported) {
206 create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_COMBINED_EYE_VARJO;
207 CHECK_XR(xrCreateReferenceSpace(oxr.session, &create_info, &oxr.combined_eye_space),
208 "Failed to create combined eye reference space.");
209 }
210}
211
212void GHOST_XrSession::start(const GHOST_XrSessionBeginInfo *begin_info)
213{
214 assert(m_context->getInstance() != XR_NULL_HANDLE);
215 assert(m_oxr->session == XR_NULL_HANDLE);
216 if (m_context->getCustomFuncs().gpu_ctx_bind_fn == nullptr) {
217 throw GHOST_XrException(
218 "Invalid API usage: No way to bind graphics context to the XR session. Call "
219 "GHOST_XrGraphicsContextBindFuncs() with valid parameters before starting the "
220 "session (through GHOST_XrSessionStart()).");
221 }
222
223 initSystem();
224
225 bindGraphicsContext();
226 if (m_gpu_ctx == nullptr) {
227 throw GHOST_XrException(
228 "Invalid API usage: No graphics context returned through the callback set with "
229 "GHOST_XrGraphicsContextBindFuncs(). This is required for session starting (through "
230 "GHOST_XrSessionStart()).");
231 }
232
233 std::string requirement_str;
234 m_gpu_binding = GHOST_XrGraphicsBindingCreateFromType(m_context->getGraphicsBindingType(),
235 *m_gpu_ctx);
236 if (!m_gpu_binding->checkVersionRequirements(
237 *m_gpu_ctx, m_context->getInstance(), m_oxr->system_id, &requirement_str))
238 {
239 std::ostringstream strstream;
240 strstream << "Available graphics context version does not meet the following requirements: "
241 << requirement_str;
242 throw GHOST_XrException(strstream.str().data());
243 }
244 m_gpu_binding->initFromGhostContext(*m_gpu_ctx, m_context->getInstance(), m_oxr->system_id);
245
246 XrSessionCreateInfo create_info = {};
247 create_info.type = XR_TYPE_SESSION_CREATE_INFO;
248 create_info.systemId = m_oxr->system_id;
249 create_info.next = &m_gpu_binding->oxr_binding;
250
251 CHECK_XR(xrCreateSession(m_context->getInstance(), &create_info, &m_oxr->session),
252 "Failed to create VR session. The OpenXR runtime may have additional requirements for "
253 "the graphics driver that are not met. Other causes are possible too however.\nTip: "
254 "The --debug-xr command line option for Blender might allow the runtime to output "
255 "detailed error information to the command line.");
256
257 prepareDrawing();
258 create_reference_spaces(*m_oxr, begin_info->base_pose, m_context->isDebugMode());
259
260 /* Create and bind actions here. */
261 m_context->getCustomFuncs().session_create_fn();
262}
263
265{
266 xrRequestExitSession(m_oxr->session);
267}
268
269void GHOST_XrSession::beginSession()
270{
271 XrSessionBeginInfo begin_info = {XR_TYPE_SESSION_BEGIN_INFO};
272 begin_info.primaryViewConfigurationType = m_oxr->view_type;
273 CHECK_XR(xrBeginSession(m_oxr->session, &begin_info), "Failed to cleanly begin the VR session.");
274}
275
276void GHOST_XrSession::endSession()
277{
278 assert(m_oxr->session != XR_NULL_HANDLE);
279 CHECK_XR(xrEndSession(m_oxr->session), "Failed to cleanly end the VR session.");
280}
281
283 const XrEventDataSessionStateChanged &lifecycle)
284{
285 m_oxr->session_state = lifecycle.state;
286
287 /* Runtime may send events for apparently destroyed session. Our handle should be nullptr then.
288 */
289 assert(m_oxr->session == XR_NULL_HANDLE || m_oxr->session == lifecycle.session);
290
291 switch (lifecycle.state) {
292 case XR_SESSION_STATE_READY:
293 beginSession();
294 break;
295 case XR_SESSION_STATE_STOPPING:
296 endSession();
297 break;
298 case XR_SESSION_STATE_EXITING:
299 case XR_SESSION_STATE_LOSS_PENDING:
300 return SESSION_DESTROY;
301 default:
302 break;
303 }
304
305 return SESSION_KEEP_ALIVE;
306}
307 /* State Management */
309
310/* -------------------------------------------------------------------- */
313
314void GHOST_XrSession::prepareDrawing()
315{
316 assert(m_context->getInstance() != XR_NULL_HANDLE);
317
318 std::vector<XrViewConfigurationView> view_configs;
319 uint32_t view_count;
320
321 /* Attempt to use quad view if supported. */
322 if (m_context->isExtensionEnabled(XR_VARJO_QUAD_VIEWS_EXTENSION_NAME)) {
323 m_oxr->view_type = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_QUAD_VARJO;
324 }
325
326 m_oxr->foveation_supported = m_context->isExtensionEnabled(
327 XR_VARJO_FOVEATED_RENDERING_EXTENSION_NAME);
328
329 CHECK_XR(
330 xrEnumerateViewConfigurationViews(
331 m_context->getInstance(), m_oxr->system_id, m_oxr->view_type, 0, &view_count, nullptr),
332 "Failed to get count of view configurations.");
333 view_configs.resize(view_count, {XR_TYPE_VIEW_CONFIGURATION_VIEW});
334 CHECK_XR(xrEnumerateViewConfigurationViews(m_context->getInstance(),
335 m_oxr->system_id,
336 m_oxr->view_type,
337 view_configs.size(),
338 &view_count,
339 view_configs.data()),
340 "Failed to get view configurations.");
341
342 /* If foveated rendering is used, query the foveated views. */
343 if (m_oxr->foveation_supported) {
344 std::vector<XrFoveatedViewConfigurationViewVARJO> request_foveated_config{
345 view_count, {XR_TYPE_FOVEATED_VIEW_CONFIGURATION_VIEW_VARJO, nullptr, XR_TRUE}};
346
347 auto foveated_views = std::vector<XrViewConfigurationView>(view_count,
348 {XR_TYPE_VIEW_CONFIGURATION_VIEW});
349
350 for (uint32_t i = 0; i < view_count; i++) {
351 foveated_views[i].next = &request_foveated_config[i];
352 }
353 CHECK_XR(xrEnumerateViewConfigurationViews(m_context->getInstance(),
354 m_oxr->system_id,
355 m_oxr->view_type,
356 view_configs.size(),
357 &view_count,
358 foveated_views.data()),
359 "Failed to get foveated view configurations.");
360
361 /* Ensure swapchains have correct size even when foveation is being used. */
362 for (uint32_t i = 0; i < view_count; i++) {
363 view_configs[i].recommendedImageRectWidth = std::max(
364 view_configs[i].recommendedImageRectWidth, foveated_views[i].recommendedImageRectWidth);
365 view_configs[i].recommendedImageRectHeight = std::max(
366 view_configs[i].recommendedImageRectHeight,
367 foveated_views[i].recommendedImageRectHeight);
368 }
369 }
370
371 for (const XrViewConfigurationView &view_config : view_configs) {
372 m_oxr->swapchains.emplace_back(*m_gpu_binding, m_oxr->session, view_config);
373 }
374
375 m_oxr->views.resize(view_count, {XR_TYPE_VIEW});
376
377 m_draw_info = std::make_unique<GHOST_XrDrawInfo>();
378}
379
380void GHOST_XrSession::beginFrameDrawing()
381{
382 XrFrameWaitInfo wait_info = {XR_TYPE_FRAME_WAIT_INFO};
383 XrFrameBeginInfo begin_info = {XR_TYPE_FRAME_BEGIN_INFO};
384 XrFrameState frame_state = {XR_TYPE_FRAME_STATE};
385
386 /* TODO Blocking call. Drawing should run on a separate thread to avoid interferences. */
387 CHECK_XR(xrWaitFrame(m_oxr->session, &wait_info, &frame_state),
388 "Failed to synchronize frame rates between Blender and the device.");
389
390 /* Check if we have foveation available for the current frame. */
391 m_draw_info->foveation_active = false;
392 if (m_oxr->foveation_supported) {
393 XrSpaceLocation render_gaze_location{XR_TYPE_SPACE_LOCATION};
394 CHECK_XR(xrLocateSpace(m_oxr->combined_eye_space,
395 m_oxr->view_space,
396 frame_state.predictedDisplayTime,
397 &render_gaze_location),
398 "Failed to locate combined eye space.");
399
400 m_draw_info->foveation_active = (render_gaze_location.locationFlags &
401 XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT) != 0;
402 }
403
404 CHECK_XR(xrBeginFrame(m_oxr->session, &begin_info),
405 "Failed to submit frame rendering start state.");
406
407 m_draw_info->frame_state = frame_state;
408
409 if (m_context->isDebugTimeMode()) {
410 m_draw_info->frame_begin_time = std::chrono::high_resolution_clock::now();
411 }
412}
413
415{
417 std::chrono::duration<double, std::milli> duration = std::chrono::high_resolution_clock::now() -
418 draw_info.frame_begin_time;
419 const double duration_ms = duration.count();
420 const int avg_frame_count = 8;
421 double avg_ms_tot = 0.0;
422
423 if (draw_info.last_frame_times.size() >= avg_frame_count) {
424 draw_info.last_frame_times.pop_front();
425 assert(draw_info.last_frame_times.size() == avg_frame_count - 1);
426 }
427 draw_info.last_frame_times.push_back(duration_ms);
428 for (double ms_iter : draw_info.last_frame_times) {
429 avg_ms_tot += ms_iter;
430 }
431
432 printf("VR frame render time: %.0fms - %.2f FPS (%.2f FPS 8 frames average)\n",
433 duration_ms,
434 1000.0 / duration_ms,
435 1000.0 / (avg_ms_tot / draw_info.last_frame_times.size()));
436}
437
438void GHOST_XrSession::endFrameDrawing(std::vector<XrCompositionLayerBaseHeader *> &layers)
439{
440 XrFrameEndInfo end_info = {XR_TYPE_FRAME_END_INFO};
441
442 end_info.displayTime = m_draw_info->frame_state.predictedDisplayTime;
443 end_info.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
444 end_info.layerCount = layers.size();
445 end_info.layers = layers.data();
446
447 CHECK_XR(xrEndFrame(m_oxr->session, &end_info), "Failed to submit rendered frame.");
448
449 if (m_context->isDebugTimeMode()) {
450 print_debug_timings(*m_draw_info);
451 }
452}
453
454void GHOST_XrSession::draw(void *draw_customdata)
455{
456 std::vector<XrCompositionLayerProjectionView>
457 projection_layer_views; /* Keep alive until #xrEndFrame() call! */
458 XrCompositionLayerProjection proj_layer;
459 std::vector<XrCompositionLayerBaseHeader *> layers;
460
461 beginFrameDrawing();
462
463 if (m_context->getCustomFuncs().passthrough_enabled_fn(draw_customdata)) {
464 enablePassthrough();
465 if (m_oxr->passthrough_supported) {
466 layers.push_back((XrCompositionLayerBaseHeader *)&m_oxr->passthrough_layer);
467 }
468 else {
469 m_context->getCustomFuncs().disable_passthrough_fn(draw_customdata);
470 }
471 }
472
473 if (m_draw_info->frame_state.shouldRender) {
474 proj_layer = drawLayer(projection_layer_views, draw_customdata);
475 if (layers.size() > 0) {
476 proj_layer.layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
477 }
478 layers.push_back(reinterpret_cast<XrCompositionLayerBaseHeader *>(&proj_layer));
479 }
480
481 endFrameDrawing(layers);
482}
483
484static void ghost_xr_draw_view_info_from_view(const XrView &view, GHOST_XrDrawViewInfo &r_info)
485{
486 /* Set and convert to Blender coordinate space. */
487 copy_openxr_pose_to_ghost_pose(view.pose, r_info.eye_pose);
488
489 r_info.fov.angle_left = view.fov.angleLeft;
490 r_info.fov.angle_right = view.fov.angleRight;
491 r_info.fov.angle_up = view.fov.angleUp;
492 r_info.fov.angle_down = view.fov.angleDown;
493}
494
495void GHOST_XrSession::drawView(GHOST_XrSwapchain &swapchain,
496 XrSwapchainImageBaseHeader &swapchain_image,
497 XrCompositionLayerProjectionView &r_proj_layer_view,
498 const XrSpaceLocation &view_location,
499 const XrView &view,
500 uint32_t view_idx,
501 void *draw_customdata)
502{
503 r_proj_layer_view.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW;
504 r_proj_layer_view.pose = view.pose;
505 r_proj_layer_view.fov = view.fov;
506 swapchain.updateCompositionLayerProjectViewSubImage(r_proj_layer_view.subImage);
507
508 assert(view_idx < 256);
509 GHOST_XrDrawViewInfo draw_view_info = {};
510 draw_view_info.view_idx = char(view_idx);
511 draw_view_info.swapchain_format = swapchain.getFormat();
512 draw_view_info.expects_srgb_buffer = swapchain.isBufferSRGB();
513 draw_view_info.ofsx = r_proj_layer_view.subImage.imageRect.offset.x;
514 draw_view_info.ofsy = r_proj_layer_view.subImage.imageRect.offset.y;
515 draw_view_info.width = r_proj_layer_view.subImage.imageRect.extent.width;
516 draw_view_info.height = r_proj_layer_view.subImage.imageRect.extent.height;
517 copy_openxr_pose_to_ghost_pose(view_location.pose, draw_view_info.local_pose);
519
520 /* Draw! */
521 m_context->getCustomFuncs().draw_view_fn(&draw_view_info, draw_customdata);
522 m_gpu_binding->submitToSwapchainImage(swapchain_image, draw_view_info);
523}
524
525XrCompositionLayerProjection GHOST_XrSession::drawLayer(
526 std::vector<XrCompositionLayerProjectionView> &r_proj_layer_views, void *draw_customdata)
527{
528 XrViewLocateInfo viewloc_info = {XR_TYPE_VIEW_LOCATE_INFO};
529 XrViewLocateFoveatedRenderingVARJO foveated_info{
530 XR_TYPE_VIEW_LOCATE_FOVEATED_RENDERING_VARJO, nullptr, true};
531 XrViewState view_state = {XR_TYPE_VIEW_STATE};
532 XrCompositionLayerProjection layer = {XR_TYPE_COMPOSITION_LAYER_PROJECTION};
533 XrSpaceLocation view_location{XR_TYPE_SPACE_LOCATION};
534 uint32_t view_count;
535
536 viewloc_info.viewConfigurationType = m_oxr->view_type;
537 viewloc_info.displayTime = m_draw_info->frame_state.predictedDisplayTime;
538 viewloc_info.space = m_oxr->reference_space;
539
540 if (m_draw_info->foveation_active) {
541 viewloc_info.next = &foveated_info;
542 }
543
544 CHECK_XR(xrLocateViews(m_oxr->session,
545 &viewloc_info,
546 &view_state,
547 m_oxr->views.size(),
548 &view_count,
549 m_oxr->views.data()),
550 "Failed to query frame view and projection state.");
551
552 assert(m_oxr->swapchains.size() == view_count);
553
554 CHECK_XR(
555 xrLocateSpace(
556 m_oxr->view_space, m_oxr->reference_space, viewloc_info.displayTime, &view_location),
557 "Failed to query frame view space");
558
559 r_proj_layer_views.resize(view_count);
560 std::vector<XrSwapchainImageBaseHeader *> swapchain_images;
561 swapchain_images.resize(view_count);
562
563 for (uint32_t view_idx = 0; view_idx < view_count; view_idx++) {
564 GHOST_XrSwapchain &swapchain = m_oxr->swapchains[view_idx];
565 swapchain_images[view_idx] = swapchain.acquireDrawableSwapchainImage();
566 }
567
568 m_gpu_binding->submitToSwapchainBegin();
569 for (uint32_t view_idx = 0; view_idx < view_count; view_idx++) {
570 GHOST_XrSwapchain &swapchain = m_oxr->swapchains[view_idx];
571 XrSwapchainImageBaseHeader &swapchain_image = *swapchain_images[view_idx];
572 drawView(swapchain,
573 swapchain_image,
574 r_proj_layer_views[view_idx],
575 view_location,
576 m_oxr->views[view_idx],
577 view_idx,
578 draw_customdata);
579 }
580 m_gpu_binding->submitToSwapchainEnd();
581
582 for (uint32_t view_idx = 0; view_idx < view_count; view_idx++) {
583 GHOST_XrSwapchain &swapchain = m_oxr->swapchains[view_idx];
584 swapchain.releaseImage();
585 swapchain_images[view_idx] = nullptr;
586 }
587
588 layer.space = m_oxr->reference_space;
589 layer.viewCount = r_proj_layer_views.size();
590 layer.views = r_proj_layer_views.data();
591
592 return layer;
593}
594
596{
597 return m_gpu_binding && m_gpu_binding->needsUpsideDownDrawing(*m_gpu_ctx);
598}
599 /* Drawing */
601
602/* -------------------------------------------------------------------- */
605
607{
608 if (m_oxr->session == XR_NULL_HANDLE) {
609 return false;
610 }
611 switch (m_oxr->session_state) {
612 case XR_SESSION_STATE_READY:
613 case XR_SESSION_STATE_SYNCHRONIZED:
614 case XR_SESSION_STATE_VISIBLE:
615 case XR_SESSION_STATE_FOCUSED:
616 return true;
617 default:
618 return false;
619 }
620}
621 /* State Queries */
623
624/* -------------------------------------------------------------------- */
633
634void GHOST_XrSession::bindGraphicsContext()
635{
636 const GHOST_XrCustomFuncs &custom_funcs = m_context->getCustomFuncs();
637 assert(custom_funcs.gpu_ctx_bind_fn);
638 m_gpu_ctx = static_cast<GHOST_Context *>(custom_funcs.gpu_ctx_bind_fn());
639}
640
642{
643 const GHOST_XrCustomFuncs &custom_funcs = m_context->getCustomFuncs();
644 if (custom_funcs.gpu_ctx_unbind_fn) {
645 custom_funcs.gpu_ctx_unbind_fn((GHOST_ContextHandle)m_gpu_ctx);
646 }
647 m_gpu_ctx = nullptr;
648}
649 /* Graphics Context Injection */
651
652/* -------------------------------------------------------------------- */
656
657static GHOST_XrActionSet *find_action_set(OpenXRSessionData *oxr, const char *action_set_name)
658{
659 std::map<std::string, GHOST_XrActionSet>::iterator it = oxr->action_sets.find(action_set_name);
660 if (it == oxr->action_sets.end()) {
661 return nullptr;
662 }
663 return &it->second;
664}
665
666bool GHOST_XrSession::createActionSet(const GHOST_XrActionSetInfo &info)
667{
668 std::map<std::string, GHOST_XrActionSet> &action_sets = m_oxr->action_sets;
669 if (action_sets.find(info.name) != action_sets.end()) {
670 return false;
671 }
672
673 XrInstance instance = m_context->getInstance();
674
675 action_sets.emplace(
676 std::piecewise_construct, std::make_tuple(info.name), std::make_tuple(instance, info));
677
678 return true;
679}
680
681void GHOST_XrSession::destroyActionSet(const char *action_set_name)
682{
683 std::map<std::string, GHOST_XrActionSet> &action_sets = m_oxr->action_sets;
684 /* It's possible nothing is removed. */
685 action_sets.erase(action_set_name);
686}
687
688bool GHOST_XrSession::createActions(const char *action_set_name,
689 uint32_t count,
690 const GHOST_XrActionInfo *infos)
691{
692 GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name);
693 if (action_set == nullptr) {
694 return false;
695 }
696
697 XrInstance instance = m_context->getInstance();
698
699 for (uint32_t i = 0; i < count; ++i) {
700 if (!action_set->createAction(instance, infos[i])) {
701 return false;
702 }
703 }
704
705 return true;
706}
707
708void GHOST_XrSession::destroyActions(const char *action_set_name,
709 uint32_t count,
710 const char *const *action_names)
711{
712 GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name);
713 if (action_set == nullptr) {
714 return;
715 }
716
717 for (uint32_t i = 0; i < count; ++i) {
718 action_set->destroyAction(action_names[i]);
719 }
720}
721
722bool GHOST_XrSession::createActionBindings(const char *action_set_name,
723 uint32_t count,
724 const GHOST_XrActionProfileInfo *infos)
725{
726 GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name);
727 if (action_set == nullptr) {
728 return false;
729 }
730
731 XrInstance instance = m_context->getInstance();
732 XrSession session = m_oxr->session;
733
734 for (uint32_t profile_idx = 0; profile_idx < count; ++profile_idx) {
735 const GHOST_XrActionProfileInfo &info = infos[profile_idx];
736
737 GHOST_XrAction *action = action_set->findAction(info.action_name);
738 if (action == nullptr) {
739 continue;
740 }
741
742 action->createBinding(instance, session, info);
743 }
744
745 return true;
746}
747
748void GHOST_XrSession::destroyActionBindings(const char *action_set_name,
749 uint32_t count,
750 const char *const *action_names,
751 const char *const *profile_paths)
752{
753 GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name);
754 if (action_set == nullptr) {
755 return;
756 }
757
758 for (uint32_t i = 0; i < count; ++i) {
759 GHOST_XrAction *action = action_set->findAction(action_names[i]);
760 if (action == nullptr) {
761 continue;
762 }
763
764 action->destroyBinding(profile_paths[i]);
765 }
766}
767
769{
770 /* Suggest action bindings for all action sets. */
771 std::map<XrPath, std::vector<XrActionSuggestedBinding>> profile_bindings;
772 for (auto &[name, action_set] : m_oxr->action_sets) {
773 action_set.getBindings(profile_bindings);
774 }
775
776 if (profile_bindings.size() < 1) {
777 return false;
778 }
779
780 XrInteractionProfileSuggestedBinding bindings_info{
781 XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING};
782 XrInstance instance = m_context->getInstance();
783
784 for (auto &[profile, bindings] : profile_bindings) {
785 bindings_info.interactionProfile = profile;
786 bindings_info.countSuggestedBindings = uint32_t(bindings.size());
787 bindings_info.suggestedBindings = bindings.data();
788
789 CHECK_XR(xrSuggestInteractionProfileBindings(instance, &bindings_info),
790 "Failed to suggest interaction profile bindings.");
791 }
792
793 /* Attach action sets. */
794 XrSessionActionSetsAttachInfo attach_info{XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO};
795 attach_info.countActionSets = uint32_t(m_oxr->action_sets.size());
796
797 /* Create an aligned copy of the action sets to pass to xrAttachSessionActionSets(). */
798 std::vector<XrActionSet> action_sets(attach_info.countActionSets);
799 uint32_t i = 0;
800 for (auto &[name, action_set] : m_oxr->action_sets) {
801 action_sets[i++] = action_set.getActionSet();
802 }
803 attach_info.actionSets = action_sets.data();
804
805 CHECK_XR(xrAttachSessionActionSets(m_oxr->session, &attach_info),
806 "Failed to attach XR action sets.");
807
808 return true;
809}
810
811bool GHOST_XrSession::syncActions(const char *action_set_name)
812{
813 std::map<std::string, GHOST_XrActionSet> &action_sets = m_oxr->action_sets;
814
815 XrActionsSyncInfo sync_info{XR_TYPE_ACTIONS_SYNC_INFO};
816 sync_info.countActiveActionSets = (action_set_name != nullptr) ? 1 :
817 uint32_t(action_sets.size());
818 if (sync_info.countActiveActionSets < 1) {
819 return false;
820 }
821
822 std::vector<XrActiveActionSet> active_action_sets(sync_info.countActiveActionSets);
823 GHOST_XrActionSet *action_set = nullptr;
824
825 if (action_set_name != nullptr) {
826 action_set = find_action_set(m_oxr.get(), action_set_name);
827 if (action_set == nullptr) {
828 return false;
829 }
830
831 XrActiveActionSet &active_action_set = active_action_sets[0];
832 active_action_set.actionSet = action_set->getActionSet();
833 active_action_set.subactionPath = XR_NULL_PATH;
834 }
835 else {
836 uint32_t i = 0;
837 for (auto &[name, action_set] : action_sets) {
838 XrActiveActionSet &active_action_set = active_action_sets[i++];
839 active_action_set.actionSet = action_set.getActionSet();
840 active_action_set.subactionPath = XR_NULL_PATH;
841 }
842 }
843 sync_info.activeActionSets = active_action_sets.data();
844
845 CHECK_XR(xrSyncActions(m_oxr->session, &sync_info), "Failed to synchronize XR actions.");
846
847 /* Update action states (i.e. Blender custom data). */
848 XrSession session = m_oxr->session;
849 XrSpace reference_space = m_oxr->reference_space;
850 const XrTime &predicted_display_time = m_draw_info->frame_state.predictedDisplayTime;
851
852 if (action_set != nullptr) {
853 action_set->updateStates(session, reference_space, predicted_display_time);
854 }
855 else {
856 for (auto &[name, action_set] : action_sets) {
857 action_set.updateStates(session, reference_space, predicted_display_time);
858 }
859 }
860
861 return true;
862}
863
864bool GHOST_XrSession::applyHapticAction(const char *action_set_name,
865 const char *action_name,
866 const char *subaction_path,
867 const int64_t &duration,
868 const float &frequency,
869 const float &amplitude)
870{
871 GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name);
872 if (action_set == nullptr) {
873 return false;
874 }
875
876 GHOST_XrAction *action = action_set->findAction(action_name);
877 if (action == nullptr) {
878 return false;
879 }
880
881 action->applyHapticFeedback(
882 m_oxr->session, action_name, subaction_path, duration, frequency, amplitude);
883
884 return true;
885}
886
887void GHOST_XrSession::stopHapticAction(const char *action_set_name,
888 const char *action_name,
889 const char *subaction_path)
890{
891 GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name);
892 if (action_set == nullptr) {
893 return;
894 }
895
896 GHOST_XrAction *action = action_set->findAction(action_name);
897 if (action == nullptr) {
898 return;
899 }
900
901 action->stopHapticFeedback(m_oxr->session, action_name, subaction_path);
902}
903
904void *GHOST_XrSession::getActionSetCustomdata(const char *action_set_name)
905{
906 GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name);
907 if (action_set == nullptr) {
908 return nullptr;
909 }
910
911 return action_set->getCustomdata();
912}
913
914void *GHOST_XrSession::getActionCustomdata(const char *action_set_name, const char *action_name)
915{
916 GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name);
917 if (action_set == nullptr) {
918 return nullptr;
919 }
920
921 GHOST_XrAction *action = action_set->findAction(action_name);
922 if (action == nullptr) {
923 return nullptr;
924 }
925
926 return action->getCustomdata();
927}
928
929uint32_t GHOST_XrSession::getActionCount(const char *action_set_name)
930{
931 const GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name);
932 if (action_set == nullptr) {
933 return 0;
934 }
935
936 return action_set->getActionCount();
937}
938
939void GHOST_XrSession::getActionCustomdataArray(const char *action_set_name,
940 void **r_customdata_array)
941{
942 GHOST_XrActionSet *action_set = find_action_set(m_oxr.get(), action_set_name);
943 if (action_set == nullptr) {
944 return;
945 }
946
947 action_set->getActionCustomdataArray(r_customdata_array);
948}
949 /* Actions */
951
952/* -------------------------------------------------------------------- */
956
957bool GHOST_XrSession::loadControllerModel(const char *subaction_path)
958{
959 if (!m_context->isExtensionEnabled(XR_MSFT_CONTROLLER_MODEL_EXTENSION_NAME)) {
960 return false;
961 }
962
963 XrSession session = m_oxr->session;
964 std::map<std::string, GHOST_XrControllerModel> &controller_models = m_oxr->controller_models;
965 std::map<std::string, GHOST_XrControllerModel>::iterator it = controller_models.find(
966 subaction_path);
967
968 if (it == controller_models.end()) {
969 XrInstance instance = m_context->getInstance();
970 it = controller_models
971 .emplace(std::piecewise_construct,
972 std::make_tuple(subaction_path),
973 std::make_tuple(instance, subaction_path))
974 .first;
975 }
976
977 it->second.load(session);
978
979 return true;
980}
981
982void GHOST_XrSession::unloadControllerModel(const char *subaction_path)
983{
984 std::map<std::string, GHOST_XrControllerModel> &controller_models = m_oxr->controller_models;
985 /* It's possible nothing is removed. */
986 controller_models.erase(subaction_path);
987}
988
990{
991 XrSession session = m_oxr->session;
992 std::map<std::string, GHOST_XrControllerModel>::iterator it = m_oxr->controller_models.find(
993 subaction_path);
994 if (it == m_oxr->controller_models.end()) {
995 return false;
996 }
997
998 it->second.updateComponents(session);
999
1000 return true;
1001}
1002
1003bool GHOST_XrSession::getControllerModelData(const char *subaction_path,
1004 GHOST_XrControllerModelData &r_data)
1005{
1006 std::map<std::string, GHOST_XrControllerModel>::iterator it = m_oxr->controller_models.find(
1007 subaction_path);
1008 if (it == m_oxr->controller_models.end()) {
1009 return false;
1010 }
1011
1012 it->second.getData(r_data);
1013
1014 return true;
1015}
1016 /* Controller Model */
1018
1019/* -------------------------------------------------------------------- */
1023
1024static PFN_xrCreatePassthroughFB g_xrCreatePassthroughFB = nullptr;
1025static PFN_xrCreatePassthroughLayerFB g_xrCreatePassthroughLayerFB = nullptr;
1026static PFN_xrPassthroughStartFB g_xrPassthroughStartFB = nullptr;
1027static PFN_xrPassthroughLayerResumeFB g_xrPassthroughLayerResumeFB = nullptr;
1028
1029static void init_passthrough_extension_functions(XrInstance instance)
1030{
1031 if (g_xrCreatePassthroughFB == nullptr) {
1032 INIT_EXTENSION_FUNCTION(xrCreatePassthroughFB);
1033 }
1034 if (g_xrCreatePassthroughLayerFB == nullptr) {
1035 INIT_EXTENSION_FUNCTION(xrCreatePassthroughLayerFB);
1036 }
1037 if (g_xrPassthroughStartFB == nullptr) {
1038 INIT_EXTENSION_FUNCTION(xrPassthroughStartFB);
1039 }
1040 if (g_xrPassthroughLayerResumeFB == nullptr) {
1041 INIT_EXTENSION_FUNCTION(xrPassthroughLayerResumeFB);
1042 }
1043}
1044
1045void GHOST_XrSession::enablePassthrough()
1046{
1047 if (!m_context->isExtensionEnabled(XR_FB_PASSTHROUGH_EXTENSION_NAME)) {
1048 m_oxr->passthrough_supported = false;
1049 return;
1050 }
1051
1052 if (m_oxr->passthrough_layer.layerHandle != XR_NULL_HANDLE) {
1053 return; /* Already initialized */
1054 }
1055
1056 init_passthrough_extension_functions(m_context->getInstance());
1057
1058 XrResult result;
1059
1060 XrPassthroughCreateInfoFB passthrough_create_info = {};
1061 passthrough_create_info.type = XR_TYPE_PASSTHROUGH_CREATE_INFO_FB;
1062 passthrough_create_info.next = nullptr;
1063 passthrough_create_info.flags |= XR_PASSTHROUGH_IS_RUNNING_AT_CREATION_BIT_FB;
1064
1065 XrPassthroughFB passthrough_handle;
1066 result = g_xrCreatePassthroughFB(m_oxr->session, &passthrough_create_info, &passthrough_handle);
1067
1068 XrPassthroughLayerCreateInfoFB passthrough_layer_create_info;
1069 passthrough_layer_create_info.type = XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB;
1070 passthrough_layer_create_info.next = nullptr;
1071 passthrough_layer_create_info.passthrough = passthrough_handle;
1072 passthrough_layer_create_info.flags |= XR_PASSTHROUGH_IS_RUNNING_AT_CREATION_BIT_FB;
1073 passthrough_layer_create_info.purpose = XR_PASSTHROUGH_LAYER_PURPOSE_RECONSTRUCTION_FB;
1074
1075 XrPassthroughLayerFB passthrough_layer_handle;
1077 m_oxr->session, &passthrough_layer_create_info, &passthrough_layer_handle);
1078
1079 g_xrPassthroughStartFB(passthrough_handle);
1080 g_xrPassthroughLayerResumeFB(passthrough_layer_handle);
1081
1082 m_oxr->passthrough_layer.type = XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_FB;
1083 m_oxr->passthrough_layer.next = nullptr;
1084 m_oxr->passthrough_layer.flags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
1085 m_oxr->passthrough_layer.space = nullptr;
1086 m_oxr->passthrough_layer.layerHandle = passthrough_layer_handle;
1087
1088 m_oxr->passthrough_supported = (result == XR_SUCCESS);
1089}
1090 /* Meta Quest Passthrough */
static AppView * view
GHOST C-API function and type declarations.
std::unique_ptr< GHOST_IXrGraphicsBinding > GHOST_XrGraphicsBindingCreateFromType(GHOST_TXrGraphicsBinding type, GHOST_Context &context)
static void init_passthrough_extension_functions(XrInstance instance)
static void create_reference_spaces(OpenXRSessionData &oxr, const GHOST_XrPose &base_pose, bool isDebugMode)
static void print_debug_timings(GHOST_XrDrawInfo &draw_info)
static void ghost_xr_draw_view_info_from_view(const XrView &view, GHOST_XrDrawViewInfo &r_info)
static PFN_xrCreatePassthroughLayerFB g_xrCreatePassthroughLayerFB
static PFN_xrPassthroughLayerResumeFB g_xrPassthroughLayerResumeFB
static PFN_xrCreatePassthroughFB g_xrCreatePassthroughFB
static PFN_xrPassthroughStartFB g_xrPassthroughStartFB
static GHOST_XrActionSet * find_action_set(OpenXRSessionData *oxr, const char *action_set_name)
void copy_openxr_pose_to_ghost_pose(const XrPosef &oxr_pose, GHOST_XrPose &r_ghost_pose)
#define INIT_EXTENSION_FUNCTION(name)
#define CHECK_XR_ASSERT(call)
#define CHECK_XR(call, error_msg)
long long int int64_t
void updateStates(XrSession session, XrSpace reference_space, const XrTime &predicted_display_time)
GHOST_XrAction * findAction(const char *action_name)
bool createAction(XrInstance instance, const GHOST_XrActionInfo &info)
XrActionSet getActionSet() const
void getActionCustomdataArray(void **r_customdata_array)
uint32_t getActionCount() const
void destroyAction(const char *action_name)
bool createBinding(XrInstance instance, XrSession session, const GHOST_XrActionProfileInfo &info)
void destroyBinding(const char *profile_path)
void applyHapticFeedback(XrSession session, const char *action_name, const char *subaction_path_str, const int64_t &duration, const float &frequency, const float &amplitude)
void stopHapticFeedback(XrSession session, const char *action_name, const char *subaction_path_str)
Main GHOST container to manage OpenXR through.
XrInstance getInstance() const
const GHOST_XrCustomFuncs & getCustomFuncs() const
bool isExtensionEnabled(const char *ext) const
void destroyActionBindings(const char *action_set_name, uint32_t count, const char *const *action_names, const char *const *profile_paths)
void destroyActionSet(const char *action_set_name)
void draw(void *draw_customdata)
bool createActionSet(const GHOST_XrActionSetInfo &info)
void * getActionCustomdata(const char *action_set_name, const char *action_name)
void unloadControllerModel(const char *subaction_path)
void * getActionSetCustomdata(const char *action_set_name)
bool updateControllerModelComponents(const char *subaction_path)
bool createActionBindings(const char *action_set_name, uint32_t count, const GHOST_XrActionProfileInfo *infos)
GHOST_XrSession(GHOST_XrContext &xr_context)
void stopHapticAction(const char *action_set_name, const char *action_name, const char *subaction_path)
bool isRunning() const
LifeExpectancy handleStateChangeEvent(const XrEventDataSessionStateChanged &lifecycle)
void getActionCustomdataArray(const char *action_set_name, void **r_customdata_array)
bool loadControllerModel(const char *subaction_path)
bool syncActions(const char *action_set_name=nullptr)
bool needsUpsideDownDrawing() const
void start(const GHOST_XrSessionBeginInfo *begin_info)
void destroyActions(const char *action_set_name, uint32_t count, const char *const *action_names)
bool applyHapticAction(const char *action_set_name, const char *action_name, const char *subaction_path, const int64_t &duration, const float &frequency, const float &amplitude)
uint32_t getActionCount(const char *action_set_name)
bool getControllerModelData(const char *subaction_path, GHOST_XrControllerModelData &r_data)
bool createActions(const char *action_set_name, uint32_t count, const GHOST_XrActionInfo *infos)
GHOST_TXrSwapchainFormat getFormat() const
void updateCompositionLayerProjectViewSubImage(XrSwapchainSubImage &r_sub_image)
XrSwapchainImageBaseHeader * acquireDrawableSwapchainImage()
#define assert(assertion)
#define printf(...)
int count
GHOST_XrGraphicsContextUnbindFn gpu_ctx_unbind_fn
GHOST_XrGraphicsContextBindFn gpu_ctx_bind_fn
std::chrono::high_resolution_clock::time_point frame_begin_time
XrFrameState frame_state
std::list< double > last_frame_times
XrCompositionLayerPassthroughFB passthrough_layer
std::map< std::string, GHOST_XrControllerModel > controller_models
XrViewConfigurationType view_type
std::map< std::string, GHOST_XrActionSet > action_sets
XrSessionState session_state
std::vector< GHOST_XrSwapchain > swapchains
std::vector< XrView > views
i
Definition text_draw.cc:230