Read/write the cross-process diagnostic counters atomically
present_calls, frames_dropped (VideoShare) and frames_rendered (AudioStreamInfo) were plain `+= 1` / stores in the DLL, read by the host cross-process. On an x86 DLL a 64-bit store is two halves, so the x64 host could read a torn value during a carry. Benign (display-only), but a real data race. Use std::atomic_ref at the access sites rather than changing the field types: the structs stay plain POD so the layout/offset asserts are unchanged and AudioStreamInfo stays trivially copyable (it's published/read as a whole struct). The DLL writers (note_present / note_video_dropped / note_audio_frames) and the host readers (IpcServer::video_share / hook_status) now use relaxed atomic_ref; hook_status reloads frames_rendered atomically after the wholesale struct copy. The dev-tool readers (vk_validate, audio_probe) keep plain reads -- diagnostics of diagnostics, and same-bitness in practice. Validated by present_hook_test (present_calls via atomic_ref) and audio_hook_test (frames_rendered) -- also confirms no atomic_ref alignment fault. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -47,10 +47,11 @@ static_assert(sizeof(CoopPadState) == 20, "CoopPadState layout must stay stable
|
||||
// first ("primary"); the rest are surfaced so a multi-stream game is visible.
|
||||
inline constexpr std::uint32_t kMaxAudioStreams = 4;
|
||||
|
||||
// One render stream the hook observed, for the Audio panel's debug view. Plain
|
||||
// POD (no atomics): diagnostics tolerate benign cross-process races like the
|
||||
// other HookStatus counters. frames_rendered is cumulative; the host derives
|
||||
// "live vs idle" from successive deltas.
|
||||
// One render stream the hook observed, for the Audio panel's debug view. Plain POD so it stays
|
||||
// trivially copyable (it's published/read as a whole struct). frames_rendered is cumulative and
|
||||
// updated per audio buffer, so it's read/written via std::atomic_ref at its hot sites to avoid a
|
||||
// torn cross-process read (the other fields change rarely, at stream discovery). The host derives
|
||||
// "live vs idle" from successive frames_rendered deltas.
|
||||
// How confidently the hook knows a render stream's format. A stream that existed before
|
||||
// we injected (the common case) was never seen at Initialize, so its format starts as a
|
||||
// guess (the device mix format) and its true sample rate is measured from the render
|
||||
@@ -191,6 +192,9 @@ struct VideoShare
|
||||
std::uint64_t frames_dropped; // cumulative captures skipped because the shared
|
||||
// keyed mutex was busy (host mid-copy) -- a frame
|
||||
// the game produced that never reached the mirror
|
||||
// present_calls / frames_dropped stay plain uint64_t (POD layout) but are read/written via
|
||||
// std::atomic_ref so the host's cross-process read isn't torn (an x86 DLL stores 64 bits in two
|
||||
// halves). Kept as fields, not std::atomic, only so the layout/offset asserts stay simple.
|
||||
};
|
||||
|
||||
// --- Mouse + keyboard forwarding -------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user