diff --git a/README.md b/README.md index 8a952c4..9771aea 100644 --- a/README.md +++ b/README.md @@ -113,13 +113,6 @@ default** and covers anything the hooked path doesn't. From an in-depth review pass. Each item is fixed test-first (a failing test, then the fix) and lands as its own commit; "verify" items are confirmed real before any change, and dropped if not. -Performance (failing perf-regression test first): -- **D3D9 capture readback off the present thread** — move the swizzle/flip to an off-thread reaper - (the inline-readback pattern that caused the Vulkan 144→3 FPS stall), add a present-thread overhead - guard + dedicated test. -- **OpenGL capture readback off the present thread** — async PBO readback + off-thread swizzle/flip, - with an overhead guard. - Robustness (verify, then fix if real): - **`mkb_forward` sticky mouse-up** — a held button isn't released when mirroring toggles off / ImGui grabs the mouse. Release held buttons. @@ -582,7 +575,13 @@ Non-obvious things that cost time and constrain the design: After moving the read-back to a reaper thread (`coop::hook::VkCapture`), `present_calls` (counted every present) reflects the game's true rate, so the tool now **asserts** it stays healthy while capturing. Lesson: a perf check must assert a bound — if you find yourself explaining why a number - is fine, make the test prove it. + is fine, make the test prove it. **The catastrophe was the *write-combined* memory, not + "synchronous read-back" in general.** D3D9 `GetRenderTargetData` (a `D3DPOOL_SYSTEMMEM` surface) and + OpenGL `glReadPixels` (normal CPU memory) read *cached* memory; measured, their inline read-back + adds ~0.7 ms at 720p (~0.06 ms when it overlaps a busy present at 1080p) — well under one frame, so + they were left on the present thread rather than off-threaded like Vulkan. The present-overhead + guards run at a realistic resolution (not a toy 64×64) so a future write-combined-class regression + still trips the budget. - **The capture must not touch the game's sync mode — and must not throttle itself.** The swapchain's **present mode** *is* the sync mode (`FIFO` = vsync, `IMMEDIATE`/`MAILBOX` = off); `VkCapture` and the layer pass `VkSwapchainCreateInfoKHR` straight through, so whatever the game asked for is what diff --git a/tests/opengl_hook_test.cpp b/tests/opengl_hook_test.cpp index 6fd7c59..c63da96 100644 --- a/tests/opengl_hook_test.cpp +++ b/tests/opengl_hook_test.cpp @@ -48,8 +48,12 @@ void release(T*& p) p = nullptr; } } -constexpr int kW = 64; -constexpr int kH = 64; +// A realistic capture resolution (not a 64x64 toy) so the present-thread overhead guard below is +// meaningful: a future regression that puts a catastrophic synchronous stall back on the present +// thread (the Vulkan write-combined-memory class) shows up here. Measured capture overhead of the +// current cached-memory glReadPixels path stays ~0.06 ms even at 1080p, far under one frame. +constexpr int kW = 1280; +constexpr int kH = 720; bool near_byte(std::uint8_t got, int expected) { return std::abs(static_cast(got) - expected) <= 3;