VR: Initial Virtual Reality support - Milestone 1, Scene Inspection
authorJulian Eisel <julian@blender.org>
Tue, 17 Mar 2020 19:20:55 +0000 (20:20 +0100)
committerJulian Eisel <julian@blender.org>
Tue, 17 Mar 2020 20:42:44 +0000 (21:42 +0100)
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

74 files changed:
CMakeLists.txt
intern/ghost/CMakeLists.txt
intern/ghost/GHOST_C-api.h
intern/ghost/GHOST_IContext.h
intern/ghost/GHOST_Types.h
intern/ghost/intern/GHOST_C-api.cpp
intern/ghost/intern/GHOST_Context.h
intern/ghost/intern/GHOST_IXrGraphicsBinding.h
intern/ghost/intern/GHOST_XrContext.cpp
intern/ghost/intern/GHOST_XrContext.h
intern/ghost/intern/GHOST_XrGraphicsBinding.cpp
intern/ghost/intern/GHOST_XrSession.cpp
intern/ghost/intern/GHOST_XrSession.h
release/windows/batch/blender_oculus.cmd [new file with mode: 0644]
release/windows/batch/oculus.json [new file with mode: 0644]
source/blender/CMakeLists.txt
source/blender/blenkernel/BKE_global.h
source/blender/blenkernel/CMakeLists.txt
source/blender/blenkernel/intern/object.c
source/blender/blenlib/BLI_math_geom.h
source/blender/blenlib/intern/math_geom.c
source/blender/blenloader/intern/readfile.c
source/blender/blenloader/intern/versioning_280.c
source/blender/blenloader/intern/writefile.c
source/blender/draw/CMakeLists.txt
source/blender/draw/DRW_engine.h
source/blender/draw/intern/draw_manager.c
source/blender/editors/include/ED_view3d.h
source/blender/editors/include/ED_view3d_offscreen.h
source/blender/editors/screen/screen_ops.c
source/blender/editors/sculpt_paint/paint_cursor.c
source/blender/editors/space_view3d/CMakeLists.txt
source/blender/editors/space_view3d/space_view3d.c
source/blender/editors/space_view3d/view3d_draw.c
source/blender/editors/space_view3d/view3d_edit.c
source/blender/editors/space_view3d/view3d_fly.c
source/blender/editors/space_view3d/view3d_gizmo_navigate.c
source/blender/editors/space_view3d/view3d_utils.c
source/blender/editors/space_view3d/view3d_view.c
source/blender/editors/space_view3d/view3d_walk.c
source/blender/gpu/GPU_viewport.h
source/blender/gpu/intern/gpu_viewport.c
source/blender/makesdna/DNA_view3d_enums.h
source/blender/makesdna/DNA_view3d_types.h
source/blender/makesdna/DNA_windowmanager_types.h
source/blender/makesdna/DNA_xr_types.h [new file with mode: 0644]
source/blender/makesdna/intern/makesdna.c
source/blender/makesrna/RNA_access.h
source/blender/makesrna/intern/CMakeLists.txt
source/blender/makesrna/intern/makesrna.c
source/blender/makesrna/intern/rna_internal.h
source/blender/makesrna/intern/rna_space.c
source/blender/makesrna/intern/rna_wm.c
source/blender/makesrna/intern/rna_wm_gizmo.c
source/blender/makesrna/intern/rna_xr.c [new file with mode: 0644]
source/blender/python/gpu/gpu_py_offscreen.c
source/blender/windowmanager/CMakeLists.txt
source/blender/windowmanager/WM_api.h
source/blender/windowmanager/WM_types.h
source/blender/windowmanager/gizmo/WM_gizmo_types.h
source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c
source/blender/windowmanager/intern/wm.c
source/blender/windowmanager/intern/wm_draw.c
source/blender/windowmanager/intern/wm_init_exit.c
source/blender/windowmanager/intern/wm_operators.c
source/blender/windowmanager/intern/wm_surface.c [new file with mode: 0644]
source/blender/windowmanager/intern/wm_window.c
source/blender/windowmanager/intern/wm_xr.c [new file with mode: 0644]
source/blender/windowmanager/wm.h
source/blender/windowmanager/wm_surface.h [new file with mode: 0644]
source/creator/CMakeLists.txt
source/creator/creator_args.c
tests/python/bl_load_addons.py
tests/python/bl_load_py_modules.py

index b78b0e7d64776162835be15c33820bc28db43918..9f4d0c208860b5d58617da87b1b0efa6bb5dceb1 100644 (file)
@@ -186,8 +186,7 @@ if(APPLE)
   option(WITH_XR_OPENXR   "Enable VR features through the OpenXR specification" OFF)
   mark_as_advanced(WITH_XR_OPENXR)
 else()
-  # Disabled until there's more than just the build system stuff. Should be enabled soon.
-  option(WITH_XR_OPENXR   "Enable VR features through the OpenXR specification" OFF)
+  option(WITH_XR_OPENXR   "Enable VR features through the OpenXR specification" ON)
 endif()
 
 # Compositor
index 07d98475c00e5794cded7bc41c5e5f86fa8ef903..611da8d6a4466147070bd20291aeaaf85ec0e487 100644 (file)
@@ -377,6 +377,9 @@ if(WITH_XR_OPENXR)
   list(APPEND INC_SYS
     ${XR_OPENXR_SDK_INCLUDE_DIR}
   )
+  list(APPEND LIB
+    ${XR_OPENXR_SDK_LIBRARIES}
+  )
 
   set(XR_PLATFORM_DEFINES -DXR_USE_GRAPHICS_API_OPENGL)
 
