[Bf-blender-cvs] [8537cdc71d8] soc-2019-openxr: Core XR Support [part 3]: Ghost-XR API based on OpenXR

Julian Eisel noreply at git.blender.org
Fri Nov 15 11:29:41 CET 2019


Commit: 8537cdc71d8145572a06eabc7ec39030b3c99118
Author: Julian Eisel
Date:   Tue Nov 5 14:28:22 2019 +0100
Branches: soc-2019-openxr
https://developer.blender.org/rB8537cdc71d8145572a06eabc7ec39030b3c99118

Core XR Support [part 3]: Ghost-XR API based on OpenXR

## Design Overview
* For code using this API, the most important object is a GHOST_XrContext handle. Through it, all API functions and internal state can be accessed/modified.
* Main responsibilities of the Ghost XR-context are to manage lifetimes of the OpenXR runtime connection (represented by XrInstance), the session and to delegate operations/data to the session.
* The OpenXR related graphics code, which is OS dependent, is managed through a `GHOST_IXrGraphicsBinding` interface, that can be implemented for the different graphics libraries supported (currently OpenGL and DirectX).
* Much of this code here has to follow the OpenXR specification and is based on the OpenXR [[https://github.com/KhronosGroup/OpenXR-SDK-Source/tree/master/src/tests/hello_xr | `hello_xr`]] implentation.
* In future we may want to take some code out of the context, e.g. extension and API layer management.
* There are runtime debugging and benchmarking options (exposed through --debug-xr and --debug-xr-time, but not as part of this patch).
* Error handling is described in a section below.

## Why have this in Ghost?

Early on, I decided to do the OpenXR level access through GHOST. Main reasons:
* OpenXR requires access to low level, OS dependent graphics lib data (e.g. see [[https://www.khronos.org/registry/OpenXR/specs/0.90/man/html/openxr.html#XrGraphicsBindingOpenGLXlibKHR| XrGraphicsBindingOpenGLXlibKHR]])
* Some C++ features appeared handy (`std::vector`, RAII + exception handling, cleaner code through object methods, etc.)
* General low level nature of the OpenXR API

After all I think much of the functionality is too high level to live in GHOST however. I would like to address this by having a separate `VAMR` (virtual + augmented + mixed reality) module, placed in `intern/`.  The main issue is getting this to work well with Ghost data, especially how to get the mentioned low level data out of Ghost.
This is something I'd like to look into again before too long, but for now I think having this in Ghost is reasonable.

## Error Handling Strategy

The error handling strategy I chose uses C++ exceptions, a controversial feature. Let me explain why I think this is reasonable here.

The strategy requirements were:
* If an error occurs, cleanly exit the VR session (or destroy the entire context), causing no resource leaks or side effects to the rest of Blender.
* Show a *useful* error message to the user.
* Don't impair readability of code too much with error handling.

Here's why I chose an exception based strategy:
* Most alternatives require early exiting functions. This early exiting has to be 'bubbled up' the call stack to the point that performs error handling. For safe code, early exit checks have to be performed everywhere and code gets really impaired by error checking. Tried this first and wasn't happy at all. Even if error handling is wrapped into macros.
* All `GHOST_Xr` resources are managed via RAII. So stack unwinding will cause them to be released cleanly whenever an exception is thrown.
* `GHOST_Xr` has a clear boundary (the Ghost C-API) with only a handful of public functions. That is the only place we need to have try-catch blocks at. (Generally, try-catch blocks at kinda random places are a bad code smell IMHO. Module boundaries are a valid place to put them.)
* Exceptions allow us to pass multiple bits of error information through mulitple layers of the call stack. This information can also be made specific with a useful error message. As of now, they conain a user error message, the OpenXR error code (if any), as well as the exact source code location the error was caught at.

So the strategy I went with works as follows:
* If a VR related error occurs within `GHOST_Xr`, throw an exception (`GHOST_XrException` currently).
* OpenXR calls are wrapped into a macro throwing an exception if the return value indicates an error.
* Useful debugging information and user messages are stored in the exceptions.
* All resources must be managed through RAII, so throwing an exception will release 'dangling' ones cleanly.
* In the GHOST C-API wrappers, the exceptions are caught and contained error information is forwarded to a custom error handling callback.
* The error handling callback is set in `wm_xr.c`, prior to creating the XR-Context, and implements clean destruction of the context.

Differential Revision: https://developer.blender.org/D6192

===================================================================

M	intern/ghost/CMakeLists.txt
M	intern/ghost/GHOST_C-api.h
A	intern/ghost/GHOST_IXrContext.h
M	intern/ghost/GHOST_Types.h
M	intern/ghost/intern/GHOST_C-api.cpp
M	intern/ghost/intern/GHOST_ContextD3D.h
M	intern/ghost/intern/GHOST_ContextGLX.cpp
M	intern/ghost/intern/GHOST_ContextGLX.h
M	intern/ghost/intern/GHOST_ContextWGL.h
A	intern/ghost/intern/GHOST_IXrGraphicsBinding.h
A	intern/ghost/intern/GHOST_Xr.cpp
A	intern/ghost/intern/GHOST_XrContext.cpp
A	intern/ghost/intern/GHOST_XrContext.h
A	intern/ghost/intern/GHOST_XrEvent.cpp
A	intern/ghost/intern/GHOST_XrException.h
A	intern/ghost/intern/GHOST_XrGraphicsBinding.cpp
A	intern/ghost/intern/GHOST_XrSession.cpp
A	intern/ghost/intern/GHOST_XrSession.h
A	intern/ghost/intern/GHOST_Xr_intern.h
A	intern/ghost/intern/GHOST_Xr_openxr_includes.h

===================================================================

diff --git a/intern/ghost/CMakeLists.txt b/intern/ghost/CMakeLists.txt
index 4779ca6d306..3902322113c 100644
--- a/intern/ghost/CMakeLists.txt
+++ b/intern/ghost/CMakeLists.txt
@@ -349,6 +349,35 @@ elseif(WIN32)
 
 endif()
 
+if(WITH_OPENXR)
+  list(APPEND SRC
+    intern/GHOST_Xr.cpp
+    intern/GHOST_XrContext.cpp
+    intern/GHOST_XrEvent.cpp
+    intern/GHOST_XrGraphicsBinding.cpp
+    intern/GHOST_XrSession.cpp
+
+    GHOST_IXrContext.h
+    intern/GHOST_Xr_intern.h
+    intern/GHOST_Xr_openxr_includes.h
+    intern/GHOST_XrContext.h
+    intern/GHOST_IXrGraphicsBinding.h
+    intern/GHOST_XrSession.h
+  )
+  list(APPEND INC_SYS
+    ${OPENXR_SDK_INCLUDE_DIR}
+  )
+  if(WIN32)
+    list(APPEND LIB
+      shlwapi
+    )
+  endif()
+
+  include(xr_platform_defines)
+
+  add_definitions(-DWITH_OPENXR)
+endif()
+
 add_definitions(${GL_DEFINITIONS})
 
 blender_add_lib(bf_intern_ghost "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
diff --git a/intern/ghost/GHOST_C-api.h b/intern/ghost/GHOST_C-api.h
index 0f96b6aac27..5a1e2a60f4b 100644
--- a/intern/ghost/GHOST_C-api.h
+++ b/intern/ghost/GHOST_C-api.h
@@ -30,21 +30,6 @@
 extern "C" {
 #endif
 
-/**
- * Creates a "handle" for a C++ GHOST object.
- * A handle is just an opaque pointer to an empty struct.
- * In the API the pointer is cast to the actual C++ class.
- * The 'name' argument to the macro is the name of the handle to create.
- */
-
-GHOST_DECLARE_HANDLE(GHOST_SystemHandle);
-GHOST_DECLARE_HANDLE(GHOST_TimerTaskHandle);
-GHOST_DECLARE_HANDLE(GHOST_WindowHandle);
-GHOST_DECLARE_HANDLE(GHOST_EventHandle);
-GHOST_DECLARE_HANDLE(GHOST_RectangleHandle);
-GHOST_DECLARE_HANDLE(GHOST_EventConsumerHandle);
-GHOST_DECLARE_HANDLE(GHOST_ContextHandle);
-
 /**
  * Definition of a callback routine that receives events.
  * \param event The event received.
@@ -1001,6 +986,36 @@ extern void GHOST_BeginIME(GHOST_WindowHandle windowhandle,
  */
 extern void GHOST_EndIME(GHOST_WindowHandle windowhandle);
 
+#ifdef WITH_OPENXR
+
+/* XR-context */
+
+/**
+ * Set a custom callback to be executed whenever an error occurs. Should be set before calling
+ * #GHOST_XrContextCreate().
+ */
+void GHOST_XrErrorHandler(GHOST_XrErrorHandlerFn handler_fn, void *customdata);
+
+GHOST_XrContextHandle GHOST_XrContextCreate(const GHOST_XrContextCreateInfo *create_info);
+void GHOST_XrContextDestroy(GHOST_XrContextHandle xr_context);
+
+void GHOST_XrGraphicsContextBindFuncs(GHOST_XrContextHandle xr_context,
+                                      GHOST_XrGraphicsContextBindFn bind_fn,
+                                      GHOST_XrGraphicsContextUnbindFn unbind_fn);
+
+void GHOST_XrDrawViewFunc(GHOST_XrContextHandle xr_context, GHOST_XrDrawViewFn draw_view_fn);
+
+/* sessions */
+int GHOST_XrSessionIsRunning(const GHOST_XrContextHandle xr_context);
+void GHOST_XrSessionStart(GHOST_XrContextHandle xr_context,
+                          const GHOST_XrSessionBeginInfo *begin_info);
+void GHOST_XrSessionEnd(GHOST_XrContextHandle xr_context);
+void GHOST_XrSessionDrawViews(GHOST_XrContextHandle xr_context, void *customdata);
+
+/* events */
+GHOST_TSuccess GHOST_XrEventsHandle(GHOST_XrContextHandle xr_context);
+#endif
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/intern/ghost/GHOST_IXrContext.h b/intern/ghost/GHOST_IXrContext.h
new file mode 100644
index 00000000000..362bc923ee8
--- /dev/null
+++ b/intern/ghost/GHOST_IXrContext.h
@@ -0,0 +1,42 @@
+/*
+ * 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 GHOST
+ */
+
+#ifndef __GHOST_IXRCONTEXT_H__
+#define __GHOST_IXRCONTEXT_H__
+
+#include "GHOST_Types.h"
+
+class GHOST_IXrContext {
+ public:
+  virtual ~GHOST_IXrContext() = default;
+
+  virtual void startSession(const GHOST_XrSessionBeginInfo *begin_info) = 0;
+  virtual void endSession() = 0;
+  virtual bool isSessionRunning() const = 0;
+  virtual void drawSessionViews(void *draw_customdata) = 0;
+
+  virtual void dispatchErrorMessage(const class GHOST_XrException *) const = 0;
+
+  virtual void setGraphicsContextBindFuncs(GHOST_XrGraphicsContextBindFn bind_fn,
+                                           GHOST_XrGraphicsContextUnbindFn unbind_fn) = 0;
+  virtual void setDrawViewFunc(GHOST_XrDrawViewFn draw_view_fn) = 0;
+};
+
+#endif  // __GHOST_IXRCONTEXT_H__
diff --git a/intern/ghost/GHOST_Types.h b/intern/ghost/GHOST_Types.h
index 3971914033d..20d0f7d1e7d 100644
--- a/intern/ghost/GHOST_Types.h
+++ b/intern/ghost/GHOST_Types.h
@@ -41,6 +41,22 @@
     } * name
 #endif
 
+/**
+ * Creates a "handle" for a C++ GHOST object.
+ * A handle is just an opaque pointer to an empty struct.
+ * In the API the pointer is cast to the actual C++ class.
+ * The 'name' argument to the macro is the name of the handle to create.
+ */
+
+GHOST_DECLARE_HANDLE(GHOST_SystemHandle);
+GHOST_DECLARE_HANDLE(GHOST_TimerTaskHandle);
+GHOST_DECLARE_HANDLE(GHOST_WindowHandle);
+GHOST_DECLARE_HANDLE(GHOST_EventHandle);
+GHOST_DECLARE_HANDLE(GHOST_RectangleHandle);
+GHOST_DECLARE_HANDLE(GHOST_EventConsumerHandle);
+GHOST_DECLARE_HANDLE(GHOST_ContextHandle);
+GHOST_DECLARE_HANDLE(GHOST_XrContextHandle);
+
 typedef char GHOST_TInt8;
 typedef unsigned char GHOST_TUns8;
 typedef short GHOST_TInt16;
@@ -565,4 +581,81 @@ struct GHOST_TimerTaskHandle__;
 typedef void (*GHOST_TimerProcPtr)(struct GHOST_TimerTaskHandle__ *task, GHOST_TUns64 time);
 #endif
 
+#ifdef WITH_OPENXR
+
+/**
+ * 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
+ * draw call will happen through another lib (say DirectX).
+ *
+ * This enum defines the possible graphics bindings to attempt to enable.
+ */
+typedef enum {
+  GHOST_kXrGraphicsUnknown = 0,
+  GHOST_kXrGraphicsOpenGL,
+#  ifdef WIN32
+  GHOST_kXrGraphicsD3D11,
+#  endif
+  /* For later */
+  //  GHOST_kXrGraphicsVulkan,
+} GHOST_TXrGraphicsBinding;
+/* 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;
+
+typedef struct {
+  float position[3];
+  /* Blender convention (w, x, y, z) */
+  float orientation_quat[4];
+} GHOST_XrPose;
+
+enum {
+  GHOST_kXrContextDebug = (1 << 0),
+  GHOST_kXrContextDebugTime = (1 << 1),
+};
+
+typedef struct {
+  const GHOST_XrGraphicsBindingCandidates gpu_binding_candidates;
+  unsigned int gpu_binding_candidates_count;
+
+  unsigned int context_flag;
+} GHOST_XrContextCreateInfo;
+
+typedef struct {
+  GHOST_XrPose base_pose;
+} GHOST_XrSessionBeginInfo;
+
+typedef struct {
+  int ofsx, ofsy;
+  int width, height;
+
+  GHOST_XrPose pose;
+
+  struct {
+    float angle_left, angle_right;
+    float angle_up, angle_down;
+  } fov;
+
+  /** Set if the buffer should be submitted with a srgb transfer applied. */
+  char expects_srgb_buffer;
+} GHOST_XrDrawViewInfo;
+
+typedef struct {
+  const char *user_message;
+
+  /** File path and line number the error was found at. */
+  const char *source_location;
+
+  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__
diff --git a/intern/ghost/intern/GHOST_C-api.cpp b/intern/ghost/intern/GHOST_C-api.cpp
index 360724aba33..6b47d57d4ff 100644
--- a/intern/ghost/intern/GHOST_C-api.cpp
+++ b/intern/ghost/intern/GHOST_C-api.cpp
@@ -30,7 +30,11 @@
 #include "GHOST_ISystem.h"
 #include "GHOST_IEvent.h"
 #include "GHOST_IEventConsumer.h"
+#ifdef WITH_OPENXR
+#  include "GHOST_IXrContext.h"
+#endif
 #include "intern/GHOST_CallbackEventConsumer.h"
+#include "intern/GHOST_XrException.h"
 
 GHOST_SystemHandle GHOST_CreateSystem(void)
 {
@@ -907,3 +911,63 @@ void GHOST_EndIME(GHOST_WindowHandle windowhandle)
 }
 
 #endif /* WITH_INPUT_IME */
+
+#ifdef WITH_OPENXR
+
+#  define GHOST_XR_CAPI_CALL(call, ctx) \
+    try { \
+      call; \
+    } \
+    catch (GHOST_XrException & e) { \
+      (ctx)->dispatchErrorMessage(&e); \
+    }
+
+#  define GHOST_XR_CAPI_CALL_RET(call, ctx) \
+    try { \
+      return call; \
+    } \
+    catch (GHOST_XrException & e) { \
+      (ctx)->dispatchErrorMessage(&e); \
+    }
+
+void GHOST_XrSessionStart(GHOST_XrContextHandle xr_contexthandle,
+                          const GHOST_XrSessionBeginInfo *begin_info)
+{
+  GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
+  GHOST_XR_CAPI_CALL(xr_context->startSession(begin_info), xr_context);
+}
+
+void GHOST_XrSessionEnd(GHOST_XrContextHandle xr_contexthandle)
+{
+  GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
+  GHOST_XR_CAPI_CALL(xr_context->endSession(), xr_context);
+}
+
+int GHOST_XrSessionIsRunning(const GHOST_XrContextHandle xr_contexthandle)
+{
+  const GHOST_IXrContext *xr_context = (const GHOST_IXrContext *)xr_contexthandle;
+  GHOST_XR_CAPI_CALL_RET(xr_context->isSessionRunning(), xr_context);
+  return 0; /* Only reached if exception is thrown. */
+}
+
+void GHOST_XrSessionDrawViews(GHOST_XrContextHandle xr_contexthandle, void *draw_customdata)
+{
+  GHOST_IXrContext *xr_context = (GHOST_IXrConte

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list