wasapi: address buffer overflow when using -wasapishared (#758)

## Link to GitHub Issue or related Pull Request, if one exists
Building on #745 

## Description of change
With `-wasapishared`, some users see `AUDCLNT_E_BUFFER_TOO_LARGE`
(`0x88890006`).

The existing one-device-period buffer clamp wasn't enough on endpoints
with a small shared buffer; the shared buffer is double-buffered, so a
full-buffer write often exceeds the free space.

### Fix

Added a FIFO bridge to `SharedRedirect` that decouples the game's
per-event writes from the shared engine's clock:

- **`GetBuffer`** hands the game a pointer into the FIFO tail to write
in place.
- **`ReleaseBuffer`** commits the write and drains `min(pending,
device_free)` frames to the device - so a write can never exceed what
the device accepts, structurally preventing the overflow on any
endpoint.
- **`GetCurrentPadding`** reports the FIFO fill level (capped to the
reported buffer size) so poll/timer-driven games pace correctly against
the virtual buffer.

## Testing
This commit is contained in:
bicarus
2026-06-14 19:57:28 -07:00
committed by GitHub
parent 256ef4d341
commit 4af006e869
6 changed files with 259 additions and 62 deletions
@@ -295,6 +295,13 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::Initialize(
copy_wave_format(&hooks::audio::FORMAT, device_format);
copy_wave_format(&this->device_format, device_format);
// arm the shared-mode buffer bridge so the redirected game's full-buffer writes are paced to
// the device instead of overflowing the shared buffer (AUDCLNT_E_BUFFER_TOO_LARGE).
if (this->shared.redirected_from_exclusive) {
this->shared.enable_bridge(
device_format->nChannels * (device_format->wBitsPerSample / 8));
}
return ret;
}
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetBufferSize(UINT32 *pNumBufferFrames) {
@@ -379,6 +386,12 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetCurrentPadding(UINT32 *pNumPad
*pNumPaddingFrames = this->resample.padding_device_to_game(*pNumPaddingFrames);
}
// shared-mode bridge: the game writes into a FIFO, not the device buffer, so report the FIFO's
// fill level rather than the device's padding (which is in a different buffer space).
if (SUCCEEDED(ret) && this->shared.bridge_enabled() && pNumPaddingFrames) {
*pNumPaddingFrames = this->shared.virtual_padding();
}
CHECK_RESULT(ret);
}
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::IsFormatSupported(