Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/intern
diff options
context:
space:
mode:
authorJulian Eisel <julian@blender.org>2020-03-17 22:10:57 +0300
committerJulian Eisel <julian@blender.org>2020-03-17 23:39:59 +0300
commit406bfd43040a5526702b51f88f1491cb61aecedb (patch)
tree980c77919f9930d51c5f4f411d2e658f66a263e5 /intern
parentc9a8de1d704b807460a7a6838db28f7ae2472200 (diff)
Ghost: Ghost-XR API to abstract away and access OpenXR functionality
Extends Ghost to include an abstraction for OpenXR, which I refer to as Ghost-XR. Such an API is the base for the following commit, which introduces VR support to Blender. Main features: * Simple and high-level interface for Blender specific code to call. * Extensible for muliple graphics backends, currently OpenGL and a DirectX compatibility layer are supported. * Carefully designed error handling strategy allowing Blender to handle errors gracefully and with useful error messages. * OpenXR extension and API-layer management. * OpenXR session management. * Basic OpenXR event management. * Debug utilities for Ghost-XR and OpenXR For more information on this API, check https://wiki.blender.org/wiki/Source/Interface/XR. Reviewed by: Brecht Van Lommel Differential Revision: https://developer.blender.org/D6188
Diffstat (limited to 'intern')
-rw-r--r--intern/ghost/CMakeLists.txt44
-rw-r--r--intern/ghost/GHOST_C-api.h100
-rw-r--r--intern/ghost/GHOST_IXrContext.h42
-rw-r--r--intern/ghost/GHOST_Types.h90
-rw-r--r--intern/ghost/intern/GHOST_C-api.cpp64
-rw-r--r--intern/ghost/intern/GHOST_ContextD3D.h3
-rw-r--r--intern/ghost/intern/GHOST_ContextGLX.cpp1
-rw-r--r--intern/ghost/intern/GHOST_ContextGLX.h3
-rw-r--r--intern/ghost/intern/GHOST_ContextWGL.h3
-rw-r--r--intern/ghost/intern/GHOST_IXrGraphicsBinding.h71
-rw-r--r--intern/ghost/intern/GHOST_Xr.cpp59
-rw-r--r--intern/ghost/intern/GHOST_XrContext.cpp550
-rw-r--r--intern/ghost/intern/GHOST_XrContext.h127
-rw-r--r--intern/ghost/intern/GHOST_XrEvent.cpp64
-rw-r--r--intern/ghost/intern/GHOST_XrException.h45
-rw-r--r--intern/ghost/intern/GHOST_XrGraphicsBinding.cpp316
-rw-r--r--intern/ghost/intern/GHOST_XrSession.cpp487
-rw-r--r--intern/ghost/intern/GHOST_XrSession.h83
-rw-r--r--intern/ghost/intern/GHOST_XrSwapchain.cpp131
-rw-r--r--intern/ghost/intern/GHOST_XrSwapchain.h45
-rw-r--r--intern/ghost/intern/GHOST_Xr_intern.h50
-rw-r--r--intern/ghost/intern/GHOST_Xr_openxr_includes.h52
22 files changed, 2415 insertions, 15 deletions
diff --git a/intern/ghost/CMakeLists.txt b/intern/ghost/CMakeLists.txt
index c4f1efe1580..07d98475c00 100644
--- a/intern/ghost/CMakeLists.txt
+++ b/intern/ghost/CMakeLists.txt
@@ -357,6 +357,50 @@ elseif(WIN32)
endif()
+if(WITH_XR_OPENXR)
+ list(APPEND SRC
+ intern/GHOST_Xr.cpp
+ intern/GHOST_XrContext.cpp
+ intern/GHOST_XrEvent.cpp
+ intern/GHOST_XrGraphicsBinding.cpp
+ intern/GHOST_XrSession.cpp
+ intern/GHOST_XrSwapchain.cpp
+
+ GHOST_IXrContext.h
+ intern/GHOST_IXrGraphicsBinding.h
+ intern/GHOST_Xr_intern.h
+ intern/GHOST_Xr_openxr_includes.h
+ intern/GHOST_XrContext.h
+ intern/GHOST_XrSession.h
+ intern/GHOST_XrSwapchain.h
+ )
+ list(APPEND INC_SYS
+ ${XR_OPENXR_SDK_INCLUDE_DIR}
+ )
+
+ set(XR_PLATFORM_DEFINES -DXR_USE_GRAPHICS_API_OPENGL)
+
+ # Add compiler defines as required by the OpenXR specification.
+ if(WIN32)
+ list(APPEND XR_PLATFORM_DEFINES
+ -DXR_USE_PLATFORM_WIN32
+ -DXR_USE_GRAPHICS_API_D3D11
+ )
+ list(APPEND LIB
+ shlwapi
+ )
+ elseif(UNIX AND NOT APPLE)
+ list(APPEND XR_PLATFORM_DEFINES
+ -DXR_OS_LINUX
+ -DXR_USE_PLATFORM_XLIB
+ )
+ endif()
+
+ add_definitions(-DWITH_XR_OPENXR ${XR_PLATFORM_DEFINES})
+
+ unset(XR_PLATFORM_DEFINES)
+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 a3cc9aa1df5..aafe374a93b 100644
--- a/intern/ghost/GHOST_C-api.h
+++ b/intern/ghost/GHOST_C-api.h
@@ -31,21 +31,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.
* \param userdata The callback's user data, supplied to GHOST_CreateSystem.
@@ -1006,6 +991,91 @@ extern void GHOST_BeginIME(GHOST_WindowHandle windowhandle,
*/
extern void GHOST_EndIME(GHOST_WindowHandle windowhandle);
+#ifdef WITH_XR_OPENXR
+
+/* XR-context */
+
+/**
+ * Set a custom callback to be executed whenever an error occurs. Should be set before calling
+ * #GHOST_XrContextCreate() to get error handling during context creation too.
+ *
+ * \param customdata: Handle to some data that will get passed to \a handler_fn should an error be
+ * thrown.
+ */
+void GHOST_XrErrorHandler(GHOST_XrErrorHandlerFn handler_fn, void *customdata);
+
+/**
+ * \brief Initialize the Ghost XR-context.
+ *
+ * Includes setting up the OpenXR runtime link, querying available extensions and API layers,
+ * enabling extensions and API layers.
+ *
+ * \param create_info: Options for creating the XR-context, e.g. debug-flags and ordered array of
+ * graphics bindings to try enabling.
+ */
+GHOST_XrContextHandle GHOST_XrContextCreate(const GHOST_XrContextCreateInfo *create_info);
+/**
+ * Free a XR-context involving OpenXR runtime link destruction and freeing of all internal data.
+ */
+void GHOST_XrContextDestroy(GHOST_XrContextHandle xr_context);
+
+/**
+ * Set callbacks for binding and unbinding a graphics context for a session. The binding callback
+ * may create a new graphics context thereby. In fact that's the sole reason for this callback
+ * approach to binding. Just make sure to have an unbind function set that properly destructs.
+ *
+ * \param bind_fn: Function to retrieve (possibly create) a graphics context.
+ * \param unbind_fn: Function to release (possibly free) a graphics context.
+ */
+void GHOST_XrGraphicsContextBindFuncs(GHOST_XrContextHandle xr_context,
+ GHOST_XrGraphicsContextBindFn bind_fn,
+ GHOST_XrGraphicsContextUnbindFn unbind_fn);
+
+/**
+ * Set the drawing callback for views. A view would typically be either the left or the right eye,
+ * although other configurations are possible. When #GHOST_XrSessionDrawViews() is called to draw
+ * an XR frame, \a draw_view_fn is executed for each view.
+ *
+ * \param draw_view_fn: The callback to draw a single view for an XR frame.
+ */
+void GHOST_XrDrawViewFunc(GHOST_XrContextHandle xr_context, GHOST_XrDrawViewFn draw_view_fn);
+
+/* sessions */
+/**
+ * Create internal session data for \a xr_context and ask the OpenXR runtime to invoke a session.
+ *
+ * \param begin_info: Options for the session creation.
+ */
+void GHOST_XrSessionStart(GHOST_XrContextHandle xr_context,
+ const GHOST_XrSessionBeginInfo *begin_info);
+/**
+ * Destruct internal session data for \a xr_context and ask the OpenXR runtime to stop a session.
+ */
+void GHOST_XrSessionEnd(GHOST_XrContextHandle xr_context);
+/**
+ * Draw a single frame by calling the view drawing callback defined by #GHOST_XrDrawViewFunc() for
+ * each view and submit it to the OpenXR runtime.
+ *
+ * \param customdata: Handle to some data that will get passed to the view drawing callback.
+ */
+void GHOST_XrSessionDrawViews(GHOST_XrContextHandle xr_context, void *customdata);
+/**
+ * Check if a \a xr_context has a session that, according to the OpenXR definition would be
+ * considered to be 'running'
+ * (https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#session_running).
+ */
+int GHOST_XrSessionIsRunning(const GHOST_XrContextHandle xr_context);
+
+/* events */
+/**
+ * Invoke handling of all OpenXR events for \a xr_context. Should be called on every main-loop
+ * iteration and will early-exit if \a xr_context is NULL (so caller doesn't have to check).
+ *
+ * \returns GHOST_kSuccess if any event was handled, otherwise GHOST_kFailure.
+ */
+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 8bc75d01b96..adda782b96d 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;
@@ -580,4 +596,78 @@ struct GHOST_TimerTaskHandle__;
typedef void (*GHOST_TimerProcPtr)(struct GHOST_TimerTaskHandle__ *task, GHOST_TUns64 time);
#endif
+#ifdef WITH_XR_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;
+
+ 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 9a7e3da828b..60d20474a5a 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_XR_OPENXR
+# include "GHOST_IXrContext.h"
+#endif
#include "intern/GHOST_CallbackEventConsumer.h"
+#include "intern/GHOST_XrException.h"
GHOST_SystemHandle GHOST_CreateSystem(void)
{
@@ -914,3 +918,63 @@ void GHOST_EndIME(GHOST_WindowHandle windowhandle)
}
#endif /* WITH_INPUT_IME */
+
+#ifdef WITH_XR_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);
+}
+
+void GHOST_XrSessionDrawViews(GHOST_XrContextHandle xr_contexthandle, void *draw_customdata)
+{
+ GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
+ GHOST_XR_CAPI_CALL(xr_context->drawSessionViews(draw_customdata), 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_XrGraphicsContextBindFuncs(GHOST_XrContextHandle xr_contexthandle,
+ GHOST_XrGraphicsContextBindFn bind_fn,
+ GHOST_XrGraphicsContextUnbindFn unbind_fn)
+{
+ GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
+ GHOST_XR_CAPI_CALL(xr_context->setGraphicsContextBindFuncs(bind_fn, unbind_fn), xr_context);
+}
+
+void GHOST_XrDrawViewFunc(GHOST_XrContextHandle xr_contexthandle, GHOST_XrDrawViewFn draw_view_fn)
+{
+ GHOST_IXrContext *xr_context = (GHOST_IXrContext *)xr_contexthandle;
+ GHOST_XR_CAPI_CALL(xr_context->setDrawViewFunc(draw_view_fn), xr_context);
+}
+
+#endif
diff --git a/intern/ghost/intern/GHOST_ContextD3D.h b/intern/ghost/intern/GHOST_ContextD3D.h
index 601282fc80f..c482992a6e2 100644
--- a/intern/ghost/intern/GHOST_ContextD3D.h
+++ b/intern/ghost/intern/GHOST_ContextD3D.h
@@ -30,6 +30,9 @@
#include "GHOST_Context.h"
class GHOST_ContextD3D : public GHOST_Context {
+ /* XR code needs low level graphics data to send to OpenXR. */
+ friend class GHOST_XrGraphicsBindingD3D;
+
public:
GHOST_ContextD3D(bool stereoVisual, HWND hWnd);
~GHOST_ContextD3D();
diff --git a/intern/ghost/intern/GHOST_ContextGLX.cpp b/intern/ghost/intern/GHOST_ContextGLX.cpp
index fac75f299fc..ecf824aafb7 100644
--- a/intern/ghost/intern/GHOST_ContextGLX.cpp
+++ b/intern/ghost/intern/GHOST_ContextGLX.cpp
@@ -273,6 +273,7 @@ GHOST_TSuccess GHOST_ContextGLX::initializeDrawingContext()
m_window = (Window)glXCreatePbuffer(m_display, framebuffer_config[0], pbuffer_attribs);
}
+ m_fbconfig = framebuffer_config[0];
XFree(framebuffer_config);
}
}
diff --git a/intern/ghost/intern/GHOST_ContextGLX.h b/intern/ghost/intern/GHOST_ContextGLX.h
index ba8df7dac1b..07d2601cd17 100644
--- a/intern/ghost/intern/GHOST_ContextGLX.h
+++ b/intern/ghost/intern/GHOST_ContextGLX.h
@@ -38,6 +38,9 @@
#endif
class GHOST_ContextGLX : public GHOST_Context {
+ /* XR code needs low level graphics data to send to OpenXR. */
+ friend class GHOST_XrGraphicsBindingOpenGL;
+
public:
/**
* Constructor.
diff --git a/intern/ghost/intern/GHOST_ContextWGL.h b/intern/ghost/intern/GHOST_ContextWGL.h
index a990e42c4ff..a8d2c18b463 100644
--- a/intern/ghost/intern/GHOST_ContextWGL.h
+++ b/intern/ghost/intern/GHOST_ContextWGL.h
@@ -35,6 +35,9 @@
#endif
class GHOST_ContextWGL : public GHOST_Context {
+ /* XR code needs low level graphics data to send to OpenXR. */
+ friend class GHOST_XrGraphicsBindingOpenGL;
+
public:
/**
* Constructor.
diff --git a/intern/ghost/intern/GHOST_IXrGraphicsBinding.h b/intern/ghost/intern/GHOST_IXrGraphicsBinding.h
new file mode 100644
index 00000000000..19fe00cdad5
--- /dev/null
+++ b/intern/ghost/intern/GHOST_IXrGraphicsBinding.h
@@ -0,0 +1,71 @@
+/*
+ * 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_IXRGRAPHICSBINDING_H__
+#define __GHOST_IXRGRAPHICSBINDING_H__
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "GHOST_Xr_openxr_includes.h"
+
+class GHOST_IXrGraphicsBinding {
+ friend std::unique_ptr<GHOST_IXrGraphicsBinding> GHOST_XrGraphicsBindingCreateFromType(
+ GHOST_TXrGraphicsBinding type);
+
+ public:
+ union {
+#if defined(WITH_X11)
+ XrGraphicsBindingOpenGLXlibKHR glx;
+#elif defined(WIN32)
+ XrGraphicsBindingOpenGLWin32KHR wgl;
+ XrGraphicsBindingD3D11KHR d3d11;
+#endif
+ } oxr_binding;
+
+ /**
+ * Does __not__ require this object is initialized (can be called prior to
+ * #initFromGhostContext). It's actually meant to be called first.
+ *
+ * \param r_requirement_info Return argument to retrieve an informal string on the requirements
+ * to be met. Useful for error/debug messages.
+ */
+ virtual bool checkVersionRequirements(class GHOST_Context *ghost_ctx,
+ XrInstance instance,
+ XrSystemId system_id,
+ std::string *r_requirement_info) const = 0;
+ virtual void initFromGhostContext(class GHOST_Context *ghost_ctx) = 0;
+ virtual bool chooseSwapchainFormat(const std::vector<int64_t> &runtime_formats,
+ int64_t *r_result) const = 0;
+ virtual std::vector<XrSwapchainImageBaseHeader *> createSwapchainImages(
+ uint32_t image_count) = 0;
+ virtual void submitToSwapchainImage(XrSwapchainImageBaseHeader *swapchain_image,
+ const GHOST_XrDrawViewInfo *draw_info) = 0;
+
+ protected:
+ /* Use GHOST_XrGraphicsBindingCreateFromType! */
+ GHOST_IXrGraphicsBinding() = default;
+};
+
+std::unique_ptr<GHOST_IXrGraphicsBinding> GHOST_XrGraphicsBindingCreateFromType(
+ GHOST_TXrGraphicsBinding type);
+
+#endif /* __GHOST_IXRGRAPHICSBINDING_H__ */
diff --git a/intern/ghost/intern/GHOST_Xr.cpp b/intern/ghost/intern/GHOST_Xr.cpp
new file mode 100644
index 00000000000..2f122ca8e13
--- /dev/null
+++ b/intern/ghost/intern/GHOST_Xr.cpp
@@ -0,0 +1,59 @@
+/*
+ * 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
+ *
+ * Abstraction for XR (VR, AR, MR, ..) access via OpenXR.
+ */
+
+#include <cassert>
+#include <string>
+
+#include "GHOST_C-api.h"
+
+#include "GHOST_Xr_intern.h"
+#include "GHOST_XrContext.h"
+#include "GHOST_XrException.h"
+
+GHOST_XrContextHandle GHOST_XrContextCreate(const GHOST_XrContextCreateInfo *create_info)
+{
+ GHOST_XrContext *xr_context = new GHOST_XrContext(create_info);
+
+ /* TODO GHOST_XrContext's should probably be owned by the GHOST_System, which will handle context
+ * creation and destruction. Try-catch logic can be moved to C-API then. */
+ try {
+ xr_context->initialize(create_info);
+ }
+ catch (GHOST_XrException &e) {
+ xr_context->dispatchErrorMessage(&e);
+ delete xr_context;
+
+ return nullptr;
+ }
+
+ return (GHOST_XrContextHandle)xr_context;
+}
+
+void GHOST_XrContextDestroy(GHOST_XrContextHandle xr_contexthandle)
+{
+ delete (GHOST_XrContext *)xr_contexthandle;
+}
+
+void GHOST_XrErrorHandler(GHOST_XrErrorHandlerFn handler_fn, void *customdata)
+{
+ GHOST_XrContext::setErrorHandler(handler_fn, customdata);
+}
diff --git a/intern/ghost/intern/GHOST_XrContext.cpp b/intern/ghost/intern/GHOST_XrContext.cpp
new file mode 100644
index 00000000000..410837e9805
--- /dev/null
+++ b/intern/ghost/intern/GHOST_XrContext.cpp
@@ -0,0 +1,550 @@
+/*
+ * 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
+ *
+ * Abstraction for XR (VR, AR, MR, ..) access via OpenXR.
+ */
+
+#include <cassert>
+#include <sstream>
+#include <string>
+
+#include "GHOST_Types.h"
+#include "GHOST_Xr_intern.h"
+#include "GHOST_XrException.h"
+#include "GHOST_XrSession.h"
+
+#include "GHOST_XrContext.h"
+
+struct OpenXRInstanceData {
+ XrInstance instance = XR_NULL_HANDLE;
+ XrInstanceProperties instance_properties = {};
+
+ std::vector<XrExtensionProperties> extensions;
+ std::vector<XrApiLayerProperties> layers;
+
+ static PFN_xrCreateDebugUtilsMessengerEXT s_xrCreateDebugUtilsMessengerEXT_fn;
+ static PFN_xrDestroyDebugUtilsMessengerEXT s_xrDestroyDebugUtilsMessengerEXT_fn;
+
+ XrDebugUtilsMessengerEXT debug_messenger = XR_NULL_HANDLE;
+};
+
+PFN_xrCreateDebugUtilsMessengerEXT OpenXRInstanceData::s_xrCreateDebugUtilsMessengerEXT_fn =
+ nullptr;
+PFN_xrDestroyDebugUtilsMessengerEXT OpenXRInstanceData::s_xrDestroyDebugUtilsMessengerEXT_fn =
+ nullptr;
+
+GHOST_XrErrorHandlerFn GHOST_XrContext::s_error_handler = nullptr;
+void *GHOST_XrContext::s_error_handler_customdata = nullptr;
+
+/* -------------------------------------------------------------------- */
+/** \name Create, Initialize and Destruct
+ *
+ * \{ */
+
+GHOST_XrContext::GHOST_XrContext(const GHOST_XrContextCreateInfo *create_info)
+ : m_oxr(new OpenXRInstanceData()),
+ m_debug(create_info->context_flag & GHOST_kXrContextDebug),
+ m_debug_time(create_info->context_flag & GHOST_kXrContextDebugTime)
+{
+}
+
+GHOST_XrContext::~GHOST_XrContext()
+{
+ /* Destroy session data first. Otherwise xrDestroyInstance will implicitly do it, before the
+ * session had a chance to do so explicitly. */
+ m_session = nullptr;
+
+ if (m_oxr->debug_messenger != XR_NULL_HANDLE) {
+ assert(m_oxr->s_xrDestroyDebugUtilsMessengerEXT_fn != nullptr);
+ m_oxr->s_xrDestroyDebugUtilsMessengerEXT_fn(m_oxr->debug_messenger);
+ }
+ if (m_oxr->instance != XR_NULL_HANDLE) {
+ CHECK_XR_ASSERT(xrDestroyInstance(m_oxr->instance));
+ m_oxr->instance = XR_NULL_HANDLE;
+ }
+}
+
+void GHOST_XrContext::initialize(const GHOST_XrContextCreateInfo *create_info)
+{
+ initApiLayers();
+ initExtensions();
+ if (isDebugMode()) {
+ printAvailableAPILayersAndExtensionsInfo();
+ }
+
+ m_gpu_binding_type = determineGraphicsBindingTypeToEnable(create_info);
+
+ assert(m_oxr->instance == XR_NULL_HANDLE);
+ createOpenXRInstance();
+ storeInstanceProperties();
+ printInstanceInfo();
+ if (isDebugMode()) {
+ initDebugMessenger();
+ }
+}
+
+void GHOST_XrContext::createOpenXRInstance()
+{
+ XrInstanceCreateInfo create_info = {XR_TYPE_INSTANCE_CREATE_INFO};
+
+ std::string("Blender").copy(create_info.applicationInfo.applicationName,
+ XR_MAX_APPLICATION_NAME_SIZE);
+ create_info.applicationInfo.apiVersion = XR_CURRENT_API_VERSION;
+
+ getAPILayersToEnable(m_enabled_layers);
+ getExtensionsToEnable(m_enabled_extensions);
+ create_info.enabledApiLayerCount = m_enabled_layers.size();
+ create_info.enabledApiLayerNames = m_enabled_layers.data();
+ create_info.enabledExtensionCount = m_enabled_extensions.size();
+ create_info.enabledExtensionNames = m_enabled_extensions.data();
+ if (isDebugMode()) {
+ printExtensionsAndAPILayersToEnable();
+ }
+
+ CHECK_XR(xrCreateInstance(&create_info, &m_oxr->instance),
+ "Failed to connect to an OpenXR runtime.");
+}
+
+void GHOST_XrContext::storeInstanceProperties()
+{
+ const std::map<std::string, GHOST_TXrOpenXRRuntimeID> runtime_map = {
+ {"Monado(XRT) by Collabora et al", OPENXR_RUNTIME_MONADO},
+ {"Oculus", OPENXR_RUNTIME_OCULUS},
+ {"Windows Mixed Reality Runtime", OPENXR_RUNTIME_WMR}};
+ decltype(runtime_map)::const_iterator runtime_map_iter;
+
+ m_oxr->instance_properties.type = XR_TYPE_INSTANCE_PROPERTIES;
+ CHECK_XR(xrGetInstanceProperties(m_oxr->instance, &m_oxr->instance_properties),
+ "Failed to get OpenXR runtime information. Do you have an active runtime set up?");
+
+ runtime_map_iter = runtime_map.find(m_oxr->instance_properties.runtimeName);
+ if (runtime_map_iter != runtime_map.end()) {
+ m_runtime_id = runtime_map_iter->second;
+ }
+}
+
+/** \} */ /* Create, Initialize and Destruct */
+
+/* -------------------------------------------------------------------- */
+/** \name Debug Printing
+ *
+ * \{ */
+
+void GHOST_XrContext::printInstanceInfo()
+{
+ assert(m_oxr->instance != XR_NULL_HANDLE);
+
+ printf("Connected to OpenXR runtime: %s (Version %u.%u.%u)\n",
+ m_oxr->instance_properties.runtimeName,
+ XR_VERSION_MAJOR(m_oxr->instance_properties.runtimeVersion),
+ XR_VERSION_MINOR(m_oxr->instance_properties.runtimeVersion),
+ XR_VERSION_PATCH(m_oxr->instance_properties.runtimeVersion));
+}
+
+void GHOST_XrContext::printAvailableAPILayersAndExtensionsInfo()
+{
+ puts("Available OpenXR API-layers/extensions:");
+ for (XrApiLayerProperties &layer_info : m_oxr->layers) {
+ printf("Layer: %s\n", layer_info.layerName);
+ }
+ for (XrExtensionProperties &ext_info : m_oxr->extensions) {
+ printf("Extension: %s\n", ext_info.extensionName);
+ }
+}
+
+void GHOST_XrContext::printExtensionsAndAPILayersToEnable()
+{
+ for (const char *layer_name : m_enabled_layers) {
+ printf("Enabling OpenXR API-Layer: %s\n", layer_name);
+ }
+ for (const char *ext_name : m_enabled_extensions) {
+ printf("Enabling OpenXR Extension: %s\n", ext_name);
+ }
+}
+
+static XrBool32 debug_messenger_func(XrDebugUtilsMessageSeverityFlagsEXT /*messageSeverity*/,
+ XrDebugUtilsMessageTypeFlagsEXT /*messageTypes*/,
+ const XrDebugUtilsMessengerCallbackDataEXT *callbackData,
+ void * /*userData*/)
+{
+ puts("OpenXR Debug Message:");
+ puts(callbackData->message);
+ return XR_FALSE; /* OpenXR spec suggests always returning false. */
+}
+
+void GHOST_XrContext::initDebugMessenger()
+{
+ XrDebugUtilsMessengerCreateInfoEXT create_info = {XR_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT};
+
+ /* Extension functions need to be obtained through xrGetInstanceProcAddr(). */
+ if (XR_FAILED(xrGetInstanceProcAddr(
+ m_oxr->instance,
+ "xrCreateDebugUtilsMessengerEXT",
+ (PFN_xrVoidFunction *)&m_oxr->s_xrCreateDebugUtilsMessengerEXT_fn)) ||
+ XR_FAILED(xrGetInstanceProcAddr(
+ m_oxr->instance,
+ "xrDestroyDebugUtilsMessengerEXT",
+ (PFN_xrVoidFunction *)&m_oxr->s_xrDestroyDebugUtilsMessengerEXT_fn))) {
+ m_oxr->s_xrCreateDebugUtilsMessengerEXT_fn = nullptr;
+ m_oxr->s_xrDestroyDebugUtilsMessengerEXT_fn = nullptr;
+
+ fprintf(stderr,
+ "Could not use XR_EXT_debug_utils to enable debug prints. Not a fatal error, "
+ "continuing without the messenger.\n");
+ return;
+ }
+
+ create_info.messageSeverities = XR_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
+ XR_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT |
+ XR_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
+ XR_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
+ create_info.messageTypes = XR_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
+ XR_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
+ XR_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
+ create_info.userCallback = debug_messenger_func;
+
+ if (XR_FAILED(m_oxr->s_xrCreateDebugUtilsMessengerEXT_fn(
+ m_oxr->instance, &create_info, &m_oxr->debug_messenger))) {
+ fprintf(stderr,
+ "Failed to create OpenXR debug messenger. Not a fatal error, continuing without the "
+ "messenger.\n");
+ return;
+ }
+}
+
+/** \} */ /* Debug Printing */
+
+/* -------------------------------------------------------------------- */
+/** \name Error handling
+ *
+ * \{ */
+
+void GHOST_XrContext::dispatchErrorMessage(const GHOST_XrException *exception) const
+{
+ GHOST_XrError error;
+
+ error.user_message = exception->m_msg;
+ error.customdata = s_error_handler_customdata;
+
+ if (isDebugMode()) {
+ fprintf(stderr,
+ "Error: \t%s\n\tOpenXR error value: %i\n",
+ error.user_message,
+ exception->m_result);
+ }
+
+ /* Potentially destroys GHOST_XrContext */
+ s_error_handler(&error);
+}
+
+void GHOST_XrContext::setErrorHandler(GHOST_XrErrorHandlerFn handler_fn, void *customdata)
+{
+ s_error_handler = handler_fn;
+ s_error_handler_customdata = customdata;
+}
+
+/** \} */ /* Error handling */
+
+/* -------------------------------------------------------------------- */
+/** \name OpenXR API-Layers and Extensions
+ *
+ * \{ */
+
+/**
+ * \param layer_name May be NULL for extensions not belonging to a specific layer.
+ */
+void GHOST_XrContext::initExtensionsEx(std::vector<XrExtensionProperties> &extensions,
+ const char *layer_name)
+{
+ uint32_t extension_count = 0;
+
+ /* Get count for array creation/init first. */
+ CHECK_XR(xrEnumerateInstanceExtensionProperties(layer_name, 0, &extension_count, nullptr),
+ "Failed to query OpenXR runtime information. Do you have an active runtime set up?");
+
+ if (extension_count == 0) {
+ /* Extensions are optional, can successfully exit. */
+ return;
+ }
+
+ for (uint32_t i = 0; i < extension_count; i++) {
+ XrExtensionProperties ext = {XR_TYPE_EXTENSION_PROPERTIES};
+ extensions.push_back(ext);
+ }
+
+ /* Actually get the extensions. */
+ CHECK_XR(xrEnumerateInstanceExtensionProperties(
+ layer_name, extension_count, &extension_count, extensions.data()),
+ "Failed to query OpenXR runtime information. Do you have an active runtime set up?");
+}
+
+void GHOST_XrContext::initExtensions()
+{
+ initExtensionsEx(m_oxr->extensions, nullptr);
+}
+
+void GHOST_XrContext::initApiLayers()
+{
+ uint32_t layer_count = 0;
+
+ /* Get count for array creation/init first. */
+ CHECK_XR(xrEnumerateApiLayerProperties(0, &layer_count, nullptr),
+ "Failed to query OpenXR runtime information. Do you have an active runtime set up?");
+
+ if (layer_count == 0) {
+ /* Layers are optional, can safely exit. */
+ return;
+ }
+
+ m_oxr->layers = std::vector<XrApiLayerProperties>(layer_count);
+ for (XrApiLayerProperties &layer : m_oxr->layers) {
+ layer.type = XR_TYPE_API_LAYER_PROPERTIES;
+ }
+
+ /* Actually get the layers. */
+ CHECK_XR(xrEnumerateApiLayerProperties(layer_count, &layer_count, m_oxr->layers.data()),
+ "Failed to query OpenXR runtime information. Do you have an active runtime set up?");
+ for (XrApiLayerProperties &layer : m_oxr->layers) {
+ /* Each layer may have own extensions. */
+ initExtensionsEx(m_oxr->extensions, layer.layerName);
+ }
+}
+
+static bool openxr_layer_is_available(const std::vector<XrApiLayerProperties> layers_info,
+ const std::string &layer_name)
+{
+ for (const XrApiLayerProperties &layer_info : layers_info) {
+ if (layer_info.layerName == layer_name) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool openxr_extension_is_available(const std::vector<XrExtensionProperties> extensions_info,
+ const std::string &extension_name)
+{
+ for (const XrExtensionProperties &ext_info : extensions_info) {
+ if (ext_info.extensionName == extension_name) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Gather an array of names for the API-layers to enable.
+ */
+void GHOST_XrContext::getAPILayersToEnable(std::vector<const char *> &r_ext_names)
+{
+ static std::vector<std::string> try_layers;
+
+ try_layers.clear();
+
+ if (isDebugMode()) {
+ try_layers.push_back("XR_APILAYER_LUNARG_core_validation");
+ }
+
+ r_ext_names.reserve(try_layers.size());
+
+ for (const std::string &layer : try_layers) {
+ if (openxr_layer_is_available(m_oxr->layers, layer)) {
+ r_ext_names.push_back(layer.c_str());
+ }
+ }
+}
+
+static const char *openxr_ext_name_from_wm_gpu_binding(GHOST_TXrGraphicsBinding binding)
+{
+ switch (binding) {
+ case GHOST_kXrGraphicsOpenGL:
+ return XR_KHR_OPENGL_ENABLE_EXTENSION_NAME;
+#ifdef WIN32
+ case GHOST_kXrGraphicsD3D11:
+ return XR_KHR_D3D11_ENABLE_EXTENSION_NAME;
+#endif
+ case GHOST_kXrGraphicsUnknown:
+ assert(!"Could not identify graphics binding to choose.");
+ return nullptr;
+ }
+
+ return nullptr;
+}
+
+/**
+ * Gather an array of names for the extensions to enable.
+ */
+void GHOST_XrContext::getExtensionsToEnable(std::vector<const char *> &r_ext_names)
+{
+ assert(m_gpu_binding_type != GHOST_kXrGraphicsUnknown);
+
+ const char *gpu_binding = openxr_ext_name_from_wm_gpu_binding(m_gpu_binding_type);
+ static std::vector<std::string> try_ext;
+
+ try_ext.clear();
+
+ /* Try enabling debug extension. */
+#ifndef WIN32
+ if (isDebugMode()) {
+ try_ext.push_back(XR_EXT_DEBUG_UTILS_EXTENSION_NAME);
+ }
+#endif
+
+ r_ext_names.reserve(try_ext.size() + 1); /* + 1 for graphics binding extension. */
+
+ /* Add graphics binding extension. */
+ assert(gpu_binding);
+ assert(openxr_extension_is_available(m_oxr->extensions, gpu_binding));
+ r_ext_names.push_back(gpu_binding);
+
+ for (const std::string &ext : try_ext) {
+ if (openxr_extension_is_available(m_oxr->extensions, ext)) {
+ r_ext_names.push_back(ext.c_str());
+ }
+ }
+}
+
+/**
+ * Decide which graphics binding extension to use based on
+ * #GHOST_XrContextCreateInfo.gpu_binding_candidates and available extensions.
+ */
+GHOST_TXrGraphicsBinding GHOST_XrContext::determineGraphicsBindingTypeToEnable(
+ const GHOST_XrContextCreateInfo *create_info)
+{
+ assert(create_info->gpu_binding_candidates != NULL);
+ assert(create_info->gpu_binding_candidates_count > 0);
+
+ for (uint32_t i = 0; i < create_info->gpu_binding_candidates_count; i++) {
+ assert(create_info->gpu_binding_candidates[i] != GHOST_kXrGraphicsUnknown);
+ const char *ext_name = openxr_ext_name_from_wm_gpu_binding(
+ create_info->gpu_binding_candidates[i]);
+ if (openxr_extension_is_available(m_oxr->extensions, ext_name)) {
+ return create_info->gpu_binding_candidates[i];
+ }
+ }
+
+ return GHOST_kXrGraphicsUnknown;
+}
+
+/** \} */ /* OpenXR API-Layers and Extensions */
+
+/* -------------------------------------------------------------------- */
+/** \name Session management
+ *
+ * Manage session lifetime and delegate public calls to #GHOST_XrSession.
+ * \{ */
+
+void GHOST_XrContext::startSession(const GHOST_XrSessionBeginInfo *begin_info)
+{
+ if (m_session == nullptr) {
+ m_session = std::unique_ptr<GHOST_XrSession>(new GHOST_XrSession(this));
+ }
+
+ m_session->start(begin_info);
+}
+
+void GHOST_XrContext::endSession()
+{
+ m_session->requestEnd();
+}
+
+bool GHOST_XrContext::isSessionRunning() const
+{
+ return m_session && m_session->isRunning();
+}
+
+void GHOST_XrContext::drawSessionViews(void *draw_customdata)
+{
+ m_session->draw(draw_customdata);
+}
+
+/**
+ * Delegates event to session, allowing context to destruct the session if needed.
+ */
+void GHOST_XrContext::handleSessionStateChange(const XrEventDataSessionStateChanged *lifecycle)
+{
+ if (m_session &&
+ m_session->handleStateChangeEvent(lifecycle) == GHOST_XrSession::SESSION_DESTROY) {
+ m_session = nullptr;
+ }
+}
+
+/** \} */ /* Session Management */
+
+/* -------------------------------------------------------------------- */
+/** \name Public Accessors and Mutators
+ *
+ * Public as in, exposed in the Ghost API.
+ * \{ */
+
+void GHOST_XrContext::setGraphicsContextBindFuncs(GHOST_XrGraphicsContextBindFn bind_fn,
+ GHOST_XrGraphicsContextUnbindFn unbind_fn)
+{
+ if (m_session) {
+ m_session->unbindGraphicsContext();
+ }
+ m_custom_funcs.gpu_ctx_bind_fn = bind_fn;
+ m_custom_funcs.gpu_ctx_unbind_fn = unbind_fn;
+}
+
+void GHOST_XrContext::setDrawViewFunc(GHOST_XrDrawViewFn draw_view_fn)
+{
+ m_custom_funcs.draw_view_fn = draw_view_fn;
+}
+
+/** \} */ /* Public Accessors and Mutators */
+
+/* -------------------------------------------------------------------- */
+/** \name Ghost Internal Accessors and Mutators
+ *
+ * \{ */
+
+GHOST_TXrOpenXRRuntimeID GHOST_XrContext::getOpenXRRuntimeID() const
+{
+ return m_runtime_id;
+}
+
+const GHOST_XrCustomFuncs &GHOST_XrContext::getCustomFuncs() const
+{
+ return m_custom_funcs;
+}
+
+GHOST_TXrGraphicsBinding GHOST_XrContext::getGraphicsBindingType() const
+{
+ return m_gpu_binding_type;
+}
+
+XrInstance GHOST_XrContext::getInstance() const
+{
+ return m_oxr->instance;
+}
+
+bool GHOST_XrContext::isDebugMode() const
+{
+ return m_debug;
+}
+
+bool GHOST_XrContext::isDebugTimeMode() const
+{
+ return m_debug_time;
+}
+
+/** \} */ /* Ghost Internal Accessors and Mutators */
diff --git a/intern/ghost/intern/GHOST_XrContext.h b/intern/ghost/intern/GHOST_XrContext.h
new file mode 100644
index 00000000000..b361fb5caf8
--- /dev/null
+++ b/intern/ghost/intern/GHOST_XrContext.h
@@ -0,0 +1,127 @@
+/*
+ * 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_XRCONTEXT_H__
+#define __GHOST_XRCONTEXT_H__
+
+#include <memory>
+#include <vector>
+#include "GHOST_IXrContext.h"
+
+struct OpenXRInstanceData;
+
+struct GHOST_XrCustomFuncs {
+ /** Function to retrieve (possibly create) a graphics context. */
+ GHOST_XrGraphicsContextBindFn gpu_ctx_bind_fn = nullptr;
+ /** Function to release (possibly free) a graphics context. */
+ GHOST_XrGraphicsContextUnbindFn gpu_ctx_unbind_fn = nullptr;
+
+ /** Custom per-view draw function for Blender side drawing. */
+ GHOST_XrDrawViewFn draw_view_fn = nullptr;
+};
+
+/**
+ * In some occasions, runtime specific handling is needed, e.g. to work around runtime bugs.
+ */
+enum GHOST_TXrOpenXRRuntimeID {
+ OPENXR_RUNTIME_MONADO,
+ OPENXR_RUNTIME_OCULUS,
+ OPENXR_RUNTIME_WMR, /* Windows Mixed Reality */
+
+ OPENXR_RUNTIME_UNKNOWN
+};
+
+/**
+ * \brief Main GHOST container to manage OpenXR through.
+ *
+ * Creating a context using #GHOST_XrContextCreate involves dynamically connecting to the OpenXR
+ * runtime, likely reading the OS OpenXR configuration (i.e. active_runtime.json). So this is
+ * something that should better be done using lazy-initialization.
+ */
+class GHOST_XrContext : public GHOST_IXrContext {
+ public:
+ GHOST_XrContext(const GHOST_XrContextCreateInfo *create_info);
+ ~GHOST_XrContext();
+ void initialize(const GHOST_XrContextCreateInfo *create_info);
+
+ void startSession(const GHOST_XrSessionBeginInfo *begin_info) override;
+ void endSession() override;
+ bool isSessionRunning() const override;
+ void drawSessionViews(void *draw_customdata) override;
+
+ static void setErrorHandler(GHOST_XrErrorHandlerFn handler_fn, void *customdata);
+ void dispatchErrorMessage(const class GHOST_XrException *exception) const override;
+
+ void setGraphicsContextBindFuncs(GHOST_XrGraphicsContextBindFn bind_fn,
+ GHOST_XrGraphicsContextUnbindFn unbind_fn) override;
+ void setDrawViewFunc(GHOST_XrDrawViewFn draw_view_fn) override;
+
+ void handleSessionStateChange(const XrEventDataSessionStateChanged *lifecycle);
+
+ GHOST_TXrOpenXRRuntimeID getOpenXRRuntimeID() const;
+ const GHOST_XrCustomFuncs &getCustomFuncs() const;
+ GHOST_TXrGraphicsBinding getGraphicsBindingType() const;
+ XrInstance getInstance() const;
+ bool isDebugMode() const;
+ bool isDebugTimeMode() const;
+
+ private:
+ static GHOST_XrErrorHandlerFn s_error_handler;
+ static void *s_error_handler_customdata;
+
+ std::unique_ptr<OpenXRInstanceData> m_oxr;
+
+ GHOST_TXrOpenXRRuntimeID m_runtime_id = OPENXR_RUNTIME_UNKNOWN;
+
+ /* The active GHOST XR Session. Null while no session runs. */
+ std::unique_ptr<class GHOST_XrSession> m_session;
+
+ /** Active graphics binding type. */
+ GHOST_TXrGraphicsBinding m_gpu_binding_type = GHOST_kXrGraphicsUnknown;
+
+ /** Names of enabled extensions. */
+ std::vector<const char *> m_enabled_extensions;
+ /** Names of enabled API-layers. */
+ std::vector<const char *> m_enabled_layers;
+
+ GHOST_XrCustomFuncs m_custom_funcs;
+
+ /** Enable debug message prints and OpenXR API validation layers. */
+ bool m_debug = false;
+ bool m_debug_time = false;
+
+ void createOpenXRInstance();
+ void storeInstanceProperties();
+ void initDebugMessenger();
+
+ void printInstanceInfo();
+ void printAvailableAPILayersAndExtensionsInfo();
+ void printExtensionsAndAPILayersToEnable();
+
+ void initApiLayers();
+ void initExtensions();
+ void initExtensionsEx(std::vector<XrExtensionProperties> &extensions, const char *layer_name);
+ void getAPILayersToEnable(std::vector<const char *> &r_ext_names);
+ void getExtensionsToEnable(std::vector<const char *> &r_ext_names);
+ GHOST_TXrGraphicsBinding determineGraphicsBindingTypeToEnable(
+ const GHOST_XrContextCreateInfo *create_info);
+};
+
+#endif // __GHOST_XRCONTEXT_H__
diff --git a/intern/ghost/intern/GHOST_XrEvent.cpp b/intern/ghost/intern/GHOST_XrEvent.cpp
new file mode 100644
index 00000000000..dfee2e95f10
--- /dev/null
+++ b/intern/ghost/intern/GHOST_XrEvent.cpp
@@ -0,0 +1,64 @@
+/*
+ * 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
+ */
+
+#include <iostream>
+
+#include "GHOST_C-api.h"
+#include "GHOST_Xr_intern.h"
+#include "GHOST_XrContext.h"
+
+static bool GHOST_XrEventPollNext(XrInstance instance, XrEventDataBuffer &r_event_data)
+{
+ /* (Re-)initialize as required by specification. */
+ r_event_data.type = XR_TYPE_EVENT_DATA_BUFFER;
+ r_event_data.next = nullptr;
+
+ return (xrPollEvent(instance, &r_event_data) == XR_SUCCESS);
+}
+
+GHOST_TSuccess GHOST_XrEventsHandle(GHOST_XrContextHandle xr_contexthandle)
+{
+ GHOST_XrContext *xr_context = (GHOST_XrContext *)xr_contexthandle;
+ XrEventDataBuffer event_buffer; /* Structure big enough to hold all possible events. */
+
+ if (xr_context == NULL) {
+ return GHOST_kFailure;
+ }
+
+ while (GHOST_XrEventPollNext(xr_context->getInstance(), event_buffer)) {
+ XrEventDataBaseHeader *event = (XrEventDataBaseHeader *)&event_buffer;
+
+ switch (event->type) {
+ case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED:
+ xr_context->handleSessionStateChange((XrEventDataSessionStateChanged *)event);
+ return GHOST_kSuccess;
+ case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING:
+ GHOST_XrContextDestroy(xr_contexthandle);
+ return GHOST_kSuccess;
+ default:
+ if (xr_context->isDebugMode()) {
+ printf("Unhandled event: %i\n", event->type);
+ }
+ return GHOST_kFailure;
+ }
+ }
+
+ return GHOST_kFailure;
+}
diff --git a/intern/ghost/intern/GHOST_XrException.h b/intern/ghost/intern/GHOST_XrException.h
new file mode 100644
index 00000000000..9f779961e4f
--- /dev/null
+++ b/intern/ghost/intern/GHOST_XrException.h
@@ -0,0 +1,45 @@
+/*
+ * 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_XREXCEPTION_H__
+#define __GHOST_XREXCEPTION_H__
+
+#include <exception>
+
+class GHOST_XrException : public std::exception {
+ friend class GHOST_XrContext;
+
+ public:
+ GHOST_XrException(const char *msg, int result = 0)
+ : std::exception(), m_msg(msg), m_result(result)
+ {
+ }
+
+ const char *what() const noexcept override
+ {
+ return m_msg;
+ }
+
+ private:
+ const char *m_msg;
+ int m_result;
+};
+
+#endif // __GHOST_XREXCEPTION_H__
diff --git a/intern/ghost/intern/GHOST_XrGraphicsBinding.cpp b/intern/ghost/intern/GHOST_XrGraphicsBinding.cpp
new file mode 100644
index 00000000000..ddc757b8f8a
--- /dev/null
+++ b/intern/ghost/intern/GHOST_XrGraphicsBinding.cpp
@@ -0,0 +1,316 @@
+/*
+ * 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
+ */
+
+#include <algorithm>
+#include <list>
+#include <sstream>
+
+#if defined(WITH_X11)
+# include "GHOST_ContextGLX.h"
+#elif defined(WIN32)
+# include "GHOST_ContextWGL.h"
+# include "GHOST_ContextD3D.h"
+#endif
+#include "GHOST_C-api.h"
+#include "GHOST_Xr_intern.h"
+
+#include "GHOST_IXrGraphicsBinding.h"
+
+static bool choose_swapchain_format_from_candidates(std::vector<int64_t> gpu_binding_formats,
+ std::vector<int64_t> runtime_formats,
+ int64_t *r_result)
+{
+ if (gpu_binding_formats.empty()) {
+ return false;
+ }
+
+ auto res = std::find_first_of(gpu_binding_formats.begin(),
+ gpu_binding_formats.end(),
+ runtime_formats.begin(),
+ runtime_formats.end());
+ if (res == gpu_binding_formats.end()) {
+ return false;
+ }
+
+ *r_result = *res;
+ return true;
+}
+
+class GHOST_XrGraphicsBindingOpenGL : public GHOST_IXrGraphicsBinding {
+ public:
+ ~GHOST_XrGraphicsBindingOpenGL()
+ {
+ if (m_fbo != 0) {
+ glDeleteFramebuffers(1, &m_fbo);
+ }
+ }
+
+ bool checkVersionRequirements(GHOST_Context *ghost_ctx,
+ XrInstance instance,
+ XrSystemId system_id,
+ std::string *r_requirement_info) const override
+ {
+#if defined(WITH_X11)
+ GHOST_ContextGLX *ctx_gl = static_cast<GHOST_ContextGLX *>(ghost_ctx);
+#else
+ GHOST_ContextWGL *ctx_gl = static_cast<GHOST_ContextWGL *>(ghost_ctx);
+#endif
+ static PFN_xrGetOpenGLGraphicsRequirementsKHR s_xrGetOpenGLGraphicsRequirementsKHR_fn =
+ nullptr;
+ XrGraphicsRequirementsOpenGLKHR gpu_requirements = {XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR};
+ const XrVersion gl_version = XR_MAKE_VERSION(
+ ctx_gl->m_contextMajorVersion, ctx_gl->m_contextMinorVersion, 0);
+
+ if (!s_xrGetOpenGLGraphicsRequirementsKHR_fn &&
+ XR_FAILED(xrGetInstanceProcAddr(
+ instance,
+ "xrGetOpenGLGraphicsRequirementsKHR",
+ (PFN_xrVoidFunction *)&s_xrGetOpenGLGraphicsRequirementsKHR_fn))) {
+ s_xrGetOpenGLGraphicsRequirementsKHR_fn = nullptr;
+ }
+
+ s_xrGetOpenGLGraphicsRequirementsKHR_fn(instance, system_id, &gpu_requirements);
+
+ if (r_requirement_info) {
+ std::ostringstream strstream;
+ strstream << "Min OpenGL version "
+ << XR_VERSION_MAJOR(gpu_requirements.minApiVersionSupported) << "."
+ << XR_VERSION_MINOR(gpu_requirements.minApiVersionSupported) << std::endl;
+ strstream << "Max OpenGL version "
+ << XR_VERSION_MAJOR(gpu_requirements.maxApiVersionSupported) << "."
+ << XR_VERSION_MINOR(gpu_requirements.maxApiVersionSupported) << std::endl;
+
+ *r_requirement_info = strstream.str();
+ }
+
+ return (gl_version >= gpu_requirements.minApiVersionSupported) &&
+ (gl_version <= gpu_requirements.maxApiVersionSupported);
+ }
+
+ void initFromGhostContext(GHOST_Context *ghost_ctx) override
+ {
+#if defined(WITH_X11)
+ GHOST_ContextGLX *ctx_glx = static_cast<GHOST_ContextGLX *>(ghost_ctx);
+ XVisualInfo *visual_info = glXGetVisualFromFBConfig(ctx_glx->m_display, ctx_glx->m_fbconfig);
+
+ oxr_binding.glx.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR;
+ oxr_binding.glx.xDisplay = ctx_glx->m_display;
+ oxr_binding.glx.glxFBConfig = ctx_glx->m_fbconfig;
+ oxr_binding.glx.glxDrawable = ctx_glx->m_window;
+ oxr_binding.glx.glxContext = ctx_glx->m_context;
+ oxr_binding.glx.visualid = visual_info->visualid;
+#elif defined(WIN32)
+ GHOST_ContextWGL *ctx_wgl = static_cast<GHOST_ContextWGL *>(ghost_ctx);
+
+ oxr_binding.wgl.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR;
+ oxr_binding.wgl.hDC = ctx_wgl->m_hDC;
+ oxr_binding.wgl.hGLRC = ctx_wgl->m_hGLRC;
+#endif
+
+ /* Generate a framebuffer to use for blitting into the texture. */
+ glGenFramebuffers(1, &m_fbo);
+ }
+
+ bool chooseSwapchainFormat(const std::vector<int64_t> &runtime_formats,
+ int64_t *r_result) const override
+ {
+ std::vector<int64_t> gpu_binding_formats = {GL_RGBA8};
+ return choose_swapchain_format_from_candidates(gpu_binding_formats, runtime_formats, r_result);
+ }
+
+ std::vector<XrSwapchainImageBaseHeader *> createSwapchainImages(uint32_t image_count) override
+ {
+ std::vector<XrSwapchainImageOpenGLKHR> ogl_images(image_count);
+ std::vector<XrSwapchainImageBaseHeader *> base_images;
+
+ /* Need to return vector of base header pointers, so of a different type. Need to build a new
+ * list with this type, and keep the initial one alive. */
+ for (XrSwapchainImageOpenGLKHR &image : ogl_images) {
+ image.type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR;
+ base_images.push_back(reinterpret_cast<XrSwapchainImageBaseHeader *>(&image));
+ }
+
+ /* Keep alive. */
+ m_image_cache.push_back(std::move(ogl_images));
+
+ return base_images;
+ }
+
+ void submitToSwapchainImage(XrSwapchainImageBaseHeader *swapchain_image,
+ const GHOST_XrDrawViewInfo *draw_info) override
+ {
+ XrSwapchainImageOpenGLKHR *ogl_swapchain_image = reinterpret_cast<XrSwapchainImageOpenGLKHR *>(
+ swapchain_image);
+
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo);
+
+ glFramebufferTexture2D(
+ GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ogl_swapchain_image->image, 0);
+
+ glBlitFramebuffer(draw_info->ofsx,
+ draw_info->ofsy,
+ draw_info->ofsx + draw_info->width,
+ draw_info->ofsy + draw_info->height,
+ draw_info->ofsx,
+ draw_info->ofsy,
+ draw_info->ofsx + draw_info->width,
+ draw_info->ofsy + draw_info->height,
+ GL_COLOR_BUFFER_BIT,
+ GL_LINEAR);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ }
+
+ private:
+ std::list<std::vector<XrSwapchainImageOpenGLKHR>> m_image_cache;
+ GLuint m_fbo = 0;
+};
+
+#ifdef WIN32
+class GHOST_XrGraphicsBindingD3D : public GHOST_IXrGraphicsBinding {
+ public:
+ ~GHOST_XrGraphicsBindingD3D()
+ {
+ if (m_shared_resource) {
+ m_ghost_ctx->disposeSharedOpenGLResource(m_shared_resource);
+ }
+ }
+
+ bool checkVersionRequirements(GHOST_Context *ghost_ctx,
+ XrInstance instance,
+ XrSystemId system_id,
+ std::string *r_requirement_info) const override
+ {
+ GHOST_ContextD3D *ctx_dx = static_cast<GHOST_ContextD3D *>(ghost_ctx);
+ static PFN_xrGetD3D11GraphicsRequirementsKHR s_xrGetD3D11GraphicsRequirementsKHR_fn = nullptr;
+ XrGraphicsRequirementsD3D11KHR gpu_requirements = {XR_TYPE_GRAPHICS_REQUIREMENTS_D3D11_KHR};
+
+ if (!s_xrGetD3D11GraphicsRequirementsKHR_fn &&
+ XR_FAILED(xrGetInstanceProcAddr(
+ instance,
+ "xrGetD3D11GraphicsRequirementsKHR",
+ (PFN_xrVoidFunction *)&s_xrGetD3D11GraphicsRequirementsKHR_fn))) {
+ s_xrGetD3D11GraphicsRequirementsKHR_fn = nullptr;
+ }
+
+ s_xrGetD3D11GraphicsRequirementsKHR_fn(instance, system_id, &gpu_requirements);
+
+ if (r_requirement_info) {
+ std::ostringstream strstream;
+ strstream << "Minimum DirectX 11 Feature Level " << gpu_requirements.minFeatureLevel
+ << std::endl;
+
+ *r_requirement_info = std::move(strstream.str());
+ }
+
+ return ctx_dx->m_device->GetFeatureLevel() >= gpu_requirements.minFeatureLevel;
+ }
+
+ void initFromGhostContext(GHOST_Context *ghost_ctx) override
+ {
+ GHOST_ContextD3D *ctx_d3d = static_cast<GHOST_ContextD3D *>(ghost_ctx);
+
+ oxr_binding.d3d11.type = XR_TYPE_GRAPHICS_BINDING_D3D11_KHR;
+ oxr_binding.d3d11.device = ctx_d3d->m_device;
+ m_ghost_ctx = ctx_d3d;
+ }
+
+ bool chooseSwapchainFormat(const std::vector<int64_t> &runtime_formats,
+ int64_t *r_result) const override
+ {
+ std::vector<int64_t> gpu_binding_formats = {DXGI_FORMAT_R8G8B8A8_UNORM};
+ return choose_swapchain_format_from_candidates(gpu_binding_formats, runtime_formats, r_result);
+ }
+
+ std::vector<XrSwapchainImageBaseHeader *> createSwapchainImages(uint32_t image_count) override
+ {
+ std::vector<XrSwapchainImageD3D11KHR> d3d_images(image_count);
+ std::vector<XrSwapchainImageBaseHeader *> base_images;
+
+ /* Need to return vector of base header pointers, so of a different type. Need to build a new
+ * list with this type, and keep the initial one alive. */
+ for (XrSwapchainImageD3D11KHR &image : d3d_images) {
+ image.type = XR_TYPE_SWAPCHAIN_IMAGE_D3D11_KHR;
+ base_images.push_back(reinterpret_cast<XrSwapchainImageBaseHeader *>(&image));
+ }
+
+ /* Keep alive. */
+ m_image_cache.push_back(std::move(d3d_images));
+
+ return base_images;
+ }
+
+ void submitToSwapchainImage(XrSwapchainImageBaseHeader *swapchain_image,
+ const GHOST_XrDrawViewInfo *draw_info) override
+ {
+ XrSwapchainImageD3D11KHR *d3d_swapchain_image = reinterpret_cast<XrSwapchainImageD3D11KHR *>(
+ swapchain_image);
+
+# if 0
+ /* Ideally we'd just create a render target view for the OpenXR swapchain image texture and
+ * blit from the OpenGL context into it. The NV_DX_interop extension doesn't want to work with
+ * this though. At least not with Optimus hardware. See:
+ * https://github.com/mpv-player/mpv/issues/2949#issuecomment-197262807.
+ */
+
+ ID3D11RenderTargetView *rtv;
+ CD3D11_RENDER_TARGET_VIEW_DESC rtv_desc(D3D11_RTV_DIMENSION_TEXTURE2D,
+ DXGI_FORMAT_R8G8B8A8_UNORM);
+
+ m_ghost_ctx->m_device->CreateRenderTargetView(d3d_swapchain_image->texture, &rtv_desc, &rtv);
+ if (!m_shared_resource) {
+ m_shared_resource = m_ghost_ctx->createSharedOpenGLResource(
+ draw_info->width, draw_info->height, rtv);
+ }
+ m_ghost_ctx->blitFromOpenGLContext(m_shared_resource, draw_info->width, draw_info->height);
+# else
+ if (!m_shared_resource) {
+ m_shared_resource = m_ghost_ctx->createSharedOpenGLResource(draw_info->width,
+ draw_info->height);
+ }
+ m_ghost_ctx->blitFromOpenGLContext(m_shared_resource, draw_info->width, draw_info->height);
+
+ m_ghost_ctx->m_device_ctx->OMSetRenderTargets(0, nullptr, nullptr);
+ m_ghost_ctx->m_device_ctx->CopyResource(d3d_swapchain_image->texture,
+ m_ghost_ctx->getSharedTexture2D(m_shared_resource));
+# endif
+ }
+
+ private:
+ GHOST_ContextD3D *m_ghost_ctx;
+ GHOST_SharedOpenGLResource *m_shared_resource;
+ std::list<std::vector<XrSwapchainImageD3D11KHR>> m_image_cache;
+};
+#endif // WIN32
+
+std::unique_ptr<GHOST_IXrGraphicsBinding> GHOST_XrGraphicsBindingCreateFromType(
+ GHOST_TXrGraphicsBinding type)
+{
+ switch (type) {
+ case GHOST_kXrGraphicsOpenGL:
+ return std::unique_ptr<GHOST_XrGraphicsBindingOpenGL>(new GHOST_XrGraphicsBindingOpenGL());
+#ifdef WIN32
+ case GHOST_kXrGraphicsD3D11:
+ return std::unique_ptr<GHOST_XrGraphicsBindingD3D>(new GHOST_XrGraphicsBindingD3D());
+#endif
+ default:
+ return nullptr;
+ }
+}
diff --git a/intern/ghost/intern/GHOST_XrSession.cpp b/intern/ghost/intern/GHOST_XrSession.cpp
new file mode 100644
index 00000000000..1e2b8c0bc9d
--- /dev/null
+++ b/intern/ghost/intern/GHOST_XrSession.cpp
@@ -0,0 +1,487 @@
+/*
+ * 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
+ */
+
+#include <algorithm>
+#include <cassert>
+#include <chrono>
+#include <cstdio>
+#include <list>
+#include <sstream>
+
+#include "GHOST_C-api.h"
+
+#include "GHOST_IXrGraphicsBinding.h"
+#include "GHOST_Xr_intern.h"
+#include "GHOST_XrContext.h"
+#include "GHOST_XrException.h"
+#include "GHOST_XrSwapchain.h"
+
+#include "GHOST_XrSession.h"
+
+struct OpenXRSessionData {
+ XrSystemId system_id = XR_NULL_SYSTEM_ID;
+ XrSession session = XR_NULL_HANDLE;
+ XrSessionState session_state = XR_SESSION_STATE_UNKNOWN;
+
+ /* Only stereo rendering supported now. */
+ const XrViewConfigurationType view_type = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
+ XrSpace reference_space;
+ std::vector<XrView> views;
+ std::vector<std::unique_ptr<GHOST_XrSwapchain>> swapchains;
+};
+
+struct GHOST_XrDrawInfo {
+ XrFrameState frame_state;
+
+ /** Time at frame start to benchmark frame render durations. */
+ std::chrono::high_resolution_clock::time_point frame_begin_time;
+ /* Time previous frames took for rendering (in ms). */
+ std::list<double> last_frame_times;
+};
+
+/* -------------------------------------------------------------------- */
+/** \name Create, Initialize and Destruct
+ *
+ * \{ */
+
+GHOST_XrSession::GHOST_XrSession(GHOST_XrContext *xr_context)
+ : m_context(xr_context), m_oxr(new OpenXRSessionData())
+{
+}
+
+GHOST_XrSession::~GHOST_XrSession()
+{
+ unbindGraphicsContext();
+
+ m_oxr->swapchains.clear();
+
+ if (m_oxr->reference_space != XR_NULL_HANDLE) {
+ CHECK_XR_ASSERT(xrDestroySpace(m_oxr->reference_space));
+ }
+ if (m_oxr->session != XR_NULL_HANDLE) {
+ CHECK_XR_ASSERT(xrDestroySession(m_oxr->session));
+ }
+
+ m_oxr->session = XR_NULL_HANDLE;
+ m_oxr->session_state = XR_SESSION_STATE_UNKNOWN;
+}
+
+/**
+ * A system in OpenXR the combination of some sort of HMD plus controllers and whatever other
+ * devices are managed through OpenXR. So this attempts to init the HMD and the other devices.
+ */
+void GHOST_XrSession::initSystem()
+{
+ assert(m_context->getInstance() != XR_NULL_HANDLE);
+ assert(m_oxr->system_id == XR_NULL_SYSTEM_ID);
+
+ XrSystemGetInfo system_info = {};
+ system_info.type = XR_TYPE_SYSTEM_GET_INFO;
+ system_info.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
+
+ CHECK_XR(xrGetSystem(m_context->getInstance(), &system_info, &m_oxr->system_id),
+ "Failed to get device information. Is a device plugged in?");
+}
+
+/** \} */ /* Create, Initialize and Destruct */
+
+/* -------------------------------------------------------------------- */
+/** \name State Management
+ *
+ * \{ */
+
+static void create_reference_space(OpenXRSessionData *oxr, const GHOST_XrPose *base_pose)
+{
+ XrReferenceSpaceCreateInfo create_info = {XR_TYPE_REFERENCE_SPACE_CREATE_INFO};
+ create_info.poseInReferenceSpace.orientation.w = 1.0f;
+
+ create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
+#if 0
+/* TODO
+ *
+ * Proper reference space set up is not supported yet. We simply hand OpenXR
+ * the global space as reference space and apply its pose onto the active
+ * camera matrix to get a basic viewing experience going. If there's no active
+ * camera with stick to the world origin.
+ *
+ * Once we have proper reference space set up (i.e. a way to define origin, up-
+ * direction and an initial view rotation perpendicular to the up-direction),
+ * we can hand OpenXR a proper reference pose/space.
+ */
+ create_info.poseInReferenceSpace.position.x = base_pose->position[0];
+ create_info.poseInReferenceSpace.position.y = base_pose->position[1];
+ create_info.poseInReferenceSpace.position.z = base_pose->position[2];
+ create_info.poseInReferenceSpace.orientation.x = base_pose->orientation_quat[1];
+ create_info.poseInReferenceSpace.orientation.y = base_pose->orientation_quat[2];
+ create_info.poseInReferenceSpace.orientation.z = base_pose->orientation_quat[3];
+ create_info.poseInReferenceSpace.orientation.w = base_pose->orientation_quat[0];
+#else
+ (void)base_pose;
+#endif
+
+ CHECK_XR(xrCreateReferenceSpace(oxr->session, &create_info, &oxr->reference_space),
+ "Failed to create reference space.");
+}
+
+void GHOST_XrSession::start(const GHOST_XrSessionBeginInfo *begin_info)
+{
+ assert(m_context->getInstance() != XR_NULL_HANDLE);
+ assert(m_oxr->session == XR_NULL_HANDLE);
+ if (m_context->getCustomFuncs().gpu_ctx_bind_fn == nullptr) {
+ throw GHOST_XrException(
+ "Invalid API usage: No way to bind graphics context to the XR session. Call "
+ "GHOST_XrGraphicsContextBindFuncs() with valid parameters before starting the "
+ "session (through GHOST_XrSessionStart()).");
+ }
+
+ initSystem();
+
+ bindGraphicsContext();
+ if (m_gpu_ctx == nullptr) {
+ throw GHOST_XrException(
+ "Invalid API usage: No graphics context returned through the callback set with "
+ "GHOST_XrGraphicsContextBindFuncs(). This is required for session starting (through "
+ "GHOST_XrSessionStart()).");
+ }
+
+ std::string requirement_str;
+ m_gpu_binding = GHOST_XrGraphicsBindingCreateFromType(m_context->getGraphicsBindingType());
+ if (!m_gpu_binding->checkVersionRequirements(
+ m_gpu_ctx, m_context->getInstance(), m_oxr->system_id, &requirement_str)) {
+ std::ostringstream strstream;
+ strstream << "Available graphics context version does not meet the following requirements: "
+ << requirement_str;
+ throw GHOST_XrException(strstream.str().c_str());
+ }
+ m_gpu_binding->initFromGhostContext(m_gpu_ctx);
+
+ XrSessionCreateInfo create_info = {};
+ create_info.type = XR_TYPE_SESSION_CREATE_INFO;
+ create_info.systemId = m_oxr->system_id;
+ create_info.next = &m_gpu_binding->oxr_binding;
+
+ CHECK_XR(xrCreateSession(m_context->getInstance(), &create_info, &m_oxr->session),
+ "Failed to create VR session. The OpenXR runtime may have additional requirements for "
+ "the graphics driver that are not met. Other causes are possible too however.\nTip: "
+ "The --debug-xr command line option for Blender might allow the runtime to output "
+ "detailed error information to the command line.");
+
+ prepareDrawing();
+ create_reference_space(m_oxr.get(), &begin_info->base_pose);
+}
+
+void GHOST_XrSession::requestEnd()
+{
+ xrRequestExitSession(m_oxr->session);
+}
+
+void GHOST_XrSession::end()
+{
+ assert(m_oxr->session != XR_NULL_HANDLE);
+
+ CHECK_XR(xrEndSession(m_oxr->session), "Failed to cleanly end the VR session.");
+ unbindGraphicsContext();
+ m_draw_info = nullptr;
+}
+
+GHOST_XrSession::LifeExpectancy GHOST_XrSession::handleStateChangeEvent(
+ const XrEventDataSessionStateChanged *lifecycle)
+{
+ m_oxr->session_state = lifecycle->state;
+
+ /* Runtime may send events for apparently destroyed session. Our handle should be NULL then. */
+ assert((m_oxr->session == XR_NULL_HANDLE) || (m_oxr->session == lifecycle->session));
+
+ switch (lifecycle->state) {
+ case XR_SESSION_STATE_READY: {
+ XrSessionBeginInfo begin_info = {XR_TYPE_SESSION_BEGIN_INFO};
+
+ begin_info.primaryViewConfigurationType = m_oxr->view_type;
+ CHECK_XR(xrBeginSession(m_oxr->session, &begin_info),
+ "Failed to cleanly begin the VR session.");
+ break;
+ }
+ case XR_SESSION_STATE_STOPPING:
+ /* Runtime will change state to STATE_EXITING, don't destruct session yet. */
+ end();
+ break;
+ case XR_SESSION_STATE_EXITING:
+ case XR_SESSION_STATE_LOSS_PENDING:
+ return SESSION_DESTROY;
+ default:
+ break;
+ }
+
+ return SESSION_KEEP_ALIVE;
+}
+/** \} */ /* State Management */
+
+/* -------------------------------------------------------------------- */
+/** \name Drawing
+ *
+ * \{ */
+
+void GHOST_XrSession::prepareDrawing()
+{
+ std::vector<XrViewConfigurationView> view_configs;
+ uint32_t view_count;
+
+ CHECK_XR(
+ xrEnumerateViewConfigurationViews(
+ m_context->getInstance(), m_oxr->system_id, m_oxr->view_type, 0, &view_count, nullptr),
+ "Failed to get count of view configurations.");
+ view_configs.resize(view_count, {XR_TYPE_VIEW_CONFIGURATION_VIEW});
+ CHECK_XR(xrEnumerateViewConfigurationViews(m_context->getInstance(),
+ m_oxr->system_id,
+ m_oxr->view_type,
+ view_configs.size(),
+ &view_count,
+ view_configs.data()),
+ "Failed to get count of view configurations.");
+
+ for (const XrViewConfigurationView &view_config : view_configs) {
+ m_oxr->swapchains.push_back(std::unique_ptr<GHOST_XrSwapchain>(
+ new GHOST_XrSwapchain(*m_gpu_binding, m_oxr->session, view_config)));
+ }
+
+ m_oxr->views.resize(view_count, {XR_TYPE_VIEW});
+
+ m_draw_info = std::unique_ptr<GHOST_XrDrawInfo>(new GHOST_XrDrawInfo());
+}
+
+void GHOST_XrSession::beginFrameDrawing()
+{
+ XrFrameWaitInfo wait_info = {XR_TYPE_FRAME_WAIT_INFO};
+ XrFrameBeginInfo begin_info = {XR_TYPE_FRAME_BEGIN_INFO};
+ XrFrameState frame_state = {XR_TYPE_FRAME_STATE};
+
+ /* TODO Blocking call. Drawing should run on a separate thread to avoid interferences. */
+ CHECK_XR(xrWaitFrame(m_oxr->session, &wait_info, &frame_state),
+ "Failed to synchronize frame rates between Blender and the device.");
+
+ CHECK_XR(xrBeginFrame(m_oxr->session, &begin_info),
+ "Failed to submit frame rendering start state.");
+
+ m_draw_info->frame_state = frame_state;
+
+ if (m_context->isDebugTimeMode()) {
+ m_draw_info->frame_begin_time = std::chrono::high_resolution_clock::now();
+ }
+}
+
+static void print_debug_timings(GHOST_XrDrawInfo *draw_info)
+{
+ /** Render time of last 8 frames (in ms) to calculate an average. */
+ std::chrono::duration<double, std::milli> duration = std::chrono::high_resolution_clock::now() -
+ draw_info->frame_begin_time;
+ const double duration_ms = duration.count();
+ const int avg_frame_count = 8;
+ double avg_ms_tot = 0.0;
+
+ if (draw_info->last_frame_times.size() >= avg_frame_count) {
+ draw_info->last_frame_times.pop_front();
+ assert(draw_info->last_frame_times.size() == avg_frame_count - 1);
+ }
+ draw_info->last_frame_times.push_back(duration_ms);
+ for (double ms_iter : draw_info->last_frame_times) {
+ avg_ms_tot += ms_iter;
+ }
+
+ printf("VR frame render time: %.0fms - %.2f FPS (%.2f FPS 8 frames average)\n",
+ duration_ms,
+ 1000.0 / duration_ms,
+ 1000.0 / (avg_ms_tot / draw_info->last_frame_times.size()));
+}
+
+void GHOST_XrSession::endFrameDrawing(std::vector<XrCompositionLayerBaseHeader *> *layers)
+{
+ XrFrameEndInfo end_info = {XR_TYPE_FRAME_END_INFO};
+
+ end_info.displayTime = m_draw_info->frame_state.predictedDisplayTime;
+ end_info.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
+ end_info.layerCount = layers->size();
+ end_info.layers = layers->data();
+
+ CHECK_XR(xrEndFrame(m_oxr->session, &end_info), "Failed to submit rendered frame.");
+
+ if (m_context->isDebugTimeMode()) {
+ print_debug_timings(m_draw_info.get());
+ }
+}
+
+void GHOST_XrSession::draw(void *draw_customdata)
+{
+ std::vector<XrCompositionLayerProjectionView>
+ projection_layer_views; /* Keep alive until xrEndFrame() call! */
+ XrCompositionLayerProjection proj_layer;
+ std::vector<XrCompositionLayerBaseHeader *> layers;
+
+ beginFrameDrawing();
+
+ if (m_draw_info->frame_state.shouldRender) {
+ proj_layer = drawLayer(projection_layer_views, draw_customdata);
+ layers.push_back(reinterpret_cast<XrCompositionLayerBaseHeader *>(&proj_layer));
+ }
+
+ endFrameDrawing(&layers);
+}
+
+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;
+
+ r_info.fov.angle_left = view.fov.angleLeft;
+ r_info.fov.angle_right = view.fov.angleRight;
+ r_info.fov.angle_up = view.fov.angleUp;
+ r_info.fov.angle_down = view.fov.angleDown;
+}
+
+static bool ghost_xr_draw_view_expects_srgb_buffer(const GHOST_XrContext *context)
+{
+ /* Monado seems to be faulty and doesn't do OETF transform correctly. So expect a SRGB buffer to
+ * compensate. You get way too dark rendering without this, it's pretty obvious (even in the
+ * default startup scene). */
+ return (context->getOpenXRRuntimeID() == OPENXR_RUNTIME_MONADO);
+}
+
+void GHOST_XrSession::drawView(GHOST_XrSwapchain &swapchain,
+ XrCompositionLayerProjectionView &r_proj_layer_view,
+ XrView &view,
+ void *draw_customdata)
+{
+ XrSwapchainImageBaseHeader *swapchain_image = swapchain.acquireDrawableSwapchainImage();
+ GHOST_XrDrawViewInfo draw_view_info = {};
+
+ r_proj_layer_view.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW;
+ r_proj_layer_view.pose = view.pose;
+ r_proj_layer_view.fov = view.fov;
+ swapchain.updateCompositionLayerProjectViewSubImage(r_proj_layer_view.subImage);
+
+ draw_view_info.expects_srgb_buffer = ghost_xr_draw_view_expects_srgb_buffer(m_context);
+ draw_view_info.ofsx = r_proj_layer_view.subImage.imageRect.offset.x;
+ 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;
+ ghost_xr_draw_view_info_from_view(view, draw_view_info);
+
+ /* Draw! */
+ m_context->getCustomFuncs().draw_view_fn(&draw_view_info, draw_customdata);
+ m_gpu_binding->submitToSwapchainImage(swapchain_image, &draw_view_info);
+
+ swapchain.releaseImage();
+}
+
+XrCompositionLayerProjection GHOST_XrSession::drawLayer(
+ std::vector<XrCompositionLayerProjectionView> &r_proj_layer_views, void *draw_customdata)
+{
+ XrViewLocateInfo viewloc_info = {XR_TYPE_VIEW_LOCATE_INFO};
+ XrViewState view_state = {XR_TYPE_VIEW_STATE};
+ XrCompositionLayerProjection layer = {XR_TYPE_COMPOSITION_LAYER_PROJECTION};
+ uint32_t view_count;
+
+ viewloc_info.viewConfigurationType = m_oxr->view_type;
+ viewloc_info.displayTime = m_draw_info->frame_state.predictedDisplayTime;
+ viewloc_info.space = m_oxr->reference_space;
+
+ CHECK_XR(xrLocateViews(m_oxr->session,
+ &viewloc_info,
+ &view_state,
+ m_oxr->views.size(),
+ &view_count,
+ m_oxr->views.data()),
+ "Failed to query frame view and projection state.");
+ assert(m_oxr->swapchains.size() == view_count);
+
+ 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],
+ m_oxr->views[view_idx],
+ draw_customdata);
+ }
+
+ layer.space = m_oxr->reference_space;
+ layer.viewCount = r_proj_layer_views.size();
+ layer.views = r_proj_layer_views.data();
+
+ return layer;
+}
+
+/** \} */ /* Drawing */
+
+/* -------------------------------------------------------------------- */
+/** \name State Queries
+ *
+ * \{ */
+
+bool GHOST_XrSession::isRunning() const
+{
+ if (m_oxr->session == XR_NULL_HANDLE) {
+ return false;
+ }
+ switch (m_oxr->session_state) {
+ case XR_SESSION_STATE_READY:
+ case XR_SESSION_STATE_SYNCHRONIZED:
+ case XR_SESSION_STATE_VISIBLE:
+ case XR_SESSION_STATE_FOCUSED:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/** \} */ /* State Queries */
+
+/* -------------------------------------------------------------------- */
+/** \name Graphics Context Injection
+ *
+ * Sessions need access to Ghost graphics context information. Additionally, this API allows
+ * creating contexts on the fly (created on start, destructed on end). For this, callbacks to bind
+ * (potentially create) and unbind (potentially destruct) a Ghost graphics context have to be set,
+ * which will be called on session start and end respectively.
+ *
+ * \{ */
+
+void GHOST_XrSession::bindGraphicsContext()
+{
+ const GHOST_XrCustomFuncs &custom_funcs = m_context->getCustomFuncs();
+ assert(custom_funcs.gpu_ctx_bind_fn);
+ m_gpu_ctx = static_cast<GHOST_Context *>(
+ custom_funcs.gpu_ctx_bind_fn(m_context->getGraphicsBindingType()));
+}
+
+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);
+ }
+ m_gpu_ctx = nullptr;
+}
+
+/** \} */ /* Graphics Context Injection */
diff --git a/intern/ghost/intern/GHOST_XrSession.h b/intern/ghost/intern/GHOST_XrSession.h
new file mode 100644
index 00000000000..3340385c1b6
--- /dev/null
+++ b/intern/ghost/intern/GHOST_XrSession.h
@@ -0,0 +1,83 @@
+/*
+ * 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_XRSESSION_H__
+#define __GHOST_XRSESSION_H__
+
+#include <map>
+#include <memory>
+
+class GHOST_XrContext;
+struct OpenXRSessionData;
+struct GHOST_XrDrawInfo;
+struct GHOST_XrSwapchain;
+
+class GHOST_XrSession {
+ public:
+ enum LifeExpectancy {
+ SESSION_KEEP_ALIVE,
+ SESSION_DESTROY,
+ };
+
+ GHOST_XrSession(GHOST_XrContext *xr_context);
+ ~GHOST_XrSession();
+
+ void start(const GHOST_XrSessionBeginInfo *begin_info);
+ void requestEnd();
+
+ LifeExpectancy handleStateChangeEvent(const XrEventDataSessionStateChanged *lifecycle);
+
+ bool isRunning() const;
+
+ void unbindGraphicsContext(); /* Public so context can ensure it's unbound as needed. */
+
+ void draw(void *draw_customdata);
+
+ private:
+ /** Pointer back to context managing this session. Would be nice to avoid, but needed to access
+ * custom callbacks set before session start. */
+ class GHOST_XrContext *m_context;
+
+ std::unique_ptr<OpenXRSessionData> m_oxr; /* Could use stack, but PImpl is preferable. */
+
+ /** Active Ghost graphic context. Owned by Blender, not GHOST. */
+ class GHOST_Context *m_gpu_ctx = nullptr;
+ std::unique_ptr<class GHOST_IXrGraphicsBinding> m_gpu_binding;
+
+ /** Rendering information. Set when drawing starts. */
+ std::unique_ptr<GHOST_XrDrawInfo> m_draw_info;
+
+ void initSystem();
+ void end();
+
+ void bindGraphicsContext();
+
+ void prepareDrawing();
+ XrCompositionLayerProjection drawLayer(
+ std::vector<XrCompositionLayerProjectionView> &r_proj_layer_views, void *draw_customdata);
+ void drawView(GHOST_XrSwapchain &swapchain,
+ XrCompositionLayerProjectionView &r_proj_layer_view,
+ XrView &view,
+ void *draw_customdata);
+ void beginFrameDrawing();
+ void endFrameDrawing(std::vector<XrCompositionLayerBaseHeader *> *layers);
+};
+
+#endif /* GHOST_XRSESSION_H__ */
diff --git a/intern/ghost/intern/GHOST_XrSwapchain.cpp b/intern/ghost/intern/GHOST_XrSwapchain.cpp
new file mode 100644
index 00000000000..f0b2fb80bf1
--- /dev/null
+++ b/intern/ghost/intern/GHOST_XrSwapchain.cpp
@@ -0,0 +1,131 @@
+/*
+ * 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
+ */
+
+#include <cassert>
+
+#include "GHOST_C-api.h"
+
+#include "GHOST_IXrGraphicsBinding.h"
+#include "GHOST_Xr_intern.h"
+#include "GHOST_XrException.h"
+#include "GHOST_XrSession.h"
+
+#include "GHOST_XrSwapchain.h"
+
+struct OpenXRSwapchainData {
+ using ImageVec = std::vector<XrSwapchainImageBaseHeader *>;
+
+ XrSwapchain swapchain = XR_NULL_HANDLE;
+ ImageVec swapchain_images;
+};
+
+static OpenXRSwapchainData::ImageVec swapchain_images_create(XrSwapchain swapchain,
+ GHOST_IXrGraphicsBinding &gpu_binding)
+{
+ std::vector<XrSwapchainImageBaseHeader *> images;
+ uint32_t image_count;
+
+ CHECK_XR(xrEnumerateSwapchainImages(swapchain, 0, &image_count, nullptr),
+ "Failed to get count of swapchain images to create for the VR session.");
+ images = gpu_binding.createSwapchainImages(image_count);
+ CHECK_XR(xrEnumerateSwapchainImages(swapchain, images.size(), &image_count, images[0]),
+ "Failed to create swapchain images for the VR session.");
+
+ return images;
+}
+
+GHOST_XrSwapchain::GHOST_XrSwapchain(GHOST_IXrGraphicsBinding &gpu_binding,
+ const XrSession &session,
+ const XrViewConfigurationView &view_config)
+ : m_oxr(new OpenXRSwapchainData())
+{
+ XrSwapchainCreateInfo create_info = {XR_TYPE_SWAPCHAIN_CREATE_INFO};
+ uint32_t format_count = 0;
+ int64_t chosen_format;
+
+ CHECK_XR(xrEnumerateSwapchainFormats(session, 0, &format_count, nullptr),
+ "Failed to get count of swapchain image formats.");
+ std::vector<int64_t> swapchain_formats(format_count);
+ CHECK_XR(xrEnumerateSwapchainFormats(
+ session, swapchain_formats.size(), &format_count, swapchain_formats.data()),
+ "Failed to get swapchain image formats.");
+ assert(swapchain_formats.size() == format_count);
+
+ if (!gpu_binding.chooseSwapchainFormat(swapchain_formats, &chosen_format)) {
+ throw GHOST_XrException(
+ "Error: No format matching OpenXR runtime supported swapchain formats found.");
+ }
+
+ create_info.usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT |
+ XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT;
+ create_info.format = chosen_format;
+ create_info.sampleCount = view_config.recommendedSwapchainSampleCount;
+ create_info.width = view_config.recommendedImageRectWidth;
+ create_info.height = view_config.recommendedImageRectHeight;
+ create_info.faceCount = 1;
+ create_info.arraySize = 1;
+ create_info.mipCount = 1;
+
+ CHECK_XR(xrCreateSwapchain(session, &create_info, &m_oxr->swapchain),
+ "Failed to create OpenXR swapchain.");
+
+ m_image_width = create_info.width;
+ m_image_height = create_info.height;
+
+ m_oxr->swapchain_images = swapchain_images_create(m_oxr->swapchain, gpu_binding);
+}
+
+GHOST_XrSwapchain::~GHOST_XrSwapchain()
+{
+ if (m_oxr->swapchain != XR_NULL_HANDLE) {
+ CHECK_XR_ASSERT(xrDestroySwapchain(m_oxr->swapchain));
+ }
+}
+
+XrSwapchainImageBaseHeader *GHOST_XrSwapchain::acquireDrawableSwapchainImage()
+
+{
+ XrSwapchainImageAcquireInfo acquire_info = {XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO};
+ XrSwapchainImageWaitInfo wait_info = {XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO};
+ uint32_t image_idx;
+
+ CHECK_XR(xrAcquireSwapchainImage(m_oxr->swapchain, &acquire_info, &image_idx),
+ "Failed to acquire swapchain image for the VR session.");
+ wait_info.timeout = XR_INFINITE_DURATION;
+ CHECK_XR(xrWaitSwapchainImage(m_oxr->swapchain, &wait_info),
+ "Failed to acquire swapchain image for the VR session.");
+
+ return m_oxr->swapchain_images[image_idx];
+}
+
+void GHOST_XrSwapchain::updateCompositionLayerProjectViewSubImage(XrSwapchainSubImage &r_sub_image)
+{
+ r_sub_image.swapchain = m_oxr->swapchain;
+ r_sub_image.imageRect.offset = {0, 0};
+ r_sub_image.imageRect.extent = {m_image_width, m_image_height};
+}
+
+void GHOST_XrSwapchain::releaseImage()
+{
+ XrSwapchainImageReleaseInfo release_info = {XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO};
+
+ CHECK_XR(xrReleaseSwapchainImage(m_oxr->swapchain, &release_info),
+ "Failed to release swapchain image used to submit VR session frame.");
+}
diff --git a/intern/ghost/intern/GHOST_XrSwapchain.h b/intern/ghost/intern/GHOST_XrSwapchain.h
new file mode 100644
index 00000000000..df9cd924dd0
--- /dev/null
+++ b/intern/ghost/intern/GHOST_XrSwapchain.h
@@ -0,0 +1,45 @@
+/*
+ * 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_XRSWAPCHAIN_H__
+#define __GHOST_XRSWAPCHAIN_H__
+
+#include <memory>
+
+struct OpenXRSwapchainData;
+
+class GHOST_XrSwapchain {
+ public:
+ GHOST_XrSwapchain(GHOST_IXrGraphicsBinding &gpu_binding,
+ const XrSession &session,
+ const XrViewConfigurationView &view_config);
+ ~GHOST_XrSwapchain();
+
+ XrSwapchainImageBaseHeader *acquireDrawableSwapchainImage();
+ void releaseImage();
+
+ void updateCompositionLayerProjectViewSubImage(XrSwapchainSubImage &r_sub_image);
+
+ private:
+ std::unique_ptr<OpenXRSwapchainData> m_oxr; /* Could use stack, but PImpl is preferable. */
+ int32_t m_image_width, m_image_height;
+};
+
+#endif // GHOST_XRSWAPCHAIN_H
diff --git a/intern/ghost/intern/GHOST_Xr_intern.h b/intern/ghost/intern/GHOST_Xr_intern.h
new file mode 100644
index 00000000000..d59ffd31940
--- /dev/null
+++ b/intern/ghost/intern/GHOST_Xr_intern.h
@@ -0,0 +1,50 @@
+/*
+ * 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_XR_INTERN_H__
+#define __GHOST_XR_INTERN_H__
+
+#include <memory>
+#include <vector>
+
+#include "GHOST_Xr_openxr_includes.h"
+
+#define CHECK_XR(call, error_msg) \
+ { \
+ XrResult _res = call; \
+ if (XR_FAILED(_res)) { \
+ throw GHOST_XrException(error_msg, _res); \
+ } \
+ } \
+ (void)0
+
+/**
+ * Variation of CHECK_XR() that doesn't throw, but asserts for success. Especially useful for
+ * destructors, which shouldn't throw.
+ */
+#define CHECK_XR_ASSERT(call) \
+ { \
+ XrResult _res = call; \
+ assert(_res == XR_SUCCESS); \
+ (void)_res; \
+ } \
+ (void)0
+
+#endif /* __GHOST_XR_INTERN_H__ */
diff --git a/intern/ghost/intern/GHOST_Xr_openxr_includes.h b/intern/ghost/intern/GHOST_Xr_openxr_includes.h
new file mode 100644
index 00000000000..925d6037750
--- /dev/null
+++ b/intern/ghost/intern/GHOST_Xr_openxr_includes.h
@@ -0,0 +1,52 @@
+/*
+ * 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
+ *
+ * \note This is taken mostly from the OpenXR SDK, but with modified D3D versions (e.g. d3d11_4.h
+ * -> d3d11.h). Take care for that when updating, we don't want to require newest Win SDKs to be
+ * installed.
+ */
+
+#ifndef __GHOST_XR_SYSTEM_INCLUDES_H__
+#define __GHOST_XR_SYSTEM_INCLUDES_H__
+
+/* Platform headers */
+#ifdef XR_USE_PLATFORM_WIN32
+# define WIN32_LEAN_AND_MEAN
+# define NOMINMAX
+# include <windows.h>
+#endif
+
+/* Graphics headers */
+#ifdef XR_USE_GRAPHICS_API_D3D10
+# include <d3d10_1.h>
+#endif
+#ifdef XR_USE_GRAPHICS_API_D3D11
+# include <d3d11.h>
+#endif
+#ifdef XR_USE_GRAPHICS_API_D3D12
+# include <d3d12.h>
+#endif
+#ifdef WITH_X11
+# include <GL/glxew.h>
+#endif
+
+#include <openxr/openxr.h>
+#include <openxr/openxr_platform.h>
+
+#endif /* __GHOST_XR_SYSTEM_INCLUDES_H__ */