index aafe374a93b3fcf73643605395c821b375c696dd..aba5b5f733b3a00ad2017b4ea4e2893e13f09c2f 100644 (file)
@@ -756,6 +756,18 @@ extern GHOST_TSuccess GHOST_ActivateOpenGLContext(GHOST_ContextHandle contexthan
  */
 extern GHOST_TSuccess GHOST_ReleaseOpenGLContext(GHOST_ContextHandle contexthandle);
 
+/**
+ * Get the OpenGL framebuffer handle that serves as a default framebuffer.
+ */
+extern unsigned int GHOST_GetContextDefaultOpenGLFramebuffer(GHOST_ContextHandle contexthandle);
+
+/**
+ * Returns whether a context is rendered upside down compared to OpenGL. This only needs to be
+ * called if there's a non-OpenGL context, which is really the exception.
+ * So generally, this does not need to be called.
+ */
+extern int GHOST_isUpsideDownContext(GHOST_ContextHandle contexthandle);
+
 /**
  * Get the OpenGL framebuffer handle that serves as a default framebuffer.
  */
index a341e18ca0a67a424d40ee14f79ef5c4129c66d2..33422b8e351df5075a281ab29137974276a7e882 100644 (file)
@@ -56,6 +56,15 @@ class GHOST_IContext {
    */
   virtual GHOST_TSuccess releaseDrawingContext() = 0;
 
+  virtual unsigned int getDefaultFramebuffer() = 0;
+
+  virtual GHOST_TSuccess swapBuffers() = 0;
+
+  /**
+   * Returns if the window is rendered upside down compared to OpenGL.
+   */
+  virtual bool isUpsideDown() const = 0;
+
 #ifdef WITH_CXX_GUARDEDALLOC
   MEM_CXX_CLASS_ALLOC_FUNCS("GHOST:GHOST_IContext")
 #endif
index adda782b96da4a5990493e3d4355f86e31f578b1..70c4d3ef00c479e05422344c7c363ac0d20b9e38 100644 (file)
@@ -598,6 +598,8 @@ typedef void (*GHOST_TimerProcPtr)(struct GHOST_TimerTaskHandle__ *task, GHOST_T
 
 #ifdef WITH_XR_OPENXR
 
+struct GHOST_XrError;
+struct GHOST_XrDrawViewInfo;
 /**
  * The XR view (i.e. the OpenXR runtime) may require a different graphics library than OpenGL. An
  * offscreen texture of the viewport will then be drawn into using OpenGL, but the final texture
@@ -605,7 +607,7 @@ typedef void (*GHOST_TimerProcPtr)(struct GHOST_TimerTaskHandle__ *task, GHOST_T
  *
  * This enum defines the possible graphics bindings to attempt to enable.
  */
-typedef enum {
+typedef enum GHOST_TXrGraphicsBinding {
   GHOST_kXrGraphicsUnknown = 0,
   GHOST_kXrGraphicsOpenGL,
 #  ifdef WIN32
@@ -614,6 +616,16 @@ typedef enum {
   /* For later */
   //  GHOST_kXrGraphicsVulkan,
 } GHOST_TXrGraphicsBinding;
+
+typedef void (*GHOST_XrErrorHandlerFn)(const struct GHOST_XrError *);
+
+typedef void (*GHOST_XrSessionExitFn)(void *customdata);
+
+typedef void *(*GHOST_XrGraphicsContextBindFn)(enum GHOST_TXrGraphicsBinding graphics_lib);
+typedef void (*GHOST_XrGraphicsContextUnbindFn)(enum GHOST_TXrGraphicsBinding graphics_lib,
+                                                GHOST_ContextHandle graphics_context);
+typedef void (*GHOST_XrDrawViewFn)(const struct GHOST_XrDrawViewInfo *draw_view, void *customdata);
+
 /* An array of GHOST_TXrGraphicsBinding items defining the candidate bindings to use. The first
  * available candidate will be chosen, so order defines priority. */
 typedef const GHOST_TXrGraphicsBinding *GHOST_XrGraphicsBindingCandidates;
@@ -638,13 +650,17 @@ typedef struct {
 
 typedef struct {
   GHOST_XrPose base_pose;
+
+  GHOST_XrSessionExitFn exit_fn;
+  void *exit_customdata;
 } GHOST_XrSessionBeginInfo;
 
-typedef struct {
+typedef struct GHOST_XrDrawViewInfo {
   int ofsx, ofsy;
   int width, height;
 
-  GHOST_XrPose pose;
+  GHOST_XrPose eye_pose;
+  GHOST_XrPose local_pose;
 
   struct {
     float angle_left, angle_right;
@@ -655,19 +671,12 @@ typedef struct {
   char expects_srgb_buffer;
 } GHOST_XrDrawViewInfo;
 
-typedef struct {
+typedef struct GHOST_XrError {
   const char *user_message;
 
   void *customdata;
 } GHOST_XrError;
 
-typedef void (*GHOST_XrErrorHandlerFn)(const GHOST_XrError *);
-
-typedef void *(*GHOST_XrGraphicsContextBindFn)(GHOST_TXrGraphicsBinding graphics_lib);
-typedef void (*GHOST_XrGraphicsContextUnbindFn)(GHOST_TXrGraphicsBinding graphics_lib,
-                                                void *graphics_context);
-typedef void (*GHOST_XrDrawViewFn)(const GHOST_XrDrawViewInfo *draw_view, void *customdata);
-
 #endif
 
 #endif  // __GHOST_TYPES_H__
index 60d20474a5af5d0405aa8b3a42301f1f7b76850d..d43a2637ad311925f9f99aa7112dc7607c977670 100644 (file)
@@ -709,6 +709,20 @@ GHOST_TSuccess GHOST_ReleaseOpenGLContext(GHOST_ContextHandle contexthandle)
   return context->releaseDrawingContext();
 }
 
+unsigned int GHOST_GetContextDefaultOpenGLFramebuffer(GHOST_ContextHandle contexthandle)
+{
+  GHOST_IContext *context = (GHOST_IContext *)contexthandle;
+
+  return context->getDefaultFramebuffer();
+}
+
+int GHOST_isUpsideDownContext(GHOST_ContextHandle contexthandle)
+{
+  GHOST_IContext *context = (GHOST_IContext *)contexthandle;
+
+  return context->isUpsideDown();
+}
+
 unsigned int GHOST_GetDefaultOpenGLFramebuffer(GHOST_WindowHandle windowhandle)
 {
   GHOST_IWindow *window = (GHOST_IWindow *)windowhandle;
index bbf6d6a510db25f623e65fdd0a02efd7ccca4c1c..0bd6f63d07ec113bef4b654066b3af7578efb6d9 100644 (file)
@@ -119,6 +119,14 @@ class GHOST_Context : public GHOST_IContext {
     return m_stereoVisual;
   }
 
+  /**
+   * Returns if the window is rendered upside down compared to OpenGL.
+   */
+  inline bool isUpsideDown() const
+  {
+    return false;
+  }
+
   /**
    * Gets the OpenGL framebuffer associated with the OpenGL context
    * \return The ID of an OpenGL framebuffer object.
index 19fe00cdad57774c0d7cc425e79ef6d200f5ac60..25281d3d0baf0ea22388628d1b922339238a533a 100644 (file)
@@ -41,6 +41,8 @@ class GHOST_IXrGraphicsBinding {
 #endif
   } oxr_binding;
 
+  virtual ~GHOST_IXrGraphicsBinding() = default;
+
   /**
    * Does __not__ require this object is initialized (can be called prior to
    * #initFromGhostContext). It's actually meant to be called first.
index 410837e9805419835f03475a80e2af4fed6ecf9d..d7b83114c85a02003387408a601a76bea6a06042 100644 (file)
@@ -454,10 +454,12 @@ GHOST_TXrGraphicsBinding GHOST_XrContext::determineGraphicsBindingTypeToEnable(
 
 void GHOST_XrContext::startSession(const GHOST_XrSessionBeginInfo *begin_info)
 {
+  m_custom_funcs.session_exit_fn = begin_info->exit_fn;
+  m_custom_funcs.session_exit_customdata = begin_info->exit_customdata;
+
   if (m_session == nullptr) {
     m_session = std::unique_ptr<GHOST_XrSession>(new GHOST_XrSession(this));
   }
-
   m_session->start(begin_info);
 }
 
index b361fb5caf88d5d920f64a5a071fca719f9d3132..5140ad7ea2722cf741df1741bf801a3b14d74cde 100644 (file)
@@ -33,6 +33,9 @@ struct GHOST_XrCustomFuncs {
   /** Function to release (possibly free) a graphics context. */
   GHOST_XrGraphicsContextUnbindFn gpu_ctx_unbind_fn = nullptr;
 
+  GHOST_XrSessionExitFn session_exit_fn = nullptr;
+  void *session_exit_customdata = nullptr;
+
   /** Custom per-view draw function for Blender side drawing. */
   GHOST_XrDrawViewFn draw_view_fn = nullptr;
 };
index ddc757b8f8a766d8da17d585e65c22d327b4a8b7..f094b0744a2973635f6d1c44d9a662d86c1a4c92 100644 (file)
@@ -116,6 +116,8 @@ class GHOST_XrGraphicsBindingOpenGL : public GHOST_IXrGraphicsBinding {
     oxr_binding.glx.glxDrawable = ctx_glx->m_window;
     oxr_binding.glx.glxContext = ctx_glx->m_context;
     oxr_binding.glx.visualid = visual_info->visualid;
+
+    XFree(visual_info);
 #elif defined(WIN32)
     GHOST_ContextWGL *ctx_wgl = static_cast<GHOST_ContextWGL *>(ghost_ctx);
 
index 1e2b8c0bc9d5526cca49f4475cfadde6e1ddff5d..a85bde3cab602e2d6878264874078c6344a41309 100644 (file)
@@ -43,6 +43,7 @@ struct OpenXRSessionData {
   /* Only stereo rendering supported now. */
   const XrViewConfigurationType view_type = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
   XrSpace reference_space;
+  XrSpace view_space;
   std::vector<XrView> views;
   std::vector<std::unique_ptr<GHOST_XrSwapchain>> swapchains;
 };
@@ -81,6 +82,8 @@ GHOST_XrSession::~GHOST_XrSession()
 
   m_oxr->session = XR_NULL_HANDLE;
   m_oxr->session_state = XR_SESSION_STATE_UNKNOWN;
+
+  m_context->getCustomFuncs().session_exit_fn(m_context->getCustomFuncs().session_exit_customdata);
 }
 
 /**
@@ -107,7 +110,7 @@ void GHOST_XrSession::initSystem()
  *
  * \{ */
 
-static void create_reference_space(OpenXRSessionData *oxr, const GHOST_XrPose *base_pose)
+static void create_reference_spaces(OpenXRSessionData *oxr, const GHOST_XrPose *base_pose)
 {
   XrReferenceSpaceCreateInfo create_info = {XR_TYPE_REFERENCE_SPACE_CREATE_INFO};
   create_info.poseInReferenceSpace.orientation.w = 1.0f;
@@ -138,6 +141,10 @@ static void create_reference_space(OpenXRSessionData *oxr, const GHOST_XrPose *b
 
   CHECK_XR(xrCreateReferenceSpace(oxr->session, &create_info, &oxr->reference_space),
            "Failed to create reference space.");
+
+  create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW;
+  CHECK_XR(xrCreateReferenceSpace(oxr->session, &create_info, &oxr->view_space),
+           "Failed to create view reference space.");
 }
 
 void GHOST_XrSession::start(const GHOST_XrSessionBeginInfo *begin_info)
@@ -184,7 +191,7 @@ void GHOST_XrSession::start(const GHOST_XrSessionBeginInfo *begin_info)
            "detailed error information to the command line.");
 
   prepareDrawing();
-  create_reference_space(m_oxr.get(), &begin_info->base_pose);
+  create_reference_spaces(m_oxr.get(), &begin_info->base_pose);
 }
 
 void GHOST_XrSession::requestEnd()
@@ -343,16 +350,22 @@ void GHOST_XrSession::draw(void *draw_customdata)
   endFrameDrawing(&layers);
 }
 
+static void copy_openxr_pose_to_ghost_pose(const XrPosef &oxr_pose, GHOST_XrPose &r_ghost_pose)
+{
+  /* Set and convert to Blender coodinate space. */
+  r_ghost_pose.position[0] = oxr_pose.position.x;
+  r_ghost_pose.position[1] = oxr_pose.position.y;
+  r_ghost_pose.position[2] = oxr_pose.position.z;
+  r_ghost_pose.orientation_quat[0] = oxr_pose.orientation.w;
+  r_ghost_pose.orientation_quat[1] = oxr_pose.orientation.x;
+  r_ghost_pose.orientation_quat[2] = oxr_pose.orientation.y;
+  r_ghost_pose.orientation_quat[3] = oxr_pose.orientation.z;
+}
+
 static void ghost_xr_draw_view_info_from_view(const XrView &view, GHOST_XrDrawViewInfo &r_info)
 {
   /* Set and convert to Blender coodinate space. */
-  r_info.pose.position[0] = view.pose.position.x;
-  r_info.pose.position[1] = view.pose.position.y;
-  r_info.pose.position[2] = view.pose.position.z;
-  r_info.pose.orientation_quat[0] = view.pose.orientation.w;
-  r_info.pose.orientation_quat[1] = view.pose.orientation.x;
-  r_info.pose.orientation_quat[2] = view.pose.orientation.y;
-  r_info.pose.orientation_quat[3] = view.pose.orientation.z;
+  copy_openxr_pose_to_ghost_pose(view.pose, r_info.eye_pose);
 
   r_info.fov.angle_left = view.fov.angleLeft;
   r_info.fov.angle_right = view.fov.angleRight;
@@ -370,6 +383,7 @@ static bool ghost_xr_draw_view_expects_srgb_buffer(const GHOST_XrContext *contex
 
 void GHOST_XrSession::drawView(GHOST_XrSwapchain &swapchain,
                                XrCompositionLayerProjectionView &r_proj_layer_view,
+                               XrSpaceLocation &view_location,
                                XrView &view,
                                void *draw_customdata)
 {
@@ -386,6 +400,7 @@ void GHOST_XrSession::drawView(GHOST_XrSwapchain &swapchain,
   draw_view_info.ofsy = r_proj_layer_view.subImage.imageRect.offset.y;
   draw_view_info.width = r_proj_layer_view.subImage.imageRect.extent.width;
   draw_view_info.height = r_proj_layer_view.subImage.imageRect.extent.height;
+  copy_openxr_pose_to_ghost_pose(view_location.pose, draw_view_info.local_pose);
   ghost_xr_draw_view_info_from_view(view, draw_view_info);
 
   /* Draw! */
@@ -401,6 +416,7 @@ XrCompositionLayerProjection GHOST_XrSession::drawLayer(
   XrViewLocateInfo viewloc_info = {XR_TYPE_VIEW_LOCATE_INFO};
   XrViewState view_state = {XR_TYPE_VIEW_STATE};
   XrCompositionLayerProjection layer = {XR_TYPE_COMPOSITION_LAYER_PROJECTION};
+  XrSpaceLocation view_location{XR_TYPE_SPACE_LOCATION};
   uint32_t view_count;
 
   viewloc_info.viewConfigurationType = m_oxr->view_type;
@@ -416,11 +432,17 @@ XrCompositionLayerProjection GHOST_XrSession::drawLayer(
            "Failed to query frame view and projection state.");
   assert(m_oxr->swapchains.size() == view_count);
 
+  CHECK_XR(
+      xrLocateSpace(
+          m_oxr->view_space, m_oxr->reference_space, viewloc_info.displayTime, &view_location),
+      "Failed to query frame view space");
+
   r_proj_layer_views.resize(view_count);
 
   for (uint32_t view_idx = 0; view_idx < view_count; view_idx++) {
     drawView(*m_oxr->swapchains[view_idx],
              r_proj_layer_views[view_idx],
+             view_location,
              m_oxr->views[view_idx],
              draw_customdata);
   }
@@ -479,7 +501,8 @@ void GHOST_XrSession::unbindGraphicsContext()
 {
   const GHOST_XrCustomFuncs &custom_funcs = m_context->getCustomFuncs();
   if (custom_funcs.gpu_ctx_unbind_fn) {
-    custom_funcs.gpu_ctx_unbind_fn(m_context->getGraphicsBindingType(), m_gpu_ctx);
+    custom_funcs.gpu_ctx_unbind_fn(m_context->getGraphicsBindingType(),
+                                   (GHOST_ContextHandle)m_gpu_ctx);
   }
   m_gpu_ctx = nullptr;
 }
index 3340385c1b6eb90813ab410b4ae3a5222a2c20ea..ef2da61df3d90fe5088922638a3fb72534d84717 100644 (file)
@@ -25,9 +25,9 @@
 #include <memory>
 
 class GHOST_XrContext;
+class GHOST_XrSwapchain;
 struct OpenXRSessionData;
 struct GHOST_XrDrawInfo;
-struct GHOST_XrSwapchain;
 
 class GHOST_XrSession {
  public:
@@ -74,6 +74,7 @@ class GHOST_XrSession {
       std::vector<XrCompositionLayerProjectionView> &r_proj_layer_views, void *draw_customdata);
   void drawView(GHOST_XrSwapchain &swapchain,
                 XrCompositionLayerProjectionView &r_proj_layer_view,
+                XrSpaceLocation &view_location,
                 XrView &view,
                 void *draw_customdata);
   void beginFrameDrawing();
diff --git a/release/windows/batch/blender_oculus.cmd b/release/windows/batch/blender_oculus.cmd
new file mode 100644 (file)
index 0000000..ffb725e
--- /dev/null
@@ -0,0 +1,14 @@
+@echo off
+
+REM Helper setting hints to get the OpenXR preview support enabled for Oculus.
+REM Of course this is not meant as a permanent solution. Oculus will likely provide a better setup at some point.
+
+echo Starting Blender with Oculus OpenXR support. This assumes the Oculus runtime
+echo is installed in the default location. If this is not the case, please adjust
+echo the path inside oculus.json.
+echo.
+echo Note that OpenXR support in Oculus is considered a preview. Use with care!
+echo.
+pause
+set XR_RUNTIME_JSON=%~dp0oculus.json
+blender
diff --git a/release/windows/batch/oculus.json b/release/windows/batch/oculus.json
new file mode 100644 (file)
index 0000000..ba8767f
--- /dev/null
@@ -0,0 +1,9 @@
+{
+    "file_format_version": "1.0.0",
+    "runtime":
+    {
+        "api_version": "1.0",
+        "name": "Oculus OpenXR",
+        "library_path": "c:\\Program Files\\Oculus\\Support\\oculus-runtime\\LibOVRRT64_1.dll"
+    }
+}
index b44b6db8804ecfd7f9d1123833757b46d9138b50..203b6da272f8655fb990c824c964f5e7e99a38f5 100644 (file)
@@ -90,6 +90,7 @@ set(SRC_DNA_INC
   ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_windowmanager_types.h
   ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_workspace_types.h
   ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_world_types.h
+  ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_xr_types.h
 )
 
 add_subdirectory(datatoc)
index fe050075bae4119aeb0cdc669ce672aed9d919d5..f6cae6d8a9c5ca072c86524040121b459c5e745b 100644 (file)
@@ -153,6 +153,8 @@ enum {
   G_DEBUG_IO = (1 << 17),                    /* IO Debugging (for Collada, ...)*/
   G_DEBUG_GPU_SHADERS = (1 << 18),           /* GLSL shaders */
   G_DEBUG_GPU_FORCE_WORKAROUNDS = (1 << 19), /* force gpu workarounds bypassing detections. */
+  G_DEBUG_XR = (1 << 20),                    /* XR/OpenXR messages */
+  G_DEBUG_XR_TIME = (1 << 21),               /* XR/OpenXR timing messages */
 
   G_DEBUG_GHOST = (1 << 20), /* Debug GHOST module. */
 };
index 923108240dd1bc25d5af5e8cbc9660400b816728..047901b4c8132fd01d69a8a250875415a9f0f77f 100644 (file)
@@ -667,6 +667,10 @@ if(WITH_TBB)
   )
 endif()
 
+if(WITH_XR_OPENXR)
+  add_definitions(-DWITH_XR_OPENXR)
+endif()
+
 # # Warnings as errors, this is too strict!
 # if(MSVC)
 #    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /WX")
index d7247518f9e9b2fa45033b605ca283aa0bca022b..fa3284c18d64b675f152c7c23cb92282bbda9715 100644 (file)
@@ -2680,7 +2680,8 @@ void BKE_object_workob_calc_parent(Depsgraph *depsgraph, Scene *scene, Object *o
 }
 
 /**
- * Applies the global transformation \a mat to the \a ob using a relative parent space if supplied.
+ * Applies the global transformation \a mat to the \a ob using a relative parent space if
+ * supplied.
  *
  * \param mat: the global transformation mat that the object should be set object to.
  * \param parent: the parent space in which this object will be set relative to
@@ -3183,7 +3184,8 @@ typedef struct ObTfmBack {
   float obmat[4][4];
   /** inverse result of parent, so that object doesn't 'stick' to parent. */
   float parentinv[4][4];
-  /** inverse result of constraints. doesn't include effect of parent or object local transform. */
+  /** inverse result of constraints. doesn't include effect of parent or object local transform.
+   */
   float constinv[4][4];
   /** inverse matrix of 'obmat' for during render, temporally: ipokeys of transform. */
   float imat[4][4];
index 534c25f6e01ec8edb5366fa1c651a858e80f6d78..2049f36857845db42f9007b4d82debf8da805a75 100644 (file)
@@ -636,6 +636,13 @@ void perspective_m4(float mat[4][4],
                     const float top,
                     const float nearClip,
                     const float farClip);
+void perspective_m4_fov(float mat[4][4],
+                        const float angle_left,
+                        const float angle_right,
+                        const float angle_up,
+                        const float angle_down,
+                        const float nearClip,
+                        const float farClip);
 void orthographic_m4(float mat[4][4],
                      const float left,
                      const float right,
index d54ec0f1ac127894a573cb222e0baf1b30942926..fa50bf202a83a4b850fa1a021a70e8799c3eb9f8 100644 (file)
@@ -4709,6 +4709,25 @@ void perspective_m4(float mat[4][4],
       mat[3][3] = 0.0f;
 }
 
+void perspective_m4_fov(float mat[4][4],
+                        const float angle_left,
+                        const float angle_right,
+                        const float angle_up,
+                        const float angle_down,
+                        const float nearClip,
+                        const float farClip)
+{
+  const float tan_angle_left = tanf(angle_left);
+  const float tan_angle_right = tanf(angle_right);
+  const float tan_angle_bottom = tanf(angle_up);
+  const float tan_angle_top = tanf(angle_down);
+
+  perspective_m4(
+      mat, tan_angle_left, tan_angle_right, tan_angle_top, tan_angle_bottom, nearClip, farClip);
+  mat[0][0] /= nearClip;
+  mat[1][1] /= nearClip;
+}
+
 /* translate a matrix created by orthographic_m4 or perspective_m4 in XY coords
  * (used to jitter the view) */
 void window_translate_m4(float winmat[4][4], float perspmat[4][4], const float x, const float y)
index c3fba697bd34830c6c15dabf9ed7a83b0c55e3a2..f1f274f97d5f7d8e615b6acbeb14ccc8885f9e53 100644 (file)
@@ -7187,6 +7187,7 @@ static void direct_link_region(FileData *fd, ARegion *region, int spacetype)
         rv3d->smooth_timer = NULL;
 
         rv3d->rflag &= ~(RV3D_NAVIGATING | RV3D_PAINTING);
+        rv3d->runtime_viewlock = 0;
       }
     }
   }
@@ -7277,7 +7278,10 @@ static void direct_link_area(FileData *fd, ScrArea *area)
         direct_link_gpencil(fd, v3d->gpd);
       }
       v3d->localvd = newdataadr(fd, v3d->localvd);
+
+      /* Runtime data */
       v3d->runtime.properties_storage = NULL;
+      v3d->runtime.flag = 0;
 
       /* render can be quite heavy, set to solid on load */
       if (v3d->shading.type == OB_RENDER) {
@@ -7657,6 +7661,23 @@ static bool direct_link_area_map(FileData *fd, ScrAreaMap *area_map)
 
 /** \} */
 
+/* -------------------------------------------------------------------- */
+/** \name XR-data
+ * \{ */
+
+static void direct_link_wm_xr_data(FileData *fd, wmXrData *xr_data)
+{
+  direct_link_view3dshading(fd, &xr_data->session_settings.shading);
+}
+
+static void lib_link_wm_xr_data(FileData *fd, ID *parent_id, wmXrData *xr_data)
+{
+  xr_data->session_settings.base_pose_object = newlibadr(
+      fd, parent_id->lib, xr_data->session_settings.base_pose_object);
+}
+
+/** \} */
+
 /* -------------------------------------------------------------------- */
 /** \name Read ID: Window Manager
  * \{ */
@@ -7710,6 +7731,8 @@ static void direct_link_windowmanager(FileData *fd, wmWindowManager *wm)
     }
   }
 
+  direct_link_wm_xr_data(fd, &wm->xr);
+
   BLI_listbase_clear(&wm->timers);
   BLI_listbase_clear(&wm->operators);
   BLI_listbase_clear(&wm->paintcursors);
@@ -7724,6 +7747,8 @@ static void direct_link_windowmanager(FileData *fd, wmWindowManager *wm)
 
   wm->message_bus = NULL;
 
+  wm->xr.runtime = NULL;
+
   BLI_listbase_clear(&wm->jobs);
   BLI_listbase_clear(&wm->drags);
 
@@ -7747,6 +7772,8 @@ static void lib_link_windowmanager(FileData *fd, Main *UNUSED(bmain), wmWindowMa
     for (ScrArea *area = win->global_areas.areabase.first; area; area = area->next) {
       lib_link_area(fd, &wm->id, area);
     }
+
+    lib_link_wm_xr_data(fd, &wm->id, &wm->xr);
   }
 }
 
@@ -7911,6 +7938,12 @@ static void lib_link_main_data_restore(struct IDNameLib_Map *id_map, Main *newma
   FOREACH_MAIN_ID_END;
 }
 
+static void lib_link_wm_xr_data_restore(struct IDNameLib_Map *id_map, wmXrData *xr_data)
+{
+  xr_data->session_settings.base_pose_object = restore_pointer_by_name(
+      id_map, (ID *)xr_data->session_settings.base_pose_object, USER_REAL);
+}
+
 static void lib_link_window_scene_data_restore(wmWindow *win, Scene *scene, ViewLayer *view_layer)
 {
   bScreen *screen = BKE_workspace_active_screen_get(win->workspace_hook);
@@ -8241,6 +8274,8 @@ void blo_lib_link_restore(Main *oldmain,
     BLI_assert(win->screen == NULL);
   }
 
+  lib_link_wm_xr_data_restore(id_map, &curwm->xr);
+
   /* Restore all ID pointers in Main database itself
    * (especially IDProperties might point to some word-space of other 'weirdly unchanged' ID
    * pointers, see T69146).
index 87442a10d1293276adec9643b0036bb96749071d..3535897aa6602e2d76d6af0ef70ee6d997b74635 100644 (file)
@@ -30,6 +30,8 @@
 #include "BLI_string.h"
 #include "BLI_utildefines.h"
 
+#include "DNA_defaults.h"
+
 #include "DNA_anim_types.h"
 #include "DNA_object_types.h"
 #include "DNA_camera_types.h"
@@ -4845,5 +4847,21 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain)
         }
       }
     }
+
+    if (!DNA_struct_find(fd->filesdna, "XrSessionSettings")) {
+      for (wmWindowManager *wm = bmain->wm.first; wm; wm = wm->id.next) {
+        const View3D *v3d_default = DNA_struct_default_get(View3D);
+
+        wm->xr.session_settings.shading = v3d_default->shading;
+        /* Don't rotate light with the viewer by default, make it fixed. */
+        wm->xr.session_settings.shading.flag |= V3D_SHADING_WORLD_ORIENTATION;
+        wm->xr.session_settings.draw_flags = (V3D_OFSDRAW_SHOW_GRIDFLOOR |
+                                              V3D_OFSDRAW_SHOW_ANNOTATION);
+        wm->xr.session_settings.clip_start = v3d_default->clip_start;
+        wm->xr.session_settings.clip_end = v3d_default->clip_end;
+
+        wm->xr.session_settings.flag = XR_SESSION_USE_POSITION_TRACKING;
+      }
+    }
   }
 }
index 29366e3bae52053d04b050dd2a13a9dbbf7ecf50..837134c0156f29b0755cdee867ec890ff98f24bd 100644 (file)
@@ -2820,6 +2820,11 @@ static void write_gpencil(WriteData *wd, bGPdata *gpd)
   }
 }
 
+static void write_wm_xr_data(WriteData *wd, wmXrData *xr_data)
+{
+  write_view3dshading(wd, &xr_data->session_settings.shading);
+}
+
 static void write_region(WriteData *wd, ARegion *region, int spacetype)
 {
   writestruct(wd, DATA, ARegion, 1, region);
@@ -3066,6 +3071,7 @@ static void write_windowmanager(WriteData *wd, wmWindowManager *wm)
 {
   writestruct(wd, ID_WM, wmWindowManager, 1, wm);
   write_iddata(wd, &wm->id);
+  write_wm_xr_data(wd, &wm->xr);
 
   for (wmWindow *win = wm->windows.first; win; win = win->next) {
 #ifndef WITH_GLOBAL_AREA_WRITING
index a1213be4be0405f95bb61410947365361b297afd..81f2214b4027feb3749892acd1cc97ea13786f56 100644 (file)
@@ -390,4 +390,8 @@ if(WITH_FREESTYLE)
   add_definitions(-DWITH_FREESTYLE)
 endif()
 
+if(WITH_XR_OPENXR)
+  add_definitions(-DWITH_XR_OPENXR)
+endif()
+
 blender_add_lib(bf_draw "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
index b4c23d5e57c97fb837ff7f32dfd3396ddedadd5f..7ecf9df275dc46ac4301fc8c0a854e76c9abd8e8 100644 (file)
@@ -93,6 +93,7 @@ void DRW_draw_render_loop_offscreen(struct Depsgraph *depsgraph,
                                     struct RenderEngineType *engine_type,
                                     struct ARegion *region,
                                     struct View3D *v3d,
+                                    const bool is_image_render,
                                     const bool draw_background,
                                     const bool do_color_management,
                                     struct GPUOffScreen *ofs,
@@ -139,6 +140,14 @@ void DRW_opengl_context_destroy(void);
 void DRW_opengl_context_enable(void);
 void DRW_opengl_context_disable(void);
 
+#ifdef WITH_XR_OPENXR
+/* XXX see comment on DRW_xr_opengl_context_get() */
+void *DRW_xr_opengl_context_get(void);
+void *DRW_xr_gpu_context_get(void);
+void DRW_xr_drawing_begin(void);
+void DRW_xr_drawing_end(void);
+#endif
+
 /* For garbage collection */
 void DRW_cache_free_old_batches(struct Main *bmain);
 
index 71e2d8d9b57c62425b2b74062eaae9fd1b99d94a..618922d854416bf1da6a076976df18d1e4b32665 100644 (file)
@@ -562,7 +562,7 @@ static void drw_viewport_var_init(void)
     DRW_view_camtexco_set(DST.view_default, rv3d->viewcamtexcofac);
 
     if (DST.draw_ctx.sh_cfg == GPU_SHADER_CFG_CLIPPED) {
-      int plane_len = (rv3d->viewlock & RV3D_BOXCLIP) ? 4 : 6;
+      int plane_len = (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXCLIP) ? 4 : 6;
       DRW_view_clip_planes_set(DST.view_default, rv3d->clip, plane_len);
     }
 
@@ -1554,6 +1554,7 @@ void DRW_draw_render_loop_offscreen(struct Depsgraph *depsgraph,
                                     RenderEngineType *engine_type,
                                     ARegion *region,
                                     View3D *v3d,
+                                    const bool is_image_render,
                                     const bool draw_background,
                                     const bool do_color_management,
                                     GPUOffScreen *ofs,
@@ -1569,7 +1570,7 @@ void DRW_draw_render_loop_offscreen(struct Depsgraph *depsgraph,
 
   /* Reset before using it. */
   drw_state_prepare_clean_for_draw(&DST);
-  DST.options.is_image_render = true;
+  DST.options.is_image_render = is_image_render;
   DST.options.do_color_management = do_color_management;
   DST.options.draw_background = draw_background;
   DRW_draw_render_loop_ex(depsgraph, engine_type, region, v3d, render_viewport, NULL);
@@ -2829,4 +2830,39 @@ void DRW_gpu_render_context_disable(void *UNUSED(re_gpu_context))
   GPU_context_active_set(NULL);
 }
 
+#ifdef WITH_XR_OPENXR
+
+/* XXX
+ * There should really be no such getter, but for VR we currently can't easily avoid it. OpenXR
+ * needs some low level info for the OpenGL context that will be used for submitting the
+ * final framebuffer. VR could in theory create its own context, but that would mean we have to
+ * switch to it just to submit the final frame, which has notable performance impact.
+ *
+ * We could "inject" a context through DRW_opengl_render_context_enable(), but that would have to
+ * work from the main thread, which is tricky to get working too. The preferable solution would be
+ * using a separate thread for VR drawing where a single context can stay active. */
+void *DRW_xr_opengl_context_get(void)
+{
+  return DST.gl_context;
+}
+
+/* XXX See comment on DRW_xr_opengl_context_get(). */
+void *DRW_xr_gpu_context_get(void)
+{
+  return DST.gpu_context;
+}
+
+/* XXX See comment on DRW_xr_opengl_context_get(). */
+void DRW_xr_drawing_begin(void)
+{
+  BLI_ticket_mutex_lock(DST.gl_context_mutex);
+}
+
+/* XXX See comment on DRW_xr_opengl_context_get(). */
+void DRW_xr_drawing_end(void)
+{
+  BLI_ticket_mutex_unlock(DST.gl_context_mutex);
+}
+
+#endif
 /** \} */
index f882d6be9a25472e21d46778a3119bea39c40210..06de4705ac61333c39858a16d396a70c3058a156 100644 (file)
@@ -561,6 +561,9 @@ struct RegionView3D *ED_view3d_context_rv3d(struct bContext *C);
 bool ED_view3d_context_user_region(struct bContext *C,
                                    struct View3D **r_v3d,
                                    struct ARegion **r_ar);
+bool ED_view3d_area_user_region(const struct ScrArea *sa,
+                                const struct View3D *v3d,
+                                struct ARegion **r_ar);
 bool ED_operator_rv3d_user_region_poll(struct bContext *C);
 
 void ED_view3d_init_mats_rv3d(struct Object *ob, struct RegionView3D *rv3d);
@@ -584,7 +587,8 @@ void ED_draw_object_facemap(struct Depsgraph *depsgraph,
 struct RenderEngineType *ED_view3d_engine_type(const struct Scene *scene, int drawtype);
 
 bool ED_view3d_context_activate(struct bContext *C);
-void ED_view3d_draw_setup_view(struct wmWindow *win,
+void ED_view3d_draw_setup_view(const struct wmWindowManager *wm,
+                               struct wmWindow *win,
                                struct Depsgraph *depsgraph,
                                struct Scene *scene,
                                struct ARegion *region,
@@ -730,6 +734,18 @@ void ED_view3d_buttons_region_layout_ex(const struct bContext *C,
 bool ED_view3d_local_collections_set(struct Main *bmain, struct View3D *v3d);
 void ED_view3d_local_collections_reset(struct bContext *C, const bool reset_all);
 
+#ifdef WITH_XR_OPENXR
+void ED_view3d_xr_mirror_update(const struct ScrArea *area,
+                                const struct View3D *v3d,
+                                const bool enable);
+void ED_view3d_xr_shading_update(struct wmWindowManager *wm,
+                                 const View3D *v3d,
+                                 const struct Scene *scene);
+bool ED_view3d_is_region_xr_mirror_active(const struct wmWindowManager *wm,
+                                          const struct View3D *v3d,
+                                          const struct ARegion *region);
+#endif
+
 #ifdef __cplusplus
 }
 #endif
index 3cbb13fa8b563f56bae4378a526748c08b4e20fa..e1ed7472973a2294d973b7877df16380294026b3 100644 (file)
@@ -51,12 +51,31 @@ void ED_view3d_draw_offscreen(struct Depsgraph *depsgraph,
                               int winy,
                               float viewmat[4][4],
                               float winmat[4][4],
+                              bool is_image_render,
                               bool do_sky,
                               bool is_persp,
                               const char *viewname,
                               const bool do_color_management,
                               struct GPUOffScreen *ofs,
                               struct GPUViewport *viewport);
+void ED_view3d_draw_offscreen_simple(struct Depsgraph *depsgraph,
+                                     struct Scene *scene,
+                                     struct View3DShading *shading_override,
+                                     int drawtype,
+                                     int winx,
+                                     int winy,
+                                     unsigned int draw_flags,
+                                     float viewmat[4][4],
+                                     float winmat[4][4],
+                                     float clip_start,
+                                     float clip_end,
+                                     bool is_image_render,
+                                     bool do_sky,
+                                     bool is_persp,
+                                     const char *viewname,
+                                     const bool do_color_management,
+                                     struct GPUOffScreen *ofs,
+                                     struct GPUViewport *viewport);
 
 struct ImBuf *ED_view3d_draw_offscreen_imbuf(struct Depsgraph *depsgraph,
                                              struct Scene *scene,
index a9f44f54b3a63736ed926a0546a13392638c69eb..a72b18f63f8d099a12b581f67ef61b878ced9b58 100644 (file)
@@ -3818,6 +3818,7 @@ static void region_quadview_init_rv3d(
   }
 
   rv3d->viewlock = viewlock;
+  rv3d->runtime_viewlock = 0;
   rv3d->view = view;
   rv3d->view_axis_roll = RV3D_VIEW_AXIS_ROLL_0;
   rv3d->persp = persp;
@@ -3915,7 +3916,7 @@ static int region_quadview_exec(bContext *C, wmOperator *op)
       RegionView3D *rv3d = region->regiondata;
       const char viewlock = (rv3d->viewlock_quad & RV3D_VIEWLOCK_INIT) ?
                                 (rv3d->viewlock_quad & ~RV3D_VIEWLOCK_INIT) :
-                                RV3D_LOCKED;
+                                RV3D_LOCK_ROTATION;
 
       region_quadview_init_rv3d(
           sa, region, viewlock, ED_view3d_lock_view_from_index(index_qsplit++), RV3D_ORTHO);
index dd922834d9e54efc13a25d4fdd13b3e7c64a0384..0ddce8725459335f7258320484197b64def41074 100644 (file)
@@ -1239,6 +1239,7 @@ static void paint_draw_cursor(bContext *C, int x, int y, void *UNUSED(unused))
     return;
   }
 
+  const wmWindowManager *wm = CTX_wm_manager(C);
   Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
   Scene *scene = CTX_data_scene(C);
   UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings;
@@ -1443,7 +1444,8 @@ static void paint_draw_cursor(bContext *C, int x, int y, void *UNUSED(unused))
 
           /* Draw 3D brush cursor. */
           GPU_matrix_push_projection();
-          ED_view3d_draw_setup_view(CTX_wm_window(C),
+          ED_view3d_draw_setup_view(wm,
+                                    CTX_wm_window(C),
                                     CTX_data_depsgraph_pointer(C),
                                     CTX_data_scene(C),
                                     region,
@@ -1537,7 +1539,8 @@ static void paint_draw_cursor(bContext *C, int x, int y, void *UNUSED(unused))
               !is_multires) {
             if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES && ss->deform_modifiers_active) {
               GPU_matrix_push_projection();
-              ED_view3d_draw_setup_view(CTX_wm_window(C),
+              ED_view3d_draw_setup_view(wm,
+                                        CTX_wm_window(C),
                                         CTX_data_depsgraph_pointer(C),
                                         CTX_data_scene(C),
                                         region,
@@ -1556,7 +1559,8 @@ static void paint_draw_cursor(bContext *C, int x, int y, void *UNUSED(unused))
           if (brush->sculpt_tool == SCULPT_TOOL_MULTIPLANE_SCRAPE &&
               brush->flag2 & BRUSH_MULTIPLANE_SCRAPE_PLANES_PREVIEW && !ss->cache->first_time) {
             GPU_matrix_push_projection();
-            ED_view3d_draw_setup_view(CTX_wm_window(C),
+            ED_view3d_draw_setup_view(wm,
+                                      CTX_wm_window(C),
                                       CTX_data_depsgraph_pointer(C),
                                       CTX_data_scene(C),
                                       region,
@@ -1573,7 +1577,8 @@ static void paint_draw_cursor(bContext *C, int x, int y, void *UNUSED(unused))
 
           if (brush->sculpt_tool == SCULPT_TOOL_CLOTH && !ss->cache->first_time) {
             GPU_matrix_push_projection();
-            ED_view3d_draw_setup_view(CTX_wm_window(C),
+            ED_view3d_draw_setup_view(CTX_wm_manager(C),
+                                      CTX_wm_window(C),
                                       CTX_data_depsgraph_pointer(C),
                                       CTX_data_scene(C),
                                       region,
index 91694cfc1ef6c6f5a9e2deac3f97dc569114147e..c7fe82e0cbb9216b9136cb06371fed6c995940f1 100644 (file)
@@ -94,6 +94,10 @@ if(WITH_FREESTYLE)
   add_definitions(-DWITH_FREESTYLE)
 endif()
 
+if(WITH_XR_OPENXR)
+  add_definitions(-DWITH_XR_OPENXR)
+endif()
+
 blender_add_lib(bf_editor_space_view3d "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
 
 # Needed so we can use dna_type_offsets.h for defaults initialization.
index b2fc6c6e4ccc1dd4b12bb74d753c7a89eabdfb8a..f16e19c598eb8ebfd145322f4d160a13240ca08a 100644 (file)
@@ -39,6 +39,7 @@
 
 #include "BKE_context.h"
 #include "BKE_curve.h"
+#include "BKE_global.h"
 #include "BKE_icons.h"
 #include "BKE_idprop.h"
 #include "BKE_lattice.h"
@@ -114,44 +115,61 @@ bool ED_view3d_context_user_region(bContext *C, View3D **r_v3d, ARegion **r_ar)
     if (region) {
       RegionView3D *rv3d;
       if ((region->regiontype == RGN_TYPE_WINDOW) && (rv3d = region->regiondata) &&
-          (rv3d->viewlock & RV3D_LOCKED) == 0) {
+          (rv3d->viewlock & RV3D_LOCK_ROTATION) == 0) {
         *r_v3d = v3d;
         *r_ar = region;
         return true;
       }
       else {
-        ARegion *ar_unlock_user = NULL;
-        ARegion *ar_unlock = NULL;
-        for (region = sa->regionbase.first; region; region = region->next) {
-          /* find the first unlocked rv3d */
-          if (region->regiondata && region->regiontype == RGN_TYPE_WINDOW) {
-            rv3d = region->regiondata;
-            if ((rv3d->viewlock & RV3D_LOCKED) == 0) {
-              ar_unlock = region;
-              if (rv3d->persp == RV3D_PERSP || rv3d->persp == RV3D_CAMOB) {
-                ar_unlock_user = region;
-                break;
-              }
-            }
-          }
-        }
-
-        /* camera/perspective view get priority when the active region is locked */
-        if (ar_unlock_user) {
+        if (ED_view3d_area_user_region(sa, v3d, r_ar)) {
           *r_v3d = v3d;
-          *r_ar = ar_unlock_user;
           return true;
         }
+      }
+    }
+  }
 
-        if (ar_unlock) {
-          *r_v3d = v3d;
-          *r_ar = ar_unlock;
-          return true;
+  return false;
+}
+
+/**
+ * Similar to #ED_view3d_context_user_region() but does not use context. Always performs a lookup.
+ * Also works if \a v3d is not the active space.
+ */
+bool ED_view3d_area_user_region(const ScrArea *sa, const View3D *v3d, ARegion **r_ar)
+{
+  RegionView3D *rv3d = NULL;
+  ARegion *ar_unlock_user = NULL;
+  ARegion *ar_unlock = NULL;
+  const ListBase *region_list = (v3d == sa->spacedata.first) ? &sa->regionbase : &v3d->regionbase;
+
+  BLI_assert(v3d->spacetype == SPACE_VIEW3D);
+
+  for (ARegion *region = region_list->first; region; region = region->next) {
+    /* find the first unlocked rv3d */
+    if (region->regiondata && region->regiontype == RGN_TYPE_WINDOW) {
+      rv3d = region->regiondata;
+      if ((rv3d->viewlock & RV3D_LOCK_ROTATION) == 0) {
+        ar_unlock = region;
+        if (rv3d->persp == RV3D_PERSP || rv3d->persp == RV3D_CAMOB) {
+          ar_unlock_user = region;
+          break;
         }
       }
     }
   }
 
+  /* camera/perspective view get priority when the active region is locked */
+  if (ar_unlock_user) {
+    *r_ar = ar_unlock_user;
+    return true;
+  }
+
+  if (ar_unlock) {
+    *r_ar = ar_unlock;
+    return true;
+  }
+
   return false;
 }
 
@@ -333,9 +351,11 @@ static SpaceLink *view3d_duplicate(SpaceLink *sl)
     v3dn->localvd = NULL;
     v3dn->runtime.properties_storage = NULL;
   }
+  /* Only one View3D is allowed to have this flag! */
+  v3dn->runtime.flag &= ~V3D_RUNTIME_XR_SESSION_ROOT;
 
   v3dn->local_collections_uuid = 0;
-  v3dn->flag &= ~V3D_LOCAL_COLLECTIONS;
+  v3dn->flag &= ~(V3D_LOCAL_COLLECTIONS | V3D_XR_SESSION_MIRROR);
 
   if (v3dn->shading.type == OB_RENDER) {
     v3dn->shading.type = OB_SOLID;
@@ -715,6 +735,13 @@ static void view3d_main_region_listener(
       if (ELEM(wmn->data, ND_UNDO)) {
         WM_gizmomap_tag_refresh(gzmap);
       }
+      else if (ELEM(wmn->data, ND_XR_DATA_CHANGED)) {
+        /* Only cause a redraw if this a VR session mirror. Should more features be added that
+         * require redraws, we could pass something to wmn->reference, e.g. the flag value. */
+        if (v3d->flag & V3D_XR_SESSION_MIRROR) {
+          ED_region_tag_redraw(region);
+        }
+      }
       break;
     case NC_ANIMATION:
       switch (wmn->data) {
@@ -912,6 +939,11 @@ static void view3d_main_region_listener(
         if (wmn->subtype == NS_VIEW3D_GPU) {
           rv3d->rflag |= RV3D_GPULIGHT_UPDATE;
         }
+#ifdef WITH_XR_OPENXR
+        else if (wmn->subtype == NS_VIEW3D_SHADING) {
+          ED_view3d_xr_shading_update(G_MAIN->wm.first, v3d, scene);
+        }
+#endif
         ED_region_tag_redraw(region);
         WM_gizmomap_tag_refresh(gzmap);
       }
@@ -1374,6 +1406,11 @@ static void view3d_buttons_region_listener(wmWindow *UNUSED(win),
         ED_region_tag_redraw(region);
       }
       break;
+    case NC_WM:
+      if (wmn->data == ND_XR_DATA_CHANGED) {
+        ED_region_tag_redraw(region);
+      }
+      break;
   }
 }
 
index 4cf5ecfc4f0a6dcb224005067153e606b836d96c..78d053c36a76ccda1685275b43a84e86f5ce2835 100644 (file)
@@ -317,10 +317,35 @@ static void view3d_stereo3d_setup(
   }
 }
 
+#ifdef WITH_XR_OPENXR
+static void view3d_xr_mirror_setup(const wmWindowManager *wm,
+                                   Depsgraph *depsgraph,
+                                   Scene *scene,
+                                   View3D *v3d,
+                                   ARegion *region,
+                                   const rcti *rect)
+{
+  RegionView3D *rv3d = region->regiondata;
+  float viewmat[4][4];
+  const float lens_old = v3d->lens;
+
+  if (!WM_xr_session_state_viewer_pose_matrix_info_get(&wm->xr, viewmat, &v3d->lens)) {
+    /* Can't get info from XR session, use fallback values. */
+    copy_m4_m4(viewmat, rv3d->viewmat);
+    v3d->lens = lens_old;
+  }
+  view3d_main_region_setup_view(depsgraph, scene, v3d, region, viewmat, NULL, rect);
+
+  /* Reset overridden View3D data */
+  v3d->lens = lens_old;
+}
+#endif /* WITH_XR_OPENXR */
+
 /**
  * Set the correct matrices
  */
-void ED_view3d_draw_setup_view(wmWindow *win,
+void ED_view3d_draw_setup_view(const wmWindowManager *wm,
+                               wmWindow *win,
                                Depsgraph *depsgraph,
                                Scene *scene,
                                ARegion *region,
@@ -331,13 +356,23 @@ void ED_view3d_draw_setup_view(wmWindow *win,
 {
   RegionView3D *rv3d = region->regiondata;
 
+#ifdef WITH_XR_OPENXR
   /* Setup the view matrix. */
-  if (view3d_stereo3d_active(win, scene, v3d, rv3d)) {
+  if (ED_view3d_is_region_xr_mirror_active(wm, v3d, region)) {
+    view3d_xr_mirror_setup(wm, depsgraph, scene, v3d, region, rect);
+  }
+  else
+#endif
+      if (view3d_stereo3d_active(win, scene, v3d, rv3d)) {
     view3d_stereo3d_setup(depsgraph, scene, v3d, region, rect);
   }
   else {
     view3d_main_region_setup_view(depsgraph, scene, v3d, region, viewmat, winmat, rect);
   }
+
+#ifndef WITH_XR_OPENXR
+  UNUSED_VARS(wm);
+#endif
 }
 
 /** \} */
@@ -803,7 +838,8 @@ void ED_view3d_draw_depth(Depsgraph *depsgraph, ARegion *region, View3D *v3d, bo
   UI_Theme_Store(&theme_state);
   UI_SetTheme(SPACE_VIEW3D, RGN_TYPE_WINDOW);
 
-  ED_view3d_draw_setup_view(NULL, depsgraph, scene, region, v3d, NULL, NULL, NULL);
+  ED_view3d_draw_setup_view(
+      G_MAIN->wm.first, NULL, depsgraph, scene, region, v3d, NULL, NULL, NULL);
 
   GPU_clear(GPU_DEPTH_BIT);
 
@@ -1481,7 +1517,7 @@ void view3d_draw_region_info(const bContext *C, ARegion *region)
   wmWindowManager *wm = CTX_wm_manager(C);
 
 #ifdef WITH_INPUT_NDOF
-  if ((U.ndof_flag & NDOF_SHOW_GUIDE) && ((rv3d->viewlock & RV3D_LOCKED) == 0) &&
+  if ((U.ndof_flag & NDOF_SHOW_GUIDE) && ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) == 0) &&
       (rv3d->persp != RV3D_CAMOB)) {
     /* TODO: draw something else (but not this) during fly mode */
     draw_rotation_guide(rv3d);
@@ -1552,7 +1588,8 @@ void view3d_draw_region_info(const bContext *C, ARegion *region)
 
 static void view3d_draw_view(const bContext *C, ARegion *region)
 {
-  ED_view3d_draw_setup_view(CTX_wm_window(C),
+  ED_view3d_draw_setup_view(CTX_wm_manager(C),
+                            CTX_wm_window(C),
                             CTX_data_expect_evaluated_depsgraph(C),
                             CTX_data_scene(C),
                             region,
@@ -1641,6 +1678,7 @@ void ED_view3d_draw_offscreen(Depsgraph *depsgraph,
                               int winy,
                               float viewmat[4][4],
                               float winmat[4][4],
+                              bool is_image_render,
                               bool do_sky,
                               bool UNUSED(is_persp),
                               const char *viewname,
@@ -1690,8 +1728,15 @@ void ED_view3d_draw_offscreen(Depsgraph *depsgraph,
   }
 
   /* main drawing call */
-  DRW_draw_render_loop_offscreen(
-      depsgraph, engine_type, region, v3d, do_sky, do_color_management, ofs, viewport);
+  DRW_draw_render_loop_offscreen(depsgraph,
+                                 engine_type,
+                                 region,
+                                 v3d,
+                                 is_image_render,
+                                 do_sky,
+                                 do_color_management,
+                                 ofs,
+                                 viewport);
 
   /* restore size */
   region->winx = bwinx;
@@ -1706,6 +1751,94 @@ void ED_view3d_draw_offscreen(Depsgraph *depsgraph,
   G.f &= ~G_FLAG_RENDER_VIEWPORT;
 }
 
+/**
+ * Creates own fake 3d views (wrapping #ED_view3d_draw_offscreen). Similar too
+ * #ED_view_draw_offscreen_imbuf_simple, but takes view/projection matrices as arguments.
+ */
+void ED_view3d_draw_offscreen_simple(Depsgraph *depsgraph,
+                                     Scene *scene,
+                                     View3DShading *shading_override,
+                                     int drawtype,
+                                     int winx,
+                                     int winy,
+                                     uint draw_flags,
+                                     float viewmat[4][4],
+                                     float winmat[4][4],
+                                     float clip_start,
+                                     float clip_end,
+                                     bool is_image_render,
+                                     bool do_sky,
+                                     bool is_persp,
+                                     const char *viewname,
+                                     const bool do_color_management,
+                                     GPUOffScreen *ofs,
+                                     GPUViewport *viewport)
+{
+  View3D v3d = {NULL};
+  ARegion ar = {NULL};
+  RegionView3D rv3d = {{{0}}};
+
+  v3d.regionbase.first = v3d.regionbase.last = &ar;
+  ar.regiondata = &rv3d;
+  ar.regiontype = RGN_TYPE_WINDOW;
+
+  View3DShading *source_shading_settings = &scene->display.shading;
+  if (draw_flags & V3D_OFSDRAW_OVERRIDE_SCENE_SETTINGS && shading_override != NULL) {
+    source_shading_settings = shading_override;
+  }
+  memcpy(&v3d.shading, source_shading_settings, sizeof(View3DShading));
+  v3d.shading.type = drawtype;
+
+  if (shading_override) {
+    /* Pass. */
+  }
+  else if (drawtype == OB_MATERIAL) {
+    v3d.shading.flag = V3D_SHADING_SCENE_WORLD | V3D_SHADING_SCENE_LIGHTS;
+  }
+
+  if (draw_flags & V3D_OFSDRAW_SHOW_ANNOTATION) {
+    v3d.flag2 |= V3D_SHOW_ANNOTATION;
+  }
+  if (draw_flags & V3D_OFSDRAW_SHOW_GRIDFLOOR) {
+    v3d.gridflag |= V3D_SHOW_FLOOR | V3D_SHOW_X | V3D_SHOW_Y;
+    v3d.grid = 1.0f;
+    v3d.gridlines = 16;
+    v3d.gridsubdiv = 10;
+
+    /* Show grid, disable other overlays (set all available _HIDE_ flags). */
+    v3d.overlay.flag |= V3D_OVERLAY_HIDE_CURSOR | V3D_OVERLAY_HIDE_TEXT |
+                        V3D_OVERLAY_HIDE_MOTION_PATHS | V3D_OVERLAY_HIDE_BONES |
+                        V3D_OVERLAY_HIDE_OBJECT_XTRAS | V3D_OVERLAY_HIDE_OBJECT_ORIGINS;
+    v3d.flag |= V3D_HIDE_HELPLINES;
+  }
+  else {
+    v3d.flag2 = V3D_HIDE_OVERLAYS;
+  }
+
+  rv3d.persp = RV3D_PERSP;
+  v3d.clip_start = clip_start;
+  v3d.clip_end = clip_end;
+  /* Actually not used since we pass in the projection matrix. */
+  v3d.lens = 0;
+
+  ED_view3d_draw_offscreen(depsgraph,
+                           scene,
+                           drawtype,
+                           &v3d,
+                           &ar,
+                           winx,
+                           winy,
+                           viewmat,
+                           winmat,
+                           is_image_render,
+                           do_sky,
+                           is_persp,
+                           viewname,
+                           do_color_management,
+                           ofs,
+                           viewport);
+}
+
 /**
  * Utility func for ED_view3d_draw_offscreen
  *
@@ -1815,6 +1948,7 @@ ImBuf *ED_view3d_draw_offscreen_imbuf(Depsgraph *depsgraph,
                            sizey,
                            NULL,
                            winmat,
+                           true,
                            draw_sky,
                            !is_ortho,
                            viewname,
@@ -1902,6 +2036,9 @@ ImBuf *ED_view3d_draw_offscreen_imbuf_simple(Depsgraph *depsgraph,
   if (draw_flags & V3D_OFSDRAW_SHOW_ANNOTATION) {
     v3d.flag2 |= V3D_SHOW_ANNOTATION;
   }
+  if (draw_flags & V3D_OFSDRAW_SHOW_GRIDFLOOR) {
+    v3d.gridflag |= V3D_SHOW_FLOOR | V3D_SHOW_X | V3D_SHOW_Y;
+  }
 
   v3d.shading.background_type = V3D_SHADING_BACKGROUND_WORLD;
 
@@ -2212,7 +2349,7 @@ float view3d_depth_near(ViewDepths *d)
 void ED_view3d_draw_depth_gpencil(Depsgraph *depsgraph, Scene *scene, ARegion *region, View3D *v3d)
 {
   /* Setup view matrix. */
-  ED_view3d_draw_setup_view(NULL, depsgraph, scene, region, v3d, NULL, NULL, NULL);
+  ED_view3d_draw_setup_view(NULL, NULL, depsgraph, scene, region, v3d, NULL, NULL, NULL);
 
   GPU_clear(GPU_DEPTH_BIT);
 
index a7136cc580c9b28ba596d847ff94738a20fc4672..68d5765e43d80adde4ffd5f35b73961bd83599d1 100644 (file)
@@ -85,6 +85,52 @@ enum {
   HAS_ROTATE = (1 << 0),
 };
 
+/* test for unlocked camera view in quad view */
+static bool view3d_camera_user_poll(bContext *C)
+{
+  View3D *v3d;
+  ARegion *region;
+
+  if (ED_view3d_context_user_region(C, &v3d, &region)) {
+    RegionView3D *rv3d = region->regiondata;
+    if ((rv3d->persp == RV3D_CAMOB) && !(RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ANY_TRANSFORM)) {
+      return 1;
+    }
+  }
+
+  return 0;
+}
+
+static bool view3d_lock_poll(bContext *C)
+{
+  View3D *v3d = CTX_wm_view3d(C);
+  if (v3d) {
+    RegionView3D *rv3d = CTX_wm_region_view3d(C);
+    if (rv3d) {
+      return ED_view3d_offset_lock_check(v3d, rv3d);
+    }
+  }
+  return false;
+}
+
+static bool view3d_pan_poll(bContext *C)
+{
+  if (ED_operator_region_view3d_active(C)) {
+    const RegionView3D *rv3d = CTX_wm_region_view3d(C);
+    return !(RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_LOCATION);
+  }
+  return false;
+}
+
+static bool view3d_zoom_or_dolly_poll(bContext *C)
+{
+  if (ED_operator_region_view3d_active(C)) {
+    const RegionView3D *rv3d = CTX_wm_region_view3d(C);
+    return !(RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ZOOM_AND_DOLLY);
+  }
+  return false;
+}
+
 /* -------------------------------------------------------------------- */
 /** \name Generic View Operator Properties
  * \{ */
@@ -935,7 +981,7 @@ static int viewrotate_invoke(bContext *C, wmOperator *op, const wmEvent *event)
   vod = op->customdata;
 
   /* poll should check but in some cases fails, see poll func for details */
-  if (vod->rv3d->viewlock & RV3D_LOCKED) {
+  if (RV3D_LOCK_FLAGS(vod->rv3d) & RV3D_LOCK_ROTATION) {
     viewops_data_free(C, op);
     return OPERATOR_PASS_THROUGH;
   }
@@ -983,34 +1029,6 @@ static int viewrotate_invoke(bContext *C, wmOperator *op, const wmEvent *event)
   }
 }
 
-/* test for unlocked camera view in quad view */
-static bool view3d_camera_user_poll(bContext *C)
-{
-  View3D *v3d;
-  ARegion *region;
-
-  if (ED_view3d_context_user_region(C, &v3d, &region)) {
-    RegionView3D *rv3d = region->regiondata;
-    if (rv3d->persp == RV3D_CAMOB) {
-      return 1;
-    }
-  }
-
-  return 0;
-}
-
-static bool view3d_lock_poll(bContext *C)
-{
-  View3D *v3d = CTX_wm_view3d(C);
-  if (v3d) {
-    RegionView3D *rv3d = CTX_wm_region_view3d(C);
-    if (rv3d) {
-      return ED_view3d_offset_lock_check(v3d, rv3d);
-    }
-  }
-  return false;
-}
-
 static void viewrotate_cancel(bContext *C, wmOperator *op)
 {
   viewops_data_free(C, op);
@@ -1051,7 +1069,7 @@ static bool ndof_has_translate(const wmNDOFMotionData *ndof,
 
 static bool ndof_has_rotate(const wmNDOFMotionData *ndof, const RegionView3D *rv3d)
 {
-  return !is_zero_v3(ndof->rvec) && ((rv3d->viewlock & RV3D_LOCKED) == 0);
+  return !is_zero_v3(ndof->rvec) && ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) == 0);
 }
 
 /**
@@ -1159,7 +1177,7 @@ static void view3d_ndof_pan_zoom(const struct wmNDOFMotionData *ndof,
     /* move center of view opposite of hand motion (this is camera mode, not object mode) */
     sub_v3_v3(rv3d->ofs, pan_vec);
 
-    if (rv3d->viewlock & RV3D_BOXVIEW) {
+    if (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXVIEW) {
       view3d_boxview_sync(sa, region);
     }
   }
@@ -1176,7 +1194,7 @@ static void view3d_ndof_orbit(const struct wmNDOFMotionData *ndof,
 
   float view_inv[4];
 
-  BLI_assert((rv3d->viewlock & RV3D_LOCKED) == 0);
+  BLI_assert((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) == 0);
 
   ED_view3d_persp_ensure(vod->depsgraph, v3d, region);
 
@@ -1400,7 +1418,7 @@ static int ndof_orbit_invoke(bContext *C, wmOperator *op, const wmEvent *event)
     const bool has_rotation = ndof_has_rotate(ndof, rv3d);
     /* if we can't rotate, fallback to translate (locked axis views) */
     const bool has_translate = ndof_has_translate(ndof, v3d, rv3d) &&
-                               (rv3d->viewlock & RV3D_LOCKED);
+                               (RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION);
     const bool has_zoom = (ndof->tvec[2] != 0.0f) && !rv3d->is_persp;
 
     if (has_translate || has_zoom) {
@@ -1732,7 +1750,7 @@ static void viewmove_apply(ViewOpsData *vod, int x, int y)
 
     add_v3_v3(vod->rv3d->ofs, dvec);
 
-    if (vod->rv3d->viewlock & RV3D_BOXVIEW) {
+    if (RV3D_LOCK_FLAGS(vod->rv3d) & RV3D_BOXVIEW) {
       view3d_boxview_sync(vod->sa, vod->region);
     }
   }
@@ -1807,12 +1825,17 @@ static int viewmove_invoke(bContext *C, wmOperator *op, const wmEvent *event)
 
   /* makes op->customdata */
   viewops_data_alloc(C, op);
+  vod = op->customdata;
+  if (RV3D_LOCK_FLAGS(vod->rv3d) & RV3D_LOCK_LOCATION) {
+    viewops_data_free(C, op);
+    return OPERATOR_PASS_THROUGH;
+  }
+
   viewops_data_create(C,
                       op,
                       event,
                       (viewops_flag_from_prefs() & ~VIEWOPS_FLAG_ORBIT_SELECT) |
                           (use_cursor_init ? VIEWOPS_FLAG_USE_MOUSE_INIT : 0));
-  vod = op->customdata;
 
   ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->region);
 
@@ -2165,7 +2188,7 @@ static void viewzoom_apply_3d(ViewOpsData *vod,
   /* these limits were in old code too */
   CLAMP(vod->rv3d->dist, dist_range[0], dist_range[1]);
 
-  if (vod->rv3d->viewlock & RV3D_BOXVIEW) {
+  if (RV3D_LOCK_FLAGS(vod->rv3d) & RV3D_BOXVIEW) {
     view3d_boxview_sync(vod->sa, vod->region);
   }
 
@@ -2318,7 +2341,7 @@ static int viewzoom_exec(bContext *C, wmOperator *op)
     }
   }
 
-  if (rv3d->viewlock & RV3D_BOXVIEW) {
+  if (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXVIEW) {
     view3d_boxview_sync(sa, region);
   }
 
@@ -2416,7 +2439,7 @@ void VIEW3D_OT_zoom(wmOperatorType *ot)
   ot->invoke = viewzoom_invoke;
   ot->exec = viewzoom_exec;
   ot->modal = viewzoom_modal;
-  ot->poll = ED_operator_region_view3d_active;
+  ot->poll = view3d_zoom_or_dolly_poll;
   ot->cancel = viewzoom_cancel;
 
   /* flags */
@@ -2514,7 +2537,7 @@ static void viewdolly_apply(ViewOpsData *vod, const int xy[2], const short zoom_
     view_dolly_to_vector_3d(vod->region, vod->init.ofs, vod->init.mousevec, zfac);
   }
 
-  if (vod->rv3d->viewlock & RV3D_BOXVIEW) {
+  if (RV3D_LOCK_FLAGS(vod->rv3d) & RV3D_BOXVIEW) {
     view3d_boxview_sync(vod->sa, vod->region);
   }
 
@@ -2612,7 +2635,7 @@ static int viewdolly_exec(bContext *C, wmOperator *op)
 
   view_dolly_to_vector_3d(region, rv3d->ofs, mousevec, delta < 0 ? 0.2f : 1.8f);
 
-  if (rv3d->viewlock & RV3D_BOXVIEW) {
+  if (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXVIEW) {
     view3d_boxview_sync(sa, region);
   }
 
@@ -2641,7 +2664,7 @@ static int viewdolly_invoke(bContext *C, wmOperator *op, const wmEvent *event)
   vod = op->customdata;
 
   /* poll should check but in some cases fails, see poll func for details */
-  if (vod->rv3d->viewlock & RV3D_LOCKED) {
+  if (RV3D_LOCK_FLAGS(vod->rv3d) & RV3D_LOCK_ROTATION) {
     viewops_data_free(C, op);
     return OPERATOR_PASS_THROUGH;
   }
@@ -3121,7 +3144,7 @@ void VIEW3D_OT_view_selected(wmOperatorType *ot)
 
   /* api callbacks */
   ot->exec = viewselected_exec;
-  ot->poll = ED_operator_region_view3d_active;
+  ot->poll = view3d_zoom_or_dolly_poll;
 
   /* flags */
   ot->flag = 0;
@@ -3265,7 +3288,7 @@ void VIEW3D_OT_view_center_cursor(wmOperatorType *ot)
 
   /* api callbacks */
   ot->exec = viewcenter_cursor_exec;
-  ot->poll = ED_operator_view3d_active;
+  ot->poll = view3d_pan_poll;
 
   /* flags */
   ot->flag = 0;
@@ -3317,7 +3340,7 @@ void VIEW3D_OT_view_center_pick(wmOperatorType *ot)
 
   /* api callbacks */
   ot->invoke = viewcenter_pick_invoke;
-  ot->poll = ED_operator_view3d_active;
+  ot->poll = view3d_pan_poll;
 
   /* flags */
   ot->flag = 0;
@@ -3701,7 +3724,7 @@ static int view3d_zoom_border_exec(bContext *C, wmOperator *op)
                             .dist = &new_dist,
                         });
 
-  if (rv3d->viewlock & RV3D_BOXVIEW) {
+  if (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXVIEW) {
     view3d_boxview_sync(CTX_wm_area(C), region);
   }
 
@@ -3721,7 +3744,7 @@ void VIEW3D_OT_zoom_border(wmOperatorType *ot)
   ot->modal = WM_gesture_box_modal;
   ot->cancel = WM_gesture_box_cancel;
 
-  ot->poll = ED_operator_region_view3d_active;
+  ot->poll = view3d_zoom_or_dolly_poll;
 
   /* flags */
   ot->flag = 0;
@@ -3834,7 +3857,7 @@ static void axis_set_view(bContext *C,
     rv3d->view_axis_roll = view_axis_roll;
   }
 
-  if (rv3d->viewlock & RV3D_LOCKED) {
+  if (RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) {
     ED_region_tag_redraw(region);
     return;
   }
@@ -4058,7 +4081,7 @@ static int view_camera_exec(bContext *C, wmOperator *op)
 
   ED_view3d_smooth_view_force_finish(C, v3d, region);
 
-  if ((rv3d->viewlock & RV3D_LOCKED) == 0) {
+  if ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ANY_TRANSFORM) == 0) {
     /* lastview -  */
 
     ViewLayer *view_layer = CTX_data_view_layer(C);
@@ -4207,7 +4230,7 @@ static int vieworbit_exec(bContext *C, wmOperator *op)
                                                   RV3D_VIEW_USER;
   orbitdir = RNA_enum_get(op->ptr, "type");
 
-  if ((rv3d->viewlock & RV3D_LOCKED) && (view_opposite == RV3D_VIEW_USER)) {
+  if ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) && (view_opposite == RV3D_VIEW_USER)) {
     /* no NULL check is needed, poll checks */
     ED_view3d_context_user_region(C, &v3d, &region);
     rv3d = region->regiondata;
@@ -4215,7 +4238,7 @@ static int vieworbit_exec(bContext *C, wmOperator *op)
 
   ED_view3d_smooth_view_force_finish(C, v3d, region);
 
-  if ((rv3d->viewlock & RV3D_LOCKED) == 0 || (view_opposite != RV3D_VIEW_USER)) {
+  if ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) == 0 || (view_opposite != RV3D_VIEW_USER)) {
     if ((rv3d->persp != RV3D_CAMOB) || ED_view3d_camera_lock_check(v3d, rv3d)) {
       int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
       float quat_mul[4];
@@ -4352,7 +4375,7 @@ static void viewroll_apply(ViewOpsData *vod, int x, int UNUSED(y))
         vod->rv3d->ofs, vod->init.ofs, vod->init.quat, vod->rv3d->viewquat, vod->dyn_ofs);
   }
 
-  if (vod->rv3d->viewlock & RV3D_BOXVIEW) {
+  if (RV3D_LOCK_FLAGS(vod->rv3d) & RV3D_BOXVIEW) {
     view3d_boxview_sync(vod->sa, vod->region);
   }
 
@@ -4621,7 +4644,7 @@ void VIEW3D_OT_view_pan(wmOperatorType *ot)
 
   /* api callbacks */
   ot->invoke = viewpan_invoke;
-  ot->poll = ED_operator_region_view3d_active;
+  ot->poll = view3d_pan_poll;
 
   /* flags */
   ot->flag = 0;
@@ -4647,7 +4670,8 @@ static int viewpersportho_exec(bContext *C, wmOperator *UNUSED(op))
   ED_view3d_context_user_region(C, &v3d_dummy, &region);
   rv3d = region->regiondata;
 
-  if ((rv3d->viewlock & RV3D_LOCKED) == 0) {
+  /* Could add a separate lock flag for locking persp. */
+  if ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ANY_TRANSFORM) == 0) {
     if (rv3d->persp != RV3D_ORTHO) {
       rv3d->persp = RV3D_ORTHO;
     }
index eb7b1412fde7bf6a21574b8eccbe39803e262e9c..b32bcbde3e9cec53f8bd77e55a6677fdcf50c181 100644 (file)
@@ -1039,7 +1039,7 @@ static int fly_invoke(bContext *C, wmOperator *op, const wmEvent *event)
   RegionView3D *rv3d = CTX_wm_region_view3d(C);
   FlyInfo *fly;
 
-  if (rv3d->viewlock & RV3D_LOCKED) {
+  if (RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ANY_TRANSFORM) {
     return OPERATOR_CANCELLED;
   }
 
index 422cba7c52e7ee0ea7623d91b64391bea47cf46f..91a2ee297adb28a52b2862141f35a8f08ea81cb0 100644 (file)
@@ -252,14 +252,14 @@ static void WIDGETGROUP_navigate_draw_prepare(const bContext *C, wmGizmoGroup *g
       (navgroup->state.rect_visible.ymax == rect_visible->ymax) &&
       (navgroup->state.rv3d.is_persp == rv3d->is_persp) &&
       (navgroup->state.rv3d.is_camera == (rv3d->persp == RV3D_CAMOB)) &&
-      (navgroup->state.rv3d.viewlock == rv3d->viewlock)) {
+      (navgroup->state.rv3d.viewlock == RV3D_LOCK_FLAGS(rv3d))) {
     return;
   }
 
   navgroup->state.rect_visible = *rect_visible;
   navgroup->state.rv3d.is_persp = rv3d->is_persp;
   navgroup->state.rv3d.is_camera = (rv3d->persp == RV3D_CAMOB);
-  navgroup->state.rv3d.viewlock = rv3d->viewlock;
+  navgroup->state.rv3d.viewlock = RV3D_LOCK_FLAGS(rv3d);
 
   const bool show_navigate = (U.uiflag & USER_SHOW_GIZMO_NAVIGATE) != 0;
   const bool show_rotate_gizmo = (U.mini_axis_type == USER_MINI_AXIS_TYPE_GIZMO);
@@ -296,7 +296,6 @@ static void WIDGETGROUP_navigate_draw_prepare(const bContext *C, wmGizmoGroup *g
     WM_gizmo_set_flag(gz, WM_GIZMO_HIDDEN, true);
   }
 
-  /* RV3D_LOCKED or Camera: only show supported buttons. */
   if (show_rotate_gizmo) {
     gz = navgroup->gz_array[GZ_INDEX_ROTATE];
     gz->matrix_basis[3][0] = co_rotate[0];
@@ -306,26 +305,30 @@ static void WIDGETGROUP_navigate_draw_prepare(const bContext *C, wmGizmoGroup *g
 
   if (show_navigate) {
     int icon_mini_slot = 0;
-    gz = navgroup->gz_array[GZ_INDEX_ZOOM];
-    gz->matrix_basis[3][0] = roundf(co[0]);
-    gz->matrix_basis[3][1] = roundf(co[1] - (icon_offset_mini * icon_mini_slot++));
-    WM_gizmo_set_flag(gz, WM_GIZMO_HIDDEN, false);
-
-    gz = navgroup->gz_array[GZ_INDEX_MOVE];
-    gz->matrix_basis[3][0] = roundf(co[0]);
-    gz->matrix_basis[3][1] = roundf(co[1] - (icon_offset_mini * icon_mini_slot++));
-    WM_gizmo_set_flag(gz, WM_GIZMO_HIDDEN, false);
+    if ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ZOOM_AND_DOLLY) == 0) {
+      gz = navgroup->gz_array[GZ_INDEX_ZOOM];
+      gz->matrix_basis[3][0] = roundf(co[0]);
+      gz->matrix_basis[3][1] = roundf(co[1] - (icon_offset_mini * icon_mini_slot++));
+      WM_gizmo_set_flag(gz, WM_GIZMO_HIDDEN, false);
+    }
 
-    if ((rv3d->viewlock & RV3D_LOCKED) == 0) {
-      gz = navgroup->gz_array[GZ_INDEX_CAMERA];
+    if ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_LOCATION) == 0) {
+      gz = navgroup->gz_array[GZ_INDEX_MOVE];
       gz->matrix_basis[3][0] = roundf(co[0]);
       gz->matrix_basis[3][1] = roundf(co[1] - (icon_offset_mini * icon_mini_slot++));
       WM_gizmo_set_flag(gz, WM_GIZMO_HIDDEN, false);
+    }
+
+    if ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) == 0) {
+      gz = navgroup->gz_array[GZ_INDEX_CAMERA];
+      gz->matrix_basis[3][0] = co[0];
+      gz->matrix_basis[3][1] = co[1] - (icon_offset_mini * icon_mini_slot++);
+      WM_gizmo_set_flag(gz, WM_GIZMO_HIDDEN, false);
 
       if (navgroup->state.rv3d.is_camera == false) {
         gz = navgroup->gz_array[rv3d->is_persp ? GZ_INDEX_PERSP : GZ_INDEX_ORTHO];
-        gz->matrix_basis[3][0] = roundf(co[0]);
-        gz->matrix_basis[3][1] = roundf(co[1] - (icon_offset_mini * icon_mini_slot++));
+        gz->matrix_basis[3][0] = co[0];
+        gz->matrix_basis[3][1] = co[1] - (icon_offset_mini * icon_mini_slot++);
         WM_gizmo_set_flag(gz, WM_GIZMO_HIDDEN, false);
       }
     }
index bad283740c85a1f26eea888431fcdc04af6fc9ea..55778db73534a2fb835063c35db3ddd5612e4f36 100644 (file)
@@ -459,7 +459,7 @@ bool ED_view3d_persp_ensure(const Depsgraph *depsgraph, View3D *v3d, ARegion *re
   RegionView3D *rv3d = region->regiondata;
   const bool autopersp = (U.uiflag & USER_AUTOPERSP) != 0;
 
-  BLI_assert((rv3d->viewlock & RV3D_LOCKED) == 0);
+  BLI_assert((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ANY_TRANSFORM) == 0);
 
   if (ED_view3d_camera_lock_check(v3d, rv3d)) {
     return false;
@@ -679,7 +679,7 @@ static void view3d_boxview_clip(ScrArea *sa)
     if (region->regiontype == RGN_TYPE_WINDOW) {
       RegionView3D *rv3d = region->regiondata;
 
-      if (rv3d->viewlock & RV3D_BOXCLIP) {
+      if (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXCLIP) {
         if (ELEM(rv3d->view, RV3D_VIEW_TOP, RV3D_VIEW_BOTTOM)) {
           if (region->winx > region->winy) {
             x1 = rv3d->dist;
@@ -751,7 +751,7 @@ static void view3d_boxview_clip(ScrArea *sa)
     if (region->regiontype == RGN_TYPE_WINDOW) {
       RegionView3D *rv3d = region->regiondata;
 
-      if (rv3d->viewlock & RV3D_BOXCLIP) {
+      if (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXCLIP) {
         rv3d->rflag |= RV3D_CLIPPING;
         memcpy(rv3d->clip, clip, sizeof(clip));
         if (rv3d->clipbb) {
@@ -822,10 +822,10 @@ void view3d_boxview_sync(ScrArea *sa, ARegion *region)
     if (artest != region && artest->regiontype == RGN_TYPE_WINDOW) {
       RegionView3D *rv3dtest = artest->regiondata;
 
-      if (rv3dtest->viewlock & RV3D_LOCKED) {
+      if (RV3D_LOCK_FLAGS(rv3dtest) & RV3D_LOCK_ROTATION) {
         rv3dtest->dist = rv3d->dist;
         view3d_boxview_sync_axis(rv3dtest, rv3d);
-        clip |= rv3dtest->viewlock & RV3D_BOXCLIP;
+        clip |= RV3D_LOCK_FLAGS(rv3dtest) & RV3D_BOXCLIP;
 
         ED_region_tag_redraw(artest);
       }
@@ -848,12 +848,12 @@ void view3d_boxview_copy(ScrArea *sa, ARegion *region)
     if (artest != region && artest->regiontype == RGN_TYPE_WINDOW) {
       RegionView3D *rv3dtest = artest->regiondata;
 
-      if (rv3dtest->viewlock) {
+      if (RV3D_LOCK_FLAGS(rv3dtest)) {
         rv3dtest->dist = rv3d->dist;
         copy_v3_v3(rv3dtest->ofs, rv3d->ofs);
         ED_region_tag_redraw(artest);
 
-        clip |= ((rv3dtest->viewlock & RV3D_BOXCLIP) != 0);
+        clip |= ((RV3D_LOCK_FLAGS(rv3dtest) & RV3D_BOXCLIP) != 0);
       }
     }
   }
@@ -874,7 +874,7 @@ void ED_view3d_quadview_update(ScrArea *sa, ARegion *region, bool do_clip)
    * properties are always being edited, weak */
   viewlock = rv3d->viewlock;
 
-  if ((viewlock & RV3D_LOCKED) == 0) {
+  if ((viewlock & RV3D_LOCK_ROTATION) == 0) {
     do_clip = (viewlock & RV3D_BOXCLIP) != 0;
     viewlock = 0;
   }
@@ -899,12 +899,12 @@ void ED_view3d_quadview_update(ScrArea *sa, ARegion *region, bool do_clip)
     }
   }
 
-  if (rv3d->viewlock & RV3D_BOXVIEW) {
+  if (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXVIEW) {
     view3d_boxview_sync(sa, ar_sync ? ar_sync : sa->regionbase.last);
   }
 
   /* ensure locked regions have an axis, locked user views don't make much sense */
-  if (viewlock & RV3D_LOCKED) {
+  if (viewlock & RV3D_LOCK_ROTATION) {
     int index_qsplit = 0;
     for (region = sa->regionbase.first; region; region = region->next) {
       if (region->alignment == RGN_ALIGN_QSPLIT) {
index b27bdf9338967a98a886b56dcc3cae176e435fe2..159ea2493370b50c900561cf3ca26b06f6575865 100644 (file)
@@ -36,6 +36,7 @@
 #include "BKE_action.h"
 #include "BKE_camera.h"
 #include "BKE_context.h"
+#include "BKE_idprop.h"
 #include "BKE_object.h"
 #include "BKE_global.h"
 #include "BKE_layer.h"
@@ -228,7 +229,7 @@ void ED_view3d_smooth_view_ex(
             ob_camera_old_eval, sms.src.ofs, sms.src.quat, &sms.src.dist, &sms.src.lens);
       }
       /* grid draw as floor */
-      if ((rv3d->viewlock & RV3D_LOCKED) == 0) {
+      if ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) == 0) {
         /* use existing if exists, means multiple calls to smooth view
          * wont loose the original 'view' setting */
         rv3d->view = RV3D_VIEW_USER;
@@ -291,7 +292,7 @@ void ED_view3d_smooth_view_ex(
       ED_view3d_camera_lock_sync(depsgraph, v3d, rv3d);
     }
 
-    if (rv3d->viewlock & RV3D_BOXVIEW) {
+    if (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXVIEW) {
       view3d_boxview_copy(sa, region);
     }
 
@@ -344,7 +345,7 @@ static void view3d_smoothview_apply(bContext *C, View3D *v3d, ARegion *region, b
       ED_view3d_camera_lock_autokey(v3d, rv3d, C, true, true);
     }
 
-    if ((rv3d->viewlock & RV3D_LOCKED) == 0) {
+    if ((RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) == 0) {
       rv3d->view = sms->org_view;
     }
 
@@ -384,7 +385,7 @@ static void view3d_smoothview_apply(bContext *C, View3D *v3d, ARegion *region, b
     WM_event_add_mousemove(CTX_wm_window(C));
   }
 
-  if (sync_boxview && (rv3d->viewlock & RV3D_BOXVIEW)) {
+  if (sync_boxview && (RV3D_LOCK_FLAGS(rv3d) & RV3D_BOXVIEW)) {
     view3d_boxview_copy(CTX_wm_area(C), region);
   }
 
@@ -494,7 +495,7 @@ static bool view3d_camera_to_view_poll(bContext *C)
   if (ED_view3d_context_user_region(C, &v3d, &region)) {
     RegionView3D *rv3d = region->regiondata;
     if (v3d && v3d->camera && !ID_IS_LINKED(v3d->camera)) {
-      if (rv3d && (rv3d->viewlock & RV3D_LOCKED) == 0) {
+      if (rv3d && (RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ANY_TRANSFORM) == 0) {
         if (rv3d->persp != RV3D_CAMOB) {
           return 1;
         }
@@ -826,7 +827,7 @@ void view3d_viewmatrix_set(Depsgraph *depsgraph,
     bool use_lock_ofs = false;
 
     /* should be moved to better initialize later on XXX */
-    if (rv3d->viewlock & RV3D_LOCKED) {
+    if (RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ROTATION) {
       ED_view3d_lock(rv3d);
     }
 
@@ -991,6 +992,7 @@ int view3d_opengl_select(ViewContext *vc,
                          eV3DSelectObjectFilter select_filter)
 {
   struct bThemeState theme_state;
+  const wmWindowManager *wm = CTX_wm_manager(vc->C);
   Depsgraph *depsgraph = vc->depsgraph;
   Scene *scene = vc->scene;
   View3D *v3d = vc->v3d;
@@ -1097,7 +1099,7 @@ int view3d_opengl_select(ViewContext *vc,
   /* Important we use the 'viewmat' and don't re-calculate since
    * the object & bone view locking takes 'rect' into account, see: T51629. */
   ED_view3d_draw_setup_view(
-      vc->win, depsgraph, scene, region, v3d, vc->rv3d->viewmat, NULL, &rect);
+      wm, vc->win, depsgraph, scene, region, v3d, vc->rv3d->viewmat, NULL, &rect);
 
   if (!XRAY_ACTIVE(v3d)) {
     GPU_depth_test(true);
@@ -1165,7 +1167,8 @@ int view3d_opengl_select(ViewContext *vc,
   }
 
   G.f &= ~G_FLAG_PICKSEL;
-  ED_view3d_draw_setup_view(vc->win, depsgraph, scene, region, v3d, vc->rv3d->viewmat, NULL, NULL);
+  ED_view3d_draw_setup_view(
+      wm, vc->win, depsgraph, scene, region, v3d, vc->rv3d->viewmat, NULL, NULL);
 
   if (!XRAY_ACTIVE(v3d)) {
     GPU_depth_test(false);
@@ -1684,3 +1687,83 @@ void ED_view3d_local_collections_reset(struct bContext *C, const bool reset_all)
 }
 
 /** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name XR Functionality
+ * \{ */
+
+#ifdef WITH_XR_OPENXR
+
+static void view3d_xr_mirror_begin(RegionView3D *rv3d)
+{
+  /* If there is no session yet, changes below should not be applied! */
+  BLI_assert(WM_xr_session_exists(&((wmWindowManager *)G_MAIN->wm.first)->xr));
+
+  rv3d->runtime_viewlock |= RV3D_LOCK_ANY_TRANSFORM;
+  /* Force perspective view. This isn't reset but that's not really an issue. */
+  rv3d->persp = RV3D_PERSP;
+}
+
+static void view3d_xr_mirror_end(RegionView3D *rv3d)
+{
+  rv3d->runtime_viewlock &= ~RV3D_LOCK_ANY_TRANSFORM;
+}
+
+void ED_view3d_xr_mirror_update(const ScrArea *area, const View3D *v3d, const bool enable)
+{
+  ARegion *region_rv3d;
+
+  BLI_assert(v3d->spacetype == SPACE_VIEW3D);
+
+  if (ED_view3d_area_user_region(area, v3d, &region_rv3d)) {
+    if (enable) {
+      view3d_xr_mirror_begin(region_rv3d->regiondata);
+    }
+    else {
+      view3d_xr_mirror_end(region_rv3d->regiondata);
+    }
+  }
+}
+
+void ED_view3d_xr_shading_update(wmWindowManager *wm, const View3D *v3d, const Scene *scene)
+{
+  if (v3d->runtime.flag & V3D_RUNTIME_XR_SESSION_ROOT) {
+    View3DShading *xr_shading = &wm->xr.session_settings.shading;
+
+    BLI_assert(WM_xr_session_exists(&wm->xr));
+
+    if (v3d->shading.type == OB_RENDER) {
+      if (!(BKE_scene_uses_blender_workbench(scene) || BKE_scene_uses_blender_eevee(scene))) {
+        /* Keep old shading while using Cycles or another engine, they are typically not usable in
+         * VR. */
+        return;
+      }
+    }
+
+    if (xr_shading->prop) {
+      IDP_FreeProperty(xr_shading->prop);
+      xr_shading->prop = NULL;
+    }
+
+    /* Copy shading from View3D to VR view. */
+    *xr_shading = v3d->shading;
+    if (v3d->shading.prop) {
+      xr_shading->prop = IDP_CopyProperty(xr_shading->prop);
+    }
+  }
+}
+
+bool ED_view3d_is_region_xr_mirror_active(const wmWindowManager *wm,
+                                          const View3D *v3d,
+                                          const ARegion *region)
+{
+  return (v3d->flag & V3D_XR_SESSION_MIRROR) &&
+         /* The free region (e.g. the camera region in quad-view) is always the last in the list
+            base. We don't want any other to be affected. */
+         !region->next &&  //
+         WM_xr_session_is_ready(&wm->xr);
+}
+
+#endif
+
+/** \} */
index 9f488a1ef78b63d3d0d0091c4e2c99c792b05457..e25f8b4b8ad50f99a3ed2e4212c1bd13c6491515 100644 (file)
@@ -1347,7 +1347,7 @@ static int walk_invoke(bContext *C, wmOperator *op, const wmEvent *event)
   RegionView3D *rv3d = CTX_wm_region_view3d(C);
   WalkInfo *walk;
 
-  if (rv3d->viewlock & RV3D_LOCKED) {
+  if (RV3D_LOCK_FLAGS(rv3d) & RV3D_LOCK_ANY_TRANSFORM) {
     return OPERATOR_CANCELLED;
   }
 
index 986d9720ab95c41f86241004966fb72795daaafe..848da778d1cc0eefacf212c70921209ece654a5b 100644 (file)
@@ -104,6 +104,9 @@ GPUViewport *GPU_viewport_create(void);
 void GPU_viewport_bind(GPUViewport *viewport, const rcti *rect);
 void GPU_viewport_unbind(GPUViewport *viewport);
 void GPU_viewport_draw_to_screen(GPUViewport *viewport, const rcti *rect);
+void GPU_viewport_draw_to_screen_ex(GPUViewport *viewport,
+                                    const rcti *rect,
+                                    bool display_colorspace);
 void GPU_viewport_free(GPUViewport *viewport);
 
 void GPU_viewport_colorspace_set(GPUViewport *viewport,
index 8ccfa3d9898b6d781bf3d8e1f1079f56f719753e..e4fd5d3f122976c1a9c9874f9e92a6a15e0f566b 100644 (file)
@@ -532,7 +532,13 @@ static void gpu_viewport_draw_colormanaged(GPUViewport *viewport,
   }
 }
 
-void GPU_viewport_draw_to_screen(GPUViewport *viewport, const rcti *rect)
+/**
+ * Version of #GPU_viewport_draw_to_screen() that lets caller decide if display colorspace
+ * transform should be performed.
+ */
+void GPU_viewport_draw_to_screen_ex(GPUViewport *viewport,
+                                    const rcti *rect,
+                                    bool display_colorspace)
 {
   DefaultFramebufferList *dfbl = viewport->fbl;
   DefaultTextureList *dtxl = viewport->txl;
@@ -545,18 +551,22 @@ void GPU_viewport_draw_to_screen(GPUViewport *viewport, const rcti *rect)
   const float w = (float)GPU_texture_width(color);
   const float h = (float)GPU_texture_height(color);
 
-  BLI_assert(w == BLI_rcti_size_x(rect) + 1);
-  BLI_assert(h == BLI_rcti_size_y(rect) + 1);
+  /* We allow rects with min/max swapped, but we also need coorectly assigned coordinates. */
+  rcti sanitized_rect = *rect;
+  BLI_rcti_sanitize(&sanitized_rect);
+
+  BLI_assert(w == BLI_rcti_size_x(&sanitized_rect) + 1);
+  BLI_assert(h == BLI_rcti_size_y(&sanitized_rect) + 1);
 
   /* wmOrtho for the screen has this same offset */
   const float halfx = GLA_PIXEL_OFS / w;
   const float halfy = GLA_PIXEL_OFS / h;
 
   rctf pos_rect = {
-      .xmin = rect->xmin,
-      .ymin = rect->ymin,
-      .xmax = rect->xmin + w,
-      .ymax = rect->ymin + h,
+      .xmin = sanitized_rect.xmin,
+      .ymin = sanitized_rect.ymin,
+      .xmax = sanitized_rect.xmin + w,
+      .ymax = sanitized_rect.ymin + h,
   };
 
   rctf uv_rect = {
@@ -565,8 +575,28 @@ void GPU_viewport_draw_to_screen(GPUViewport *viewport, const rcti *rect)
       .xmax = halfx + 1.0f,
       .ymax = halfy + 1.0f,
   };
+  /* Mirror the UV rect in case axis-swapped drawing is requested (by passing a rect with min and
+   * max values swapped). */
+  if (BLI_rcti_size_x(rect) < 0) {
+    SWAP(float, uv_rect.xmin, uv_rect.xmax);
+  }
+  if (BLI_rcti_size_y(rect) < 0) {
+    SWAP(float, uv_rect.ymin, uv_rect.ymax);
+  }
+
+  gpu_viewport_draw_colormanaged(viewport, &pos_rect, &uv_rect, display_colorspace);
+}
 
-  gpu_viewport_draw_colormanaged(viewport, &pos_rect, &uv_rect, true);
+/**
+ * Merge and draw the buffers of \a viewport into the currently active framebuffer, performing
+ * color transform to display space.
+ *
+ * \param rect: Coordinates to draw into. By swapping min and max values, drawing can be done with
+ *              inversed axis coordinates (upside down or sideways).
+ */
+void GPU_viewport_draw_to_screen(GPUViewport *viewport, const rcti *rect)
+{
+  GPU_viewport_draw_to_screen_ex(viewport, rect, true);
 }
 
 /**
index 85522ea88a2c9184fc5e1b7917c302e31c9cf78c..f8c772422bb133f9ea85d28fb2a978f4039489a3 100644 (file)
@@ -26,6 +26,7 @@ typedef enum eV3DOffscreenDrawFlag {
   V3D_OFSDRAW_NONE = (0),
   V3D_OFSDRAW_SHOW_ANNOTATION = (1 << 0),
   V3D_OFSDRAW_OVERRIDE_SCENE_SETTINGS = (1 << 1),
+  V3D_OFSDRAW_SHOW_GRIDFLOOR = (1 << 2),
 } eV3DOffscreenDrawFlag;
 
 /** #View3DShading.light */
index a25d66cbc9b48f4418268ced05ddf51b4a4d5e1d..3dc29d40bc121d81afd96b46ecb0c4a45159174b 100644 (file)
@@ -106,10 +106,12 @@ typedef struct RegionView3D {
   char persp;
   char view;
   char view_axis_roll;
-  char viewlock;
+  char viewlock; /* Should usually be accessed with RV3D_LOCK_FLAGS()! */
+  /** Options for runtime only locking (cleared on file read) */
+  char runtime_viewlock; /* Should usually be accessed with RV3D_LOCK_FLAGS()! */
   /** Options for quadview (store while out of quad view). */
   char viewlock_quad;
-  char _pad[2];
+  char _pad[1];
   /** Normalized offset for locked view: (-1, -1) bottom left, (1, 1) upper right. */
   float ofs_lock[2];
 
@@ -232,6 +234,10 @@ typedef struct View3DOverlay {
 typedef struct View3D_Runtime {
   /** Nkey panel stores stuff here. */
   void *properties_storage;
+  /** Runtime only flags. */
+  int flag;
+
+  char _pad1[4];
 } View3D_Runtime;
 
 /** 3D ViewPort Struct. */
@@ -342,6 +348,7 @@ typedef struct View3D {
 #define V3D_FLAG_UNUSED_1 (1 << 1) /* cleared */
 #define V3D_HIDE_HELPLINES (1 << 2)
 #define V3D_INVALID_BACKBUF (1 << 3)
+#define V3D_XR_SESSION_MIRROR (1 << 4)
 
 #define V3D_FLAG_UNUSED_10 (1 << 10) /* cleared */
 #define V3D_SELECT_OUTLINE (1 << 11)
@@ -349,6 +356,12 @@ typedef struct View3D {
 #define V3D_GLOBAL_STATS (1 << 13)
 #define V3D_DRAW_CENTERS (1 << 15)
 
+/** #View3D_Runtime.flag */
+enum {
+  /** The 3D view which the XR session was created in is flagged with this. */
+  V3D_RUNTIME_XR_SESSION_ROOT = (1 << 0),
+};
+
 /** #RegionView3D.persp */
 #define RV3D_ORTHO 0
 #define RV3D_PERSP 1
@@ -367,9 +380,19 @@ typedef struct View3D {
 #define RV3D_ZOFFSET_DISABLED 64
 
 /** #RegionView3D.viewlock */
-#define RV3D_LOCKED (1 << 0)
-#define RV3D_BOXVIEW (1 << 1)
-#define RV3D_BOXCLIP (1 << 2)
+enum {
+  RV3D_LOCK_ROTATION = (1 << 0),
+  RV3D_BOXVIEW = (1 << 1),
+  RV3D_BOXCLIP = (1 << 2),
+  RV3D_LOCK_LOCATION = (1 << 3),
+  RV3D_LOCK_ZOOM_AND_DOLLY = (1 << 4),
+
+  RV3D_LOCK_ANY_TRANSFORM = (RV3D_LOCK_LOCATION | RV3D_LOCK_ROTATION | RV3D_LOCK_ZOOM_AND_DOLLY),
+};
+
+/* Bitwise OR of the regular lock-flags with runtime only lock-flags. */
+#define RV3D_LOCK_FLAGS(rv3d) ((rv3d)->viewlock | ((rv3d)->runtime_viewlock))
+
 /** #RegionView3D.viewlock_quad */
 #define RV3D_VIEWLOCK_INIT (1 << 7)
 
index 1eaf3777d0aa472338beaf884fe72928442e4cf0..321e392515561700186612a622cb24056965398e 100644 (file)
@@ -28,6 +28,7 @@
 #include "DNA_screen_types.h"
 #include "DNA_vec_types.h"
 #include "DNA_userdef_types.h"
+#include "DNA_xr_types.h"
 
 #include "DNA_ID.h"
 
@@ -119,6 +120,16 @@ typedef struct ReportTimerInfo {
   float widthfac;
 } ReportTimerInfo;
 
+//#ifdef WITH_XR_OPENXR
+typedef struct wmXrData {
+  /** Runtime information for managing Blender specific behaviors. */
+  struct wmXrRuntimeData *runtime;
+  /** Permanent session settings (draw mode, feature toggles, etc). Stored in files and accessible
+   * even before the session runs. */
+  XrSessionSettings session_settings;
+} wmXrData;
+//#endif
+
 /* reports need to be before wmWindowManager */
 
 /* windowmanager is saved, tag WMAN */
@@ -180,6 +191,9 @@ typedef struct wmWindowManager {
 
   struct wmMsgBus *message_bus;
 
+  //#ifdef WITH_XR_OPENXR
+  wmXrData xr;
+  //#endif
 } wmWindowManager;
 
 /* wmWindowManager.initialized */
diff --git a/source/blender/makesdna/DNA_xr_types.h b/source/blender/makesdna/DNA_xr_types.h
new file mode 100644 (file)
index 0000000..a026f75
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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 DNA
+ */
+
+#ifndef __DNA_XR_TYPES_H__
+#define __DNA_XR_TYPES_H__
+
+#include "DNA_view3d_types.h"
+
+typedef struct XrSessionSettings {
+  /** Shading settings, struct shared with 3D-View so settings are the same. */
+  struct View3DShading shading;
+
+  char _pad[7];
+
+  char base_pose_type; /* eXRSessionBasePoseType */
+  /** Object to take the location and rotation as base position from. */
+  Object *base_pose_object;
+  float base_pose_location[3];
+  float base_pose_angle;
+
+  /** View3D draw flags (V3D_OFSDRAW_NONE, V3D_OFSDRAW_SHOW_ANNOTATION, ...). */
+  char draw_flags;
+  char _pad2[3];
+
+  /** Clipping distance. */
+  float clip_start, clip_end;
+
+  int flag;
+} XrSessionSettings;
+
+typedef enum eXrSessionFlag {
+  XR_SESSION_USE_POSITION_TRACKING = (1 << 0),
+} eXrSessionFlag;
+
+typedef enum eXRSessionBasePoseType {
+  XR_BASE_POSE_SCENE_CAMERA = 0,
+  XR_BASE_POSE_OBJECT = 1,
+  XR_BASE_POSE_CUSTOM = 2,
+} eXRSessionBasePoseType;
+
+#endif /* __DNA_XR_TYPES_H__ */
index 6b4b48545154e54d5262a027982e58ddb23eb5d4..a33f13be09aa0eddb13e0ec204cea2fd23f9093e 100644 (file)
@@ -132,6 +132,7 @@ static const char *includefiles[] = {
     "DNA_workspace_types.h",
     "DNA_lightprobe_types.h",
     "DNA_curveprofile_types.h",
+    "DNA_xr_types.h",
 
     /* see comment above before editing! */
 
@@ -1598,6 +1599,7 @@ int main(int argc, char **argv)
 #include "DNA_workspace_types.h"
 #include "DNA_lightprobe_types.h"
 #include "DNA_curveprofile_types.h"
+#include "DNA_xr_types.h"
 
 /* end of list */
 
index fa8cf6903ad6602c7680e499fe40dc5533d77c62..1e07da23429b1f5ff57920afe7d5ded97014cd9b 100644 (file)
@@ -694,6 +694,8 @@ extern StructRNA RNA_WorkSpace;
 extern StructRNA RNA_World;
 extern StructRNA RNA_WorldLighting;
 extern StructRNA RNA_WorldMistSettings;
+extern StructRNA RNA_XrSessionSettings;
+extern StructRNA RNA_XrSessionState;
 extern StructRNA RNA_uiPopover;
 extern StructRNA RNA_wmOwnerIDs;
 
index a813fe12c026b555ccf0e368ebbd121b039a9591..30989a62603ef7ae9625a5c9b5c729885d36f46a 100644 (file)
@@ -92,6 +92,7 @@ set(DEFSRC
   rna_wm_gizmo.c
   rna_workspace.c
   rna_world.c
+  rna_xr.c
 )
 
 set(APISRC
@@ -325,6 +326,10 @@ if(WITH_INPUT_NDOF)
   add_definitions(-DWITH_INPUT_NDOF)
 endif()
 
+if(WITH_XR_OPENXR)
+  add_definitions(-DWITH_XR_OPENXR)
+endif()
+
 # Build makesrna executable
 blender_include_dirs(
   .
index 0f69adf62984f2338273a86bfeb21897b5271b4a..7ec8f6167d0541ad8c9a0ff0fe2437858404c6b1 100644 (file)
@@ -4312,6 +4312,7 @@ static RNAProcessItem PROCESS_ITEMS[] = {
     {"rna_movieclip.c", NULL, RNA_def_movieclip},
     {"rna_tracking.c", NULL, RNA_def_tracking},
     {"rna_mask.c", NULL, RNA_def_mask},
+    {"rna_xr.c", NULL, RNA_def_xr},
     {NULL, NULL},
 };
 
index 6bda8ebdb4ce707eedf377bae719e1f49d91bcaa..3e18e882e2b9f40c7035e3b4e3d35311e4792cbd 100644 (file)
@@ -205,6 +205,7 @@ void RNA_def_world(struct BlenderRNA *brna);
 void RNA_def_movieclip(struct BlenderRNA *brna);
 void RNA_def_tracking(struct BlenderRNA *brna);
 void RNA_def_mask(struct BlenderRNA *brna);
+void RNA_def_xr(struct BlenderRNA *brna);
 
 /* Common Define functions */
 
index 66aeab725ead586bf164d60b3c17c0c96555dcd3..b4bb141ba7aa73c50abff9db6bdcb695563a2e37 100644 (file)
@@ -997,7 +997,7 @@ static IDProperty *rna_View3DShading_idprops(PointerRNA *ptr, bool create)
 static void rna_3DViewShading_type_update(Main *bmain, Scene *scene, PointerRNA *ptr)
 {
   ID *id = ptr->owner_id;
-  if (GS(id->name) == ID_SCE) {
+  if (GS(id->name) != ID_SCR) {
     return;
   }
 
@@ -1333,6 +1333,25 @@ static const EnumPropertyItem *rna_SpaceView3D_stereo3d_camera_itemf(bContext *C
   }
 }
 
+static void rna_SpaceView3D_mirror_xr_session_update(Main *main,
+                                                     Scene *UNUSED(scene),
+                                                     PointerRNA *ptr)
+{
+#  ifdef WITH_XR_OPENXR
+  const wmWindowManager *wm = main->wm.first;
+
+  /* Handle mirror toggling while there is a session already. */
+  if (WM_xr_session_exists(&wm->xr)) {
+    const View3D *v3d = ptr->data;
+    const ScrArea *area = rna_area_from_space(ptr);
+    ED_view3d_xr_mirror_update(area, v3d, v3d->flag & V3D_XR_SESSION_MIRROR);
+  }
+
+#  else
+  UNUSED_VARS(main, ptr);
+#  endif
+}
+
 static int rna_SpaceView3D_icon_from_show_object_viewport_get(PointerRNA *ptr)
 {
   const View3D *v3d = (View3D *)ptr->data;
@@ -3166,19 +3185,20 @@ static void rna_def_space_view3d_shading(BlenderRNA *brna)
                               "rna_3DViewShading_type_itemf");
   RNA_def_property_ui_text(
       prop, "Viewport Shading", "Method to display/shade objects in the 3D View");
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, "rna_3DViewShading_type_update");
+  RNA_def_property_update(
+      prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, "rna_3DViewShading_type_update");
 
   prop = RNA_def_property(srna, "light", PROP_ENUM, PROP_NONE);
   RNA_def_property_enum_sdna(prop, NULL, "light");
   RNA_def_property_enum_items(prop, rna_enum_viewport_lighting_items);
   RNA_def_property_ui_text(prop, "Lighting", "Lighting Method for Solid/Texture Viewport Shading");
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "show_object_outline", PROP_BOOLEAN, PROP_NONE);
   RNA_def_property_boolean_sdna(prop, NULL, "flag", V3D_SHADING_OBJECT_OUTLINE);
   RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
   RNA_def_property_ui_text(prop, "Outline", "Show Object Outline");
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "studio_light", PROP_ENUM, PROP_NONE);
   RNA_def_property_enum_items(prop, rna_enum_studio_light_items);
@@ -3188,45 +3208,45 @@ static void rna_def_space_view3d_shading(BlenderRNA *brna)
                               "rna_View3DShading_studio_light_set",
                               "rna_View3DShading_studio_light_itemf");
   RNA_def_property_ui_text(prop, "Studiolight", "Studio lighting setup");
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "use_world_space_lighting", PROP_BOOLEAN, PROP_NONE);
   RNA_def_property_boolean_sdna(prop, NULL, "flag", V3D_SHADING_WORLD_ORIENTATION);
   RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
   RNA_def_property_ui_text(
       prop, "World Space Lighting", "Make the lighting fixed and not follow the camera");
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "show_backface_culling", PROP_BOOLEAN, PROP_NONE);
   RNA_def_property_boolean_sdna(prop, NULL, "flag", V3D_SHADING_BACKFACE_CULLING);
   RNA_def_property_ui_text(
       prop, "Backface Culling", "Use back face culling to hide the back side of faces");
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "show_cavity", PROP_BOOLEAN, PROP_NONE);
   RNA_def_property_boolean_sdna(prop, NULL, "flag", V3D_SHADING_CAVITY);
   RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
   RNA_def_property_ui_text(prop, "Cavity", "Show Cavity");
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "cavity_type", PROP_ENUM, PROP_NONE);
   RNA_def_property_enum_items(prop, cavity_type_items);
   RNA_def_property_ui_text(prop, "Cavity Type", "Way to draw the cavity shading");
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "curvature_ridge_factor", PROP_FLOAT, PROP_FACTOR);
   RNA_def_property_float_sdna(prop, NULL, "curvature_ridge_factor");
   RNA_def_property_ui_text(prop, "Curvature Ridge", "Factor for the curvature ridges");
   RNA_def_property_range(prop, 0.0f, 2.0f);
   RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "curvature_valley_factor", PROP_FLOAT, PROP_FACTOR);
   RNA_def_property_float_sdna(prop, NULL, "curvature_valley_factor");
   RNA_def_property_ui_text(prop, "Curvature Valley", "Factor for the curvature valleys");
   RNA_def_property_range(prop, 0.0f, 2.0f);
   RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "cavity_ridge_factor", PROP_FLOAT, PROP_FACTOR);
   RNA_def_property_float_sdna(prop, NULL, "cavity_ridge_factor");
@@ -3234,7 +3254,7 @@ static void rna_def_space_view3d_shading(BlenderRNA *brna)
   RNA_def_property_range(prop, 0.0f, 250.0f);
   RNA_def_property_ui_range(prop, 0.00f, 2.5f, 1, 3);
   RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "cavity_valley_factor", PROP_FLOAT, PROP_FACTOR);
   RNA_def_property_float_sdna(prop, NULL, "cavity_valley_factor");
@@ -3242,7 +3262,7 @@ static void rna_def_space_view3d_shading(BlenderRNA *brna)
   RNA_def_property_range(prop, 0.0f, 250.0f);
   RNA_def_property_ui_range(prop, 0.00f, 2.5f, 1, 3);
   RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "selected_studio_light", PROP_POINTER, PROP_NONE);
   RNA_def_property_struct_type(prop, "StudioLight");
@@ -3259,7 +3279,7 @@ static void rna_def_space_view3d_shading(BlenderRNA *brna)
       prop, "Studiolight Rotation", "Rotation of the studiolight around the Z-Axis");
   RNA_def_property_range(prop, -M_PI, M_PI);
   RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "studiolight_intensity", PROP_FLOAT, PROP_FACTOR);
   RNA_def_property_float_sdna(prop, NULL, "studiolight_intensity");
@@ -3267,7 +3287,7 @@ static void rna_def_space_view3d_shading(BlenderRNA *brna)
   RNA_def_property_ui_text(prop, "Strength", "Strength of the studiolight");
   RNA_def_property_range(prop, 0.0f, FLT_MAX);
   RNA_def_property_ui_range(prop, 0.0f, 2.0f, 1, 3);
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "studiolight_background_alpha", PROP_FLOAT, PROP_FACTOR);
   RNA_def_property_float_sdna(prop, NULL, "studiolight_background");
@@ -3275,7 +3295,7 @@ static void rna_def_space_view3d_shading(BlenderRNA *brna)
   RNA_def_property_range(prop, 0.0f, 1.0f);
   RNA_def_property_ui_range(prop, 0.0f, 1.0f, 1, 3);
   RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "studiolight_background_blur", PROP_FLOAT, PROP_FACTOR);
   RNA_def_property_float_sdna(prop, NULL, "studiolight_blur");
@@ -3284,7 +3304,7 @@ static void rna_def_space_view3d_shading(BlenderRNA *brna)
   RNA_def_property_range(prop, 0.0f, 1.0f);
   RNA_def_property_ui_range(prop, 0.0f, 1.0f, 1, 2);
   RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "color_type", PROP_ENUM, PROP_NONE);
   RNA_def_property_enum_sdna(prop, NULL, "color_type");
@@ -3292,64 +3312,65 @@ static void rna_def_space_view3d_shading(BlenderRNA *brna)
   RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_View3DShading_color_type_itemf");
   RNA_def_property_ui_text(prop, "Color", "Color Type");
   RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, "rna_GPencil_update");
+  RNA_def_property_update(
+      prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, "rna_GPencil_update");
 
   prop = RNA_def_property(srna, "wireframe_color_type", PROP_ENUM, PROP_NONE);
   RNA_def_property_enum_sdna(prop, NULL, "wire_color_type");
   RNA_def_property_enum_items(prop, rna_enum_shading_color_type_items);
   RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_View3DShading_color_type_itemf");
   RNA_def_property_ui_text(prop, "Color", "Color Type");
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "single_color", PROP_FLOAT, PROP_COLOR);
   RNA_def_property_float_sdna(prop, NULL, "single_color");
   RNA_def_property_array(prop, 3);
   RNA_def_property_ui_text(prop, "Color", "Color for single color mode");
   RNA_def_property_range(prop, 0.0f, 1.0f);
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "background_type", PROP_ENUM, PROP_NONE);
   RNA_def_property_enum_items(prop, background_type_items);
   RNA_def_property_ui_text(prop, "Background", "Way to draw the background");
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "background_color", PROP_FLOAT, PROP_COLOR);
   RNA_def_property_array(prop, 3);
   RNA_def_property_ui_text(prop, "Background Color", "Color for custom background color");
   RNA_def_property_range(prop, 0.0f, 1.0f);
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "show_shadows", PROP_BOOLEAN, PROP_NONE);
   RNA_def_property_boolean_sdna(prop, NULL, "flag", V3D_SHADING_SHADOW);
   RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
   RNA_def_property_ui_text(prop, "Shadow", "Show Shadow");
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "show_xray", PROP_BOOLEAN, PROP_NONE);
   RNA_def_property_boolean_sdna(prop, NULL, "flag", V3D_SHADING_XRAY);
   RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
   RNA_def_property_ui_text(prop, "Show X-Ray", "Show whole scene transparent");
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "show_xray_wireframe", PROP_BOOLEAN, PROP_NONE);
   RNA_def_property_boolean_sdna(prop, NULL, "flag", V3D_SHADING_XRAY_WIREFRAME);
   RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
   RNA_def_property_ui_text(prop, "Show X-Ray", "Show whole scene transparent");
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "xray_alpha", PROP_FLOAT, PROP_FACTOR);
   RNA_def_property_float_sdna(prop, NULL, "xray_alpha");
   RNA_def_property_ui_text(prop, "X-Ray Alpha", "Amount of alpha to use");
   RNA_def_property_range(prop, 0.0f, 1.0f);
   RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "xray_alpha_wireframe", PROP_FLOAT, PROP_FACTOR);
   RNA_def_property_float_sdna(prop, NULL, "xray_alpha_wire");
   RNA_def_property_ui_text(prop, "X-Ray Alpha", "Amount of alpha to use");
   RNA_def_property_range(prop, 0.0f, 1.0f);
   RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "use_dof", PROP_BOOLEAN, PROP_NONE);
   RNA_def_property_boolean_sdna(prop, NULL, "flag", V3D_SHADING_DEPTH_OF_FIELD);
@@ -3358,46 +3379,46 @@ static void rna_def_space_view3d_shading(BlenderRNA *brna)
       prop,
       "Depth Of Field",
       "Use depth of field on viewport using the values from the active camera");
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "use_scene_lights", PROP_BOOLEAN, PROP_NONE);
   RNA_def_property_boolean_sdna(prop, NULL, "flag", V3D_SHADING_SCENE_LIGHTS);
   RNA_def_property_boolean_default(prop, false);
   RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
   RNA_def_property_ui_text(prop, "Scene Lights", "Render lights and light probes of the scene");
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "use_scene_world", PROP_BOOLEAN, PROP_NONE);
   RNA_def_property_boolean_sdna(prop, NULL, "flag", V3D_SHADING_SCENE_WORLD);
   RNA_def_property_boolean_default(prop, false);
   RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
   RNA_def_property_ui_text(prop, "Scene World", "Use scene world for lighting");
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "use_scene_lights_render", PROP_BOOLEAN, PROP_NONE);
   RNA_def_property_boolean_sdna(prop, NULL, "flag", V3D_SHADING_SCENE_LIGHTS_RENDER);
   RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
   RNA_def_property_ui_text(prop, "Scene Lights", "Render lights and light probes of the scene");
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "use_scene_world_render", PROP_BOOLEAN, PROP_NONE);
   RNA_def_property_boolean_sdna(prop, NULL, "flag", V3D_SHADING_SCENE_WORLD_RENDER);
   RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
   RNA_def_property_ui_text(prop, "Scene World", "Use scene world for lighting");
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "show_specular_highlight", PROP_BOOLEAN, PROP_NONE);
   RNA_def_property_boolean_sdna(prop, NULL, "flag", V3D_SHADING_SPECULAR_HIGHLIGHT);
   RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
   RNA_def_property_ui_text(prop, "Specular Highlights", "Render specular highlights");
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "object_outline_color", PROP_FLOAT, PROP_COLOR);
   RNA_def_property_float_sdna(prop, NULL, "object_outline_color");
   RNA_def_property_array(prop, 3);
   RNA_def_property_ui_text(prop, "Outline Color", "Color for object outline");
   RNA_def_property_range(prop, 0.0f, 1.0f);
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "shadow_intensity", PROP_FLOAT, PROP_FACTOR);
   RNA_def_property_float_sdna(prop, NULL, "shadow_intensity");
@@ -3405,7 +3426,7 @@ static void rna_def_space_view3d_shading(BlenderRNA *brna)
   RNA_def_property_range(prop, 0.0f, 1.0f);
   RNA_def_property_ui_range(prop, 0.00f, 1.0f, 1, 3);
   RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 
   prop = RNA_def_property(srna, "render_pass", PROP_ENUM, PROP_NONE);
   RNA_def_property_enum_sdna(prop, NULL, "render_pass");
@@ -3413,7 +3434,7 @@ static void rna_def_space_view3d_shading(BlenderRNA *brna)
   RNA_def_property_ui_text(prop, "Render Pass", "Render Pass to show in the viewport");
   RNA_def_property_enum_funcs(
       prop, "rna_3DViewShading_render_pass_get", NULL, "rna_3DViewShading_render_pass_itemf");
-  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D | NS_VIEW3D_SHADING, NULL);
 }
 
 static void rna_def_space_view3d_overlay(BlenderRNA *brna)
@@ -4189,6 +4210,15 @@ static void rna_def_space_view3d(BlenderRNA *brna)
   RNA_def_property_ui_text(prop, "Volume Alpha", "Opacity (alpha) of the cameras' frustum volume");
   RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, NULL);
 
+  prop = RNA_def_property(srna, "mirror_xr_session", PROP_BOOLEAN, PROP_NONE);
+  RNA_def_property_boolean_sdna(prop, NULL, "flag", V3D_XR_SESSION_MIRROR);
+  RNA_def_property_ui_text(
+      prop,
+      "Mirror VR Session",
+      "Synchronize the viewer perspective of virtual reality sessions with this 3D viewport");
+  RNA_def_property_update(
+      prop, NC_SPACE | ND_SPACE_VIEW3D, "rna_SpaceView3D_mirror_xr_session_update");
+
   {
     struct {
       const char *name;
@@ -4268,7 +4298,7 @@ static void rna_def_space_view3d(BlenderRNA *brna)
   RNA_def_struct_ui_text(srna, "3D View Region", "3D View region data");
 
   prop = RNA_def_property(srna, "lock_rotation", PROP_BOOLEAN, PROP_NONE);
-  RNA_def_property_boolean_sdna(prop, NULL, "viewlock", RV3D_LOCKED);
+  RNA_def_property_boolean_sdna(prop, NULL, "viewlock", RV3D_LOCK_ROTATION);
   RNA_def_property_ui_text(prop, "Lock", "Lock view rotation in side views");
   RNA_def_property_update(prop, NC_SPACE | ND_SPACE_VIEW3D, "rna_RegionView3D_quadview_update");
 
index 929540f10aca1a3a0c967e4997d9feab58a39147..261f1fb9d3b0095cfd0418519ac92a6530f129ae 100644 (file)
@@ -1266,6 +1266,20 @@ static void rna_wmClipboard_set(PointerRNA *UNUSED(ptr), const char *value)
   WM_clipboard_text_set((void *)value, false);
 }
 
+static PointerRNA rna_WindowManager_xr_session_state_get(PointerRNA *ptr)
+{
+  wmWindowManager *wm = ptr->data;
+  struct wmXrSessionState *state =
+#  ifdef WITH_XR_OPENXR
+      WM_xr_session_state_handle_get(&wm->xr);
+#  else
+      NULL;
+  UNUSED_VAR(wm);
+#  endif
+
+  return rna_pointer_inherit_refine(ptr, &RNA_XrSessionState, state);
+}
+
 #  ifdef WITH_PYTHON
 
 static bool rna_operator_poll_cb(bContext *C, wmOperatorType *ot)
@@ -2484,6 +2498,18 @@ static void rna_def_windowmanager(BlenderRNA *brna)
       prop, "rna_wmClipboard_get", "rna_wmClipboard_length", "rna_wmClipboard_set");
   RNA_def_property_ui_text(prop, "Text Clipboard", "");
 
+  prop = RNA_def_property(srna, "xr_session_settings", PROP_POINTER, PROP_NONE);
+  RNA_def_property_pointer_sdna(prop, NULL, "xr.session_settings");
+  RNA_def_property_flag(prop, PROP_NEVER_NULL);
+  RNA_def_property_ui_text(prop, "XR Session Settings", "");
+
+  prop = RNA_def_property(srna, "xr_session_state", PROP_POINTER, PROP_NONE);
+  RNA_def_property_struct_type(prop, "XrSessionState");
+  RNA_def_property_pointer_funcs(prop, "rna_WindowManager_xr_session_state_get", NULL, NULL, NULL);
+  RNA_def_property_clear_flag(prop, PROP_EDITABLE);
+  RNA_def_property_ui_text(
+      prop, "XR Session State", "Runtime state information about the VR session");
+
   RNA_api_wm(srna);
 }
 
index da12c7820520ddc16d57fd187012f27dd26d3bbe..47d793382a72dc5cd168b306a339a0923208716d 100644 (file)
@@ -1376,6 +1376,12 @@ static void rna_def_gizmogroup(BlenderRNA *brna)
        0,
        "Tool Init",
        "Postpone running until tool operator run (when used with a tool)"},
+      {WM_GIZMOGROUPTYPE_VR_REDRAWS,
+       "VR_REDRAWS",
+       0,
+       "VR Redraws",
+       "The gizmos are made for use with virtual reality sessions and require special redraw "
+       "management"},
       {0, NULL, 0, NULL, NULL},
   };
   prop = RNA_def_property(srna, "bl_options", PROP_ENUM, PROP_NONE);
diff --git a/source/blender/makesrna/intern/rna_xr.c b/source/blender/makesrna/intern/rna_xr.c
new file mode 100644 (file)
index 0000000..76cbc99
--- /dev/null
@@ -0,0 +1,229 @@
+/*
+ * 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 RNA
+ */
+
+#include "DNA_view3d_types.h"
+#include "DNA_xr_types.h"
+
+#include "RNA_define.h"
+#include "RNA_enum_types.h"
+
+#include "WM_types.h"
+
+#include "rna_internal.h"
+
+#ifdef RNA_RUNTIME
+
+#  include "BLI_math.h"
+
+#  include "WM_api.h"
+
+static bool rna_XrSessionState_is_running(bContext *C)
+{
+#  ifdef WITH_XR_OPENXR
+  const wmWindowManager *wm = CTX_wm_manager(C);
+  return WM_xr_session_is_ready(&wm->xr);
+#  else
+  UNUSED_VARS(C);
+  return false;
+#  endif
+}
+
+#  ifdef WITH_XR_OPENXR
+static wmXrData *rna_XrSessionState_wm_xr_data_get(PointerRNA *ptr)
+{
+  /* Callers could also get XrSessionState pointer through ptr->data, but prefer if we just
+   * consistently pass wmXrData pointers to the WM_xr_xxx() API. */
+
+  BLI_assert(ptr->type == &RNA_XrSessionState);
+
+  wmWindowManager *wm = (wmWindowManager *)ptr->owner_id;
+  BLI_assert(wm && (GS(wm->id.name) == ID_WM));
+
+  return &wm->xr;
+}
+#  endif
+
+static void rna_XrSessionState_viewer_pose_location_get(PointerRNA *ptr, float *r_values)
+{
+#  ifdef WITH_XR_OPENXR
+  const wmXrData *xr = rna_XrSessionState_wm_xr_data_get(ptr);
+  WM_xr_session_state_viewer_pose_location_get(xr, r_values);
+#  else
+  UNUSED_VARS(ptr);
+  zero_v3(r_values);
+#  endif
+}
+
+static void rna_XrSessionState_viewer_pose_rotation_get(PointerRNA *ptr, float *r_values)
+{
+#  ifdef WITH_XR_OPENXR
+  const wmXrData *xr = rna_XrSessionState_wm_xr_data_get(ptr);
+  WM_xr_session_state_viewer_pose_rotation_get(xr, r_values);
+#  else
+  UNUSED_VARS(ptr);
+  unit_qt(r_values);
+#  endif
+}
+
+#else /* RNA_RUNTIME */
+
+static void rna_def_xr_session_settings(BlenderRNA *brna)
+{
+  StructRNA *srna;
+  PropertyRNA *prop;
+
+  static const EnumPropertyItem base_pose_types[] = {
+      {XR_BASE_POSE_SCENE_CAMERA,
+       "SCENE_CAMERA",
+       0,
+       "Scene Camera",
+       "Follow the active scene camera to define the VR view's base pose"},
+      {XR_BASE_POSE_OBJECT,
+       "OBJECT",
+       0,
+       "Object",
+       "Follow the transformation of an object to define the VR view's base pose"},
+      {XR_BASE_POSE_CUSTOM,
+       "CUSTOM",
+       0,
+       "Custom",
+       "Follow a custom transformation to define the VR view's base pose"},
+      {0, NULL, 0, NULL, NULL},
+  };
+
+  srna = RNA_def_struct(brna, "XrSessionSettings", NULL);
+  RNA_def_struct_ui_text(srna, "XR Session Settings", "");
+
+  prop = RNA_def_property(srna, "shading", PROP_POINTER, PROP_NONE);
+  RNA_def_property_flag(prop, PROP_NEVER_NULL);
+  RNA_def_property_ui_text(prop, "Shading Settings", "");
+  RNA_def_property_clear_flag(prop, PROP_EDITABLE);
+
+  prop = RNA_def_property(srna, "base_pose_type", PROP_ENUM, PROP_NONE);
+  RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
+  RNA_def_property_enum_items(prop, base_pose_types);
+  RNA_def_property_ui_text(
+      prop,
+      "Base Pose Type",
+      "Define where the location and rotation for the VR view come from, to which "
+      "translation and rotation deltas from the VR headset will be applied to");
+  RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL);
+
+  prop = RNA_def_property(srna, "base_pose_object", PROP_POINTER, PROP_NONE);
+  RNA_def_property_flag(prop, PROP_EDITABLE);
+  RNA_def_property_ui_text(prop,
+                           "Base Pose Object",
+                           "Object to take the location and rotation to which translation and "
+                           "rotation deltas from the VR headset will be applied to");
+  RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL);
+
+  prop = RNA_def_property(srna, "base_pose_location", PROP_FLOAT, PROP_TRANSLATION);
+  RNA_def_property_ui_text(prop,
+                           "Base Pose Location",
+                           "Coordinates to apply translation deltas from the VR headset to");
+  RNA_def_property_ui_range(prop, -FLT_MAX, FLT_MAX, 1, RNA_TRANSLATION_PREC_DEFAULT);
+  RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL);
+
+  prop = RNA_def_property(srna, "base_pose_angle", PROP_FLOAT, PROP_AXISANGLE);
+  RNA_def_property_ui_text(
+      prop,
+      "Base Pose Angle",
+      "Rotation angle around the Z-Axis to apply the rotation deltas from the VR headset to");
+  RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL);
+
+  prop = RNA_def_property(srna, "show_floor", PROP_BOOLEAN, PROP_NONE);
+  RNA_def_property_boolean_sdna(prop, NULL, "draw_flags", V3D_OFSDRAW_SHOW_GRIDFLOOR);
+  RNA_def_property_ui_text(prop, "Display Grid Floor", "Show the ground plane grid");
+  RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL);
+
+  prop = RNA_def_property(srna, "show_annotation", PROP_BOOLEAN, PROP_NONE);
+  RNA_def_property_boolean_sdna(prop, NULL, "draw_flags", V3D_OFSDRAW_SHOW_ANNOTATION);
+  RNA_def_property_ui_text(prop, "Show Annotation", "Show annotations for this view");
+  RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL);
+
+  prop = RNA_def_property(srna, "clip_start", PROP_FLOAT, PROP_DISTANCE);
+  RNA_def_property_range(prop, 1e-6f, FLT_MAX);
+  RNA_def_property_ui_range(prop, 0.001f, FLT_MAX, 10, 3);
+  RNA_def_property_ui_text(prop, "Clip Start", "VR viewport near clipping distance");
+  RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL);
+
+  prop = RNA_def_property(srna, "clip_end", PROP_FLOAT, PROP_DISTANCE);
+  RNA_def_property_range(prop, 1e-6f, FLT_MAX);
+  RNA_def_property_ui_range(prop, 0.001f, FLT_MAX, 10, 3);
+  RNA_def_property_ui_text(prop, "Clip End", "VR viewport far clipping distance");
+  RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL);
+
+  prop = RNA_def_property(srna, "use_positional_tracking", PROP_BOOLEAN, PROP_NONE);
+  RNA_def_property_boolean_sdna(prop, NULL, "flag", XR_SESSION_USE_POSITION_TRACKING);
+  RNA_def_property_ui_text(
+      prop,
+      "Positional Tracking",
+      "Allow VR headsets to affect the location in virtual space, in addition to the rotation");
+  RNA_def_property_update(prop, NC_WM | ND_XR_DATA_CHANGED, NULL);
+}
+
+static void rna_def_xr_session_state(BlenderRNA *brna)
+{
+  StructRNA *srna;
+  FunctionRNA *func;
+  PropertyRNA *parm, *prop;
+
+  srna = RNA_def_struct(brna, "XrSessionState", NULL);
+  RNA_def_struct_clear_flag(srna, STRUCT_UNDO);
+  RNA_def_struct_ui_text(srna, "Session State", "Runtime state information about the VR session");
+
+  func = RNA_def_function(srna, "is_running", "rna_XrSessionState_is_running");
+  RNA_def_function_ui_description(func, "Query if the VR session is currently running");
+  RNA_def_function_flag(func, FUNC_NO_SELF);
+  parm = RNA_def_pointer(func, "context", "Context", "", "");
+  RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED);
+  parm = RNA_def_boolean(func, "result", 0, "Result", "");
+  RNA_def_function_return(func, parm);
+
+  prop = RNA_def_property(srna, "viewer_pose_location", PROP_FLOAT, PROP_TRANSLATION);
+  RNA_def_property_array(prop, 3);
+  RNA_def_property_float_funcs(prop, "rna_XrSessionState_viewer_pose_location_get", NULL, NULL);
+  RNA_def_property_clear_flag(prop, PROP_EDITABLE);
+  RNA_def_property_ui_text(
+      prop,
+      "Viewer Pose Location",
+      "Last known location of the viewer pose (center between the eyes) in world space");
+
+  prop = RNA_def_property(srna, "viewer_pose_rotation", PROP_FLOAT, PROP_QUATERNION);
+  RNA_def_property_array(prop, 4);
+  RNA_def_property_float_funcs(prop, "rna_XrSessionState_viewer_pose_rotation_get", NULL, NULL);
+  RNA_def_property_clear_flag(prop, PROP_EDITABLE);
+  RNA_def_property_ui_text(
+      prop,
+      "Viewer Pose Rotation",
+      "Last known rotation of the viewer pose (center between the eyes) in world space");
+}
+
+void RNA_def_xr(BlenderRNA *brna)
+{
+  RNA_define_animate_sdna(false);
+
+  rna_def_xr_session_settings(brna);
+  rna_def_xr_session_state(brna);
+
+  RNA_define_animate_sdna(true);
+}
+
+#endif /* RNA_RUNTIME */
index f09440a1ceb068a3067455018d9b2ca7030d9ae2..f128c46ec052969c6906f5d0e3a34df773ee08c0 100644 (file)
@@ -257,6 +257,7 @@ static PyObject *bpygpu_offscreen_draw_view3d(BPyGPUOffScreen *self,
                            (float(*)[4])py_mat_projection->matrix,
                            true,
                            true,
+                           true,
                            "",
                            false,
                            self->ofs,
index e7a4ca9a005b44e26dad18288a842dc500e2eb93..a1b67216f1a0b16c4b30a6f3d33217f13563cac3 100644 (file)
@@ -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}")
index 6781f055fc2f769e918f4a4017b1561bf2bd6415..1be7a8679c45b4612beff0d5f41c6a1f8731e51f 100644 (file)
@@ -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
index b9dd6b3923daa1565afa9e23bc9e6bda84ed3b6a..1edfe252cf4c946cd0ba75845074f14ab7858c20 100644 (file)
@@ -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)
index c667dd564f7abf227db126af81fb7e5cd6446565..04311b9c0702988f636da04aad19632f2e717670 100644 (file)
@@ -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;
 
 /**
index 28690fec2d828c0d9cb5900aae8e0a8d5232f067..d463f1df2e94f1c40c9db814e9ceef1b0b34e0af 100644 (file)
@@ -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];
index 2edef54c7780e7bb951db4c72dc75e3e82e5148a..2a4ba7fdb8e9fad06dcce182eeed18682eb7523d 100644 (file)
@@ -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);
index 9ccff2a3e3d4471a9846aa3c6d40cdd6976582c8..a0e8374a46ab314dde9cb04687ae1d27cff32f6d 100644 (file)
@@ -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))
index a87f0a3e42c070a6ac28051778f832e8a4ecfdc8..7b04b40e62653aba138ac00680a1b631cb0a0d17 100644 (file)
@@ -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();
index d649b21569e38665232b014b9e27213a902af57b..1d6ae10c3962f43d9f1b3142ba5bded86f189625 100644 (file)
@@ -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 (file)
index 0000000..cb575cc
--- /dev/null
@@ -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));
+}
index fe6272686bcb6381e43f35f5022c1c38016c4618..eaa0fa7b7a5cce1311b7b3ac7758f9dbd5659576 100644 (file)
@@ -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 (file)
index 0000000..bd9fd7c
--- /dev/null
@@ -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 */
index 5bb9de87e8231d43c9b6679f26b8db6cddec3ae9..22c01df5d3b5d2ac23a814cdd8c3a60786be5862 100644 (file)
@@ -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 (file)
index 0000000..98d67c5
--- /dev/null
@@ -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__ */
index c485caf2779dffd03c4e1ef59305b47c1f7c1f40..bbef3a4d52a5a3cca81f2eac8455e9eecc8c1dfd 100644 (file)
@@ -111,6 +111,10 @@ if(WITH_FREESTYLE)
   add_definitions(-DWITH_FREESTYLE)
 endif()
 
+if(WITH_XR_OPENXR)
+  add_definitions(-DWITH_XR_OPENXR)
+endif()
+
 # Setup the exe sources and buildinfo
 set(SRC
   creator.c
@@ -856,6 +860,8 @@ elseif(WIN32)
       ${CMAKE_SOURCE_DIR}/release/windows/batch/blender_debug_gpu_glitchworkaround.cmd
       ${CMAKE_SOURCE_DIR}/release/windows/batch/blender_debug_log.cmd
       ${CMAKE_SOURCE_DIR}/release/windows/batch/blender_factory_startup.cmd
+      ${CMAKE_SOURCE_DIR}/release/windows/batch/blender_oculus.cmd
+      ${CMAKE_SOURCE_DIR}/release/windows/batch/oculus.json
     DESTINATION "."
   )
 
index f265112570f181bde16881ccde011a695e2ddc60..565cf60d2e1db3612490ac9f2fac65e6b6b5b6b6 100644 (file)
@@ -603,6 +603,10 @@ static int arg_handle_print_help(int UNUSED(argc), const char **UNUSED(argv), vo
   BLI_argsPrintArgDoc(ba, "--debug-gpu-shaders");
   BLI_argsPrintArgDoc(ba, "--debug-gpu-force-workarounds");
   BLI_argsPrintArgDoc(ba, "--debug-wm");
+#  ifdef WITH_XR_OPENXR
+  BLI_argsPrintArgDoc(ba, "--debug-xr");
+  BLI_argsPrintArgDoc(ba, "--debug-xr-time");
+#  endif
   BLI_argsPrintArgDoc(ba, "--debug-all");
   BLI_argsPrintArgDoc(ba, "--debug-io");
 
@@ -940,6 +944,16 @@ static const char arg_handle_debug_mode_generic_set_doc_wm[] =
     "\n\t"
     "Enable debug messages for the window manager, shows all operators in search, shows "
     "keymap errors.";
+#  ifdef WITH_XR_OPENXR
+static const char arg_handle_debug_mode_generic_set_doc_xr[] =
+    "\n\t"
+    "Enable debug messages for virtual reality contexts.\n"
+    "\tEnables the OpenXR API validation layer, (OpenXR) debug messages and general information "
+    "prints.";
+static const char arg_handle_debug_mode_generic_set_doc_xr_time[] =
+    "\n\t"
+    "Enable debug messages for virtual reality frame rendering times.";
+#  endif
 static const char arg_handle_debug_mode_generic_set_doc_jobs[] =
     "\n\t"
     "Enable time profiling for background jobs.";
@@ -2091,6 +2105,16 @@ void main_args_setup(bContext *C, bArgs *ba)
               (void *)G_DEBUG_HANDLERS);
   BLI_argsAdd(
       ba, 1, NULL, "--debug-wm", CB_EX(arg_handle_debug_mode_generic_set, wm), (void *)G_DEBUG_WM);
+#  ifdef WITH_XR_OPENXR
+  BLI_argsAdd(
+      ba, 1, NULL, "--debug-xr", CB_EX(arg_handle_debug_mode_generic_set, xr), (void *)G_DEBUG_XR);
+  BLI_argsAdd(ba,
+              1,
+              NULL,
+              "--debug-xr-time",
+              CB_EX(arg_handle_debug_mode_generic_set, xr_time),
+              (void *)G_DEBUG_XR_TIME);
+#  endif
   BLI_argsAdd(ba,
               1,
               NULL,
index bb730bc362ccb7add5963e135e24624b0902183b..f0c2f3f7fdfa69837a1603d922b199b17d00498e 100644 (file)
@@ -50,6 +50,9 @@ def _init_addon_blacklist():
     # netrender has known problems re-registering
     BLACKLIST_ADDONS.add("netrender")
 
+    if not bpy.app.build_options.xr_openxr:
+        BLACKLIST_ADDONS.add("viewport_vr_preview")
+
     for mod in addon_utils.modules():
         if addon_utils.module_bl_info(mod)['blender'] < (2, 80, 0):
             BLACKLIST_ADDONS.add(mod.__name__)
index 2252af7a02fb19f817a51d0e973cbed3aec99ce2..5d1a5dd8ee013c91a56a9709309df1edf01b9f07 100644 (file)
@@ -56,6 +56,9 @@ MODULE_SYS_PATHS = {
 if not bpy.app.build_options.freestyle:
     BLACKLIST.add("render_freestyle_svg")
 
+if not bpy.app.build_options.xr_openxr:
+    BLACKLIST.add("viewport_vr_preview")
+
 BLACKLIST_DIRS = (
     os.path.join(bpy.utils.resource_path('USER'), "scripts"),
 ) + tuple(addon_utils.paths()[1:])