2019-04-11 20:47:25 +00:00
|
|
|
#include "headset/headset.h"
|
|
|
|
#include "event/event.h"
|
2019-08-04 02:06:46 +00:00
|
|
|
#include "filesystem/filesystem.h"
|
2019-04-11 20:47:25 +00:00
|
|
|
#include "graphics/graphics.h"
|
|
|
|
#include "graphics/canvas.h"
|
|
|
|
#include "graphics/texture.h"
|
2019-08-04 02:06:46 +00:00
|
|
|
#include "core/util.h"
|
2020-08-19 03:09:06 +00:00
|
|
|
#include <stdlib.h>
|
2019-04-11 20:47:25 +00:00
|
|
|
#include <math.h>
|
2020-08-19 03:09:06 +00:00
|
|
|
#if defined(_WIN32)
|
2020-08-24 08:04:06 +00:00
|
|
|
#define XR_USE_PLATFORM_WIN32
|
2021-02-07 23:49:34 +00:00
|
|
|
#define WIN32_LEAN_AND_MEAN
|
2021-12-27 13:16:50 +00:00
|
|
|
#include <unknwn.h>
|
2020-08-24 08:04:06 +00:00
|
|
|
#include <windows.h>
|
2020-08-19 03:09:06 +00:00
|
|
|
#elif defined(__ANDROID__)
|
2020-08-24 08:04:06 +00:00
|
|
|
#define XR_USE_PLATFORM_ANDROID
|
2020-08-30 01:45:52 +00:00
|
|
|
#include <android_native_app_glue.h>
|
2020-08-24 08:04:06 +00:00
|
|
|
#include <EGL/egl.h>
|
|
|
|
#include <jni.h>
|
|
|
|
#endif
|
|
|
|
#if defined(LOVR_GL)
|
|
|
|
#define XR_USE_GRAPHICS_API_OPENGL
|
|
|
|
#define GRAPHICS_EXTENSION "XR_KHR_opengl_enable"
|
|
|
|
#elif defined(LOVR_GLES)
|
2020-08-30 01:45:52 +00:00
|
|
|
#define XR_USE_GRAPHICS_API_OPENGL_ES
|
2020-08-24 08:04:06 +00:00
|
|
|
#define GRAPHICS_EXTENSION "XR_KHR_opengl_es_enable"
|
2019-04-11 20:47:25 +00:00
|
|
|
#endif
|
2020-10-24 18:29:25 +00:00
|
|
|
#if defined(LOVR_LINUX_X11)
|
|
|
|
#define XR_USE_PLATFORM_XLIB
|
|
|
|
typedef unsigned long XID;
|
|
|
|
typedef struct Display Display;
|
|
|
|
typedef XID GLXFBConfig;
|
|
|
|
typedef XID GLXDrawable;
|
|
|
|
typedef XID GLXContext;
|
|
|
|
#endif
|
|
|
|
#if defined(LOVR_LINUX_EGL)
|
|
|
|
#define XR_USE_PLATFORM_EGL
|
|
|
|
#define EGL_NO_X11
|
|
|
|
#include <EGL/egl.h>
|
|
|
|
#endif
|
2020-08-28 23:04:45 +00:00
|
|
|
#define XR_NO_PROTOTYPES
|
2019-04-11 20:47:25 +00:00
|
|
|
#include <openxr/openxr.h>
|
|
|
|
#include <openxr/openxr_platform.h>
|
2020-08-30 01:45:52 +00:00
|
|
|
#ifdef __ANDROID__
|
|
|
|
#include <openxr/openxr_oculus.h>
|
|
|
|
#endif
|
2020-08-22 21:40:52 +00:00
|
|
|
#include "resources/openxr_actions.h"
|
2019-04-11 20:47:25 +00:00
|
|
|
|
|
|
|
#define XR(f) handleResult(f, __FILE__, __LINE__)
|
2019-08-04 02:06:46 +00:00
|
|
|
#define XR_INIT(f) if (XR_FAILED(f)) return openxr_destroy(), false;
|
2020-08-26 19:01:18 +00:00
|
|
|
#define SESSION_ACTIVE(s) (s >= XR_SESSION_STATE_READY && s <= XR_SESSION_STATE_FOCUSED)
|
2020-08-19 03:09:06 +00:00
|
|
|
#define GL_SRGB8_ALPHA8 0x8C43
|
2019-04-11 20:47:25 +00:00
|
|
|
#define MAX_IMAGES 4
|
|
|
|
|
2020-08-22 21:40:52 +00:00
|
|
|
#if defined(_WIN32)
|
2021-02-25 16:00:12 +00:00
|
|
|
HANDLE os_get_win32_window(void);
|
|
|
|
HGLRC os_get_context(void);
|
2020-08-22 21:40:52 +00:00
|
|
|
#elif defined(__ANDROID__)
|
2021-02-25 16:00:12 +00:00
|
|
|
struct ANativeActivity* os_get_activity(void);
|
|
|
|
EGLDisplay os_get_egl_display(void);
|
|
|
|
EGLContext os_get_egl_context(void);
|
|
|
|
EGLConfig os_get_egl_config(void);
|
2020-10-24 18:29:25 +00:00
|
|
|
#elif defined(LOVR_LINUX_X11)
|
2021-02-25 16:00:12 +00:00
|
|
|
Display* os_get_x11_display(void);
|
|
|
|
GLXDrawable os_get_glx_drawable(void);
|
|
|
|
GLXContext os_get_glx_context(void);
|
2020-10-24 18:29:25 +00:00
|
|
|
#elif defined(LOVR_LINUX_EGL)
|
2021-02-25 16:00:12 +00:00
|
|
|
PFNEGLGETPROCADDRESSPROC os_get_egl_proc_addr(void);
|
|
|
|
EGLDisplay os_get_egl_display(void);
|
|
|
|
EGLContext os_get_egl_context(void);
|
|
|
|
EGLConfig os_get_egl_config(void);
|
2020-08-22 21:40:52 +00:00
|
|
|
#endif
|
2019-04-11 20:47:25 +00:00
|
|
|
|
2020-08-28 23:04:45 +00:00
|
|
|
#define XR_FOREACH(X)\
|
|
|
|
X(xrDestroyInstance)\
|
|
|
|
X(xrPollEvent)\
|
|
|
|
X(xrResultToString)\
|
|
|
|
X(xrGetSystem)\
|
|
|
|
X(xrGetSystemProperties)\
|
|
|
|
X(xrCreateSession)\
|
|
|
|
X(xrDestroySession)\
|
|
|
|
X(xrCreateReferenceSpace)\
|
|
|
|
X(xrGetReferenceSpaceBoundsRect)\
|
|
|
|
X(xrCreateActionSpace)\
|
|
|
|
X(xrLocateSpace)\
|
|
|
|
X(xrDestroySpace)\
|
|
|
|
X(xrEnumerateViewConfigurations)\
|
|
|
|
X(xrEnumerateViewConfigurationViews)\
|
|
|
|
X(xrCreateSwapchain)\
|
|
|
|
X(xrDestroySwapchain)\
|
|
|
|
X(xrEnumerateSwapchainImages)\
|
|
|
|
X(xrAcquireSwapchainImage)\
|
|
|
|
X(xrWaitSwapchainImage)\
|
|
|
|
X(xrReleaseSwapchainImage)\
|
|
|
|
X(xrBeginSession)\
|
|
|
|
X(xrEndSession)\
|
|
|
|
X(xrWaitFrame)\
|
|
|
|
X(xrBeginFrame)\
|
|
|
|
X(xrEndFrame)\
|
|
|
|
X(xrLocateViews)\
|
|
|
|
X(xrStringToPath)\
|
|
|
|
X(xrCreateActionSet)\
|
|
|
|
X(xrDestroyActionSet)\
|
|
|
|
X(xrCreateAction)\
|
|
|
|
X(xrDestroyAction)\
|
|
|
|
X(xrSuggestInteractionProfileBindings)\
|
|
|
|
X(xrAttachSessionActionSets)\
|
|
|
|
X(xrGetActionStateBoolean)\
|
|
|
|
X(xrGetActionStateFloat)\
|
|
|
|
X(xrSyncActions)\
|
|
|
|
X(xrApplyHapticFeedback)\
|
|
|
|
X(xrCreateHandTrackerEXT)\
|
|
|
|
X(xrDestroyHandTrackerEXT)\
|
|
|
|
X(xrLocateHandJointsEXT)
|
|
|
|
|
2020-08-28 05:40:08 +00:00
|
|
|
#define XR_DECLARE(fn) static PFN_##fn fn;
|
2020-08-28 23:04:45 +00:00
|
|
|
#define XR_LOAD(fn) xrGetInstanceProcAddr(state.instance, #fn, (PFN_xrVoidFunction*) &fn);
|
|
|
|
XRAPI_ATTR XrResult XRAPI_CALL xrGetInstanceProcAddr(XrInstance instance, const char* name, PFN_xrVoidFunction* function);
|
|
|
|
XRAPI_ATTR XrResult XRAPI_CALL xrEnumerateInstanceExtensionProperties(const char* layerName, uint32_t propertyCapacityInput, uint32_t* propertyCountOutput, XrExtensionProperties* properties);
|
|
|
|
XRAPI_ATTR XrResult XRAPI_CALL xrCreateInstance(const XrInstanceCreateInfo* createInfo, XrInstance* instance);
|
|
|
|
XR_FOREACH(XR_DECLARE)
|
2020-08-28 05:40:08 +00:00
|
|
|
|
2019-04-11 20:47:25 +00:00
|
|
|
static struct {
|
|
|
|
XrInstance instance;
|
|
|
|
XrSystemId system;
|
|
|
|
XrSession session;
|
|
|
|
XrSessionState sessionState;
|
2019-08-04 02:06:46 +00:00
|
|
|
XrSpace referenceSpace;
|
|
|
|
XrReferenceSpaceType referenceSpaceType;
|
|
|
|
XrSpace spaces[MAX_DEVICES];
|
2019-04-11 20:47:25 +00:00
|
|
|
XrSwapchain swapchain;
|
|
|
|
XrCompositionLayerProjection layers[1];
|
|
|
|
XrCompositionLayerProjectionView layerViews[2];
|
2019-08-04 02:06:46 +00:00
|
|
|
XrFrameState frameState;
|
2020-08-31 22:31:17 +00:00
|
|
|
Canvas* canvases[MAX_IMAGES];
|
|
|
|
uint32_t imageIndex;
|
2019-04-11 20:47:25 +00:00
|
|
|
uint32_t imageCount;
|
|
|
|
uint32_t msaa;
|
|
|
|
uint32_t width;
|
|
|
|
uint32_t height;
|
|
|
|
float clipNear;
|
|
|
|
float clipFar;
|
|
|
|
XrActionSet actionSet;
|
|
|
|
XrAction actions[MAX_ACTIONS];
|
|
|
|
XrPath actionFilters[2];
|
2020-08-28 23:04:45 +00:00
|
|
|
XrHandTrackerEXT handTrackers[2];
|
2020-08-28 05:40:08 +00:00
|
|
|
struct {
|
|
|
|
bool handTracking;
|
2020-10-27 00:59:21 +00:00
|
|
|
bool overlay;
|
2020-08-28 05:40:08 +00:00
|
|
|
} features;
|
2019-04-11 20:47:25 +00:00
|
|
|
} state;
|
|
|
|
|
2020-08-22 21:40:52 +00:00
|
|
|
static XrResult handleResult(XrResult result, const char* file, int line) {
|
|
|
|
if (XR_FAILED(result)) {
|
|
|
|
char message[XR_MAX_RESULT_STRING_SIZE];
|
2020-08-26 18:56:33 +00:00
|
|
|
xrResultToString(state.instance, result, message);
|
2020-08-22 21:40:52 +00:00
|
|
|
lovrThrow("OpenXR Error: %s at %s:%d", message, file, line);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2020-08-28 05:40:08 +00:00
|
|
|
static bool hasExtension(XrExtensionProperties* extensions, uint32_t count, const char* extension) {
|
|
|
|
for (uint32_t i = 0; i < count; i++) {
|
|
|
|
if (!strcmp(extensions[i].extensionName, extension)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-04-30 23:59:15 +00:00
|
|
|
static void openxr_destroy();
|
2019-04-11 20:47:25 +00:00
|
|
|
|
2020-11-18 00:38:27 +00:00
|
|
|
static bool openxr_init(float supersample, float offset, uint32_t msaa, bool overlay) {
|
2020-08-26 19:40:31 +00:00
|
|
|
state.msaa = msaa;
|
2019-04-11 20:47:25 +00:00
|
|
|
|
2020-08-30 01:45:52 +00:00
|
|
|
#ifdef __ANDROID__
|
|
|
|
static PFN_xrInitializeLoaderKHR xrInitializeLoaderKHR;
|
|
|
|
XR_LOAD(xrInitializeLoaderKHR);
|
|
|
|
if (!xrInitializeLoaderKHR) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-02-25 16:00:12 +00:00
|
|
|
ANativeActivity* activity = os_get_activity();
|
2020-08-30 01:45:52 +00:00
|
|
|
XrLoaderInitInfoAndroidKHR loaderInfo = {
|
|
|
|
.type = XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR,
|
|
|
|
.applicationVM = activity->vm,
|
|
|
|
.applicationContext = activity->clazz
|
|
|
|
};
|
|
|
|
|
|
|
|
if (XR_FAILED(xrInitializeLoaderKHR((XrLoaderInitInfoBaseHeaderKHR*) &loaderInfo))) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2019-04-11 20:47:25 +00:00
|
|
|
{ // Instance
|
2020-08-28 05:40:08 +00:00
|
|
|
uint32_t extensionCount;
|
|
|
|
xrEnumerateInstanceExtensionProperties(NULL, 0, &extensionCount, NULL);
|
|
|
|
XrExtensionProperties* extensions = calloc(extensionCount, sizeof(*extensions));
|
|
|
|
lovrAssert(extensions, "Out of memory");
|
|
|
|
for (uint32_t i = 0; i < extensionCount; i++) extensions[i].type = XR_TYPE_EXTENSION_PROPERTIES;
|
|
|
|
xrEnumerateInstanceExtensionProperties(NULL, 32, &extensionCount, extensions);
|
|
|
|
|
2020-10-27 00:59:21 +00:00
|
|
|
const char* enabledExtensionNames[5];
|
2020-08-28 05:40:08 +00:00
|
|
|
uint32_t enabledExtensionCount = 0;
|
|
|
|
|
2020-08-30 01:45:52 +00:00
|
|
|
#ifdef __ANDROID__
|
|
|
|
enabledExtensionNames[enabledExtensionCount++] = XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME;
|
|
|
|
#endif
|
|
|
|
|
2020-08-28 05:40:08 +00:00
|
|
|
enabledExtensionNames[enabledExtensionCount++] = GRAPHICS_EXTENSION;
|
|
|
|
|
|
|
|
if (hasExtension(extensions, extensionCount, XR_EXT_HAND_TRACKING_EXTENSION_NAME)) {
|
|
|
|
enabledExtensionNames[enabledExtensionCount++] = XR_EXT_HAND_TRACKING_EXTENSION_NAME;
|
|
|
|
state.features.handTracking = true;
|
|
|
|
}
|
|
|
|
|
2020-10-27 00:59:21 +00:00
|
|
|
#ifdef XR_EXTX_overlay
|
|
|
|
// Provisional extension.
|
|
|
|
if (overlay && hasExtension(extensions, extensionCount, XR_EXTX_OVERLAY_EXTENSION_NAME)) {
|
|
|
|
enabledExtensionNames[enabledExtensionCount++] = XR_EXTX_OVERLAY_EXTENSION_NAME;
|
|
|
|
state.features.overlay = true;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2021-04-16 20:05:32 +00:00
|
|
|
#ifdef LOVR_LINUX_EGL
|
|
|
|
enabledExtensionNames[enabledExtensionCount++] = XR_MNDX_EGL_ENABLE_EXTENSION_NAME;
|
|
|
|
#endif
|
|
|
|
|
2020-08-28 05:40:08 +00:00
|
|
|
free(extensions);
|
|
|
|
|
2019-04-11 20:47:25 +00:00
|
|
|
XrInstanceCreateInfo info = {
|
|
|
|
.type = XR_TYPE_INSTANCE_CREATE_INFO,
|
2020-08-30 01:45:52 +00:00
|
|
|
#ifdef __ANDROID__
|
|
|
|
.next = &(XrInstanceCreateInfoAndroidKHR) {
|
|
|
|
.type = XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR,
|
|
|
|
.applicationVM = activity->vm,
|
|
|
|
.applicationActivity = activity->clazz
|
|
|
|
},
|
|
|
|
#endif
|
2019-04-11 20:47:25 +00:00
|
|
|
.applicationInfo.engineName = "LÖVR",
|
2020-08-28 05:40:08 +00:00
|
|
|
.applicationInfo.engineVersion = (LOVR_VERSION_MAJOR << 24) + (LOVR_VERSION_MINOR << 16) + LOVR_VERSION_PATCH,
|
2019-10-02 23:29:09 +00:00
|
|
|
.applicationInfo.applicationName = "LÖVR",
|
|
|
|
.applicationInfo.applicationVersion = 0,
|
2019-08-04 02:06:46 +00:00
|
|
|
.applicationInfo.apiVersion = XR_CURRENT_API_VERSION,
|
2020-08-28 05:40:08 +00:00
|
|
|
.enabledExtensionCount = enabledExtensionCount,
|
|
|
|
.enabledExtensionNames = enabledExtensionNames
|
2019-04-11 20:47:25 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
XR_INIT(xrCreateInstance(&info, &state.instance));
|
2020-08-28 23:04:45 +00:00
|
|
|
XR_FOREACH(XR_LOAD)
|
2019-04-11 20:47:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
{ // System
|
2019-08-04 02:06:46 +00:00
|
|
|
XrSystemGetInfo info = {
|
|
|
|
.type = XR_TYPE_SYSTEM_GET_INFO,
|
|
|
|
.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY
|
|
|
|
};
|
|
|
|
|
2019-04-11 20:47:25 +00:00
|
|
|
XR_INIT(xrGetSystem(state.instance, &info, &state.system));
|
|
|
|
|
2020-08-28 23:04:45 +00:00
|
|
|
XrSystemHandTrackingPropertiesEXT handTrackingProperties = {
|
|
|
|
.type = XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT,
|
|
|
|
.supportsHandTracking = false
|
|
|
|
};
|
|
|
|
|
|
|
|
XrSystemProperties properties = {
|
|
|
|
.type = XR_TYPE_SYSTEM_PROPERTIES,
|
|
|
|
.next = state.features.handTracking ? &handTrackingProperties : NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
XR_INIT(xrGetSystemProperties(state.instance, state.system, &properties));
|
|
|
|
state.features.handTracking = handTrackingProperties.supportsHandTracking;
|
|
|
|
|
2019-10-02 23:29:09 +00:00
|
|
|
uint32_t viewConfigurationCount;
|
|
|
|
XrViewConfigurationType viewConfigurations[2];
|
|
|
|
XR_INIT(xrEnumerateViewConfigurations(state.instance, state.system, 2, &viewConfigurationCount, viewConfigurations));
|
|
|
|
|
2019-04-11 20:47:25 +00:00
|
|
|
uint32_t viewCount;
|
2019-10-02 23:29:09 +00:00
|
|
|
XrViewConfigurationView views[2] = { [0].type = XR_TYPE_VIEW_CONFIGURATION_VIEW, [1].type = XR_TYPE_VIEW_CONFIGURATION_VIEW };
|
|
|
|
XR_INIT(xrEnumerateViewConfigurationViews(state.instance, state.system, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, 0, &viewCount, NULL));
|
2019-04-11 20:47:25 +00:00
|
|
|
XR_INIT(xrEnumerateViewConfigurationViews(state.instance, state.system, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, 2, &viewCount, views));
|
|
|
|
|
|
|
|
if ( // Only 2 views are supported, and since they're rendered together they must be identical
|
|
|
|
viewCount != 2 ||
|
|
|
|
views[0].recommendedSwapchainSampleCount != views[1].recommendedSwapchainSampleCount ||
|
|
|
|
views[0].recommendedImageRectWidth != views[1].recommendedImageRectWidth ||
|
|
|
|
views[0].recommendedImageRectHeight != views[1].recommendedImageRectHeight
|
|
|
|
) {
|
2019-08-04 02:06:46 +00:00
|
|
|
openxr_destroy();
|
2019-07-11 01:45:36 +00:00
|
|
|
return false;
|
2019-04-11 20:47:25 +00:00
|
|
|
}
|
|
|
|
|
2020-09-25 22:41:30 +00:00
|
|
|
state.width = MIN(views[0].recommendedImageRectWidth * supersample, views[0].maxImageRectWidth);
|
|
|
|
state.height = MIN(views[0].recommendedImageRectHeight * supersample, views[0].maxImageRectHeight);
|
2019-04-11 20:47:25 +00:00
|
|
|
}
|
|
|
|
|
2020-08-22 21:40:52 +00:00
|
|
|
{ // Actions
|
2019-04-11 20:47:25 +00:00
|
|
|
XrActionSetCreateInfo info = {
|
|
|
|
.type = XR_TYPE_ACTION_SET_CREATE_INFO,
|
|
|
|
.actionSetName = "default",
|
|
|
|
.localizedActionSetName = "Default",
|
|
|
|
.priority = 0
|
|
|
|
};
|
|
|
|
|
2019-08-04 02:06:46 +00:00
|
|
|
XR_INIT(xrCreateActionSet(state.instance, &info, &state.actionSet));
|
2019-04-11 20:47:25 +00:00
|
|
|
XR_INIT(xrStringToPath(state.instance, "/user/hand/left", &state.actionFilters[0]));
|
|
|
|
XR_INIT(xrStringToPath(state.instance, "/user/hand/right", &state.actionFilters[1]));
|
|
|
|
|
2020-08-22 21:40:52 +00:00
|
|
|
for (uint32_t i = 0; i < MAX_ACTIONS; i++) {
|
|
|
|
actionCreateInfo[i].subactionPaths = state.actionFilters;
|
|
|
|
XR_INIT(xrCreateAction(state.actionSet, &actionCreateInfo[i], &state.actions[i]));
|
2019-04-11 20:47:25 +00:00
|
|
|
}
|
|
|
|
|
2020-08-22 21:40:52 +00:00
|
|
|
XrActionSuggestedBinding suggestedBindings[2 * MAX_ACTIONS];
|
|
|
|
for (uint32_t profile = 0, count = 0; profile < MAX_PROFILES; profile++, count = 0) {
|
|
|
|
for (uint32_t action = 0; action < MAX_ACTIONS; action++) {
|
|
|
|
for (uint32_t hand = 0; hand < 2; hand++) {
|
|
|
|
if (bindings[profile][action][hand]) {
|
|
|
|
suggestedBindings[count].action = state.actions[action];
|
|
|
|
XR_INIT(xrStringToPath(state.instance, bindings[profile][action][hand], &suggestedBindings[count].binding));
|
|
|
|
count++;
|
2019-04-11 20:47:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
XrPath profilePath;
|
2020-08-22 21:40:52 +00:00
|
|
|
XR_INIT(xrStringToPath(state.instance, interactionProfiles[profile], &profilePath));
|
2019-08-04 02:06:46 +00:00
|
|
|
XR_INIT(xrSuggestInteractionProfileBindings(state.instance, &(XrInteractionProfileSuggestedBinding) {
|
2019-04-11 20:47:25 +00:00
|
|
|
.type = XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING,
|
|
|
|
.interactionProfile = profilePath,
|
2020-08-22 21:40:52 +00:00
|
|
|
.countSuggestedBindings = count,
|
|
|
|
.suggestedBindings = suggestedBindings
|
2019-04-11 20:47:25 +00:00
|
|
|
}));
|
|
|
|
}
|
2019-08-04 02:06:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
{ // Session
|
2020-08-26 18:57:52 +00:00
|
|
|
#if defined(LOVR_GL)
|
2020-08-30 01:45:52 +00:00
|
|
|
XrGraphicsRequirementsOpenGLKHR requirements = { .type = XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR, NULL };
|
2020-08-28 23:04:45 +00:00
|
|
|
PFN_xrGetOpenGLGraphicsRequirementsKHR xrGetOpenGLGraphicsRequirementsKHR;
|
|
|
|
XR_LOAD(xrGetOpenGLGraphicsRequirementsKHR);
|
2020-08-26 18:57:52 +00:00
|
|
|
XR_INIT(xrGetOpenGLGraphicsRequirementsKHR(state.instance, state.system, &requirements));
|
|
|
|
// TODO validate OpenGL versions
|
|
|
|
#elif defined(LOVR_GLES)
|
2020-08-30 01:45:52 +00:00
|
|
|
XrGraphicsRequirementsOpenGLESKHR requirements = { .type = XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_ES_KHR, NULL };
|
2020-08-28 23:04:45 +00:00
|
|
|
PFN_xrGetOpenGLESGraphicsRequirementsKHR xrGetOpenGLESGraphicsRequirementsKHR;
|
|
|
|
XR_LOAD(xrGetOpenGLESGraphicsRequirementsKHR);
|
2020-08-26 18:57:52 +00:00
|
|
|
XR_INIT(xrGetOpenGLESGraphicsRequirementsKHR(state.instance, state.system, &requirements));
|
|
|
|
// TODO validate OpenGLES versions
|
|
|
|
#endif
|
|
|
|
|
2020-08-24 09:09:26 +00:00
|
|
|
#if defined(_WIN32) && defined(LOVR_GL)
|
2020-08-26 18:57:52 +00:00
|
|
|
XrGraphicsBindingOpenGLWin32KHR graphicsBinding = {
|
|
|
|
.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR,
|
2021-02-25 16:00:12 +00:00
|
|
|
.hDC = os_get_win32_window(),
|
|
|
|
.hGLRC = os_get_context()
|
2020-08-26 18:57:52 +00:00
|
|
|
};
|
2020-08-24 09:09:26 +00:00
|
|
|
#elif defined(__ANDROID__) && defined(LOVR_GLES)
|
2020-08-26 18:57:52 +00:00
|
|
|
XrGraphicsBindingOpenGLESAndroidKHR graphicsBinding = {
|
|
|
|
.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_ES_ANDROID_KHR,
|
2021-02-25 16:00:12 +00:00
|
|
|
.display = os_get_egl_display(),
|
|
|
|
.config = os_get_egl_config(),
|
|
|
|
.context = os_get_egl_context()
|
2020-08-26 18:57:52 +00:00
|
|
|
};
|
2020-10-24 18:29:25 +00:00
|
|
|
#elif defined(LOVR_LINUX_X11)
|
|
|
|
XrGraphicsBindingOpenGLXlibKHR graphicsBinding = {
|
|
|
|
.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR,
|
|
|
|
.next = NULL,
|
2021-02-25 16:00:12 +00:00
|
|
|
.xDisplay = os_get_x11_display(),
|
2020-10-24 18:29:25 +00:00
|
|
|
.visualid = 0,
|
|
|
|
.glxFBConfig = 0,
|
2021-02-25 16:00:12 +00:00
|
|
|
.glxDrawable = os_get_glx_drawable(),
|
|
|
|
.glxContext = os_get_glx_context(),
|
2020-10-24 18:29:25 +00:00
|
|
|
};
|
|
|
|
#elif defined(LOVR_LINUX_EGL)
|
|
|
|
XrGraphicsBindingEGLMNDX graphicsBinding = {
|
|
|
|
.type = XR_TYPE_GRAPHICS_BINDING_EGL_MNDX,
|
|
|
|
.next = NULL,
|
2021-02-25 16:00:12 +00:00
|
|
|
.getProcAddress = os_get_egl_proc_addr(),
|
|
|
|
.display = os_get_egl_display(),
|
|
|
|
.config = os_get_egl_config(),
|
|
|
|
.context = os_get_egl_context(),
|
2020-10-24 18:29:25 +00:00
|
|
|
};
|
2019-10-02 23:29:09 +00:00
|
|
|
#else
|
2020-08-24 09:09:26 +00:00
|
|
|
#error "Unsupported OpenXR platform/graphics combination"
|
2019-10-02 23:29:09 +00:00
|
|
|
#endif
|
2020-08-26 18:57:52 +00:00
|
|
|
|
|
|
|
XrSessionCreateInfo info = {
|
|
|
|
.type = XR_TYPE_SESSION_CREATE_INFO,
|
|
|
|
.next = &graphicsBinding,
|
2019-08-04 02:06:46 +00:00
|
|
|
.systemId = state.system
|
|
|
|
};
|
|
|
|
|
2020-10-27 00:59:21 +00:00
|
|
|
#ifdef XR_EXTX_overlay
|
|
|
|
// Provisional extension.
|
|
|
|
XrSessionCreateInfoOverlayEXTX overlayInfo = {
|
|
|
|
.type = XR_TYPE_SESSION_CREATE_INFO_OVERLAY_EXTX,
|
|
|
|
.next = info.next,
|
|
|
|
// Fields may fluctuate, leave as auto initialised for now.
|
|
|
|
};
|
|
|
|
|
|
|
|
if (state.features.overlay) {
|
|
|
|
info.next = &overlayInfo;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2019-08-04 02:06:46 +00:00
|
|
|
XrSessionActionSetsAttachInfo attachInfo = {
|
|
|
|
.type = XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO,
|
|
|
|
.countActionSets = 1,
|
|
|
|
.actionSets = &state.actionSet
|
|
|
|
};
|
|
|
|
|
|
|
|
XR_INIT(xrCreateSession(state.instance, &info, &state.session));
|
|
|
|
XR_INIT(xrAttachSessionActionSets(state.session, &attachInfo));
|
|
|
|
}
|
|
|
|
|
|
|
|
{ // Spaaaaace
|
|
|
|
|
|
|
|
// Main reference space (can be stage or local)
|
|
|
|
XrReferenceSpaceCreateInfo info = {
|
|
|
|
.type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO,
|
2020-08-26 18:59:02 +00:00
|
|
|
.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE,
|
|
|
|
.poseInReferenceSpace = { .orientation = { 0.f, 0.f, 0.f, 1.f }, .position = { 0.f, 0.f, 0.f } }
|
2019-08-04 02:06:46 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
if (XR_FAILED(xrCreateReferenceSpace(state.session, &info, &state.referenceSpace))) {
|
|
|
|
info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
|
|
|
|
info.poseInReferenceSpace.position.y = -offset;
|
|
|
|
XR_INIT(xrCreateReferenceSpace(state.session, &info, &state.referenceSpace));
|
|
|
|
}
|
|
|
|
|
|
|
|
state.referenceSpaceType = info.referenceSpaceType;
|
2019-04-11 20:47:25 +00:00
|
|
|
|
2019-08-04 02:06:46 +00:00
|
|
|
// Head space (for head pose)
|
2019-04-11 20:47:25 +00:00
|
|
|
XrReferenceSpaceCreateInfo headSpaceInfo = {
|
|
|
|
.type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO,
|
2020-08-26 18:59:02 +00:00
|
|
|
.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW,
|
|
|
|
.poseInReferenceSpace = { .orientation = { 0.f, 0.f, 0.f, 1.f }, .position = { 0.f, 0.f, 0.f } }
|
2019-04-11 20:47:25 +00:00
|
|
|
};
|
|
|
|
|
2019-08-04 02:06:46 +00:00
|
|
|
XR_INIT(xrCreateReferenceSpace(state.session, &headSpaceInfo, &state.spaces[DEVICE_HEAD]));
|
|
|
|
|
|
|
|
// Left hand space
|
2019-04-11 20:47:25 +00:00
|
|
|
XrActionSpaceCreateInfo leftHandSpaceInfo = {
|
|
|
|
.type = XR_TYPE_ACTION_SPACE_CREATE_INFO,
|
2019-08-04 02:06:46 +00:00
|
|
|
.action = state.actions[ACTION_HAND_POSE],
|
2020-10-24 17:13:41 +00:00
|
|
|
.subactionPath = state.actionFilters[0],
|
|
|
|
.poseInActionSpace = { .orientation = { 0.f, 0.f, 0.f, 1.f }, .position = { 0.f, 0.f, 0.f } }
|
2019-04-11 20:47:25 +00:00
|
|
|
};
|
|
|
|
|
2019-08-04 02:06:46 +00:00
|
|
|
XR_INIT(xrCreateActionSpace(state.session, &leftHandSpaceInfo, &state.spaces[DEVICE_HAND_LEFT]));
|
|
|
|
|
|
|
|
// Right hand space
|
2019-04-11 20:47:25 +00:00
|
|
|
XrActionSpaceCreateInfo rightHandSpaceInfo = {
|
|
|
|
.type = XR_TYPE_ACTION_SPACE_CREATE_INFO,
|
2019-08-04 02:06:46 +00:00
|
|
|
.action = state.actions[ACTION_HAND_POSE],
|
2020-10-24 17:13:41 +00:00
|
|
|
.subactionPath = state.actionFilters[1],
|
|
|
|
.poseInActionSpace = { .orientation = { 0.f, 0.f, 0.f, 1.f }, .position = { 0.f, 0.f, 0.f } }
|
2019-04-11 20:47:25 +00:00
|
|
|
};
|
|
|
|
|
2019-08-04 02:06:46 +00:00
|
|
|
XR_INIT(xrCreateActionSpace(state.session, &rightHandSpaceInfo, &state.spaces[DEVICE_HAND_RIGHT]));
|
|
|
|
}
|
|
|
|
|
|
|
|
{ // Swapchain
|
2020-08-30 01:45:52 +00:00
|
|
|
#if defined(XR_USE_GRAPHICS_API_OPENGL)
|
|
|
|
TextureType textureType = TEXTURE_2D;
|
|
|
|
uint32_t width = state.width * 2;
|
|
|
|
uint32_t arraySize = 1;
|
|
|
|
XrSwapchainImageOpenGLKHR images[MAX_IMAGES];
|
|
|
|
for (uint32_t i = 0; i < MAX_IMAGES; i++) {
|
|
|
|
images[i].type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR;
|
|
|
|
images[i].next = NULL;
|
|
|
|
}
|
|
|
|
#elif defined(XR_USE_GRAPHICS_API_OPENGL_ES)
|
|
|
|
TextureType textureType = TEXTURE_ARRAY;
|
|
|
|
uint32_t width = state.width;
|
|
|
|
uint32_t arraySize = 2;
|
|
|
|
XrSwapchainImageOpenGLESKHR images[MAX_IMAGES];
|
|
|
|
for (uint32_t i = 0; i < MAX_IMAGES; i++) {
|
|
|
|
images[i].type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR;
|
|
|
|
images[i].next = NULL;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2019-08-04 02:06:46 +00:00
|
|
|
XrSwapchainCreateInfo info = {
|
|
|
|
.type = XR_TYPE_SWAPCHAIN_CREATE_INFO,
|
|
|
|
.usageFlags = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_SAMPLED_BIT,
|
|
|
|
.format = GL_SRGB8_ALPHA8,
|
2020-08-30 01:45:52 +00:00
|
|
|
.width = width,
|
2019-08-04 02:06:46 +00:00
|
|
|
.height = state.height,
|
2020-08-26 19:40:31 +00:00
|
|
|
.sampleCount = 1,
|
2019-08-04 02:06:46 +00:00
|
|
|
.faceCount = 1,
|
2020-08-30 01:45:52 +00:00
|
|
|
.arraySize = arraySize,
|
2019-08-04 02:06:46 +00:00
|
|
|
.mipCount = 1
|
|
|
|
};
|
|
|
|
|
|
|
|
XR_INIT(xrCreateSwapchain(state.session, &info, &state.swapchain));
|
|
|
|
XR_INIT(xrEnumerateSwapchainImages(state.swapchain, MAX_IMAGES, &state.imageCount, (XrSwapchainImageBaseHeader*) images));
|
|
|
|
|
2020-08-31 22:31:17 +00:00
|
|
|
CanvasFlags flags = { .depth = { true, false, FORMAT_D24S8 }, .stereo = true, .mipmaps = false, .msaa = state.msaa };
|
2019-08-04 02:06:46 +00:00
|
|
|
for (uint32_t i = 0; i < state.imageCount; i++) {
|
2020-08-31 22:31:17 +00:00
|
|
|
Texture* texture = lovrTextureCreateFromHandle(images[i].image, textureType, arraySize, state.msaa);
|
|
|
|
state.canvases[i] = lovrCanvasCreate(state.width, state.height, flags);
|
|
|
|
lovrCanvasSetAttachments(state.canvases[i], &(Attachment) { texture, 0, 0 }, 1);
|
2021-02-09 00:52:26 +00:00
|
|
|
lovrRelease(texture, lovrTextureDestroy);
|
2019-08-04 02:06:46 +00:00
|
|
|
}
|
|
|
|
|
2020-10-27 00:59:21 +00:00
|
|
|
XrCompositionLayerFlags layerFlags = 0;
|
|
|
|
|
|
|
|
if (state.features.overlay) {
|
|
|
|
layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT |
|
|
|
|
XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT;
|
|
|
|
}
|
|
|
|
|
2019-08-04 02:06:46 +00:00
|
|
|
// Pre-init composition layer
|
|
|
|
state.layers[0] = (XrCompositionLayerProjection) {
|
|
|
|
.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION,
|
|
|
|
.space = state.referenceSpace,
|
2020-10-27 00:59:21 +00:00
|
|
|
.layerFlags = layerFlags,
|
2019-08-04 02:06:46 +00:00
|
|
|
.viewCount = 2,
|
|
|
|
.views = state.layerViews
|
|
|
|
};
|
|
|
|
|
|
|
|
// Pre-init composition layer views
|
|
|
|
state.layerViews[0] = (XrCompositionLayerProjectionView) {
|
|
|
|
.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW,
|
|
|
|
.subImage = { state.swapchain, { { 0, 0 }, { state.width, state.height } }, 0 }
|
|
|
|
};
|
|
|
|
|
2020-08-30 01:45:52 +00:00
|
|
|
// Copy the left view to the right view and offset either the viewport or array index
|
2019-08-04 02:06:46 +00:00
|
|
|
state.layerViews[1] = state.layerViews[0];
|
2020-08-30 01:45:52 +00:00
|
|
|
#if defined(XR_USE_GRAPHICS_API_OPENGL)
|
2019-08-04 02:06:46 +00:00
|
|
|
state.layerViews[1].subImage.imageRect.offset.x += state.width;
|
2020-08-30 01:45:52 +00:00
|
|
|
#elif defined(XR_USE_GRAPHICS_API_OPENGL_ES)
|
|
|
|
state.layerViews[1].subImage.imageArrayIndex = 1;
|
|
|
|
#endif
|
2019-04-11 20:47:25 +00:00
|
|
|
}
|
|
|
|
|
2020-01-23 19:25:38 +00:00
|
|
|
state.clipNear = .1f;
|
2020-08-26 18:59:32 +00:00
|
|
|
state.clipFar = 100.f;
|
2020-01-23 19:25:38 +00:00
|
|
|
|
2020-08-30 01:45:52 +00:00
|
|
|
state.frameState.type = XR_TYPE_FRAME_STATE;
|
2021-02-25 16:00:12 +00:00
|
|
|
os_window_set_vsync(0);
|
2020-08-30 01:45:52 +00:00
|
|
|
|
2019-04-11 20:47:25 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-08-26 19:10:42 +00:00
|
|
|
static void openxr_destroy(void) {
|
2019-04-11 20:47:25 +00:00
|
|
|
for (uint32_t i = 0; i < state.imageCount; i++) {
|
2021-02-09 00:52:26 +00:00
|
|
|
lovrRelease(state.canvases[i], lovrCanvasDestroy);
|
2019-04-11 20:47:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for (size_t i = 0; i < MAX_ACTIONS; i++) {
|
2019-10-02 23:29:09 +00:00
|
|
|
if (state.actions[i]) {
|
|
|
|
xrDestroyAction(state.actions[i]);
|
|
|
|
}
|
2019-04-11 20:47:25 +00:00
|
|
|
}
|
|
|
|
|
2019-08-04 02:06:46 +00:00
|
|
|
for (size_t i = 0; i < MAX_DEVICES; i++) {
|
|
|
|
if (state.spaces[i]) {
|
|
|
|
xrDestroySpace(state.spaces[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-28 23:04:45 +00:00
|
|
|
if (state.handTrackers[0]) xrDestroyHandTrackerEXT(state.handTrackers[0]);
|
|
|
|
if (state.handTrackers[1]) xrDestroyHandTrackerEXT(state.handTrackers[1]);
|
2019-10-02 23:29:09 +00:00
|
|
|
if (state.actionSet) xrDestroyActionSet(state.actionSet);
|
|
|
|
if (state.swapchain) xrDestroySwapchain(state.swapchain);
|
|
|
|
if (state.referenceSpace) xrDestroySpace(state.referenceSpace);
|
|
|
|
if (state.session) xrEndSession(state.session);
|
|
|
|
if (state.instance) xrDestroyInstance(state.instance);
|
2019-08-04 02:06:46 +00:00
|
|
|
memset(&state, 0, sizeof(state));
|
2019-04-11 20:47:25 +00:00
|
|
|
}
|
|
|
|
|
2019-04-30 23:59:15 +00:00
|
|
|
static bool openxr_getName(char* name, size_t length) {
|
2021-01-22 14:43:01 +00:00
|
|
|
XrSystemProperties properties = {
|
|
|
|
.type = XR_TYPE_SYSTEM_PROPERTIES
|
|
|
|
};
|
2019-04-11 20:47:25 +00:00
|
|
|
XR(xrGetSystemProperties(state.instance, state.system, &properties));
|
|
|
|
strncpy(name, properties.systemName, length - 1);
|
|
|
|
name[length - 1] = '\0';
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-08-26 19:10:42 +00:00
|
|
|
static HeadsetOrigin openxr_getOriginType(void) {
|
2019-08-04 02:06:46 +00:00
|
|
|
return state.referenceSpaceType == XR_REFERENCE_SPACE_TYPE_STAGE ? ORIGIN_FLOOR : ORIGIN_HEAD;
|
2019-04-11 20:47:25 +00:00
|
|
|
}
|
|
|
|
|
2019-04-30 23:59:15 +00:00
|
|
|
static void openxr_getDisplayDimensions(uint32_t* width, uint32_t* height) {
|
2019-04-11 20:47:25 +00:00
|
|
|
*width = state.width;
|
|
|
|
*height = state.height;
|
|
|
|
}
|
|
|
|
|
2019-06-28 07:13:30 +00:00
|
|
|
static const float* openxr_getDisplayMask(uint32_t* count) {
|
2019-07-11 01:45:36 +00:00
|
|
|
*count = 0;
|
|
|
|
return NULL;
|
2019-06-28 07:13:30 +00:00
|
|
|
}
|
|
|
|
|
2019-04-30 23:59:15 +00:00
|
|
|
static double openxr_getDisplayTime(void) {
|
2019-08-04 02:06:46 +00:00
|
|
|
return state.frameState.predictedDisplayTime / 1e9;
|
2019-04-30 22:31:38 +00:00
|
|
|
}
|
|
|
|
|
2020-01-28 05:02:37 +00:00
|
|
|
static void getViews(XrView views[2], uint32_t* count) {
|
|
|
|
XrViewLocateInfo viewLocateInfo = {
|
|
|
|
.type = XR_TYPE_VIEW_LOCATE_INFO,
|
|
|
|
.viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
|
|
|
|
.displayTime = state.frameState.predictedDisplayTime,
|
|
|
|
.space = state.referenceSpace
|
|
|
|
};
|
|
|
|
|
2021-10-24 20:03:05 +00:00
|
|
|
for (uint32_t i = 0; i < 2; i++) {
|
|
|
|
views[i].type = XR_TYPE_VIEW;
|
|
|
|
views[i].next = NULL;
|
|
|
|
}
|
|
|
|
|
2020-08-30 01:45:52 +00:00
|
|
|
XrViewState viewState = { .type = XR_TYPE_VIEW_STATE };
|
2021-10-24 20:03:05 +00:00
|
|
|
XR(xrLocateViews(state.session, &viewLocateInfo, &viewState, 2, count, views));
|
2020-01-28 05:02:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static uint32_t openxr_getViewCount(void) {
|
|
|
|
uint32_t count;
|
|
|
|
XrView views[2];
|
|
|
|
getViews(views, &count);
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool openxr_getViewPose(uint32_t view, float* position, float* orientation) {
|
|
|
|
uint32_t count;
|
|
|
|
XrView views[2];
|
|
|
|
getViews(views, &count);
|
|
|
|
if (view < count) {
|
2020-08-19 03:09:06 +00:00
|
|
|
memcpy(position, &views[view].pose.position.x, 3 * sizeof(float));
|
|
|
|
memcpy(orientation, &views[view].pose.orientation.x, 4 * sizeof(float));
|
2020-01-28 05:02:37 +00:00
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool openxr_getViewAngles(uint32_t view, float* left, float* right, float* up, float* down) {
|
|
|
|
uint32_t count;
|
|
|
|
XrView views[2];
|
|
|
|
getViews(views, &count);
|
|
|
|
if (view < count) {
|
|
|
|
*left = views[view].fov.angleLeft;
|
|
|
|
*right = views[view].fov.angleRight;
|
|
|
|
*up = views[view].fov.angleUp;
|
|
|
|
*down = views[view].fov.angleDown;
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-30 23:59:15 +00:00
|
|
|
static void openxr_getClipDistance(float* clipNear, float* clipFar) {
|
2019-04-11 20:47:25 +00:00
|
|
|
*clipNear = state.clipNear;
|
|
|
|
*clipFar = state.clipFar;
|
|
|
|
}
|
|
|
|
|
2019-04-30 23:59:15 +00:00
|
|
|
static void openxr_setClipDistance(float clipNear, float clipFar) {
|
2019-04-11 20:47:25 +00:00
|
|
|
state.clipNear = clipNear;
|
|
|
|
state.clipFar = clipFar;
|
|
|
|
}
|
|
|
|
|
2019-04-30 23:59:15 +00:00
|
|
|
static void openxr_getBoundsDimensions(float* width, float* depth) {
|
2019-04-11 20:47:25 +00:00
|
|
|
XrExtent2Df bounds;
|
2019-08-04 02:06:46 +00:00
|
|
|
if (XR_SUCCEEDED(xrGetReferenceSpaceBoundsRect(state.session, state.referenceSpaceType, &bounds))) {
|
|
|
|
*width = bounds.width;
|
|
|
|
*depth = bounds.height;
|
|
|
|
} else {
|
|
|
|
*width = 0.f;
|
|
|
|
*depth = 0.f;
|
|
|
|
}
|
2019-04-11 20:47:25 +00:00
|
|
|
}
|
|
|
|
|
2019-05-21 03:35:07 +00:00
|
|
|
static const float* openxr_getBoundsGeometry(uint32_t* count) {
|
2019-04-11 20:47:25 +00:00
|
|
|
*count = 0;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2019-05-08 03:04:59 +00:00
|
|
|
static bool openxr_getPose(Device device, vec3 position, quat orientation) {
|
2019-08-04 02:06:46 +00:00
|
|
|
if (!state.spaces[device]) {
|
|
|
|
return false;
|
2019-04-11 20:47:25 +00:00
|
|
|
}
|
|
|
|
|
2020-10-24 17:13:41 +00:00
|
|
|
XrSpaceLocation location = { .type = XR_TYPE_SPACE_LOCATION, .next = NULL };
|
2020-08-26 19:10:42 +00:00
|
|
|
xrLocateSpace(state.spaces[device], state.referenceSpace, state.frameState.predictedDisplayTime, &location);
|
2019-08-04 02:06:46 +00:00
|
|
|
memcpy(orientation, &location.pose.orientation, 4 * sizeof(float));
|
|
|
|
memcpy(position, &location.pose.position, 3 * sizeof(float));
|
|
|
|
return location.locationFlags & (XR_SPACE_LOCATION_POSITION_VALID_BIT | XR_SPACE_LOCATION_ORIENTATION_VALID_BIT);
|
2019-04-11 20:47:25 +00:00
|
|
|
}
|
|
|
|
|
2019-08-04 02:06:46 +00:00
|
|
|
static bool openxr_getVelocity(Device device, vec3 linearVelocity, vec3 angularVelocity) {
|
|
|
|
if (!state.spaces[device]) {
|
|
|
|
return false;
|
2019-04-11 20:47:25 +00:00
|
|
|
}
|
|
|
|
|
2019-08-04 02:06:46 +00:00
|
|
|
XrSpaceVelocity velocity = { .type = XR_TYPE_SPACE_VELOCITY };
|
2020-10-24 17:13:41 +00:00
|
|
|
XrSpaceLocation location = { .type = XR_TYPE_SPACE_LOCATION, .next = &velocity };
|
2020-08-26 19:10:42 +00:00
|
|
|
xrLocateSpace(state.spaces[device], state.referenceSpace, state.frameState.predictedDisplayTime, &location);
|
2019-08-04 02:06:46 +00:00
|
|
|
memcpy(linearVelocity, &velocity.linearVelocity, 3 * sizeof(float));
|
|
|
|
memcpy(angularVelocity, &velocity.angularVelocity, 3 * sizeof(float));
|
|
|
|
return velocity.velocityFlags & (XR_SPACE_VELOCITY_LINEAR_VALID_BIT | XR_SPACE_VELOCITY_ANGULAR_VALID_BIT);
|
2019-04-11 20:47:25 +00:00
|
|
|
}
|
|
|
|
|
2019-05-08 03:04:59 +00:00
|
|
|
static XrPath getActionFilter(Device device) {
|
|
|
|
switch (device) {
|
|
|
|
case DEVICE_HAND_LEFT: return state.actionFilters[0];
|
|
|
|
case DEVICE_HAND_RIGHT: return state.actionFilters[1];
|
|
|
|
default: return XR_NULL_PATH;
|
2019-04-11 20:47:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-19 03:09:06 +00:00
|
|
|
static bool getButtonState(Device device, DeviceButton button, bool* value, bool* changed, bool touch) {
|
2019-08-04 02:06:46 +00:00
|
|
|
XrActionStateGetInfo info = {
|
|
|
|
.type = XR_TYPE_ACTION_STATE_GET_INFO,
|
|
|
|
.subactionPath = getActionFilter(device)
|
|
|
|
};
|
2019-04-11 20:47:25 +00:00
|
|
|
|
2019-08-04 02:06:46 +00:00
|
|
|
if (info.subactionPath == XR_NULL_PATH) {
|
2019-05-08 03:04:59 +00:00
|
|
|
return false;
|
|
|
|
}
|
2019-04-11 20:47:25 +00:00
|
|
|
|
2019-05-08 03:04:59 +00:00
|
|
|
switch (button) {
|
2019-08-04 02:06:46 +00:00
|
|
|
case BUTTON_TRIGGER: info.action = state.actions[ACTION_TRIGGER_DOWN + touch]; break;
|
|
|
|
case BUTTON_TOUCHPAD: info.action = state.actions[ACTION_TRACKPAD_DOWN + touch]; break;
|
|
|
|
case BUTTON_MENU: info.action = state.actions[ACTION_MENU_DOWN + touch]; break;
|
|
|
|
case BUTTON_GRIP: info.action = state.actions[ACTION_GRIP_DOWN + touch]; break;
|
2019-05-08 03:04:59 +00:00
|
|
|
default: return false;
|
2019-04-11 20:47:25 +00:00
|
|
|
}
|
|
|
|
|
2020-10-26 08:30:08 +00:00
|
|
|
XrActionStateBoolean actionState = { .type = XR_TYPE_ACTION_STATE_BOOLEAN };
|
2019-08-04 02:06:46 +00:00
|
|
|
XR(xrGetActionStateBoolean(state.session, &info, &actionState));
|
2019-05-08 03:04:59 +00:00
|
|
|
*value = actionState.currentState;
|
2020-01-25 06:46:42 +00:00
|
|
|
*changed = actionState.changedSinceLastSync;
|
2019-05-08 03:04:59 +00:00
|
|
|
return actionState.isActive;
|
2019-04-11 20:47:25 +00:00
|
|
|
}
|
|
|
|
|
2020-01-25 06:46:42 +00:00
|
|
|
static bool openxr_isDown(Device device, DeviceButton button, bool* down, bool* changed) {
|
|
|
|
return getButtonState(device, button, down, changed, false);
|
2019-04-11 20:47:25 +00:00
|
|
|
}
|
|
|
|
|
2019-05-08 03:04:59 +00:00
|
|
|
static bool openxr_isTouched(Device device, DeviceButton button, bool* touched) {
|
2020-01-25 06:46:42 +00:00
|
|
|
bool unused;
|
|
|
|
return getButtonState(device, button, touched, &unused, true);
|
2019-04-11 20:47:25 +00:00
|
|
|
}
|
|
|
|
|
2020-08-28 23:04:45 +00:00
|
|
|
static bool getFloatAction(uint32_t action, XrPath filter, float* value) {
|
2019-08-04 02:06:46 +00:00
|
|
|
XrActionStateGetInfo info = {
|
|
|
|
.type = XR_TYPE_ACTION_STATE_GET_INFO,
|
2020-08-23 22:11:20 +00:00
|
|
|
.action = state.actions[action],
|
|
|
|
.subactionPath = filter
|
2019-08-04 02:06:46 +00:00
|
|
|
};
|
2019-05-08 03:04:59 +00:00
|
|
|
|
2020-10-26 08:30:08 +00:00
|
|
|
XrActionStateFloat actionState = { .type = XR_TYPE_ACTION_STATE_FLOAT };
|
2020-08-23 22:11:20 +00:00
|
|
|
XR(xrGetActionStateFloat(state.session, &info, &actionState));
|
|
|
|
*value = actionState.currentState;
|
|
|
|
return actionState.isActive;
|
|
|
|
}
|
2019-04-11 20:47:25 +00:00
|
|
|
|
2020-08-23 22:11:20 +00:00
|
|
|
static bool openxr_getAxis(Device device, DeviceAxis axis, float* value) {
|
|
|
|
XrPath filter = getActionFilter(device);
|
|
|
|
|
|
|
|
if (filter == XR_NULL_PATH) {
|
|
|
|
return false;
|
2019-05-08 03:04:59 +00:00
|
|
|
}
|
|
|
|
|
2019-08-04 02:06:46 +00:00
|
|
|
switch (axis) {
|
2020-08-28 23:04:45 +00:00
|
|
|
case AXIS_TRIGGER: return getFloatAction(ACTION_TRIGGER_AXIS, filter, &value[0]);
|
|
|
|
case AXIS_THUMBSTICK: return getFloatAction(ACTION_THUMBSTICK_X, filter, &value[0]) && getFloatAction(ACTION_THUMBSTICK_Y, filter, &value[1]);
|
|
|
|
case AXIS_TOUCHPAD: return getFloatAction(ACTION_TRACKPAD_X, filter, &value[0]) && getFloatAction(ACTION_TRACKPAD_Y, filter, &value[1]);
|
|
|
|
case AXIS_GRIP: return getFloatAction(ACTION_GRIP_AXIS, filter, &value[0]);
|
2019-08-04 02:06:46 +00:00
|
|
|
default: return false;
|
|
|
|
}
|
2019-04-11 20:47:25 +00:00
|
|
|
}
|
|
|
|
|
2020-08-28 23:04:45 +00:00
|
|
|
static bool openxr_getSkeleton(Device device, float* poses) {
|
|
|
|
if (device != DEVICE_HAND_LEFT && device != DEVICE_HAND_RIGHT) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-11-18 19:52:52 +00:00
|
|
|
if (!state.features.handTracking) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-08-29 02:58:58 +00:00
|
|
|
XrHandTrackerEXT* handTracker = &state.handTrackers[device - DEVICE_HAND_LEFT];
|
|
|
|
|
|
|
|
// Hand trackers are created lazily because on some implementations xrCreateHandTrackerEXT will
|
|
|
|
// return XR_ERROR_FEATURE_UNSUPPORTED if called too early.
|
|
|
|
if (!*handTracker) {
|
|
|
|
XrHandTrackerCreateInfoEXT info = {
|
|
|
|
.type = XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT,
|
|
|
|
.handJointSet = XR_HAND_JOINT_SET_DEFAULT_EXT,
|
|
|
|
.hand = device == DEVICE_HAND_LEFT ? XR_HAND_LEFT_EXT : XR_HAND_RIGHT_EXT
|
|
|
|
};
|
|
|
|
|
|
|
|
if (XR_FAILED(xrCreateHandTrackerEXT(state.session, &info, handTracker))) {
|
|
|
|
return false;
|
|
|
|
}
|
2020-08-28 23:04:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
XrHandJointsLocateInfoEXT info = {
|
|
|
|
.type = XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT,
|
|
|
|
.baseSpace = state.referenceSpace,
|
|
|
|
.time = state.frameState.predictedDisplayTime
|
|
|
|
};
|
|
|
|
|
|
|
|
XrHandJointLocationEXT joints[26];
|
|
|
|
XrHandJointLocationsEXT hand = {
|
|
|
|
.type = XR_TYPE_HAND_JOINT_LOCATIONS_EXT,
|
|
|
|
.jointCount = sizeof(joints) / sizeof(joints[0]),
|
|
|
|
.jointLocations = joints
|
|
|
|
};
|
|
|
|
|
2020-08-29 02:58:58 +00:00
|
|
|
if (XR_FAILED(xrLocateHandJointsEXT(*handTracker, &info, &hand)) || !hand.isActive) {
|
2020-08-28 23:04:45 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
float* pose = poses;
|
|
|
|
for (uint32_t i = 0; i < sizeof(joints) / sizeof(joints[0]); i++) {
|
|
|
|
memcpy(pose, &joints[i].pose.position.x, 3 * sizeof(float));
|
|
|
|
pose[3] = joints[i].radius;
|
|
|
|
memcpy(pose + 4, &joints[i].pose.orientation.x, 4 * sizeof(float));
|
|
|
|
pose += 8;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-05-08 03:04:59 +00:00
|
|
|
static bool openxr_vibrate(Device device, float power, float duration, float frequency) {
|
2019-08-04 02:06:46 +00:00
|
|
|
XrHapticActionInfo info = {
|
|
|
|
.type = XR_TYPE_HAPTIC_ACTION_INFO,
|
|
|
|
.action = state.actions[ACTION_VIBRATE],
|
|
|
|
.subactionPath = getActionFilter(device)
|
|
|
|
};
|
2019-04-11 20:47:25 +00:00
|
|
|
|
2019-08-04 02:06:46 +00:00
|
|
|
if (info.subactionPath == XR_NULL_PATH) {
|
2019-04-11 20:47:25 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
XrHapticVibration vibration = {
|
|
|
|
.type = XR_TYPE_HAPTIC_VIBRATION,
|
2019-08-04 02:06:46 +00:00
|
|
|
.duration = (XrDuration) (duration * 1e9f + .5f),
|
2019-04-11 20:47:25 +00:00
|
|
|
.frequency = frequency,
|
|
|
|
.amplitude = power
|
|
|
|
};
|
|
|
|
|
2019-08-04 02:06:46 +00:00
|
|
|
XR(xrApplyHapticFeedback(state.session, &info, (XrHapticBaseHeader*) &vibration));
|
2019-04-11 20:47:25 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-08-02 23:25:51 +00:00
|
|
|
static struct ModelData* openxr_newModelData(Device device, bool animated) {
|
2019-04-11 20:47:25 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2020-08-02 23:25:51 +00:00
|
|
|
static bool openxr_animate(Device device, struct Model* model) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-04-30 23:59:15 +00:00
|
|
|
static void openxr_renderTo(void (*callback)(void*), void* userdata) {
|
2020-08-26 19:01:18 +00:00
|
|
|
if (!SESSION_ACTIVE(state.sessionState)) { return; }
|
2019-04-11 20:47:25 +00:00
|
|
|
|
2020-08-26 19:01:18 +00:00
|
|
|
XrFrameBeginInfo beginInfo = {
|
|
|
|
.type = XR_TYPE_FRAME_BEGIN_INFO
|
|
|
|
};
|
|
|
|
|
|
|
|
XrFrameEndInfo endInfo = {
|
|
|
|
.type = XR_TYPE_FRAME_END_INFO,
|
|
|
|
.displayTime = state.frameState.predictedDisplayTime,
|
2020-08-30 01:45:52 +00:00
|
|
|
.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE,
|
|
|
|
.layers = (const XrCompositionLayerBaseHeader*[1]) { (XrCompositionLayerBaseHeader*) &state.layers[0] }
|
2020-08-26 19:01:18 +00:00
|
|
|
};
|
2019-04-11 20:47:25 +00:00
|
|
|
|
|
|
|
XR(xrBeginFrame(state.session, &beginInfo));
|
|
|
|
|
2019-08-04 02:06:46 +00:00
|
|
|
if (state.frameState.shouldRender) {
|
2020-08-31 22:31:17 +00:00
|
|
|
XR(xrAcquireSwapchainImage(state.swapchain, NULL, &state.imageIndex));
|
2019-04-11 20:47:25 +00:00
|
|
|
XrSwapchainImageWaitInfo waitInfo = { XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO, .timeout = 1e9 };
|
|
|
|
|
|
|
|
if (XR(xrWaitSwapchainImage(state.swapchain, &waitInfo)) != XR_TIMEOUT_EXPIRED) {
|
2020-01-28 05:02:37 +00:00
|
|
|
uint32_t count;
|
|
|
|
XrView views[2];
|
|
|
|
getViews(views, &count);
|
|
|
|
|
2019-04-11 20:47:25 +00:00
|
|
|
for (int eye = 0; eye < 2; eye++) {
|
2020-09-25 02:03:37 +00:00
|
|
|
float viewMatrix[16];
|
2019-04-11 20:47:25 +00:00
|
|
|
XrView* view = &views[eye];
|
2020-09-25 02:03:37 +00:00
|
|
|
mat4_fromQuat(viewMatrix, &view->pose.orientation.x);
|
2020-10-12 22:39:56 +00:00
|
|
|
memcpy(viewMatrix + 12, &view->pose.position.x, 3 * sizeof(float));
|
2020-09-25 02:03:37 +00:00
|
|
|
mat4_invert(viewMatrix);
|
|
|
|
lovrGraphicsSetViewMatrix(eye, viewMatrix);
|
|
|
|
|
|
|
|
float projection[16];
|
2019-04-11 20:47:25 +00:00
|
|
|
XrFovf* fov = &view->fov;
|
2020-09-25 02:03:37 +00:00
|
|
|
mat4_fov(projection, -fov->angleLeft, fov->angleRight, fov->angleUp, -fov->angleDown, state.clipNear, state.clipFar);
|
|
|
|
lovrGraphicsSetProjection(eye, projection);
|
2019-04-11 20:47:25 +00:00
|
|
|
}
|
|
|
|
|
2020-09-25 02:03:37 +00:00
|
|
|
lovrGraphicsSetBackbuffer(state.canvases[state.imageIndex], true, true);
|
2019-04-11 20:47:25 +00:00
|
|
|
callback(userdata);
|
2020-09-25 02:03:37 +00:00
|
|
|
lovrGraphicsSetBackbuffer(NULL, false, false);
|
2019-04-11 20:47:25 +00:00
|
|
|
|
2020-08-26 19:10:42 +00:00
|
|
|
endInfo.layerCount = 1;
|
2019-04-11 20:47:25 +00:00
|
|
|
state.layerViews[0].pose = views[0].pose;
|
|
|
|
state.layerViews[0].fov = views[0].fov;
|
|
|
|
state.layerViews[1].pose = views[1].pose;
|
|
|
|
state.layerViews[1].fov = views[1].fov;
|
|
|
|
}
|
|
|
|
|
|
|
|
XR(xrReleaseSwapchainImage(state.swapchain, NULL));
|
|
|
|
}
|
|
|
|
|
|
|
|
XR(xrEndFrame(state.session, &endInfo));
|
2020-08-26 19:01:18 +00:00
|
|
|
lovrGpuDirtyTexture();
|
2019-04-11 20:47:25 +00:00
|
|
|
}
|
|
|
|
|
2020-08-26 19:01:18 +00:00
|
|
|
static Texture* openxr_getMirrorTexture(void) {
|
2020-08-31 22:31:17 +00:00
|
|
|
Canvas* canvas = state.canvases[state.imageIndex];
|
|
|
|
return canvas ? lovrCanvasGetAttachments(canvas, NULL)[0].texture : NULL;
|
2020-08-26 19:01:18 +00:00
|
|
|
}
|
2019-04-11 20:47:25 +00:00
|
|
|
|
2020-08-26 19:01:18 +00:00
|
|
|
static void openxr_update(float dt) {
|
|
|
|
XrEventDataBuffer e; // Not using designated initializers here to avoid an implicit 4k zero
|
2019-04-11 20:47:25 +00:00
|
|
|
e.type = XR_TYPE_EVENT_DATA_BUFFER;
|
|
|
|
e.next = NULL;
|
|
|
|
|
2020-08-26 19:01:18 +00:00
|
|
|
while (xrPollEvent(state.instance, &e) == XR_SUCCESS) {
|
2019-04-11 20:47:25 +00:00
|
|
|
switch (e.type) {
|
|
|
|
case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: {
|
|
|
|
XrEventDataSessionStateChanged* event = (XrEventDataSessionStateChanged*) &e;
|
|
|
|
|
|
|
|
switch (event->state) {
|
2019-05-26 03:15:42 +00:00
|
|
|
case XR_SESSION_STATE_READY:
|
2019-04-11 20:47:25 +00:00
|
|
|
XR(xrBeginSession(state.session, &(XrSessionBeginInfo) {
|
|
|
|
.type = XR_TYPE_SESSION_BEGIN_INFO,
|
|
|
|
.primaryViewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO
|
|
|
|
}));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XR_SESSION_STATE_STOPPING:
|
|
|
|
XR(xrEndSession(state.session));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case XR_SESSION_STATE_EXITING:
|
|
|
|
case XR_SESSION_STATE_LOSS_PENDING:
|
2020-08-19 03:09:06 +00:00
|
|
|
lovrEventPush((Event) { .type = EVENT_QUIT, .data.quit.exitCode = 0 });
|
2019-04-11 20:47:25 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
default: break;
|
|
|
|
}
|
2020-08-24 08:10:12 +00:00
|
|
|
|
|
|
|
bool wasFocused = state.sessionState == XR_SESSION_STATE_FOCUSED;
|
|
|
|
bool isFocused = event->state == XR_SESSION_STATE_FOCUSED;
|
|
|
|
if (wasFocused != isFocused) {
|
2020-08-30 01:45:52 +00:00
|
|
|
lovrEventPush((Event) { .type = EVENT_FOCUS, .data.boolean.value = isFocused });
|
2020-08-24 08:10:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
state.sessionState = event->state;
|
2019-04-11 20:47:25 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
}
|
2020-08-26 19:01:18 +00:00
|
|
|
|
|
|
|
if (SESSION_ACTIVE(state.sessionState)) {
|
|
|
|
XR(xrWaitFrame(state.session, NULL, &state.frameState));
|
|
|
|
|
|
|
|
XrActionsSyncInfo syncInfo = {
|
|
|
|
.type = XR_TYPE_ACTIONS_SYNC_INFO,
|
2020-10-24 22:57:39 +00:00
|
|
|
.countActiveActionSets = 2,
|
2020-08-26 19:01:18 +00:00
|
|
|
.activeActionSets = (XrActiveActionSet[]) {
|
|
|
|
{ state.actionSet, state.actionFilters[0] },
|
|
|
|
{ state.actionSet, state.actionFilters[1] }
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
XR(xrSyncActions(state.session, &syncInfo));
|
|
|
|
}
|
2019-04-11 20:47:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
HeadsetInterface lovrHeadsetOpenXRDriver = {
|
|
|
|
.driverType = DRIVER_OPENXR,
|
2019-04-30 23:59:15 +00:00
|
|
|
.init = openxr_init,
|
|
|
|
.destroy = openxr_destroy,
|
|
|
|
.getName = openxr_getName,
|
|
|
|
.getOriginType = openxr_getOriginType,
|
|
|
|
.getDisplayDimensions = openxr_getDisplayDimensions,
|
2019-06-28 07:13:30 +00:00
|
|
|
.getDisplayMask = openxr_getDisplayMask,
|
2019-04-30 23:59:15 +00:00
|
|
|
.getDisplayTime = openxr_getDisplayTime,
|
2020-01-28 05:02:37 +00:00
|
|
|
.getViewCount = openxr_getViewCount,
|
|
|
|
.getViewPose = openxr_getViewPose,
|
|
|
|
.getViewAngles = openxr_getViewAngles,
|
2019-04-30 23:59:15 +00:00
|
|
|
.getClipDistance = openxr_getClipDistance,
|
|
|
|
.setClipDistance = openxr_setClipDistance,
|
|
|
|
.getBoundsDimensions = openxr_getBoundsDimensions,
|
|
|
|
.getBoundsGeometry = openxr_getBoundsGeometry,
|
|
|
|
.getPose = openxr_getPose,
|
|
|
|
.getVelocity = openxr_getVelocity,
|
|
|
|
.isDown = openxr_isDown,
|
|
|
|
.isTouched = openxr_isTouched,
|
|
|
|
.getAxis = openxr_getAxis,
|
2020-08-28 23:04:45 +00:00
|
|
|
.getSkeleton = openxr_getSkeleton,
|
2019-04-30 23:59:15 +00:00
|
|
|
.vibrate = openxr_vibrate,
|
|
|
|
.newModelData = openxr_newModelData,
|
2020-08-19 03:09:06 +00:00
|
|
|
.animate = openxr_animate,
|
2019-04-30 23:59:15 +00:00
|
|
|
.renderTo = openxr_renderTo,
|
2020-08-26 19:01:18 +00:00
|
|
|
.getMirrorTexture = openxr_getMirrorTexture,
|
2019-04-30 23:59:15 +00:00
|
|
|
.update = openxr_update
|
2019-04-11 20:47:25 +00:00
|
|
|
};
|