forked from bartvdbraak/blender
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:
parent
c9a8de1d70
commit
406bfd4304
@ -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}")
|
||||
|
@ -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
|
||||
|
42
intern/ghost/GHOST_IXrContext.h
Normal file
42
intern/ghost/GHOST_IXrContext.h
Normal 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__
|
@ -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__
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
71
intern/ghost/intern/GHOST_IXrGraphicsBinding.h
Normal file
71
intern/ghost/intern/GHOST_IXrGraphicsBinding.h
Normal 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__ */
|
59
intern/ghost/intern/GHOST_Xr.cpp
Normal file
59
intern/ghost/intern/GHOST_Xr.cpp
Normal 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);
|
||||
}
|
550
intern/ghost/intern/GHOST_XrContext.cpp
Normal file
550
intern/ghost/intern/GHOST_XrContext.cpp
Normal 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 */
|
127
intern/ghost/intern/GHOST_XrContext.h
Normal file
127
intern/ghost/intern/GHOST_XrContext.h
Normal 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__
|
64
intern/ghost/intern/GHOST_XrEvent.cpp
Normal file
64
intern/ghost/intern/GHOST_XrEvent.cpp
Normal 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;
|
||||
}
|
45
intern/ghost/intern/GHOST_XrException.h
Normal file
45
intern/ghost/intern/GHOST_XrException.h
Normal 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__
|
316
intern/ghost/intern/GHOST_XrGraphicsBinding.cpp
Normal file
316
intern/ghost/intern/GHOST_XrGraphicsBinding.cpp
Normal 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;
|
||||
}
|
||||
}
|
487
intern/ghost/intern/GHOST_XrSession.cpp
Normal file
487
intern/ghost/intern/GHOST_XrSession.cpp
Normal 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 */
|
83
intern/ghost/intern/GHOST_XrSession.h
Normal file
83
intern/ghost/intern/GHOST_XrSession.h
Normal 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__ */
|
131
intern/ghost/intern/GHOST_XrSwapchain.cpp
Normal file
131
intern/ghost/intern/GHOST_XrSwapchain.cpp
Normal 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.");
|
||||
}
|
45
intern/ghost/intern/GHOST_XrSwapchain.h
Normal file
45
intern/ghost/intern/GHOST_XrSwapchain.h
Normal 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
|
50
intern/ghost/intern/GHOST_Xr_intern.h
Normal file
50
intern/ghost/intern/GHOST_Xr_intern.h
Normal 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__ */
|
52
intern/ghost/intern/GHOST_Xr_openxr_includes.h
Normal file
52
intern/ghost/intern/GHOST_Xr_openxr_includes.h
Normal 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__ */
|
Loading…
Reference in New Issue
Block a user