mirror of
https://github.com/daniel-lynch/ovrplugin-openxr-shim.git
synced 2026-10-06 01:00:05 +02:00
fix(input): derive hand validity every sync; seqlock the input snapshot
lvalid/rvalid were only ever set to 1, so a controller that went off or lost tracking kept reporting a tracked pose frozen at its last position. Validity is now recomputed each xrr_input_sync from xrLocateSpace (success AND both ORIENTATION_VALID and POSITION_VALID), so lost tracking reports untracked. The render thread writes the state while the game thread reads it; publish it through the same cheap seqlock the view snapshot uses so a reader never pairs one frame's pose with another frame's valid flag (or reads a torn pose). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
1d64595266
commit
2088845a52
1 file changed
+64
-19
+64
-19
@@ -21,12 +21,43 @@ static XrAction a_lpose, a_rpose;
|
||||
static XrAction a_lhaptic, a_rhaptic;
|
||||
static XrSpace s_lspace, s_rspace;
|
||||
|
||||
static struct {
|
||||
typedef struct {
|
||||
uint32_t buttons;
|
||||
float ltrig, rtrig, lgrip, rgrip;
|
||||
ovrpVector2f lstick, rstick;
|
||||
XrPosef lpose, rpose; int lvalid, rvalid;
|
||||
} s_in;
|
||||
} InputState;
|
||||
|
||||
/* Published input snapshot. The render thread (xrr_input_sync, inside begin_frame under
|
||||
* g_xrlock — single writer) publishes it; the GAME thread reads it (controller state,
|
||||
* node validity, hand poses). Same SEQLOCK as the view snapshot in xr_runtime.c: even
|
||||
* s_inSeq = stable, odd = writer mid-update; readers retry instead of taking a lock, so a
|
||||
* reader never sees a pose from one frame paired with another frame's valid flag. */
|
||||
static volatile unsigned s_inSeq;
|
||||
static InputState s_in;
|
||||
|
||||
static void input_publish(const InputState *st) {
|
||||
unsigned s = s_inSeq;
|
||||
__atomic_store_n(&s_inSeq, s + 1, __ATOMIC_RELAXED); /* odd: write in progress */
|
||||
__atomic_thread_fence(__ATOMIC_RELEASE); /* odd-mark visible before data */
|
||||
s_in = *st;
|
||||
__atomic_store_n(&s_inSeq, s + 2, __ATOMIC_RELEASE); /* even: stable */
|
||||
}
|
||||
|
||||
static void input_snapshot(InputState *out) {
|
||||
for (;;) {
|
||||
unsigned s1 = __atomic_load_n(&s_inSeq, __ATOMIC_ACQUIRE);
|
||||
if (s1 & 1u) continue; /* writer mid-update -> retry */
|
||||
*out = s_in;
|
||||
__atomic_thread_fence(__ATOMIC_ACQUIRE); /* data reads complete before re-check */
|
||||
if (__atomic_load_n(&s_inSeq, __ATOMIC_RELAXED) == s1) return;
|
||||
}
|
||||
}
|
||||
|
||||
/* A hand pose is usable only if the runtime vouches for BOTH position and orientation
|
||||
* this frame; anything less (controller off, out of tracking) reports untracked so the
|
||||
* game hides the hand instead of freezing it at the last pose. */
|
||||
#define POSE_VALID_BITS (XR_SPACE_LOCATION_ORIENTATION_VALID_BIT | XR_SPACE_LOCATION_POSITION_VALID_BIT)
|
||||
|
||||
static XrAction mk(XrActionType t, const char *n) {
|
||||
XrActionCreateInfo ci = { XR_TYPE_ACTION_CREATE_INFO };
|
||||
@@ -138,6 +169,8 @@ void xrr_input_sync(void) {
|
||||
if (sr != XR_SUCCESS && dbg < 3) { dbg++; XRRLOG("xrSyncActions rc=%d", (int)sr); }
|
||||
if (XR_FAILED(sr)) return;
|
||||
|
||||
InputState st;
|
||||
input_snapshot(&st); /* start from the last published state (keeps last poses) */
|
||||
uint32_t btn = 0;
|
||||
if (bget(a_A)) btn |= OVRP_BTN_A;
|
||||
if (bget(a_B)) btn |= OVRP_BTN_B;
|
||||
@@ -146,20 +179,27 @@ void xrr_input_sync(void) {
|
||||
if (bget(a_menu)) btn |= OVRP_BTN_START;
|
||||
if (bget(a_lstickc)) btn |= OVRP_BTN_LTHUMB;
|
||||
if (bget(a_rstickc)) btn |= OVRP_BTN_RTHUMB;
|
||||
s_in.buttons = btn;
|
||||
s_in.ltrig = fget(a_ltrig); s_in.rtrig = fget(a_rtrig);
|
||||
s_in.lgrip = fget(a_lgrip); s_in.rgrip = fget(a_rgrip);
|
||||
s_in.lstick = v2get(a_lstick); s_in.rstick = v2get(a_rstick);
|
||||
st.buttons = btn;
|
||||
st.ltrig = fget(a_ltrig); st.rtrig = fget(a_rtrig);
|
||||
st.lgrip = fget(a_lgrip); st.rgrip = fget(a_rgrip);
|
||||
st.lstick = v2get(a_lstick); st.rstick = v2get(a_rstick);
|
||||
|
||||
/* validity is re-derived EVERY sync, so lost tracking drops back to untracked */
|
||||
XrSpaceLocation loc = { XR_TYPE_SPACE_LOCATION };
|
||||
XrResult lr = xrLocateSpace(s_lspace, g_xr.appSpace, g_xr.frameState.predictedDisplayTime, &loc);
|
||||
if (lr == XR_SUCCESS && (loc.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT)) {
|
||||
s_in.lpose = loc.pose; s_in.lvalid = 1;
|
||||
}
|
||||
st.lvalid = (lr == XR_SUCCESS && (loc.locationFlags & POSE_VALID_BITS) == POSE_VALID_BITS);
|
||||
if (st.lvalid) st.lpose = loc.pose;
|
||||
XrSpaceLocation rloc = { XR_TYPE_SPACE_LOCATION };
|
||||
XrResult rr = xrLocateSpace(s_rspace, g_xr.appSpace, g_xr.frameState.predictedDisplayTime, &rloc);
|
||||
if (rr == XR_SUCCESS && (rloc.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT)) {
|
||||
s_in.rpose = rloc.pose; s_in.rvalid = 1;
|
||||
st.rvalid = (rr == XR_SUCCESS && (rloc.locationFlags & POSE_VALID_BITS) == POSE_VALID_BITS);
|
||||
if (st.rvalid) st.rpose = rloc.pose;
|
||||
input_publish(&st);
|
||||
{ /* DIAG: log each hand's tracked<->untracked transition (capped) */
|
||||
static int plv = -1, prv = -1, tl = 0;
|
||||
if ((st.lvalid != plv || st.rvalid != prv) && tl++ < 40)
|
||||
XRRLOG("input: hand valid L=%d (flags=0x%x) R=%d (flags=0x%x)",
|
||||
st.lvalid, (unsigned)loc.locationFlags, st.rvalid, (unsigned)rloc.locationFlags);
|
||||
plv = st.lvalid; prv = st.rvalid;
|
||||
}
|
||||
static int pd = 0;
|
||||
if (pd < 4) { pd++;
|
||||
@@ -190,12 +230,13 @@ void xrr_set_vibration(unsigned int mask, float frequency, float amplitude) {
|
||||
|
||||
void xrr_get_controller_state(unsigned int mask, ovrpControllerState4 *out) {
|
||||
(void)mask;
|
||||
InputState st; input_snapshot(&st);
|
||||
memset(out, 0, sizeof(*out));
|
||||
out->ConnectedControllers = 0x01 | 0x02 | 0x80000000u; /* LTouch|RTouch|Active */
|
||||
out->Buttons = s_in.buttons;
|
||||
out->LIndexTrigger = s_in.ltrig; out->RIndexTrigger = s_in.rtrig;
|
||||
out->LHandTrigger = s_in.lgrip; out->RHandTrigger = s_in.rgrip;
|
||||
out->LThumbstick = s_in.lstick; out->RThumbstick = s_in.rstick;
|
||||
out->Buttons = st.buttons;
|
||||
out->LIndexTrigger = st.ltrig; out->RIndexTrigger = st.rtrig;
|
||||
out->LHandTrigger = st.lgrip; out->RHandTrigger = st.rgrip;
|
||||
out->LThumbstick = st.lstick; out->RThumbstick = st.rstick;
|
||||
}
|
||||
|
||||
/* node presence/validity — the game gates pose queries on these. Reporting the
|
||||
@@ -213,16 +254,20 @@ int xrr_node_valid(int node) {
|
||||
case ovrpNode_Head: case ovrpNode_EyeLeft: case ovrpNode_EyeRight:
|
||||
case ovrpNode_EyeCenter:
|
||||
return g_xr.running ? 1 : 0;
|
||||
case ovrpNode_HandLeft: return s_in.lvalid;
|
||||
case ovrpNode_HandRight: return s_in.rvalid;
|
||||
case ovrpNode_HandLeft:
|
||||
case ovrpNode_HandRight: {
|
||||
InputState st; input_snapshot(&st);
|
||||
return node == ovrpNode_HandLeft ? st.lvalid : st.rvalid;
|
||||
}
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int xrr_get_hand_pose(int node, ovrpPoseStatef *out) {
|
||||
InputState st; input_snapshot(&st); /* pose + valid from the SAME sync */
|
||||
const XrPosef *p = NULL;
|
||||
if (node == ovrpNode_HandLeft && s_in.lvalid) p = &s_in.lpose;
|
||||
if (node == ovrpNode_HandRight && s_in.rvalid) p = &s_in.rpose;
|
||||
if (node == ovrpNode_HandLeft && st.lvalid) p = &st.lpose;
|
||||
if (node == ovrpNode_HandRight && st.rvalid) p = &st.rpose;
|
||||
if (!p) return 0;
|
||||
ovrp_pose_from_xr(p, &out->Pose);
|
||||
return 1;
|
||||
|
||||
Reference in new issue
Block a user