diff options
author | Julian Eisel <julian@blender.org> | 2020-03-17 22:20:55 +0300 |
---|---|---|
committer | Julian Eisel <julian@blender.org> | 2020-03-17 23:42:44 +0300 |
commit | dc2df8307f41888bab722f75fa9e73adecf86b72 (patch) | |
tree | f83c54f43e27a07e9cb9fed306d79b08864f29f2 /source/blender/windowmanager | |
parent | 406bfd43040a5526702b51f88f1491cb61aecedb (diff) |
VR: Initial Virtual Reality support - Milestone 1, Scene Inspection
NOTE: While most of the milestone 1 goals are there, a few smaller features and
improvements are still to be done.
Big picture of this milestone: Initial, OpenXR-based virtual reality support
for users and foundation for advanced use cases.
Maniphest Task: https://developer.blender.org/T71347
The tasks contains more information about this milestone.
To be clear: This is not a feature rich VR implementation, it's focused on the
initial scene inspection use case. We intentionally focused on that, further
features like controller support are part of the next milestone.
- How to use?
Instructions on how to use this are here:
https://wiki.blender.org/wiki/User:Severin/GSoC-2019/How_to_Test
These will be updated and moved to a more official place (likely the manual) soon.
Currently Windows Mixed Reality and Oculus devices are usable. Valve/HTC
headsets don't support the OpenXR standard yet and hence, do not work with this
implementation.
---------------
This is the C-side implementation of the features added for initial VR
support as per milestone 1. A "VR Scene Inspection" Add-on will be
committed separately, to expose the VR functionality in the UI. It also
adds some further features for milestone 1, namely a landmarking system
(stored view locations in the VR space)
Main additions/features:
* Support for rendering viewports to an HMD, with good performance.
* Option to sync the VR view perspective with a fully interactive,
regular 3D View (VR-Mirror).
* Option to disable positional tracking. Keeps the current position (calculated
based on the VR eye center pose) when enabled while a VR session is running.
* Some regular viewport settings for the VR view
* RNA/Python-API to query and set VR session state information.
* WM-XR: Layer tying Ghost-XR to the Blender specific APIs/data
* wmSurface API: drawable, non-window container (manages Ghost-OpenGL and GPU
context)
* DNA/RNA for management of VR session settings
* `--debug-xr` and `--debug-xr-time` commandline options
* Utility batch & config file for using the Oculus runtime on Windows.
* Most VR data is runtime only. The exception is user settings which are saved
to files (`XrSessionSettings`).
* VR support can be disabled through the `WITH_XR_OPENXR` compiler flag.
For architecture and code documentation, see
https://wiki.blender.org/wiki/Source/Interface/XR.
---------------
A few thank you's:
* A huge shoutout to Ray Molenkamp for his help during the project - it would
have not been that successful without him!
* Sebastian Koenig and Simeon Conzendorf for testing and feedback!
* The reviewers, especially Brecht Van Lommel!
* Dalai Felinto for pushing and managing me to get this done ;)
* The OpenXR working group for providing an open standard. I think we're the
first bigger application to adopt OpenXR. Congratulations to them and
ourselves :)
This project started as a Google Summer of Code 2019 project - "Core Support of
Virtual Reality Headsets through OpenXR" (see
https://wiki.blender.org/wiki/User:Severin/GSoC-2019/).
Some further information, including ideas for further improvements can be found
in the final GSoC report:
https://wiki.blender.org/wiki/User:Severin/GSoC-2019/Final_Report
Differential Revisions: D6193, D7098
Reviewed by: Brecht Van Lommel, Jeroen Bakker
Diffstat (limited to 'source/blender/windowmanager')
-rw-r--r-- | source/blender/windowmanager/CMakeLists.txt | 9 | ||||
-rw-r--r-- | source/blender/windowmanager/WM_api.h | 16 | ||||
-rw-r--r-- | source/blender/windowmanager/WM_types.h | 2 | ||||
-rw-r--r-- | source/blender/windowmanager/gizmo/WM_gizmo_types.h | 7 | ||||
-rw-r--r-- | source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c | 5 | ||||
-rw-r--r-- | source/blender/windowmanager/intern/wm.c | 5 | ||||
-rw-r--r-- | source/blender/windowmanager/intern/wm_draw.c | 82 | ||||
-rw-r--r-- | source/blender/windowmanager/intern/wm_init_exit.c | 2 | ||||
-rw-r--r-- | source/blender/windowmanager/intern/wm_operators.c | 81 | ||||
-rw-r--r-- | source/blender/windowmanager/intern/wm_surface.c | 118 | ||||
-rw-r--r-- | source/blender/windowmanager/intern/wm_window.c | 29 | ||||
-rw-r--r-- | source/blender/windowmanager/intern/wm_xr.c | 759 | ||||
-rw-r--r-- | source/blender/windowmanager/wm.h | 10 | ||||
-rw-r--r-- | source/blender/windowmanager/wm_surface.h | 57 |
14 files changed, 1168 insertions, 14 deletions
diff --git a/source/blender/windowmanager/CMakeLists.txt b/source/blender/windowmanager/CMakeLists.txt index e7a4ca9a005..a1b67216f1a 100644 --- a/source/blender/windowmanager/CMakeLists.txt +++ b/source/blender/windowmanager/CMakeLists.txt @@ -75,6 +75,7 @@ set(SRC intern/wm_splash_screen.c intern/wm_stereo.c intern/wm_subwindow.c + intern/wm_surface.c intern/wm_toolsystem.c intern/wm_tooltip.c intern/wm_uilist_type.c @@ -101,6 +102,7 @@ set(SRC wm_event_system.h wm_event_types.h wm_files.h + wm_surface.h wm_window.h intern/wm_platform_support.h intern/wm_window_private.h @@ -187,4 +189,11 @@ if(WITH_COMPOSITOR) add_definitions(-DWITH_COMPOSITOR) endif() +if(WITH_XR_OPENXR) + add_definitions(-DWITH_XR_OPENXR) + list(APPEND SRC + intern/wm_xr.c + ) +endif() + blender_add_lib_nolist(bf_windowmanager "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h index 6781f055fc2..1be7a8679c4 100644 --- a/source/blender/windowmanager/WM_api.h +++ b/source/blender/windowmanager/WM_api.h @@ -156,6 +156,10 @@ void *WM_opengl_context_create(void); void WM_opengl_context_dispose(void *context); void WM_opengl_context_activate(void *context); void WM_opengl_context_release(void *context); +#ifdef WIN32 +void *WM_directx_context_create(void); +void WM_directx_context_dispose(void *context); +#endif struct wmWindow *WM_window_open(struct bContext *C, const struct rcti *rect); struct wmWindow *WM_window_open_temp(struct bContext *C, @@ -867,6 +871,18 @@ void WM_generic_callback_free(struct wmGenericCallback *callback); void WM_generic_user_data_free(struct wmGenericUserData *user_data); +#ifdef WITH_XR_OPENXR +/* wm_xr.c */ +bool WM_xr_session_exists(const wmXrData *xr); +bool WM_xr_session_is_ready(const wmXrData *xr); +struct wmXrSessionState *WM_xr_session_state_handle_get(const wmXrData *xr); +bool WM_xr_session_state_viewer_pose_location_get(const wmXrData *xr, float r_location[3]); +bool WM_xr_session_state_viewer_pose_rotation_get(const wmXrData *xr, float r_rotation[4]); +bool WM_xr_session_state_viewer_pose_matrix_info_get(const wmXrData *xr, + float r_viewmat[4][4], + float *r_focal_len); +#endif + #ifdef __cplusplus } #endif diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h index b9dd6b3923d..1edfe252cf4 100644 --- a/source/blender/windowmanager/WM_types.h +++ b/source/blender/windowmanager/WM_types.h @@ -310,6 +310,7 @@ typedef struct wmNotifier { #define ND_HISTORY (4 << 16) #define ND_JOB (5 << 16) #define ND_UNDO (6 << 16) +#define ND_XR_DATA_CHANGED (7 << 17) /* NC_SCREEN */ #define ND_LAYOUTBROWSE (1 << 16) @@ -437,6 +438,7 @@ typedef struct wmNotifier { /* subtype 3d view editing */ #define NS_VIEW3D_GPU (16 << 8) +#define NS_VIEW3D_SHADING (16 << 9) /* action classification */ #define NOTE_ACTION (0x000000FF) diff --git a/source/blender/windowmanager/gizmo/WM_gizmo_types.h b/source/blender/windowmanager/gizmo/WM_gizmo_types.h index c667dd564f7..04311b9c070 100644 --- a/source/blender/windowmanager/gizmo/WM_gizmo_types.h +++ b/source/blender/windowmanager/gizmo/WM_gizmo_types.h @@ -139,6 +139,13 @@ typedef enum eWM_GizmoFlagGroupTypeFlag { * with click drag events by popping up under the cursor and catching the tweak event. */ WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK = (1 << 8), + + /** + * Cause continuous redraws, i.e. set the region redraw flag on every main loop itertion. This + * should really be avoided by using proper region redraw tagging, notifiers and the message-bus, + * however for VR it's sometimes needed. + */ + WM_GIZMOGROUPTYPE_VR_REDRAWS = (1 << 9), } eWM_GizmoFlagGroupTypeFlag; /** diff --git a/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c b/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c index 28690fec2d8..d463f1df2e9 100644 --- a/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c +++ b/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c @@ -576,6 +576,7 @@ static int gizmo_find_intersected_3d_intern(wmGizmo **visible_gizmos, const int co[2], const int hotspot) { + const wmWindowManager *wm = CTX_wm_manager(C); ScrArea *sa = CTX_wm_area(C); ARegion *region = CTX_wm_region(C); View3D *v3d = sa->spacedata.first; @@ -588,7 +589,7 @@ static int gizmo_find_intersected_3d_intern(wmGizmo **visible_gizmos, BLI_rcti_init_pt_radius(&rect, co, hotspot); ED_view3d_draw_setup_view( - CTX_wm_window(C), depsgraph, CTX_data_scene(C), region, v3d, NULL, NULL, &rect); + wm, CTX_wm_window(C), depsgraph, CTX_data_scene(C), region, v3d, NULL, NULL, &rect); bool use_select_bias = false; @@ -608,7 +609,7 @@ static int gizmo_find_intersected_3d_intern(wmGizmo **visible_gizmos, } ED_view3d_draw_setup_view( - CTX_wm_window(C), depsgraph, CTX_data_scene(C), region, v3d, NULL, NULL, NULL); + wm, CTX_wm_window(C), depsgraph, CTX_data_scene(C), region, v3d, NULL, NULL, NULL); if (use_select_bias && (hits > 1)) { float co_direction[3]; diff --git a/source/blender/windowmanager/intern/wm.c b/source/blender/windowmanager/intern/wm.c index 2edef54c778..2a4ba7fdb8e 100644 --- a/source/blender/windowmanager/intern/wm.c +++ b/source/blender/windowmanager/intern/wm.c @@ -379,6 +379,11 @@ void wm_close_and_free(bContext *C, wmWindowManager *wm) wm_autosave_timer_ended(wm); } +#ifdef WITH_XR_OPENXR + /* May send notifier, so do before freeing notifier queue. */ + wm_xr_exit(wm); +#endif + while ((win = BLI_pophead(&wm->windows))) { /* prevent draw clear to use screen */ BKE_workspace_active_set(win->workspace_hook, NULL); diff --git a/source/blender/windowmanager/intern/wm_draw.c b/source/blender/windowmanager/intern/wm_draw.c index 9ccff2a3e3d..a0e8374a46a 100644 --- a/source/blender/windowmanager/intern/wm_draw.c +++ b/source/blender/windowmanager/intern/wm_draw.c @@ -67,6 +67,7 @@ #include "wm_draw.h" #include "wm_window.h" #include "wm_event_system.h" +#include "wm_surface.h" #ifdef WITH_OPENSUBDIV # include "BKE_subsurf.h" @@ -186,7 +187,10 @@ static void wm_area_mark_invalid_backbuf(ScrArea *sa) } } -static void wm_region_test_gizmo_do_draw(ARegion *region, bool tag_redraw) +static void wm_region_test_gizmo_do_draw(bContext *C, + ScrArea *sa, + ARegion *region, + bool tag_redraw) { if (region->gizmo_map == NULL) { return; @@ -195,10 +199,26 @@ static void wm_region_test_gizmo_do_draw(ARegion *region, bool tag_redraw) wmGizmoMap *gzmap = region->gizmo_map; for (wmGizmoGroup *gzgroup = WM_gizmomap_group_list(gzmap)->first; gzgroup; gzgroup = gzgroup->next) { + if (tag_redraw && (gzgroup->type->flag & WM_GIZMOGROUPTYPE_VR_REDRAWS)) { + ScrArea *ctx_sa = CTX_wm_area(C); + ARegion *ctx_ar = CTX_wm_region(C); + + CTX_wm_area_set(C, sa); + CTX_wm_region_set(C, region); + + if (WM_gizmo_group_type_poll(C, gzgroup->type)) { + ED_region_tag_redraw_editor_overlays(region); + } + + /* Reset. */ + CTX_wm_area_set(C, ctx_sa); + CTX_wm_region_set(C, ctx_ar); + } + for (wmGizmo *gz = gzgroup->gizmos.first; gz; gz = gz->next) { if (gz->do_draw) { if (tag_redraw) { - ED_region_tag_redraw_no_rebuild(region); + ED_region_tag_redraw_editor_overlays(region); } gz->do_draw = false; } @@ -237,6 +257,19 @@ static void wm_region_test_render_do_draw(const Scene *scene, } } +#ifdef WITH_XR_OPENXR +static void wm_region_test_xr_do_draw(const wmWindowManager *wm, + const ScrArea *area, + ARegion *region) +{ + if ((area->spacetype == SPACE_VIEW3D) && (region->regiontype == RGN_TYPE_WINDOW)) { + if (ED_view3d_is_region_xr_mirror_active(wm, area->spacedata.first, region)) { + ED_region_tag_redraw_no_rebuild(region); + } + } +} +#endif + static bool wm_region_use_viewport_by_type(short space_type, short region_type) { return (ELEM(space_type, SPACE_VIEW3D, SPACE_IMAGE) && region_type == RGN_TYPE_WINDOW); @@ -836,11 +869,26 @@ static void wm_draw_window(bContext *C, wmWindow *win) screen->do_draw = false; } +/** + * Draw offscreen contexts not bound to a specific window. + */ +static void wm_draw_surface(bContext *C, wmSurface *surface) +{ + wm_window_clear_drawable(CTX_wm_manager(C)); + wm_surface_make_drawable(surface); + + surface->draw(C); + + /* Avoid interference with window drawable */ + wm_surface_clear_drawable(); +} + /****************** main update call **********************/ /* quick test to prevent changing window drawable */ -static bool wm_draw_update_test_window(Main *bmain, wmWindow *win) +static bool wm_draw_update_test_window(Main *bmain, bContext *C, wmWindow *win) { + const wmWindowManager *wm = CTX_wm_manager(C); Scene *scene = WM_window_get_active_scene(win); ViewLayer *view_layer = WM_window_get_active_view_layer(win); struct Depsgraph *depsgraph = BKE_scene_get_depsgraph(bmain, scene, view_layer, true); @@ -861,8 +909,11 @@ static bool wm_draw_update_test_window(Main *bmain, wmWindow *win) ED_screen_areas_iter(win, screen, sa) { for (region = sa->regionbase.first; region; region = region->next) { - wm_region_test_gizmo_do_draw(region, true); + wm_region_test_gizmo_do_draw(C, sa, region, true); wm_region_test_render_do_draw(scene, depsgraph, sa, region); +#ifdef WITH_XR_OPENXR + wm_region_test_xr_do_draw(wm, sa, region); +#endif if (region->visible && region->do_draw) { do_draw = true; @@ -890,19 +941,23 @@ static bool wm_draw_update_test_window(Main *bmain, wmWindow *win) return true; } +#ifndef WITH_XR_OPENXR + UNUSED_VARS(wm); +#endif + return false; } /* Clear drawing flags, after drawing is complete so any draw flags set during * drawing don't cause any additional redraws. */ -static void wm_draw_update_clear_window(wmWindow *win) +static void wm_draw_update_clear_window(bContext *C, wmWindow *win) { bScreen *screen = WM_window_get_active_screen(win); ED_screen_areas_iter(win, screen, sa) { for (ARegion *region = sa->regionbase.first; region; region = region->next) { - wm_region_test_gizmo_do_draw(region, false); + wm_region_test_gizmo_do_draw(C, sa, region, false); } } @@ -944,10 +999,10 @@ void wm_draw_update(bContext *C) } #endif - if (wm_draw_update_test_window(bmain, win)) { - bScreen *screen = WM_window_get_active_screen(win); + CTX_wm_window_set(C, win); - CTX_wm_window_set(C, win); + if (wm_draw_update_test_window(bmain, C, win)) { + bScreen *screen = WM_window_get_active_screen(win); /* sets context window+screen */ wm_window_make_drawable(wm, win); @@ -956,13 +1011,16 @@ void wm_draw_update(bContext *C) ED_screen_ensure_updated(wm, win, screen); wm_draw_window(C, win); - wm_draw_update_clear_window(win); + wm_draw_update_clear_window(C, win); wm_window_swap_buffers(win); - - CTX_wm_window_set(C, NULL); } } + + CTX_wm_window_set(C, NULL); + + /* Draw non-windows (surfaces) */ + wm_surfaces_iter(C, wm_draw_surface); } void wm_draw_region_clear(wmWindow *win, ARegion *UNUSED(region)) diff --git a/source/blender/windowmanager/intern/wm_init_exit.c b/source/blender/windowmanager/intern/wm_init_exit.c index a87f0a3e42c..7b04b40e626 100644 --- a/source/blender/windowmanager/intern/wm_init_exit.c +++ b/source/blender/windowmanager/intern/wm_init_exit.c @@ -98,6 +98,7 @@ #include "wm_event_system.h" #include "wm.h" #include "wm_files.h" +#include "wm_surface.h" #include "wm_window.h" #include "wm_platform_support.h" @@ -534,6 +535,7 @@ void WM_exit_ex(bContext *C, const bool do_python) BKE_materials_exit(); wm_operatortype_free(); + wm_surfaces_free(); wm_dropbox_free(); WM_menutype_free(); WM_uilisttype_free(); diff --git a/source/blender/windowmanager/intern/wm_operators.c b/source/blender/windowmanager/intern/wm_operators.c index d649b21569e..1d6ae10c396 100644 --- a/source/blender/windowmanager/intern/wm_operators.c +++ b/source/blender/windowmanager/intern/wm_operators.c @@ -3645,6 +3645,84 @@ static void WM_OT_stereo3d_set(wmOperatorType *ot) /** \} */ +#ifdef WITH_XR_OPENXR + +static void wm_xr_session_update_screen(Main *bmain, const wmXrData *xr_data) +{ + const bool session_exists = WM_xr_session_exists(xr_data); + + for (bScreen *screen = bmain->screens.first; screen; screen = screen->id.next) { + for (ScrArea *area = screen->areabase.first; area; area = area->next) { + for (SpaceLink *slink = area->spacedata.first; slink; slink = slink->next) { + if (slink->spacetype == SPACE_VIEW3D) { + View3D *v3d = (View3D *)slink; + + if (v3d->flag & V3D_XR_SESSION_MIRROR) { + ED_view3d_xr_mirror_update(area, v3d, session_exists); + } + + if (session_exists) { + wmWindowManager *wm = bmain->wm.first; + const Scene *scene = WM_windows_scene_get_from_screen(wm, screen); + + ED_view3d_xr_shading_update(wm, v3d, scene); + } + /* Ensure no 3D View is tagged as session root. */ + else { + v3d->runtime.flag &= ~V3D_RUNTIME_XR_SESSION_ROOT; + } + } + } + } + } +} + +static void wm_xr_session_update_screen_on_exit_cb(const wmXrData *xr_data) +{ + /* Just use G_MAIN here, storing main isn't reliable enough on file read or exit. */ + wm_xr_session_update_screen(G_MAIN, xr_data); +} + +static int wm_xr_session_toggle_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Main *bmain = CTX_data_main(C); + wmWindowManager *wm = CTX_wm_manager(C); + View3D *v3d = CTX_wm_view3d(C); + + /* Lazy-create xr context - tries to dynlink to the runtime, reading active_runtime.json. */ + if (wm_xr_init(wm) == false) { + return OPERATOR_CANCELLED; + } + + v3d->runtime.flag |= V3D_RUNTIME_XR_SESSION_ROOT; + wm_xr_session_toggle(wm, wm_xr_session_update_screen_on_exit_cb); + wm_xr_session_update_screen(bmain, &wm->xr); + + WM_event_add_notifier(C, NC_WM | ND_XR_DATA_CHANGED, NULL); + + return OPERATOR_FINISHED; +} + +static void WM_OT_xr_session_toggle(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Toggle VR Session"; + ot->idname = "WM_OT_xr_session_toggle"; + ot->description = + "Open a view for use with virtual reality headsets, or close it if already " + "opened"; + + /* callbacks */ + ot->exec = wm_xr_session_toggle_exec; + ot->poll = ED_operator_view3d_active; + + /* XXX INTERNAL just to hide it from the search menu by default, an Add-on will expose it in the + * UI instead. Not meant as a permanent solution. */ + ot->flag = OPTYPE_INTERNAL; +} + +#endif /* WITH_XR_OPENXR */ + /* -------------------------------------------------------------------- */ /** \name Operator Registration & Keymaps * \{ */ @@ -3686,6 +3764,9 @@ void wm_operatortypes_register(void) WM_operatortype_append(WM_OT_call_panel); WM_operatortype_append(WM_OT_radial_control); WM_operatortype_append(WM_OT_stereo3d_set); +#ifdef WITH_XR_OPENXR + WM_operatortype_append(WM_OT_xr_session_toggle); +#endif #if defined(WIN32) WM_operatortype_append(WM_OT_console_toggle); #endif diff --git a/source/blender/windowmanager/intern/wm_surface.c b/source/blender/windowmanager/intern/wm_surface.c new file mode 100644 index 00000000000..cb575cc8f7d --- /dev/null +++ b/source/blender/windowmanager/intern/wm_surface.c @@ -0,0 +1,118 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup wm + */ + +#include "BKE_context.h" + +#include "BLF_api.h" + +#include "BLI_listbase.h" +#include "BLI_threads.h" + +#include "GHOST_C-api.h" + +#include "GPU_batch_presets.h" +#include "GPU_framebuffer.h" +#include "GPU_immediate.h" +#include "GPU_context.h" + +#include "MEM_guardedalloc.h" + +#include "WM_types.h" +#include "WM_api.h" +#include "wm.h" + +#include "wm_surface.h" + +static ListBase global_surface_list = {NULL, NULL}; +static wmSurface *g_drawable = NULL; + +void wm_surfaces_iter(bContext *C, void (*cb)(bContext *C, wmSurface *)) +{ + for (wmSurface *surf = global_surface_list.first; surf; surf = surf->next) { + cb(C, surf); + } +} + +void wm_surface_clear_drawable(void) +{ + if (g_drawable) { + BLF_batch_reset(); + gpu_batch_presets_reset(); + immDeactivate(); + + g_drawable = NULL; + } +} + +void wm_surface_set_drawable(wmSurface *surface, bool activate) +{ + BLI_assert(ELEM(g_drawable, NULL, surface)); + + g_drawable = surface; + if (activate) { + GHOST_ActivateOpenGLContext(surface->ghost_ctx); + } + + GPU_context_active_set(surface->gpu_ctx); + immActivate(); +} + +void wm_surface_make_drawable(wmSurface *surface) +{ + BLI_assert(GPU_framebuffer_active_get() == NULL); + + if (surface != g_drawable) { + wm_surface_clear_drawable(); + wm_surface_set_drawable(surface, true); + } +} + +void wm_surface_reset_drawable(void) +{ + BLI_assert(BLI_thread_is_main()); + BLI_assert(GPU_framebuffer_active_get() == NULL); + + if (g_drawable) { + wm_surface_clear_drawable(); + wm_surface_set_drawable(g_drawable, true); + } +} + +void wm_surface_add(wmSurface *surface) +{ + BLI_addtail(&global_surface_list, surface); +} + +void wm_surface_remove(wmSurface *surface) +{ + BLI_remlink(&global_surface_list, surface); + surface->free_data(surface); + MEM_freeN(surface); +} + +void wm_surfaces_free(void) +{ + for (wmSurface *surf = global_surface_list.first, *surf_next; surf; surf = surf_next) { + surf_next = surf->next; + wm_surface_remove(surf); + } + + BLI_assert(BLI_listbase_is_empty(&global_surface_list)); +} diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c index fe6272686bc..eaa0fa7b7a5 100644 --- a/source/blender/windowmanager/intern/wm_window.c +++ b/source/blender/windowmanager/intern/wm_window.c @@ -1631,6 +1631,11 @@ void wm_window_process_events(const bContext *C) GHOST_DispatchEvents(g_system); } hasevent |= wm_window_timer(C); +#ifdef WITH_XR_OPENXR + /* XR events don't use the regular window queues. So here we don't only trigger + * processing/dispatching but also handling. */ + hasevent |= wm_xr_events_handle(CTX_wm_manager(C)); +#endif /* no event, we sleep 5 milliseconds */ if (hasevent == 0) { @@ -1957,6 +1962,9 @@ void wm_window_raise(wmWindow *win) /** \name Window Buffers * \{ */ +/** + * \brief Push rendered buffer to the screen. + */ void wm_window_swap_buffers(wmWindow *win) { GHOST_SwapWindowBuffers(win->ghostwin); @@ -2446,3 +2454,24 @@ void WM_ghost_show_message_box(const char *title, GHOST_ShowMessageBox(g_system, title, message, help_label, continue_label, link, dialog_options); } /** \} */ + +#ifdef WIN32 +/* -------------------------------------------------------------------- */ +/** \name Direct DirectX Context Management + * \{ */ + +void *WM_directx_context_create(void) +{ + BLI_assert(GPU_framebuffer_active_get() == NULL); + return GHOST_CreateDirectXContext(g_system); +} + +void WM_directx_context_dispose(void *context) +{ + BLI_assert(GPU_framebuffer_active_get() == NULL); + GHOST_DisposeDirectXContext(g_system, context); +} + +/** \} */ + +#endif diff --git a/source/blender/windowmanager/intern/wm_xr.c b/source/blender/windowmanager/intern/wm_xr.c new file mode 100644 index 00000000000..bd9fd7cdc3e --- /dev/null +++ b/source/blender/windowmanager/intern/wm_xr.c @@ -0,0 +1,759 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup wm + * + * \name Window-Manager XR API + * + * Implements Blender specific functionality for the GHOST_Xr API. + */ + +#include "BKE_context.h" +#include "BKE_global.h" +#include "BKE_idprop.h" +#include "BKE_main.h" +#include "BKE_object.h" +#include "BKE_report.h" +#include "BKE_screen.h" + +#include "BLI_ghash.h" +#include "BLI_math_geom.h" +#include "BLI_math_matrix.h" + +#include "CLG_log.h" + +#include "DNA_camera_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_view3d_types.h" +#include "DNA_xr_types.h" + +#include "DRW_engine.h" + +#include "ED_view3d.h" +#include "ED_view3d_offscreen.h" + +#include "GHOST_C-api.h" + +#include "GPU_context.h" +#include "GPU_draw.h" +#include "GPU_matrix.h" +#include "GPU_viewport.h" + +#include "MEM_guardedalloc.h" + +#include "UI_interface.h" + +#include "WM_types.h" +#include "WM_api.h" + +#include "wm.h" +#include "wm_surface.h" +#include "wm_window.h" + +struct wmXrRuntimeData *wm_xr_runtime_data_create(void); +void wm_xr_runtime_data_free(struct wmXrRuntimeData **runtime); +void wm_xr_draw_view(const GHOST_XrDrawViewInfo *, void *); +void *wm_xr_session_gpu_binding_context_create(GHOST_TXrGraphicsBinding); +void wm_xr_session_gpu_binding_context_destroy(GHOST_TXrGraphicsBinding, GHOST_ContextHandle); +wmSurface *wm_xr_session_surface_create(wmWindowManager *, unsigned int); +void wm_xr_pose_to_viewmat(const GHOST_XrPose *pose, float r_viewmat[4][4]); + +/* -------------------------------------------------------------------- */ + +typedef struct wmXrSessionState { + bool is_started; + + /** Last known viewer pose (centroid of eyes, in world space) stored for queries. */ + GHOST_XrPose viewer_pose; + /** The last known view matrix, calculated from above's viewer pose. */ + float viewer_viewmat[4][4]; + float focal_len; + + /** Copy of XrSessionSettings.flag created on the last draw call, stored to detect changes. */ + int prev_settings_flag; + /** Copy of wmXrDrawData.eye_position_ofs. */ + float prev_eye_position_ofs[3]; + + bool is_view_data_set; +} wmXrSessionState; + +typedef struct wmXrRuntimeData { + GHOST_XrContextHandle context; + + /* Although this struct is internal, RNA gets a handle to this for state information queries. */ + wmXrSessionState session_state; + wmXrSessionExitFn exit_fn; +} wmXrRuntimeData; + +typedef struct wmXrDrawData { + /** The pose (location + rotation) to which eye deltas will be applied to when drawing (world + * space). With positional tracking enabled, it should be the same as the base pose, when + * disabled it also contains a location delta from the moment the option was toggled. */ + GHOST_XrPose base_pose; + float eye_position_ofs[3]; /* Local/view space. */ +} wmXrDrawData; + +typedef struct { + GHOST_TXrGraphicsBinding gpu_binding_type; + GPUOffScreen *offscreen; + GPUViewport *viewport; + + GHOST_ContextHandle secondary_ghost_ctx; +} wmXrSurfaceData; + +typedef struct { + wmWindowManager *wm; +} wmXrErrorHandlerData; + +/* -------------------------------------------------------------------- */ + +static wmSurface *g_xr_surface = NULL; +static CLG_LogRef LOG = {"wm.xr"}; + +/* -------------------------------------------------------------------- */ +/** \name XR-Context + * + * All XR functionality is accessed through a #GHOST_XrContext handle. + * The lifetime of this context also determines the lifetime of the OpenXR instance, which is the + * representation of the OpenXR runtime connection within the application. + * + * \{ */ + +static void wm_xr_error_handler(const GHOST_XrError *error) +{ + wmXrErrorHandlerData *handler_data = error->customdata; + wmWindowManager *wm = handler_data->wm; + + BKE_reports_clear(&wm->reports); + WM_report(RPT_ERROR, error->user_message); + WM_report_banner_show(); + + if (wm->xr.runtime->context) { + /* Just play safe and destroy the entire runtime data, including context. */ + wm_xr_runtime_data_free(&wm->xr.runtime); + } +} + +bool wm_xr_init(wmWindowManager *wm) +{ + if (wm->xr.runtime && wm->xr.runtime->context) { + return true; + } + static wmXrErrorHandlerData error_customdata; + + /* Set up error handling */ + error_customdata.wm = wm; + GHOST_XrErrorHandler(wm_xr_error_handler, &error_customdata); + + { + const GHOST_TXrGraphicsBinding gpu_bindings_candidates[] = { + GHOST_kXrGraphicsOpenGL, +#ifdef WIN32 + GHOST_kXrGraphicsD3D11, +#endif + }; + GHOST_XrContextCreateInfo create_info = { + .gpu_binding_candidates = gpu_bindings_candidates, + .gpu_binding_candidates_count = ARRAY_SIZE(gpu_bindings_candidates), + }; + GHOST_XrContextHandle context; + + if (G.debug & G_DEBUG_XR) { + create_info.context_flag |= GHOST_kXrContextDebug; + } + if (G.debug & G_DEBUG_XR_TIME) { + create_info.context_flag |= GHOST_kXrContextDebugTime; + } + + if (!(context = GHOST_XrContextCreate(&create_info))) { + return false; + } + + /* Set up context callbacks */ + GHOST_XrGraphicsContextBindFuncs(context, + wm_xr_session_gpu_binding_context_create, + wm_xr_session_gpu_binding_context_destroy); + GHOST_XrDrawViewFunc(context, wm_xr_draw_view); + + if (!wm->xr.runtime) { + wm->xr.runtime = wm_xr_runtime_data_create(); + wm->xr.runtime->context = context; + } + } + BLI_assert(wm->xr.runtime && wm->xr.runtime->context); + + return true; +} + +void wm_xr_exit(wmWindowManager *wm) +{ + if (wm->xr.runtime != NULL) { + wm_xr_runtime_data_free(&wm->xr.runtime); + } + if (wm->xr.session_settings.shading.prop) { + IDP_FreeProperty(wm->xr.session_settings.shading.prop); + wm->xr.session_settings.shading.prop = NULL; + } +} + +bool wm_xr_events_handle(wmWindowManager *wm) +{ + if (wm->xr.runtime && wm->xr.runtime->context) { + return GHOST_XrEventsHandle(wm->xr.runtime->context); + } + return false; +} + +/** \} */ /* XR-Context */ + +/* -------------------------------------------------------------------- */ +/** \name XR Runtime Data + * + * \{ */ + +wmXrRuntimeData *wm_xr_runtime_data_create(void) +{ + wmXrRuntimeData *runtime = MEM_callocN(sizeof(*runtime), __func__); + return runtime; +} + +void wm_xr_runtime_data_free(wmXrRuntimeData **runtime) +{ + /* Note that this function may be called twice, because of an indirect recursion: If a session is + * running while WM-XR calls this function, calling GHOST_XrContextDestroy() will call this + * again, because it's also set as the session exit callback. So NULL-check and NULL everything + * that is freed here. */ + + /* We free all runtime XR data here, so if the context is still alive, destroy it. */ + if ((*runtime)->context != NULL) { + GHOST_XrContextHandle context = (*runtime)->context; + /* Prevent recursive GHOST_XrContextDestroy() call by NULL'ing the context pointer before the + * first call, see comment above. */ + (*runtime)->context = NULL; + GHOST_XrContextDestroy(context); + } + MEM_SAFE_FREE(*runtime); +} + +static void wm_xr_base_pose_calc(const Scene *scene, + const XrSessionSettings *settings, + GHOST_XrPose *r_base_pose) +{ + const Object *base_pose_object = ((settings->base_pose_type == XR_BASE_POSE_OBJECT) && + settings->base_pose_object) ? + settings->base_pose_object : + scene->camera; + + if (settings->base_pose_type == XR_BASE_POSE_CUSTOM) { + float tmp_quatx[4], tmp_quatz[4]; + + copy_v3_v3(r_base_pose->position, settings->base_pose_location); + axis_angle_to_quat_single(tmp_quatx, 'X', M_PI_2); + axis_angle_to_quat_single(tmp_quatz, 'Z', settings->base_pose_angle); + mul_qt_qtqt(r_base_pose->orientation_quat, tmp_quatz, tmp_quatx); + } + else if (base_pose_object) { + float tmp_quat[4]; + float tmp_eul[3]; + + mat4_to_loc_quat(r_base_pose->position, tmp_quat, base_pose_object->obmat); + + /* Only use rotation around Z-axis to align view with floor. */ + quat_to_eul(tmp_eul, tmp_quat); + tmp_eul[0] = M_PI_2; + tmp_eul[1] = 0; + eul_to_quat(r_base_pose->orientation_quat, tmp_eul); + } + else { + copy_v3_fl(r_base_pose->position, 0.0f); + axis_angle_to_quat_single(r_base_pose->orientation_quat, 'X', M_PI_2); + } +} + +static void wm_xr_draw_data_populate(const wmXrSessionState *state, + const GHOST_XrDrawViewInfo *draw_view, + const XrSessionSettings *settings, + const Scene *scene, + wmXrDrawData *r_draw_data) +{ + const bool position_tracking_toggled = ((state->prev_settings_flag & + XR_SESSION_USE_POSITION_TRACKING) != + (settings->flag & XR_SESSION_USE_POSITION_TRACKING)); + const bool use_position_tracking = settings->flag & XR_SESSION_USE_POSITION_TRACKING; + + memset(r_draw_data, 0, sizeof(*r_draw_data)); + + wm_xr_base_pose_calc(scene, settings, &r_draw_data->base_pose); + + if (position_tracking_toggled || !state->is_view_data_set) { + if (use_position_tracking) { + copy_v3_fl(r_draw_data->eye_position_ofs, 0.0f); + } + else { + /* Store the current local offset (local pose) so that we can apply that to the eyes. This + * way the eyes stay exactly where they are when disabling positional tracking. */ + copy_v3_v3(r_draw_data->eye_position_ofs, draw_view->local_pose.position); + } + } + else if (!use_position_tracking) { + /* Keep previous offset when positional tracking is disabled. */ + copy_v3_v3(r_draw_data->eye_position_ofs, state->prev_eye_position_ofs); + } +} + +/** + * Update information that is only stored for external state queries. E.g. for Python API to + * request the current (as in, last known) viewer pose. + */ +static void wm_xr_session_state_update(wmXrSessionState *state, + const GHOST_XrDrawViewInfo *draw_view, + const XrSessionSettings *settings, + const wmXrDrawData *draw_data) +{ + GHOST_XrPose viewer_pose; + const bool use_position_tracking = settings->flag & XR_SESSION_USE_POSITION_TRACKING; + + mul_qt_qtqt(viewer_pose.orientation_quat, + draw_data->base_pose.orientation_quat, + draw_view->local_pose.orientation_quat); + copy_v3_v3(viewer_pose.position, draw_data->base_pose.position); + /* The local pose and the eye pose (which is copied from an earlier local pose) both are view + * space, so Y-up. In this case we need them in regular Z-up. */ + viewer_pose.position[0] += draw_data->eye_position_ofs[0]; + viewer_pose.position[1] -= draw_data->eye_position_ofs[2]; + viewer_pose.position[2] += draw_data->eye_position_ofs[1]; + if (use_position_tracking) { + viewer_pose.position[0] += draw_view->local_pose.position[0]; + viewer_pose.position[1] -= draw_view->local_pose.position[2]; + viewer_pose.position[2] += draw_view->local_pose.position[1]; + } + + copy_v3_v3(state->viewer_pose.position, viewer_pose.position); + copy_qt_qt(state->viewer_pose.orientation_quat, viewer_pose.orientation_quat); + wm_xr_pose_to_viewmat(&viewer_pose, state->viewer_viewmat); + /* No idea why, but multiplying by two seems to make it match the VR view more. */ + state->focal_len = 2.0f * + fov_to_focallength(draw_view->fov.angle_right - draw_view->fov.angle_left, + DEFAULT_SENSOR_WIDTH); + + copy_v3_v3(state->prev_eye_position_ofs, draw_data->eye_position_ofs); + state->prev_settings_flag = settings->flag; + state->is_view_data_set = true; +} + +wmXrSessionState *WM_xr_session_state_handle_get(const wmXrData *xr) +{ + return xr->runtime ? &xr->runtime->session_state : NULL; +} + +bool WM_xr_session_state_viewer_pose_location_get(const wmXrData *xr, float r_location[3]) +{ + if (!WM_xr_session_is_ready(xr) || !xr->runtime->session_state.is_view_data_set) { + zero_v3(r_location); + return false; + } + + copy_v3_v3(r_location, xr->runtime->session_state.viewer_pose.position); + return true; +} + +bool WM_xr_session_state_viewer_pose_rotation_get(const wmXrData *xr, float r_rotation[4]) +{ + if (!WM_xr_session_is_ready(xr) || !xr->runtime->session_state.is_view_data_set) { + unit_qt(r_rotation); + return false; + } + + copy_v4_v4(r_rotation, xr->runtime->session_state.viewer_pose.orientation_quat); + return true; +} + +bool WM_xr_session_state_viewer_pose_matrix_info_get(const wmXrData *xr, + float r_viewmat[4][4], + float *r_focal_len) +{ + if (!WM_xr_session_is_ready(xr) || !xr->runtime->session_state.is_view_data_set) { + unit_m4(r_viewmat); + *r_focal_len = 0.0f; + return false; + } + + copy_m4_m4(r_viewmat, xr->runtime->session_state.viewer_viewmat); + *r_focal_len = xr->runtime->session_state.focal_len; + + return true; +} + +/** \} */ /* XR Runtime Data */ + +/* -------------------------------------------------------------------- */ +/** \name XR-Session + * + * \{ */ + +void *wm_xr_session_gpu_binding_context_create(GHOST_TXrGraphicsBinding graphics_binding) +{ + wmSurface *surface = wm_xr_session_surface_create(G_MAIN->wm.first, graphics_binding); + wmXrSurfaceData *data = surface->customdata; + + wm_surface_add(surface); + + /* Some regions may need to redraw with updated session state after the session is entirely up + * and running. */ + WM_main_add_notifier(NC_WM | ND_XR_DATA_CHANGED, NULL); + + return data->secondary_ghost_ctx ? data->secondary_ghost_ctx : surface->ghost_ctx; +} + +void wm_xr_session_gpu_binding_context_destroy(GHOST_TXrGraphicsBinding UNUSED(graphics_lib), + GHOST_ContextHandle UNUSED(context)) +{ + if (g_xr_surface) { /* Might have been freed already */ + wm_surface_remove(g_xr_surface); + } + + wm_window_reset_drawable(); + + /* Some regions may need to redraw with updated session state after the session is entirely + * stopped. */ + WM_main_add_notifier(NC_WM | ND_XR_DATA_CHANGED, NULL); +} + +static void wm_xr_session_exit_cb(void *customdata) +{ + wmXrData *xr_data = customdata; + + xr_data->runtime->session_state.is_started = false; + if (xr_data->runtime->exit_fn) { + xr_data->runtime->exit_fn(xr_data); + } + + /* Free the entire runtime data (including session state and context), to play safe. */ + wm_xr_runtime_data_free(&xr_data->runtime); +} + +static void wm_xr_session_begin_info_create(wmXrData *xr_data, + GHOST_XrSessionBeginInfo *r_begin_info) +{ + /* WM-XR exit function, does some own stuff and calls callback passed to wm_xr_session_toggle(), + * to allow external code to execute its own session-exit logic. */ + r_begin_info->exit_fn = wm_xr_session_exit_cb; + r_begin_info->exit_customdata = xr_data; +} + +void wm_xr_session_toggle(wmWindowManager *wm, wmXrSessionExitFn session_exit_fn) +{ + wmXrData *xr_data = &wm->xr; + + if (WM_xr_session_exists(xr_data)) { + GHOST_XrSessionEnd(xr_data->runtime->context); + } + else { + GHOST_XrSessionBeginInfo begin_info; + + xr_data->runtime->session_state.is_started = true; + xr_data->runtime->exit_fn = session_exit_fn; + + wm_xr_session_begin_info_create(xr_data, &begin_info); + GHOST_XrSessionStart(xr_data->runtime->context, &begin_info); + } +} + +/** + * Check if the XR-Session was triggered. + * If an error happened while trying to start a session, this returns false too. + */ +bool WM_xr_session_exists(const wmXrData *xr) +{ + return xr->runtime && xr->runtime->context && xr->runtime->session_state.is_started; +} + +/** + * Check if the session is running, according to the OpenXR definition. + */ +bool WM_xr_session_is_ready(const wmXrData *xr) +{ + return WM_xr_session_exists(xr) && GHOST_XrSessionIsRunning(xr->runtime->context); +} + +/** \} */ /* XR-Session */ + +/* -------------------------------------------------------------------- */ +/** \name XR-Session Surface + * + * A wmSurface is used to manage drawing of the VR viewport. It's created and destroyed with the + * session. + * + * \{ */ + +/** + * \brief Call Ghost-XR to draw a frame + * + * Draw callback for the XR-session surface. It's expected to be called on each main loop iteration + * and tells Ghost-XR to submit a new frame by drawing its views. Note that for drawing each view, + * #wm_xr_draw_view() will be called through Ghost-XR (see GHOST_XrDrawViewFunc()). + */ +static void wm_xr_session_surface_draw(bContext *C) +{ + wmXrSurfaceData *surface_data = g_xr_surface->customdata; + wmWindowManager *wm = CTX_wm_manager(C); + + if (!GHOST_XrSessionIsRunning(wm->xr.runtime->context)) { + return; + } + DRW_xr_drawing_begin(); + GHOST_XrSessionDrawViews(wm->xr.runtime->context, C); + GPU_offscreen_unbind(surface_data->offscreen, false); + DRW_xr_drawing_end(); +} + +static void wm_xr_session_free_data(wmSurface *surface) +{ + wmXrSurfaceData *data = surface->customdata; + + if (data->secondary_ghost_ctx) { +#ifdef WIN32 + if (data->gpu_binding_type == GHOST_kXrGraphicsD3D11) { + WM_directx_context_dispose(data->secondary_ghost_ctx); + } +#endif + } + if (data->viewport) { + GPU_viewport_free(data->viewport); + } + if (data->offscreen) { + GPU_offscreen_free(data->offscreen); + } + + MEM_freeN(surface->customdata); + + g_xr_surface = NULL; +} + +static bool wm_xr_session_surface_offscreen_ensure(const GHOST_XrDrawViewInfo *draw_view) +{ + wmXrSurfaceData *surface_data = g_xr_surface->customdata; + const bool size_changed = surface_data->offscreen && + (GPU_offscreen_width(surface_data->offscreen) != draw_view->width) && + (GPU_offscreen_height(surface_data->offscreen) != draw_view->height); + char err_out[256] = "unknown"; + bool failure = false; + + if (surface_data->offscreen) { + BLI_assert(surface_data->viewport); + + if (!size_changed) { + return true; + } + GPU_viewport_free(surface_data->viewport); + GPU_offscreen_free(surface_data->offscreen); + } + + if (!(surface_data->offscreen = GPU_offscreen_create( + draw_view->width, draw_view->height, 0, true, false, err_out))) { + failure = true; + } + + if (failure) { + /* Pass. */ + } + else if (!(surface_data->viewport = GPU_viewport_create())) { + GPU_offscreen_free(surface_data->offscreen); + failure = true; + } + + if (failure) { + CLOG_ERROR(&LOG, "Failed to get buffer, %s\n", err_out); + return false; + } + + return true; +} + +wmSurface *wm_xr_session_surface_create(wmWindowManager *UNUSED(wm), unsigned int gpu_binding_type) +{ + if (g_xr_surface) { + BLI_assert(false); + return g_xr_surface; + } + + wmSurface *surface = MEM_callocN(sizeof(*surface), __func__); + wmXrSurfaceData *data = MEM_callocN(sizeof(*data), "XrSurfaceData"); + +#ifndef WIN32 + BLI_assert(gpu_binding_type == GHOST_kXrGraphicsOpenGL); +#endif + + surface->draw = wm_xr_session_surface_draw; + surface->free_data = wm_xr_session_free_data; + + data->gpu_binding_type = gpu_binding_type; + surface->customdata = data; + + surface->ghost_ctx = DRW_xr_opengl_context_get(); + + switch (gpu_binding_type) { + case GHOST_kXrGraphicsOpenGL: + break; +#ifdef WIN32 + case GHOST_kXrGraphicsD3D11: + data->secondary_ghost_ctx = WM_directx_context_create(); + break; +#endif + } + + surface->gpu_ctx = DRW_xr_gpu_context_get(); + + g_xr_surface = surface; + + return surface; +} + +/** \} */ /* XR-Session Surface */ + +/* -------------------------------------------------------------------- */ +/** \name XR Drawing + * + * \{ */ + +void wm_xr_pose_to_viewmat(const GHOST_XrPose *pose, float r_viewmat[4][4]) +{ + float iquat[4]; + invert_qt_qt_normalized(iquat, pose->orientation_quat); + quat_to_mat4(r_viewmat, iquat); + translate_m4(r_viewmat, -pose->position[0], -pose->position[1], -pose->position[2]); +} + +static void wm_xr_draw_matrices_create(const wmXrDrawData *draw_data, + const GHOST_XrDrawViewInfo *draw_view, + const XrSessionSettings *session_settings, + float r_view_mat[4][4], + float r_proj_mat[4][4]) +{ + GHOST_XrPose eye_pose; + + copy_qt_qt(eye_pose.orientation_quat, draw_view->eye_pose.orientation_quat); + copy_v3_v3(eye_pose.position, draw_view->eye_pose.position); + add_v3_v3(eye_pose.position, draw_data->eye_position_ofs); + if ((session_settings->flag & XR_SESSION_USE_POSITION_TRACKING) == 0) { + sub_v3_v3(eye_pose.position, draw_view->local_pose.position); + } + + perspective_m4_fov(r_proj_mat, + draw_view->fov.angle_left, + draw_view->fov.angle_right, + draw_view->fov.angle_up, + draw_view->fov.angle_down, + session_settings->clip_start, + session_settings->clip_end); + + float eye_mat[4][4]; + float base_mat[4][4]; + + wm_xr_pose_to_viewmat(&eye_pose, eye_mat); + /* Calculate the base pose matrix (in world space!). */ + wm_xr_pose_to_viewmat(&draw_data->base_pose, base_mat); + + mul_m4_m4m4(r_view_mat, eye_mat, base_mat); +} + +static void wm_xr_draw_viewport_buffers_to_active_framebuffer( + const wmXrSurfaceData *surface_data, const GHOST_XrDrawViewInfo *draw_view) +{ + const bool is_upside_down = surface_data->secondary_ghost_ctx && + GHOST_isUpsideDownContext(surface_data->secondary_ghost_ctx); + rcti rect = {.xmin = 0, .ymin = 0, .xmax = draw_view->width - 1, .ymax = draw_view->height - 1}; + + wmViewport(&rect); + + /* For upside down contexts, draw with inverted y-values. */ + if (is_upside_down) { + SWAP(int, rect.ymin, rect.ymax); + } + GPU_viewport_draw_to_screen_ex(surface_data->viewport, &rect, draw_view->expects_srgb_buffer); +} + +/** + * \brief Draw a viewport for a single eye. + * + * This is the main viewport drawing function for VR sessions. It's assigned to Ghost-XR as a + * callback (see GHOST_XrDrawViewFunc()) and executed for each view (read: eye). + */ +void wm_xr_draw_view(const GHOST_XrDrawViewInfo *draw_view, void *customdata) +{ + bContext *C = customdata; + wmWindowManager *wm = CTX_wm_manager(C); + wmXrSurfaceData *surface_data = g_xr_surface->customdata; + wmXrSessionState *session_state = &wm->xr.runtime->session_state; + XrSessionSettings *settings = &wm->xr.session_settings; + wmXrDrawData draw_data; + Scene *scene = CTX_data_scene(C); + + const int display_flags = V3D_OFSDRAW_OVERRIDE_SCENE_SETTINGS | settings->draw_flags; + + float viewmat[4][4], winmat[4][4]; + + BLI_assert(WM_xr_session_is_ready(&wm->xr)); + + wm_xr_draw_data_populate(session_state, draw_view, settings, scene, &draw_data); + wm_xr_draw_matrices_create(&draw_data, draw_view, settings, viewmat, winmat); + wm_xr_session_state_update(session_state, draw_view, settings, &draw_data); + + if (!wm_xr_session_surface_offscreen_ensure(draw_view)) { + return; + } + + /* In case a framebuffer is still bound from drawing the last eye. */ + GPU_framebuffer_restore(); + /* Some systems have drawing glitches without this. */ + GPU_clear(GPU_DEPTH_BIT); + + /* Draws the view into the surface_data->viewport's framebuffers */ + ED_view3d_draw_offscreen_simple(CTX_data_ensure_evaluated_depsgraph(C), + scene, + &wm->xr.session_settings.shading, + wm->xr.session_settings.shading.type, + draw_view->width, + draw_view->height, + display_flags, + viewmat, + winmat, + settings->clip_start, + settings->clip_end, + false, + true, + true, + NULL, + false, + surface_data->offscreen, + surface_data->viewport); + + /* The draw-manager uses both GPUOffscreen and GPUViewport to manage frame and texture buffers. A + * call to GPU_viewport_draw_to_screen() is still needed to get the final result from the + * viewport buffers composited together and potentially color managed for display on screen. + * It needs a bound framebuffer to draw into, for which we simply reuse the GPUOffscreen one. + * + * In a next step, Ghost-XR will use the the currently bound framebuffer to retrieve the image to + * be submitted to the OpenXR swapchain. So do not un-bind the offscreen yet! */ + + GPU_offscreen_bind(surface_data->offscreen, false); + + wm_xr_draw_viewport_buffers_to_active_framebuffer(surface_data, draw_view); +} + +/** \} */ /* XR Drawing */ diff --git a/source/blender/windowmanager/wm.h b/source/blender/windowmanager/wm.h index 5bb9de87e82..22c01df5d3b 100644 --- a/source/blender/windowmanager/wm.h +++ b/source/blender/windowmanager/wm.h @@ -98,4 +98,14 @@ void wm_stereo3d_set_cancel(bContext *C, wmOperator *op); void wm_open_init_load_ui(wmOperator *op, bool use_prefs); void wm_open_init_use_scripts(wmOperator *op, bool use_prefs); +#ifdef WITH_XR_OPENXR +typedef void (*wmXrSessionExitFn)(const wmXrData *xr_data); + +/* wm_xr.c */ +bool wm_xr_init(wmWindowManager *wm); +void wm_xr_exit(wmWindowManager *wm); +void wm_xr_session_toggle(wmWindowManager *wm, wmXrSessionExitFn session_exit_fn); +bool wm_xr_events_handle(wmWindowManager *wm); +#endif + #endif /* __WM_H__ */ diff --git a/source/blender/windowmanager/wm_surface.h b/source/blender/windowmanager/wm_surface.h new file mode 100644 index 00000000000..98d67c55619 --- /dev/null +++ b/source/blender/windowmanager/wm_surface.h @@ -0,0 +1,57 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup wm + * + * \name WM-Surface + * + * Container to manage painting in an offscreen context. + */ + +#ifndef __WM_SURFACE_H__ +#define __WM_SURFACE_H__ + +struct bContext; + +typedef struct wmSurface { + struct wmSurface *next, *prev; + + GHOST_ContextHandle ghost_ctx; + struct GPUContext *gpu_ctx; + + void *customdata; + + void (*draw)(struct bContext *); + /** Free customdata, not the surface itself (done by wm_surface API) */ + void (*free_data)(struct wmSurface *); +} wmSurface; + +/* Create/Free */ +void wm_surface_add(wmSurface *surface); +void wm_surface_remove(wmSurface *surface); +void wm_surfaces_free(void); + +/* Utils */ +void wm_surfaces_iter(struct bContext *C, void (*cb)(bContext *, wmSurface *)); + +/* Drawing */ +void wm_surface_make_drawable(wmSurface *surface); +void wm_surface_clear_drawable(void); +void wm_surface_set_drawable(wmSurface *surface, bool activate); +void wm_surface_reset_drawable(void); + +#endif /* __WM_SURFACE_H__ */ |