diff --git a/shim/src/xr_input.c b/shim/src/xr_input.c index 37e301d..61f44b0 100644 --- a/shim/src/xr_input.c +++ b/shim/src/xr_input.c @@ -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;