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
This commit is contained in:
Julian Eisel 2020-03-17 20:10:57 +01:00
parent c9a8de1d70
commit 406bfd4304
22 changed files with 2415 additions and 15 deletions

View File

@ -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}")

View File

@ -30,21 +30,6 @@
extern "C" {
#endif
/**
* Creates a "handle" for a C++ GHOST object.
* A handle is just an opaque pointer to an empty struct.
* In the API the pointer is cast to the actual C++ class.
* The 'name' argument to the macro is the name of the handle to create.
*/
GHOST_DECLARE_HANDLE(GHOST_SystemHandle);
GHOST_DECLARE_HANDLE(GHOST_TimerTaskHandle);
GHOST_DECLARE_HANDLE(GHOST_WindowHandle);
GHOST_DECLARE_HANDLE(GHOST_EventHandle);
GHOST_DECLARE_HANDLE(GHOST_RectangleHandle);
GHOST_DECLARE_HANDLE(GHOST_EventConsumerHandle);
GHOST_DECLARE_HANDLE(GHOST_ContextHandle);
/**
* Definition of a callback routine that receives events.
* \param event The event received.
@ -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

View File

@ -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__

View File

@ -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__

View File

@ -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

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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.

View File

@ -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.

View File

@ -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__ */

View File

@ -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);
}

View File

@ -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 */

View File

@ -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__

View File

@ -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;
}

View File

@ -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__

View File

@ -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;
}
}

View File

@ -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 */

View File

@ -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__ */

View File

@ -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.");
}

View File

@ -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

View File

@ -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__ */

View File

@ -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__ */