Files
CoopAllTheThings/hook/CMakeLists.txt
BlackMark 304857dcf0 Fix Vulkan capture perf collapse: read back off the present thread
Against Sphere Spectacle (144 FPS, runs without Steam) the implicit-layer
capture dropped the game to ~3 FPS. Measured cause (per-stage trace in the
layer): the read-back ran on the game's PRESENT THREAD and spent ~370 ms per
1080p frame -- not the GPU copy (~2 ms) but the CPU swizzle, because the staging
buffer was a plain HOST_VISIBLE|HOST_COHERENT type (write-combined / uncached on
a discrete GPU), where a scattered CPU read runs at PCIe latency. 3 captures/s =
the 3 FPS the user saw.

Test-first: tests/vk_capture_perf_test reproduces the stall as a deterministic
unit test (372 ms/present, ratio 1.0 -> FAIL via `--sync`), then proves the fix
(0.02 ms/present, byte-correct BGRA->RGBA, ratio ~0 -> PASS).

Fix: extract the near-identical read-back from vk_hook.cpp and coop_vk_layer.cpp
into one shared coop::hook::VkCapture that:
  * has the present thread only record + submit the copy (sub-ms) and return;
  * runs a dedicated reaper thread for the fence wait + swizzle + D3D upload, off
    the critical path, with a ring of in-flight slots (game never waits);
  * allocates HOST_CACHED staging (fast CPU read), invalidating when non-coherent;
  * throttles capture to ~150 Hz (a guest stream is <= the host refresh; no point
    mirroring an uncapped 400+ FPS game and burning reaper CPU).

Real-game A/B: present rate now matches the no-capture baseline (605->470 vs
593->405 over the same ramp) with the mirror at ~130 fps -- no measurable impact.

Also adds present-thread overhead guards to the other GPU backends' hook tests
(present_overhead.hpp): DX11 0.05 ms, DX12 0.34 ms, OpenGL 0.09 ms overhead, all
asserted < one 60 Hz frame, so any future synchronous-stall regression fails.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 08:19:47 +02:00

49 lines
1.6 KiB
CMake

add_library(coop_hook SHARED
src/dllmain.cpp
src/xinput_hook.cpp
src/focus_spoof.cpp
src/audio_hook.cpp
src/present_hook.cpp
src/opengl_hook.cpp
src/d3d9_hook.cpp
src/vk_hook.cpp
src/vk_capture.cpp
src/mkb_hook.cpp
src/debug_log.cpp
src/hook_registry.cpp)
target_include_directories(coop_hook PRIVATE src)
# The audio render-hook uses IAudioClient3 / process-audio interfaces, which need
# the Windows 10 20H1 (NTDDI_WIN10_CO) headers.
target_compile_definitions(coop_hook PRIVATE NTDDI_VERSION=0x0A00000B)
# Vulkan headers only (the hook resolves entry points itself, VK_NO_PROTOTYPES). Use the include
# dir directly rather than the third_party `vulkan_headers` target, so the x86 sub-build -- which
# doesn't add third_party/ -- still builds the hook.
coop_require_submodule("Vulkan-Headers" "third_party/Vulkan-Headers/include/vulkan/vulkan.h")
target_include_directories(coop_hook PRIVATE ${CMAKE_SOURCE_DIR}/third_party/Vulkan-Headers/include)
target_link_libraries(coop_hook PRIVATE
coop_common
safetyhook::safetyhook
user32
ole32
mmdevapi
dxguid
d3d11
d3d9
dxgi)
# The x86 sub-build (COOP_X86_HELPER_BUILD) stages alongside the x64 DLL in the
# same bin/<config> dir, so give it a distinct name the host loads for 32-bit games.
if(COOP_X86_HELPER_BUILD)
set_target_properties(coop_hook PROPERTIES OUTPUT_NAME "coop_hook_x86")
else()
set_target_properties(coop_hook PROPERTIES OUTPUT_NAME "coop_hook")
endif()
# TODO(dist): statically link the VC runtime (/MT) so the DLL loads in games on
# machines without the matching VC redist. Deferred until SafetyHook + Zydis are
# also forced to a static CRT to avoid a /MT-vs-/MD mismatch.