M2(Vulkan): Vulkan mock-game backend + volk/Vulkan-Headers submodules
Add render_vk.cpp (selectable as `vk`): brings up a real Vulkan instance/device/swap chain via volk (which dlopens vulkan-1.dll -- the loader-bypass case the capture hook must handle) and clears the swap-chain image to the frame-counter colour each frame with vkCmdClearColorImage (no pipeline, no shaders, no SPIR-V) and presents. The whole image encodes the frame number, so it animates and stale frames are detectable. Adds the official Khronos Vulkan-Headers + zeux/volk submodules and a coop_require_submodule() CMake helper that fails with a clear "git submodule update --init --recursive" message rather than auto-cloning. volk is pinned to the project's dynamic CRT (no LNK4098). mock_game_test gets a vk liveness check (its present pointer is cached at init, so late injection can't hook it -- the capture path needs the early-load path, to come). 16/16 ctest, skips cleanly without a Vulkan driver. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -300,6 +300,37 @@ void test_video_capture(const char* backend, ID3D11Device* device)
|
||||
game.kill();
|
||||
}
|
||||
|
||||
// Liveness smoke check for a backend whose capture can't be exercised by late injection
|
||||
// (Vulkan caches its present pointer at init): launch it for a couple of seconds and assert it
|
||||
// comes up and exits cleanly. Exit code 2 = backend unavailable on this machine (e.g. no Vulkan
|
||||
// driver) -> skip without failing.
|
||||
void test_liveness(const char* backend)
|
||||
{
|
||||
std::printf("== liveness: %s ==\n", backend);
|
||||
std::wstring args;
|
||||
for (const char* p = backend; *p != '\0'; ++p)
|
||||
{
|
||||
args.push_back(static_cast<wchar_t>(*p));
|
||||
}
|
||||
args += L" 2"; // run 2s then exit on its own
|
||||
MockGame game = MockGame::launch(args);
|
||||
if (!game.ok)
|
||||
{
|
||||
check(false, "launch coop_mock_game (liveness)");
|
||||
return;
|
||||
}
|
||||
WaitForSingleObject(game.pi.hProcess, 6000); // let the 2s run finish
|
||||
if (!game.alive() && game.exit_code() == 2)
|
||||
{
|
||||
std::printf(" backend '%s' unavailable on this machine -- skipping\n", backend);
|
||||
game.kill();
|
||||
return;
|
||||
}
|
||||
check(!game.alive(), "backend ran and exited within the time limit (no hang)");
|
||||
check(game.exit_code() == 0, "backend came up and exited cleanly");
|
||||
game.kill();
|
||||
}
|
||||
|
||||
// Launch the mock game rendering audio at `rate`/`channels`/`bits`/`fmt`, inject the audio
|
||||
// hook (late, so it's the guessed path), and verify the hook MEASURES the right sample rate
|
||||
// for this variant and captures non-silent audio. (Channels/bit-depth aren't recoverable for
|
||||
@@ -522,6 +553,10 @@ int main()
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Vulkan: its present pointer is cached at init, so late injection can't hook it -- a
|
||||
// liveness check (the mock comes up + presents + exits) is the automated coverage here; the
|
||||
// capture path is exercised via the early-load path documented in the README.
|
||||
test_liveness("vk");
|
||||
test_video_capture("gl", device);
|
||||
test_video_capture("dx9ex", device);
|
||||
test_video_capture("dx9", device);
|
||||
|
||||
Reference in New Issue
Block a user