2019-04-11 20:47:25 +00:00
# include "headset/headset.h"
2022-03-20 00:49:13 +00:00
# include "data/blob.h"
2022-06-06 03:38:14 +00:00
# include "data/image.h"
# include "data/modelData.h"
2019-04-11 20:47:25 +00:00
# include "event/event.h"
2022-06-06 03:38:14 +00:00
# include "graphics/graphics.h"
# include "core/maf.h"
2021-06-12 20:42:30 +00:00
# include "core/os.h"
2022-03-22 07:13:21 +00:00
# include "util.h"
2022-04-24 20:05:52 +00:00
# include <assert.h>
2020-08-19 03:09:06 +00:00
# include <stdlib.h>
2022-06-06 03:38:14 +00:00
# include <string.h>
2019-04-11 20:47:25 +00:00
# include <math.h>
2022-03-20 22:39:02 +00:00
2020-08-19 03:09:06 +00:00
# if defined(_WIN32)
2023-06-26 23:41:42 +00:00
# define XR_USE_PLATFORM_WIN32
# define WIN32_LEAN_AND_MEAN
# include <unknwn.h>
# include <windows.h>
# define XR_FOREACH_PLATFORM(X) X(xrConvertWin32PerformanceCounterToTimeKHR)
# else
# if defined(__ANDROID__)
# define XR_USE_PLATFORM_ANDROID
void * os_get_java_vm ( void ) ;
void * os_get_jni_context ( void ) ;
# include <jni.h>
# endif
# include <time.h>
# define XR_USE_TIMESPEC
# define XR_FOREACH_PLATFORM(X) X(xrConvertTimespecTimeToTimeKHR)
2020-08-24 08:04:06 +00:00
# endif
2022-03-20 22:39:02 +00:00
2022-06-06 03:38:14 +00:00
# ifdef LOVR_VK
# define XR_USE_GRAPHICS_API_VULKAN
uintptr_t gpu_vk_get_instance ( void ) ;
uintptr_t gpu_vk_get_physical_device ( void ) ;
uintptr_t gpu_vk_get_device ( void ) ;
uintptr_t gpu_vk_get_queue ( uint32_t * queueFamilyIndex , uint32_t * queueIndex ) ;
# include <vulkan/vulkan.h>
2020-10-24 18:29:25 +00:00
# endif
2022-03-20 22:39:02 +00:00
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>
2023-03-08 04:16:59 +00:00
# define XR(f, s) xrthrow(f, s)
# define XR_INIT(f, s) if (!xrwarn(f, s)) 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)
2019-04-11 20:47:25 +00:00
# define MAX_IMAGES 4
2023-02-02 02:36:05 +00:00
# define MAX_HAND_JOINTS 27
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 ) \
2022-06-06 03:38:14 +00:00
X ( xrCreateVulkanInstanceKHR ) \
X ( xrGetVulkanGraphicsDevice2KHR ) \
X ( xrCreateVulkanDeviceKHR ) \
2020-08-28 23:04:45 +00:00
X ( xrCreateSession ) \
X ( xrDestroySession ) \
2023-06-26 23:41:42 +00:00
X ( xrEnumerateReferenceSpaces ) \
2020-08-28 23:04:45 +00:00
X ( xrCreateReferenceSpace ) \
X ( xrGetReferenceSpaceBoundsRect ) \
X ( xrCreateActionSpace ) \
X ( xrLocateSpace ) \
X ( xrDestroySpace ) \
X ( xrEnumerateViewConfigurations ) \
X ( xrEnumerateViewConfigurationViews ) \
2023-03-31 02:51:17 +00:00
X ( xrEnumerateEnvironmentBlendModes ) \
2022-08-07 05:52:18 +00:00
X ( xrEnumerateSwapchainFormats ) \
2020-08-28 23:04:45 +00:00
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 ) \
2022-03-20 00:49:13 +00:00
X ( xrGetActionStatePose ) \
2020-08-28 23:04:45 +00:00
X ( xrSyncActions ) \
X ( xrApplyHapticFeedback ) \
2023-06-28 03:45:44 +00:00
X ( xrStopHapticFeedback ) \
2020-08-28 23:04:45 +00:00
X ( xrCreateHandTrackerEXT ) \
X ( xrDestroyHandTrackerEXT ) \
2022-03-20 00:49:13 +00:00
X ( xrLocateHandJointsEXT ) \
2022-03-21 07:51:24 +00:00
X ( xrGetHandMeshFB ) \
2022-11-16 17:19:57 +00:00
X ( xrGetControllerModelKeyMSFT ) \
X ( xrLoadControllerModelMSFT ) \
X ( xrGetControllerModelPropertiesMSFT ) \
X ( xrGetControllerModelStateMSFT ) \
X ( xrGetDisplayRefreshRateFB ) \
X ( xrEnumerateDisplayRefreshRatesFB ) \
X ( xrRequestDisplayRefreshRateFB ) \
X ( xrQuerySystemTrackedKeyboardFB ) \
2023-02-06 03:51:12 +00:00
X ( xrCreateKeyboardSpaceFB ) \
X ( xrCreatePassthroughFB ) \
X ( xrDestroyPassthroughFB ) \
X ( xrPassthroughStartFB ) \
X ( xrPassthroughPauseFB ) \
X ( xrCreatePassthroughLayerFB ) \
X ( xrDestroyPassthroughLayerFB )
2020-08-28 23:04:45 +00:00
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 )
2023-06-26 23:41:42 +00:00
XR_FOREACH_PLATFORM ( XR_DECLARE )
2020-08-28 05:40:08 +00:00
2022-03-20 22:39:02 +00:00
enum {
ACTION_HAND_POSE ,
ACTION_POINTER_POSE ,
2022-03-21 01:04:06 +00:00
ACTION_TRACKER_POSE ,
2022-03-21 22:05:23 +00:00
ACTION_GAZE_POSE ,
2022-03-20 22:39:02 +00:00
ACTION_TRIGGER_DOWN ,
ACTION_TRIGGER_TOUCH ,
ACTION_TRIGGER_AXIS ,
ACTION_TRACKPAD_DOWN ,
ACTION_TRACKPAD_TOUCH ,
ACTION_TRACKPAD_X ,
ACTION_TRACKPAD_Y ,
ACTION_THUMBSTICK_DOWN ,
ACTION_THUMBSTICK_TOUCH ,
ACTION_THUMBSTICK_X ,
ACTION_THUMBSTICK_Y ,
ACTION_MENU_DOWN ,
ACTION_MENU_TOUCH ,
ACTION_GRIP_DOWN ,
ACTION_GRIP_TOUCH ,
ACTION_GRIP_AXIS ,
ACTION_A_DOWN ,
ACTION_A_TOUCH ,
ACTION_B_DOWN ,
ACTION_B_TOUCH ,
ACTION_X_DOWN ,
ACTION_X_TOUCH ,
ACTION_Y_DOWN ,
ACTION_Y_TOUCH ,
ACTION_THUMBREST_TOUCH ,
ACTION_VIBRATE ,
MAX_ACTIONS
} ;
2022-08-07 05:52:18 +00:00
enum { COLOR , DEPTH } ;
2019-04-11 20:47:25 +00:00
static struct {
2022-08-03 05:00:11 +00:00
HeadsetConfig config ;
2019-04-11 20:47:25 +00:00
XrInstance instance ;
XrSystemId system ;
XrSession session ;
XrSessionState sessionState ;
2019-08-04 02:06:46 +00:00
XrSpace referenceSpace ;
2023-05-12 17:01:42 +00:00
float * refreshRates ;
uint32_t refreshRateCount ;
2023-05-12 13:13:48 +00:00
XrEnvironmentBlendMode * blendModes ;
2023-03-31 02:39:50 +00:00
XrEnvironmentBlendMode blendMode ;
2023-05-12 13:13:48 +00:00
uint32_t blendModeCount ;
2019-08-04 02:06:46 +00:00
XrSpace spaces [ MAX_DEVICES ] ;
2022-08-07 05:52:18 +00:00
XrSwapchain swapchain [ 2 ] ;
2019-04-11 20:47:25 +00:00
XrCompositionLayerProjection layers [ 1 ] ;
XrCompositionLayerProjectionView layerViews [ 2 ] ;
2022-08-07 05:52:18 +00:00
XrCompositionLayerDepthInfoKHR depthInfo [ 2 ] ;
2023-02-06 03:51:12 +00:00
XrCompositionLayerPassthroughFB passthroughLayer ;
2019-08-04 02:06:46 +00:00
XrFrameState frameState ;
2023-05-02 02:05:57 +00:00
XrTime lastDisplayTime ;
2023-05-02 02:08:25 +00:00
XrTime epoch ;
2022-09-21 02:09:04 +00:00
TextureFormat depthFormat ;
2022-08-07 05:52:18 +00:00
Texture * textures [ 2 ] [ MAX_IMAGES ] ;
2022-08-03 05:00:11 +00:00
Pass * pass ;
2022-08-07 05:52:18 +00:00
uint32_t textureIndex [ 2 ] ;
uint32_t textureCount [ 2 ] ;
2019-04-11 20:47:25 +00:00
uint32_t width ;
uint32_t height ;
float clipNear ;
float clipFar ;
2022-03-30 20:32:28 +00:00
bool waited ;
2022-08-03 05:00:11 +00:00
bool began ;
2019-04-11 20:47:25 +00:00
XrActionSet actionSet ;
XrAction actions [ MAX_ACTIONS ] ;
2022-03-21 01:04:06 +00:00
XrPath actionFilters [ MAX_DEVICES ] ;
2020-08-28 23:04:45 +00:00
XrHandTrackerEXT handTrackers [ 2 ] ;
2022-11-16 17:19:57 +00:00
XrControllerModelKeyMSFT controllerModelKeys [ 2 ] ;
2023-02-06 03:51:12 +00:00
XrPassthroughFB passthrough ;
XrPassthroughLayerFB passthroughLayerHandle ;
bool passthroughActive ;
2020-08-28 05:40:08 +00:00
struct {
2023-02-02 03:26:18 +00:00
bool controllerModel ;
2022-08-07 05:52:18 +00:00
bool depth ;
2022-03-21 22:05:23 +00:00
bool gaze ;
2020-08-28 05:40:08 +00:00
bool handTracking ;
2022-03-21 22:45:48 +00:00
bool handTrackingAim ;
2023-02-02 02:36:05 +00:00
bool handTrackingElbow ;
2022-03-20 00:49:13 +00:00
bool handTrackingMesh ;
2022-11-26 22:40:39 +00:00
bool headless ;
2022-09-21 03:16:58 +00:00
bool keyboardTracking ;
2023-05-27 00:03:11 +00:00
bool ml2Controller ;
2023-06-26 23:41:42 +00:00
bool localFloor ;
2020-10-27 00:59:21 +00:00
bool overlay ;
2023-05-12 13:13:48 +00:00
bool questPassthrough ;
2023-05-12 09:47:47 +00:00
bool picoController ;
2022-03-21 07:51:24 +00:00
bool refreshRate ;
2022-03-21 01:04:06 +00:00
bool viveTrackers ;
2020-08-28 05:40:08 +00:00
} features ;
2019-04-11 20:47:25 +00:00
} state ;
2023-03-08 04:16:59 +00:00
static bool xrwarn ( XrResult result , const char * message ) {
if ( XR_SUCCEEDED ( result ) ) return true ;
char errorCode [ XR_MAX_RESULT_STRING_SIZE ] ;
if ( state . instance & & XR_SUCCEEDED ( xrResultToString ( state . instance , result , errorCode ) ) ) {
lovrLog ( LOG_WARN , " XR " , " OpenXR failed to start: %s (%s) " , message , errorCode ) ;
} else {
lovrLog ( LOG_WARN , " XR " , " OpenXR failed to start: %s (%d) " , message , result ) ;
2020-08-22 21:40:52 +00:00
}
2023-03-08 04:16:59 +00:00
return false ;
}
static bool xrthrow ( XrResult result , const char * message ) {
if ( XR_SUCCEEDED ( result ) ) return true ;
char errorCode [ XR_MAX_RESULT_STRING_SIZE ] ;
if ( state . instance & & XR_SUCCEEDED ( xrResultToString ( state . instance , result , errorCode ) ) ) {
lovrThrow ( " OpenXR Error: %s (%s) " , message , errorCode ) ;
} else {
lovrThrow ( " OpenXR Error: %s (%d) " , message , result ) ;
}
return false ;
2020-08-22 21:40:52 +00:00
}
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 ;
}
2023-06-26 23:41:42 +00:00
static XrTime getCurrentXrTime ( void ) {
XrTime time ;
# ifdef _WIN32
LARGE_INTEGER t ;
QueryPerformanceCounter ( & t ) ;
XR ( xrConvertWin32PerformanceCounterToTimeKHR ( state . instance , & t , & time ) , " Failed to get time " ) ;
# else
struct timespec t ;
clock_gettime ( CLOCK_MONOTONIC , & t ) ;
XR ( xrConvertTimespecTimeToTimeKHR ( state . instance , & t , & time ) , " Failed to get time " ) ;
# endif
return time ;
}
static void createReferenceSpace ( void ) {
XrReferenceSpaceCreateInfo info = {
. type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO ,
. poseInReferenceSpace = { { 0.f , 0.f , 0.f , 1.f } , { 0.f , 0.f , 0.f } }
} ;
Replace HeadsetOrigin with 'seated' flag;
Origin type used to be a query-able property of the VR system that
indicated whether the tracking was roomscale or seated-scale.
The t.headset.offset config value could be used to design an
origin-agnostic experience, which by default shifted content up 1.7
meters when tracking was seated-scale. That way, stuff rendered at
y=1.7m was always at "eye level". It worked pretty well.
It's getting replaced with a t.headset.seated flag.
- If seated is false (the default), the origin of the coordinate space
will be on the floor, enabling the y=1.7m eye level paradigm. If
tracking is not roomscale, a floor offset of 1.7m will be emulated.
- If seated is true, the origin of the coordinate space will be y=0
at eye level (where the headset was when the app started). This is
the case on both roomscale and seated-scale tracking.
So basically 'seated' is an opt-in preference for where the app wants
its vertical origin to be.
One advantage of this is that it's possible to consistently get a y=0
eye level coordinate space, which was not possible before. This makes
it easier to design simpler experiences that only need to render a
floating UI and don't want to render a full environment or deal with
offsetting everything relative to a 'floor'. This also makes it easier
to implement hybrid VR+flatscreen experiences, because the camera is at
y=0 when the headset module is disabled.
The opt-in nature of the flag, coupled with the fact that it is
consistent across all types of tracking and hardware, is hopefully a
more useful design.
2023-06-28 23:38:36 +00:00
// Reference space doesn't need to be recreated for seated experiences (those always use local
// space), or when local-floor is supported. Otherwise, vertical offset must be re-measured.
if ( state . referenceSpace & & ( state . features . localFloor | | state . config . seated ) ) {
return ;
}
2023-06-26 23:41:42 +00:00
if ( state . features . localFloor ) {
Replace HeadsetOrigin with 'seated' flag;
Origin type used to be a query-able property of the VR system that
indicated whether the tracking was roomscale or seated-scale.
The t.headset.offset config value could be used to design an
origin-agnostic experience, which by default shifted content up 1.7
meters when tracking was seated-scale. That way, stuff rendered at
y=1.7m was always at "eye level". It worked pretty well.
It's getting replaced with a t.headset.seated flag.
- If seated is false (the default), the origin of the coordinate space
will be on the floor, enabling the y=1.7m eye level paradigm. If
tracking is not roomscale, a floor offset of 1.7m will be emulated.
- If seated is true, the origin of the coordinate space will be y=0
at eye level (where the headset was when the app started). This is
the case on both roomscale and seated-scale tracking.
So basically 'seated' is an opt-in preference for where the app wants
its vertical origin to be.
One advantage of this is that it's possible to consistently get a y=0
eye level coordinate space, which was not possible before. This makes
it easier to design simpler experiences that only need to render a
floating UI and don't want to render a full environment or deal with
offsetting everything relative to a 'floor'. This also makes it easier
to implement hybrid VR+flatscreen experiences, because the camera is at
y=0 when the headset module is disabled.
The opt-in nature of the flag, coupled with the fact that it is
consistent across all types of tracking and hardware, is hopefully a
more useful design.
2023-06-28 23:38:36 +00:00
info . referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT ;
} else if ( state . config . seated ) {
info . referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL ;
2023-06-28 03:23:44 +00:00
} else if ( state . spaces [ DEVICE_FLOOR ] ) {
2023-06-26 23:41:42 +00:00
XrSpace local ;
info . referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL ;
XR ( xrCreateReferenceSpace ( state . session , & info , & local ) , " Failed to create local space " ) ;
XrSpaceLocation location = { . type = XR_TYPE_SPACE_LOCATION } ;
2023-06-28 03:23:44 +00:00
XR ( xrLocateSpace ( state . spaces [ DEVICE_FLOOR ] , local , getCurrentXrTime ( ) , & location ) , " Failed to locate space " ) ;
2023-06-26 23:41:42 +00:00
XR ( xrDestroySpace ( local ) , " Failed to destroy local space " ) ;
if ( location . locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT ) {
info . poseInReferenceSpace . position . y = location . pose . position . y ;
} else {
Replace HeadsetOrigin with 'seated' flag;
Origin type used to be a query-able property of the VR system that
indicated whether the tracking was roomscale or seated-scale.
The t.headset.offset config value could be used to design an
origin-agnostic experience, which by default shifted content up 1.7
meters when tracking was seated-scale. That way, stuff rendered at
y=1.7m was always at "eye level". It worked pretty well.
It's getting replaced with a t.headset.seated flag.
- If seated is false (the default), the origin of the coordinate space
will be on the floor, enabling the y=1.7m eye level paradigm. If
tracking is not roomscale, a floor offset of 1.7m will be emulated.
- If seated is true, the origin of the coordinate space will be y=0
at eye level (where the headset was when the app started). This is
the case on both roomscale and seated-scale tracking.
So basically 'seated' is an opt-in preference for where the app wants
its vertical origin to be.
One advantage of this is that it's possible to consistently get a y=0
eye level coordinate space, which was not possible before. This makes
it easier to design simpler experiences that only need to render a
floating UI and don't want to render a full environment or deal with
offsetting everything relative to a 'floor'. This also makes it easier
to implement hybrid VR+flatscreen experiences, because the camera is at
y=0 when the headset module is disabled.
The opt-in nature of the flag, coupled with the fact that it is
consistent across all types of tracking and hardware, is hopefully a
more useful design.
2023-06-28 23:38:36 +00:00
info . poseInReferenceSpace . position . y = - 1.7f ;
2023-06-26 23:41:42 +00:00
}
} else {
info . referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL ;
Replace HeadsetOrigin with 'seated' flag;
Origin type used to be a query-able property of the VR system that
indicated whether the tracking was roomscale or seated-scale.
The t.headset.offset config value could be used to design an
origin-agnostic experience, which by default shifted content up 1.7
meters when tracking was seated-scale. That way, stuff rendered at
y=1.7m was always at "eye level". It worked pretty well.
It's getting replaced with a t.headset.seated flag.
- If seated is false (the default), the origin of the coordinate space
will be on the floor, enabling the y=1.7m eye level paradigm. If
tracking is not roomscale, a floor offset of 1.7m will be emulated.
- If seated is true, the origin of the coordinate space will be y=0
at eye level (where the headset was when the app started). This is
the case on both roomscale and seated-scale tracking.
So basically 'seated' is an opt-in preference for where the app wants
its vertical origin to be.
One advantage of this is that it's possible to consistently get a y=0
eye level coordinate space, which was not possible before. This makes
it easier to design simpler experiences that only need to render a
floating UI and don't want to render a full environment or deal with
offsetting everything relative to a 'floor'. This also makes it easier
to implement hybrid VR+flatscreen experiences, because the camera is at
y=0 when the headset module is disabled.
The opt-in nature of the flag, coupled with the fact that it is
consistent across all types of tracking and hardware, is hopefully a
more useful design.
2023-06-28 23:38:36 +00:00
info . poseInReferenceSpace . position . y = - 1.7f ;
2023-06-26 23:41:42 +00:00
}
2023-06-28 03:14:28 +00:00
if ( state . referenceSpace ) {
XR ( xrDestroySpace ( state . referenceSpace ) , " Failed to destroy reference space " ) ;
}
2023-06-26 23:41:42 +00:00
XR ( xrCreateReferenceSpace ( state . session , & info , & state . referenceSpace ) , " Failed to create reference space " ) ;
}
2022-03-21 01:04:06 +00:00
static XrAction getPoseActionForDevice ( Device device ) {
switch ( device ) {
case DEVICE_HEAD :
return XR_NULL_HANDLE ; // Uses reference space
case DEVICE_HAND_LEFT :
case DEVICE_HAND_RIGHT :
return state . actions [ ACTION_HAND_POSE ] ;
case DEVICE_HAND_LEFT_POINT :
case DEVICE_HAND_RIGHT_POINT :
return state . actions [ ACTION_POINTER_POSE ] ;
case DEVICE_ELBOW_LEFT :
case DEVICE_ELBOW_RIGHT :
case DEVICE_SHOULDER_LEFT :
case DEVICE_SHOULDER_RIGHT :
case DEVICE_CHEST :
case DEVICE_WAIST :
case DEVICE_KNEE_LEFT :
case DEVICE_KNEE_RIGHT :
case DEVICE_FOOT_LEFT :
case DEVICE_FOOT_RIGHT :
case DEVICE_CAMERA :
case DEVICE_KEYBOARD :
2022-09-21 21:56:06 +00:00
return state . features . viveTrackers ? state . actions [ ACTION_TRACKER_POSE ] : XR_NULL_HANDLE ;
2022-03-21 22:05:23 +00:00
case DEVICE_EYE_GAZE :
return state . actions [ ACTION_GAZE_POSE ] ;
2022-03-21 01:04:06 +00:00
default :
return XR_NULL_HANDLE ;
}
}
2022-03-20 00:49:13 +00:00
// Hand trackers are created lazily because on some implementations xrCreateHandTrackerEXT will
// return XR_ERROR_FEATURE_UNSUPPORTED if called too early.
static XrHandTrackerEXT getHandTracker ( Device device ) {
if ( ! state . features . handTracking | | ( device ! = DEVICE_HAND_LEFT & & device ! = DEVICE_HAND_RIGHT ) ) {
return XR_NULL_HANDLE ;
}
XrHandTrackerEXT * tracker = & state . handTrackers [ device = = DEVICE_HAND_RIGHT ] ;
if ( ! * tracker ) {
XrHandTrackerCreateInfoEXT info = {
. type = XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT ,
2023-02-02 02:36:05 +00:00
. handJointSet = state . features . handTrackingElbow ?
XR_HAND_JOINT_SET_HAND_WITH_FOREARM_ULTRALEAP :
XR_HAND_JOINT_SET_DEFAULT_EXT ,
2022-03-20 00:49:13 +00:00
. hand = device = = DEVICE_HAND_RIGHT ? XR_HAND_RIGHT_EXT : XR_HAND_LEFT_EXT
} ;
if ( XR_FAILED ( xrCreateHandTrackerEXT ( state . session , & info , tracker ) ) ) {
return XR_NULL_HANDLE ;
}
}
return * tracker ;
}
2022-11-16 17:19:57 +00:00
// Controller model keys are created lazily because the runtime is allowed to
// return XR_NULL_CONTROLLER_MODEL_KEY_MSFT until it is ready.
static XrControllerModelKeyMSFT getControllerModelKey ( Device device ) {
if ( ! state . features . controllerModel | | ( device ! = DEVICE_HAND_LEFT & & device ! = DEVICE_HAND_RIGHT ) ) {
return XR_NULL_CONTROLLER_MODEL_KEY_MSFT ;
}
XrControllerModelKeyMSFT * modelKey = & state . controllerModelKeys [ device = = DEVICE_HAND_RIGHT ] ;
if ( ! * modelKey ) {
XrControllerModelKeyStateMSFT modelKeyState = {
. type = XR_TYPE_CONTROLLER_MODEL_KEY_STATE_MSFT ,
} ;
if ( XR_FAILED ( xrGetControllerModelKeyMSFT ( state . session , state . actionFilters [ device ] , & modelKeyState ) ) ) {
return XR_NULL_CONTROLLER_MODEL_KEY_MSFT ;
}
* modelKey = modelKeyState . modelKey ;
}
return * modelKey ;
}
2022-06-06 03:38:14 +00:00
static void openxr_getVulkanPhysicalDevice ( void * instance , uintptr_t physicalDevice ) {
XrVulkanGraphicsDeviceGetInfoKHR info = {
. type = XR_TYPE_VULKAN_GRAPHICS_DEVICE_GET_INFO_KHR ,
. systemId = state . system ,
. vulkanInstance = ( VkInstance ) instance
} ;
2023-03-08 04:16:59 +00:00
XR ( xrGetVulkanGraphicsDevice2KHR ( state . instance , & info , ( VkPhysicalDevice * ) physicalDevice ) , " Failed to get Vulkan graphics device " ) ;
2022-06-06 03:38:14 +00:00
}
static uint32_t openxr_createVulkanInstance ( void * instanceCreateInfo , void * allocator , uintptr_t instance , void * getInstanceProcAddr ) {
XrVulkanInstanceCreateInfoKHR info = {
. type = XR_TYPE_VULKAN_INSTANCE_CREATE_INFO_KHR ,
. systemId = state . system ,
. pfnGetInstanceProcAddr = ( PFN_vkGetInstanceProcAddr ) getInstanceProcAddr ,
. vulkanCreateInfo = instanceCreateInfo ,
. vulkanAllocator = allocator
} ;
VkResult result ;
2023-03-08 04:16:59 +00:00
XR ( xrCreateVulkanInstanceKHR ( state . instance , & info , ( VkInstance * ) instance , & result ) , " Failed to create Vulkan instance " ) ;
2022-06-06 03:38:14 +00:00
return result ;
}
static uint32_t openxr_createVulkanDevice ( void * instance , void * deviceCreateInfo , void * allocator , uintptr_t device , void * getInstanceProcAddr ) {
XrVulkanDeviceCreateInfoKHR info = {
. type = XR_TYPE_VULKAN_DEVICE_CREATE_INFO_KHR ,
. systemId = state . system ,
. pfnGetInstanceProcAddr = ( PFN_vkGetInstanceProcAddr ) getInstanceProcAddr ,
. vulkanPhysicalDevice = ( VkPhysicalDevice ) gpu_vk_get_physical_device ( ) ,
. vulkanCreateInfo = deviceCreateInfo ,
. vulkanAllocator = allocator
} ;
VkResult result ;
2023-03-08 04:16:59 +00:00
XR ( xrCreateVulkanDeviceKHR ( state . instance , & info , ( VkDevice * ) device , & result ) , " Failed to create Vulkan device " ) ;
2022-06-06 03:38:14 +00:00
return result ;
}
2019-04-30 23:59:15 +00:00
static void openxr_destroy ( ) ;
2019-04-11 20:47:25 +00:00
2022-08-03 05:00:11 +00:00
static bool openxr_init ( HeadsetConfig * config ) {
state . config = * config ;
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 ;
}
XrLoaderInitInfoAndroidKHR loaderInfo = {
. type = XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR ,
2022-11-16 04:35:39 +00:00
. applicationVM = os_get_java_vm ( ) ,
. applicationContext = os_get_jni_context ( )
2020-08-30 01:45:52 +00:00
} ;
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 ;
2023-05-12 13:13:48 +00:00
XrResult result = xrEnumerateInstanceExtensionProperties ( NULL , 0 , & extensionCount , NULL ) ;
if ( result = = XR_ERROR_RUNTIME_UNAVAILABLE ) {
return openxr_destroy ( ) , false ;
} else {
XR_INIT ( result , " Failed to query extensions " ) ;
}
2022-03-22 00:01:03 +00:00
XrExtensionProperties * extensionProperties = calloc ( extensionCount , sizeof ( * extensionProperties ) ) ;
lovrAssert ( extensionProperties , " Out of memory " ) ;
for ( uint32_t i = 0 ; i < extensionCount ; i + + ) extensionProperties [ i ] . type = XR_TYPE_EXTENSION_PROPERTIES ;
xrEnumerateInstanceExtensionProperties ( NULL , extensionCount , & extensionCount , extensionProperties ) ;
2020-08-28 05:40:08 +00:00
2022-08-07 05:52:18 +00:00
// Extensions with feature == NULL must be present. The enable flag can be used to
// conditionally enable extensions based on config, platform, etc.
struct { const char * name ; bool * feature ; bool enable ; } extensions [ ] = {
2022-06-06 03:38:14 +00:00
# ifdef LOVR_VK
2022-08-07 05:52:18 +00:00
{ " XR_KHR_vulkan_enable2 " , NULL , true } ,
2020-10-27 00:59:21 +00:00
# endif
2022-11-22 17:22:27 +00:00
# ifdef __ANDROID__
{ " XR_KHR_android_create_instance " , NULL , true } ,
# endif
2022-08-07 05:52:18 +00:00
{ " XR_KHR_composition_layer_depth " , & state . features . depth , config - > submitDepth } ,
2023-06-26 23:41:42 +00:00
# ifdef _WIN32
{ " XR_KHR_win32_convert_performance_counter_time " , NULL , true } ,
# else
{ " XR_KHR_convert_timespec_time " , NULL , true } ,
# endif
2022-08-07 05:52:18 +00:00
{ " XR_EXT_eye_gaze_interaction " , & state . features . gaze , true } ,
{ " XR_EXT_hand_tracking " , & state . features . handTracking , true } ,
2023-06-26 23:41:42 +00:00
{ " XR_EXT_local_floor " , & state . features . localFloor , true } ,
2023-05-12 09:47:47 +00:00
{ " XR_BD_controller_interaction " , & state . features . picoController , true } ,
2022-08-07 05:52:18 +00:00
{ " XR_FB_display_refresh_rate " , & state . features . refreshRate , true } ,
{ " XR_FB_hand_tracking_aim " , & state . features . handTrackingAim , true } ,
{ " XR_FB_hand_tracking_mesh " , & state . features . handTrackingMesh , true } ,
2022-11-26 22:40:39 +00:00
{ " XR_FB_keyboard_tracking " , & state . features . keyboardTracking , true } ,
2023-05-12 13:13:48 +00:00
{ " XR_FB_passthrough " , & state . features . questPassthrough , true } ,
2023-05-27 00:03:11 +00:00
{ " XR_ML_ml2_controller_interaction " , & state . features . ml2Controller , true } ,
2022-11-26 22:40:39 +00:00
{ " XR_MND_headless " , & state . features . headless , true } ,
2023-02-02 03:26:18 +00:00
{ " XR_MSFT_controller_model " , & state . features . controllerModel , true } ,
2023-02-02 02:36:05 +00:00
{ " XR_ULTRALEAP_hand_tracking_forearm " , & state . features . handTrackingElbow , true } ,
2022-08-07 05:52:18 +00:00
{ " XR_EXTX_overlay " , & state . features . overlay , config - > overlay } ,
2022-11-26 22:40:39 +00:00
{ " XR_HTCX_vive_tracker_interaction " , & state . features . viveTrackers , true }
2022-03-22 00:01:03 +00:00
} ;
2020-10-27 00:59:21 +00:00
2022-03-22 00:01:03 +00:00
uint32_t enabledExtensionCount = 0 ;
const char * enabledExtensionNames [ COUNTOF ( extensions ) ] ;
for ( uint32_t i = 0 ; i < COUNTOF ( extensions ) ; i + + ) {
2022-08-07 05:52:18 +00:00
if ( ! extensions [ i ] . enable ) continue ;
2022-03-22 00:01:03 +00:00
if ( ! extensions [ i ] . feature | | hasExtension ( extensionProperties , extensionCount , extensions [ i ] . name ) ) {
enabledExtensionNames [ enabledExtensionCount + + ] = extensions [ i ] . name ;
if ( extensions [ i ] . feature ) * extensions [ i ] . feature = true ;
}
2022-03-21 01:04:06 +00:00
}
2022-03-22 00:01:03 +00:00
free ( extensionProperties ) ;
2020-08-28 05:40:08 +00:00
2022-11-22 17:22:27 +00:00
# ifdef __ANDROID__
2023-01-31 03:44:23 +00:00
XrInstanceCreateInfoAndroidKHR androidInfo = {
2022-11-22 17:22:27 +00:00
. type = XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR ,
. applicationVM = os_get_java_vm ( ) ,
. applicationActivity = os_get_jni_context ( ) ,
. next = NULL
} ;
# endif
2019-04-11 20:47:25 +00:00
XrInstanceCreateInfo info = {
. type = XR_TYPE_INSTANCE_CREATE_INFO ,
2022-12-08 04:05:30 +00:00
# ifdef __ANDROID__
2023-01-31 03:44:23 +00:00
. next = & androidInfo ,
2022-12-08 04:05:30 +00:00
# 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
} ;
2023-03-08 04:16:59 +00:00
XR_INIT ( xrCreateInstance ( & info , & state . instance ) , " Failed to create instance " ) ;
2020-08-28 23:04:45 +00:00
XR_FOREACH ( XR_LOAD )
2023-06-26 23:41:42 +00:00
XR_FOREACH_PLATFORM ( 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
} ;
2023-03-08 04:16:59 +00:00
XR_INIT ( xrGetSystem ( state . instance , & info , & state . system ) , " Failed to query system " ) ;
2019-04-11 20:47:25 +00:00
2023-02-06 03:51:12 +00:00
XrSystemEyeGazeInteractionPropertiesEXT eyeGazeProperties = { . type = XR_TYPE_SYSTEM_EYE_GAZE_INTERACTION_PROPERTIES_EXT } ;
XrSystemHandTrackingPropertiesEXT handTrackingProperties = { . type = XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT } ;
XrSystemKeyboardTrackingPropertiesFB keyboardTrackingProperties = { . type = XR_TYPE_SYSTEM_KEYBOARD_TRACKING_PROPERTIES_FB } ;
XrSystemPassthroughProperties2FB passthroughProperties = { . type = XR_TYPE_SYSTEM_PASSTHROUGH_PROPERTIES2_FB } ;
XrSystemProperties properties = { . type = XR_TYPE_SYSTEM_PROPERTIES } ;
2020-08-28 23:04:45 +00:00
2022-03-21 22:05:23 +00:00
if ( state . features . gaze ) {
eyeGazeProperties . next = properties . next ;
properties . next = & eyeGazeProperties ;
}
if ( state . features . handTracking ) {
handTrackingProperties . next = properties . next ;
properties . next = & handTrackingProperties ;
}
2022-09-21 03:16:58 +00:00
if ( state . features . keyboardTracking ) {
keyboardTrackingProperties . next = properties . next ;
properties . next = & keyboardTrackingProperties ;
}
2023-05-12 13:13:48 +00:00
if ( state . features . questPassthrough ) {
2023-02-06 03:51:12 +00:00
passthroughProperties . next = properties . next ;
properties . next = & passthroughProperties ;
}
2023-03-08 04:16:59 +00:00
XR_INIT ( xrGetSystemProperties ( state . instance , state . system , & properties ) , " Failed to query system properties " ) ;
2022-03-21 22:05:23 +00:00
state . features . gaze = eyeGazeProperties . supportsEyeGazeInteraction ;
2020-08-28 23:04:45 +00:00
state . features . handTracking = handTrackingProperties . supportsHandTracking ;
2022-09-21 03:16:58 +00:00
state . features . keyboardTracking = keyboardTrackingProperties . supportsKeyboardTracking ;
2023-05-12 13:13:48 +00:00
state . features . questPassthrough = passthroughProperties . capabilities & XR_PASSTHROUGH_CAPABILITY_BIT_FB ;
2020-08-28 23:04:45 +00:00
2019-10-02 23:29:09 +00:00
uint32_t viewConfigurationCount ;
XrViewConfigurationType viewConfigurations [ 2 ] ;
2023-03-08 04:16:59 +00:00
XR_INIT ( xrEnumerateViewConfigurations ( state . instance , state . system , 2 , & viewConfigurationCount , viewConfigurations ) , " Failed to query view configurations " ) ;
2019-10-02 23:29:09 +00:00
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 } ;
2023-03-08 04:16:59 +00:00
XR_INIT ( xrEnumerateViewConfigurationViews ( state . instance , state . system , XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO , 0 , & viewCount , NULL ) , " Failed to query view configurations " ) ;
XR_INIT ( xrEnumerateViewConfigurationViews ( state . instance , state . system , XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO , 2 , & viewCount , views ) , " Failed to query view configurations " ) ;
2019-04-11 20:47:25 +00:00
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
}
2022-08-03 05:00:11 +00:00
state . width = MIN ( views [ 0 ] . recommendedImageRectWidth * config - > supersample , views [ 0 ] . maxImageRectWidth ) ;
state . height = MIN ( views [ 0 ] . recommendedImageRectHeight * config - > supersample , views [ 0 ] . maxImageRectHeight ) ;
2023-03-31 02:39:50 +00:00
2023-05-12 13:13:48 +00:00
// Blend modes
XR_INIT ( xrEnumerateEnvironmentBlendModes ( state . instance , state . system , XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO , 0 , & state . blendModeCount , NULL ) , " Failed to query blend modes " ) ;
state . blendModes = malloc ( state . blendModeCount * sizeof ( XrEnvironmentBlendMode ) ) ;
lovrAssert ( state . blendModes , " Out of memory " ) ;
XR_INIT ( xrEnumerateEnvironmentBlendModes ( state . instance , state . system , XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO , state . blendModeCount , & state . blendModeCount , state . blendModes ) , " Failed to query blend modes " ) ;
state . blendMode = state . blendModes [ 0 ] ;
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 ,
. localizedActionSetName = " Default " ,
2022-03-20 22:39:02 +00:00
. actionSetName = " default "
2019-04-11 20:47:25 +00:00
} ;
2023-03-08 04:16:59 +00:00
XR_INIT ( xrCreateActionSet ( state . instance , & info , & state . actionSet ) , " Failed to create action set " ) ;
2022-03-20 22:39:02 +00:00
2022-03-21 01:04:06 +00:00
// Subaction paths, for filtering actions by device
2023-03-08 04:16:59 +00:00
XR_INIT ( xrStringToPath ( state . instance , " /user/hand/left " , & state . actionFilters [ DEVICE_HAND_LEFT ] ) , " Failed to create path " ) ;
XR_INIT ( xrStringToPath ( state . instance , " /user/hand/right " , & state . actionFilters [ DEVICE_HAND_RIGHT ] ) , " Failed to create path " ) ;
2022-03-21 01:04:06 +00:00
state . actionFilters [ DEVICE_HAND_LEFT_POINT ] = state . actionFilters [ DEVICE_HAND_LEFT ] ;
state . actionFilters [ DEVICE_HAND_RIGHT_POINT ] = state . actionFilters [ DEVICE_HAND_RIGHT ] ;
if ( state . features . viveTrackers ) {
2023-03-08 04:16:59 +00:00
XR_INIT ( xrStringToPath ( state . instance , " /user/vive_tracker_htcx/role/left_elbow " , & state . actionFilters [ DEVICE_ELBOW_LEFT ] ) , " Failed to create path " ) ;
XR_INIT ( xrStringToPath ( state . instance , " /user/vive_tracker_htcx/role/right_elbow " , & state . actionFilters [ DEVICE_ELBOW_RIGHT ] ) , " Failed to create path " ) ;
XR_INIT ( xrStringToPath ( state . instance , " /user/vive_tracker_htcx/role/left_shoulder " , & state . actionFilters [ DEVICE_SHOULDER_LEFT ] ) , " Failed to create path " ) ;
XR_INIT ( xrStringToPath ( state . instance , " /user/vive_tracker_htcx/role/right_shoulder " , & state . actionFilters [ DEVICE_SHOULDER_RIGHT ] ) , " Failed to create path " ) ;
XR_INIT ( xrStringToPath ( state . instance , " /user/vive_tracker_htcx/role/chest " , & state . actionFilters [ DEVICE_CHEST ] ) , " Failed to create path " ) ;
XR_INIT ( xrStringToPath ( state . instance , " /user/vive_tracker_htcx/role/waist " , & state . actionFilters [ DEVICE_WAIST ] ) , " Failed to create path " ) ;
XR_INIT ( xrStringToPath ( state . instance , " /user/vive_tracker_htcx/role/left_knee " , & state . actionFilters [ DEVICE_KNEE_LEFT ] ) , " Failed to create path " ) ;
XR_INIT ( xrStringToPath ( state . instance , " /user/vive_tracker_htcx/role/right_knee " , & state . actionFilters [ DEVICE_KNEE_RIGHT ] ) , " Failed to create path " ) ;
XR_INIT ( xrStringToPath ( state . instance , " /user/vive_tracker_htcx/role/left_foot " , & state . actionFilters [ DEVICE_FOOT_LEFT ] ) , " Failed to create path " ) ;
XR_INIT ( xrStringToPath ( state . instance , " /user/vive_tracker_htcx/role/right_foot " , & state . actionFilters [ DEVICE_FOOT_RIGHT ] ) , " Failed to create path " ) ;
XR_INIT ( xrStringToPath ( state . instance , " /user/vive_tracker_htcx/role/camera " , & state . actionFilters [ DEVICE_CAMERA ] ) , " Failed to create path " ) ;
XR_INIT ( xrStringToPath ( state . instance , " /user/vive_tracker_htcx/role/keyboard " , & state . actionFilters [ DEVICE_KEYBOARD ] ) , " Failed to create path " ) ;
2022-03-21 01:04:06 +00:00
}
2019-04-11 20:47:25 +00:00
2022-03-20 22:39:02 +00:00
XrPath hands [ ] = {
2022-03-21 01:04:06 +00:00
state . actionFilters [ DEVICE_HAND_LEFT ] ,
state . actionFilters [ DEVICE_HAND_RIGHT ]
} ;
XrPath trackers [ ] = {
state . actionFilters [ DEVICE_ELBOW_LEFT ] ,
state . actionFilters [ DEVICE_ELBOW_RIGHT ] ,
state . actionFilters [ DEVICE_SHOULDER_LEFT ] ,
state . actionFilters [ DEVICE_SHOULDER_RIGHT ] ,
state . actionFilters [ DEVICE_CHEST ] ,
state . actionFilters [ DEVICE_WAIST ] ,
state . actionFilters [ DEVICE_KNEE_LEFT ] ,
state . actionFilters [ DEVICE_KNEE_RIGHT ] ,
state . actionFilters [ DEVICE_FOOT_LEFT ] ,
state . actionFilters [ DEVICE_FOOT_RIGHT ] ,
state . actionFilters [ DEVICE_CAMERA ] ,
state . actionFilters [ DEVICE_KEYBOARD ]
2022-03-20 22:39:02 +00:00
} ;
XrActionCreateInfo actionInfo [ ] = {
{ 0 , NULL , " hand_pose " , XR_ACTION_TYPE_POSE_INPUT , 2 , hands , " Hand Pose " } ,
{ 0 , NULL , " pointer_pose " , XR_ACTION_TYPE_POSE_INPUT , 2 , hands , " Pointer Pose " } ,
2022-03-21 01:04:06 +00:00
{ 0 , NULL , " tracker_pose " , XR_ACTION_TYPE_POSE_INPUT , 12 , trackers , " Tracker Pose " } ,
2022-03-21 22:05:23 +00:00
{ 0 , NULL , " gaze_pose " , XR_ACTION_TYPE_POSE_INPUT , 0 , NULL , " Gaze Pose " } ,
2022-03-20 22:39:02 +00:00
{ 0 , NULL , " trigger_down " , XR_ACTION_TYPE_BOOLEAN_INPUT , 2 , hands , " Trigger Down " } ,
{ 0 , NULL , " trigger_touch " , XR_ACTION_TYPE_BOOLEAN_INPUT , 2 , hands , " Trigger Touch " } ,
{ 0 , NULL , " trigger_axis " , XR_ACTION_TYPE_FLOAT_INPUT , 2 , hands , " Trigger Axis " } ,
{ 0 , NULL , " trackpad_down " , XR_ACTION_TYPE_BOOLEAN_INPUT , 2 , hands , " Trackpad Down " } ,
{ 0 , NULL , " trackpad_touch " , XR_ACTION_TYPE_BOOLEAN_INPUT , 2 , hands , " Trackpad Touch " } ,
{ 0 , NULL , " trackpad_x " , XR_ACTION_TYPE_FLOAT_INPUT , 2 , hands , " Trackpad X " } ,
{ 0 , NULL , " trackpad_y " , XR_ACTION_TYPE_FLOAT_INPUT , 2 , hands , " Trackpad Y " } ,
{ 0 , NULL , " thumbstick_down " , XR_ACTION_TYPE_BOOLEAN_INPUT , 2 , hands , " Thumbstick Down " } ,
{ 0 , NULL , " thumbstick_touch " , XR_ACTION_TYPE_BOOLEAN_INPUT , 2 , hands , " Thumbstick Touch " } ,
{ 0 , NULL , " thumbstick_x " , XR_ACTION_TYPE_FLOAT_INPUT , 2 , hands , " Thumbstick X " } ,
{ 0 , NULL , " thumbstick_y " , XR_ACTION_TYPE_FLOAT_INPUT , 2 , hands , " Thumbstick Y " } ,
{ 0 , NULL , " menu_down " , XR_ACTION_TYPE_BOOLEAN_INPUT , 2 , hands , " Menu Down " } ,
{ 0 , NULL , " menu_touch " , XR_ACTION_TYPE_BOOLEAN_INPUT , 2 , hands , " Menu Touch " } ,
{ 0 , NULL , " grip_down " , XR_ACTION_TYPE_BOOLEAN_INPUT , 2 , hands , " Grip Down " } ,
{ 0 , NULL , " grip_touch " , XR_ACTION_TYPE_BOOLEAN_INPUT , 2 , hands , " Grip Touch " } ,
{ 0 , NULL , " grip_axis " , XR_ACTION_TYPE_FLOAT_INPUT , 2 , hands , " Grip Axis " } ,
{ 0 , NULL , " a_down " , XR_ACTION_TYPE_BOOLEAN_INPUT , 2 , hands , " A Down " } ,
{ 0 , NULL , " a_touch " , XR_ACTION_TYPE_BOOLEAN_INPUT , 2 , hands , " A Touch " } ,
{ 0 , NULL , " b_down " , XR_ACTION_TYPE_BOOLEAN_INPUT , 2 , hands , " B Down " } ,
{ 0 , NULL , " b_touch " , XR_ACTION_TYPE_BOOLEAN_INPUT , 2 , hands , " B Touch " } ,
{ 0 , NULL , " x_down " , XR_ACTION_TYPE_BOOLEAN_INPUT , 2 , hands , " X Down " } ,
{ 0 , NULL , " x_touch " , XR_ACTION_TYPE_BOOLEAN_INPUT , 2 , hands , " X Touch " } ,
{ 0 , NULL , " y_down " , XR_ACTION_TYPE_BOOLEAN_INPUT , 2 , hands , " Y Down " } ,
{ 0 , NULL , " y_touch " , XR_ACTION_TYPE_BOOLEAN_INPUT , 2 , hands , " Y Touch " } ,
{ 0 , NULL , " thumbrest_touch " , XR_ACTION_TYPE_BOOLEAN_INPUT , 2 , hands , " Thumbrest Touch " } ,
{ 0 , NULL , " vibrate " , XR_ACTION_TYPE_VIBRATION_OUTPUT , 2 , hands , " Vibrate " }
} ;
2022-04-24 03:12:32 +00:00
static_assert ( COUNTOF ( actionInfo ) = = MAX_ACTIONS , " Unbalanced action table! " ) ;
2022-03-20 22:39:02 +00:00
2022-03-21 01:04:06 +00:00
if ( ! state . features . viveTrackers ) {
actionInfo [ ACTION_TRACKER_POSE ] . countSubactionPaths = 0 ;
}
2022-03-21 22:05:23 +00:00
if ( ! state . features . gaze ) {
actionInfo [ ACTION_GAZE_POSE ] . countSubactionPaths = 0 ;
}
2020-08-22 21:40:52 +00:00
for ( uint32_t i = 0 ; i < MAX_ACTIONS ; i + + ) {
2022-03-20 22:39:02 +00:00
actionInfo [ i ] . type = XR_TYPE_ACTION_CREATE_INFO ;
2023-03-08 04:16:59 +00:00
XR_INIT ( xrCreateAction ( state . actionSet , & actionInfo [ i ] , & state . actions [ i ] ) , " Failed to create action " ) ;
2019-04-11 20:47:25 +00:00
}
2022-03-20 22:39:02 +00:00
enum {
PROFILE_SIMPLE ,
PROFILE_VIVE ,
PROFILE_TOUCH ,
PROFILE_GO ,
PROFILE_INDEX ,
PROFILE_WMR ,
2023-05-27 00:03:11 +00:00
PROFILE_ML2 ,
2023-05-05 02:06:51 +00:00
PROFILE_PICO_NEO3 ,
2023-05-18 07:27:18 +00:00
PROFILE_PICO4 ,
2022-03-21 01:04:06 +00:00
PROFILE_TRACKER ,
2022-03-21 22:05:23 +00:00
PROFILE_GAZE ,
2022-03-20 22:39:02 +00:00
MAX_PROFILES
} ;
const char * interactionProfilePaths [ ] = {
[ PROFILE_SIMPLE ] = " /interaction_profiles/khr/simple_controller " ,
[ PROFILE_VIVE ] = " /interaction_profiles/htc/vive_controller " ,
[ PROFILE_TOUCH ] = " /interaction_profiles/oculus/touch_controller " ,
[ PROFILE_GO ] = " /interaction_profiles/oculus/go_controller " ,
[ PROFILE_INDEX ] = " /interaction_profiles/valve/index_controller " ,
2022-03-21 01:04:06 +00:00
[ PROFILE_WMR ] = " /interaction_profiles/microsoft/motion_controller " ,
2023-05-27 00:03:11 +00:00
[ PROFILE_ML2 ] = " /interaction_profiles/ml/ml2_controller " ,
2023-05-05 02:06:51 +00:00
[ PROFILE_PICO_NEO3 ] = " /interaction_profiles/bytedance/pico_neo3_controller " ,
2023-05-18 07:27:18 +00:00
[ PROFILE_PICO4 ] = " /interaction_profiles/bytedance/pico4_controller " ,
2022-03-21 22:05:23 +00:00
[ PROFILE_TRACKER ] = " /interaction_profiles/htc/vive_tracker_htcx " ,
2023-05-05 02:06:51 +00:00
[ PROFILE_GAZE ] = " /interaction_profiles/ext/eye_gaze_interaction "
2022-03-20 22:39:02 +00:00
} ;
typedef struct {
int action ;
const char * path ;
} Binding ;
Binding * bindings [ ] = {
[ PROFILE_SIMPLE ] = ( Binding [ ] ) {
{ ACTION_HAND_POSE , " /user/hand/left/input/grip/pose " } ,
{ ACTION_HAND_POSE , " /user/hand/right/input/grip/pose " } ,
{ ACTION_POINTER_POSE , " /user/hand/left/input/aim/pose " } ,
{ ACTION_POINTER_POSE , " /user/hand/right/input/aim/pose " } ,
{ ACTION_TRIGGER_DOWN , " /user/hand/left/input/select/click " } ,
{ ACTION_TRIGGER_DOWN , " /user/hand/right/input/select/click " } ,
{ ACTION_MENU_DOWN , " /user/hand/left/input/menu/click " } ,
{ ACTION_MENU_DOWN , " /user/hand/right/input/menu/click " } ,
{ ACTION_VIBRATE , " /user/hand/left/output/haptic " } ,
{ ACTION_VIBRATE , " /user/hand/right/output/haptic " } ,
{ 0 , NULL }
} ,
[ PROFILE_VIVE ] = ( Binding [ ] ) {
{ ACTION_HAND_POSE , " /user/hand/left/input/grip/pose " } ,
{ ACTION_HAND_POSE , " /user/hand/right/input/grip/pose " } ,
{ ACTION_POINTER_POSE , " /user/hand/left/input/aim/pose " } ,
{ ACTION_POINTER_POSE , " /user/hand/right/input/aim/pose " } ,
{ ACTION_TRIGGER_DOWN , " /user/hand/left/input/trigger/click " } ,
{ ACTION_TRIGGER_DOWN , " /user/hand/right/input/trigger/click " } ,
{ ACTION_TRIGGER_AXIS , " /user/hand/left/input/trigger/value " } ,
{ ACTION_TRIGGER_AXIS , " /user/hand/right/input/trigger/value " } ,
{ ACTION_TRACKPAD_DOWN , " /user/hand/left/input/trackpad/click " } ,
{ ACTION_TRACKPAD_DOWN , " /user/hand/right/input/trackpad/click " } ,
{ ACTION_TRACKPAD_TOUCH , " /user/hand/left/input/trackpad/touch " } ,
{ ACTION_TRACKPAD_TOUCH , " /user/hand/right/input/trackpad/touch " } ,
{ ACTION_TRACKPAD_X , " /user/hand/left/input/trackpad/x " } ,
{ ACTION_TRACKPAD_X , " /user/hand/right/input/trackpad/x " } ,
{ ACTION_TRACKPAD_Y , " /user/hand/left/input/trackpad/y " } ,
{ ACTION_TRACKPAD_Y , " /user/hand/right/input/trackpad/y " } ,
{ ACTION_MENU_DOWN , " /user/hand/left/input/menu/click " } ,
{ ACTION_MENU_DOWN , " /user/hand/right/input/menu/click " } ,
{ ACTION_GRIP_DOWN , " /user/hand/left/input/squeeze/click " } ,
{ ACTION_GRIP_DOWN , " /user/hand/right/input/squeeze/click " } ,
{ ACTION_VIBRATE , " /user/hand/left/output/haptic " } ,
{ ACTION_VIBRATE , " /user/hand/right/output/haptic " } ,
{ 0 , NULL }
} ,
[ PROFILE_TOUCH ] = ( Binding [ ] ) {
{ ACTION_HAND_POSE , " /user/hand/left/input/grip/pose " } ,
{ ACTION_HAND_POSE , " /user/hand/right/input/grip/pose " } ,
{ ACTION_POINTER_POSE , " /user/hand/left/input/aim/pose " } ,
{ ACTION_POINTER_POSE , " /user/hand/right/input/aim/pose " } ,
{ ACTION_TRIGGER_DOWN , " /user/hand/left/input/trigger/value " } ,
{ ACTION_TRIGGER_DOWN , " /user/hand/right/input/trigger/value " } ,
{ ACTION_TRIGGER_TOUCH , " /user/hand/left/input/trigger/touch " } ,
{ ACTION_TRIGGER_TOUCH , " /user/hand/right/input/trigger/touch " } ,
{ ACTION_TRIGGER_AXIS , " /user/hand/left/input/trigger/value " } ,
{ ACTION_TRIGGER_AXIS , " /user/hand/right/input/trigger/value " } ,
{ ACTION_THUMBSTICK_DOWN , " /user/hand/left/input/thumbstick/click " } ,
{ ACTION_THUMBSTICK_DOWN , " /user/hand/right/input/thumbstick/click " } ,
{ ACTION_THUMBSTICK_TOUCH , " /user/hand/left/input/thumbstick/touch " } ,
{ ACTION_THUMBSTICK_TOUCH , " /user/hand/right/input/thumbstick/touch " } ,
{ ACTION_THUMBSTICK_X , " /user/hand/left/input/thumbstick/x " } ,
{ ACTION_THUMBSTICK_X , " /user/hand/right/input/thumbstick/x " } ,
{ ACTION_THUMBSTICK_Y , " /user/hand/left/input/thumbstick/y " } ,
{ ACTION_THUMBSTICK_Y , " /user/hand/right/input/thumbstick/y " } ,
{ ACTION_MENU_DOWN , " /user/hand/left/input/menu/click " } ,
{ ACTION_MENU_DOWN , " /user/hand/right/input/system/click " } ,
{ ACTION_GRIP_DOWN , " /user/hand/left/input/squeeze/value " } ,
{ ACTION_GRIP_DOWN , " /user/hand/right/input/squeeze/value " } ,
{ ACTION_GRIP_AXIS , " /user/hand/left/input/squeeze/value " } ,
{ ACTION_GRIP_AXIS , " /user/hand/right/input/squeeze/value " } ,
{ ACTION_A_DOWN , " /user/hand/right/input/a/click " } ,
{ ACTION_A_TOUCH , " /user/hand/right/input/a/touch " } ,
{ ACTION_B_DOWN , " /user/hand/right/input/b/click " } ,
{ ACTION_B_TOUCH , " /user/hand/right/input/b/touch " } ,
{ ACTION_X_DOWN , " /user/hand/left/input/x/click " } ,
{ ACTION_X_TOUCH , " /user/hand/left/input/x/touch " } ,
{ ACTION_Y_DOWN , " /user/hand/left/input/y/click " } ,
{ ACTION_Y_TOUCH , " /user/hand/left/input/y/touch " } ,
{ ACTION_THUMBREST_TOUCH , " /user/hand/left/input/thumbrest/touch " } ,
{ ACTION_THUMBREST_TOUCH , " /user/hand/right/input/thumbrest/touch " } ,
{ ACTION_VIBRATE , " /user/hand/left/output/haptic " } ,
{ ACTION_VIBRATE , " /user/hand/right/output/haptic " } ,
{ 0 , NULL }
} ,
[ PROFILE_GO ] = ( Binding [ ] ) {
{ ACTION_HAND_POSE , " /user/hand/left/input/grip/pose " } ,
{ ACTION_HAND_POSE , " /user/hand/right/input/grip/pose " } ,
{ ACTION_POINTER_POSE , " /user/hand/left/input/aim/pose " } ,
{ ACTION_POINTER_POSE , " /user/hand/right/input/aim/pose " } ,
{ ACTION_TRIGGER_DOWN , " /user/hand/left/input/trigger/click " } ,
{ ACTION_TRIGGER_DOWN , " /user/hand/right/input/trigger/click " } ,
{ ACTION_TRACKPAD_DOWN , " /user/hand/left/input/trackpad/click " } ,
{ ACTION_TRACKPAD_DOWN , " /user/hand/right/input/trackpad/click " } ,
{ ACTION_TRACKPAD_TOUCH , " /user/hand/left/input/trackpad/touch " } ,
{ ACTION_TRACKPAD_TOUCH , " /user/hand/right/input/trackpad/touch " } ,
{ ACTION_TRACKPAD_X , " /user/hand/left/input/trackpad/x " } ,
{ ACTION_TRACKPAD_X , " /user/hand/right/input/trackpad/x " } ,
{ ACTION_TRACKPAD_Y , " /user/hand/left/input/trackpad/y " } ,
{ ACTION_TRACKPAD_Y , " /user/hand/right/input/trackpad/y " } ,
{ 0 , NULL }
} ,
[ PROFILE_INDEX ] = ( Binding [ ] ) {
{ ACTION_HAND_POSE , " /user/hand/left/input/grip/pose " } ,
{ ACTION_HAND_POSE , " /user/hand/right/input/grip/pose " } ,
{ ACTION_POINTER_POSE , " /user/hand/left/input/aim/pose " } ,
{ ACTION_POINTER_POSE , " /user/hand/right/input/aim/pose " } ,
{ ACTION_TRIGGER_DOWN , " /user/hand/left/input/trigger/click " } ,
{ ACTION_TRIGGER_DOWN , " /user/hand/right/input/trigger/click " } ,
{ ACTION_TRIGGER_TOUCH , " /user/hand/left/input/trigger/touch " } ,
{ ACTION_TRIGGER_TOUCH , " /user/hand/right/input/trigger/touch " } ,
{ ACTION_TRIGGER_AXIS , " /user/hand/left/input/trigger/value " } ,
{ ACTION_TRIGGER_AXIS , " /user/hand/right/input/trigger/value " } ,
{ ACTION_TRACKPAD_DOWN , " /user/hand/left/input/trackpad/force " } ,
{ ACTION_TRACKPAD_DOWN , " /user/hand/right/input/trackpad/force " } ,
{ ACTION_TRACKPAD_TOUCH , " /user/hand/left/input/trackpad/touch " } ,
{ ACTION_TRACKPAD_TOUCH , " /user/hand/right/input/trackpad/touch " } ,
{ ACTION_TRACKPAD_X , " /user/hand/left/input/trackpad/x " } ,
{ ACTION_TRACKPAD_X , " /user/hand/right/input/trackpad/x " } ,
{ ACTION_TRACKPAD_Y , " /user/hand/left/input/trackpad/y " } ,
{ ACTION_TRACKPAD_Y , " /user/hand/right/input/trackpad/y " } ,
{ ACTION_THUMBSTICK_DOWN , " /user/hand/left/input/thumbstick/click " } ,
{ ACTION_THUMBSTICK_DOWN , " /user/hand/right/input/thumbstick/click " } ,
{ ACTION_THUMBSTICK_TOUCH , " /user/hand/left/input/thumbstick/touch " } ,
{ ACTION_THUMBSTICK_TOUCH , " /user/hand/right/input/thumbstick/touch " } ,
{ ACTION_THUMBSTICK_X , " /user/hand/left/input/thumbstick/x " } ,
{ ACTION_THUMBSTICK_X , " /user/hand/right/input/thumbstick/x " } ,
{ ACTION_THUMBSTICK_Y , " /user/hand/left/input/thumbstick/y " } ,
{ ACTION_THUMBSTICK_Y , " /user/hand/right/input/thumbstick/y " } ,
2022-03-30 18:13:55 +00:00
{ ACTION_GRIP_DOWN , " /user/hand/left/input/squeeze/force " } ,
{ ACTION_GRIP_DOWN , " /user/hand/right/input/squeeze/force " } ,
{ ACTION_GRIP_TOUCH , " /user/hand/left/input/squeeze/value " } ,
{ ACTION_GRIP_TOUCH , " /user/hand/right/input/squeeze/value " } ,
{ ACTION_GRIP_AXIS , " /user/hand/left/input/squeeze/force " } ,
{ ACTION_GRIP_AXIS , " /user/hand/right/input/squeeze/force " } ,
2022-03-20 22:39:02 +00:00
{ ACTION_A_DOWN , " /user/hand/left/input/a/click " } ,
{ ACTION_A_DOWN , " /user/hand/right/input/a/click " } ,
{ ACTION_A_TOUCH , " /user/hand/left/input/a/touch " } ,
{ ACTION_A_TOUCH , " /user/hand/right/input/a/touch " } ,
{ ACTION_B_DOWN , " /user/hand/left/input/b/click " } ,
{ ACTION_B_DOWN , " /user/hand/right/input/b/click " } ,
{ ACTION_B_TOUCH , " /user/hand/left/input/b/touch " } ,
{ ACTION_B_TOUCH , " /user/hand/right/input/b/touch " } ,
{ ACTION_VIBRATE , " /user/hand/left/output/haptic " } ,
{ ACTION_VIBRATE , " /user/hand/right/output/haptic " } ,
{ 0 , NULL }
} ,
[ PROFILE_WMR ] = ( Binding [ ] ) {
{ ACTION_HAND_POSE , " /user/hand/left/input/grip/pose " } ,
{ ACTION_HAND_POSE , " /user/hand/right/input/grip/pose " } ,
{ ACTION_POINTER_POSE , " /user/hand/left/input/aim/pose " } ,
{ ACTION_POINTER_POSE , " /user/hand/right/input/aim/pose " } ,
{ ACTION_TRIGGER_DOWN , " /user/hand/left/input/trigger/value " } ,
{ ACTION_TRIGGER_DOWN , " /user/hand/right/input/trigger/value " } ,
{ ACTION_TRIGGER_AXIS , " /user/hand/left/input/trigger/value " } ,
{ ACTION_TRIGGER_AXIS , " /user/hand/right/input/trigger/value " } ,
{ ACTION_TRACKPAD_DOWN , " /user/hand/left/input/trackpad/click " } ,
{ ACTION_TRACKPAD_DOWN , " /user/hand/right/input/trackpad/click " } ,
{ ACTION_TRACKPAD_TOUCH , " /user/hand/left/input/trackpad/touch " } ,
{ ACTION_TRACKPAD_TOUCH , " /user/hand/right/input/trackpad/touch " } ,
{ ACTION_TRACKPAD_X , " /user/hand/left/input/trackpad/x " } ,
{ ACTION_TRACKPAD_X , " /user/hand/right/input/trackpad/x " } ,
{ ACTION_TRACKPAD_Y , " /user/hand/left/input/trackpad/y " } ,
{ ACTION_TRACKPAD_Y , " /user/hand/right/input/trackpad/y " } ,
{ ACTION_THUMBSTICK_DOWN , " /user/hand/left/input/thumbstick/click " } ,
{ ACTION_THUMBSTICK_DOWN , " /user/hand/right/input/thumbstick/click " } ,
{ ACTION_THUMBSTICK_X , " /user/hand/left/input/thumbstick/x " } ,
{ ACTION_THUMBSTICK_X , " /user/hand/right/input/thumbstick/x " } ,
{ ACTION_THUMBSTICK_Y , " /user/hand/left/input/thumbstick/y " } ,
{ ACTION_THUMBSTICK_Y , " /user/hand/right/input/thumbstick/y " } ,
{ ACTION_MENU_DOWN , " /user/hand/left/input/menu/click " } ,
{ ACTION_MENU_DOWN , " /user/hand/right/input/menu/click " } ,
{ ACTION_GRIP_DOWN , " /user/hand/left/input/squeeze/click " } ,
{ ACTION_GRIP_DOWN , " /user/hand/right/input/squeeze/click " } ,
{ ACTION_GRIP_AXIS , " /user/hand/left/input/squeeze/click " } ,
{ ACTION_GRIP_AXIS , " /user/hand/right/input/squeeze/click " } ,
{ ACTION_VIBRATE , " /user/hand/left/output/haptic " } ,
{ ACTION_VIBRATE , " /user/hand/right/output/haptic " } ,
{ 0 , NULL }
2022-03-21 01:04:06 +00:00
} ,
2023-05-27 00:03:11 +00:00
[ PROFILE_ML2 ] = ( Binding [ ] ) {
{ ACTION_HAND_POSE , " /user/hand/left/input/grip/pose " } ,
{ ACTION_HAND_POSE , " /user/hand/right/input/grip/pose " } ,
{ ACTION_POINTER_POSE , " /user/hand/left/input/aim/pose " } ,
{ ACTION_POINTER_POSE , " /user/hand/right/input/aim/pose " } ,
{ ACTION_TRIGGER_DOWN , " /user/hand/left/input/trigger/click " } ,
{ ACTION_TRIGGER_DOWN , " /user/hand/right/input/trigger/click " } ,
{ ACTION_TRIGGER_AXIS , " /user/hand/left/input/trigger/value " } ,
{ ACTION_TRIGGER_AXIS , " /user/hand/right/input/trigger/value " } ,
{ ACTION_TRACKPAD_DOWN , " /user/hand/left/input/trackpad/click " } ,
{ ACTION_TRACKPAD_DOWN , " /user/hand/right/input/trackpad/click " } ,
{ ACTION_TRACKPAD_TOUCH , " /user/hand/left/input/trackpad/touch " } ,
{ ACTION_TRACKPAD_TOUCH , " /user/hand/right/input/trackpad/touch " } ,
{ ACTION_TRACKPAD_X , " /user/hand/left/input/trackpad/x " } ,
{ ACTION_TRACKPAD_X , " /user/hand/right/input/trackpad/x " } ,
{ ACTION_TRACKPAD_Y , " /user/hand/left/input/trackpad/y " } ,
{ ACTION_TRACKPAD_Y , " /user/hand/right/input/trackpad/y " } ,
{ ACTION_MENU_DOWN , " /user/hand/left/input/menu/click " } ,
{ ACTION_MENU_DOWN , " /user/hand/right/input/menu/click " } ,
{ ACTION_GRIP_DOWN , " /user/hand/left/input/shoulder/click " } ,
{ ACTION_GRIP_DOWN , " /user/hand/right/input/shoulder/click " } ,
{ ACTION_VIBRATE , " /user/hand/left/output/haptic " } ,
{ ACTION_VIBRATE , " /user/hand/right/output/haptic " } ,
{ 0 , NULL }
} ,
2023-05-05 02:06:51 +00:00
[ PROFILE_PICO_NEO3 ] = ( Binding [ ] ) {
2022-11-22 17:22:27 +00:00
{ ACTION_HAND_POSE , " /user/hand/left/input/grip/pose " } ,
{ ACTION_HAND_POSE , " /user/hand/right/input/grip/pose " } ,
{ ACTION_POINTER_POSE , " /user/hand/left/input/aim/pose " } ,
{ ACTION_POINTER_POSE , " /user/hand/right/input/aim/pose " } ,
2023-05-05 02:06:51 +00:00
{ ACTION_TRIGGER_DOWN , " /user/hand/left/input/trigger/click " } ,
{ ACTION_TRIGGER_DOWN , " /user/hand/right/input/trigger/click " } ,
2022-11-22 17:22:27 +00:00
{ ACTION_TRIGGER_TOUCH , " /user/hand/left/input/trigger/touch " } ,
{ ACTION_TRIGGER_TOUCH , " /user/hand/right/input/trigger/touch " } ,
{ ACTION_TRIGGER_AXIS , " /user/hand/left/input/trigger/value " } ,
{ ACTION_TRIGGER_AXIS , " /user/hand/right/input/trigger/value " } ,
{ ACTION_THUMBSTICK_DOWN , " /user/hand/left/input/thumbstick/click " } ,
{ ACTION_THUMBSTICK_DOWN , " /user/hand/right/input/thumbstick/click " } ,
{ ACTION_THUMBSTICK_TOUCH , " /user/hand/left/input/thumbstick/touch " } ,
{ ACTION_THUMBSTICK_TOUCH , " /user/hand/right/input/thumbstick/touch " } ,
{ ACTION_THUMBSTICK_X , " /user/hand/left/input/thumbstick/x " } ,
{ ACTION_THUMBSTICK_X , " /user/hand/right/input/thumbstick/x " } ,
{ ACTION_THUMBSTICK_Y , " /user/hand/left/input/thumbstick/y " } ,
{ ACTION_THUMBSTICK_Y , " /user/hand/right/input/thumbstick/y " } ,
2023-05-05 02:06:51 +00:00
{ ACTION_MENU_DOWN , " /user/hand/left/input/menu/click " } ,
{ ACTION_MENU_DOWN , " /user/hand/right/input/menu/click " } ,
2022-11-22 17:22:27 +00:00
{ ACTION_GRIP_DOWN , " /user/hand/left/input/squeeze/click " } ,
{ ACTION_GRIP_DOWN , " /user/hand/right/input/squeeze/click " } ,
{ ACTION_GRIP_AXIS , " /user/hand/left/input/squeeze/value " } ,
{ ACTION_GRIP_AXIS , " /user/hand/right/input/squeeze/value " } ,
{ ACTION_A_DOWN , " /user/hand/right/input/a/click " } ,
{ ACTION_A_TOUCH , " /user/hand/right/input/a/touch " } ,
{ ACTION_B_DOWN , " /user/hand/right/input/b/click " } ,
{ ACTION_B_TOUCH , " /user/hand/right/input/b/touch " } ,
{ ACTION_X_DOWN , " /user/hand/left/input/x/click " } ,
{ ACTION_X_TOUCH , " /user/hand/left/input/x/touch " } ,
{ ACTION_Y_DOWN , " /user/hand/left/input/y/click " } ,
{ ACTION_Y_TOUCH , " /user/hand/left/input/y/touch " } ,
{ ACTION_VIBRATE , " /user/hand/left/output/haptic " } ,
{ ACTION_VIBRATE , " /user/hand/right/output/haptic " } ,
2023-01-31 03:03:53 +00:00
{ 0 , NULL }
2023-05-05 02:06:51 +00:00
} ,
2023-05-18 07:27:18 +00:00
[ PROFILE_PICO4 ] = ( Binding [ ] ) {
{ ACTION_HAND_POSE , " /user/hand/left/input/grip/pose " } ,
{ ACTION_HAND_POSE , " /user/hand/right/input/grip/pose " } ,
{ ACTION_POINTER_POSE , " /user/hand/left/input/aim/pose " } ,
{ ACTION_POINTER_POSE , " /user/hand/right/input/aim/pose " } ,
{ ACTION_TRIGGER_DOWN , " /user/hand/left/input/trigger/value " } ,
{ ACTION_TRIGGER_DOWN , " /user/hand/right/input/trigger/value " } ,
{ ACTION_TRIGGER_TOUCH , " /user/hand/left/input/trigger/touch " } ,
{ ACTION_TRIGGER_TOUCH , " /user/hand/right/input/trigger/touch " } ,
{ ACTION_TRIGGER_AXIS , " /user/hand/left/input/trigger/value " } ,
{ ACTION_TRIGGER_AXIS , " /user/hand/right/input/trigger/value " } ,
{ ACTION_THUMBSTICK_DOWN , " /user/hand/left/input/thumbstick/click " } ,
{ ACTION_THUMBSTICK_DOWN , " /user/hand/right/input/thumbstick/click " } ,
{ ACTION_THUMBSTICK_TOUCH , " /user/hand/left/input/thumbstick/touch " } ,
{ ACTION_THUMBSTICK_TOUCH , " /user/hand/right/input/thumbstick/touch " } ,
{ ACTION_THUMBSTICK_X , " /user/hand/left/input/thumbstick/x " } ,
{ ACTION_THUMBSTICK_X , " /user/hand/right/input/thumbstick/x " } ,
{ ACTION_THUMBSTICK_Y , " /user/hand/left/input/thumbstick/y " } ,
{ ACTION_THUMBSTICK_Y , " /user/hand/right/input/thumbstick/y " } ,
{ ACTION_MENU_DOWN , " /user/hand/left/input/menu/click " } ,
{ ACTION_MENU_DOWN , " /user/hand/right/input/system/click " } ,
{ ACTION_GRIP_DOWN , " /user/hand/left/input/squeeze/click " } ,
{ ACTION_GRIP_DOWN , " /user/hand/right/input/squeeze/click " } ,
{ ACTION_GRIP_AXIS , " /user/hand/left/input/squeeze/value " } ,
{ ACTION_GRIP_AXIS , " /user/hand/right/input/squeeze/value " } ,
{ ACTION_A_DOWN , " /user/hand/right/input/a/click " } ,
{ ACTION_A_TOUCH , " /user/hand/right/input/a/touch " } ,
{ ACTION_B_DOWN , " /user/hand/right/input/b/click " } ,
{ ACTION_B_TOUCH , " /user/hand/right/input/b/touch " } ,
{ ACTION_X_DOWN , " /user/hand/left/input/x/click " } ,
{ ACTION_X_TOUCH , " /user/hand/left/input/x/touch " } ,
{ ACTION_Y_DOWN , " /user/hand/left/input/y/click " } ,
{ ACTION_Y_TOUCH , " /user/hand/left/input/y/touch " } ,
{ ACTION_THUMBREST_TOUCH , " /user/hand/left/input/thumbrest/touch " } ,
{ ACTION_THUMBREST_TOUCH , " /user/hand/right/input/thumbrest/touch " } ,
{ ACTION_VIBRATE , " /user/hand/left/output/haptic " } ,
{ ACTION_VIBRATE , " /user/hand/right/output/haptic " } ,
{ 0 , NULL }
} ,
2023-05-05 02:06:51 +00:00
[ PROFILE_TRACKER ] = ( Binding [ ] ) {
{ ACTION_TRACKER_POSE , " /user/vive_tracker_htcx/role/left_elbow/input/grip/pose " } ,
{ ACTION_TRACKER_POSE , " /user/vive_tracker_htcx/role/right_elbow/input/grip/pose " } ,
{ ACTION_TRACKER_POSE , " /user/vive_tracker_htcx/role/left_shoulder/input/grip/pose " } ,
{ ACTION_TRACKER_POSE , " /user/vive_tracker_htcx/role/right_shoulder/input/grip/pose " } ,
{ ACTION_TRACKER_POSE , " /user/vive_tracker_htcx/role/chest/input/grip/pose " } ,
{ ACTION_TRACKER_POSE , " /user/vive_tracker_htcx/role/waist/input/grip/pose " } ,
{ ACTION_TRACKER_POSE , " /user/vive_tracker_htcx/role/left_knee/input/grip/pose " } ,
{ ACTION_TRACKER_POSE , " /user/vive_tracker_htcx/role/right_knee/input/grip/pose " } ,
{ ACTION_TRACKER_POSE , " /user/vive_tracker_htcx/role/left_foot/input/grip/pose " } ,
{ ACTION_TRACKER_POSE , " /user/vive_tracker_htcx/role/right_foot/input/grip/pose " } ,
{ ACTION_TRACKER_POSE , " /user/vive_tracker_htcx/role/camera/input/grip/pose " } ,
{ ACTION_TRACKER_POSE , " /user/vive_tracker_htcx/role/keyboard/input/grip/pose " } ,
{ 0 , NULL }
} ,
[ PROFILE_GAZE ] = ( Binding [ ] ) {
{ ACTION_GAZE_POSE , " /user/eyes_ext/input/gaze_ext/pose " } ,
{ 0 , NULL }
2022-03-20 22:39:02 +00:00
}
} ;
2022-03-21 22:05:23 +00:00
// Don't suggest bindings for unsupported input profiles
2023-05-27 00:03:11 +00:00
if ( ! state . features . ml2Controller ) {
bindings [ PROFILE_ML2 ] [ 0 ] . path = NULL ;
}
2023-05-12 09:47:47 +00:00
if ( ! state . features . picoController ) {
2023-05-05 02:06:51 +00:00
bindings [ PROFILE_PICO_NEO3 ] [ 0 ] . path = NULL ;
2023-05-26 23:44:14 +00:00
bindings [ PROFILE_PICO4 ] [ 0 ] . path = NULL ;
2023-05-05 02:06:51 +00:00
}
2022-03-21 01:04:06 +00:00
if ( ! state . features . viveTrackers ) {
bindings [ PROFILE_TRACKER ] [ 0 ] . path = NULL ;
}
2022-03-21 22:05:23 +00:00
if ( ! state . features . gaze ) {
bindings [ PROFILE_GAZE ] [ 0 ] . path = NULL ;
}
2022-03-20 22:39:02 +00:00
XrPath path ;
2022-03-30 18:13:55 +00:00
XrActionSuggestedBinding suggestedBindings [ 64 ] ;
2022-03-20 22:39:02 +00:00
for ( uint32_t i = 0 , count = 0 ; i < MAX_PROFILES ; i + + , count = 0 ) {
for ( uint32_t j = 0 ; bindings [ i ] [ j ] . path ; j + + , count + + ) {
2023-03-08 04:16:59 +00:00
XR_INIT ( xrStringToPath ( state . instance , bindings [ i ] [ j ] . path , & path ) , " Failed to create path " ) ;
2022-03-20 22:39:02 +00:00
suggestedBindings [ j ] . action = state . actions [ bindings [ i ] [ j ] . action ] ;
suggestedBindings [ j ] . binding = path ;
2019-04-11 20:47:25 +00:00
}
2022-03-21 01:04:06 +00:00
if ( count > 0 ) {
2023-03-08 04:16:59 +00:00
XR_INIT ( xrStringToPath ( state . instance , interactionProfilePaths [ i ] , & path ) , " Failed to create path " ) ;
2023-01-31 03:44:23 +00:00
XrResult result = ( xrSuggestInteractionProfileBindings ( state . instance , & ( XrInteractionProfileSuggestedBinding ) {
2022-03-21 01:04:06 +00:00
. type = XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING ,
. interactionProfile = path ,
. countSuggestedBindings = count ,
. suggestedBindings = suggestedBindings
} ) ) ;
2023-01-31 03:44:23 +00:00
if ( XR_FAILED ( result ) ) {
lovrLog ( LOG_WARN , " XR " , " Failed to suggest input bindings for %s " , interactionProfilePaths [ i ] ) ;
}
2022-03-21 01:04:06 +00:00
}
2019-04-11 20:47:25 +00:00
}
2019-08-04 02:06:46 +00:00
}
2022-07-17 18:05:24 +00:00
state . clipNear = .01f ;
2022-07-17 19:37:59 +00:00
state . clipFar = 0.f ;
2021-06-10 23:26:15 +00:00
state . frameState . type = XR_TYPE_FRAME_STATE ;
return true ;
}
static void openxr_start ( void ) {
2022-11-26 22:40:39 +00:00
# ifdef LOVR_DISABLE_GRAPHICS
2023-04-30 06:02:37 +00:00
bool hasGraphics = false ;
2019-10-02 23:29:09 +00:00
# else
2023-04-30 06:02:37 +00:00
bool hasGraphics = lovrGraphicsIsInitialized ( ) ;
2019-10-02 23:29:09 +00:00
# endif
2020-08-26 18:57:52 +00:00
2022-11-26 22:40:39 +00:00
{ // Session
2020-08-26 18:57:52 +00:00
XrSessionCreateInfo info = {
. type = XR_TYPE_SESSION_CREATE_INFO ,
2019-08-04 02:06:46 +00:00
. systemId = state . system
} ;
2022-11-26 22:40:39 +00:00
# if !defined(LOVR_DISABLE_GRAPHICS) && defined(LOVR_VK)
XrGraphicsBindingVulkanKHR graphicsBinding = {
. type = XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR ,
. next = info . next
} ;
XrGraphicsRequirementsVulkanKHR requirements = {
. type = XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR
} ;
if ( hasGraphics ) {
PFN_xrGetVulkanGraphicsRequirements2KHR xrGetVulkanGraphicsRequirements2KHR ;
XR_LOAD ( xrGetVulkanGraphicsRequirements2KHR ) ;
2023-03-08 04:16:59 +00:00
XR ( xrGetVulkanGraphicsRequirements2KHR ( state . instance , state . system , & requirements ) , " Failed to query Vulkan graphics requirements " ) ;
2022-11-26 22:40:39 +00:00
if ( XR_VERSION_MAJOR ( requirements . minApiVersionSupported ) > 1 | | XR_VERSION_MINOR ( requirements . minApiVersionSupported ) > 1 ) {
lovrThrow ( " OpenXR Vulkan version not supported " ) ;
}
graphicsBinding . instance = ( VkInstance ) gpu_vk_get_instance ( ) ;
graphicsBinding . physicalDevice = ( VkPhysicalDevice ) gpu_vk_get_physical_device ( ) ;
graphicsBinding . device = ( VkDevice ) gpu_vk_get_device ( ) ;
gpu_vk_get_queue ( & graphicsBinding . queueFamilyIndex , & graphicsBinding . queueIndex ) ;
info . next = & graphicsBinding ;
}
# endif
lovrAssert ( hasGraphics | | state . features . headless , " Graphics module is not available, and headless headset is not supported " ) ;
2020-10-27 00:59:21 +00:00
# ifdef XR_EXTX_overlay
XrSessionCreateInfoOverlayEXTX overlayInfo = {
. type = XR_TYPE_SESSION_CREATE_INFO_OVERLAY_EXTX ,
2022-03-21 01:04:06 +00:00
. next = info . next
2020-10-27 00:59:21 +00:00
} ;
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
} ;
2023-03-08 04:16:59 +00:00
XR ( xrCreateSession ( state . instance , & info , & state . session ) , " Failed to create session " ) ;
XR ( xrAttachSessionActionSets ( state . session , & attachInfo ) , " Failed to attach action sets " ) ;
2019-08-04 02:06:46 +00:00
}
2021-06-10 23:26:15 +00:00
{ // Spaaace
2023-06-26 23:41:42 +00:00
createReferenceSpace ( ) ;
2023-06-28 03:09:29 +00:00
2023-06-28 03:23:44 +00:00
XrReferenceSpaceCreateInfo referenceSpaceInfo = {
2023-06-28 03:09:29 +00:00
. type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO ,
. poseInReferenceSpace = { { 0.f , 0.f , 0.f , 1.f } , { 0.f , 0.f , 0.f } }
} ;
2023-06-28 03:23:44 +00:00
// Head
referenceSpaceInfo . referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW ;
XR ( xrCreateReferenceSpace ( state . session , & referenceSpaceInfo , & state . spaces [ DEVICE_HEAD ] ) , " Failed to create head space " ) ;
// Floor (may not be supported, which is okay)
referenceSpaceInfo . referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE ;
if ( XR_FAILED ( xrCreateReferenceSpace ( state . session , & referenceSpaceInfo , & state . spaces [ DEVICE_FLOOR ] ) ) ) {
state . spaces [ DEVICE_FLOOR ] = XR_NULL_HANDLE ;
}
2023-06-28 03:09:29 +00:00
2023-06-28 03:23:44 +00:00
// Action spaces
2023-06-28 03:09:29 +00:00
XrActionSpaceCreateInfo actionSpaceInfo = {
. type = XR_TYPE_ACTION_SPACE_CREATE_INFO ,
. poseInActionSpace = { { 0.f , 0.f , 0.f , 1.f } , { 0.f , 0.f , 0.f } }
} ;
for ( uint32_t i = 0 ; i < MAX_DEVICES ; i + + ) {
actionSpaceInfo . action = getPoseActionForDevice ( i ) ;
actionSpaceInfo . subactionPath = state . actionFilters [ i ] ;
if ( ! actionSpaceInfo . action ) {
continue ;
}
XR ( xrCreateActionSpace ( state . session , & actionSpaceInfo , & state . spaces [ i ] ) , " Failed to create action space " ) ;
}
2019-08-04 02:06:46 +00:00
}
2022-11-26 22:40:39 +00:00
// Swapchain
if ( hasGraphics ) {
2022-09-21 02:09:04 +00:00
state . depthFormat = state . config . stencil ? FORMAT_D32FS8 : FORMAT_D32F ;
if ( state . config . stencil & & ! lovrGraphicsIsFormatSupported ( state . depthFormat , TEXTURE_FEATURE_RENDER ) ) {
state . depthFormat = FORMAT_D24S8 ; // Guaranteed to be supported if the other one isn't
}
2023-04-30 06:02:37 +00:00
state . pass = lovrPassCreate ( ) ;
2022-06-06 03:38:14 +00:00
# ifdef LOVR_VK
2022-08-07 05:52:18 +00:00
XrSwapchainImageVulkanKHR images [ 2 ] [ MAX_IMAGES ] ;
2020-08-30 01:45:52 +00:00
for ( uint32_t i = 0 ; i < MAX_IMAGES ; i + + ) {
2022-08-07 05:52:18 +00:00
images [ COLOR ] [ i ] . type = images [ DEPTH ] [ i ] . type = XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR ;
images [ COLOR ] [ i ] . next = images [ DEPTH ] [ i ] . next = NULL ;
2020-08-30 01:45:52 +00:00
}
2022-08-07 05:52:18 +00:00
2022-09-21 02:09:04 +00:00
int64_t nativeColorFormat = VK_FORMAT_R8G8B8A8_SRGB ;
int64_t nativeDepthFormat ;
switch ( state . depthFormat ) {
case FORMAT_D32F : nativeDepthFormat = VK_FORMAT_D32_SFLOAT ; break ;
case FORMAT_D24S8 : nativeDepthFormat = VK_FORMAT_D24_UNORM_S8_UINT ; break ;
case FORMAT_D32FS8 : nativeDepthFormat = VK_FORMAT_D32_SFLOAT_S8_UINT ; break ;
default : lovrUnreachable ( ) ;
}
2020-08-30 01:45:52 +00:00
# endif
2022-08-20 18:03:14 +00:00
int64_t formats [ 128 ] ;
2022-08-07 05:52:18 +00:00
uint32_t formatCount ;
2023-03-08 04:16:59 +00:00
XR ( xrEnumerateSwapchainFormats ( state . session , COUNTOF ( formats ) , & formatCount , formats ) , " Failed to query swapchain formats " ) ;
2022-08-07 05:52:18 +00:00
bool supportsColor = false ;
bool supportsDepth = false ;
for ( uint32_t i = 0 ; i < formatCount & & ! supportsColor & & ! supportsDepth ; i + + ) {
2022-09-21 02:09:04 +00:00
if ( formats [ i ] = = nativeColorFormat ) {
2022-08-07 05:52:18 +00:00
supportsColor = true ;
2022-09-21 02:09:04 +00:00
} else if ( formats [ i ] = = nativeDepthFormat ) {
2022-08-07 05:52:18 +00:00
supportsDepth = true ;
}
}
lovrAssert ( supportsColor , " This VR runtime does not support sRGB rgba8 textures " ) ;
if ( ! supportsDepth ) {
state . features . depth = false ;
}
2019-08-04 02:06:46 +00:00
XrSwapchainCreateInfo info = {
. type = XR_TYPE_SWAPCHAIN_CREATE_INFO ,
2022-08-03 05:00:11 +00:00
. usageFlags = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_SAMPLED_BIT ,
2022-09-21 02:09:04 +00:00
. format = nativeColorFormat ,
2022-06-06 03:38:14 +00:00
. width = state . 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 ,
2022-06-06 03:38:14 +00:00
. arraySize = 2 ,
2019-08-04 02:06:46 +00:00
. mipCount = 1
} ;
2023-03-08 04:16:59 +00:00
XR ( xrCreateSwapchain ( state . session , & info , & state . swapchain [ COLOR ] ) , " Failed to create swapchain " ) ;
XR ( xrEnumerateSwapchainImages ( state . swapchain [ COLOR ] , MAX_IMAGES , & state . textureCount [ COLOR ] , ( XrSwapchainImageBaseHeader * ) images ) , " Failed to query swapchain images " ) ;
2019-08-04 02:06:46 +00:00
2022-08-07 05:52:18 +00:00
for ( uint32_t i = 0 ; i < state . textureCount [ COLOR ] ; i + + ) {
state . textures [ COLOR ] [ i ] = lovrTextureCreate ( & ( TextureInfo ) {
2022-06-06 03:38:14 +00:00
. type = TEXTURE_ARRAY ,
. format = FORMAT_RGBA8 ,
. srgb = true ,
. width = state . width ,
. height = state . height ,
2022-08-02 05:43:44 +00:00
. layers = 2 ,
2022-06-06 03:38:14 +00:00
. mipmaps = 1 ,
. samples = 1 ,
2022-08-03 05:00:11 +00:00
. usage = TEXTURE_RENDER | TEXTURE_SAMPLE ,
2022-08-07 05:52:18 +00:00
. handle = ( uintptr_t ) images [ COLOR ] [ i ] . image ,
. label = " OpenXR Color Swapchain " ,
2022-08-06 01:36:51 +00:00
. xr = true
2022-06-06 03:38:14 +00:00
} ) ;
2019-08-04 02:06:46 +00:00
}
2022-08-07 05:52:18 +00:00
if ( state . features . depth ) {
info . usageFlags = XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT ;
2022-09-21 02:09:04 +00:00
info . format = nativeDepthFormat ;
2022-08-07 05:52:18 +00:00
2023-03-08 04:16:59 +00:00
XR ( xrCreateSwapchain ( state . session , & info , & state . swapchain [ DEPTH ] ) , " Failed to create swapchain " ) ;
XR ( xrEnumerateSwapchainImages ( state . swapchain [ DEPTH ] , MAX_IMAGES , & state . textureCount [ DEPTH ] , ( XrSwapchainImageBaseHeader * ) images ) , " Failed to query swapchain images " ) ;
2022-08-07 05:52:18 +00:00
for ( uint32_t i = 0 ; i < state . textureCount [ DEPTH ] ; i + + ) {
state . textures [ DEPTH ] [ i ] = lovrTextureCreate ( & ( TextureInfo ) {
. type = TEXTURE_ARRAY ,
2022-09-21 02:09:04 +00:00
. format = state . depthFormat ,
2023-04-30 06:02:37 +00:00
. srgb = false ,
2022-08-07 05:52:18 +00:00
. width = state . width ,
. height = state . height ,
. layers = 2 ,
. mipmaps = 1 ,
. samples = 1 ,
. usage = TEXTURE_RENDER | TEXTURE_SAMPLE ,
. handle = ( uintptr_t ) images [ DEPTH ] [ i ] . image ,
. label = " OpenXR Depth Swapchain " ,
. xr = true
} ) ;
}
}
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 ,
. viewCount = 2 ,
. views = state . layerViews
} ;
// Pre-init composition layer views
state . layerViews [ 0 ] = ( XrCompositionLayerProjectionView ) {
. type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW ,
2022-08-07 05:52:18 +00:00
. subImage = { state . swapchain [ COLOR ] , { { 0 , 0 } , { state . width , state . height } } , 0 }
2019-08-04 02:06:46 +00:00
} ;
2022-06-06 03:38:14 +00:00
state . layerViews [ 1 ] = ( XrCompositionLayerProjectionView ) {
. type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW ,
2022-08-07 05:52:18 +00:00
. subImage = { state . swapchain [ COLOR ] , { { 0 , 0 } , { state . width , state . height } } , 1 }
2022-06-06 03:38:14 +00:00
} ;
2022-08-07 05:52:18 +00:00
if ( state . features . depth ) {
for ( uint32_t i = 0 ; i < 2 ; i + + ) {
state . layerViews [ i ] . next = & state . depthInfo [ i ] ;
state . depthInfo [ i ] = ( XrCompositionLayerDepthInfoKHR ) {
. type = XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR ,
. subImage . swapchain = state . swapchain [ DEPTH ] ,
. subImage . imageRect = state . layerViews [ i ] . subImage . imageRect ,
. subImage . imageArrayIndex = i ,
. minDepth = 0.f ,
. maxDepth = 1.f
} ;
}
}
2019-04-11 20:47:25 +00:00
}
2022-09-21 03:16:58 +00:00
if ( state . features . keyboardTracking ) {
XrKeyboardTrackingQueryFB queryInfo = {
. type = XR_TYPE_KEYBOARD_TRACKING_QUERY_FB ,
. flags = XR_KEYBOARD_TRACKING_QUERY_LOCAL_BIT_FB
} ;
XrKeyboardTrackingDescriptionFB keyboard ;
XrResult result = xrQuerySystemTrackedKeyboardFB ( state . session , & queryInfo , & keyboard ) ;
if ( result = = XR_SUCCESS ) {
XrKeyboardSpaceCreateInfoFB spaceInfo = {
. type = XR_TYPE_KEYBOARD_SPACE_CREATE_INFO_FB ,
. trackedKeyboardId = keyboard . trackedKeyboardId
} ;
xrCreateKeyboardSpaceFB ( state . session , & spaceInfo , & state . spaces [ DEVICE_KEYBOARD ] ) ;
} else {
state . features . keyboardTracking = false ;
}
}
2023-05-12 17:01:42 +00:00
if ( state . features . refreshRate ) {
XR ( xrEnumerateDisplayRefreshRatesFB ( state . session , 0 , & state . refreshRateCount , NULL ) , " Failed to query refresh rates " ) ;
state . refreshRates = malloc ( state . refreshRateCount * sizeof ( float ) ) ;
lovrAssert ( state . refreshRates , " Out of memory " ) ;
XR ( xrEnumerateDisplayRefreshRatesFB ( state . session , state . refreshRateCount , & state . refreshRateCount , state . refreshRates ) , " Failed to query refresh rates " ) ;
}
2019-04-11 20:47:25 +00:00
}
2022-08-09 06:27:35 +00:00
static void openxr_stop ( void ) {
if ( ! state . session ) {
return ;
}
2022-08-07 05:52:18 +00:00
for ( uint32_t i = 0 ; i < 2 ; i + + ) {
for ( uint32_t j = 0 ; j < state . textureCount [ i ] ; j + + ) {
lovrRelease ( state . textures [ i ] [ j ] , lovrTextureDestroy ) ;
}
2019-04-11 20:47:25 +00:00
}
2023-04-30 06:02:37 +00:00
lovrRelease ( state . pass , lovrPassDestroy ) ;
2022-08-09 06:27:35 +00:00
if ( state . swapchain [ COLOR ] ) xrDestroySwapchain ( state . swapchain [ COLOR ] ) ;
if ( state . swapchain [ DEPTH ] ) xrDestroySwapchain ( state . swapchain [ DEPTH ] ) ;
2022-08-03 05:00:11 +00:00
2022-08-09 06:27:35 +00:00
if ( state . handTrackers [ 0 ] ) xrDestroyHandTrackerEXT ( state . handTrackers [ 0 ] ) ;
if ( state . handTrackers [ 1 ] ) xrDestroyHandTrackerEXT ( state . handTrackers [ 1 ] ) ;
2019-04-11 20:47:25 +00:00
2023-02-06 03:51:12 +00:00
if ( state . passthrough ) xrDestroyPassthroughFB ( state . passthrough ) ;
if ( state . passthroughLayerHandle ) xrDestroyPassthroughLayerFB ( state . passthroughLayerHandle ) ;
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 ] ) ;
}
}
2019-10-02 23:29:09 +00:00
if ( state . referenceSpace ) xrDestroySpace ( state . referenceSpace ) ;
2022-08-06 01:36:51 +00:00
if ( state . session ) xrDestroySession ( state . session ) ;
2022-08-09 06:27:35 +00:00
state . session = NULL ;
}
static void openxr_destroy ( void ) {
openxr_stop ( ) ;
if ( state . actionSet ) xrDestroyActionSet ( state . actionSet ) ;
2019-10-02 23:29:09 +00:00
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 ) {
2022-03-18 02:30:21 +00:00
XrSystemProperties properties = { . type = XR_TYPE_SYSTEM_PROPERTIES } ;
2023-03-08 04:16:59 +00:00
XR ( xrGetSystemProperties ( state . instance , state . system , & properties ) , " Failed to query system properties " ) ;
2019-04-11 20:47:25 +00:00
strncpy ( name , properties . systemName , length - 1 ) ;
name [ length - 1 ] = ' \0 ' ;
return true ;
}
Replace HeadsetOrigin with 'seated' flag;
Origin type used to be a query-able property of the VR system that
indicated whether the tracking was roomscale or seated-scale.
The t.headset.offset config value could be used to design an
origin-agnostic experience, which by default shifted content up 1.7
meters when tracking was seated-scale. That way, stuff rendered at
y=1.7m was always at "eye level". It worked pretty well.
It's getting replaced with a t.headset.seated flag.
- If seated is false (the default), the origin of the coordinate space
will be on the floor, enabling the y=1.7m eye level paradigm. If
tracking is not roomscale, a floor offset of 1.7m will be emulated.
- If seated is true, the origin of the coordinate space will be y=0
at eye level (where the headset was when the app started). This is
the case on both roomscale and seated-scale tracking.
So basically 'seated' is an opt-in preference for where the app wants
its vertical origin to be.
One advantage of this is that it's possible to consistently get a y=0
eye level coordinate space, which was not possible before. This makes
it easier to design simpler experiences that only need to render a
floating UI and don't want to render a full environment or deal with
offsetting everything relative to a 'floor'. This also makes it easier
to implement hybrid VR+flatscreen experiences, because the camera is at
y=0 when the headset module is disabled.
The opt-in nature of the flag, coupled with the fact that it is
consistent across all types of tracking and hardware, is hopefully a
more useful design.
2023-06-28 23:38:36 +00:00
static bool openxr_isSeated ( void ) {
return state . config . seated ;
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 ;
}
2023-05-12 17:01:42 +00:00
static float openxr_getRefreshRate ( void ) {
2022-03-21 07:51:24 +00:00
if ( ! state . features . refreshRate ) return 0.f ;
2023-05-12 17:01:42 +00:00
float refreshRate ;
XR ( xrGetDisplayRefreshRateFB ( state . session , & refreshRate ) , " Failed to query refresh rate " ) ;
return refreshRate ;
2022-05-06 00:05:45 +00:00
}
2023-05-12 17:01:42 +00:00
static bool openxr_setRefreshRate ( float refreshRate ) {
2022-05-06 17:46:40 +00:00
if ( ! state . features . refreshRate ) return false ;
2023-05-12 17:01:42 +00:00
XrResult result = xrRequestDisplayRefreshRateFB ( state . session , refreshRate ) ;
if ( result = = XR_ERROR_DISPLAY_REFRESH_RATE_UNSUPPORTED_FB ) return false ;
XR ( result , " Failed to set refresh rate " ) ;
2022-05-06 00:05:45 +00:00
return true ;
}
2022-05-06 17:45:25 +00:00
2023-05-12 17:01:42 +00:00
static const float * openxr_getRefreshRates ( uint32_t * count ) {
* count = state . refreshRateCount ;
return state . refreshRates ;
}
2023-05-12 13:13:48 +00:00
static XrEnvironmentBlendMode convertPassthroughMode ( PassthroughMode mode ) {
switch ( mode ) {
case PASSTHROUGH_OPAQUE : return XR_ENVIRONMENT_BLEND_MODE_OPAQUE ;
case PASSTHROUGH_BLEND : return XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND ;
case PASSTHROUGH_ADD : return XR_ENVIRONMENT_BLEND_MODE_ADDITIVE ;
default : lovrUnreachable ( ) ;
}
}
static PassthroughMode openxr_getPassthrough ( void ) {
switch ( state . blendMode ) {
case XR_ENVIRONMENT_BLEND_MODE_OPAQUE : return PASSTHROUGH_OPAQUE ;
case XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND : return PASSTHROUGH_BLEND ;
case XR_ENVIRONMENT_BLEND_MODE_ADDITIVE : return PASSTHROUGH_ADD ;
default : lovrUnreachable ( ) ;
}
}
static bool openxr_setPassthrough ( PassthroughMode mode ) {
if ( state . features . questPassthrough ) {
if ( mode = = PASSTHROUGH_ADD ) {
return false ;
}
if ( ! state . passthrough ) {
XrPassthroughCreateInfoFB info = { . type = XR_TYPE_PASSTHROUGH_CREATE_INFO_FB } ;
if ( XR_FAILED ( xrCreatePassthroughFB ( state . session , & info , & state . passthrough ) ) ) {
return false ;
}
XrPassthroughLayerCreateInfoFB layerInfo = {
. type = XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB ,
. passthrough = state . passthrough ,
. purpose = XR_PASSTHROUGH_LAYER_PURPOSE_RECONSTRUCTION_FB ,
. flags = XR_PASSTHROUGH_IS_RUNNING_AT_CREATION_BIT_FB
} ;
if ( XR_FAILED ( xrCreatePassthroughLayerFB ( state . session , & layerInfo , & state . passthroughLayerHandle ) ) ) {
xrDestroyPassthroughFB ( state . passthrough ) ;
state . passthrough = NULL ;
return false ;
}
state . passthroughLayer = ( XrCompositionLayerPassthroughFB ) {
. type = XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_FB ,
. layerHandle = state . passthroughLayerHandle
} ;
}
bool enable = mode = = PASSTHROUGH_BLEND | | mode = = PASSTHROUGH_TRANSPARENT ;
if ( state . passthroughActive = = enable ) {
return true ;
}
if ( enable ) {
if ( XR_SUCCEEDED ( xrPassthroughStartFB ( state . passthrough ) ) ) {
state . passthroughActive = true ;
return true ;
}
} else {
if ( XR_SUCCEEDED ( xrPassthroughPauseFB ( state . passthrough ) ) ) {
state . passthroughActive = false ;
return true ;
}
}
return false ;
}
if ( mode = = PASSTHROUGH_DEFAULT ) {
state . blendMode = state . blendModes [ 0 ] ;
return true ;
} else if ( mode = = PASSTHROUGH_TRANSPARENT ) {
for ( uint32_t i = 0 ; i < state . blendModeCount ; i + + ) {
switch ( state . blendModes [ i ] ) {
case XR_ENVIRONMENT_BLEND_MODE_ADDITIVE :
case XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND :
state . blendMode = state . blendModes [ i ] ;
return true ;
default : continue ;
}
}
} else {
XrEnvironmentBlendMode blendMode = convertPassthroughMode ( mode ) ;
for ( uint32_t i = 0 ; i < state . blendModeCount ; i + + ) {
if ( state . blendModes [ i ] = = blendMode ) {
state . blendMode = state . blendModes [ i ] ;
return true ;
}
}
}
return false ;
}
static bool openxr_isPassthroughSupported ( PassthroughMode mode ) {
XrEnvironmentBlendMode blendMode = convertPassthroughMode ( mode ) ;
for ( uint32_t i = 0 ; i < state . blendModeCount ; i + + ) {
if ( state . blendModes [ i ] = = blendMode ) {
return true ;
}
}
return false ;
}
2019-04-30 23:59:15 +00:00
static double openxr_getDisplayTime ( void ) {
2023-05-02 02:08:25 +00:00
return ( state . frameState . predictedDisplayTime - state . epoch ) / 1e9 ;
2019-04-30 22:31:38 +00:00
}
2022-03-23 00:52:16 +00:00
static double openxr_getDeltaTime ( void ) {
return ( state . frameState . predictedDisplayTime - state . lastDisplayTime ) / 1e9 ;
}
2023-05-02 01:47:57 +00:00
static XrViewStateFlags getViews ( XrView views [ 2 ] , uint32_t * count ) {
if ( state . frameState . predictedDisplayTime < = 0 ) {
return 0 ;
}
2020-01-28 05:02:37 +00:00
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 } ;
2023-03-08 04:16:59 +00:00
XR ( xrLocateViews ( state . session , & viewLocateInfo , & viewState , 2 , count , views ) , " Failed to locate views " ) ;
2023-05-02 01:47:57 +00:00
return viewState . viewStateFlags ;
2020-01-28 05:02:37 +00:00
}
static uint32_t openxr_getViewCount ( void ) {
2023-05-02 01:57:41 +00:00
return 2 ;
2020-01-28 05:02:37 +00:00
}
static bool openxr_getViewPose ( uint32_t view , float * position , float * orientation ) {
uint32_t count ;
XrView views [ 2 ] ;
2023-05-02 01:47:57 +00:00
XrViewStateFlags flags = getViews ( views , & count ) ;
if ( view > = count | | ! flags ) {
return false ;
}
if ( flags & XR_VIEW_STATE_POSITION_VALID_BIT ) {
2020-08-19 03:09:06 +00:00
memcpy ( position , & views [ view ] . pose . position . x , 3 * sizeof ( float ) ) ;
2023-05-02 01:47:57 +00:00
} else {
memset ( position , 0 , 3 * sizeof ( float ) ) ;
}
if ( flags & XR_VIEW_STATE_ORIENTATION_VALID_BIT ) {
2020-08-19 03:09:06 +00:00
memcpy ( orientation , & views [ view ] . pose . orientation . x , 4 * sizeof ( float ) ) ;
2020-01-28 05:02:37 +00:00
} else {
2023-05-02 01:47:57 +00:00
memset ( orientation , 0 , 4 * sizeof ( float ) ) ;
2020-01-28 05:02:37 +00:00
}
2023-05-02 01:47:57 +00:00
return true ;
2020-01-28 05:02:37 +00:00
}
static bool openxr_getViewAngles ( uint32_t view , float * left , float * right , float * up , float * down ) {
uint32_t count ;
XrView views [ 2 ] ;
2023-05-02 01:47:57 +00:00
XrViewStateFlags flags = getViews ( views , & count ) ;
if ( view > = count | | ! flags ) {
2020-01-28 05:02:37 +00:00
return false ;
}
2023-05-02 01:47:57 +00:00
* left = - views [ view ] . fov . angleLeft ;
* right = views [ view ] . fov . angleRight ;
* up = views [ view ] . fov . angleUp ;
* down = - views [ view ] . fov . angleDown ;
return true ;
2020-01-28 05:02:37 +00:00
}
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 ;
2023-06-26 23:41:42 +00:00
if ( XR_SUCCEEDED ( xrGetReferenceSpaceBoundsRect ( state . session , XR_REFERENCE_SPACE_TYPE_STAGE , & bounds ) ) ) {
2019-08-04 02:06:46 +00:00
* 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 ;
}
2022-06-06 03:38:14 +00:00
static bool openxr_getPose ( Device device , float * position , float * orientation ) {
2023-05-02 01:47:57 +00:00
if ( state . frameState . predictedDisplayTime < = 0 ) {
return false ;
}
2022-03-21 01:04:06 +00:00
XrAction action = getPoseActionForDevice ( device ) ;
2023-02-02 02:36:05 +00:00
XrActionStatePose poseState = { . type = XR_TYPE_ACTION_STATE_POSE } ;
2022-03-20 00:49:13 +00:00
2023-02-02 02:36:05 +00:00
// If there's a pose action for this device, see if the action is active before locating its space
// (because Oculus runtimes had a bug that forced checking the action before locating the space)
2022-03-20 00:49:13 +00:00
if ( action ) {
XrActionStateGetInfo info = {
. type = XR_TYPE_ACTION_STATE_GET_INFO ,
. action = action ,
2022-03-21 01:04:06 +00:00
. subactionPath = state . actionFilters [ device ]
2022-03-20 00:49:13 +00:00
} ;
2023-03-08 04:16:59 +00:00
XR ( xrGetActionStatePose ( state . session , & info , & poseState ) , " Failed to get pose " ) ;
2023-02-02 02:36:05 +00:00
}
2022-03-20 00:49:13 +00:00
2023-02-02 02:36:05 +00:00
// If there's no space to locate, or the pose action isn't active, fall back to alternative
// methods, e.g. hand tracking can sometimes be used for grip/aim/elbow devices
if ( ! state . spaces [ device ] | | ( action & & ! poseState . isActive ) ) {
bool point = false ;
bool elbow = false ;
2022-03-21 22:45:48 +00:00
2023-02-02 02:36:05 +00:00
if ( state . features . handTrackingAim & & ( device = = DEVICE_HAND_LEFT_POINT | | device = = DEVICE_HAND_RIGHT_POINT ) ) {
device = DEVICE_HAND_LEFT + ( device = = DEVICE_HAND_RIGHT_POINT ) ;
point = true ;
}
2022-03-21 22:45:48 +00:00
2023-02-02 02:36:05 +00:00
if ( state . features . handTrackingElbow & & ( device = = DEVICE_ELBOW_LEFT | | device = = DEVICE_ELBOW_RIGHT ) ) {
device = DEVICE_HAND_LEFT + ( device = = DEVICE_ELBOW_RIGHT ) ;
elbow = true ;
}
2022-03-20 00:49:13 +00:00
2023-02-02 02:36:05 +00:00
XrHandTrackerEXT tracker = getHandTracker ( device ) ;
2022-03-20 00:49:13 +00:00
2023-02-02 02:36:05 +00:00
if ( ! tracker ) {
return false ;
}
2022-03-20 00:49:13 +00:00
2023-02-02 02:36:05 +00:00
XrHandJointsLocateInfoEXT info = {
. type = XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT ,
. baseSpace = state . referenceSpace ,
. time = state . frameState . predictedDisplayTime
} ;
2022-03-20 00:49:13 +00:00
2023-02-02 02:36:05 +00:00
XrHandJointLocationEXT joints [ MAX_HAND_JOINTS ] ;
XrHandJointLocationsEXT hand = {
. type = XR_TYPE_HAND_JOINT_LOCATIONS_EXT ,
. jointCount = 26 + state . features . handTrackingElbow ,
. jointLocations = joints
} ;
2022-03-21 22:45:48 +00:00
2023-02-02 02:36:05 +00:00
XrHandTrackingAimStateFB aimState = {
. type = XR_TYPE_HAND_TRACKING_AIM_STATE_FB
} ;
2022-03-21 22:45:48 +00:00
2023-02-02 02:36:05 +00:00
if ( point ) {
hand . next = & aimState ;
}
2022-03-20 00:49:13 +00:00
2023-02-02 02:36:05 +00:00
if ( XR_FAILED ( xrLocateHandJointsEXT ( tracker , & info , & hand ) ) | | ! hand . isActive ) {
return false ;
2022-03-20 00:49:13 +00:00
}
2023-02-02 02:36:05 +00:00
XrPosef * pose ;
if ( point ) {
pose = & aimState . aimPose ;
} else if ( elbow ) {
pose = & joints [ XR_HAND_FOREARM_JOINT_ELBOW_ULTRALEAP ] . pose ;
} else {
pose = & joints [ XR_HAND_JOINT_WRIST_EXT ] . pose ;
}
memcpy ( orientation , & pose - > orientation , 4 * sizeof ( float ) ) ;
memcpy ( position , & pose - > position , 3 * sizeof ( float ) ) ;
return true ;
2022-03-20 00:49:13 +00:00
}
XrSpaceLocation location = { . type = XR_TYPE_SPACE_LOCATION } ;
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
}
2022-06-06 03:38:14 +00:00
static bool openxr_getVelocity ( Device device , float * linearVelocity , float * angularVelocity ) {
2023-05-02 01:47:57 +00:00
if ( ! state . spaces [ device ] | | state . frameState . predictedDisplayTime < = 0 ) {
2019-08-04 02:06:46 +00:00
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
}
2022-03-21 01:04:06 +00:00
static XrPath getInputActionFilter ( Device device ) {
return ( device = = DEVICE_HAND_LEFT | | device = = DEVICE_HAND_RIGHT ) ? state . actionFilters [ device ] : 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 ,
2022-03-21 01:04:06 +00:00
. subactionPath = getInputActionFilter ( device )
2019-08-04 02:06:46 +00:00
} ;
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 ;
2022-03-14 21:09:47 +00:00
case BUTTON_THUMBREST : info . action = touch ? state . actions [ ACTION_THUMBREST_TOUCH ] : XR_NULL_HANDLE ; break ;
2022-03-21 01:04:06 +00:00
case BUTTON_THUMBSTICK : info . action = state . actions [ ACTION_THUMBSTICK_DOWN + touch ] ; break ;
2019-08-04 02:06:46 +00:00
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 ;
2022-03-17 23:33:18 +00:00
case BUTTON_A : info . action = state . actions [ ACTION_A_DOWN + touch ] ; break ;
case BUTTON_B : info . action = state . actions [ ACTION_B_DOWN + touch ] ; break ;
case BUTTON_X : info . action = state . actions [ ACTION_X_DOWN + touch ] ; break ;
case BUTTON_Y : info . action = state . actions [ ACTION_Y_DOWN + touch ] ; break ;
2019-05-08 03:04:59 +00:00
default : return false ;
2019-04-11 20:47:25 +00:00
}
2022-03-14 21:09:47 +00:00
if ( ! info . action ) {
return false ;
}
2020-10-26 08:30:08 +00:00
XrActionStateBoolean actionState = { . type = XR_TYPE_ACTION_STATE_BOOLEAN } ;
2023-03-08 04:16:59 +00:00
XR ( xrGetActionStateBoolean ( state . session , & info , & actionState ) , " Failed to read button input " ) ;
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 } ;
2023-03-08 04:16:59 +00:00
XR ( xrGetActionStateFloat ( state . session , & info , & actionState ) , " Failed to read axis input " ) ;
2020-08-23 22:11:20 +00:00
* 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 ) {
2022-03-21 01:04:06 +00:00
XrPath filter = getInputActionFilter ( device ) ;
2020-08-23 22:11:20 +00:00
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 ) {
2022-03-21 22:45:48 +00:00
case AXIS_TRIGGER :
if ( getFloatAction ( ACTION_TRIGGER_AXIS , filter , & value [ 0 ] ) ) {
return true ;
}
// FB extension for pinch
if ( ! state . features . handTrackingAim ) {
return false ;
}
XrHandTrackerEXT tracker = getHandTracker ( device ) ;
if ( ! tracker ) {
return false ;
}
XrHandJointsLocateInfoEXT info = {
. type = XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT ,
. baseSpace = state . referenceSpace ,
. time = state . frameState . predictedDisplayTime
} ;
XrHandTrackingAimStateFB aimState = {
. type = XR_TYPE_HAND_TRACKING_AIM_STATE_FB
} ;
2023-02-02 02:36:05 +00:00
XrHandJointLocationEXT joints [ MAX_HAND_JOINTS ] ;
2022-03-21 22:45:48 +00:00
XrHandJointLocationsEXT hand = {
. type = XR_TYPE_HAND_JOINT_LOCATIONS_EXT ,
. next = & aimState ,
2023-02-02 02:36:05 +00:00
. jointCount = 26 + state . features . handTrackingElbow ,
2022-03-21 22:45:48 +00:00
. jointLocations = joints
} ;
if ( XR_FAILED ( xrLocateHandJointsEXT ( tracker , & info , & hand ) ) ) {
return false ;
}
* value = aimState . pinchStrengthIndex ;
2022-10-02 20:59:59 +00:00
return true ;
2020-08-28 23:04:45 +00:00
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 ) {
2022-03-20 00:49:13 +00:00
XrHandTrackerEXT tracker = getHandTracker ( device ) ;
2020-08-28 23:04:45 +00:00
2023-05-02 01:47:57 +00:00
if ( ! tracker | | state . frameState . predictedDisplayTime < = 0 ) {
2020-11-18 19:52:52 +00:00
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
} ;
2023-02-02 02:36:05 +00:00
XrHandJointLocationEXT joints [ MAX_HAND_JOINTS ] ;
2020-08-28 23:04:45 +00:00
XrHandJointLocationsEXT hand = {
. type = XR_TYPE_HAND_JOINT_LOCATIONS_EXT ,
2023-02-02 02:36:05 +00:00
. jointCount = 26 + state . features . handTrackingElbow ,
2020-08-28 23:04:45 +00:00
. jointLocations = joints
} ;
2022-03-20 00:49:13 +00:00
if ( XR_FAILED ( xrLocateHandJointsEXT ( tracker , & info , & hand ) ) | | ! hand . isActive ) {
2020-08-28 23:04:45 +00:00
return false ;
}
float * pose = poses ;
2023-02-02 02:36:05 +00:00
for ( uint32_t i = 0 ; i < HAND_JOINT_COUNT ; i + + ) {
2020-08-28 23:04:45 +00:00
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 ] ,
2022-03-21 01:04:06 +00:00
. subactionPath = getInputActionFilter ( device )
2019-08-04 02:06:46 +00:00
} ;
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 * 1e9 f + .5f ) ,
2019-04-11 20:47:25 +00:00
. frequency = frequency ,
. amplitude = power
} ;
2023-03-08 04:16:59 +00:00
XR ( xrApplyHapticFeedback ( state . session , & info , ( XrHapticBaseHeader * ) & vibration ) , " Failed to apply haptic feedback " ) ;
2019-04-11 20:47:25 +00:00
return true ;
}
2023-06-28 03:45:44 +00:00
static void openxr_stopVibration ( Device device ) {
XrHapticActionInfo info = {
. type = XR_TYPE_HAPTIC_ACTION_INFO ,
. action = state . actions [ ACTION_VIBRATE ] ,
. subactionPath = getInputActionFilter ( device )
} ;
if ( info . subactionPath = = XR_NULL_PATH ) {
return ;
}
XR ( xrStopHapticFeedback ( state . session , & info ) , " Failed to stop haptic feedback " ) ;
}
2022-11-16 17:19:57 +00:00
static ModelData * openxr_newModelDataFB ( XrHandTrackerEXT tracker , bool animated ) {
2022-09-23 21:36:20 +00:00
// First, figure out how much data there is
2022-03-20 00:49:13 +00:00
XrHandTrackingMeshFB mesh = { . type = XR_TYPE_HAND_TRACKING_MESH_FB } ;
XrResult result = xrGetHandMeshFB ( tracker , & mesh ) ;
if ( XR_FAILED ( result ) ) {
return NULL ;
}
uint32_t jointCount = mesh . jointCapacityInput = mesh . jointCountOutput ;
uint32_t vertexCount = mesh . vertexCapacityInput = mesh . vertexCountOutput ;
uint32_t indexCount = mesh . indexCapacityInput = mesh . indexCountOutput ;
2022-09-23 21:36:20 +00:00
// Sum all the sizes to get the total amount of memory required
2022-03-20 00:49:13 +00:00
size_t sizes [ 10 ] ;
size_t totalSize = 0 ;
size_t alignment = 8 ;
totalSize + = sizes [ 0 ] = ALIGN ( jointCount * sizeof ( XrPosef ) , alignment ) ;
totalSize + = sizes [ 1 ] = ALIGN ( jointCount * sizeof ( float ) , alignment ) ;
totalSize + = sizes [ 2 ] = ALIGN ( jointCount * sizeof ( XrHandJointEXT ) , alignment ) ;
totalSize + = sizes [ 3 ] = ALIGN ( vertexCount * sizeof ( XrVector3f ) , alignment ) ;
totalSize + = sizes [ 4 ] = ALIGN ( vertexCount * sizeof ( XrVector3f ) , alignment ) ;
totalSize + = sizes [ 5 ] = ALIGN ( vertexCount * sizeof ( XrVector2f ) , alignment ) ;
totalSize + = sizes [ 6 ] = ALIGN ( vertexCount * sizeof ( XrVector4sFB ) , alignment ) ;
totalSize + = sizes [ 7 ] = ALIGN ( vertexCount * sizeof ( XrVector4f ) , alignment ) ;
totalSize + = sizes [ 8 ] = ALIGN ( indexCount * sizeof ( int16_t ) , alignment ) ;
totalSize + = sizes [ 9 ] = ALIGN ( jointCount * 16 * sizeof ( float ) , alignment ) ;
2022-09-23 21:36:20 +00:00
// Allocate
2022-03-20 00:49:13 +00:00
char * meshData = malloc ( totalSize ) ;
if ( ! meshData ) return NULL ;
2022-09-23 21:36:20 +00:00
// Write offseted pointers to the mesh struct, to be filled in by the second call
2022-03-20 00:49:13 +00:00
size_t offset = 0 ;
mesh . jointBindPoses = ( XrPosef * ) ( meshData + offset ) , offset + = sizes [ 0 ] ;
mesh . jointRadii = ( float * ) ( meshData + offset ) , offset + = sizes [ 1 ] ;
mesh . jointParents = ( XrHandJointEXT * ) ( meshData + offset ) , offset + = sizes [ 2 ] ;
mesh . vertexPositions = ( XrVector3f * ) ( meshData + offset ) , offset + = sizes [ 3 ] ;
mesh . vertexNormals = ( XrVector3f * ) ( meshData + offset ) , offset + = sizes [ 4 ] ;
mesh . vertexUVs = ( XrVector2f * ) ( meshData + offset ) , offset + = sizes [ 5 ] ;
mesh . vertexBlendIndices = ( XrVector4sFB * ) ( meshData + offset ) , offset + = sizes [ 6 ] ;
mesh . vertexBlendWeights = ( XrVector4f * ) ( meshData + offset ) , offset + = sizes [ 7 ] ;
mesh . indices = ( int16_t * ) ( meshData + offset ) , offset + = sizes [ 8 ] ;
float * inverseBindMatrices = ( float * ) ( meshData + offset ) ; offset + = sizes [ 9 ] ;
2022-08-06 07:29:46 +00:00
lovrAssert ( offset = = totalSize , " Unreachable! " ) ;
2022-03-20 00:49:13 +00:00
2022-09-23 21:36:20 +00:00
// Populate the data
2022-03-20 00:49:13 +00:00
result = xrGetHandMeshFB ( tracker , & mesh ) ;
if ( XR_FAILED ( result ) ) {
free ( meshData ) ;
return NULL ;
}
ModelData * model = calloc ( 1 , sizeof ( ModelData ) ) ;
lovrAssert ( model , " Out of memory " ) ;
model - > ref = 1 ;
model - > blobCount = 1 ;
model - > bufferCount = 6 ;
model - > attributeCount = 6 ;
model - > primitiveCount = 1 ;
model - > skinCount = 1 ;
model - > jointCount = jointCount ;
model - > childCount = jointCount + 1 ;
model - > nodeCount = 2 + jointCount ;
lovrModelDataAllocate ( model ) ;
2022-11-19 15:26:44 +00:00
model - > metadata = malloc ( sizeof ( XrHandTrackerEXT ) ) ;
lovrAssert ( model - > metadata , " Out of memory " ) ;
* ( ( XrHandTrackerEXT * ) model - > metadata ) = tracker ;
model - > metadataSize = sizeof ( XrHandTrackerEXT ) ;
model - > metadataType = META_HANDTRACKING_FB ;
2022-03-20 00:49:13 +00:00
model - > blobs [ 0 ] = lovrBlobCreate ( meshData , totalSize , " Hand Mesh Data " ) ;
model - > buffers [ 0 ] = ( ModelBuffer ) {
. offset = ( char * ) mesh . vertexPositions - ( char * ) meshData ,
. data = ( char * ) mesh . vertexPositions ,
. size = sizeof ( mesh . vertexPositions [ 0 ] ) * vertexCount ,
. stride = sizeof ( mesh . vertexPositions [ 0 ] )
} ;
model - > buffers [ 1 ] = ( ModelBuffer ) {
. offset = ( char * ) mesh . vertexNormals - ( char * ) meshData ,
. data = ( char * ) mesh . vertexNormals ,
. size = sizeof ( mesh . vertexNormals [ 0 ] ) * vertexCount ,
. stride = sizeof ( mesh . vertexNormals [ 0 ] )
} ;
model - > buffers [ 2 ] = ( ModelBuffer ) {
. offset = ( char * ) mesh . vertexUVs - ( char * ) meshData ,
. data = ( char * ) mesh . vertexUVs ,
. size = sizeof ( mesh . vertexUVs [ 0 ] ) * vertexCount ,
. stride = sizeof ( mesh . vertexUVs [ 0 ] )
} ;
model - > buffers [ 3 ] = ( ModelBuffer ) {
. offset = ( char * ) mesh . vertexBlendIndices - ( char * ) meshData ,
. data = ( char * ) mesh . vertexBlendIndices ,
. size = sizeof ( mesh . vertexBlendIndices [ 0 ] ) * vertexCount ,
. stride = sizeof ( mesh . vertexBlendIndices [ 0 ] )
} ;
model - > buffers [ 4 ] = ( ModelBuffer ) {
. offset = ( char * ) mesh . vertexBlendWeights - ( char * ) meshData ,
. data = ( char * ) mesh . vertexBlendWeights ,
. size = sizeof ( mesh . vertexBlendWeights [ 0 ] ) * vertexCount ,
. stride = sizeof ( mesh . vertexBlendWeights [ 0 ] )
} ;
model - > buffers [ 5 ] = ( ModelBuffer ) {
. offset = ( char * ) mesh . indices - ( char * ) meshData ,
. data = ( char * ) mesh . indices ,
. size = sizeof ( mesh . indices [ 0 ] ) * indexCount ,
. stride = sizeof ( mesh . indices [ 0 ] )
} ;
2022-08-06 07:29:46 +00:00
model - > attributes [ 0 ] = ( ModelAttribute ) { . buffer = 0 , . type = F32 , . components = 3 , . count = vertexCount } ;
2022-03-20 00:49:13 +00:00
model - > attributes [ 1 ] = ( ModelAttribute ) { . buffer = 1 , . type = F32 , . components = 3 } ;
model - > attributes [ 2 ] = ( ModelAttribute ) { . buffer = 2 , . type = F32 , . components = 2 } ;
model - > attributes [ 3 ] = ( ModelAttribute ) { . buffer = 3 , . type = I16 , . components = 4 } ;
model - > attributes [ 4 ] = ( ModelAttribute ) { . buffer = 4 , . type = F32 , . components = 4 } ;
model - > attributes [ 5 ] = ( ModelAttribute ) { . buffer = 5 , . type = U16 , . count = indexCount } ;
model - > primitives [ 0 ] = ( ModelPrimitive ) {
. mode = DRAW_TRIANGLES ,
. attributes = {
[ ATTR_POSITION ] = & model - > attributes [ 0 ] ,
[ ATTR_NORMAL ] = & model - > attributes [ 1 ] ,
2022-08-14 04:10:03 +00:00
[ ATTR_UV ] = & model - > attributes [ 2 ] ,
2022-07-04 00:26:31 +00:00
[ ATTR_JOINTS ] = & model - > attributes [ 3 ] ,
2022-03-20 00:49:13 +00:00
[ ATTR_WEIGHTS ] = & model - > attributes [ 4 ]
} ,
. indices = & model - > attributes [ 5 ] ,
. material = ~ 0u
} ;
// The nodes in the Model correspond directly to the joints in the skin, for convenience
uint32_t * children = model - > children ;
model - > skins [ 0 ] . joints = model - > joints ;
model - > skins [ 0 ] . jointCount = model - > jointCount ;
model - > skins [ 0 ] . inverseBindMatrices = inverseBindMatrices ;
for ( uint32_t i = 0 ; i < model - > jointCount ; i + + ) {
model - > joints [ i ] = i ;
// Joint node
model - > nodes [ i ] = ( ModelNode ) {
2022-08-03 06:06:49 +00:00
. transform . translation = { 0.f , 0.f , 0.f } ,
. transform . rotation = { 0.f , 0.f , 0.f , 1.f } ,
. transform . scale = { 1.f , 1.f , 1.f } ,
2022-03-20 00:49:13 +00:00
. skin = ~ 0u
} ;
// Inverse bind matrix
XrPosef * pose = & mesh . jointBindPoses [ i ] ;
float * inverseBindMatrix = inverseBindMatrices + 16 * i ;
mat4_fromQuat ( inverseBindMatrix , & pose - > orientation . x ) ;
memcpy ( inverseBindMatrix + 12 , & pose - > position . x , 3 * sizeof ( float ) ) ;
mat4_invert ( inverseBindMatrix ) ;
// Add child bones by looking for any bones that have a parent of the current bone.
// This is somewhat slow; use the fact that bones are sorted to reduce the work a bit.
model - > nodes [ i ] . childCount = 0 ;
model - > nodes [ i ] . children = children ;
for ( uint32_t j = i + 1 ; j < jointCount ; j + + ) {
if ( mesh . jointParents [ j ] = = i ) {
model - > nodes [ i ] . children [ model - > nodes [ i ] . childCount + + ] = j ;
children + + ;
}
}
}
// Add a node that holds the skinned mesh
model - > nodes [ model - > jointCount ] = ( ModelNode ) {
2022-09-26 21:58:34 +00:00
. transform . translation = { 0.f , 0.f , 0.f } ,
. transform . rotation = { 0.f , 0.f , 0.f , 1.f } ,
. transform . scale = { 1.f , 1.f , 1.f } ,
2022-03-20 00:49:13 +00:00
. primitiveIndex = 0 ,
. primitiveCount = 1 ,
. skin = 0
} ;
// The root node has the mesh node and root joint as children
model - > rootNode = model - > jointCount + 1 ;
model - > nodes [ model - > rootNode ] = ( ModelNode ) {
2022-08-03 06:06:49 +00:00
. hasMatrix = true ,
2022-03-20 00:49:13 +00:00
. transform = { MAT4_IDENTITY } ,
. childCount = 2 ,
. children = children ,
. skin = ~ 0u
} ;
// Add the children to the root node
* children + + = XR_HAND_JOINT_WRIST_EXT ;
* children + + = model - > jointCount ;
2022-08-06 07:29:46 +00:00
lovrModelDataFinalize ( model ) ;
2022-03-20 00:49:13 +00:00
return model ;
2019-04-11 20:47:25 +00:00
}
2022-11-19 15:26:44 +00:00
typedef struct {
XrControllerModelKeyMSFT modelKey ;
uint32_t * nodeIndices ;
} MetadataControllerMSFT ;
2022-11-16 17:19:57 +00:00
static ModelData * openxr_newModelDataMSFT ( XrControllerModelKeyMSFT modelKey , bool animated ) {
uint32_t size ;
if ( XR_FAILED ( xrLoadControllerModelMSFT ( state . session , modelKey , 0 , & size , NULL ) ) ) {
return NULL ;
}
2022-03-20 00:49:13 +00:00
2022-11-16 17:19:57 +00:00
unsigned char * modelData = malloc ( size ) ;
if ( ! modelData ) return NULL ;
if ( XR_FAILED ( xrLoadControllerModelMSFT ( state . session , modelKey , size , & size , modelData ) ) ) {
free ( modelData ) ;
return NULL ;
2022-03-20 00:49:13 +00:00
}
2022-11-16 17:19:57 +00:00
Blob * blob = lovrBlobCreate ( modelData , size , " Controller Model Data " ) ;
ModelData * model = lovrModelDataCreate ( blob , NULL ) ;
lovrRelease ( blob , lovrBlobDestroy ) ;
2022-11-19 15:26:44 +00:00
XrControllerModelNodePropertiesMSFT nodeProperties [ 16 ] ;
for ( uint32_t i = 0 ; i < COUNTOF ( nodeProperties ) ; i + + ) {
nodeProperties [ i ] . type = XR_TYPE_CONTROLLER_MODEL_NODE_PROPERTIES_MSFT ;
nodeProperties [ i ] . next = 0 ;
}
XrControllerModelPropertiesMSFT properties = {
. type = XR_TYPE_CONTROLLER_MODEL_PROPERTIES_MSFT ,
. nodeCapacityInput = COUNTOF ( nodeProperties ) ,
. nodeProperties = nodeProperties ,
} ;
if ( XR_FAILED ( xrGetControllerModelPropertiesMSFT ( state . session , modelKey , & properties ) ) ) {
return false ;
}
free ( model - > metadata ) ;
model - > metadataType = META_CONTROLLER_MSFT ;
model - > metadataSize = sizeof ( MetadataControllerMSFT ) + sizeof ( uint32_t ) * properties . nodeCountOutput ;
model - > metadata = malloc ( model - > metadataSize ) ;
lovrAssert ( model - > metadata , " Out of memory " ) ;
MetadataControllerMSFT * metadata = model - > metadata ;
metadata - > modelKey = modelKey ;
2023-01-19 02:21:53 +00:00
metadata - > nodeIndices = ( uint32_t * ) ( ( char * ) model - > metadata + sizeof ( MetadataControllerMSFT ) ) ;
2022-11-19 15:26:44 +00:00
for ( uint32_t i = 0 ; i < properties . nodeCountOutput ; i + + ) {
const char * name = nodeProperties [ i ] . nodeName ;
uint64_t nodeIndex = map_get ( & model - > nodeMap , hash64 ( name , strlen ( name ) ) ) ;
lovrCheck ( nodeIndex ! = MAP_NIL , " ModelData has no node named '%s' " , name ) ;
metadata - > nodeIndices [ i ] = nodeIndex ;
}
2022-11-16 17:19:57 +00:00
return model ;
}
static ModelData * openxr_newModelData ( Device device , bool animated ) {
XrHandTrackerEXT tracker ;
if ( ( tracker = getHandTracker ( device ) ) ) {
return openxr_newModelDataFB ( tracker , animated ) ;
}
XrControllerModelKeyMSFT modelKey ;
if ( ( modelKey = getControllerModelKey ( device ) ) ) {
return openxr_newModelDataMSFT ( modelKey , animated ) ;
}
return NULL ;
}
2022-11-19 15:26:44 +00:00
static bool openxr_animateFB ( Model * model , const ModelInfo * info ) {
XrHandTrackerEXT tracker = * ( XrHandTrackerEXT * ) info - > data - > metadata ;
2022-03-20 00:49:13 +00:00
// TODO might be nice to cache joints so getSkeleton/animate only locate joints once (profile)
2022-11-19 15:26:44 +00:00
XrHandJointsLocateInfoEXT locateInfo = {
2022-03-20 00:49:13 +00:00
. type = XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT ,
. baseSpace = state . referenceSpace ,
. time = state . frameState . predictedDisplayTime
} ;
2023-02-02 02:36:05 +00:00
XrHandJointLocationEXT joints [ MAX_HAND_JOINTS ] ;
2022-03-20 00:49:13 +00:00
XrHandJointLocationsEXT hand = {
. type = XR_TYPE_HAND_JOINT_LOCATIONS_EXT ,
2023-02-02 02:36:05 +00:00
. jointCount = 26 + state . features . handTrackingElbow ,
2022-03-20 00:49:13 +00:00
. jointLocations = joints
} ;
2022-11-19 15:26:44 +00:00
if ( XR_FAILED ( xrLocateHandJointsEXT ( tracker , & locateInfo , & hand ) ) | | ! hand . isActive ) {
2022-03-20 00:49:13 +00:00
return false ;
}
2022-08-03 06:06:49 +00:00
lovrModelResetNodeTransforms ( model ) ;
2022-03-20 00:49:13 +00:00
// This is kinda brittle, ideally we would use the jointParents from the actual mesh object
2023-02-02 02:36:05 +00:00
uint32_t jointParents [ HAND_JOINT_COUNT ] = {
2022-03-20 00:49:13 +00:00
XR_HAND_JOINT_WRIST_EXT ,
~ 0u ,
XR_HAND_JOINT_WRIST_EXT ,
XR_HAND_JOINT_THUMB_METACARPAL_EXT ,
XR_HAND_JOINT_THUMB_PROXIMAL_EXT ,
XR_HAND_JOINT_THUMB_DISTAL_EXT ,
XR_HAND_JOINT_WRIST_EXT ,
XR_HAND_JOINT_INDEX_METACARPAL_EXT ,
XR_HAND_JOINT_INDEX_PROXIMAL_EXT ,
XR_HAND_JOINT_INDEX_INTERMEDIATE_EXT ,
XR_HAND_JOINT_INDEX_DISTAL_EXT ,
XR_HAND_JOINT_WRIST_EXT ,
XR_HAND_JOINT_MIDDLE_METACARPAL_EXT ,
XR_HAND_JOINT_MIDDLE_PROXIMAL_EXT ,
XR_HAND_JOINT_MIDDLE_INTERMEDIATE_EXT ,
XR_HAND_JOINT_MIDDLE_DISTAL_EXT ,
XR_HAND_JOINT_WRIST_EXT ,
XR_HAND_JOINT_RING_METACARPAL_EXT ,
XR_HAND_JOINT_RING_PROXIMAL_EXT ,
XR_HAND_JOINT_RING_INTERMEDIATE_EXT ,
XR_HAND_JOINT_RING_DISTAL_EXT ,
XR_HAND_JOINT_WRIST_EXT ,
XR_HAND_JOINT_LITTLE_METACARPAL_EXT ,
XR_HAND_JOINT_LITTLE_PROXIMAL_EXT ,
XR_HAND_JOINT_LITTLE_INTERMEDIATE_EXT ,
XR_HAND_JOINT_LITTLE_DISTAL_EXT
} ;
2022-08-06 07:29:46 +00:00
float scale [ 4 ] = { 1.f , 1.f , 1.f , 1.f } ;
2022-03-20 00:49:13 +00:00
// The following can be optimized a lot (ideally we would set the global transform for the nodes)
2023-02-02 02:36:05 +00:00
for ( uint32_t i = 0 ; i < HAND_JOINT_COUNT ; i + + ) {
2022-03-20 00:49:13 +00:00
if ( jointParents [ i ] = = ~ 0u ) {
float position [ 4 ] = { 0.f , 0.f , 0.f } ;
2022-09-26 21:58:34 +00:00
float orientation [ 4 ] = { 0.f , 0.f , 0.f , 1.f } ;
2022-08-03 06:06:49 +00:00
lovrModelSetNodeTransform ( model , i , position , scale , orientation , 1.f ) ;
2022-03-20 00:49:13 +00:00
} else {
XrPosef * parent = & joints [ jointParents [ i ] ] . pose ;
XrPosef * pose = & joints [ i ] . pose ;
// Convert global pose to parent-local pose (premultiply with inverse of parent pose)
// TODO there should be maf for this
float position [ 4 ] , orientation [ 4 ] ;
vec3_init ( position , & pose - > position . x ) ;
vec3_sub ( position , & parent - > position . x ) ;
quat_init ( orientation , & parent - > orientation . x ) ;
quat_conjugate ( orientation ) ;
quat_rotate ( orientation , position ) ;
quat_mul ( orientation , orientation , & pose - > orientation . x ) ;
2022-08-03 06:06:49 +00:00
lovrModelSetNodeTransform ( model , i , position , scale , orientation , 1.f ) ;
2022-03-20 00:49:13 +00:00
}
2022-08-03 06:06:49 +00:00
}
2022-03-20 00:49:13 +00:00
return true ;
2020-08-02 23:25:51 +00:00
}
2022-11-19 15:26:44 +00:00
static bool openxr_animateMSFT ( Model * model , const ModelInfo * info ) {
MetadataControllerMSFT * metadata = info - > data - > metadata ;
2022-11-16 17:19:57 +00:00
XrControllerModelNodeStateMSFT nodeStates [ 16 ] ;
for ( uint32_t i = 0 ; i < COUNTOF ( nodeStates ) ; i + + ) {
nodeStates [ i ] . type = XR_TYPE_CONTROLLER_MODEL_NODE_STATE_MSFT ;
nodeStates [ i ] . next = 0 ;
}
XrControllerModelStateMSFT modelState = {
. type = XR_TYPE_CONTROLLER_MODEL_STATE_MSFT ,
. nodeCapacityInput = COUNTOF ( nodeStates ) ,
. nodeStates = nodeStates ,
} ;
2022-11-19 15:26:44 +00:00
if ( XR_FAILED ( xrGetControllerModelStateMSFT ( state . session , metadata - > modelKey , & modelState ) ) ) {
2022-11-16 17:19:57 +00:00
return false ;
}
for ( uint32_t i = 0 ; i < modelState . nodeCountOutput ; i + + ) {
float position [ 4 ] , rotation [ 4 ] ;
vec3_init ( position , ( vec3 ) & nodeStates [ i ] . nodePose . position ) ;
quat_init ( rotation , ( quat ) & nodeStates [ i ] . nodePose . orientation ) ;
2022-11-19 15:26:44 +00:00
lovrModelSetNodeTransform ( model , metadata - > nodeIndices [ i ] , position , NULL , rotation , 1 ) ;
2022-11-16 17:19:57 +00:00
}
return false ;
}
2022-11-19 15:26:44 +00:00
static bool openxr_animate ( Model * model ) {
const ModelInfo * info = lovrModelGetInfo ( model ) ;
2022-11-16 17:19:57 +00:00
2022-11-19 15:26:44 +00:00
switch ( info - > data - > metadataType ) {
2023-05-02 01:47:57 +00:00
case META_HANDTRACKING_FB : return openxr_animateFB ( model , info ) ;
case META_CONTROLLER_MSFT : return openxr_animateMSFT ( model , info ) ;
default : return false ;
2022-11-19 15:26:44 +00:00
}
2022-11-16 17:19:57 +00:00
}
2022-06-06 03:38:14 +00:00
static Texture * openxr_getTexture ( void ) {
2022-08-06 07:29:46 +00:00
if ( ! SESSION_ACTIVE ( state . sessionState ) ) {
return NULL ;
}
2022-08-03 05:00:11 +00:00
if ( state . began ) {
2022-08-07 05:52:18 +00:00
return state . frameState . shouldRender ? state . textures [ COLOR ] [ state . textureIndex [ COLOR ] ] : NULL ;
2022-06-06 03:38:14 +00:00
}
XrFrameBeginInfo beginfo = { . type = XR_TYPE_FRAME_BEGIN_INFO } ;
2023-03-08 04:16:59 +00:00
XR ( xrBeginFrame ( state . session , & beginfo ) , " Failed to begin headset rendering " ) ;
2022-08-03 05:00:11 +00:00
state . began = true ;
2022-06-06 03:38:14 +00:00
2022-06-20 22:51:24 +00:00
if ( ! state . frameState . shouldRender ) {
return NULL ;
}
2022-06-06 03:38:14 +00:00
XrSwapchainImageWaitInfo waitInfo = { XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO , . timeout = XR_INFINITE_DURATION } ;
2023-03-08 04:16:59 +00:00
XR ( xrAcquireSwapchainImage ( state . swapchain [ COLOR ] , NULL , & state . textureIndex [ COLOR ] ) , " Failed to acquire color swapchain image " ) ;
XR ( xrWaitSwapchainImage ( state . swapchain [ COLOR ] , & waitInfo ) , " Failed to wait on color swapchain image " ) ;
2022-08-06 01:36:51 +00:00
2022-08-07 05:52:18 +00:00
if ( state . features . depth ) {
2023-03-08 04:16:59 +00:00
XR ( xrAcquireSwapchainImage ( state . swapchain [ DEPTH ] , NULL , & state . textureIndex [ DEPTH ] ) , " Failed to acquire depth swapchain image " ) ;
XR ( xrWaitSwapchainImage ( state . swapchain [ DEPTH ] , & waitInfo ) , " Failed to wait for depth swapchain image " ) ;
2022-08-07 05:52:18 +00:00
}
return state . textures [ COLOR ] [ state . textureIndex [ COLOR ] ] ;
}
static Texture * openxr_getDepthTexture ( void ) {
if ( ! SESSION_ACTIVE ( state . sessionState ) | | ! state . features . depth ) {
return NULL ;
}
if ( state . began ) {
return state . frameState . shouldRender ? state . textures [ DEPTH ] [ state . textureIndex [ DEPTH ] ] : NULL ;
}
XrFrameBeginInfo beginfo = { . type = XR_TYPE_FRAME_BEGIN_INFO } ;
2023-03-08 04:16:59 +00:00
XR ( xrBeginFrame ( state . session , & beginfo ) , " Failed to begin headset rendering " ) ;
2022-08-07 05:52:18 +00:00
state . began = true ;
if ( ! state . frameState . shouldRender ) {
return NULL ;
}
XrSwapchainImageWaitInfo waitInfo = { XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO , . timeout = XR_INFINITE_DURATION } ;
2023-03-08 04:16:59 +00:00
XR ( xrAcquireSwapchainImage ( state . swapchain [ COLOR ] , NULL , & state . textureIndex [ COLOR ] ) , " Failed to acquire color swapchain image " ) ;
XR ( xrWaitSwapchainImage ( state . swapchain [ COLOR ] , & waitInfo ) , " Failed to wait for color swapchain image " ) ;
2022-08-07 05:52:18 +00:00
if ( state . features . depth ) {
2023-03-08 04:16:59 +00:00
XR ( xrAcquireSwapchainImage ( state . swapchain [ DEPTH ] , NULL , & state . textureIndex [ DEPTH ] ) , " Failed to acquire depth swapchain image " ) ;
XR ( xrWaitSwapchainImage ( state . swapchain [ DEPTH ] , & waitInfo ) , " Failed to wait for depth swapchain image " ) ;
2022-08-07 05:52:18 +00:00
}
return state . textures [ DEPTH ] [ state . textureIndex [ DEPTH ] ] ;
2022-08-06 01:36:51 +00:00
}
static Pass * openxr_getPass ( void ) {
if ( state . began ) {
return state . frameState . shouldRender ? state . pass : NULL ;
}
Texture * texture = openxr_getTexture ( ) ;
2022-08-07 05:52:18 +00:00
Texture * depthTexture = openxr_getDepthTexture ( ) ;
2022-08-06 01:36:51 +00:00
if ( ! texture ) {
return NULL ;
}
2023-04-30 06:02:37 +00:00
Texture * textures [ 4 ] = { texture } ;
Texture * depth = depthTexture ;
2022-08-26 04:57:15 +00:00
2023-04-30 06:02:37 +00:00
lovrPassReset ( state . pass ) ;
lovrPassSetCanvas ( state . pass , textures , depth , state . depthFormat , state . config . antialias ? 4 : 1 ) ;
2022-06-06 03:38:14 +00:00
2023-05-03 23:35:09 +00:00
float background [ 4 ] ;
LoadAction load = LOAD_CLEAR ;
lovrGraphicsGetBackgroundColor ( background ) ;
lovrPassSetClear ( state . pass , & load , & background , LOAD_CLEAR , 0.f ) ;
2022-06-06 03:38:14 +00:00
uint32_t count ;
XrView views [ 2 ] ;
2023-05-02 01:47:57 +00:00
XrViewStateFlags flags = getViews ( views , & count ) ;
2022-06-06 03:38:14 +00:00
2022-08-03 05:00:11 +00:00
for ( uint32_t i = 0 ; i < count ; i + + ) {
state . layerViews [ i ] . pose = views [ i ] . pose ;
state . layerViews [ i ] . fov = views [ i ] . fov ;
float viewMatrix [ 16 ] ;
float projection [ 16 ] ;
2023-05-02 01:47:57 +00:00
if ( flags & XR_VIEW_STATE_ORIENTATION_VALID_BIT ) {
mat4_fromQuat ( viewMatrix , & views [ i ] . pose . orientation . x ) ;
} else {
mat4_identity ( viewMatrix ) ;
}
if ( flags & XR_VIEW_STATE_POSITION_VALID_BIT ) {
memcpy ( viewMatrix + 12 , & views [ i ] . pose . position . x , 3 * sizeof ( float ) ) ;
}
mat4_invert ( viewMatrix ) ;
2022-08-03 05:00:11 +00:00
lovrPassSetViewMatrix ( state . pass , i , viewMatrix ) ;
2023-05-02 01:47:57 +00:00
if ( flags ! = 0 ) {
XrFovf * fov = & views [ i ] . fov ;
mat4_fov ( projection , - fov - > angleLeft , fov - > angleRight , fov - > angleUp , - fov - > angleDown , state . clipNear , state . clipFar ) ;
lovrPassSetProjection ( state . pass , i , projection ) ;
}
2022-08-03 05:00:11 +00:00
}
return state . pass ;
2022-06-06 03:38:14 +00:00
}
static void openxr_submit ( void ) {
2022-08-03 05:00:11 +00:00
if ( ! state . began | | ! SESSION_ACTIVE ( state . sessionState ) ) {
2022-03-30 20:32:28 +00:00
state . waited = false ;
return ;
}
2019-04-11 20:47:25 +00:00
2023-02-06 03:51:12 +00:00
XrCompositionLayerBaseHeader const * layers [ 2 ] ;
2022-08-03 05:00:11 +00:00
XrFrameEndInfo info = {
2020-08-26 19:01:18 +00:00
. type = XR_TYPE_FRAME_END_INFO ,
. displayTime = state . frameState . predictedDisplayTime ,
2023-03-31 02:39:50 +00:00
. environmentBlendMode = state . blendMode ,
2023-02-06 03:51:12 +00:00
. layers = layers
2020-08-26 19:01:18 +00:00
} ;
2019-04-11 20:47:25 +00:00
2022-08-07 05:52:18 +00:00
if ( state . features . depth ) {
if ( state . clipFar = = 0.f ) {
state . depthInfo [ 0 ] . nearZ = state . depthInfo [ 1 ] . nearZ = + INFINITY ;
state . depthInfo [ 0 ] . farZ = state . depthInfo [ 1 ] . farZ = state . clipNear ;
} else {
state . depthInfo [ 0 ] . nearZ = state . depthInfo [ 1 ] . nearZ = state . clipNear ;
state . depthInfo [ 0 ] . farZ = state . depthInfo [ 1 ] . farZ = state . clipFar ;
}
}
2022-08-03 05:00:11 +00:00
if ( state . frameState . shouldRender ) {
2023-03-08 04:16:59 +00:00
XR ( xrReleaseSwapchainImage ( state . swapchain [ COLOR ] , NULL ) , " Failed to release color swapchain image " ) ;
2022-08-07 05:52:18 +00:00
if ( state . features . depth ) {
2023-03-08 04:16:59 +00:00
XR ( xrReleaseSwapchainImage ( state . swapchain [ DEPTH ] , NULL ) , " Failed to release depth swapchain image " ) ;
2022-08-07 05:52:18 +00:00
}
2023-02-06 03:51:12 +00:00
if ( state . passthroughActive ) {
layers [ 0 ] = ( const XrCompositionLayerBaseHeader * ) & state . passthroughLayer ;
layers [ 1 ] = ( const XrCompositionLayerBaseHeader * ) & state . layers [ 0 ] ;
info . layerCount = 2 ;
} else {
layers [ 0 ] = ( const XrCompositionLayerBaseHeader * ) & state . layers [ 0 ] ;
info . layerCount = 1 ;
}
if ( state . features . overlay | | state . passthroughActive ) {
state . layers [ 0 ] . layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT | XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT ;
} else {
state . layers [ 0 ] . layerFlags = 0 ;
}
2019-04-11 20:47:25 +00:00
}
2023-03-08 04:16:59 +00:00
XR ( xrEndFrame ( state . session , & info ) , " Failed to submit layers " ) ;
2022-08-03 05:00:11 +00:00
state . began = false ;
2022-03-30 20:32:28 +00:00
state . waited = false ;
2019-04-11 20:47:25 +00:00
}
2023-06-14 04:06:05 +00:00
static bool openxr_isVisible ( void ) {
return state . sessionState > = XR_SESSION_STATE_VISIBLE ;
}
2022-03-23 02:43:00 +00:00
static bool openxr_isFocused ( void ) {
return state . sessionState = = XR_SESSION_STATE_FOCUSED ;
}
2022-03-23 00:52:16 +00:00
static double openxr_update ( void ) {
2022-08-03 05:00:11 +00:00
if ( state . waited ) return openxr_getDeltaTime ( ) ;
2022-03-30 20:32:28 +00:00
2020-08-26 19:01:18 +00:00
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
2023-03-08 04:16:59 +00:00
} ) , " Failed to begin session " ) ;
2019-04-11 20:47:25 +00:00
break ;
case XR_SESSION_STATE_STOPPING :
2023-03-08 04:16:59 +00:00
XR ( xrEndSession ( state . session ) , " Failed to end session " ) ;
2019-04-11 20:47:25 +00:00
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
2023-06-14 04:06:05 +00:00
bool wasVisible = state . sessionState > = XR_SESSION_STATE_VISIBLE ;
bool isVisible = event - > state > = XR_SESSION_STATE_VISIBLE ;
if ( wasVisible ! = isVisible ) {
lovrEventPush ( ( Event ) { . type = EVENT_VISIBLE , . data . boolean . value = isVisible } ) ;
}
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 ;
}
2023-06-26 23:41:42 +00:00
case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING : {
XrEventDataReferenceSpaceChangePending * event = ( XrEventDataReferenceSpaceChangePending * ) & e ;
if ( event - > referenceSpaceType = = XR_REFERENCE_SPACE_TYPE_LOCAL ) {
createReferenceSpace ( ) ;
lovrEventPush ( ( Event ) { . type = EVENT_RECENTER } ) ;
}
break ;
}
2019-04-11 20:47:25 +00:00
default : break ;
}
2022-03-29 15:56:26 +00:00
e . type = XR_TYPE_EVENT_DATA_BUFFER ;
2019-04-11 20:47:25 +00:00
}
2020-08-26 19:01:18 +00:00
if ( SESSION_ACTIVE ( state . sessionState ) ) {
2022-03-23 00:52:16 +00:00
state . lastDisplayTime = state . frameState . predictedDisplayTime ;
2023-03-08 04:16:59 +00:00
XR ( xrWaitFrame ( state . session , NULL , & state . frameState ) , " Failed to wait for next frame " ) ;
2022-03-30 20:32:28 +00:00
state . waited = true ;
2020-08-26 19:01:18 +00:00
2023-05-02 02:08:25 +00:00
if ( state . epoch = = 0 ) {
state . epoch = state . frameState . predictedDisplayTime - state . frameState . predictedDisplayPeriod ;
state . lastDisplayTime = state . epoch ;
2022-03-23 00:52:16 +00:00
}
2022-03-30 20:32:07 +00:00
XrActiveActionSet activeSets [ ] = {
{ state . actionSet , XR_NULL_PATH }
} ;
2020-08-26 19:01:18 +00:00
XrActionsSyncInfo syncInfo = {
. type = XR_TYPE_ACTIONS_SYNC_INFO ,
2022-03-30 20:32:07 +00:00
. countActiveActionSets = COUNTOF ( activeSets ) ,
. activeActionSets = activeSets
2020-08-26 19:01:18 +00:00
} ;
2023-03-08 04:16:59 +00:00
XR ( xrSyncActions ( state . session , & syncInfo ) , " Failed to sync actions " ) ;
2020-08-26 19:01:18 +00:00
}
2022-03-23 00:52:16 +00:00
2023-03-03 03:33:35 +00:00
// Throttle when session is idle (but not too much, a desktop window might be rendering stuff)
if ( state . sessionState = = XR_SESSION_STATE_IDLE ) {
os_sleep ( .001 ) ;
}
2022-03-23 00:52:16 +00:00
return openxr_getDeltaTime ( ) ;
2019-04-11 20:47:25 +00:00
}
HeadsetInterface lovrHeadsetOpenXRDriver = {
. driverType = DRIVER_OPENXR ,
2022-06-06 03:38:14 +00:00
. getVulkanPhysicalDevice = openxr_getVulkanPhysicalDevice ,
. createVulkanInstance = openxr_createVulkanInstance ,
. createVulkanDevice = openxr_createVulkanDevice ,
2019-04-30 23:59:15 +00:00
. init = openxr_init ,
2021-06-10 23:26:15 +00:00
. start = openxr_start ,
2022-08-09 06:27:35 +00:00
. stop = openxr_stop ,
2019-04-30 23:59:15 +00:00
. destroy = openxr_destroy ,
. getName = openxr_getName ,
Replace HeadsetOrigin with 'seated' flag;
Origin type used to be a query-able property of the VR system that
indicated whether the tracking was roomscale or seated-scale.
The t.headset.offset config value could be used to design an
origin-agnostic experience, which by default shifted content up 1.7
meters when tracking was seated-scale. That way, stuff rendered at
y=1.7m was always at "eye level". It worked pretty well.
It's getting replaced with a t.headset.seated flag.
- If seated is false (the default), the origin of the coordinate space
will be on the floor, enabling the y=1.7m eye level paradigm. If
tracking is not roomscale, a floor offset of 1.7m will be emulated.
- If seated is true, the origin of the coordinate space will be y=0
at eye level (where the headset was when the app started). This is
the case on both roomscale and seated-scale tracking.
So basically 'seated' is an opt-in preference for where the app wants
its vertical origin to be.
One advantage of this is that it's possible to consistently get a y=0
eye level coordinate space, which was not possible before. This makes
it easier to design simpler experiences that only need to render a
floating UI and don't want to render a full environment or deal with
offsetting everything relative to a 'floor'. This also makes it easier
to implement hybrid VR+flatscreen experiences, because the camera is at
y=0 when the headset module is disabled.
The opt-in nature of the flag, coupled with the fact that it is
consistent across all types of tracking and hardware, is hopefully a
more useful design.
2023-06-28 23:38:36 +00:00
. isSeated = openxr_isSeated ,
2019-04-30 23:59:15 +00:00
. getDisplayDimensions = openxr_getDisplayDimensions ,
2023-05-12 17:23:23 +00:00
. getRefreshRate = openxr_getRefreshRate ,
. setRefreshRate = openxr_setRefreshRate ,
. getRefreshRates = openxr_getRefreshRates ,
2023-05-12 13:13:48 +00:00
. getPassthrough = openxr_getPassthrough ,
. setPassthrough = openxr_setPassthrough ,
. isPassthroughSupported = openxr_isPassthroughSupported ,
2019-04-30 23:59:15 +00:00
. getDisplayTime = openxr_getDisplayTime ,
2022-03-23 00:52:16 +00:00
. getDeltaTime = openxr_getDeltaTime ,
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 ,
2023-06-28 03:45:44 +00:00
. stopVibration = openxr_stopVibration ,
2019-04-30 23:59:15 +00:00
. newModelData = openxr_newModelData ,
2020-08-19 03:09:06 +00:00
. animate = openxr_animate ,
2022-06-06 03:38:14 +00:00
. getTexture = openxr_getTexture ,
2022-08-03 05:00:11 +00:00
. getPass = openxr_getPass ,
2022-06-06 03:38:14 +00:00
. submit = openxr_submit ,
2023-06-14 04:06:05 +00:00
. isVisible = openxr_isVisible ,
2022-03-23 02:43:00 +00:00
. isFocused = openxr_isFocused ,
2019-04-30 23:59:15 +00:00
. update = openxr_update
2019-04-11 20:47:25 +00:00
} ;