Problem
DynamicBuffer::allocate keeps at most 64 retired names per buffer (DYN_KEEP_ELIGIBLE). A constant buffer that a game renames on every draw retires hundreds of names a frame; once the GPU catches up the trim frees all but 64, and the next frame creates them again. In Dark Souls III (~600 draws a frame) that is 15–30k new MTLBuffers a second, ~1000 a frame, 438 bytes on average, each a newBuffer plus a release on the game's own render threads. Over a 26-minute session dynamic recycle reported fresh=30867297, hit-rate=64%. Each call also walks the whole FIFO for per-instance statistics.
Change
Opt-in DXMT_DYN_KEEP=<n>: an instance keeps up to max(64, min(n, DXMT_DYN_KEEP_KB*1024 / length)) retired names (DXMT_DYN_KEEP_KB defaults to 4096, so a 64 KB buffer still keeps 64). In this mode the trim pops GPU-complete entries from the front of the FIFO (which is in non-decreasing will_free_at order) in O(dropped) and skips the per-instance census walk; [trim] and dynamic recycle totals stay exact, [dyn-census] per-instance rows go stale. One [dyn-keep] log line when on. Unset or 0: unchanged.
Tested (iPhone Air, iOS 27.2, Dark Souls III, d3d11.dll built from this change together with #40, #41 and the companion DXMT_CMDLIST_KEEP_BLOCK change)
- With
DXMT_DYN_KEEP=1024:dynamic recyclefresh=585551,hit-rate=99%over 12 minutes (~820 new buffers/s), vsfresh=30867297,hit-rate=64%over 26 minutes (~20k/s) before. Peak FIFO depth 2210 entries. Memory cost:[mem-census] buffer live650–780 MB over the session vs 555–755 MB before; process footprint ~5.4–5.7 GB in both. - Frame rate, heavy scenes (
[gpu-perf]windows with > 300 draws/frame): median 21.5 → 36.7 fps, p10 15.2 → 28.8; after thermal throttling (performance cores at 1.31 GHz) 32–34 fps. 12-minute session, no freezes; the GPU is now ~88 % busy. - The fps numbers are for a combined run: the same session also turned on
DXMT_CMDLIST_KEEP_BLOCK(#42), Madeira'sMADEIRA_THREAD_QOS(willfaust/Madeira#401) and wine'sMADEIRA_TLS_X64/MADEIRA_SYNC_LEAN(willfaust/wine#50, willfaust/wine#51); the DLL also cut one diagnostic log line ([bc-stream], ~50 lines/s) that is not part of this PR. The baseline is the same game and save with my previous settings. The share of the gain from this change alone was not measured. - This split of the change compiles (
-fsyntax-onlywith the arm64ec toolchain); the DLL that ran on the device had both DXMT switches in one commit.
🤖 Generated with Claude Code