diff options
author | Julian Eisel <julian@blender.org> | 2020-03-17 22:10:57 +0300 |
---|---|---|
committer | Julian Eisel <julian@blender.org> | 2020-03-17 23:39:59 +0300 |
commit | 406bfd43040a5526702b51f88f1491cb61aecedb (patch) | |
tree | 980c77919f9930d51c5f4f411d2e658f66a263e5 | |
parent | c9a8de1d704b807460a7a6838db28f7ae2472200 (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
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__ */ |