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
17 /** \file
18 * \ingroup wm
19 */
20
21 #include "BKE_context.h"
22 #include "BKE_main.h"
23 #include "BKE_scene.h"
24
25 #include "BLI_listbase.h"
26 #include "BLI_math.h"
27
28 #include "DEG_depsgraph.h"
29
30 #include "DNA_camera_types.h"
31
32 #include "DRW_engine.h"
33
34 #include "GHOST_C-api.h"
35
36 #include "GPU_viewport.h"
37
38 #include "MEM_guardedalloc.h"
39
40 #include "WM_api.h"
41 #include "WM_types.h"
42
43 #include "wm_surface.h"
44 #include "wm_window.h"
45 #include "wm_xr_intern.h"
46
47 static wmSurface *g_xr_surface = NULL;
48 static CLG_LogRef LOG = {"wm.xr"};
49
50 /* -------------------------------------------------------------------- */
51
wm_xr_session_exit_cb(void * customdata)52 static void wm_xr_session_exit_cb(void *customdata)
53 {
54 wmXrData *xr_data = customdata;
55
56 xr_data->runtime->session_state.is_started = false;
57 if (xr_data->runtime->exit_fn) {
58 xr_data->runtime->exit_fn(xr_data);
59 }
60
61 /* Free the entire runtime data (including session state and context), to play safe. */
62 wm_xr_runtime_data_free(&xr_data->runtime);
63 }
64
wm_xr_session_begin_info_create(wmXrData * xr_data,GHOST_XrSessionBeginInfo * r_begin_info)65 static void wm_xr_session_begin_info_create(wmXrData *xr_data,
66 GHOST_XrSessionBeginInfo *r_begin_info)
67 {
68 /* WM-XR exit function, does some own stuff and calls callback passed to wm_xr_session_toggle(),
69 * to allow external code to execute its own session-exit logic. */
70 r_begin_info->exit_fn = wm_xr_session_exit_cb;
71 r_begin_info->exit_customdata = xr_data;
72 }
73
wm_xr_session_toggle(wmWindowManager * wm,wmWindow * session_root_win,wmXrSessionExitFn session_exit_fn)74 void wm_xr_session_toggle(wmWindowManager *wm,
75 wmWindow *session_root_win,
76 wmXrSessionExitFn session_exit_fn)
77 {
78 wmXrData *xr_data = &wm->xr;
79
80 if (WM_xr_session_exists(xr_data)) {
81 GHOST_XrSessionEnd(xr_data->runtime->context);
82 }
83 else {
84 GHOST_XrSessionBeginInfo begin_info;
85
86 xr_data->runtime->session_root_win = session_root_win;
87 xr_data->runtime->session_state.is_started = true;
88 xr_data->runtime->exit_fn = session_exit_fn;
89
90 wm_xr_session_begin_info_create(xr_data, &begin_info);
91 GHOST_XrSessionStart(xr_data->runtime->context, &begin_info);
92 }
93 }
94
95 /**
96 * Check if the XR-Session was triggered.
97 * If an error happened while trying to start a session, this returns false too.
98 */
WM_xr_session_exists(const wmXrData * xr)99 bool WM_xr_session_exists(const wmXrData *xr)
100 {
101 return xr->runtime && xr->runtime->context && xr->runtime->session_state.is_started;
102 }
103
WM_xr_session_base_pose_reset(wmXrData * xr)104 void WM_xr_session_base_pose_reset(wmXrData *xr)
105 {
106 xr->runtime->session_state.force_reset_to_base_pose = true;
107 }
108
109 /**
110 * Check if the session is running, according to the OpenXR definition.
111 */
WM_xr_session_is_ready(const wmXrData * xr)112 bool WM_xr_session_is_ready(const wmXrData *xr)
113 {
114 return WM_xr_session_exists(xr) && GHOST_XrSessionIsRunning(xr->runtime->context);
115 }
116
wm_xr_session_base_pose_calc(const Scene * scene,const XrSessionSettings * settings,GHOST_XrPose * r_base_pose)117 static void wm_xr_session_base_pose_calc(const Scene *scene,
118 const XrSessionSettings *settings,
119 GHOST_XrPose *r_base_pose)
120 {
121 const Object *base_pose_object = ((settings->base_pose_type == XR_BASE_POSE_OBJECT) &&
122 settings->base_pose_object) ?
123 settings->base_pose_object :
124 scene->camera;
125
126 if (settings->base_pose_type == XR_BASE_POSE_CUSTOM) {
127 float tmp_quatx[4], tmp_quatz[4];
128
129 copy_v3_v3(r_base_pose->position, settings->base_pose_location);
130 axis_angle_to_quat_single(tmp_quatx, 'X', M_PI_2);
131 axis_angle_to_quat_single(tmp_quatz, 'Z', settings->base_pose_angle);
132 mul_qt_qtqt(r_base_pose->orientation_quat, tmp_quatz, tmp_quatx);
133 }
134 else if (base_pose_object) {
135 float tmp_quat[4];
136 float tmp_eul[3];
137
138 mat4_to_loc_quat(r_base_pose->position, tmp_quat, base_pose_object->obmat);
139
140 /* Only use rotation around Z-axis to align view with floor. */
141 quat_to_eul(tmp_eul, tmp_quat);
142 tmp_eul[0] = M_PI_2;
143 tmp_eul[1] = 0;
144 eul_to_quat(r_base_pose->orientation_quat, tmp_eul);
145 }
146 else {
147 copy_v3_fl(r_base_pose->position, 0.0f);
148 axis_angle_to_quat_single(r_base_pose->orientation_quat, 'X', M_PI_2);
149 }
150 }
151
wm_xr_session_draw_data_populate(wmXrData * xr_data,Scene * scene,Depsgraph * depsgraph,wmXrDrawData * r_draw_data)152 static void wm_xr_session_draw_data_populate(wmXrData *xr_data,
153 Scene *scene,
154 Depsgraph *depsgraph,
155 wmXrDrawData *r_draw_data)
156 {
157 const XrSessionSettings *settings = &xr_data->session_settings;
158
159 memset(r_draw_data, 0, sizeof(*r_draw_data));
160 r_draw_data->scene = scene;
161 r_draw_data->depsgraph = depsgraph;
162 r_draw_data->xr_data = xr_data;
163 r_draw_data->surface_data = g_xr_surface->customdata;
164
165 wm_xr_session_base_pose_calc(r_draw_data->scene, settings, &r_draw_data->base_pose);
166 }
167
wm_xr_session_root_window_or_fallback_get(const wmWindowManager * wm,const wmXrRuntimeData * runtime_data)168 static wmWindow *wm_xr_session_root_window_or_fallback_get(const wmWindowManager *wm,
169 const wmXrRuntimeData *runtime_data)
170 {
171 if (runtime_data->session_root_win &&
172 BLI_findindex(&wm->windows, runtime_data->session_root_win) != -1) {
173 /* Root window is still valid, use it. */
174 return runtime_data->session_root_win;
175 }
176 /* Otherwise, fallback. */
177 return wm->windows.first;
178 }
179
180 /**
181 * Get the scene and depsgraph shown in the VR session's root window (the window the session was
182 * started from) if still available. If it's not available, use some fallback window.
183 *
184 * It's important that the VR session follows some existing window, otherwise it would need to have
185 * an own depsgraph, which is an expense we should avoid.
186 */
wm_xr_session_scene_and_evaluated_depsgraph_get(Main * bmain,const wmWindowManager * wm,Scene ** r_scene,Depsgraph ** r_depsgraph)187 static void wm_xr_session_scene_and_evaluated_depsgraph_get(Main *bmain,
188 const wmWindowManager *wm,
189 Scene **r_scene,
190 Depsgraph **r_depsgraph)
191 {
192 const wmWindow *root_win = wm_xr_session_root_window_or_fallback_get(wm, wm->xr.runtime);
193
194 /* Follow the scene & view layer shown in the root 3D View. */
195 Scene *scene = WM_window_get_active_scene(root_win);
196 ViewLayer *view_layer = WM_window_get_active_view_layer(root_win);
197
198 Depsgraph *depsgraph = BKE_scene_get_depsgraph(scene, view_layer);
199 BLI_assert(scene && view_layer && depsgraph);
200 BKE_scene_graph_evaluated_ensure(depsgraph, bmain);
201 *r_scene = scene;
202 *r_depsgraph = depsgraph;
203 }
204
205 typedef enum wmXrSessionStateEvent {
206 SESSION_STATE_EVENT_NONE = 0,
207 SESSION_STATE_EVENT_START,
208 SESSION_STATE_EVENT_RESET_TO_BASE_POSE,
209 SESSION_STATE_EVENT_POSITON_TRACKING_TOGGLE,
210 } wmXrSessionStateEvent;
211
wm_xr_session_draw_data_needs_reset_to_base_pose(const wmXrSessionState * state,const XrSessionSettings * settings)212 static bool wm_xr_session_draw_data_needs_reset_to_base_pose(const wmXrSessionState *state,
213 const XrSessionSettings *settings)
214 {
215 if (state->force_reset_to_base_pose) {
216 return true;
217 }
218 return ((settings->flag & XR_SESSION_USE_POSITION_TRACKING) == 0) &&
219 ((state->prev_base_pose_type != settings->base_pose_type) ||
220 (state->prev_base_pose_object != settings->base_pose_object));
221 }
222
wm_xr_session_state_to_event(const wmXrSessionState * state,const XrSessionSettings * settings)223 static wmXrSessionStateEvent wm_xr_session_state_to_event(const wmXrSessionState *state,
224 const XrSessionSettings *settings)
225 {
226 if (!state->is_view_data_set) {
227 return SESSION_STATE_EVENT_START;
228 }
229 if (wm_xr_session_draw_data_needs_reset_to_base_pose(state, settings)) {
230 return SESSION_STATE_EVENT_RESET_TO_BASE_POSE;
231 }
232
233 const bool position_tracking_toggled = ((state->prev_settings_flag &
234 XR_SESSION_USE_POSITION_TRACKING) !=
235 (settings->flag & XR_SESSION_USE_POSITION_TRACKING));
236 if (position_tracking_toggled) {
237 return SESSION_STATE_EVENT_POSITON_TRACKING_TOGGLE;
238 }
239
240 return SESSION_STATE_EVENT_NONE;
241 }
242
wm_xr_session_draw_data_update(const wmXrSessionState * state,const XrSessionSettings * settings,const GHOST_XrDrawViewInfo * draw_view,wmXrDrawData * draw_data)243 void wm_xr_session_draw_data_update(const wmXrSessionState *state,
244 const XrSessionSettings *settings,
245 const GHOST_XrDrawViewInfo *draw_view,
246 wmXrDrawData *draw_data)
247 {
248 const wmXrSessionStateEvent event = wm_xr_session_state_to_event(state, settings);
249 const bool use_position_tracking = (settings->flag & XR_SESSION_USE_POSITION_TRACKING);
250
251 switch (event) {
252 case SESSION_STATE_EVENT_START:
253 if (use_position_tracking) {
254 /* We want to start the session exactly at landmark position.
255 * Run-times may have a non-[0,0,0] starting position that we have to subtract for that. */
256 copy_v3_v3(draw_data->eye_position_ofs, draw_view->local_pose.position);
257 }
258 else {
259 copy_v3_fl(draw_data->eye_position_ofs, 0.0f);
260 }
261 break;
262 /* This should be triggered by the VR add-on if a landmark changes. */
263 case SESSION_STATE_EVENT_RESET_TO_BASE_POSE:
264 if (use_position_tracking) {
265 /* Switch exactly to base pose, so use eye offset to cancel out current position delta. */
266 copy_v3_v3(draw_data->eye_position_ofs, draw_view->local_pose.position);
267 }
268 else {
269 copy_v3_fl(draw_data->eye_position_ofs, 0.0f);
270 }
271 break;
272 case SESSION_STATE_EVENT_POSITON_TRACKING_TOGGLE:
273 if (use_position_tracking) {
274 /* Keep the current position, and let the user move from there. */
275 copy_v3_v3(draw_data->eye_position_ofs, state->prev_eye_position_ofs);
276 }
277 else {
278 /* Back to the exact base-pose position. */
279 copy_v3_fl(draw_data->eye_position_ofs, 0.0f);
280 }
281 break;
282 case SESSION_STATE_EVENT_NONE:
283 /* Keep previous offset when positional tracking is disabled. */
284 copy_v3_v3(draw_data->eye_position_ofs, state->prev_eye_position_ofs);
285 break;
286 }
287 }
288
289 /**
290 * Update information that is only stored for external state queries. E.g. for Python API to
291 * request the current (as in, last known) viewer pose.
292 */
wm_xr_session_state_update(const XrSessionSettings * settings,const wmXrDrawData * draw_data,const GHOST_XrDrawViewInfo * draw_view,wmXrSessionState * state)293 void wm_xr_session_state_update(const XrSessionSettings *settings,
294 const wmXrDrawData *draw_data,
295 const GHOST_XrDrawViewInfo *draw_view,
296 wmXrSessionState *state)
297 {
298 GHOST_XrPose viewer_pose;
299 const bool use_position_tracking = settings->flag & XR_SESSION_USE_POSITION_TRACKING;
300
301 mul_qt_qtqt(viewer_pose.orientation_quat,
302 draw_data->base_pose.orientation_quat,
303 draw_view->local_pose.orientation_quat);
304 copy_v3_v3(viewer_pose.position, draw_data->base_pose.position);
305 /* The local pose and the eye pose (which is copied from an earlier local pose) both are view
306 * space, so Y-up. In this case we need them in regular Z-up. */
307 viewer_pose.position[0] -= draw_data->eye_position_ofs[0];
308 viewer_pose.position[1] += draw_data->eye_position_ofs[2];
309 viewer_pose.position[2] -= draw_data->eye_position_ofs[1];
310 if (use_position_tracking) {
311 viewer_pose.position[0] += draw_view->local_pose.position[0];
312 viewer_pose.position[1] -= draw_view->local_pose.position[2];
313 viewer_pose.position[2] += draw_view->local_pose.position[1];
314 }
315
316 copy_v3_v3(state->viewer_pose.position, viewer_pose.position);
317 copy_qt_qt(state->viewer_pose.orientation_quat, viewer_pose.orientation_quat);
318 wm_xr_pose_to_viewmat(&viewer_pose, state->viewer_viewmat);
319 /* No idea why, but multiplying by two seems to make it match the VR view more. */
320 state->focal_len = 2.0f *
321 fov_to_focallength(draw_view->fov.angle_right - draw_view->fov.angle_left,
322 DEFAULT_SENSOR_WIDTH);
323
324 copy_v3_v3(state->prev_eye_position_ofs, draw_data->eye_position_ofs);
325 state->prev_settings_flag = settings->flag;
326 state->prev_base_pose_type = settings->base_pose_type;
327 state->prev_base_pose_object = settings->base_pose_object;
328 state->is_view_data_set = true;
329 /* Assume this was already done through wm_xr_session_draw_data_update(). */
330 state->force_reset_to_base_pose = false;
331 }
332
WM_xr_session_state_handle_get(const wmXrData * xr)333 wmXrSessionState *WM_xr_session_state_handle_get(const wmXrData *xr)
334 {
335 return xr->runtime ? &xr->runtime->session_state : NULL;
336 }
337
WM_xr_session_state_viewer_pose_location_get(const wmXrData * xr,float r_location[3])338 bool WM_xr_session_state_viewer_pose_location_get(const wmXrData *xr, float r_location[3])
339 {
340 if (!WM_xr_session_is_ready(xr) || !xr->runtime->session_state.is_view_data_set) {
341 zero_v3(r_location);
342 return false;
343 }
344
345 copy_v3_v3(r_location, xr->runtime->session_state.viewer_pose.position);
346 return true;
347 }
348
WM_xr_session_state_viewer_pose_rotation_get(const wmXrData * xr,float r_rotation[4])349 bool WM_xr_session_state_viewer_pose_rotation_get(const wmXrData *xr, float r_rotation[4])
350 {
351 if (!WM_xr_session_is_ready(xr) || !xr->runtime->session_state.is_view_data_set) {
352 unit_qt(r_rotation);
353 return false;
354 }
355
356 copy_v4_v4(r_rotation, xr->runtime->session_state.viewer_pose.orientation_quat);
357 return true;
358 }
359
WM_xr_session_state_viewer_pose_matrix_info_get(const wmXrData * xr,float r_viewmat[4][4],float * r_focal_len)360 bool WM_xr_session_state_viewer_pose_matrix_info_get(const wmXrData *xr,
361 float r_viewmat[4][4],
362 float *r_focal_len)
363 {
364 if (!WM_xr_session_is_ready(xr) || !xr->runtime->session_state.is_view_data_set) {
365 unit_m4(r_viewmat);
366 *r_focal_len = 0.0f;
367 return false;
368 }
369
370 copy_m4_m4(r_viewmat, xr->runtime->session_state.viewer_viewmat);
371 *r_focal_len = xr->runtime->session_state.focal_len;
372
373 return true;
374 }
375
376 /* -------------------------------------------------------------------- */
377 /** \name XR-Session Surface
378 *
379 * A wmSurface is used to manage drawing of the VR viewport. It's created and destroyed with the
380 * session.
381 *
382 * \{ */
383
384 /**
385 * \brief Call Ghost-XR to draw a frame
386 *
387 * Draw callback for the XR-session surface. It's expected to be called on each main loop
388 * iteration and tells Ghost-XR to submit a new frame by drawing its views. Note that for drawing
389 * each view, #wm_xr_draw_view() will be called through Ghost-XR (see GHOST_XrDrawViewFunc()).
390 */
wm_xr_session_surface_draw(bContext * C)391 static void wm_xr_session_surface_draw(bContext *C)
392 {
393 wmXrSurfaceData *surface_data = g_xr_surface->customdata;
394 wmWindowManager *wm = CTX_wm_manager(C);
395 Main *bmain = CTX_data_main(C);
396 wmXrDrawData draw_data;
397
398 if (!GHOST_XrSessionIsRunning(wm->xr.runtime->context)) {
399 return;
400 }
401
402 Scene *scene;
403 Depsgraph *depsgraph;
404 wm_xr_session_scene_and_evaluated_depsgraph_get(bmain, wm, &scene, &depsgraph);
405 wm_xr_session_draw_data_populate(&wm->xr, scene, depsgraph, &draw_data);
406
407 GHOST_XrSessionDrawViews(wm->xr.runtime->context, &draw_data);
408
409 GPU_offscreen_unbind(surface_data->offscreen, false);
410 }
411
wm_xr_session_surface_offscreen_ensure(wmXrSurfaceData * surface_data,const GHOST_XrDrawViewInfo * draw_view)412 bool wm_xr_session_surface_offscreen_ensure(wmXrSurfaceData *surface_data,
413 const GHOST_XrDrawViewInfo *draw_view)
414 {
415 const bool size_changed = surface_data->offscreen &&
416 (GPU_offscreen_width(surface_data->offscreen) != draw_view->width) &&
417 (GPU_offscreen_height(surface_data->offscreen) != draw_view->height);
418 char err_out[256] = "unknown";
419 bool failure = false;
420
421 if (surface_data->offscreen) {
422 BLI_assert(surface_data->viewport);
423
424 if (!size_changed) {
425 return true;
426 }
427 GPU_viewport_free(surface_data->viewport);
428 GPU_offscreen_free(surface_data->offscreen);
429 }
430
431 if (!(surface_data->offscreen = GPU_offscreen_create(
432 draw_view->width, draw_view->height, true, false, err_out))) {
433 failure = true;
434 }
435
436 if (failure) {
437 /* Pass. */
438 }
439 else if (!(surface_data->viewport = GPU_viewport_create())) {
440 GPU_offscreen_free(surface_data->offscreen);
441 failure = true;
442 }
443
444 if (failure) {
445 CLOG_ERROR(&LOG, "Failed to get buffer, %s\n", err_out);
446 return false;
447 }
448
449 return true;
450 }
451
wm_xr_session_surface_free_data(wmSurface * surface)452 static void wm_xr_session_surface_free_data(wmSurface *surface)
453 {
454 wmXrSurfaceData *data = surface->customdata;
455
456 if (data->viewport) {
457 GPU_viewport_free(data->viewport);
458 }
459 if (data->offscreen) {
460 GPU_offscreen_free(data->offscreen);
461 }
462
463 MEM_freeN(surface->customdata);
464
465 g_xr_surface = NULL;
466 }
467
wm_xr_session_surface_create(void)468 static wmSurface *wm_xr_session_surface_create(void)
469 {
470 if (g_xr_surface) {
471 BLI_assert(false);
472 return g_xr_surface;
473 }
474
475 wmSurface *surface = MEM_callocN(sizeof(*surface), __func__);
476 wmXrSurfaceData *data = MEM_callocN(sizeof(*data), "XrSurfaceData");
477
478 surface->draw = wm_xr_session_surface_draw;
479 surface->free_data = wm_xr_session_surface_free_data;
480 surface->activate = DRW_xr_drawing_begin;
481 surface->deactivate = DRW_xr_drawing_end;
482
483 surface->ghost_ctx = DRW_xr_opengl_context_get();
484 surface->gpu_ctx = DRW_xr_gpu_context_get();
485
486 surface->customdata = data;
487
488 g_xr_surface = surface;
489
490 return surface;
491 }
492
wm_xr_session_gpu_binding_context_create(void)493 void *wm_xr_session_gpu_binding_context_create(void)
494 {
495 wmSurface *surface = wm_xr_session_surface_create();
496
497 wm_surface_add(surface);
498
499 /* Some regions may need to redraw with updated session state after the session is entirely up
500 * and running. */
501 WM_main_add_notifier(NC_WM | ND_XR_DATA_CHANGED, NULL);
502
503 return surface->ghost_ctx;
504 }
505
wm_xr_session_gpu_binding_context_destroy(GHOST_ContextHandle UNUSED (context))506 void wm_xr_session_gpu_binding_context_destroy(GHOST_ContextHandle UNUSED(context))
507 {
508 if (g_xr_surface) { /* Might have been freed already */
509 wm_surface_remove(g_xr_surface);
510 }
511
512 wm_window_reset_drawable();
513
514 /* Some regions may need to redraw with updated session state after the session is entirely
515 * stopped. */
516 WM_main_add_notifier(NC_WM | ND_XR_DATA_CHANGED, NULL);
517 }
518
519 /** \} */ /* XR-Session Surface */
520