Skip to content

Commit

Permalink
Fix controller emulation under DCS
Browse files Browse the repository at this point in the history
This also generally improves the way actions and spaces are tracked.

fixes #31 - Fix controller emulation compatibility with DCS's native OpenXR support
refs #29 - Only right hand gestures are working
  • Loading branch information
fredemmott committed Feb 8, 2023
1 parent 831ca5a commit a19d65c
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 132 deletions.
10 changes: 10 additions & 0 deletions src/APILayer/APILayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions src/APILayer/APILayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
69 changes: 16 additions & 53 deletions src/APILayer/APILayer_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -188,60 +198,13 @@ static XrResult xrGetInstanceProcAddr(
PFN_xrVoidFunction* function) {
std::string_view name {name_cstr};

if (name == "xrCreateSession") {
*function = reinterpret_cast<PFN_xrVoidFunction>(&xrCreateSession);
return XR_SUCCESS;
}
if (name == "xrDestroySession") {
*function = reinterpret_cast<PFN_xrVoidFunction>(&xrDestroySession);
return XR_SUCCESS;
}
if (name == "xrDestroyInstance") {
*function = reinterpret_cast<PFN_xrVoidFunction>(&xrDestroyInstance);
return XR_SUCCESS;
}
if (name == "xrWaitFrame") {
*function = reinterpret_cast<PFN_xrVoidFunction>(&xrWaitFrame);
return XR_SUCCESS;
}
if (name == "xrSuggestInteractionProfileBindings") {
*function = reinterpret_cast<PFN_xrVoidFunction>(
&xrSuggestInteractionProfileBindings);
return XR_SUCCESS;
}
if (name == "xrCreateActionSpace") {
*function = reinterpret_cast<PFN_xrVoidFunction>(&xrCreateActionSpace);
return XR_SUCCESS;
}
if (name == "xrGetActionStateBoolean") {
*function = reinterpret_cast<PFN_xrVoidFunction>(&xrGetActionStateBoolean);
return XR_SUCCESS;
}
if (name == "xrGetActionStateFloat") {
*function = reinterpret_cast<PFN_xrVoidFunction>(&xrGetActionStateFloat);
return XR_SUCCESS;
}
if (name == "xrGetActionStatePose") {
*function = reinterpret_cast<PFN_xrVoidFunction>(&xrGetActionStatePose);
return XR_SUCCESS;
}
if (name == "xrLocateSpace") {
*function = reinterpret_cast<PFN_xrVoidFunction>(&xrLocateSpace);
return XR_SUCCESS;
}
if (name == "xrSyncActions") {
*function = reinterpret_cast<PFN_xrVoidFunction>(&xrSyncActions);
return XR_SUCCESS;
}
if (name == "xrPollEvent") {
*function = reinterpret_cast<PFN_xrVoidFunction>(&xrPollEvent);
return XR_SUCCESS;
}
if (name == "xrGetCurrentInteractionProfile") {
*function
= reinterpret_cast<PFN_xrVoidFunction>(&xrGetCurrentInteractionProfile);
return XR_SUCCESS;
#define IT(x) \
if (name == #x) { \
*function = reinterpret_cast<PFN_xrVoidFunction>(&x); \
return XR_SUCCESS; \
}
INTERCEPTED_OPENXR_FUNCS
#undef IT

if (gNext) {
const auto result
Expand Down
176 changes: 113 additions & 63 deletions src/APILayer/VirtualControllerSink.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ static bool WorldLockOrientation() {
case VRControllerPointerSinkWorldLock::OrientationAndSoftPosition:
return true;
}
__assume(false);
}

std::optional<XrPosef> VirtualControllerSink::GetInputPose(
Expand Down Expand Up @@ -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(
Expand All @@ -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<uintptr_t>(*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<uintptr_t>(*space));
hand->gripSpace = *space;
hand->gripSpaces.emplace(*space);
return XR_SUCCESS;
}
}
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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");
Expand Down
14 changes: 9 additions & 5 deletions src/APILayer/VirtualControllerSink.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -109,10 +114,10 @@ class VirtualControllerSink final {
std::optional<XrPosef> savedAimPose {};
bool mUnlockedPosition {false};
XrPosef aimPose {};
XrSpace aimSpace {};
std::unordered_set<XrSpace> aimSpaces {};
std::unordered_set<XrAction> aimActions {};

XrSpace gripSpace {};
std::unordered_set<XrSpace> gripSpaces {};
std::unordered_set<XrAction> gripActions {};

// Cosmetic and 'is using controller'
Expand Down Expand Up @@ -168,6 +173,7 @@ class VirtualControllerSink final {

std::string_view ResolvePath(XrPath path);
std::unordered_map<XrPath, std::string> mPaths;
std::unordered_map<XrAction, std::unordered_set<XrSpace>> mActionSpaces;

void SetControllerActions(
XrTime predictedDisplayTime,
Expand All @@ -187,9 +193,7 @@ class VirtualControllerSink final {
const InputState& hand,
ControllerState* controller);

// For debugging
std::unordered_map<XrAction, std::string> mActionPaths;
std::unordered_map<XrSpace, XrAction> mActionSpaces;
void AddBinding(XrPath, XrAction);
};

}// namespace HandTrackedCockpitClicking
Loading

0 comments on commit a19d65c

Please sign in to comment.