graphics: take stream captures off the game's present thread to avoid game perf hit (#889)

## Link to GitHub Issue or related Pull Request, if one exists
#0

## Description of change
Capturing a frame for the API stream made the game wait for
`GetRenderTargetData` in the middle of its present, roughly 1270us per
frame at 1080p. A 120Hz cab visibly lost frames for as long as a viewer
was connected.

The present thread now only issues a `StretchRect` into a render target
we own, which is queued rather than waited on, and a pool thread does
the readback and the pixel conversion. That takes the present thread
cost to 1-4us. Each snapshot is read on the request after the one that
took it, so the blit and its transfer have a full frame to land and the
read does not stall on the GPU either, at the cost of one frame of
stream latency.

Only streaming takes this path, and only on a device created with
`D3DCREATE_MULTITHREADED`. Screenshots, `capture.get_jpg` and the
`THREAD_BAN` models keep the existing inline readback unchanged.

Also raises the x264 encoder from `i_threads = 1` to 4, which was
holding a 1080p60 stream to 41fps and making a keyframe cost 12.7ms
against 6.6ms for an ordinary frame. Capped rather than automatic
because this encodes on the same machine it is capturing.

## Testing
tested against iidx33, which was the most sensitive to frame drops
This commit is contained in:
bicarus
2026-08-23 12:24:41 -07:00
committed by GitHub
parent 1cdb7be810
commit 21e7d24ed3
7 changed files with 551 additions and 27 deletions
@@ -634,6 +634,9 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::Reset(
overlay::OVERLAY->reset_invalidate();
}
// Reset refuses to run while any default pool resource is outstanding
d3d9_readback::discard_snapshot_targets(pReal);
HRESULT res = pReal->Reset(pPresentationParameters);
// recreate overlay
@@ -2321,6 +2324,9 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3DDevice9::ResetEx(
overlay::OVERLAY->reset_invalidate();
}
// ResetEx refuses to run while any default pool resource is outstanding
d3d9_readback::discard_snapshot_targets(pReal);
HRESULT res = static_cast<IDirect3DDevice9Ex *>(pReal)->ResetEx(
gfdm_parameters.presentation_parameters,
gfdm_parameters.fullscreen_display_modes);