Blender  V2.93
GHOST_XrSession.cpp
Go to the documentation of this file.
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  */
16 
21 #include <algorithm>
22 #include <cassert>
23 #include <chrono>
24 #include <cstdio>
25 #include <list>
26 #include <sstream>
27 
28 #include "GHOST_C-api.h"
29 
31 #include "GHOST_XrContext.h"
32 #include "GHOST_XrException.h"
33 #include "GHOST_XrSwapchain.h"
34 #include "GHOST_Xr_intern.h"
35 
36 #include "GHOST_XrSession.h"
37 
39  XrSystemId system_id = XR_NULL_SYSTEM_ID;
40  XrSession session = XR_NULL_HANDLE;
41  XrSessionState session_state = XR_SESSION_STATE_UNKNOWN;
42 
43  /* Only stereo rendering supported now. */
44  const XrViewConfigurationType view_type = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
45  XrSpace reference_space;
46  XrSpace view_space;
47  std::vector<XrView> views;
48  std::vector<GHOST_XrSwapchain> swapchains;
49 };
50 
52  XrFrameState frame_state;
53 
55  std::chrono::high_resolution_clock::time_point frame_begin_time;
56  /* Time previous frames took for rendering (in ms). */
57  std::list<double> last_frame_times;
58 };
59 
60 /* -------------------------------------------------------------------- */
66  : m_context(&xr_context), m_oxr(std::make_unique<OpenXRSessionData>())
67 {
68 }
69 
71 {
73 
74  m_oxr->swapchains.clear();
75 
76  if (m_oxr->reference_space != XR_NULL_HANDLE) {
77  CHECK_XR_ASSERT(xrDestroySpace(m_oxr->reference_space));
78  }
79  if (m_oxr->view_space != XR_NULL_HANDLE) {
80  CHECK_XR_ASSERT(xrDestroySpace(m_oxr->view_space));
81  }
82  if (m_oxr->session != XR_NULL_HANDLE) {
83  CHECK_XR_ASSERT(xrDestroySession(m_oxr->session));
84  }
85 
86  m_oxr->session = XR_NULL_HANDLE;
87  m_oxr->session_state = XR_SESSION_STATE_UNKNOWN;
88 
90 }
91 
96 void GHOST_XrSession::initSystem()
97 {
98  assert(m_context->getInstance() != XR_NULL_HANDLE);
99  assert(m_oxr->system_id == XR_NULL_SYSTEM_ID);
100 
101  XrSystemGetInfo system_info = {};
102  system_info.type = XR_TYPE_SYSTEM_GET_INFO;
103  system_info.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
104 
105  CHECK_XR(xrGetSystem(m_context->getInstance(), &system_info, &m_oxr->system_id),
106  "Failed to get device information. Is a device plugged in?");
107 }
108  /* Create, Initialize and Destruct */
110 
111 /* -------------------------------------------------------------------- */
116 static void create_reference_spaces(OpenXRSessionData &oxr, const GHOST_XrPose &base_pose)
117 {
118  XrReferenceSpaceCreateInfo create_info = {XR_TYPE_REFERENCE_SPACE_CREATE_INFO};
119  create_info.poseInReferenceSpace.orientation.w = 1.0f;
120 
121  create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
122 #if 0
123 /* TODO
124  *
125  * Proper reference space set up is not supported yet. We simply hand OpenXR
126  * the global space as reference space and apply its pose onto the active
127  * camera matrix to get a basic viewing experience going. If there's no active
128  * camera with stick to the world origin.
129  *
130  * Once we have proper reference space set up (i.e. a way to define origin, up-
131  * direction and an initial view rotation perpendicular to the up-direction),
132  * we can hand OpenXR a proper reference pose/space.
133  */
134  create_info.poseInReferenceSpace.position.x = base_pose->position[0];
135  create_info.poseInReferenceSpace.position.y = base_pose->position[1];
136  create_info.poseInReferenceSpace.position.z = base_pose->position[2];
137  create_info.poseInReferenceSpace.orientation.x = base_pose->orientation_quat[1];
138  create_info.poseInReferenceSpace.orientation.y = base_pose->orientation_quat[2];
139  create_info.poseInReferenceSpace.orientation.z = base_pose->orientation_quat[3];
140  create_info.poseInReferenceSpace.orientation.w = base_pose->orientation_quat[0];
141 #else
142  (void)base_pose;
143 #endif
144 
145  CHECK_XR(xrCreateReferenceSpace(oxr.session, &create_info, &oxr.reference_space),
146  "Failed to create reference space.");
147 
148  create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW;
149  CHECK_XR(xrCreateReferenceSpace(oxr.session, &create_info, &oxr.view_space),
150  "Failed to create view reference space.");
151 }
152 
153 void GHOST_XrSession::start(const GHOST_XrSessionBeginInfo *begin_info)
154 {
155  assert(m_context->getInstance() != XR_NULL_HANDLE);
156  assert(m_oxr->session == XR_NULL_HANDLE);
157  if (m_context->getCustomFuncs().gpu_ctx_bind_fn == nullptr) {
158  throw GHOST_XrException(
159  "Invalid API usage: No way to bind graphics context to the XR session. Call "
160  "GHOST_XrGraphicsContextBindFuncs() with valid parameters before starting the "
161  "session (through GHOST_XrSessionStart()).");
162  }
163 
164  initSystem();
165 
166  bindGraphicsContext();
167  if (m_gpu_ctx == nullptr) {
168  throw GHOST_XrException(
169  "Invalid API usage: No graphics context returned through the callback set with "
170  "GHOST_XrGraphicsContextBindFuncs(). This is required for session starting (through "
171  "GHOST_XrSessionStart()).");
172  }
173 
174  std::string requirement_str;
176  *m_gpu_ctx);
177  if (!m_gpu_binding->checkVersionRequirements(
178  *m_gpu_ctx, m_context->getInstance(), m_oxr->system_id, &requirement_str)) {
179  std::ostringstream strstream;
180  strstream << "Available graphics context version does not meet the following requirements: "
181  << requirement_str;
182  throw GHOST_XrException(strstream.str().c_str());
183  }
184  m_gpu_binding->initFromGhostContext(*m_gpu_ctx);
185 
186  XrSessionCreateInfo create_info = {};
187  create_info.type = XR_TYPE_SESSION_CREATE_INFO;
188  create_info.systemId = m_oxr->system_id;
189  create_info.next = &m_gpu_binding->oxr_binding;
190 
191  CHECK_XR(xrCreateSession(m_context->getInstance(), &create_info, &m_oxr->session),
192  "Failed to create VR session. The OpenXR runtime may have additional requirements for "
193  "the graphics driver that are not met. Other causes are possible too however.\nTip: "
194  "The --debug-xr command line option for Blender might allow the runtime to output "
195  "detailed error information to the command line.");
196 
197  prepareDrawing();
198  create_reference_spaces(*m_oxr, begin_info->base_pose);
199 }
200 
202 {
203  xrRequestExitSession(m_oxr->session);
204 }
205 
206 void GHOST_XrSession::beginSession()
207 {
208  XrSessionBeginInfo begin_info = {XR_TYPE_SESSION_BEGIN_INFO};
209  begin_info.primaryViewConfigurationType = m_oxr->view_type;
210  CHECK_XR(xrBeginSession(m_oxr->session, &begin_info), "Failed to cleanly begin the VR session.");
211 }
212 
213 void GHOST_XrSession::endSession()
214 {
215  assert(m_oxr->session != XR_NULL_HANDLE);
216  CHECK_XR(xrEndSession(m_oxr->session), "Failed to cleanly end the VR session.");
217 }
218 
220  const XrEventDataSessionStateChanged &lifecycle)
221 {
222  m_oxr->session_state = lifecycle.state;
223 
224  /* Runtime may send events for apparently destroyed session. Our handle should be NULL then. */
225  assert(m_oxr->session == XR_NULL_HANDLE || m_oxr->session == lifecycle.session);
226 
227  switch (lifecycle.state) {
228  case XR_SESSION_STATE_READY: {
229  beginSession();
230  break;
231  }
232  case XR_SESSION_STATE_STOPPING:
233  endSession();
234  break;
235  case XR_SESSION_STATE_EXITING:
236  case XR_SESSION_STATE_LOSS_PENDING:
237  return SESSION_DESTROY;
238  default:
239  break;
240  }
241 
242  return SESSION_KEEP_ALIVE;
243 } /* State Management */
245 
246 /* -------------------------------------------------------------------- */
251 void GHOST_XrSession::prepareDrawing()
252 {
253  std::vector<XrViewConfigurationView> view_configs;
254  uint32_t view_count;
255 
256  CHECK_XR(
257  xrEnumerateViewConfigurationViews(
258  m_context->getInstance(), m_oxr->system_id, m_oxr->view_type, 0, &view_count, nullptr),
259  "Failed to get count of view configurations.");
260  view_configs.resize(view_count, {XR_TYPE_VIEW_CONFIGURATION_VIEW});
261  CHECK_XR(xrEnumerateViewConfigurationViews(m_context->getInstance(),
262  m_oxr->system_id,
263  m_oxr->view_type,
264  view_configs.size(),
265  &view_count,
266  view_configs.data()),
267  "Failed to get count of view configurations.");
268 
269  for (const XrViewConfigurationView &view_config : view_configs) {
270  m_oxr->swapchains.emplace_back(*m_gpu_binding, m_oxr->session, view_config);
271  }
272 
273  m_oxr->views.resize(view_count, {XR_TYPE_VIEW});
274 
275  m_draw_info = std::make_unique<GHOST_XrDrawInfo>();
276 }
277 
278 void GHOST_XrSession::beginFrameDrawing()
279 {
280  XrFrameWaitInfo wait_info = {XR_TYPE_FRAME_WAIT_INFO};
281  XrFrameBeginInfo begin_info = {XR_TYPE_FRAME_BEGIN_INFO};
282  XrFrameState frame_state = {XR_TYPE_FRAME_STATE};
283 
284  /* TODO Blocking call. Drawing should run on a separate thread to avoid interferences. */
285  CHECK_XR(xrWaitFrame(m_oxr->session, &wait_info, &frame_state),
286  "Failed to synchronize frame rates between Blender and the device.");
287 
288  CHECK_XR(xrBeginFrame(m_oxr->session, &begin_info),
289  "Failed to submit frame rendering start state.");
290 
291  m_draw_info->frame_state = frame_state;
292 
293  if (m_context->isDebugTimeMode()) {
294  m_draw_info->frame_begin_time = std::chrono::high_resolution_clock::now();
295  }
296 }
297 
298 static void print_debug_timings(GHOST_XrDrawInfo &draw_info)
299 {
301  std::chrono::duration<double, std::milli> duration = std::chrono::high_resolution_clock::now() -
302  draw_info.frame_begin_time;
303  const double duration_ms = duration.count();
304  const int avg_frame_count = 8;
305  double avg_ms_tot = 0.0;
306 
307  if (draw_info.last_frame_times.size() >= avg_frame_count) {
308  draw_info.last_frame_times.pop_front();
309  assert(draw_info.last_frame_times.size() == avg_frame_count - 1);
310  }
311  draw_info.last_frame_times.push_back(duration_ms);
312  for (double ms_iter : draw_info.last_frame_times) {
313  avg_ms_tot += ms_iter;
314  }
315 
316  printf("VR frame render time: %.0fms - %.2f FPS (%.2f FPS 8 frames average)\n",
317  duration_ms,
318  1000.0 / duration_ms,
319  1000.0 / (avg_ms_tot / draw_info.last_frame_times.size()));
320 }
321 
322 void GHOST_XrSession::endFrameDrawing(std::vector<XrCompositionLayerBaseHeader *> &layers)
323 {
324  XrFrameEndInfo end_info = {XR_TYPE_FRAME_END_INFO};
325 
326  end_info.displayTime = m_draw_info->frame_state.predictedDisplayTime;
327  end_info.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
328  end_info.layerCount = layers.size();
329  end_info.layers = layers.data();
330 
331  CHECK_XR(xrEndFrame(m_oxr->session, &end_info), "Failed to submit rendered frame.");
332 
333  if (m_context->isDebugTimeMode()) {
334  print_debug_timings(*m_draw_info);
335  }
336 }
337 
338 void GHOST_XrSession::draw(void *draw_customdata)
339 {
340  std::vector<XrCompositionLayerProjectionView>
341  projection_layer_views; /* Keep alive until #xrEndFrame() call! */
342  XrCompositionLayerProjection proj_layer;
343  std::vector<XrCompositionLayerBaseHeader *> layers;
344 
345  beginFrameDrawing();
346 
347  if (m_draw_info->frame_state.shouldRender) {
348  proj_layer = drawLayer(projection_layer_views, draw_customdata);
349  layers.push_back(reinterpret_cast<XrCompositionLayerBaseHeader *>(&proj_layer));
350  }
351 
352  endFrameDrawing(layers);
353 }
354 
355 static void copy_openxr_pose_to_ghost_pose(const XrPosef &oxr_pose, GHOST_XrPose &r_ghost_pose)
356 {
357  /* Set and convert to Blender coordinate space. */
358  r_ghost_pose.position[0] = oxr_pose.position.x;
359  r_ghost_pose.position[1] = oxr_pose.position.y;
360  r_ghost_pose.position[2] = oxr_pose.position.z;
361  r_ghost_pose.orientation_quat[0] = oxr_pose.orientation.w;
362  r_ghost_pose.orientation_quat[1] = oxr_pose.orientation.x;
363  r_ghost_pose.orientation_quat[2] = oxr_pose.orientation.y;
364  r_ghost_pose.orientation_quat[3] = oxr_pose.orientation.z;
365 }
366 
367 static void ghost_xr_draw_view_info_from_view(const XrView &view, GHOST_XrDrawViewInfo &r_info)
368 {
369  /* Set and convert to Blender coordinate space. */
370  copy_openxr_pose_to_ghost_pose(view.pose, r_info.eye_pose);
371 
372  r_info.fov.angle_left = view.fov.angleLeft;
373  r_info.fov.angle_right = view.fov.angleRight;
374  r_info.fov.angle_up = view.fov.angleUp;
375  r_info.fov.angle_down = view.fov.angleDown;
376 }
377 
378 void GHOST_XrSession::drawView(GHOST_XrSwapchain &swapchain,
379  XrCompositionLayerProjectionView &r_proj_layer_view,
380  XrSpaceLocation &view_location,
381  XrView &view,
382  void *draw_customdata)
383 {
384  XrSwapchainImageBaseHeader *swapchain_image = swapchain.acquireDrawableSwapchainImage();
385  GHOST_XrDrawViewInfo draw_view_info = {};
386 
387  r_proj_layer_view.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW;
388  r_proj_layer_view.pose = view.pose;
389  r_proj_layer_view.fov = view.fov;
390  swapchain.updateCompositionLayerProjectViewSubImage(r_proj_layer_view.subImage);
391 
392  draw_view_info.expects_srgb_buffer = swapchain.isBufferSRGB();
393  draw_view_info.ofsx = r_proj_layer_view.subImage.imageRect.offset.x;
394  draw_view_info.ofsy = r_proj_layer_view.subImage.imageRect.offset.y;
395  draw_view_info.width = r_proj_layer_view.subImage.imageRect.extent.width;
396  draw_view_info.height = r_proj_layer_view.subImage.imageRect.extent.height;
397  copy_openxr_pose_to_ghost_pose(view_location.pose, draw_view_info.local_pose);
398  ghost_xr_draw_view_info_from_view(view, draw_view_info);
399 
400  /* Draw! */
401  m_context->getCustomFuncs().draw_view_fn(&draw_view_info, draw_customdata);
402  m_gpu_binding->submitToSwapchainImage(*swapchain_image, draw_view_info);
403 
404  swapchain.releaseImage();
405 }
406 
407 XrCompositionLayerProjection GHOST_XrSession::drawLayer(
408  std::vector<XrCompositionLayerProjectionView> &r_proj_layer_views, void *draw_customdata)
409 {
410  XrViewLocateInfo viewloc_info = {XR_TYPE_VIEW_LOCATE_INFO};
411  XrViewState view_state = {XR_TYPE_VIEW_STATE};
412  XrCompositionLayerProjection layer = {XR_TYPE_COMPOSITION_LAYER_PROJECTION};
413  XrSpaceLocation view_location{XR_TYPE_SPACE_LOCATION};
414  uint32_t view_count;
415 
416  viewloc_info.viewConfigurationType = m_oxr->view_type;
417  viewloc_info.displayTime = m_draw_info->frame_state.predictedDisplayTime;
418  viewloc_info.space = m_oxr->reference_space;
419 
420  CHECK_XR(xrLocateViews(m_oxr->session,
421  &viewloc_info,
422  &view_state,
423  m_oxr->views.size(),
424  &view_count,
425  m_oxr->views.data()),
426  "Failed to query frame view and projection state.");
427  assert(m_oxr->swapchains.size() == view_count);
428 
429  CHECK_XR(
430  xrLocateSpace(
431  m_oxr->view_space, m_oxr->reference_space, viewloc_info.displayTime, &view_location),
432  "Failed to query frame view space");
433 
434  r_proj_layer_views.resize(view_count);
435 
436  for (uint32_t view_idx = 0; view_idx < view_count; view_idx++) {
437  drawView(m_oxr->swapchains[view_idx],
438  r_proj_layer_views[view_idx],
439  view_location,
440  m_oxr->views[view_idx],
441  draw_customdata);
442  }
443 
444  layer.space = m_oxr->reference_space;
445  layer.viewCount = r_proj_layer_views.size();
446  layer.views = r_proj_layer_views.data();
447 
448  return layer;
449 }
450 
452 {
453  return m_gpu_binding && m_gpu_binding->needsUpsideDownDrawing(*m_gpu_ctx);
454 }
455  /* Drawing */
457 
458 /* -------------------------------------------------------------------- */
464 {
465  if (m_oxr->session == XR_NULL_HANDLE) {
466  return false;
467  }
468  switch (m_oxr->session_state) {
469  case XR_SESSION_STATE_READY:
470  case XR_SESSION_STATE_SYNCHRONIZED:
471  case XR_SESSION_STATE_VISIBLE:
472  case XR_SESSION_STATE_FOCUSED:
473  return true;
474  default:
475  return false;
476  }
477 }
478  /* State Queries */
480 
481 /* -------------------------------------------------------------------- */
491 void GHOST_XrSession::bindGraphicsContext()
492 {
493  const GHOST_XrCustomFuncs &custom_funcs = m_context->getCustomFuncs();
494  assert(custom_funcs.gpu_ctx_bind_fn);
495  m_gpu_ctx = static_cast<GHOST_Context *>(custom_funcs.gpu_ctx_bind_fn());
496 }
497 
499 {
500  const GHOST_XrCustomFuncs &custom_funcs = m_context->getCustomFuncs();
501  if (custom_funcs.gpu_ctx_unbind_fn) {
502  custom_funcs.gpu_ctx_unbind_fn((GHOST_ContextHandle)m_gpu_ctx);
503  }
504  m_gpu_ctx = nullptr;
505 }
506  /* Graphics Context Injection */
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 print_debug_timings(GHOST_XrDrawInfo &draw_info)
static void ghost_xr_draw_view_info_from_view(const XrView &view, GHOST_XrDrawViewInfo &r_info)
static void create_reference_spaces(OpenXRSessionData &oxr, const GHOST_XrPose &base_pose)
static 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)
Main GHOST container to manage OpenXR through.
XrInstance getInstance() const
GHOST_TXrGraphicsBinding getGraphicsBindingType() const
const GHOST_XrCustomFuncs & getCustomFuncs() const
bool isDebugTimeMode() const
void draw(void *draw_customdata)
GHOST_XrSession(GHOST_XrContext &xr_context)
bool isRunning() const
LifeExpectancy handleStateChangeEvent(const XrEventDataSessionStateChanged &lifecycle)
bool needsUpsideDownDrawing() const
void start(const GHOST_XrSessionBeginInfo *begin_info)
void updateCompositionLayerProjectViewSubImage(XrSwapchainSubImage &r_sub_image)
XrSwapchainImageBaseHeader * acquireDrawableSwapchainImage()
unsigned int uint32_t
Definition: stdint.h:83
GHOST_XrDrawViewFn draw_view_fn
GHOST_XrSessionExitFn session_exit_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
XrSessionState session_state
const XrViewConfigurationType view_type
std::vector< GHOST_XrSwapchain > swapchains
std::vector< XrView > views