Files
CoopAllTheThings/tests/CMakeLists.txt
BlackMark dfd2be8c17 Add DX12 hooked capture via a D3D11On12 bridge
D3D12 games (e.g. Spider-Man: Miles Morales) fire the Present hook -- the DXGI
swapchain's Present is the same vtable function for D3D11 and D3D12 -- but
GetBuffer(0) as ID3D11Texture2D fails, so the hook used to idle. Now, when the D3D11
GetBuffer fails, present_hook bridges via D3D11On12: it gets the game's ID3D12Device
from the backbuffer, creates its own DIRECT command queue on it (no need to hook the
game's ExecuteCommandLists), builds an ID3D11On12Device, CreateWrappedResource's the
D3D12 backbuffer, and CopyResource's it into the existing shared keyed-mutex texture
-- so the host side is unchanged. The bridge is created lazily and torn down with the
hook.

Verified with a new in-process dx12_present_hook_test: it drives a real D3D12
swapchain (clears a backbuffer, Presents) and asserts present fired, the backbuffer
was bridged into the shared texture, and a second device reads the exact color back
by name -- {51,102,153,255}. Full build x64 + x86 clean (the x86 hook compiles the
D3D12 path too); ctest x64 10/10, x86 3/3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 05:32:15 +02:00

157 lines
5.9 KiB
CMake

# Self-contained verification of the forwarding core (IPC + XInput hook).
# Reuses the hook's xinput_hook.cpp directly so it exercises the shipping code.
add_executable(hook_selftest
hook_selftest.cpp
${CMAKE_SOURCE_DIR}/hook/src/xinput_hook.cpp
${CMAKE_SOURCE_DIR}/hook/src/hook_registry.cpp)
target_include_directories(hook_selftest PRIVATE ${CMAKE_SOURCE_DIR}/hook/src)
target_link_libraries(hook_selftest PRIVATE
coop_common
safetyhook::safetyhook
xinput)
add_test(NAME hook_selftest COMMAND hook_selftest)
# Unit test for the shared audio ring (lock-free SPSC push/pop, wrap-around,
# format handshake, overrun policy). Header-only, no hook or audio device.
add_executable(audio_ring_test audio_ring_test.cpp)
target_link_libraries(audio_ring_test PRIVATE coop_common)
add_test(NAME audio_ring_test COMMAND audio_ring_test)
# Unit test for the MKB event ring (SPSC push/pop, wrap-around, full/empty).
add_executable(mkb_ring_test mkb_ring_test.cpp)
target_link_libraries(mkb_ring_test PRIVATE coop_common)
add_test(NAME mkb_ring_test COMMAND mkb_ring_test)
# Unit test for the host->game mouse coordinate mapping (letterbox inverse +
# decorated-window client offset). Header-only, no device.
add_executable(mkb_map_test mkb_map_test.cpp)
target_include_directories(mkb_map_test PRIVATE ${CMAKE_SOURCE_DIR}/host/src)
add_test(NAME mkb_map_test COMMAND mkb_map_test)
# Integration test for WASAPI process-loopback capture. Reuses the shipping
# capture code and captures from coop_tone (a known sine-wave render process).
add_executable(audio_loopback_test
audio_loopback_test.cpp
${CMAKE_SOURCE_DIR}/host/src/audio/process_loopback_capture.cpp)
target_include_directories(audio_loopback_test PRIVATE ${CMAKE_SOURCE_DIR}/host/src)
# Process loopback needs the Windows 10 20H1 (NTDDI_WIN10_CO) headers.
target_compile_definitions(audio_loopback_test PRIVATE NTDDI_VERSION=0x0A00000B)
target_link_libraries(audio_loopback_test PRIVATE mmdevapi ole32)
add_dependencies(audio_loopback_test coop_tone)
add_test(NAME audio_loopback_test COMMAND audio_loopback_test)
# In-process self-test for the WASAPI render-hook. Reuses the shipping
# audio_hook.cpp and drives a real WASAPI render path in the same process, so it
# exercises COM vtable discovery + GetBuffer/ReleaseBuffer interception with no
# game and no second Steam account (the audio analogue of hook_selftest).
add_executable(audio_hook_test
audio_hook_test.cpp
${CMAKE_SOURCE_DIR}/hook/src/audio_hook.cpp
${CMAKE_SOURCE_DIR}/hook/src/debug_log.cpp
${CMAKE_SOURCE_DIR}/hook/src/hook_registry.cpp)
target_include_directories(audio_hook_test PRIVATE ${CMAKE_SOURCE_DIR}/hook/src)
# IAudioClient3 / process-audio APIs want the Windows 10 20H1 (NTDDI_WIN10_CO) headers.
target_compile_definitions(audio_hook_test PRIVATE NTDDI_VERSION=0x0A00000B)
target_link_libraries(audio_hook_test PRIVATE
coop_common
safetyhook::safetyhook
ole32
mmdevapi)
add_test(NAME audio_hook_test COMMAND audio_hook_test)
# Unit test for the sRGB->UNORM format mapping the hooked video path uses to avoid
# darkening *_SRGB-backbuffer games. Header-only, no device.
add_executable(srgb_format_test srgb_format_test.cpp)
target_include_directories(srgb_format_test PRIVATE ${CMAKE_SOURCE_DIR}/host/src)
add_test(NAME srgb_format_test COMMAND srgb_format_test)
# In-process self-test for the Present-hook video path. Reuses the shipping
# present_hook.cpp and drives a real D3D11 swapchain in the same process, so it
# exercises the IDXGISwapChain::Present inline hook, the shared keyed-mutex
# texture, and the open-by-name / readback contract (the video analogue of
# audio_hook_test). Skips on a machine without a D3D11 device.
add_executable(present_hook_test
present_hook_test.cpp
${CMAKE_SOURCE_DIR}/hook/src/present_hook.cpp
${CMAKE_SOURCE_DIR}/hook/src/debug_log.cpp
${CMAKE_SOURCE_DIR}/hook/src/hook_registry.cpp)
target_include_directories(present_hook_test PRIVATE ${CMAKE_SOURCE_DIR}/hook/src)
target_link_libraries(present_hook_test PRIVATE
coop_common
safetyhook::safetyhook
d3d11
dxgi)
add_test(NAME present_hook_test COMMAND present_hook_test)
# In-process self-test for the D3D12 path of the Present hook: drives a real D3D12
# swapchain through the (shared) IDXGISwapChain::Present vtable and verifies the
# D3D11On12 bridge copies the D3D12 backbuffer into the shared texture. Skips on a
# machine without a D3D12 device.
add_executable(dx12_present_hook_test
dx12_present_hook_test.cpp
${CMAKE_SOURCE_DIR}/hook/src/present_hook.cpp
${CMAKE_SOURCE_DIR}/hook/src/debug_log.cpp
${CMAKE_SOURCE_DIR}/hook/src/hook_registry.cpp)
target_include_directories(dx12_present_hook_test PRIVATE ${CMAKE_SOURCE_DIR}/hook/src)
target_link_libraries(dx12_present_hook_test PRIVATE
coop_common
safetyhook::safetyhook
d3d11
d3d12
dxgi)
add_test(NAME dx12_present_hook_test COMMAND dx12_present_hook_test)
# In-process self-test for the OpenGL capture path. Reuses the shipping
# opengl_hook.cpp and drives a real OpenGL context in the same process, so it
# exercises the SwapBuffers hook, the glReadPixels readback, and the upload into
# the shared texture (opened by name + read back). Skips without GL / D3D11.
add_executable(opengl_hook_test
opengl_hook_test.cpp
${CMAKE_SOURCE_DIR}/hook/src/opengl_hook.cpp
${CMAKE_SOURCE_DIR}/hook/src/debug_log.cpp
${CMAKE_SOURCE_DIR}/hook/src/hook_registry.cpp)
target_include_directories(opengl_hook_test PRIVATE ${CMAKE_SOURCE_DIR}/hook/src)
target_link_libraries(opengl_hook_test PRIVATE
coop_common
safetyhook::safetyhook
d3d11
dxgi
opengl32
gdi32)
add_test(NAME opengl_hook_test COMMAND opengl_hook_test)
# Keep the bin/<config>/ root deployable: stage every test exe under tests/. coop_tone
# (the audio_loopback_test fixture) is staged there too, next to its consumer, so the
# test's "coop_tone.exe alongside me" lookup keeps working.
coop_output_subdir(tests
hook_selftest
audio_ring_test
mkb_ring_test
mkb_map_test
audio_loopback_test
audio_hook_test
srgb_format_test
present_hook_test
dx12_present_hook_test
opengl_hook_test)