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