Vulkan capture: preserve the game's sync mode, drop the capture throttle

The capture must never change vsync, and must not frame-limit itself.

- Removed the ~150 Hz capture throttle from VkCapture. It was wrong: vsync already
  paces capture (a 144 Hz FIFO game presents 144x/s, so we mirror 144x/s). The only
  limiter left is ring backpressure (skip a frame if the reaper is behind), which is
  correctness, not a cap, and never touches the game's present thread or sync mode.
- The layer/hook already pass VkSwapchainCreateInfoKHR straight through, so the
  present mode (= the sync mode) is untouched. Added a presentMode log to prove it.

Measured on Sphere Spectacle (direct launch, layer attached): presentMode=2 (FIFO),
steady 144.0 fps, and with the throttle gone the mirror now tracks it at 144/s
(was capped ~130). The earlier 400-600 fps reading was a direct-launch artifact --
a non-foreground windowed FIFO app isn't throttled by DWM -- not the layer, and not
the case through Steam (144). vk_validate now states the mirror follows the present
rate (no throttle) and still asserts a present-rate floor.

Full suite 21/21.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 08:48:44 +02:00
parent d4412ff610
commit 9fee13789d
5 changed files with 30 additions and 35 deletions

View File

@@ -8,17 +8,6 @@
namespace coop::hook
{
namespace
{
double now_ms()
{
LARGE_INTEGER f, c;
QueryPerformanceFrequency(&f);
QueryPerformanceCounter(&c);
return 1000.0 * static_cast<double>(c.QuadPart) / static_cast<double>(f.QuadPart);
}
} // namespace
VkCapture::~VkCapture()
{
shutdown();
@@ -340,11 +329,10 @@ bool VkCapture::present(VkImage image, VkFormat fmt, std::uint32_t w, std::uint3
{
return false;
}
const double t = now_ms();
if (t - m_last_submit_ms < kMinCaptureIntervalMs)
{
return false; // throttle: mirror at ~150 Hz, not the game's (possibly 400+) present rate
}
// No time-based throttle here: capture follows the game's present rate, which vsync paces (if the
// game vsyncs at 144 Hz it presents 144x/s, so we capture 144x/s). The only limiter is ring
// backpressure below -- if the reaper can't keep up we skip a frame rather than block the game --
// which is correctness, not a frame cap (it never stalls the present thread or touches sync mode).
// Pick a slot whose previous capture the reaper has finished. None free -> the reaper is behind,
// so skip this frame (the game keeps its rate; the mirror just drops a frame).
int idx = -1;
@@ -408,7 +396,6 @@ bool VkCapture::present(VkImage image, VkFormat fmt, std::uint32_t w, std::uint3
{
return false;
}
m_last_submit_ms = t;
s.w = w;
s.h = h;
s.fmt = fmt;

View File

@@ -146,11 +146,6 @@ private:
VkCommandPool m_pool = VK_NULL_HANDLE;
Slot m_slots[kSlots];
int m_next = 0;
// Cap capture to ~150 Hz: a guest stream is at most the host's refresh (usually 60, at most 144),
// so mirroring every present of an uncapped 400+ FPS game is pure wasted reaper CPU. Throttling
// here -- not on the game -- keeps the mirror smooth while freeing the cores the game wants.
static constexpr double kMinCaptureIntervalMs = 1000.0 / 150.0;
double m_last_submit_ms = 0.0;
// --- D3D11 shared texture (reaper thread only) ---
ID3D11Device* m_d3d = nullptr;