From f15f5cdb36dbc11a877d9fd8b11ac4580d14ebfa Mon Sep 17 00:00:00 2001 From: BlackMark Date: Sun, 21 Jun 2026 20:18:29 +0200 Subject: [PATCH] Deactivate frame-sync when the hooked target isn't alive A game that used the Hooked source with "Sync flip to game frames" left the overlay sluggish (~5 FPS) after it terminated or was detached: with no live game the generation never bumps, so wait_for_hooked_frame waited out its full timeout every iteration. Gate frame_sync_active() on InjectionPanel::target_state() == Alive, so a terminated/hung/detached target falls back to normal vsync. Alive means the process is running and the hook heartbeat is advancing, so this also covers a frozen game (which would stall the generation the same way). The checkbox preference is preserved -- frame-sync auto-resumes when a new game is injected rather than silently unchecking. Co-Authored-By: Claude Opus 4.8 --- host/src/capture_panel.cpp | 9 +++++++++ host/src/capture_panel.hpp | 14 +++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/host/src/capture_panel.cpp b/host/src/capture_panel.cpp index 2615ac0..62f73bb 100644 --- a/host/src/capture_panel.cpp +++ b/host/src/capture_panel.cpp @@ -57,6 +57,15 @@ void plot_multiseries(const char* id, const GraphSeries* series, int n_series, f } } // namespace +bool CapturePanel::frame_sync_active() const +{ + // Only sync to the hook's frames while the target is actually alive and producing + // them. A terminated or hung game stops bumping the generation, so syncing would make + // every iteration wait out the timeout in wait_for_hooked_frame -- fall back to vsync. + const bool hook_alive = injection_ != nullptr && injection_->target_state() == TargetState::Alive; + return frame_sync_ && enabled_ && source_ == Source_Hooked && hook_alive; +} + bool CapturePanel::init(ID3D11Device* device) { device_ = device; diff --git a/host/src/capture_panel.hpp b/host/src/capture_panel.hpp index d0f37f5..03c4591 100644 --- a/host/src/capture_panel.hpp +++ b/host/src/capture_panel.hpp @@ -58,13 +58,13 @@ public: return source_ == Source_Hooked; } - // True when the operator asked to pace the tool's flip to the game's published - // frames (only meaningful with the Hooked source while mirroring). The main loop - // reads this to wait for a new hook frame and present without vsync. - [[nodiscard]] bool frame_sync_active() const - { - return frame_sync_ && enabled_ && source_ == Source_Hooked; - } + // True when the operator asked to pace the tool's flip to the game's published frames + // (only meaningful with the Hooked source while mirroring) AND the target is alive and + // still producing them. The main loop reads this to wait for a new hook frame and + // present without vsync; gating on liveness stops a terminated/hung game from making + // every frame wait out the sync timeout (~5 FPS sluggish overlay). Defined in the .cpp + // since it needs the InjectionPanel definition for target liveness. + [[nodiscard]] bool frame_sync_active() const; private: enum Source : int