Mock game: render every backend UNCAPPED + assert a healthy present rate

The mock backends presented with vsync ("a game-like cadence") -- wrong for a
perf/stress fixture: it does trivial work on an RTX 4090, so it must run as fast
as it can. Vsync capped them to tens of fps (dx9 30, dx10 23, dx11 63, dx12 126),
which hid both capture-induced slowdowns and the hook-removal race. Uncapped now:

  dx9/dx10 INTERVAL_IMMEDIATE / Present(0,0) (BLT), dx11/dx12 ALLOW_TEARING +
  Present(0, ALLOW_TEARING) (flip), gl wglSwapIntervalEXT(0), vk IMMEDIATE/MAILBOX.

Measured no-hook: dx9 ~21000, dx10 ~2800, dx11 ~17000, dx12 ~12000, gl ~26000, vk
~24000 fps.

mock_game_test now adds a present-rate floor per backend (>= 300/s while
capturing): with the hook live every backend stays in the hundreds-thousands
(vk 13500, dx11 9000+, gl 1800, dx9/10 ~1000-1600, dx12 2500). This is the
dimension the frame-advance checks missed -- the Vulkan 144->3 FPS stall still
advanced frames -- so it catches a present-thread stall OR an accidental vsync.

The faster storm exposed the hook-removal UAF fixed in the previous commit.
README roadmap + lessons-learned updated (incl. correcting the old "reset makes
in-flight trampoline calls safe" claim). Full suite 21/21.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 09:45:00 +02:00
parent 958c355126
commit 8a43d2f568
8 changed files with 111 additions and 37 deletions

View File

@@ -220,17 +220,38 @@ VideoShareView read_video_share(const SharedBlock* block)
return v;
}
// Real-world performance guard: with the hook (or layer) capturing, the game must keep rendering
// FAST. This is the dimension the frame-advance checks miss -- the Vulkan read-back stall (144->3
// FPS) still ADVANCED frames, just ~3/s, so every "frames advance" assertion passed while the game
// was unplayable. The mock does trivial work and renders UNCAPPED (no vsync), so on any modern GPU it
// presents at thousands/s (measured no-hook: dx9 ~21000, dx10 ~2800, dx11 ~17000, dx12 ~12000, gl
// ~26000, vk ~24000); the capture costs some of that (e.g. OpenGL's glReadPixels is the heaviest),
// but a healthy backend stays in the hundreds-thousands. A rate that has collapsed below the display
// refresh is either a present-thread stall (the bug) or an accidental vsync -- both regressions we
// want to catch -- so the floor sits above any common refresh (240) and far below the healthy range.
inline constexpr std::uint64_t kMockMinCaptureFps = 300;
void check_capture_present_rate(SharedBlock* block, const char* backend)
{
const std::uint64_t p0 = block->video.present_calls;
Sleep(1000);
const std::uint64_t fps = block->video.present_calls - p0;
std::printf(" %s present rate while capturing = %llu /s\n", backend, static_cast<unsigned long long>(fps));
check(fps >= kMockMinCaptureFps,
"game keeps rendering fast while capturing (no present-thread stall / accidental vsync)");
}
// Capture from the mock game on `backend` and assert the decoded frame numbers form a
// monotonic, advancing sequence.
void test_video_capture(const char* backend, ID3D11Device* device)
{
std::printf("== video capture: %s ==\n", backend);
std::wstring args;
std::wstring wbackend;
for (const char* p = backend; *p != '\0'; ++p) // backend names are ASCII (dx10/dx11/dx12/...)
{
args.push_back(static_cast<wchar_t>(*p));
wbackend.push_back(static_cast<wchar_t>(*p));
}
args += L" 30";
std::wstring args = wbackend + L" 30";
MockGame game = MockGame::launch(args);
if (!game.ok)
{
@@ -299,6 +320,10 @@ void test_video_capture(const char* backend, ID3D11Device* device)
check(!all_same, "captured frames are not stuck on one number");
}
if (game.alive())
{
check_capture_present_rate(block, backend);
}
game.kill();
}
@@ -402,6 +427,10 @@ void test_vk_capture(ID3D11Device* device)
{
check(seq.back() - seq.front() >= 10, "captured vk frame numbers advance");
}
if (alive())
{
check_capture_present_rate(block, "vk (inject)");
}
cleanup();
}
@@ -494,6 +523,10 @@ void test_vk_layer_capture(ID3D11Device* device)
{
check(seq.back() - seq.front() >= 10, "layer-captured frame numbers advance");
}
if (game.alive())
{
check_capture_present_rate(block, "vk (layer)");
}
game.kill();
}