Enlarge the synthetic raw-input ring to avoid stale WM_INPUT reads
Each forwarded raw-input event is written to g_raw_slots[head++ % kRawSlots] and a WM_INPUT carrying that slot's ADDRESS is posted to the game, which reads it back through hk_GetRawInputData. With only 64 slots, a burst that queues more than 64 WM_INPUTs before the game pumps could overwrite a slot before the game reads it, so it would decode a newer event for a stale message. No memory unsafety (the address stays in-bounds), but wrong event data under backlog. Grow the ring to 512 (a few tens of KB) so realistic input rates can't lap it. Deliberately not per-slot consume-tracking: that would permanently exhaust slots and silently stop forwarding for a game that ignores WM_INPUT, whereas a large ring always forwards and only risks a rare stale read under extreme backlog. Also drops the "publish() synthetic-input timing" review item: verified it uses GetTickCount64() (thread-safe), not ImGui state -- not a bug, no change needed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -114,12 +114,8 @@ From an in-depth review pass. Each item is fixed test-first (a failing test, the
|
|||||||
as its own commit; "verify" items are confirmed real before any change, and dropped if not.
|
as its own commit; "verify" items are confirmed real before any change, and dropped if not.
|
||||||
|
|
||||||
Correctness (verify, then fix if real):
|
Correctness (verify, then fix if real):
|
||||||
- **`mkb_hook` raw-input slot reuse** — a 64-slot ring can overwrite an event before the game reads
|
|
||||||
the `WM_INPUT`. Verify; fix if real.
|
|
||||||
- **Non-atomic cross-process diagnostic counters** — `present_calls`, `frames_dropped`,
|
- **Non-atomic cross-process diagnostic counters** — `present_calls`, `frames_dropped`,
|
||||||
`frames_rendered` are plain `+=`/stores read by the host. Make them atomic.
|
`frames_rendered` are plain `+=`/stores read by the host. Make them atomic.
|
||||||
- **`publish()` synthetic-input timing** — confirm it doesn't read ImGui/UI state from the input
|
|
||||||
thread; fix if so.
|
|
||||||
|
|
||||||
Performance (failing perf-regression test first):
|
Performance (failing perf-regression test first):
|
||||||
- **D3D9 capture readback off the present thread** — move the swizzle/flip to an off-thread reaper
|
- **D3D9 capture readback off the present thread** — move the swizzle/flip to an off-thread reaper
|
||||||
|
|||||||
@@ -63,7 +63,15 @@ std::atomic<bool> g_di_mouse_primed{false};
|
|||||||
// of a synthetic RAWINPUT slot, and this hook serves that slot's data when the game reads it back.
|
// of a synthetic RAWINPUT slot, and this hook serves that slot's data when the game reads it back.
|
||||||
safetyhook::InlineHook g_hk_getrawinputdata; // user32!GetRawInputData
|
safetyhook::InlineHook g_hk_getrawinputdata; // user32!GetRawInputData
|
||||||
int g_id_rawinput = -1;
|
int g_id_rawinput = -1;
|
||||||
constexpr int kRawSlots = 64; // small ring of synthetic events (games consume WM_INPUT promptly)
|
// Ring of synthetic RAWINPUT events. Each posted WM_INPUT carries the ADDRESS of its slot, and the
|
||||||
|
// game reads it back through hk_GetRawInputData. The slot must not be overwritten between the post and
|
||||||
|
// that read, or the game decodes a newer event for a stale message. A game's message loop drains
|
||||||
|
// WM_INPUT promptly (one per dispatch), so overwrite only happens if more than kRawSlots events queue
|
||||||
|
// up before the game pumps -- e.g. a burst during a stall. We size the ring generously rather than
|
||||||
|
// track per-slot consumption: consumption tracking would permanently exhaust slots (and silently stop
|
||||||
|
// forwarding) for a game that ignores WM_INPUT, whereas a large ring always forwards and only risks a
|
||||||
|
// rare stale read under extreme backlog. ~512 * sizeof(RAWINPUT) is a few tens of KB.
|
||||||
|
constexpr int kRawSlots = 512;
|
||||||
RAWINPUT g_raw_slots[kRawSlots] = {};
|
RAWINPUT g_raw_slots[kRawSlots] = {};
|
||||||
std::atomic<unsigned> g_raw_head{0};
|
std::atomic<unsigned> g_raw_head{0};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user