diff --git a/src/APILayer/APILayer.cpp b/src/APILayer/APILayer.cpp index d49b36c..0bfea7d 100644 --- a/src/APILayer/APILayer.cpp +++ b/src/APILayer/APILayer.cpp @@ -178,6 +178,16 @@ XrResult APILayer::xrGetCurrentInteractionProfile( session, topLevelUserPath, interactionProfile); } +XrResult APILayer::xrCreateAction( + XrActionSet actionSet, + const XrActionCreateInfo* createInfo, + XrAction* space) { + if (mVirtualController) { + return mVirtualController->xrCreateAction(actionSet, createInfo, space); + } + return mOpenXR->xrCreateAction(actionSet, createInfo, space); +} + XrResult APILayer::xrCreateActionSpace( XrSession session, const XrActionSpaceCreateInfo* createInfo, diff --git a/src/APILayer/APILayer.h b/src/APILayer/APILayer.h index 7cc0823..1dd9373 100644 --- a/src/APILayer/APILayer.h +++ b/src/APILayer/APILayer.h @@ -57,6 +57,11 @@ class APILayer final { XrPath topLevelUserPath, XrInteractionProfileState* interactionProfile); + XrResult xrCreateAction( + XrActionSet actionSet, + const XrActionCreateInfo* createInfo, + XrAction* action); + XrResult xrCreateActionSpace( XrSession session, const XrActionSpaceCreateInfo* createInfo, diff --git a/src/APILayer/APILayer_loader.cpp b/src/APILayer/APILayer_loader.cpp index 9c187f7..f21ecb7 100644 --- a/src/APILayer/APILayer_loader.cpp +++ b/src/APILayer/APILayer_loader.cpp @@ -135,6 +135,16 @@ static XrResult xrPollEvent(XrInstance instance, XrEventDataBuffer* eventData) { return gNext->xrPollEvent(instance, eventData); } +static XrResult xrCreateAction( + XrActionSet actionSet, + const XrActionCreateInfo* createInfo, + XrAction* action) { + if (gInstance) { + return gInstance->xrCreateAction(actionSet, createInfo, action); + } + return gNext->xrCreateAction(actionSet, createInfo, action); +} + static XrResult xrCreateActionSpace( XrSession session, const XrActionSpaceCreateInfo* createInfo, @@ -188,60 +198,13 @@ static XrResult xrGetInstanceProcAddr( PFN_xrVoidFunction* function) { std::string_view name {name_cstr}; - if (name == "xrCreateSession") { - *function = reinterpret_cast(&xrCreateSession); - return XR_SUCCESS; - } - if (name == "xrDestroySession") { - *function = reinterpret_cast(&xrDestroySession); - return XR_SUCCESS; - } - if (name == "xrDestroyInstance") { - *function = reinterpret_cast(&xrDestroyInstance); - return XR_SUCCESS; - } - if (name == "xrWaitFrame") { - *function = reinterpret_cast(&xrWaitFrame); - return XR_SUCCESS; - } - if (name == "xrSuggestInteractionProfileBindings") { - *function = reinterpret_cast( - &xrSuggestInteractionProfileBindings); - return XR_SUCCESS; - } - if (name == "xrCreateActionSpace") { - *function = reinterpret_cast(&xrCreateActionSpace); - return XR_SUCCESS; - } - if (name == "xrGetActionStateBoolean") { - *function = reinterpret_cast(&xrGetActionStateBoolean); - return XR_SUCCESS; - } - if (name == "xrGetActionStateFloat") { - *function = reinterpret_cast(&xrGetActionStateFloat); - return XR_SUCCESS; - } - if (name == "xrGetActionStatePose") { - *function = reinterpret_cast(&xrGetActionStatePose); - return XR_SUCCESS; - } - if (name == "xrLocateSpace") { - *function = reinterpret_cast(&xrLocateSpace); - return XR_SUCCESS; - } - if (name == "xrSyncActions") { - *function = reinterpret_cast(&xrSyncActions); - return XR_SUCCESS; - } - if (name == "xrPollEvent") { - *function = reinterpret_cast(&xrPollEvent); - return XR_SUCCESS; - } - if (name == "xrGetCurrentInteractionProfile") { - *function - = reinterpret_cast(&xrGetCurrentInteractionProfile); - return XR_SUCCESS; +#define IT(x) \ + if (name == #x) { \ + *function = reinterpret_cast(&x); \ + return XR_SUCCESS; \ } + INTERCEPTED_OPENXR_FUNCS +#undef IT if (gNext) { const auto result diff --git a/src/APILayer/VirtualControllerSink.cpp b/src/APILayer/VirtualControllerSink.cpp index d4ea671..fd0a210 100644 --- a/src/APILayer/VirtualControllerSink.cpp +++ b/src/APILayer/VirtualControllerSink.cpp @@ -140,6 +140,7 @@ static bool WorldLockOrientation() { case VRControllerPointerSinkWorldLock::OrientationAndSoftPosition: return true; } + __assume(false); } std::optional VirtualControllerSink::GetInputPose( @@ -536,77 +537,109 @@ XrResult VirtualControllerSink::xrSuggestInteractionProfileBindings( for (uint32_t i = 0; i < suggestedBindings->countSuggestedBindings; ++i) { const auto& it = suggestedBindings->suggestedBindings[i]; - const auto binding = ResolvePath(it.binding); - mActionPaths[it.action] = binding; + this->AddBinding(it.binding, it.action); + } + mHaveSuggestedBindings = true; - if (Config::VerboseDebug >= 2) { - DebugPrint("Binding requested: {}", binding); - } + return XR_SUCCESS; +} - ControllerState* state; - if (binding.starts_with(gLeftHandPath)) { - state = &mLeftController; - } else if (binding.starts_with(gRightHandPath)) { - state = &mRightController; - } else { - continue; - } +XrResult VirtualControllerSink::xrCreateAction( + XrActionSet actionSet, + const XrActionCreateInfo* createInfo, + XrAction* action) { + const auto ret = mOpenXR->xrCreateAction(actionSet, createInfo, action); + if (!XR_SUCCEEDED(ret)) { + return ret; + } - if (IsPointerSink()) { - if (binding.ends_with(gAimPosePath)) { - state->aimActions.insert(it.action); - continue; - } + for (uint32_t i = 0; i < createInfo->countSubactionPaths; ++i) { + AddBinding(createInfo->subactionPaths[i], *action); + } - if (binding.ends_with(gGripPosePath)) { - state->gripActions.insert(it.action); - continue; - } + return ret; +} - // Partially cosmetic, also helps with 'is using this controller' in - // some games - if (binding.ends_with(gSqueezeValuePath)) { - state->squeezeValueActions.insert(it.action); - continue; - } +void VirtualControllerSink::AddBinding(XrPath path, XrAction action) { + const auto binding = ResolvePath(path); - // Cosmetic - if (binding.ends_with(gThumbstickTouchPath)) { - state->thumbstickTouchActions.insert(it.action); - continue; - } + ControllerState* state {nullptr}; + if (binding.starts_with(gLeftHandPath)) { + state = &mLeftController; + } else if (binding.starts_with(gRightHandPath)) { + state = &mRightController; + } else { + return; + } - if (binding.ends_with(gTriggerTouchPath)) { - state->triggerTouchActions.insert(it.action); - continue; + if (IsPointerSink()) { + if (binding.ends_with(gAimPosePath)) { + state->aimActions.emplace(action); + if (mActionSpaces.contains(action)) { + const auto actionSpaces = mActionSpaces.at(action); + state->aimSpaces.insert(actionSpaces.begin(), actionSpaces.end()); } + DebugPrint("Aim action found"); + return; } - if (IsActionSink()) { - if (binding.ends_with(gThumbstickXPath)) { - state->thumbstickXActions.insert(it.action); - continue; + if (binding.ends_with(gGripPosePath)) { + state->gripActions.emplace(action); + if (mActionSpaces.contains(action)) { + const auto actionSpaces = mActionSpaces.at(action); + state->gripSpaces.insert(actionSpaces.begin(), actionSpaces.end()); } + DebugPrint("Grip action found"); + return; + } - if (binding.ends_with(gThumbstickYPath)) { - state->thumbstickYActions.insert(it.action); - continue; - } + // Partially cosmetic, also helps with 'is using this controller' in + // some games + if (binding.ends_with(gSqueezeValuePath)) { + state->squeezeValueActions.emplace(action); + DebugPrint("Squeeze action found"); + return; + } - if (binding.ends_with(gTriggerTouchPath)) { - state->triggerTouchActions.insert(it.action); - continue; - } + // Cosmetic + if (binding.ends_with(gThumbstickTouchPath)) { + state->thumbstickTouchActions.emplace(action); + DebugPrint("Thumbstick touch action found"); + return; + } - if (binding.ends_with(gTriggerValuePath)) { - state->triggerValueActions.insert(it.action); - continue; - } + if (binding.ends_with(gTriggerTouchPath)) { + state->triggerTouchActions.emplace(action); + DebugPrint("Trigger touch action found"); + return; } } - mHaveSuggestedBindings = true; - return XR_SUCCESS; + if (IsActionSink()) { + if (binding.ends_with(gThumbstickXPath)) { + state->thumbstickXActions.emplace(action); + DebugPrint("Thumbstick X action found"); + return; + } + + if (binding.ends_with(gThumbstickYPath)) { + state->thumbstickYActions.emplace(action); + DebugPrint("Thumbstick Y action found"); + return; + } + + if (binding.ends_with(gTriggerTouchPath)) { + state->triggerTouchActions.emplace(action); + DebugPrint("Trigger touch action found"); + return; + } + + if (binding.ends_with(gTriggerValuePath)) { + state->triggerValueActions.emplace(action); + DebugPrint("Trigger value action found"); + return; + } + } } XrResult VirtualControllerSink::xrCreateActionSpace( @@ -619,25 +652,39 @@ XrResult VirtualControllerSink::xrCreateActionSpace( return nextResult; } - mActionSpaces[*space] = createInfo->action; + // It's fine to call xrCreateActionSpace before the bindings have + // been suggested - so, we need to track the space, even if we have no + // reason yet to think that the action is relevant + mActionSpaces[createInfo->action].emplace(*space); const auto path = createInfo->subactionPath; - ResolvePath(path); + if (path) { + // Populate hand.path + ResolvePath(path); + } + for (auto hand: {&mLeftController, &mRightController}) { - if (path != XR_NULL_PATH && path != hand->path) { - continue; - } if (hand->aimActions.contains(createInfo->action)) { - hand->aimSpace = *space; + if (path && path != hand->path) { + DebugPrint( + "Created space for aim action, but with different subactionPath"); + continue; + } + hand->aimSpaces.emplace(*space); DebugPrint( "Found aim space: {:#016x}", reinterpret_cast(*space)); return XR_SUCCESS; } if (hand->gripActions.contains(createInfo->action)) { + if (path && path != hand->path) { + DebugPrint( + "Created space for grip action, but with different subactionPath"); + continue; + } DebugPrint( "Found grip space: {:#016x}", reinterpret_cast(*space)); - hand->gripSpace = *space; + hand->gripSpaces.emplace(*space); return XR_SUCCESS; } } @@ -780,7 +827,10 @@ XrResult VirtualControllerSink::xrLocateSpace( XrTime time, XrSpaceLocation* location) { for (const ControllerState& hand: {mLeftController, mRightController}) { - if (space != hand.aimSpace && space != hand.gripSpace) { + const bool isAimSpace = hand.aimSpaces.contains(space); + const bool isGripSpace = hand.gripSpaces.contains(space); + if (!(isAimSpace || isGripSpace)) { + TraceLoggingWrite(gTraceProvider, "xrLocateSpace_notAimOrGripSpace"); continue; } @@ -810,7 +860,7 @@ XrResult VirtualControllerSink::xrLocateSpace( : XR_POSEF_IDENTITY; const auto aimPose = hand.aimPose; - if (space == hand.aimSpace) { + if (isAimSpace) { location->pose = aimPose * spacePose; location->locationFlags |= poseValid | poseTracked; TraceLoggingWrite(gTraceProvider, "xrLocateSpace_handAimSpace"); diff --git a/src/APILayer/VirtualControllerSink.h b/src/APILayer/VirtualControllerSink.h index 2ccef02..dcd5f48 100644 --- a/src/APILayer/VirtualControllerSink.h +++ b/src/APILayer/VirtualControllerSink.h @@ -55,6 +55,11 @@ class VirtualControllerSink final { XrInstance instance, const XrInteractionProfileSuggestedBinding* suggestedBindings); + XrResult xrCreateAction( + XrActionSet actionSet, + const XrActionCreateInfo* createInfo, + XrAction* action); + XrResult xrCreateActionSpace( XrSession session, const XrActionSpaceCreateInfo* createInfo, @@ -109,10 +114,10 @@ class VirtualControllerSink final { std::optional savedAimPose {}; bool mUnlockedPosition {false}; XrPosef aimPose {}; - XrSpace aimSpace {}; + std::unordered_set aimSpaces {}; std::unordered_set aimActions {}; - XrSpace gripSpace {}; + std::unordered_set gripSpaces {}; std::unordered_set gripActions {}; // Cosmetic and 'is using controller' @@ -168,6 +173,7 @@ class VirtualControllerSink final { std::string_view ResolvePath(XrPath path); std::unordered_map mPaths; + std::unordered_map> mActionSpaces; void SetControllerActions( XrTime predictedDisplayTime, @@ -187,9 +193,7 @@ class VirtualControllerSink final { const InputState& hand, ControllerState* controller); - // For debugging - std::unordered_map mActionPaths; - std::unordered_map mActionSpaces; + void AddBinding(XrPath, XrAction); }; }// namespace HandTrackedCockpitClicking diff --git a/src/lib/OpenXRNext.h b/src/lib/OpenXRNext.h index 81623cc..655d37f 100644 --- a/src/lib/OpenXRNext.h +++ b/src/lib/OpenXRNext.h @@ -30,29 +30,32 @@ #include "DebugPrint.h" -#define NEXT_OPENXR_FUNCS \ +#define INTERCEPTED_OPENXR_FUNCS \ IT(xrDestroyInstance) \ IT(xrCreateSession) \ IT(xrDestroySession) \ - IT(xrGetInstanceProperties) \ - IT(xrCreateReferenceSpace) \ - IT(xrDestroySpace) \ IT(xrLocateSpace) \ - IT(xrEnumerateInstanceExtensionProperties) \ IT(xrWaitFrame) \ - IT(xrCreateHandTrackerEXT) \ - IT(xrDestroyHandTrackerEXT) \ - IT(xrLocateHandJointsEXT) \ - IT(xrLocateViews) \ IT(xrSuggestInteractionProfileBindings) \ - IT(xrPathToString) \ + IT(xrCreateAction) \ IT(xrCreateActionSpace) \ IT(xrGetActionStateBoolean) \ IT(xrGetActionStateFloat) \ IT(xrGetActionStatePose) \ IT(xrSyncActions) \ IT(xrGetCurrentInteractionProfile) \ - IT(xrPollEvent) \ + IT(xrPollEvent) +#define NEXT_OPENXR_FUNCS \ + INTERCEPTED_OPENXR_FUNCS \ + IT(xrCreateReferenceSpace) \ + IT(xrDestroySpace) \ + IT(xrLocateViews) \ + IT(xrPathToString) \ + IT(xrCreateHandTrackerEXT) \ + IT(xrDestroyHandTrackerEXT) \ + IT(xrLocateHandJointsEXT) \ + IT(xrGetInstanceProperties) \ + IT(xrEnumerateInstanceExtensionProperties) \ IT(xrConvertTimeToWin32PerformanceCounterKHR) \ IT(xrConvertWin32PerformanceCounterToTimeKHR)